[Ethereum] How to call a contract method using the eth_call JSON-RPC API

abicalldatacontract-invocationgo-ethereumjson-rpc

Ok, so I'm trying to call a contract method using the methods provided by the Ethereum JSON RPC interface. The JSON RPC is running on an Ubuntu machine. Unfortunately, I can't really get a result back from the test contract I created.

The first thing I did was start Go Ethereum on the Testnet with:

geth --rpc --testnet

I then open another console and open the JS console with

geth attach

I then compiled my contract using Browser Solidity, where my code is:

 contract test { function double(int a) returns(int) {   return 2*a;   } }

Which gives me the following byte code:

606060405260728060106000396000f360606040526000357c0100000000000000000000000000000000000000000000000000000000900480636ffa1caa146037576035565b005b604b60048080359060200190919050506061565b6040518082815260200191505060405180910390f35b6000816002029050606d565b91905056

Then I created the contract with:

curl localhost:8545 -X POST --data '{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from": "0x8aff0a12f3e8d55cc718d36f84e002c335df2f4a", "data": "606060405260728060106000396000f360606040526000357c0100000000000000000000000000000000000000000000000000000000900480636ffa1caa146037576035565b005b604b60048080359060200190919050506061565b6040518082815260200191505060405180910390f35b6000816002029050606d565b91905056"}],"id":1}

Which gives me back the transaction hash, which I use to get the contract address:

curl localhost:8545 -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x8290c22bd9b4d61bc57222698799edd7bbc8df5214be44e239a95f679249c59c"],"id":1}'

Which gives me back the following:

{"id":1,"jsonrpc":"2.0","result":{"transactionHash":"0x8290c22bd9b4d61bc57222698799edd7bbc8df5214be44e239a95f679249c59c","transactionIndex":"0x0","blockNumber":"0xd32da","blockHash":"0xf13f185f0eb1e4797885400e3b371c972eedebcf3eef27815a45b649283ec669","cumulativeGasUsed":"0x14293","gasUsed":"0x14293","contractAddress":"0x5c7687810ce3eae6cda44d0e6c896245cd4f97c6","logs":[]}}

I then wanted to call the method "double" with the number "5". The Contract ABI documentation says that you have to take the first 4 bytes of the Keccak hash.
The method signature is

double(int)

Which gives the hash with web3.sha3("double(int)":

6740d36c7d42fba12b8eb3b047193c9761224c267d7a2e96dc50949860652317

The first four bytes, prefixed with "0x" are:

0x6740d36c

The documentation then tells to take the parameter, encode it in hex and pad it left to 32 bytes. Which would be the following, using the number "5":

0x0000000000000000000000000000000000000000000000000000000000000005

In total, the encoded string should look like this:

0x6740d36c0000000000000000000000000000000000000000000000000000000000000005

So, using this to call the method:

 curl localhost:8545 -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from": "0x8aff0a12f3e8d55cc718d36f84e002c335df2f4a", "to": "0x5c7687810ce3eae6cda44d0e6c896245cd4f97c6", "data": "0x6740d36c0000000000000000000000000000000000000000000000000000000000000005"}, "latest"],"id":1}'

Which gives me this result:

{"id":1,"jsonrpc":"2.0","result":"0x"}

Am I doing something wrong here or forgetting an important step?

Also, are there any way to access already-existing methods within the Go Ethereum library that would free me from having to implement for example methods that to the padding myself?

Thank your very much in advance!

Best Answer

Summary

  • Your original contract code would not work correctly until I added a constant to the function definition to indicate that this function does not modify the blockchain.

  • I had to use the method signature of double(int256) instead of double(int) to get the eth_call JSON-RPC to work.



The Details

Run Your geth Instance

You can use the Testnet blockchain that is synchronised with other peers over the Internet, or you can use a private Dev blockchain that is only available on your computer and faster to develop with. The --rpc parameter will allow you to use curl to communicate with your geth instance.

Testnet Blockchain

You will need to create an account and mine some coins as you need the coins to insert your code into the blockchain or to send transactions to the blockchain:

geth --testnet account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat Passphrase: 
Address: {aaaa725f2f36a28c01bc47f883534990c9c8bbbb}

Your geth instance will have to synchronise your Testnet blockchain with other peers on the Internet. This may take and hour or more. Once synchronised, your mining operation will commence and you should mine some coins in 10 or 20 minutes. Start geth with the mining parameters:

geth --testnet --rpc --mine --minerthreads 1 console

To check if you have coins, run the command:

> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether");
25

If you want to exit from geth, press Control-D.

If you want to, you can now run your geth instance without the mining parameters as another geth instance somewhere on the Internet will mine your transactions. Use the following command to remove the mining option, or just use the previous geth command:

geth --testnet --rpc console

Dev Blockchain

You will need to create an account and mine some coins:

geth --dev account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat Passphrase: 
Address: {cccc725f2f36a28c01bc47f883534990c9c8dddd}

Start geth with the --miner and --minerthreads parameters as your geth instance will need to mine your private blockchain:

geth --dev --rpc --mine --minerthreads 1 console


Flatten Your Code And Assign To A Variable

I had to modify your code to get it to run correctly by adding the word constant to your function definition as this function does not modify the blockchain when it is run.

In your example, your code will normally be formatted for easy reading:

contract Test { 
    function double(int a) constant returns(int) {
        return 2*a;
    } 
}

You can use a service like Line Break Removal Tool to strip out your line breaks, or see How to load Solidity source file into geth for some alternatives.

Your flattened code will look like the example you posted:

contract Test { function double(int a) constant returns(int) { return 2*a; } }

You will need to assign your code to a string variable:

var testSource='contract Test {  function double(int a) constant returns(int) { return 2*a; } }'

Note that // comments will cause some problems when your code if flattened as all text after the // comment will be treated as a comment. Use /* ... */ instead if you need to add comments to your code.


Compile Your Code

In your geth instance, type in your flattened code with the assignment to the variable. Don't worry about the undefined result as this is a normal response from geth:

> var testSource='contract Test {  function double(int a) constant returns(int) { return 2*a; } }'
undefined

Compile your code:

> var testCompiled = web3.eth.compile.solidity(testSource);
I0503 09:04:15.907715    3190 solidity.go:114] solc, the solidity compiler commandline interface
Version: 0.3.2-0/Release-Linux/g++/Interpreter

path: /usr/bin/solc
undefined

If you want to see the binary form of your code:

testCompiled
{
  Test: {
    code: "0x6060604052602a8060106000396000f3606060405260e060020a60003504636ffa1caa8114601a575b005b6002600435026060908152602090f3",
    info: {
      abiDefinition: [{...}],
      compilerOptions: "--bin --abi --userdoc --devdoc --add-std --optimize -o /tmp/solc497335011",
      compilerVersion: "0.3.2",
      developerDoc: {
        methods: {}
      },
      language: "Solidity",
      languageVersion: "0.3.2",
      source: "contract Test {  function double(int a) constant returns(int) { return 2*a; } }",
      userDoc: {
        methods: {}
      }
    }
  }
}

You will need the Application Binary Interface (ABI) signature for your contract if you want to access run your function outside this geth session:

> testCompiled.Test.info.abiDefinition
[{
    constant: true,
    inputs: [{
        name: "a",
        type: "int256"
    }],
    name: "double",
    outputs: [{
        name: "",
        type: "int256"
    }],
    type: "function"
}]

Note: This is the second hint as to why your JSON RPC call did not work - the method signature has int256 for the a parameter instead of int.


Insert Your Code Into The Blockchain

Run the following commands to insert your code into the blockchain:

> var testContract = web3.eth.contract(testCompiled.Test.info.abiDefinition);
undefined
> var test = testContract.new({
    from:web3.eth.accounts[0], 
    data: testCompiled.Test.code, gas: 2000000}, 
    function(e, contract) {
      if (!e) {
        if (!contract.address) {
          console.log("Contract transaction send: TransactionHash: " +
            contract.transactionHash + " waiting to be mined...");
        } else {
          console.log("Contract mined! Address: " + contract.address);
          console.log(contract);
        }
    }
})
I0503 09:09:39.632499    3190 xeth.go:1026] Tx(0x8c15e8b8bb593d4a680b084a455ba82b103e60204638a3674ef1fb90ca0ad4f0) created: 0x7a2d8fa11aa28097135eb514f6a23ba12501191e
Contract transaction send: TransactionHash: 0x8c15e8b8bb593d4a680b084a455ba82b103e60204638a3674ef1fb90ca0ad4f0 waiting to be mined...
undefined

Wait up to a few minutes and you will see the following output:

I0503 09:10:34.319772    3190 worker.go:569] commit new work on block 11122 with 0 txs & 0 uncles. Took 431.081µs
Contract mined! Address: 0x7a2d8fa11aa28097135eb514f6a23ba12501191e
[object Object]

You should save the address if you want to run this contract from outside your geth session.


Use Your Contract Within The Same geth Session

Before adding the constant to the double function, I was getting the following results:

> test.double(5)
invalid address

Note: This is the first hint as to why your JSON RPC call failed.

To use your contract within your geth session, AFTER adding constant to the double function:

> test.double(5)
10
> test.double(55)
110


Use Your Contract Using curl And JSON-RPC

And now we get to the tricky part.

Your function double(int) is really double(int256). From Solidity - Types:

int• / uint•: Signed and unsigned integers of various sizes. Keywords uint8 to uint256 in steps of 8 (unsigned of 8 up to 256 bits) and int8 to int256. uint and int are aliases for uint256 and int256, respectively.

In geth, we run the following command to find the signature of the double(...) function:

> web3.sha3('double(int256)')
"6ffa1caacdbca40c71e3787a33872771f2864c218eaf6f1b2f862d9323ba1640"

And take the first 4 bytest to create the data parameter

0x6ffa1caa0000000000000000000000000000000000000000000000000000000000000005

And using the address from the "Contract mined! parameter", we build the following curl command:

curl localhost:8545 -X POST --data '{"jsonrpc":"2.0", "method":"eth_call", "params":[{"from": "eth.accounts[0]", "to": "0x65da172d668fbaeb1f60e206204c2327400665fd", "data": "0x6ffa1caa0000000000000000000000000000000000000000000000000000000000000005"}], "id":1}'

And run this command from the command line:

user@Kumquat:~$ curl localhost:8545 -X POST --data '{"jsonrpc":"2.0", "method":"eth_call", "params":[{"from": "eth.accounts[0]", "to": "0x65da172d668fbaeb1f60e206204c2327400665fd", "data": "0x6ffa1caa0000000000000000000000000000000000000000000000000000000000000005"}], "id":1}'
{"id":1,"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000000000000000000000000000a"}

The result of 0x0000...000a is the value 10.


Update Oct 18 2016

As documented in RPC error "invalid or missing value for params" when calling contract constant void functions, you may have to add a block parameter to the params list. Your command with the block parameter will be:

user@Kumquat:~$ curl localhost:8545 -X POST --data '{"jsonrpc":"2.0", "method":"eth_call", "params":[{"from": "eth.accounts[0]", "to": "0x65da172d668fbaeb1f60e206204c2327400665fd", "data": "0x6ffa1caa0000000000000000000000000000000000000000000000000000000000000005"}, "latest"], "id":1}'
{"id":1,"jsonrpc":"2.0","result":"0x000000000000000000000000000000000000000000000000000000000000000a"}

You may also have to leave out the from parameter.

References:


Your Other Question

Also, are there any way to access already-existing methods within the Go Ethereum library that would free me from having to implement for example methods that to the padding myself?

Could you please ask this in another question as this answer is already too long.

Related Topic