Dive into Ethereum Development. Part 2: Web3.js and gas

In the previous article we learned how to deploy contracts from Mist. But Mist is just an application that provides a graphical interface for the main features. It works based on command-line client Geth. Geth is a part of Go-implementation of the Ethereum protocol. Go-implementation is not the only of its kind. One of such widespread implementations is Parity — Rust-implementation. It is worth noting that in Parity you can use another test network — Kovan, that uses Proof-of-Authority, the same algorithm of distribution of block creation as Rinkeby (instead of Proof-of-Work in Ropsten). Besides, instead of Mist a wallet with a web-interface is used in Parity. But right now we will only talk about Go-implementation. Geth is the entry point in Ethereum. To demonstrate how it works we can use the command line it provides. The command line interprets the usual JavaScript. We’ll try to get access to the contract we created using Mist in the previous article. For starters we’ll save the address and the interface of the contact in a file, because we will need them later. Because Mist uses geth, let’s close Mist to avoid conflicts (it’s not true for the Windows-implementation of Mist, which, as it turned out, requires a running geth instance).

“Hello command line!”

Launch geth with the console flag (you can learn how to install geth here):

$ geth console --testnet 2>>geth.log

Flag --testnet connects us to the test network Ropsten. In order to connect to Rinkeby use --rinkeby instead. In addition to that let’s forward messages from the standard output (stderr) of geth into the file geth.log, otherwise they will interfere with the work in the console.

As a result of the command you will see a welcome message. In the beginning let’s find out the balance of our account with the following command (it doesn’t have to be our account — it can be any other, but we have our address: it is displayed in the welcoming message under the name coinbase):

> eth.getBalance(eth.coinbase)

The result example: 22159430784000000000

The number is so big because it’s not ether, but wei — the smallest possible unit of ether, it is also used in code when manipulating ether.

To see the number in ether, like in the Mist wallet, you can add the following conversion:

> web3.fromWei( eth.getBalance(eth.coinbase) )
22.159430784

Let’s open the contract now.

Assign the contract address to the variable:

> var address = “0x65cA73D13a2cc1dB6B92fd04eb4EBE4cEB70c5eC”;

Assign the contract interface to the variable:

> var abi = [ { “constant”: false, “inputs”: [ { “name”: “newString”, “type”: “string” } ], “name”: “setString”, “outputs”: [], “payable”: false, “type”: “function” }, { “constant”: true, “inputs”: [], “name”: “getString”, “outputs”: [ { “name”: “”, “type”: “string”, “value”: “Hello World!” } ], “payable”: false, “type”: “function” } ];

Create the contract object:

> var contract = web3.eth.contract(abi);

This object can be used for opening an already existing contract or for deploying a new one. In this case we need the former, so execute this command:

> var stringHolder = contract.at(address)

Every command returns undefined — don’t pay attention to that. Instead of the address and the interface put your values, and if you are in the same test network as we are (Ropsten), you can use our contract. Let’s call the contract functions:

> stringHolder.getString()
“Hello World!”
> stringHolder.setString(“Hello my baby, hello my honey!”);
Error: invalid address
at web3.js:3879:15
at web3.js:3705:20
at web3.js:4948:28
at map (<native code>)
at web3.js:4947:12
at web3.js:4973:18
at web3.js:4998:23
at web3.js:4061:16
at apply (<native code>)
at web3.js:4147:16

Now we see that the getString method worked correctly, but setString caused an error. In this case the error is caused because all transactions must be performed on behalf of some account that can pay for it in ether. Unblock the account with the following command (you will have to enter the private key password) and call setString again with an additional option that defines which account performs the transaction:

> web3.personal.unlockAccount(eth.coinbase);
Unlock account 0x<your account address>
Passphrase:
true
> stringHolder.setString(“Hello my baby, hello my honey!”, {from: eth.coinbase});
“0x5f9c3a61c79df36776713f7373b902feea802cf6d3903195f8070ff2d376c669”

The hash of the transaction is returned. You can track the transaction by this hash on https://ropsten.etherscan.io for Ropsten and https://rinkeby.etherscan.io for Rinkeby, just by entering the hash in the search, or by this command:

> web3.eth.getTransaction(
“0x5f9c3a61c79df36776713f7373b902feea802cf6d3903195f8070ff2d376c669”);

Don’t forget to use the hash of your transaction instead of ours.

You will see a structure with all the transaction details. Now you can execute getString and see the changed line:

> stringHolder.getString();
“Hello my baby, hello my honey!”

How gas gets spent

In the previous article we already talked about what gas is. But for convenience let’s provide a reminder:

Gas is an abstract unit of measurement necessary for estimation of work for transaction. Gas doesn’t depend on the current cost of ether, so the estimation is independent. When making a transaction you can set how much ether you pay for every unit of gas and the maximum amount of gas you are ready to buy. The more you pay, the higher priority your transaction gets for potential miners. Because paying for gas is paying for miners’ work of making your transaction and including it into the next block. That’s why a miner not only gets a fixed pay for every created block (at the moment of this writing it’s 5 ethers), but also a pay for transactions, usually a few hundredths of ether. The amount of gas for a transaction depends on computational intensity of data operations.

In order to demonstrate how the payment and spending gas work let’s create a new contract. At the same time we will use a different method of compilation and deploying — using a command line.

1. Deploy of demonstrational contract

Let’s take a look at the following contract:

pragma solidity ^0.4.10;
contract UselessWorker {
int public successfullyExecutedIterations = 0;
function doWork(int _iterations) {
successfullyExecutedIterations = _iterations;
for (int i = 0; i < _iterations; i++)
{
keccak256(i);
}
}
}

The contract contains only one function doWork, that takes the number of iterations as an argument int _iterations, and calculates the hash keccak256 from the cycle counter. This way we can give different amounts of work and notice how the gas amount depends on it. The only variable that gets stored in the contract is successfullyExecutedIterations. It is used for saving the number of successful cycles during the last launch. We need it to show what happens in case the gas consumption is too high.

Save the contract text in UselessWorker.sol file. Use solc for compilation — a solidity compiler (you can find the setup manual here):

$ solc --bin --abi UselessWorker.sol

Use --bin and--abi flags to tell the compiler to generate a binary code and an interface.

An output like this should be displayed:

======= UselessWorker.sol:UselessWorker =======
Binary:
606060405260008055341561001357600080fd5b5b60fd806100226000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637ec94a1314604757806397286e7114606d575b600080fd5b3415605157600080fd5b6057608d565b6040518082815260200191505060405180910390f35b3415607757600080fd5b608b60048080359060200190919050506093565b005b60005481565b600081600081905550600090505b8181121560cc5780604051808281526020019150506040518091039050505b808060010191505060a1565b5b50505600a165627a7a72305820b0949297821556e9ed7f4941b7ae793486db6ee48e86486dc58fa3040b224d160029
Contract JSON ABI
[{“constant”:true,”inputs”:[],”name”:”successfullyExecutedIterations”,”outputs”:[{“name”:””,”type”:”int256"}],”payable”:false,”type”:”function”},{“constant”:false,”inputs”:[{“name”:”_iterations”,”type”:”int256"}],”name”:”doWork”,”outputs”:[],”payable”:false,”type”:”function”}]

Launch geth for deploying:

$ geth console — testnet 2>>geth.log

For starters, let’s assign the binary code and the interface to the variable by copying them from the compiler’s output. Don’t forget to add 0x before the binary code:

> var bin = “0x606060405260008055341561001357600080fd5b5b60fd806100226000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680637ec94a1314604757806397286e7114606d575b600080fd5b3415605157600080fd5b6057608d565b6040518082815260200191505060405180910390f35b3415607757600080fd5b608b60048080359060200190919050506093565b005b60005481565b600081600081905550600090505b8181121560cc5780604051808281526020019150506040518091039050505b808060010191505060a1565b5b50505600a165627a7a72305820b0949297821556e9ed7f4941b7ae793486db6ee48e86486dc58fa3040b224d160029”;
> var abi = [{“constant”:true,”inputs”:[],”name”:”successfullyExecutedIterations”,”outputs”:[{“name”:””,”type”:”int256"}],”payable”:false,”type”:”function”},{“constant”:false,”inputs”:[{“name”:”_iterations”,”type”:”int256"}],”name”:”doWork”,”outputs”:[],”payable”:false,”type”:”function”}];

Create a contract object just like when opening an existing one:

> var contract = web3.eth.contract(abi);

Deploy the contract. Deploy, too, is a transaction that must be performed on behalf of a specific account, it consumes gas and needs time. That’s why a contract deploy accepts not only constructor arguments (which are not necessary in this case) and an array of parameters (from, data, gas), but a callback as well (the basic, in this case, displaying an error message or an address contract). But first it’s necessary to unblock the account:

> web3.personal.unlockAccount(eth.coinbase);

After that you can execute the command that sends a transaction from the unlocked account:

> var uselessWorker = contract.new( {from: eth.coinbase, data: bin, gas: 1000000}, function(e, contract) { if (e) { console.log(e); } else { if (contract.address) { console.log (“mined “ + contract.address); } } });

Then you should wait for a response like this:

mined 0xaad3bf6443621f24099ee4f51a22c8d7e9f63548

It means that the contract is deployed, so you can call the functions of this contract:

> uselessWorker.successfullyExecutedIterations();
0

Notice: we didn’t define such a function in the contract. It is created automatically for every public field.

2. Experiments with gas usage

We should start with a rough calculation of gas necessary for the function call. For this we can call the method estimateGas() in the contract method we need. The parameters in estimateGas are the parameters of the method we need. In our case we can call it like this:

> uselessWorker.doWork.estimateGas(1);
41801
> uselessWorker.doWork.estimateGas(2);
41914
> uselessWorker.doWork.estimateGas(3);
42027

Now we see that according to the calculations every cycle will spend 113 of gas. How much is that in ether? To answer that, you need to know the price of gas. If it is not specified during the transaction, you can see the default value (converted into ether):

> web3.fromWei( eth.gasPrice );
1e-7

This means that in this case 1 gas costs 0.0000001 ether by default. This price is not fixed, it changed even when we executed the current commands. That’s why your values will most likely be different. The transaction fee will be the gas price multiplied by the gas amount. Let’s calculate the price of one cycle (this price includes not only the cycle itself, but also some start price for transaction sending):

> web3.fromWei( eth.gasPrice * uselessWorker.doWork.estimateGas(1) );
“0.0041801”

At this price around 10000 cycles should result in a fee of around tenth of one ether. Let’s check:

> web3.fromWei( eth.gasPrice * uselessWorker.doWork.estimateGas(10000) );
“0.1171752”

It’s true. But what if we set not 10000, but 1000000? The result will be as follows:

> web3.fromWei( eth.gasPrice * uselessWorker.doWork.estimateGas(1000000) );
“0.4704624”

Such imbalance is caused by the fact that there is a maximum amount of gas that can be spent. There is a default value, but in this case we can’t check it. Let’s see what happens if we set an explicit gas limit that we are ready to pay. But first, let’s check if the price changed or not:

> web3.fromWei( eth.gasPrice );
2.8e-7

It has become almost three times bigger (in our case it happened in less than 30 minutes). The default price is the market price, so it changes dynamically. In order to keep the value for the price that we used in the beginning, we’ll assign the gas price (in wei) to the variable:

> var fixedGasPrice = 100000000000;

Now let’s calculate the price for 100 thousand cycles (a million may take too long) with our price but with a changed gas limit (gas parameter).

> web3.fromWei( fixedGasPrice * uselessWorker.doWork.estimateGas(100000, {gas: 100000000}) );
“1.1341816”

Now we’ll limit the amount of gas to 100:

> web3.fromWei( fixedGasPrice * uselessWorker.doWork.estimateGas(100000, {gas: 1000}) )
“0.0001”;

In this case all the provided gas is spent. No changes in the blockchain will be saved, but the miner gets his payment anyway, because he was doing the job until the gas ended. That’s why it’s important to set the gas limit correctly. The function estimateGas may not always show the right data, because its execution is based on the current state of blockchain, which can be different while performing the real transaction. It will lead to a different gas consumption.

Now let’s switch to the actual execution of methods and comparing with the predicted values. First, let’s check and save the balance of our account:

> initialBalance = eth.getBalance(eth.coinbase);
5006820644000000000

For example, we want to perform 10 cycles. Let’s calculate how much gas is necessary for this call:

> uselessWorker.doWork.estimateGas( 10 );
42818

For this operation we use our price fixedGasPrice, but the maximum amount of gas will be 42000 (this will most likely not be enough as it is less than the predicted value. So, our payment, considering the consumption of all the provided gas, in wei should be:

> predictedCost = 42000 * fixedGasPrice;
4200000000000000

Which converted in ether is:

> web3.fromWei(predictedCost);
“0.0042”

Let’s execute the transaction with setting our limit and price (but before we will unlock the account)

> web3.personal.unlockAccount(eth.coinbase);
> transaction = uselessWorker.doWork( 10, { from: eth.coinbase, gas: 42000, gasPrice: fixedGasPrice } );
“0xc0590a2cf39c3e4339253ecf11d124177b75502cea368adcf30d1b7d6933ef5a”

You can use the transaction hash to track its status by executing:

> result = web3.eth.getTransactionReceipt(transaction);

The structure is stored into the result, and it looks like this:

{
blockHash: “0x91b63b43856e62fd26ad7f401bfe556cc100e8adf4b5ac510261e91adb9953a3”,
blockNumber: 1375978,
contractAddress: null,
cumulativeGasUsed: 740323,
from: “0x334731990b420d7fe77347545c45a689becfca08”,
gasUsed: 42000,
logs: [],
logsBloom: “0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000”,
root: “0x07dc7178ac2abaa2460cd94d5eb7bf6d7ed9ed09e3e74a4b53321b5c210932c0”,
to: “0xaad3bf6443621f24099ee4f51a22c8d7e9f63548”,
transactionHash: “0xc0590a2cf39c3e4339253ecf11d124177b75502cea368adcf30d1b7d6933ef5a”,
transactionIndex: 1
}

If the result says nil — it means the transaction hasn’t been added to the block yet, you need to wait and repeat the command.

In the structure we can see that the gas consumption gasUsed is 42000, just as expected. Let’s see if the balance has changed:

> web3.fromWei( initialBalance — eth.getBalance(eth.coinbase) );
“0.0042”

The payment was 0.0042 ether, as expected. Let’s see if the data in the contract has changed:

> uselessWorker.successfullyExecutedIterations();
0

Nothing has been saved into the variable despite the fact that the assignment in the contract was made before the cycle. So we see that in case there is not enough gas, the changes are cancelled completely. But the transaction is added to the block anyway, because the work has been done, so we can see the block number and the rest of the information. However, in the information we don’t see any other signs of an error, besides the fact that all provided gas was consumed. How to find out the status of the transaction in a very rare, but still possible, case when all the gas was used, and no more was necessary? Unfortunately, so far there is no easier way to check than to use the website (for example, for our transaction the link was like this: https://ropsten.etherscan.io/tx/0xc0590a2cf39c3e4339253ecf11d124177b75502cea368adcf30d1b7d6933ef5a, you can look for any other transaction and check if it was performed with an error) or simulate the same transaction using debug.

Now let’s see what happens if the limit is a little bit more than predicted. Again let’s save the initial balance:

> initialBalance = eth.getBalance(eth.coinbase);
5002620644000000000

Now let’s set the limit of gas to 43000. Now we can expect the balance to change

> 43000 * fixedGasPrice;
4300000000000000

Perform the transaction with the new limit:

> web3.personal.unlockAccount(eth.coinbase);
> transaction = uselessWorker.doWork( 10, { from: eth.coinbase, gas: 43000, gasPrice: fixedGasPrice } );

And here is the result:

> result = web3.eth.getTransactionReceipt(transaction);
{
blockHash: “0xe9793206faf5e923042488d4312d542db2c5189d25a0014d894179fce222705d”,
blockNumber: 1376028,
contractAddress: null,
cumulativeGasUsed: 131780,
from: “0x334731990b420d7fe77347545c45a689becfca08”,
gasUsed: 42817,
logs: [],
logsBloom: “0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000”,
root: “0x51fd139db608c2fa833e5de205497368362853b8e778787e85180e1cde206151”,
to: “0xaad3bf6443621f24099ee4f51a22c8d7e9f63548”,
transactionHash: “0xd69e46cbb9c139cc2c56ae4860b2e73e4581a97fc016c2848dc6bd399e9b5196”,
transactionIndex: 2
}

42817 of gas was used (1 gas less that predicted)

Ether consumption:

> web3.fromWei( initialBalance — eth.getBalance(eth.coinbase) );
“0.0042817”

Exactly as needed for paying for the used gas.

Have the changes in the contract been saved?

> uselessWorker.successfullyExecutedIterations();
10

Yes, they have.

To be continued

That’s all for now. You can find the geth documentation here.

The solidity documentation.

web3.js library is used in geth, the documentation is here.

This is one of the most popular libraries for Ethereum-blockchain connection. We specialize in development on ruby-on-rails, so our goal is to find a suitable ruby-interface. In the next article we’ll describe our experience.

Like what you read? Give RubyRuby a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.