I've been doing some raw transaction on contract functions with bare-metal coding to really get what is happening under the hood. I am sharing a code sample with you as this might help people (I would have loved to find such examples). No error handling here, it will fail loud if anything does wrong.
All transactions are signed locally ! My node does not do anything but receiving raw transactions. Notice that no HTTP/IPC providers are defined as I wanted to use the bare JSON-RPC methods.
I am using a dummy smart contract:
pragma solidity ^0.4.18;
contract AdditionContract {
uint public state = 0;
function add(uint value1, uint value2) public {
state = value1 + value2;
}
function getState() public constant returns (uint) {
return state;
}
}
and my python code:
import requests
import json
import web3 # Release 4.0.0-beta.8
import pprint
import time
session = requests.Session()
w3 = web3.Web3()
pp = pprint.PrettyPrinter(indent=2)
requestId = 0
HTTPENDPOINT = 'http://localhost:8501'
PATH_GENESIS = '/home/yourUserName/privateNetworks/geth_PoA/genesis.json'
PATH_SC_TRUFFLE = '/home/yourUserName/Projects/AdditionContract/'
# extracting data from the genesis file
genesisFile = json.load(open(PATH_GENESIS))
CHAINID = genesisFile['config']['chainId']
PERIOD = genesisFile['config']['clique']['period']
GASLIMIT = int(genesisFile['gasLimit'],0)
# compile your smart contract with truffle first
truffleFile = json.load(open(PATH_SC_TRUFFLE + '/build/contracts/AdditionContract.json'))
abi = truffleFile['abi']
bytecode = truffleFile['bytecode']
# Don't share your private key !
myAddress = '0xF464A67CA59606f0fFE159092FF2F474d69FD675' # address funded in genesis file
myPrivateKey = '0x94cb9f766ef067eb229da85213439cf4cbbcd0dc97ede9479be5ee4b7a93b96f'
''' =========================== SOME FUNCTIONS ============================ '''
# see http://www.jsonrpc.org/specification
# and https://github.com/ethereum/wiki/wiki/JSON-RPC
def createJSONRPCRequestObject(_method, _params, _requestId):
return {"jsonrpc":"2.0",
"method":_method,
"params":_params, # must be an array [value1, value2, ..., valueN]
"id":_requestId}, _requestId+1
def postJSONRPCRequestObject(_HTTPEnpoint, _jsonRPCRequestObject):
response = session.post(_HTTPEnpoint,
data=json.dumps(_jsonRPCRequestObject),
headers={'Content-type': 'application/json'})
return json.loads(response.content)
''' ======================= DEPLOY A SMART CONTRACT ======================= '''
### get your nonce
requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionCount', [myAddress, 'latest'], requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
myNonce = w3.toInt(hexstr=responseObject['result'])
print('nonce of address {} is {}'.format(myAddress, myNonce))
### create your transaction
transaction_dict = {'from':myAddress,
'to':'',
'chainId':CHAINID,
'gasPrice':1, # careful with gas price, gas price below the --gasprice option of Geth CLI. I am running my node with --gasprice '1'
'gas':2000000, # rule of thumb / guess work
'nonce':myNonce,
'data':bytecode} # no constrctor in my smart contract so bytecode is enough
### sign the transaction
signed_transaction_dict = w3.eth.account.signTransaction(transaction_dict, myPrivateKey)
params = [signed_transaction_dict.rawTransaction.hex()]
### send the transacton to your node
requestObject, requestId = createJSONRPCRequestObject('eth_sendRawTransaction', params, requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
transactionHash = responseObject['result']
print('contract submission hash {}'.format(transactionHash))
### wait for the transaction to be mined and get the address of the new contract
while(True):
requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionReceipt', [transactionHash], requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
receipt = responseObject['result']
if(receipt is not None):
if(receipt['status'] == '0x1'):
contractAddress = receipt['contractAddress']
print('newly deployed contract at address {}'.format(contractAddress))
else:
pp.pprint(responseObject)
raise ValueError('transacation status is "0x0", failed to deploy contract. Check gas, gasPrice first')
break
time.sleep(PERIOD/10)
''' ================= SEND A TRANSACTION TO SMART CONTRACT ================'''
### get your nonce
requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionCount', [myAddress, 'latest'], requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
myNonce = w3.toInt(hexstr=responseObject['result'])
print('nonce of address {} is {}'.format(myAddress, myNonce))
### prepare the data field of the transaction
# function selector and argument encoding
# https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
value1, value2 = 10, 32 # random numbers here
function = 'add(uint256,uint256)'
methodId = w3.sha3(text=function)[0:4].hex()
param1 = (value1).to_bytes(32, byteorder='big').hex()
param2 = (value2).to_bytes(32, byteorder='big').hex()
data = '0x' + methodId + param1 + param2
transaction_dict = {'from':myAddress,
'to':contractAddress,
'chainId':CHAINID,
'gasPrice':1, # careful with gas price, gas price below the threshold defined in the node config will cause all sorts of issues (tx not bieng broadcasted for example)
'gas':2000000, # rule of thumb / guess work
'nonce':myNonce,
'data':data}
### sign the transaction
signed_transaction_dict = w3.eth.account.signTransaction(transaction_dict, myPrivateKey)
params = [signed_transaction_dict.rawTransaction.hex()]
### send the transacton to your node
print('executing {} with value {},{}'.format(function, value1, value2))
requestObject, requestId = createJSONRPCRequestObject('eth_sendRawTransaction', params, requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
transactionHash = responseObject['result']
print('transaction hash {}'.format(transactionHash))
### wait for the transaction to be mined
while(True):
requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionReceipt', [transactionHash], requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
receipt = responseObject['result']
if(receipt is not None):
if(receipt['status'] == '0x1'):
print('transaction successfully mined')
else:
pp.pprint(responseObject)
raise ValueError('transacation status is "0x0", failed to deploy contract. Check gas, gasPrice first')
break
time.sleep(PERIOD/10)
''' ============= READ YOUR SMART CONTRACT STATE USING GETTER =============='''
# we don't need a nonce since this does not create a transaction but only ask
# our node to read it's local database
### prepare the data field of the transaction
# function selector and argument encoding
# https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
# state is declared as public in the smart contract. This creates a getter function
methodId = w3.sha3(text='state()')[0:4].hex()
data = '0x' + methodId
transaction_dict = {'from':myAddress,
'to':contractAddress,
'chainId':CHAINID,
'data':data}
params = [transaction_dict, 'latest']
requestObject, requestId = createJSONRPCRequestObject('eth_call', params, requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
state = w3.toInt(hexstr=responseObject['result'])
print('using getter for public variables: result is {}'.format(state))
''' ============= READ YOUR SMART CONTRACT STATE GET FUNCTIONS =============='''
# we don't need a nonce since this does not create a transaction but only ask
# our node to read it's local database
### prepare the data field of the transaction
# function selector and argument encoding
# https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
methodId = w3.sha3(text='getState()')[0:4].hex()
data = '0x' + methodId
transaction_dict = {'from':myAddress,
'to':contractAddress,
'chainId':CHAINID,
'data':data}
params = [transaction_dict, 'latest']
requestObject, requestId = createJSONRPCRequestObject('eth_call', params, requestId)
responseObject = postJSONRPCRequestObject(HTTPENDPOINT, requestObject)
state = w3.toInt(hexstr=responseObject['result'])
print('using getState() function: result is {}'.format(state))
terminal prints:
nonce of address 0xF464A67CA59606f0fFE159092FF2F474d69FD675 is 1041
contract submission hash 0xe28b3cb5fb8e9eb774115aa7b4a2f836b00fafcc06e293e1993658b9c890955b
newly deployed contract at address 0xaf52a25033ae5153745efa4880a3e8ba1deac3da
nonce of address 0xF464A67CA59606f0fFE159092FF2F474d69FD675 is 1042
executing add(uint256,uint256) with value 10,32
transaction hash 0xd3e9f5d84ee1e55eb0b37df01a8f12b7c54a54888f16e0960ef2b6194546bc02
transaction successfully mined
using getter for public variables: result is 42
using getState() function: result is 42
geth
orparity
, andropsten
ormainnet
.