5

I'm having some trouble signing a transaction on a contract function. First I build the transaction:

transaction = contract_instance.functions.buy().buildTransaction(
            {
                'nonce': w3.eth.getTransactionCount(w3.eth.accounts[1]) + 1,
                'from': w3.eth.accounts[1],
                'gas': 70000000,
                'gasPrice': 18000000000,
                'value': _value,
                'chainId': 10
            }
        )

Then I decrypt:

key = decrypt_account(address=w3.eth.accounts[1], passphrase='This')

then I sign:

signed = w3.eth.account.signTransaction(transaction, key)

then I send:

w3.eth.sendRawTransaction(signed.rawTransaction)

The problem comes when the function in the contract is called: buy()

function buy() isRunning validAddress payable public {
    uint256 amount = msg.value;
    amount = amount.div(buyPrice);
    amount = amount.mul(10**18);
    _transfer(this, msg.sender, amount);
    Buy(this, msg.sender, amount, buyPrice);
}

The tx_hash response I get is:

AttributeDict({'to': '0x6d3921d65b2127bF42035CF6E39B9c77d44443Eb', 'gasPrice': 21000000000, 'value': 1000000000000000000, 'hash': HexBytes('0xfc6ad0477afd00a5d9b44917db4e42e88437edee6902c0c963061d4453a51d89'), 's': HexBytes('0x7295aa1a2322d96e12ee05464b4502764873ea6167c87ac9641ee358335538cd'), 'from': '0xcE4f50A654c8cd561207725f8F0BF5801c2c9613', 'blockHash': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'), 'r': HexBytes('0xf7040b0bf0cc13c6f469cc623603422031cdc7f1a1c2119f6e0844d12aafcd3c'), 'blockNumber': None, 'transactionIndex': 0, 'nonce': 3, 'gas': 6000000000, 'v': 56, 'input': '0xa6f2ae3a'})

As we can see the block hash looks empty and the blockNumber is None. Thats normally what I get when there was an error.

Now if I dont use the sign method and I just unlock the account:

I get a tx_hash response of:

AttributeDict({'hash': HexBytes('0xfef1bda5fc01e0ee937507cd79f911fc8c96b9819708ce114f353d8ea3ad2495'), 'input': '0xa6f2ae3a', 'gasPrice': 18000000000, 'from': '0xcE4f50A654c8cd561207725f8F0BF5801c2c9613', 'to': '0x6d3921d65b2127bF42035CF6E39B9c77d44443Eb', 'v': 27, 'gas': 700000, 'value': 1000000000000000000, 'nonce': 1, 'transactionIndex': 0, 'r': HexBytes('0x725fa7d26c3abf3e99c684bff8ec45133983635c1440d833ed66728aa67a037d'), 'blockNumber': 40, 's': HexBytes('0x720a5bcad1b8d71cf60031e838aedd32dc2b9b82d40cd52ab682a168ff50fdfa'), 'blockHash': HexBytes('0xa2292df37f6d7348c5cd6057f672a6b7235539f47b68ea0af7ed70f39dca71b9')})

So its without a doubt something with the signing and sending of the transaction.

Just so you can see how I call the transaction if I unlock:

contract_instance.functions.buy().transact({'from': user, 'gas': 700000, 'value': _value})

I'm really stuck on this one. Also, I'm running a test network chain if that matters.

{
"parentHash":  "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0xf2419003e9058198c71e42bb35f94b4452fadfa8",
"extraData": "0x686f727365",
"config": {
  "daoForkBlock": 0,
  "daoForSupport": true,
  "homesteadBlock": 0
},
"timestamp": "0x0",
"mixhash":  "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0xdeadbeefdeadbeef",
"alloc": {
  "0x3d48e3a6a661e5dc56db193e49d484e79c927652": {
    "balance": "1000000000000000000000000000000"
  }
},
"gasLimit": "0x3b62df92707000",
"difficulty": "0x01"
}
  • What kind of node are you using? Can you post the genesis file? Can you post the signed transaction generated by your unlocked account? I'd love to see this resolved with a working chainID. – carver Jan 29 '18 at 19:32
  • I setup a test node with populus. The genesis.json file is posted now. – Dorian Lee Jan 29 '18 at 22:35
  • @carver can you tell me if the chainId will matter when I move to the ropsen of main net? – Dorian Lee Jan 29 '18 at 22:42
  • chainId is used to prevent replay attacks, so it depends on the scenario, but in general the best practice is always to use it. It's possible that the test node doesn't support chain id, but that it would work correctly when connected to a production node and environment like geth or parity, and ropsten or mainnet. – carver Jan 30 '18 at 1:00
2

The problem is that the transaction sets a chainId of 10, but the genesis file does not specify chainId.

A mismatched chainId will cause a failure in your node (which is supposed to happen, that's how the node prevents transaction replays).


If your node does not support chainId in the genesis file (which populus may not), then setting chainId to None in the transaction is the correct solution.

However, if you are using geth, you can set the chainId in the genesis file, like so:

{
"parentHash":  "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0xf2419003e9058198c71e42bb35f94b4452fadfa8",
"extraData": "0x686f727365",
"config": {
  "chainId": 10,
  "daoForkBlock": 0,
  "daoForSupport": true,
  "homesteadBlock": 0
},
"timestamp": "0x0",
"mixhash":  "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0xdeadbeefdeadbeef",
"alloc": {
  "0x3d48e3a6a661e5dc56db193e49d484e79c927652": {
    "balance": "1000000000000000000000000000000"
  }
},
"gasLimit": "0x3b62df92707000",
"difficulty": "0x01"
}
|improve this answer|||||
2

By removing the chainId setting in the genesis.json file, and setting the chainId to None in the transaction creation I was able to solve my problem!

|improve this answer|||||
  • Did you find a way to solve the problem without removing the chainId though? That sounds like a huge tradeoff. – David Ammouial Jan 31 '18 at 12:10
  • I did not. I moved on. – Dorian Lee Feb 3 '18 at 19:31
1

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
|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for?Browse other questions tagged or ask your own question.