Introduction to Ethereum Smart Contract Clients (Web3js Library)
After some discussions around solidity smart contract design and issues in the last two articles (Here and Here), we will take a look at how we can integrate smart contracts with enterprise clients applications. There are multiple technologies that can interact with an ethereum node (ethereum blockchain client).
The basic architecture of the EVM (ethereum virtual machine) that runs smart contracts is that all calls to the contract are executed as a transaction where the ether required for a contract method executed is transferred from the calling account address to the contract account address. The contract code resides on the contract address on the blockchain and expects the calls to come in as transactions carrying the method parameter data along with the transaction as “input”. To enable a standard format for all clients, the method name, and parameters need to be marshaled in a recommended format.
Ethereum standard clients expose an interface for making the RPC (Remote Procedure Call) to contract code deployed on the blockchain. This is called the RPC interface serving as an HTTP post requests at default port of 8545 for the go, c++ and parity client. This can usually be customized as a command parameter or a configuration file property:
The RPC format structured on the popular JSON-RPC format. The format is complex especially for marshaling parameter values based on the types. The recommended encoding scheme has to be followed as defined with the correct padding to ensure the EVM is able to decode them.
To install web3 as a node, browser or meteor package follow the github instruction above. In case you install a node module with -g, it gets installed in the path /usr/lib/node_modules/web3. Please ensure this directory exists with the dependencies in the following structure (This is for Linux OS this is tested with, please refer to your OS specific nodejs documentation for your OS-specific path), this is important for nodejs runtime to find all the dependent libraries.
Nodejs Web3js run
To test your web3 installation with node environment. Please save the following code into a js file and run it through your node runtime. Please make sure you have an ethereum node running on the local machine on port 8545 (or change it to the one you have configured – refer to the earlier article on setting up a private node).
If there is no issue in the install, there will be the following output:
This is printing the array of accounts available with the ethereum node. (Currently, there is just one in this case) .
All the web3 features that we will discuss in the following sections will be available in the node environment as well. From here on, we will concentrate on browser-based execution, if you face any specific issues in your node instance please contact me.
In – Browser Web3js run
Let’s see a simple HTML page that imports web3 js and make a simple contract call.
The web3.js can be imported directly from the lib folder inside the node module installation. It is recommended to use the minified version, but I use the expanded version for testing to be able to debug into the library from within the browser. Please, don’t copy paste the code above without changing your code and add your contract ABI and adding a transaction object to your createCustomer method call.
We should a quick segway to understand contract ABI (Application Binary Interface). A contract ABI specification is a JSON array of the contract method and variable signatures. The ABI defines the encoding needed for each type of parameter as it forms a part of the input for the transaction that triggers a contract method.
As you will find, in the last article where you used the Remix browser app to compile your contract, there was a ABI created by the app. For the data contract, the ABU looked something like this:
As you can see it has the methods from the super contract and the current contract with the signature, parameters and returns types. This needs to passed directly as a json array into the contract (abi) constructor to create a blueprint or class of the contract.
This mechanism ensures that any method can be called directly on the instance of the contract class. The next statement creates an instance of the contract class corecontractContract
Now the contract is tied to the address where the contract code is “installed”. Now we can call methods on the contract.
This call will return a transaction hash of the transaction submitted to the contract.
Along with the method parameter, the call allows for additional non-functional parameters to be passed within the method call.
The transaction object (This is a copy of the transaction object definition in the reference document with some additional comments):
from: String – The address for the sending account. Uses the web3.eth.default Account property, if not specified. The default account property is specified at the initialization phase as web3.eth.defaultAccount = web3.eth.accounts; This indicates we will be using the first account in the accounts array, the gas needed for the transaction is deducted from this account.
to: String – (optional) The destination address of the message, left undefined for a contract-creation transaction. So for our case where we are using the transaction object for smart contract calls, this is not needed.
value: Number|String|BigNumber – (optional) The value transferred for the transaction in Wei, also the endowment if it’s a contract-creation transaction. Since we are not creating a contract (it has already been deployed.
gas: Number|String|BigNumber – (optional, default: To-Be-Determined) The amount of gas to use for the transaction (unused gas is refunded). This is important in our case as we need to pass some gas so out contract update method get the necessary gas to complete execution. The estimated gas per method is available in the remix app, we need to use that value bumped up to whole number so that our transaction doesn’t fail due to “not enough gas” error.
gasPrice: Number|String|BigNumber – (optional, default: To-Be-Determined) The price of gas for this transaction in wei, defaults to the mean network gas price.
data: String – (optional) Either a byte string containing the associated data of the message or in the case of a contract-creation transaction, the initialisation code. This is not needed in our case as ours is not a deployment call.
nonce: Number – (optional) Integer of a nonce. This allows overwriting your own pending transactions that use the same nonce.
Contract constant call Vs. Transaction call
A contract can have two types of calls as implemented in its solidity code. A constant call doesn’t change the contract state, it only reads the blockchain and returns the values it filters out according to its logic. This type of call doesn’t need any ethers to execute, so we don’t need to pass any gas parameter in our contract method call. For e.g.
This will return an array of BigNumbers for uints:
To convert BigNumbers to decimals, use toDecimal(BigNumber) utility method:
This call doesn’t create a transaction on the blockchain.
A Transaction call is intended to update the state of contract, like creating a record on the customer mapping here in the DataContract.
This call will result in a transaction and will consume some gas. Gas can be supplied along with the transaction using the transaction object:
The 20000 here is arbitrary, please use the gas estimated from the method execution in the Remix app. If the gas amount is lower than the expected by the EVM, then you will receive errors:
Uncaught Error: Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: 22680, got: 20000). Try increasing supplied gas.
The events that we generate from the solidity contract can be listened to on the Web3 layer. The library follows a polling mechanism to look for events logged on the blockchain and bubbles it up to the web3 layer if any is generated.
We have created some events related to access control in the ACLContract code. We can listen to the events as they are generated from any client call by subscribing to them using allEvents() API.
This will output events like this:
The above are two events created when createCustomer was called twice. It is possible to receive events from a single subscriber while multiple sources generate the events using the contract calls through JSON-RPC calls. This enables creating a single app for capturing audit data.