PolarSPARC |
Using Ganache and Solidity with Python
Bhaskar S | 01/02/2022 |
Overview
Ethereum is a popular decentralized, open source blockchain platform that powers the second largest cryptocurreny called Ether (ETH). In addition, the platform offers a virtual machine environment that allows one to deploy and execute decentralized applications (dApps) called Smart Contracts.
Ganache-CLI is a command-line interface for running a locally hosted Ethereum blockchain that enables one to develop and test Ethereum distributed applications (dApps) in a rapid fasion. It includes support for all the popular json-rpc API calls related to Ethereum and enables one to develop, deploy, and test dApps in a safe and deterministic environment.
Smart Contracts in Ethereum are built using a programing language called Solidity, which is a high-level, contract-oriented, statically typed, and bytecode compiled language.
For this article, we will show how to setup a development environment for building, deploying, and testing Ethereum dApps. For the demonstration, we will use some aspects of the motor vehicle use-case that is described in the introductory article Introduction to Blockchain .
Setup and Installation
The setup will be on a Ubuntu 20.04 LTS based Linux desktop.
Ensure that the desktop has Docker installed and setup. Else, please refer to the article Introduction to Docker for additional help.
In addition, ensure that the Python programming language (version 3.8 or above) is installed.
Assuming that we are logged in as polarsparc and the current working directory is the home directory /home/polarsparc, we will setup a directory structure by executing the following commands in a terminal window:
$ cd $HOME
$ mkdir -p Projects/Ethereum
$ mkdir -p Projects/Ethereum/ganache
$ mkdir -p Projects/Ethereum/solidity
$ mkdir -p Projects/Ethereum/solidity/build
Now, change the current working directory to /home/polarsparc/Projects/Ethereum. In the following sections, we will refer to this location as $ETH_HOME.
At the time of this article, the current stable version of ganache-cli docker image was 6.12.2.
To pull and download the docker image for Ganache-CLI, execute the following command:
$ docker pull trufflesuite/ganache-cli:v6.12.2
The following would be the typical output:
v6.12.2: Pulling from trufflesuite/ganache-cli 0a6724ff3fcd: Pull complete c845cd6699b8: Pull complete d44fb5719f0e: Pull complete a25b4309e732: Pull complete 7a1fff9a31e3: Pull complete Digest: sha256:c062707f17f355872d703cde3de6a12fc45a027ed42857c72514171a5f466ab7 Status: Downloaded newer image for trufflesuite/ganache-cli:v6.12.2 docker.io/trufflesuite/ganache-cli:v6.12.2
Once the download is complete, execute the following command to check the version of ganachae-cli:
$ docker run --rm --name ganache-cli trufflesuite/ganache-cli:v6.12.2 --version
The following would be the typical output:
Ganache CLI v6.12.2 (ganache-core: 2.13.2)
Next, it is time to pull the solidity compiler image. At the time of this article, the current stable version of solidity docker image was 0.8.11.
To pull and download the docker image for Solidity, execute the following command:
$ docker pull ethereum/solc:0.8.11
The following would be the typical output:
0.8.11: Pulling from ethereum/solc dd1163f2b048: Pull complete Digest: sha256:1aa1445646cec83b10445214dbecb9b6fb638b44d70a142059fb1bc984dac3c9 Status: Downloaded newer image for ethereum/solc:0.8.11 docker.io/ethereum/solc:0.8.11
Once the download is complete, execute the following command to check the version of solidity:
$ docker run --rm --name eth-solc ethereum/solc:0.8.11 solc --version
The following would be the typical output:
solc, the solidity compiler commandline interface Version: 0.8.11+commit.d7f03943.Linux.g++
The next step is to download and install the Python module Web3, which is the library for interacting with Ethereum. In order to do that, execute the following command:
$ python -m pip install web3
It is now time to launch ganache-cli. Open a new terminal and execute the following command to start a local Ethereum blockchain environment:
$ docker run --rm --name ganache-cli -u $(id -u $USER):$(id -g $USER) -p 8545:8545 -v $ETH_HOME/ganache:/home/ganache trufflesuite/ganache-cli:v6.12.2 --accounts 4 --chainId 2022 --db /home/ganache --deterministic --networkId 2022
The following would be the typical output:
Ganache CLI v6.12.2 (ganache-core: 2.13.2) (node:1) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead. (Use `node --trace-deprecation ...` to show where the warning was created) Available Accounts ================== (0) 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 (100 ETH) (1) 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0 (100 ETH) (2) 0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b (100 ETH) (3) 0xE11BA2b4D45Eaed5996Cd0823791E0C93114882d (100 ETH) Private Keys ================== (0) 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d (1) 0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1 (2) 0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c (3) 0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913 HD Wallet ================== Mnemonic: myth like bonus scare over problem client lizard pioneer submit female collect Base HD Path: m/44'/60'/0'/0/{account_index} Gas Price ================== 20000000000 Gas Limit ================== 6721975 Call Gas Limit ================== 9007199254740991 Listening on 0.0.0.0:8545
The following are the explanation of the options used:
--accounts :: indicates how many accounts to generate. For our use-case, we need four accounts - bank, buyer, dealer, and dmv. If this option is not specified, by default 10 accounts are generated
--chainId :: specifies the chain Id to use for our blockchain environment. If this option is not specified, the default chain Id is 1337
--db :: indicates the location where the blockchain data is stored
--deterministic :: indicates that our blockchain generate deterministic addresses based on a pre-defined mnemonic. Not using the option causes new addresses being generated on each run
--networkId :: specifies the network Id to use for our blockchain environment
This completes the setup and installation of the required components.
Hands-On with Python
Interacting with Ganache
To make API requests on our local blockchain environment, we will issue the following commands at the Python interpreter prompt:
>>> import json
>>> from web3 import Web3
>>> provider = Web3.HTTPProvider('http://127.0.0.1:8545')
>>> w3 = Web3(provider)
To check if we have successfully connected to our development environment, execute the following command at the Python interpreter prompt:
>>> w3.isConnected()
The following would be the typical output:
True
We generated 4 account addresses when we started ganache-cli. Let us designate the first address for the bank, the second for the buyer, the third for the dealer, and the fourth for the dmv respectively.
To assign the second address to the buyer and the third to the dealer, execute the following commands at the Python interpreter prompt:
>>> buyer = '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'
>>> dealer = '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b'
To verify if an Ethereum account address is valid, execute the following command at the Python interpreter prompt:
>>> w3.isAddress(buyer)
The following would be the typical output:
True
Ethereum provides built-in support for address checksum as a way to indicate if an address is valid and not. To verify if an Ethereum account address is checksummed address, execute the following command at the Python interpreter prompt:
>>> w3.isChecksumAddress(dealer)
The following would be the typical output:
True
To retrieve the details of the genesis block (the very first block with number 0), execute the following commands at the Python interpreter prompt:
>>> genesis_block = json.loads(w3.toJSON(w3.eth.getBlock(0)))
>>> genesis_block
The following would be the typical output:
{'number': 0, 'hash': '0xc8d3c9b510a952012ce679b3bf22794ea75c33cbfec7334206ec5591c66171e0', 'parentHash': '0x0000000000000000000000000000000000000000000000000000000000000000', 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000', 'nonce': '0x0000000000000000', 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'transactionsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 'stateRoot': '0x464221261508910eb2d88e3ae1f918f1c74e7f04442e43f99b50625eacf96762', 'receiptsRoot': '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 'miner': '0x0000000000000000000000000000000000000000', 'difficulty': 0, 'totalDifficulty': 0, 'extraData': '0x', 'size': 1000, 'gasLimit': 6721975, 'gasUsed': 0, 'timestamp': 1641062913, 'transactions': [], 'uncles': []}
To display account balance for the buyer account in ETH, execute the following command at the Python interpreter prompt:
>>> w3.fromWei(w3.eth.getBalance(buyer), 'ether')
The following would be the typical output:
Decimal('100')
We will now send a transaction to transfer 1 ETH from the buyer to the dealer. In order to do this we will need to create a transaction message. Enter the following data at the Python interpreter prompt to create the transaction message structure:
>>> buyer_dealer_txn = {
... 'from': buyer,
... 'to': dealer,
... 'value': w3.toWei(1, 'ether'),
... 'gas': 90000,
... 'gasPrice': 18000000000,
... 'nonce': 0,
... 'chainId': 2022
...}
Before sending the transaction message buyer_dealer_txn, it needs to be digitally signed using the private key of the buyer. To do that, execute the following commands at the Python interpreter prompt:
>>> buyer_priv_key = '0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1'
>>> signed_txn = w3.eth.account.signTransaction(buyer_dealer_txn, buyer_priv_key)
To send the signed transaction to transfer 1 ETH from the buyer to the dealer, execute the following command at the Python interpreter prompt:
>>> txn = w3.eth.sendRawTransaction(signed_txn.rawTransaction)
The executed method will return a transaction hash as a hex string (similar to a transaction id).
To display all the details of the just sent transaction on the local blockchain, execute the following commands at the Python interpreter prompt:
>>> txn_json = json.loads(w3.toJSON(w3.eth.getTransaction(txn)))
>>> txn_json
The following would be the typical output:
{'hash': '0xf23d6fcbe7133997ba0227e5c0e2a84ba2dd058fbbfcda04e6d99d9e5ca2b5b2', 'nonce': 0, 'blockHash': '0xe28e0d3c0546400561055713bd4ea1635e6cc85b6a80b77c5f99ee1603e61088', 'blockNumber': 1, 'transactionIndex': 0, 'from': '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', 'to': '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', 'value': 1000000000000000000, 'gas': 90000, 'gasPrice': 18000000000, 'input': '0x', 'v': 4080, 'r': '0x4b14d8984143f45f7f9163c01300cf62822738c53259e7b8a4894790059ae4fd', 's': '0x30bc70b9bb639ee81a8bdfb881027da8a667eb4913d9a9c2f25607d6502ac52d'}
To display the block number of the latest block in our local blockchain, execute the following command at the Python interpreter prompt:
>>> w3.eth.blockNumber
The following would be the typical output:
1
To retrieve the details of the latest block from our local blockchain, execute the following commands at the Python interpreter prompt:
>>> latest_block = json.loads(w3.toJSON(w3.eth.getBlock('latest')))
>>> latest_block
The following would be the typical output:
{'number': 1, 'hash': '0xe28e0d3c0546400561055713bd4ea1635e6cc85b6a80b77c5f99ee1603e61088', 'parentHash': '0xc8d3c9b510a952012ce679b3bf22794ea75c33cbfec7334206ec5591c66171e0', 'mixHash': '0x0000000000000000000000000000000000000000000000000000000000000000', 'nonce': '0x0000000000000000', 'sha3Uncles': '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'transactionsRoot': '0x74dff4bb9ac27406d5fac268b3d9e9308d71525b43c17727e5e1d96140728f2c', 'stateRoot': '0x9e0c5b5836130734c42601d71c09e883d4e66d41f0dcfc20ea3aebd779c72bfc', 'receiptsRoot': '0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2', 'miner': '0x0000000000000000000000000000000000000000', 'difficulty': 0, 'totalDifficulty': 0, 'extraData': '0x', 'size': 1000, 'gasLimit': 6721975, 'gasUsed': 21000, 'timestamp': 1641072636, 'transactions': ['0xf23d6fcbe7133997ba0227e5c0e2a84ba2dd058fbbfcda04e6d99d9e5ca2b5b2'], 'uncles': []}
To display account balance for the buyer account, execute the following command at the Python interpreter prompt:
>>> w3.fromWei(w3.eth.getBalance(buyer), 'ether')
The following would be the typical output:
Decimal('98.999622')
Similarly, to display account balance for the dealer account, execute the following command at the Python interpreter prompt:
>>> w3.fromWei(w3.eth.getBalance(dealer), 'ether')
The following would be the typical output:
Decimal('101')
As is evident from the Output.14 and Output.15 above, the account balance for the buyer has decreased, while the account balance for the dealer has increased.
EXCELLENT !!! Our local Ethereum blockchain environment is working as expected.
Moving on to writing, deploying, and testing a simple smart contract using Solidity.
Smart Contracts with Solidity
The following is the code for the simple smart contract located in the directory $ETH_HOME/solidity:
// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; contract Vehicle { string _color; string _vin; uint _cost; address _dealer; address _owner; // Will be initiated by the dealer constructor(string memory color, string memory vin, uint cost) { _color = color; _vin = vin; _cost = cost; _dealer = msg.sender; } function getColor() public view returns (string memory) { return _color; } function getVin() public view returns (string memory) { return _vin; } function getCost() public view returns (uint) { return _cost; } function getDealer() public view returns (address) { return _dealer; } function getOwner() public view returns (address) { return _owner; } // Will be initiated by the buyer function buyVehicle(uint amount) public payable { require(msg.sender != _dealer); // Equivalent to checking _owner == null require(_owner == address(0)); require(amount >= _cost); _owner = msg.sender; } }
To compile Vehicle.sol using the docker image for Solidity, execute the following command:
$ docker run --rm --name eth-solc -u $(id -u $USER):$(id -g $USER) -v $HOME/Downloads/DATA/solidity:/home/solc ethereum/solc:0.8.11 -o /home/solc/build --abi --bin --gas /home/solc/Vehicle.sol
The following would be the typical output:
======= home/solc/Vehicle.sol:Vehicle ======= Gas estimation: construction: infinite + 312000 = infinite external: buyVehicle(uint256): 31096 getColor(): infinite getCost(): 2503 getDealer(): 2566 getOwner(): 2522 getVin(): infinite
On successful compilation, there will be two files generated, namely, Vehicle.abi and Vehicle.bin located in the directory $ETH_HOME/solidity/build.
The option --gas passed to the Solidity compiler enables one to get an estimate on the amount of gas required to create/deploy a contract and to interact with the various functions.
The option --abi passed to the Solidity compiler enables the generation of the Vehicle.abi file, which serves as the standard interface that details all of the publicly accessible contract methods.
The following is a pretty version of the contents of the file Vehicle.abi:
[ { "inputs": [ { "internalType": "string", "name": "color", "type": "string" }, { "internalType": "string", "name": "vin", "type": "string" }, { "internalType": "uint256", "name": "cost", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "buyVehicle", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "getColor", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getCost", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getDealer", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getOwner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getVin", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" } ]
The option --bin passed to the Solidity compiler enables the generation of the Vehicle.bin binary file that contains the hex-encoded bytecode for the specified smart contract source file. This bytecode will be executed by the Ethereum Virtual Machine (EVM), which serves as a runtime environment for smart contracts.
The following is a pretty version of the contents of the file Vehicle.bin:
60806040523480156200001157600080fd5b5060405162000a6938038062000a69833981810160405281019062000037919062000342565b82600090805190602001 906200004f929190620000ba565b50816001908051906020019062000068929190620000ba565b508060028190555033600360006101000a81548173ffffffffffff ffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505062000441565b828054620000c890620004 0b565b90600052602060002090601f016020900481019282620000ec576000855562000138565b82601f106200010757805160ff191683800117855562000138565b 8280016001018555821562000138579182015b82811115620001375782518255916020019190600101906200011a565b5b5090506200014791906200014b565b5090 565b5b80821115620001665760008160009055506001016200014c565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b600060 1f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001d382 62000188565b810181811067ffffffffffffffff82111715620001f557620001f462000199565b5b80604052505050565b60006200020a6200016a565b9050620002 188282620001c8565b919050565b600067ffffffffffffffff8211156200023b576200023a62000199565b5b620002468262000188565b9050602081019050919050 565b60005b838110156200027357808201518184015260208101905062000256565b8381111562000283576000848401525b50505050565b6000620002a06200029a 846200021d565b620001fe565b905082815260208101848484011115620002bf57620002be62000183565b5b620002cc84828562000253565b509392505050565b60 0082601f830112620002ec57620002eb6200017e565b5b8151620002fe84826020860162000289565b91505092915050565b6000819050919050565b6200031c8162 000307565b81146200032857600080fd5b50565b6000815190506200033c8162000311565b92915050565b6000806000606084860312156200035e576200035d6200 0174565b5b600084015167ffffffffffffffff8111156200037f576200037e62000179565b5b6200038d86828701620002d4565b935050602084015167ffffffffff ffffff811115620003b157620003b062000179565b5b620003bf86828701620002d4565b9250506040620003d2868287016200032b565b9150509250925092565b7f 4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216806200042457607f 821691505b602082108114156200043b576200043a620003dc565b5b50919050565b61061880620004516000396000f3fe6080604052600436106100555760003560 e01c806336ce09201461005a578063893d20e8146100765780639a86139b146100a15780639cf6d1af146100cc578063bd3e19d4146100f7578063bf20f940146101 22575b600080fd5b610074600480360381019061006f9190610413565b61014d565b005b34801561008257600080fd5b5061008b610256565b604051610098919061 0481565b60405180910390f35b3480156100ad57600080fd5b506100b6610280565b6040516100c39190610535565b60405180910390f35b3480156100d857600080 fd5b506100e1610312565b6040516100ee9190610481565b60405180910390f35b34801561010357600080fd5b5061010c61033c565b604051610119919061056656 5b60405180910390f35b34801561012e57600080fd5b50610137610346565b6040516101449190610535565b60405180910390f35b600360009054906101000a9004 73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff 1614156101a857600080fd5b600073ffffffffffffffffffffffffffffffffffffffff16600460009054906101000a900473ffffffffffffffffffffffffffffffff ffffffff1673ffffffffffffffffffffffffffffffffffffffff161461020357600080fd5b60025481101561021257600080fd5b33600460006101000a81548173ff ffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600060046000905490610100 0a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60606000805461028f906105b0565b80601f016020809104026020016040519081016040 52809291908181526020018280546102bb906105b0565b80156103085780601f106102dd57610100808354040283529160200191610308565b820191906000526020 600020905b8154815290600101906020018083116102eb57829003601f168201915b5050505050905090565b6000600360009054906101000a900473ffffffffffff ffffffffffffffffffffffffffff16905090565b6000600254905090565b606060018054610355906105b0565b80601f016020809104026020016040519081016040 5280929190818152602001828054610381906105b0565b80156103ce5780601f106103a3576101008083540402835291602001916103ce565b820191906000526020 600020905b8154815290600101906020018083116103b157829003601f168201915b5050505050905090565b600080fd5b6000819050919050565b6103f0816103dd 565b81146103fb57600080fd5b50565b60008135905061040d816103e7565b92915050565b600060208284031215610429576104286103d8565b5b60006104378482 85016103fe565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061046b82610440565b9050919050565b 61047b81610460565b82525050565b60006020820190506104966000830184610472565b92915050565b600081519050919050565b60008282526020820190509291 5050565b60005b838110156104d65780820151818401526020810190506104bb565b838111156104e5576000848401525b50505050565b6000601f19601f83011690 50919050565b60006105078261049c565b61051181856104a7565b93506105218185602086016104b8565b61052a816104eb565b840191505092915050565b600060 2082019050818103600083015261054f81846104fc565b905092915050565b610560816103dd565b82525050565b600060208201905061057b600083018461055756 5b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b600060028204905060018216 806105c857607f821691505b602082108114156105dc576105db610581565b5b5091905056fea2646970667358221220b765b0fc979d526f589335db707032a66ce0 84d9e4e91cc021f14f4cbd97a9fc64736f6c634300080b0033
To load the contents of Vehicle.abi into a variable called abi, execute the following commands at the Python interpreter prompt:
>>> f_abi = open('solidity/build/Vehicle.abi', 'r')
>>> abi = json.loads(f_abi.read())
>>> f_abi.close()
Similarly, load the contents of Vehicle.bin into a variable called bin by executing the following commands at the Python interpreter prompt:
>>> f_bin = open('solidity/build/Vehicle.bin', 'r')
>>> bin = json.loads(f_bin.read())
>>> f_bin.close()
For our use-case, the dealer first deploys the contract of a vehicle. For that, we first need to set the default account to the dealer account. In addition, we will set a value of 5 ETHs as the cost of the vehicle. Execute the following commands at the Python interpreter prompt:
>>> cost = w3.toWei(5, 'ether')
>>> w3.eth.defaultAccount = dealer
The second step is to instantiate the smart contract as an object by executing the following command at the Python interpreter prompt:
>>> Vehicle = w3.eth.contract(abi=abi, bytecode=bin)
The third step is to deploy the smart contract to our local Ethereum blockchain environment by executing the following command at the Python interpreter prompt:
>>> txn_hash = Vehicle.constructor('Blue', 'V12345C0001', cost).transact()
The deployment of the smart contract will return a transaction hash (a handle to the transaction on the blockchain).
The fourth step is to wait for the receipt of the deployment of the smart contract into our local Ethereum blockchain environment. This is typically to wait for the transaction to be processed by a miner. In our local environment, there is no mining involved and all the transactions are processed immediately . In order to retrieve the transaction receipt, execute the following command at the Python interpreter prompt:
>>> txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
To display the transaction receipt details for the just deployed smart contract, execute the following commands at the Python interpreter prompt:
>>> txn_receipt_json = json.loads(w3.toJSON(txn_receipt))
>>> txn_receipt_json
The following would be the typical output:
{'transactionHash': '0xec3e32026fd4e2953e958d2bebd1a930847388f60730df46d36b998deb81972a', 'transactionIndex': 0, 'blockHash': '0xc9dc2a85857edfd567ba385aa38cc894a8fedfd7fefd75e1be36713e6ae8059e', 'blockNumber': 2, 'from': '0x22d491Bde2303f2f43325b2108D26f1eAbA1e32b', 'to': None, 'gasUsed': 469460, 'cumulativeGasUsed': 469460, 'contractAddress': '0x17e91224c30c5b0B13ba2ef1E84FE880Cb902352', 'logs': [], 'status': 1, 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'}
Now that we have successfully deployed the smart contract into our local Ethereum blockchain environment, we need to get the instance of that contract in order to interact with it.
To get the instance of the smart contract from our local blockchain environment into a variable called vehicle_contract, execute the following command at the Python interpreter prompt:
>>> vehicle_contract = w3.eth.contract(address=txn_receipt.contractAddress, abi=abi)
To invoke the method getVin() on the contract instance, execute the following command at the Python interpreter prompt:
>>> vehicle_contract.functions.getVin().call()
The following would be the typical output:
'V12345C0001'
Similarly, to invoke the method getOwner() on the contract instance, execute the following command at the Python interpreter prompt:
>>> vehicle_contract.functions.getOwner().call()
The following would be the typical output:
'0x0000000000000000000000000000000000000000'
The zero (0) address in Output.19 above indicates the vehicle is not yet purchased by any buyer.
Now for the exciting part - the buyer will transact with the smart contract deployed by the dealer to buy the vehicle. In order to do this we will need to create a transaction message. Enter the following data at the Python interpreter prompt to create the transaction message structure for buying the vehicle:
>>> gas_price = 1000000
>>> buyer_txn = { 'from': buyer, 'value': cost, 'gas': gas_price }
The buyer will use the transaction message buyer_txn to transact with the smart contract to buy the vehicle by executing the following command at the Python interpreter prompt:
>>> buyer_txn_hash = vehicle_contract.functions.buyVehicle(cost).transact(buyer_txn)
The interaction with the smart contract (by the buyer) will return a transaction hash.
To wait for the receipt of the transaction related to the interaction with the smart contract, execute the following command at the Python interpreter prompt:
>>> buyer_txn_receipt = w3.eth.waitForTransactionReceipt(buyer_txn_hash)
To display the transaction receipt details related to the interaction with the smart contract, execute the following commands at the Python interpreter prompt:
>>> buyer_txn_receipt = w3.eth.waitForTransactionReceipt(buyer_txn_hash)
>>> buyer_txn_receipt
The following would be the typical output:
{'transactionHash': '0x7699d01a50120c8ca7114c0fa955ccb3ec98eeb7d335619af3ead2e906c945bc', 'transactionIndex': 0, 'blockHash': '0x782646c2d79341f338b85893b4ca46da7be18e940dbd30ecf32abcaf5ef4eded', 'blockNumber': 3, 'from': '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0', 'to': '0x17e91224c30c5b0B13ba2ef1E84FE880Cb902352', 'gasUsed': 45060, 'cumulativeGasUsed': 45060, 'contractAddress': None, 'logs': [], 'status': 1, 'logsBloom': '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'}
Now invoke the method getOwner() on the contract instance, execute the following command at the Python interpreter prompt:
>>> vehicle_contract.functions.getOwner().call()
The following would be the typical output:
'0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0'
The presence of the buyer's address in Output.21 above indicates the vehicle has been successfully purchased by the buyer.
To display account balance for the buyer account, execute the following command at the Python interpreter prompt:
>>> w3.fromWei(w3.eth.getBalance(buyer), 'ether')
The following would be the typical output:
Decimal('93.9987208')
As is evident from the Output.22 above, the account balance for the buyer has decreased by the cost of the vehicle and some gas.
BINGO !!! We have successfully built, deployed, and transacted with a smart contract in our local Ethereum blockchain environment.
References