Ethereum Web Application using Web3.js, Node.js and Express


Bhaskar S 01/27/2018


Overview

In the article Introduction to Blockchain, we introduced the concept of Blockchain at a very high-level.

In the Introduction to Ethereum series, we explored the simple vehicle buying use-case (involving the buyer, the dealer, and the dmv) using a single node on a private Ethereum network.

In this article, we demonstrate the interaction between the dealer and the buyer by building a web-based application using HTML/CSS/JavaScript for the User Interface (UI) and the popular server-side JavaScript frameworks Web3.js, Node.js and Express for the Backend.

Installation

The installation is on a Ubuntu 16.04 LTS (and above) based Linux desktop.

We will assume a hypothetical user called polarsparc with the home directory located at /home/polarsparc.

Assuming that we are logged in as polarsparc and the current working directory is the home directory /home/polarsparc, we will setup a directory structure by executing the following commands:

$ mkdir -p Ethereum/app

$ mkdir -p Ethereum/app/static

Now, change the current working directory to the directory /home/polarsparc/Ethereum.

ASSUMPTION:

We assume the installation and setup from the articles Part 1 and Part 2 are followed prior to this installation.

To install the current stable version (8.x) of the popular Node.js platform, execute the following commands:

$ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -

$ sudo apt-get update

$ sudo apt-get install -y nodejs

Once the installation complete, execute the following command to check everything was ok:

$ node -v

The following would be the typical output:

Output.1

v8.9.4

To install the Express module, execute the following command:

$ sudo npm install -g express

To install the body-parser module, execute the following command:

$ sudo npm install -g body-parser

To install the Solidity module, execute the following command:

$ sudo npm install -g solc

To install the web3 module, execute the following command:

$ sudo npm install -g web3

The above command can result in an error as shown below:

Output.2

    npm WARN deprecated fs-promise@2.0.3: Use mz or fs-extra^3.0 with Promise Support
    npm WARN deprecated tar.gz@1.0.7: ⚠️  WARNING ⚠️ tar.gz module has been deprecated and your
             application is vulnerable. Please use tar module instead: https://npmjs.com/tar
    npm WARN deprecated minimatch@0.3.0: Please update to minimatch 3.0.2 or higher to avoid
             a RegExp DoS issue
    npm ERR! code 1
    npm ERR! Command failed: /usr/bin/git clone --depth=1 -q -b browserifyCompatible 
             git://github.com/frozeman/WebSocket-Node.git /home/polarsparc/.npm/_cacache/tmp/git-clone-b0ed4ad3
    npm ERR! /home/polarsparc/.npm/_cacache/tmp/git-clone-b0ed4ad3/.git: Permission denied
    npm ERR! 

    npm ERR! A complete log of this run can be found in:
    npm ERR!     /home/polarsparc/.npm/_logs/2018-27-30T21_11_32_065Z-debug.log

To fix the errors, execute the following commands:

$ mkdir ~/.npm-global

$ npm config set prefix '~/.npm-global'

$ export PATH=~/.npm-global/bin:$PATH

$ export NODE_PATH=/usr/lib/node_modules;~/.npm-global/lib/node_modules

Now we can install the web3 module by executing the following command (no need for sudo):

$ npm install -g web3

Hands-on with Ethereum Web Application

The following is the CSS stylesheet named style.css located in the directory /home/polarsparc/Ethereum/app/static:

style.css
/*
 * Author:      Bhaskar S
 * Date:        01/27/2018
 * Description: CSS style for the Auto vehicle dealer-buyer Ethereum web application
 */

body {
    font-family: 'Roboto', serif;
}

table {
    border: 0px;
    width: 100%;
}

tr {
    margin: 5px;
    width: 100%;
}

h3 {
    font-size: 18px;
    font-weight: bold;
    text-align: center;
}

p, label {
    margin: 12px;
    font-size: 12px;
    font-weight: bold;
    display: inline-block;
}

hr {
    text-align: center;
    width: 20%;
}

input {
    border-radius: 4px;
    font-size: 12px;
    display: inline-block;
}

button {
    border-radius: 4px;
    font-size: 14px;
    font-weight: bold;
}

.centeralign {
    text-align: center;
}

#title {
    background-color: #f2d7d5;
    border: 2px solid #c0392b;
    height: 5%;
    width: 100%;
    font-size: 20px;
    font-weight: bold;
    text-align: center;
}

#buyer {
    background-color: #d5f5e3;
    border: 1px solid #239b56;
    margin: 5px;
    width: 50%;
}
#buyer hr {
    color: #239b56;

}
#buyer input {
    border: 1px solid #239b56;
}

#dealer {
    background-color: #d6eaf8;
    border: 1px solid #2471a3;
    margin: 5px;
    width: 50%;
}
#dealer hr {
    color: #2471a3;
}
#dealer input {
    border: 1px solid #2471a3;
}

#events {
    background-color: #fcf3cf;
    border: 1px solid #f1c40f;
    margin: 5px;
}
#events hr {
    color: #f1c40f;
}
#events p {
    display: block;
}

The following is the JavaScript file named vehicle.js located in the directory /home/polarsparc/Ethereum/app/static:

vehicle.js
/*
 * Author:      Bhaskar S
 * Date:        01/27/2018
 * Description: Javascript data and functions for the Auto vehicle dealer-buyer Ethereum
 *              web application
 */

const evt = document.getElementById('evt_list');
const cin = document.getElementById('contract');
const pin = document.getElementById('payment');

const ast = '2px solid #c0392b';
const dst = '2px solid #000000';

const xhr = new XMLHttpRequest();

window.onload = init();

var data = undefined;
var data2 = undefined;

/*
 * This function makes an ajax call to the url '/init' to get the values for:
 * the dealer account balance, the buyer account balance, the buyer address,
 * and the vehicle cost.
 * 
 * On success, the appropriate dom elements on the page are updated
 */
function init() {
    cin.addEventListener('click' , contract);
    pin.addEventListener('click' , payment);
    toggle(false); // Enable dealer button
    xhr.open('GET', '/init', true);
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                // log(xhr.responseText);

                data = JSON.parse(xhr.responseText);

                document.getElementById('dbal').innerText = data.dealer_balance;
                document.getElementById('baddr').value = data.buyer;
                document.getElementById('vcost').value = data.cost;
                document.getElementById('vamt').value = data.cost;
                document.getElementById('bbal').innerText = data.buyer_balance;

                log('UI: Application successfully initialied');
            } else {
                log(`ERROR: status code ${xhr.status}`);
            }
        }
    };
    xhr.send();
}

/*
 * This function will ensure that only one button is active at any point in
 * time - either the button to deploy a contract on the dealer column or the
 * payment button on the buyer column once the contract has been deployed.
 */
function toggle(state) {
    cin.disabled = state;
    cin.style.border = (state ? dst : ast);
    cin.style.opacity = (state ? 0.5 : 1);
    pin.disabled = !state;
    pin.style.border = (!state ? dst : ast);
    pin.style.opacity = (!state ? 0.5 : 1);
}

/*
 * This function logs messages on the bottom part of the screen in the Event
 * Messages box.
 */
function log(msg) {
    let dt = new Date();
    let pnode = document.createElement('p');
    pnode.textContent = '[' + dt.toString() + '] ' + msg;
    evt.appendChild(pnode);
}

/*
 * This function makes an ajax call to the url '/contract' to deploy the
 * Vehicle2 contract. If the contract is successfully deployed, the server
 * returns the deployed contract address.
 * 
 * On sucess, the appropriate dom element on the page is updated
 */
function contract() {
    xhr.open('POST', '/contract', true);
    xhr.setRequestHeader("Content-type", "application/json");
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                // log(xhr.responseText);

                data2 = JSON.parse(xhr.responseText);

                document.getElementById('caddr').value = data2.contract_address;

                toggle(true); // Enable buyer button

                log('UI: Contract successfully deployed');
            } else {
                log(`ERROR: status code ${xhr.status}`);
            }
        }
    };
    xhr.send(JSON.stringify(data));
}

/*
 * This function makes an ajax call to the url '/payment' to invoke the
 * deployed Vehicle2 contract method buyVehicle. On successful invocation,
 * the server returns the current dealer account balance, the current
 * buyer account balance, and a list of events that have been emitted
 * by the deployed contract.
 * 
 * On sucess, the appropriate dom elements in the page are updated
 */
function payment() {
    xhr.open('POST', '/payment', true);
    xhr.setRequestHeader("Content-type", "application/json");
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                // log(xhr.responseText);

                let data3 = JSON.parse(xhr.responseText);

                document.getElementById('dbal').innerText = data3.dealer_balance;
                document.getElementById('bbal').innerText = data3.buyer_balance;
                document.getElementById('caddr').value = '';

                data3.events.forEach(event => {
                    log(`NODE: Event ${JSON.stringify(event)}`);
                });

                toggle(false); // Enable dealer button

                log('UI: Payment successfully executed');
            } else {
                log(`ERROR: status code ${xhr.status}`);
            }
        }
    };
    xhr.send(JSON.stringify(data2));
}

The following is the HTML file named index.html located in the directory /home/polarsparc/Ethereum/app/static:

index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset='UTF-8' />
        <title>Auto Vehicle Dealer-Buyer Interaction</title>
        <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Roboto">
        <link href="style.css" rel="stylesheet">
    </head>
    <body>
        <div id='title'>Auto Vehicle Dealer-Buyer Interaction</div>
        <br/>
        <table>
            <tr>
                <td id='dealer'>
                    <div>
                        <h3>Dealer</h3>
                        <hr />
                        <br />
                        <p>Dealer Balance:</p><label id='dbal' type='text'>0</label>
                        <br />
                        <p>Buyer Address:</p><input id='baddr' size=50 type='text' readonly />
                        <br />
                        <p>Vehicle Cost:</p><input id='vcost' type='text' readonly />
                        <br />
                        <br />
                        <div class='centeralign' id='one'>
                            <button id='contract'>Set Contract</button>
                        </div>
                    </div>
                </td>
                <td id='buyer'>
                    <div>
                        <h3>Buyer</h3>
                        <hr />
                        <br />
                        <p>Buyer Balance:</p><label id='bbal' type='text'>0</label>
                        <br />
                        <p>Contract Address:</p><input id='caddr' size=50 type='text' readonly />
                        <br />
                        <p>Amount:</p><input id='vamt' type='text' readonly />
                        <br />
                        <br />
                        <div class='centeralign' id='two'>
                            <button id='payment'>Make Payment</button>
                        </div>
                    </div>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <div id='events'>
                        <h3>Event Messages</h3>
                        <hr />
                        <br />
                        <div id='evt_list'></div>
                    </div>
                </td>
            </tr>
        </table>
        <script type="text/javascript" src="vehicle.js"></script>
    </body>
</html>

The following is the Solidity file named Vehicle2.sol located in the directory /home/polarsparc/Ethereum/src:

Vehicle2.sol
pragma solidity ^0.4.15;

/*
 * This AmountOwed contract implements a common function modifier to check the value of tranfer
 * satisfies the amount owed to dealer
 */

contract AmountOwed {
    // Modifiers can be used to change the behaviour of any function in a contract, such as checking
    // a condition prior to executing the function. Modifiers are inheritable properties of contracts
    // and may be overridden by derived contracts
    modifier validate(uint amt) {
        require(msg.value >= amt);
        _; // Function body
    }
}

/*
 * A Vehicle instance is first created in the Ethereum blockchain by the dealer.
 * Once the payment is received, the dealer transfers ownership to the buyer.
 */

contract Vehicle2 is AmountOwed {
    enum DealStatus { Open, Closed }
    
    uint _flag;
    uint _vin;
    uint _cost;
    address _owner;
    address _dealer;
    DealStatus _status;
    
    event Bought(uint vin, uint cost);
    
    function getFlag() public view returns (uint) {
        return _flag;
    }
    
    function getVin() public view returns (uint) {
        return _vin;
    }
    
    function getCost() public view returns (uint) {
        return _cost;
    }
    
    function getOwner() public view returns (address) {
        return _owner;
    }
    
    function getDealer() public view returns (address) {
        return _dealer;
    }
    
    function getStatus() public view returns (DealStatus) {
        return _status;
    }
    
    // The keyword 'payable' is important for accepting ethers from the buyer
    function buyVehicle() public payable validate(_cost) {
        _flag = 1;
        // Only the assigned owner can close the deal
        if (msg.sender == _owner) {
            _flag = 2;
            _status = DealStatus.Closed;
            _dealer.transfer(_cost);
            Bought(_vin, _cost);
        }
    }
    
    function Vehicle2(uint vin, uint cost, address buyer) public {
        _flag = 0;
        _vin = vin;
        _cost = cost;
        _owner = buyer;
        _dealer = msg.sender;
        _status = DealStatus.Open;
    }
}

The following is the Node server file named node-app.js located in the directory /home/polarsparc/Ethereum/app:

node-app.js
/*
 * Author:      Bhaskar S
 * Date:        01/27/2018
 * Description: Node.JS and Express based server for the Auto vehicle dealer-buyer Ethereum
 *              web application
 */

var fs = require('fs');
var express = require('express');
var bodyparser = require('body-parser');
var Web3 = require('web3');
var solc = require('solc');
var util = require('util');

let vin = '1234567890';
let cost = 1000000000000000000;
let gas = 1000000;
let abi = undefined;
let bin = undefined;
let web3 = undefined;

/*
 * This function prepares the server by compiling the Vehicle2 solidity contract
 * source code using the solc npm module. On successful compile, extracts the
 * ABI definition and the bytecode. In addition, initializes a web3 connection
 * to the private ethereum network.
 */
function setUp() {
    let source = fs.readFileSync('../src/Vehicle2.sol', 'UTF-8');
    let compiled = solc.compile(source);

    bin = compiled.contracts[':Vehicle2'].bytecode;

    util.log(`>>>>> setup - Bytecode: ${bin}`);
    util.log(`>>>>> setup - ABI: ${compiled.contracts[':Vehicle2'].interface}`);
    
    abi = JSON.parse(compiled.contracts[':Vehicle2'].interface);

    web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8081'));

    util.log('>>>>> setup - Completed !!!')
}

/*
 * This function handles the ajax call to the url '/init'. It uses the web3
 * connection to fetch all the 4 accounts in our private ethereum network
 * and selects the buyer (2nd) and dealer (3rd) account addresses. Next,
 * makes a call to fetch the balances for the buyer and dealer accounts.
 * 
 * Returns the results in the json format:
 * 
 * {
 *     vin: <vin number>,
 *     cost: <vehicle cost>,
 *     buyer: <buyer account address>
 *     dealer: <dealer account address>
 *     buyer_balance: <buyer account balance in ethers>,
 *     dealer_balance: <dealer account balance in ethers>
 * }
 */
function initApi(response) {
    let data = {
        vin: vin,
        cost: 1,
        buyer: undefined,
        dealer: undefined,
        buyer_balance: undefined,
        dealer_balance: undefined
    };

    web3.eth.getAccounts()
    .then(accounts => {
        util.log(`>>>>> initApi - Accounts: ${accounts}`);

        data.buyer = accounts[1];
        data.dealer = accounts[2];

        util.log(`>>>>> initApi - Buyer address: ${data.buyer}`);

        return web3.eth.getBalance(data.buyer);
    })
    .then(balance1 => {
        util.log(`>>>>> initApi - Buyer balance: ${web3.utils.fromWei(balance1, 'ether')}`);

        data.buyer_balance = balance1;

        return web3.eth.getBalance(data.dealer);
    })
    .then(balance2 => {
        util.log(`>>>>> initApi - Dealer balance: ${web3.utils.fromWei(balance2, 'ether')}`);

        data.dealer_balance = balance2;

        response.json(data);
    });
}

/*
 * This function handles the ajax call to the url '/contract'. Since the dealer
 * is responsible for deploying the contract, uses the web3 connection to unlock
 * the dealer account and then deploys the contract code to the private Ethereum
 * network. Once the contract is mined and deployed, extracts the deployed contract
 * address.
 * 
 * Returns the results in the json format:
 * 
 * {
 *     vin: <vin number>,
 *     cost: <vehicle cost>,
 *     buyer: <buyer account address>
 *     dealer: <dealer account address>
 *     contract_address: <address of the deployed contract>
 * }
 */
function contractApi(request, response) {
    let contract = new web3.eth.Contract(abi);

    let data = {
        vin: request.body.vin,
        cost: request.body.cost,
        buyer: request.body.buyer,
        dealer: request.body.dealer,
        contract_address: undefined
    };

    util.log(`>>>>> contractApi - Unlocking ${request.body.dealer} account`);

    web3.eth.personal.unlockAccount(request.body.dealer, 'dealer')
    .then(result => {
        util.log(`>>>>> contractApi - Is dealer account unlocked ? ${result}`);
        util.log('>>>>> contractApi - Ready to deploy Vehicle2 contract');

        contract.deploy({
            data: '0x'+bin,
            arguments: [request.body.vin, cost, request.body.buyer]
        })
        .send({
            from: request.body.dealer,
            gas: gas
        })
        .on('receipt', receipt => {
            util.log(`>>>>> contractApi - Contract sucessfully deployed @ address: ${receipt.contractAddress}`);

            data.contract_address = receipt.contractAddress;

            response.json(data);
        });
    }, error => {
        util.log(`***** contractApi - Dealer account unlock error - ${error}`);
    });
}

/*
 * This function handles the ajax call to the url '/payment'. Since the buyer is
 * the entity invoking the contract, uses the web3 connection to unlock the buyer
 * account and then invoke the buyVehicle method on the depoloyed contract. Once
 * the contract method is mined and executed, extracts the balances for the buyer
 * and dealer accounts as well as the emitted events.
 * 
 * Returns the results in the json format:
 * 
 * {
 *     events: <array of emitted events>,
 *     buyer_balance: <buyer account balance in ethers>,
 *     dealer_balance: <dealer account balance in ethers>
 * }
 */
function paymentApi(request, response) {
    let contract = new web3.eth.Contract(abi);

    util.log(`>>>>> paymentApi - Contract address: ${request.body.contract_address}`);

    contract.options.address = request.body.contract_address;    

    util.log(`>>>>> paymentApi - Unlocking ${request.body.buyer} account`);

    web3.eth.personal.unlockAccount(request.body.buyer, 'buyer')
    .then(result => {
        util.log(`>>>>> paymentApi - Is buyer account unlocked ? ${result}`);
        util.log('>>>>> paymentApi - Ready to invoke buyVehicle() method');

        contract.methods.buyVehicle()
        .send({
            from: request.body.buyer,
            value: cost,
            gas: gas
        })
        .on('receipt', receipt => {
            util.log(`>>>>> paymentApi - Contract method buyVehicle() successfully invoked: ${receipt}`);
    
            let data = { 
                events: [], 
                buyer_balance: 0, 
                dealer_balance: 0 
            };

            contract.getPastEvents('Bought', {
                fromBlock: 0,
                toBlock: 'latest'
            })
            .then(events => {
                events.forEach(event => {
                    data.events.push(event.returnValues);
                });
                
                util.log(`>>>>> paymentApi - List of events - ${JSON.stringify(data.events)}`);
    
                return web3.eth.getBalance(request.body.buyer);
            })
            .then(balance1 => {
                util.log(`>>>>> paymentApi - Buyer balance: ${web3.utils.fromWei(balance1, 'ether')}`);
    
                data.buyer_balance = web3.utils.fromWei(balance1, 'ether');
    
                return web3.eth.getBalance(request.body.dealer);
            })
            .then(balance2 => {
                util.log(`>>>>> paymentApi - Dealer balance: ${web3.utils.fromWei(balance2, 'ether')}`);

                data.dealer_balance = web3.utils.fromWei(balance2, 'ether');

                response.json(data);
            })
        })
    }, error => {
        util.log(`***** paymentApi - Buyer unlock error - ${error}`);
    });
}

/*
 * ----- Start of The main server code -----
 */

setUp();

var app = express();

app.use(bodyparser.json());

app.use(function(req, res, next) {
    util.log(`Request => url: ${req.url}, method: ${req.method}`);
    next();
});

app.use(express.static('./static'));

app.get('/init', function(req, res) {
    initApi(res);
});

app.post('/contract', function(req, res) {
    contractApi(req, res);
});

app.post('/payment', function(req, res) {
    paymentApi(req, res);
});

app.listen(9001);

util.log('-> Express server @localhost:9001');

Open a new terminal window (referred to as the node window) and execute the following commands:

$ cd $HOME/Ethereum/app

$ node node-app.js

The following would be the typical output:

Output.3

27 Jan 20:58:44 - >>>>> setup - Bytecode: 6060604052341561000f57600080fd5b60405160608061052d8339810160405280805190602001909190805190602001909190805190602001909190505060008081905550826001819055508160028190555080600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555033600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600460146101000a81548160ff021916908360018111156100f357fe5b02179055505050506104238061010a6000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634e69d56014610088578063893d20e8146100bf5780639cf6d1af14610114578063a4e7290214610169578063bd3e19d414610173578063bf20f9401461019c578063f9633930146101c5575b600080fd5b341561009357600080fd5b61009b6101ee565b604051808260018111156100ab57fe5b60ff16815260200191505060405180910390f35b34156100ca57600080fd5b6100d2610205565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561011f57600080fd5b61012761022f565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610171610259565b005b341561017e57600080fd5b6101866103da565b6040518082815260200191505060405180910390f35b34156101a757600080fd5b6101af6103e4565b6040518082815260200191505060405180910390f35b34156101d057600080fd5b6101d86103ee565b6040518082815260200191505060405180910390f35b6000600460149054906101000a900460ff16905090565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60025480341015151561026b57600080fd5b6001600081905550600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156103d75760026000819055506001600460146101000a81548160ff021916908360018111156102f057fe5b0217905550600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc6002549081150290604051600060405180830381858888f19350505050151561035957600080fd5b7f3ccb2ab6980b218b1dd4974b07365cd90a191e170c611da46262fecc208bd661600154600254604051808381526020018281526020019250505060405180910390a1600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b50565b6000600254905090565b6000600154905090565b600080549050905600a165627a7a723058201675c46924bd35108501f10889a27f0eef6a30339f4b79bcf853b8231efe590d0029
27 Jan 20:58:44 - >>>>> setup - ABI: [{"constant":true,"inputs":[],"name":"getStatus","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDealer","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"buyVehicle","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"getCost","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getVin","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFlag","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"vin","type":"uint256"},{"name":"cost","type":"uint256"},{"name":"buyer","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"vin","type":"uint256"},{"indexed":false,"name":"cost","type":"uint256"}],"name":"Bought","type":"event"}]
27 Jan 20:58:44 - >>>>> setup - Completed !!!
27 Jan 20:58:45 - -> Express server @localhost:9001

Open a new terminal window (referred to as the Ethereum terminal) and follow the commands listed under the section Hands-on with Ethereum from the article Part 1 till the Output.8.

To start a single node in our private Ethereum network, execute the following command:

$ geth --identity "test" --datadir ./data/test -ethash.dagdir ./data/test --networkid "21" --maxpeers 0 --nodiscover --ipcdisable --rpc --rpcaddr 127.0.0.1 --rpcport 8081 --rpcapi "web3,eth,personal" --port 30001 --rpccorsdomain "*" --verbosity 2 console

The following would be the typical output:

Output.4

WARN [01-27|20:57:23] Upgrading database to use lookup entries 
Welcome to the Geth JavaScript console!

instance: Geth/test/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9
coinbase: 0xef17ec5df3116dea3b7abaf1ff3045639453cc76
at block: 0 (Wed, 31 Dec 1969 19:00:00 EST)
 datadir: /home/polarsparc/Ethereum/data/test
 modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

>

To list all the account addresses in this node of our private network, execute the following command in the geth Javascript shell prompt:

> eth.accounts

The following would be the typical output:

Output.5

["0xef17ec5df3116dea3b7abaf1ff3045639453cc76", "0x35ddac855e0029676f489dafca9ab405a348317c", "0x46bf8994c8702919434271d89bcad8abec915612", "0x518a6377a3863201aef3310da41f3448d959a310"]

We need to start a miner for this private Ethereum network.

To start the miner with one mining thread, execute the following command in the geth Javascript shell prompt:

> miner.start(1)

Open a web browser with the URL http://localhost:9001/.

The following is an illustration of the browser window:

Initial
Initial

Clicking on the button labels Set Contract will send an AJAX request for the URL /contract to the Node server.

The following would be the typical output on the node window:

Output.6

27 Jan 20:58:51 - Request => url: /, method: GET
27 Jan 20:58:51 - Request => url: /style.css, method: GET
27 Jan 20:58:51 - Request => url: /vehicle.js, method: GET
27 Jan 20:58:51 - Request => url: /init, method: GET
27 Jan 20:58:51 - >>>>> initApi - Accounts: 0xeF17ec5df3116dEA3b7aBAf1fF3045639453cc76,0x35DdaC855e0029676f489daFca9AB405A348317C,0x46bF8994C8702919434271D89bcad8ABeC915612,0x518a6377A3863201AEF3310dA41f3448D959a310
27 Jan 20:58:51 - >>>>> initApi - Buyer address: 0x35DdaC855e0029676f489daFca9AB405A348317C
27 Jan 20:58:51 - >>>>> initApi - Buyer balance: 10
27 Jan 20:58:51 - >>>>> initApi - Dealer balance: 5
27 Jan 20:59:04 - Request => url: /contract, method: POST
27 Jan 20:59:04 - >>>>> contractApi - Unlocking 0x46bF8994C8702919434271D89bcad8ABeC915612 account
27 Jan 20:59:06 - >>>>> contractApi - Is dealer account unlocked ? true
27 Jan 20:59:06 - >>>>> contractApi - Ready to deploy Vehicle2 contract

Once the miner has processed the contract deployment transaction, the following would be the typical output on the node window:

Output.7

27 Jan 20:59:13 - >>>>> contractApi - Contract sucessfully deployed @ address: 0xfa332003301955Dd8526F0eF3AA7c670bDA5F4FA

The browser is dynamically updated and the following is an illustration of the browser window:

Contract
Contract

Clicking on the button labels Make Payment will send another AJAX request for the URL /payment to the Node server.

The following would be the typical output on the node window:

Output.8

27 Jan 20:59:33 - Request => url: /payment, method: POST
27 Jan 20:59:33 - >>>>> paymentApi - Contract address: 0xfa332003301955Dd8526F0eF3AA7c670bDA5F4FA
27 Jan 20:59:33 - >>>>> paymentApi - Unlocking 0x35DdaC855e0029676f489daFca9AB405A348317C account
27 Jan 20:59:34 - >>>>> paymentApi - Is buyer account unlocked ? true
27 Jan 20:59:34 - >>>>> paymentApi - Ready to invoke buyVehicle() method

Once the miner has processed the method invocation transaction, the following would be the typical output on the node window:

Output.9

27 Jan 20:59:41 - >>>>> paymentApi - Contract method buyVehicle() successfully invoked: [object Object]
27 Jan 20:59:41 - >>>>> paymentApi - List of events - [{"0":"1234567890","1":"1000000000000000000","vin":"1234567890","cost":"1000000000000000000"}]
27 Jan 20:59:41 - >>>>> paymentApi - Buyer balance: 8.999311824
27 Jan 20:59:41 - >>>>> paymentApi - Dealer balance: 5.992023858

The browser is dynamically updated and the following is an illustration of the browser window:

Payment
Payment

We have successfully demonstrated a simple Ethereum web application using Web3.js, Node.js, and Express.

References

Official Ethereum Web3.js Documentation

Introduction to Ethereum - Part 3

Introduction to Ethereum - Part 2

Introduction to Ethereum - Part 1

Introduction to Blockchain