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 ap

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.

JSON-RPC

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:

-- rpc -- rpcaddr < ip > -- rpcport < portnumber >

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.

Due to the low-level complexity of the JSON_RPC format, there are a number of wrapper libraries available that simplify the call format. The oldest one is a JavaScript wrapper web3.js that works in the browser and a nodejs instance. This discussion doesn’t aim to replace the reference document for all the API definitions, please refer to the above link for specific API calls. Here we will use a specific set of the APIs to create the recommendation for implementing a browser based client for the smart contract.

Install web3.js

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.

/ usr / lib / node_modules / web3 ├── bower . json ├── bundled . js ├── circle . yml ├── coder . js ├── dist │ ├── web3 . js │ ├── web3 . js . map │ ├── web3 - light . js │ ├── web3 - light . min . js │ └── web3 . min . js ├── example │ ├── balance . html │ ├── contract_array . html │ ├── contract . html │ ├── event_inc . html │ ├── icap . html │ ├── namereg . html │ └── node - app . js ├── gulpfile . js ├── index . js ├── lib │ ├── contracts │ │ ├── GlobalRegistrar . json │ │ ├── ICAPRegistrar . json │ │ └── SmartExchange . json │ ├── solidity │ │ ├── address . js │ │ ├── bool . js │ │ ├── bytes . js │ │ ├── coder . js │ │ ├── dynamicbytes . js │ │ ├── formatters . js │ │ ├── int . js │ │ ├── param . js │ │ ├── real . js │ │ ├── string . js │ │ ├── type . js │ │ ├── uint . js │ │ └── ureal . js │ ├── utils │ │ ├── browser - bn . js │ │ ├── browser - xhr . js │ │ ├── config . js │ │ ├── sha3 . js │ │ └── utils . js │ ├── version . json │ ├── web3 │ │ ├── allevents . js │ │ ├── batch . js │ │ ├── contract . js │ │ ├── errors . js │ │ ├── event . js │ │ ├── extend . js │ │ ├── filter . js │ │ ├── formatters . js │ │ ├── function . js │ │ ├── httpprovider . js │ │ ├── iban . js │ │ ├── ipcprovider . js │ │ ├── jsonrpc . js │ │ ├── method . js │ │ ├── methods │ │ │ ├── db . js │ │ │ ├── eth . js │ │ │ ├── net . js │ │ │ ├── personal . js │ │ │ ├── shh . js │ │ │ ├── swarm . js │ │ │ └── watches . js │ │ ├── namereg . js │ │ ├── property . js │ │ ├── requestmanager . js │ │ ├── settings . js │ │ ├── syncing . js │ │ └── transfer . js │ └── web3 . js ├── LICENSE ├── node_modules │ ├── bignumber . js │ │ ├── bignumber . js │ │ ├── bignumber . js . map │ │ ├── bignumber . min . js │ │ ├── bower . json │ │ ├── doc │ │ │ └── API . html │ │ ├── LICENCE │ │ ├── package . json │ │ └── README . md │ ├── crypto - js │ │ ├── aes . js │ │ ├── bower . json │ │ ├── cipher - core . js │ │ ├── CONTRIBUTING . md │ │ ├── core . js │ │ ├── crypto - js . js │ │ ├── docs │ │ │ └── QuickStartGuide . wiki │ │ ├── enc - base64 . js │ │ ├── enc - hex . js │ │ ├── enc - latin1 . js │ │ ├── enc - utf16 . js │ │ ├── enc - utf8 . js │ │ ├── evpkdf . js │ │ ├── format - hex . js │ │ ├── format - openssl . js │ │ ├── hmac . js │ │ ├── hmac - md5 . js │ │ ├── hmac - ripemd160 . js │ │ ├── hmac - sha1 . js │ │ ├── hmac - sha224 . js │ │ ├── hmac - sha256 . js │ │ ├── hmac - sha384 . js │ │ ├── hmac - sha3 . js │ │ ├── hmac - sha512 . js │ │ ├── index . js │ │ ├── lib - typedarrays . js │ │ ├── LICENSE │ │ ├── md5 . js │ │ ├── mode - cfb . js │ │ ├── mode - ctr - gladman . js │ │ ├── mode - ctr . js │ │ ├── mode - ecb . js │ │ ├── mode - ofb . js │ │ ├── package . json │ │ ├── pad - ansix923 . js │ │ ├── pad - iso10126 . js │ │ ├── pad - iso97971 . js │ │ ├── pad - nopadding . js │ │ ├── pad - pkcs7 . js │ │ ├── pad - zeropadding . js │ │ ├── pbkdf2 . js │ │ ├── rabbit . js │ │ ├── rabbit - legacy . js │ │ ├── rc4 . js │ │ ├── README . md │ │ ├── ripemd160 . js │ │ ├── sha1 . js │ │ ├── sha224 . js │ │ ├── sha256 . js │ │ ├── sha384 . js │ │ ├── sha3 . js │ │ ├── sha512 . js │ │ ├── tripledes . js │ │ └── x64 - core . js │ ├── utf8 │ │ ├── LICENSE - MIT . txt │ │ ├── package . json │ │ ├── README . md │ │ └── utf8 . js │ ├── xhr2 │ │ ├── Cakefile │ │ ├── CONTRIBUTING . md │ │ ├── lib │ │ │ ├── browser . js │ │ │ └── xhr2 . js │ │ ├── LICENSE . txt │ │ ├── package . json │ │ ├── README . md │ │ ├── src │ │ │ ├── 000 - xml_http_request_event_target . coffee │ │ │ ├── 001 - xml_http_request . coffee │ │ │ ├── errors . coffee │ │ │ ├── progress_event . coffee │ │ │ └── xml_http_request_upload . coffee │ │ └── test │ │ ├── fixtures │ │ │ ├── hello . json │ │ │ ├── hello . txt │ │ │ └── xhr2 . png │ │ ├── html │ │ │ └── browser_test . html │ │ └── src │ │ ├── events_test . coffee │ │ ├── event_target_test . coffee │ │ ├── headers_test . coffee │ │ ├── helpers │ │ │ ├── browser_mocha_runner . coffee │ │ │ ├── browser_mocha_setup . coffee │ │ │ ├── setup . coffee │ │ │ └── xhr_server . coffee │ │ ├── nodejs_set_test . coffee │ │ ├── redirect_test . coffee │ │ ├── response_type_test . coffee │ │ ├── responseurl_test . coffee │ │ ├── send_test . coffee │ │ ├── status_test . coffee │ │ └── xhr_test . coffee │ └── xmlhttprequest │ ├── lib │ │ └── XMLHttpRequest . js │ ├── LICENSE │ ├── package . json │ └── README . md ├── package - init . js ├── package . js ├── package . json ├── README . md ├── styleguide . md └── yarn . lock

Nodejs Web3js run

Nodejs is a popular server-side javascript runtime framework. It supports a lot of middleware projects, it makes sense to enable nodejs to interact with smart contracts. Please refer to some node documentation on how to install nodejs on your platform.

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).

console . log( "starting..." ); var Web3 = require( '/usr/lib/node_modules/web3' ); web3 = new Web3(new Web3 . providers . HttpProvider( 'http://localhost:8545' )); console . log(web3 . eth . accounts);

If there is no issue in the install, there will be the following output:

local @local - Lenovo - G50 - 70 : / nodeinstall / node $ node eth . js starting ... [ '0x81c95efa213ed798cc99e80f79eece314f76fbe8' ] local @local - Lenovo - G50 - 70 : / nodeinstall / node $

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

Web3 can be imported into your browser html code to create UI for your contracts. This approach is not recommended as you will not be exposing your RPC interface for the client browser machine to connect with. Usually, you will have an enterprise application connecting to your ethereum node instead of the browser. This is why our test with nodejs is important. There are other legacy technologies like java that need to connect with ethereum smart contracts. There are ways to create the wrapper libraries in each technology if we follow the JSON-RPC format as it was prescribed. We will, however, stick to the in-browser javascript environment to see all the facilities and features that an ethereum client provides.

Let’s see a simple HTML page that imports web3 js and make a simple contract call.

& lt;html & gt; & lt;head & gt; & lt;title & gt;Tester for eth web3 & lt; / title & gt; & lt;script src = "https://static.blockgeeks.com/usr/lib/node_modules/web3/lib/web3.js" & gt; & lt; / script & gt; & lt;script & gt; & nbsp; function start(){ var Web3 = require( 'web3' ); var web3 = new Web3(); web3 . setProvider(new web3 . providers . HttpProvider( 'http://localhost:8545' )); var abi = [ ... ]; var corecontractContract = web3 . eth . contract(abi ); var corecontractContractInst = corecontractContract . at( '0x0e22a4f27c2fc3b47e66b70fada85e1c4ca33681' ); console . log(corecontractContract . createCustomer( 287187 , ” custName ” , 13243244 , 1213334 )); } & lt; / script & gt; & lt; / head & gt; & lt;body onload = "start();" & gt; & lt;body & gt; & lt; / html & gt;

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.

Contract ABI

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:

[{ "constant" :true, "inputs" :[], "name" : "count" , "outputs" :[{ "name" : "" , "type" : "uint256" }], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "candidate" , "type" : "address" },{ "name" : "method" , "type" : "string" }], "name" : "isUserAuthorized" , "outputs" :[{ "name" : "" , "type" : "bool" }], "payable" :false, "type" : "function" },{ "constant" :true, "inputs" :[{ "name" : "" , "type" : "uint256" }], "name" : "users" , "outputs" :[{ "name" : "" , "type" : "address" }], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[], "name" : "kill" , "outputs" :[], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "user" , "type" : "address" }], "name" : "addUser" , "outputs" :[], "payable" :true, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "candidate" , "type" : "address" },{ "name" : "method" , "type" : "string" }], "name" : "isUser" , "outputs" :[{ "name" : "" , "type" : "bool" }], "payable" :false, "type" : "function" },{ "constant" :true, "inputs" :[{ "name" : "i" , "type" : "uint256" }], "name" : "getIthUser" , "outputs" :[{ "name" : "" , "type" : "address" }], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "index" , "type" : "uint256" },{ "name" : "name" , "type" : "string" }], "name" : "updateCustomer" , "outputs" :[], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "i" , "type" : "uint256" }], "name" : "deleteIthUser" , "outputs" :[], "payable" :false, "type" : "function" },{ "constant" :true, "inputs" :[], "name" : "owner" , "outputs" :[{ "name" : "" , "type" : "address" }], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "index" , "type" : "uint256" },{ "name" : "status" , "type" : "uint256" }], "name" : "updateCustomerStatus" , "outputs" :[], "payable" :false, "type" : "function" },{ "constant" :false, "inputs" :[{ "name" : "id" , "type" : "uint256" },{ "name" : "name" , "type" : "string" },{ "name" : "dateOfBirth" , "type" : "uint256" },{ "name" : "social" , "type" : "uint256" }], "name" : "createCustomer" , "outputs" :[], "payable" :false, "type" : "function" },{ "constant" :true, "inputs" :[], "name" : "getUserCount" , "outputs" :[{ "name" : "" , "type" : "uint256" }], "payable" :false, "type" : "function" },{ "constant" :true, "inputs" :[{ "name" : "index" , "type" : "uint256" }], "name" : "getCustomer" , "outputs" :[{ "name" : "id" , "type" : "uint256" },{ "name" : "name" , "type" : "string" },{ "name" : "dateOfBirth" , "type" : "uint256" },{ "name" : "social" , "type" : "uint256" },{ "name" : "status" , "type" : "uint256" }], "payable" :false, "type" : "function" },{ "constant" :true, "inputs" :[{ "name" : "id" , "type" : "uint256" }], "name" : "getCustomerById" , "outputs" :[{ "name" : "idRet" , "type" : "uint256" },{ "name" : "name" , "type" : "string" },{ "name" : "dateOfBirth" , "type" : "uint256" },{ "name" : "social" , "type" : "uint256" },{ "name" : "status" , "type" : "uint256" }], "payable" :false, "type" : "function" },{ "anonymous" :false, "inputs" :[{ "indexed" :false, "name" : "eventType" , "type" : "string" },{ "indexed" :false, "name" : "id" , "type" : "uint256" }], "name" : "Alert" , "type" : "event" },{ "anonymous" :false, "inputs" :[{ "indexed" :true, "name" : "by" , "type" : "address" },{ "indexed" :true, "name" : "accessTime" , "type" : "uint256" },{ "indexed" :false, "name" : "method" , "type" : "string" },{ "indexed" :false, "name" : "desc" , "type" : "string" }], "name" : "LogAccess" , "type" : "event" }]

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.

var abi = [ ... ]; var corecontractContract = web3 . eth . contract(abi );

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

var corecontractContractInst = corecontractContract . at( '0x0e22a4f27c2fc3b47e66b70fada85e1c4ca33681' );

Now the contract is tied to the address where the contract code is “installed”. Now we can call methods on the contract.

corecontractContract . createCustomer( 287187 , ” custName ” , 13243244 , 1213334 );

This call will return a transaction hash of the transaction submitted to the contract.

Additional parameters:

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[0]; 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.

console . log(corecontractContractInst . getCustomer( 0 ));

This will return an array of BigNumbers for uints:

[BigNumber, "aCust" , BigNumber, BigNumber, BigNumber]

To convert BigNumbers to decimals, use toDecimal(BigNumber) utility method:

console . log(web3 . toDecimal(corecontractContractInst . getCustomer( 0 )[ 0 ]));

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.

corecontractContract . createCustomer( 287187 , ” custName ” , 13243244 , 1213334 );

This call will result in a transaction and will consume some gas. Gas can be supplied along with the transaction using the transaction object:

console . log(corecontractContractInst . createCustomer( 133423 , 'aCust' , 3334 , 454545 ), {gas: 20000 });

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.

at Object . InvalidResponse (web3 . js: 3120 ) at RequestManager . send (web3 . js: 6043 ) 0 });

Filters:

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.

var events = corecontractContractInst . allEvents(); // watch for changes events . watch(function(error, event){ if ( ! error) console . log(JSON . stringify(event)); });

This will output events like this:

{ "address" : "0x969f563858ddef891e32de8d8c9232f6f74103d0" , "blockHash" : "0x9c44f138a2f1b3aa4a6457252e62fff56257329d17261901048b1d50176e39b4" , "blockNumber" : 47 , "logIndex" : 0 , "transactionHash" : "0xb4ca62b604c9cd43a0125703c07c8a1624963a8fd399b38f943357549f96c90a" , "transactionIndex" : 0 , "transactionLogIndex" : "0x0" , "type" : "mined" , "event" : "LogAccess" , "args" :{ "by" : "0x81c95efa213ed798cc99e80f79eece314f76fbe8" , "accessTime" : "1494178629" , "method" : "createCustomer" , "desc" : "successful access" }} { "address" : "0x969f563858ddef891e32de8d8c9232f6f74103d0" , "blockHash" : "0x9c44f138a2f1b3aa4a6457252e62fff56257329d17261901048b1d50176e39b4" , "blockNumber" : 47 , "logIndex" : 0 , "transactionHash" : "0xb4ca62b604c9cd43a0125703c07c8a1624963a8fd399b38f943357549f96c90a" , "transactionIndex" : 0 , "transactionLogIndex" : "0x0" , "type" : "mined" , "event" : "LogAccess" , "args" :{ "by" : "0x81c95efa213ed798cc99e80f79eece314f76fbe8" , "accessTime" : "1494178629" , "method" : "createCustomer" , "desc" : "successful access" }}

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.

In this above discussion, we touched upon some very basics of the javascript client. In the next one, we will look at some advanced concepts like how to write test code for functions through the web3 library, Creating a complete Dapp, using truffle framework and more.

Favorite