This is part 3 of my Ethereum IOT Kid Ground Device project. In this part, we will discuss the codes of the web service backend written in Node.js and running as a server application. You may read part 1 here and part 2 here.
In my project, the web service is where the Ethereum transactions are really signed and executed. The Node.js backend accepts messages from the PIR sensor, the Unabell button and the Web UI. It takes these requests and makes the corresponding function calls to the Kids Grounder Smart Contract that runs on the Ethereum Blockchain. I have mentioned in part 1 that this is a problematic implementation because:
- I am running an Ethereum Wallet within the Node.JS backend. In fact, it is the Node.JS backend that is signing and paying for the transactions on the Blockchain. In scenarios where IOTT devices need to sign their own transaction, they will all need to be running their own Node.JS backend web services.
- Running an Ethereum Wallet on the backend requires me to provide the backend web service with my wallet's private key. If this is a commercial solution, no one will let me keep his Ethereum wallet's private key.
- The Kid Grounder Web Based Front End does not directly receive updates from my Smart Contract. Any updates about whether my kid moves or presses the button requires me to manually refresh the page.
But well, it's a prototype and nevertheless still a useful article to read if you are interested in one or more of the following concepts in Ethereum development:
- Using the web3, ethereumjs-util, ethereumjs-tx and eth-lightwallet libraries with Node.js.
- Serializing the bytecode of a Smart Contract and deploying it on the Ethereum blockchain with Node.js.
The packages I need
Here are the packages you need to run the Node.js backend.
To run web services here are the packages:
- http: This is a built-in module that lets Node.js transfer data using the HTTP protocol.
- Express: This is a web application framework that lets your Node.js backend works like a web service API, serving JSON web services to folks who makes the calls to them.
- body-parser: This module lets you read and decipher bodies of data that your web service receives.
To get Node.js to do Ethereum, here are the packages to install:
- web3: This is the main library for letting you do Ethereum stuff with JavaScript.
- ethereumjs-util: This is a collection of utility functions for Ethereum such as those that lets you do string manipulation.
- ethereumjs-tx: This module lets you execute ethereum transactions.
- eth-lightwallet: This is a HD wallet that lets you store your Ethereum private keys. This is the module that allows you to sign your Ethereum transactions.
Other that http, which is built-in to Node.js, the other packages must be installed. For example, to install ethereumjs-tx run:
npm install ethereumjs-tx
Serializing a Smart Contract
To write a node.js program to re-deploy my Smart Contract over and over again, I will need to obtain:
- Its byte code, which is the smart contract converted from Solidity (which is a language I understand) to machine language (which is what Ethereum Virtual Machine understands)
- Its Application Binary Interface (ABI), which like an API, tells people who writes programs to make calls to your Smart Contract how to call it.
To get your Smart Contract byte code and ABI, go back to Remix, where you first executed and tested Grounder.sol. Click [Details]
To get the byte code, copy the long string of numbers highlighted in blue. Make sure that you do not copy the double quotes ("). Paste it somewhere because you will need it later.
To get the ABI, press the copy button at the ABI section. Paste this somewhere, we will need it later.
Codes for index.js
We will now walk through index.js. The line numbers that I refer to in the section below corresponds to the line numbers for my index.js codes in Github.
Setting Up
//JSON Service Libraries
var http = require('http'),
express = require('express'),
parser = require('body-parser');
//Ethereum Libraries
var Web3 = require('web3'),
util = require('ethereumjs-util'),
tx = require('ethereumjs-tx'),
lightwallet = require('eth-lightwallet');
From lines 1 to 10, we declare the JSON and Ethereum libraries that you would have installed with NPM.
var txutils = lightwallet.txutils;
var web3 = new Web3(
new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/<your api key>')
);
In lines 12 to 15 we declare txutils, which is the lightwallet's txutils module. We also declare web3. I am using the Infura Ethereum API service instead of running my own Geth node. I have written about how to work with Infura here earlier.
var address = '<your wallet address>';
var key = '<your wallet private key>';
Lines 16 and 17 are where you state your wallet's address and private key. If you are using MetaMask wallet, select "Export Private Key" at your wallet's account detail to get your wallet's private key.
var bytecode = '...';
var interface = [ { "constant": true, "inputs": [], "name": ...;
Lines 18 and 19 is where you copy and paste the bytecode and ABI interface from the "Serializing a Smart Contract" section above.
// Setup express
var app = express();
app.use(parser.json());
app.use(parser.urlencoded({ extended: true }));
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.setHeader('Access-Control-Allow-Credentials', true);
next();
});
app.set('port', process.env.PORT || 5000);
// Set default route
app.get('/', function (req, res) {
res.send('<html><body><p>Welcome to Kid Grounder Wrapper</p></body></html>');
});
// Create server
http.createServer(app).listen(app.get('port'), function(){
console.log('Server listening on port ' + app.get('port'));
});
In lines 21 to 43, we set up Express to allow the node.js program to listen for requests.
Execute a New Grounding Smart Contract
//Endpoint: http://127.0.0.1:5000/startground
app.post('/startground', function (req,res) {
var startTx = {
nonce: web3.toHex(web3.eth.getTransactionCount(address)),
gasLimit: web3.toHex(800000),
gasPrice: web3.toHex(20000000000),
data: '0x' + bytecode + '00000000000000000000000000000000000000000000000000000' + Date.now().toString(16)
};
sendStartGroundRaw(startTx, res);
});
Lines 45 to 56 allows an API call to be made to execute the Grounder Smart Contract's constructor. A transaction startTx
is declared by fixing up the transaction's nounce, gas limit and gas price. The contract's bytecode and the date/time at this moment the call is serialized as the data for startTx. sendStartGroundRaw()
is then run to execute this transaction.
function sendStartGroundRaw(rawTx, res) {
var privateKey = new Buffer(key, 'hex');
var transaction = new tx(rawTx);
transaction.sign(privateKey);
var serializedTx = transaction.serialize().toString('hex');
web3.eth.sendRawTransaction(
'0x' + serializedTx, function(err, result) {
if(err) {
console.log(err);
var obj = {"transactionid":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
} else {
console.log(result);
transactionid = result;
var obj = {"transactionid":result};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
});
}
Lines 58 to 78 declare sendStartGroundRaw()
. It creates a new transaction, signs and then executes it. 2 things could result here - either the transaction does not get sent successfully or it does. Note that we will not be able to find out if Ethereum has successfully created the grounder.sol smart contract at this point (it sometime takes a minute or more on the Ethereum blockchain) - just whether or not the transaction is successfully sent to the Ethereum network. If the transaction is sent to the Ethereum network successfully, we receive a transaction ID.
Getting the grounder.sol Smart Contract Address
//Endpoint: http://127.0.0.1:5000/getcontract
app.post('/getcontract', function (req,res) {
if (typeof req.body.transactionid !== 'undefined'){
var transactionid = req.body.transactionid;
try{
var receipt = web3.eth.getTransactionReceipt(transactionid);
var obj = {"contractaddress":receipt.contractAddress};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
catch (err) {
var obj = {"contractaddress":-1};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));;
}
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(400).send(JSON.stringify({'result' : 'error', 'msg' : 'Please fill in transaction id'}));
}
});
Lines 80 to 100 declare the web service getcontract
that accepts a transaction id and returns the corresponding smart contract address. This web service allows the user to find out if the transaction to create the grounder smart contract that he has executed in the previous step has completed successfully. When you provide the transaction id to this getcontract
web service and receives a smart contract address, you know that the transaction has executed successfully.
Executing functions in Grounder.sol
function sendTransactionRaw(rawTx, res) {
var privateKey = new Buffer(key, 'hex');
var transaction = new tx(rawTx);
transaction.sign(privateKey);
var serializedTx = transaction.serialize().toString('hex');
web3.eth.sendRawTransaction(
'0x' + serializedTx, function(err, result) {
if(err) {
console.log(err);
var obj = {"transactionid":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
} else {
console.log(result);
var obj = {"transactionid":result};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
});
}
The function sendTransactionRaw()
is used to sign and send a transaction to the Ethereum blockchain. It returns the transaction id if this transaction is successfully sent, or -1 if it fails. We will use sendTransactionRaw()
in the web services calls that follows. This function is declared from line 210 to 229.
End Ground
//Endpoint: http://127.0.0.1:5000/endground
app.post('/endground', function (req,res) {
if (typeof req.body.contractaddress !== 'undefined'){
var contractaddress = req.body.contractaddress;
try{
var txOptions = {
nonce: web3.toHex(web3.eth.getTransactionCount(address)),
gasLimit: web3.toHex(800000),
gasPrice: web3.toHex(20000000000),
to: contractaddress
}
var endTx = txutils.functionTx(interface, 'endGround', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
}
catch (err) {
console.log(err);
var obj = {"transactionid":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(400).send(JSON.stringify({'result' : 'error', 'msg' : 'Please fill in contract address'}));
}
});
End Ground will execute the corresponding endGround
function in the Grounder smart contract. It is declared from line 102 to 127. To call endGround
, you provide the address of the Grounder smart contract that you have deployed.
In particular, these 2 lines will execute endGround
by passing in the date and time at this moment that this function is is called and send it as a transaction to the Ethereum blockchain.
var endTx = txutils.functionTx(interface, 'endGround', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
Call for help
//Endpoint: http://127.0.0.1:5000/callforhelp
app.post('/callforhelp', function (req,res) {
if (typeof req.body.contractaddress !== 'undefined'){
var contractaddress = req.body.contractaddress;
try{
var txOptions = {
nonce: web3.toHex(web3.eth.getTransactionCount(address)),
gasLimit: web3.toHex(800000),
gasPrice: web3.toHex(20000000000),
to: contractaddress
}
var endTx = txutils.functionTx(interface, 'callforhelp', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
}
catch (err) {
console.log(err);
var obj = {"transactionid":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(400).send(JSON.stringify({'result' : 'error', 'msg' : 'Please fill in contract address'}));
}
});
Lines 129 to 154 is where callforhelp
is written, and in the same way as endground
. To execute callforhelp
, you provide the address of the grounder smart contract that you have deployed.
The following lines do the main tasks of executing callforhelp
in the grounder smart contract.
var endTx = txutils.functionTx(interface, 'callforhelp', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
Moving
//Endpoint: http://127.0.0.1:5000/move
app.get('/move', function (req,res) {
if (typeof req.query.contractaddress !== 'undefined'){
var contractaddress = req.query.contractaddress;
try{
var txOptions = {
nonce: web3.toHex(web3.eth.getTransactionCount(address)),
gasLimit: web3.toHex(800000),
gasPrice: web3.toHex(20000000000),
to: contractaddress
}
var endTx = txutils.functionTx(interface, 'move', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
}
catch (err) {
console.log(err);
var obj = {"transactionid":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(400).send(JSON.stringify({'result' : 'error', 'msg' : 'Please fill in contract addres$
}
});
move
(Lines 156 to 181) executes move in the grounder smart contract. It is called when the kid moves during the time he is grounded. To execute move, you provide the address of the grounder smart contract that you have deployed.
Here are the important lines that do the work:
var endTx = txutils.functionTx(interface, 'move', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
Re-Ground
//Endpoint: http://127.0.0.1:5000/reground
app.post('/reground', function (req,res) {
if (typeof req.body.contractaddress !== 'undefined'){
var contractaddress = req.body.contractaddress;
try{
var txOptions = {
nonce: web3.toHex(web3.eth.getTransactionCount(address)),
gasLimit: web3.toHex(800000),
gasPrice: web3.toHex(20000000000),
to: contractaddress
}
var endTx = txutils.functionTx(interface, 'reGround', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
}
catch (err) {
console.log(err);
var obj = {"transactionid":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(400).send(JSON.stringify({'result' : 'error', 'msg' : 'Please fill in contract address'}));
}
});
reground
(lines 183 - 208) re-initializes the grounder smart contract into its original state so that the kid can be grounded again. To execute reground, you provide the address of the grounder smart contract that you have deployed.
To reground the kid, the reGround function on the grounder smart contract is executed.
var endTx = txutils.functionTx(interface, 'reGround', [Date.now()], txOptions);
sendTransactionRaw(endTx, res);
Reading public variables from the grounder smart contract
The rest of the webservices, namely, calltimestamp
, endgroundtimestamp
, startgroundtimestamp
, getmovecount
and getcallcount
reads the public variables from the grounder smart contract and returns them to the caller. Here's getcallcount
as an example. getcallcount
returns the total number of times the kid presses the callforhelp sigfox button.
//Endpoint: http://127.0.0.1:5000/getcallcount
app.post('/getcallcount', function (req,res) {
if (typeof req.body.contractaddress !== 'undefined'){
var contractaddress = req.body.contractaddress;
var contract = web3.eth.contract(interface);
var instance = contract.at(contractaddress);
instance.getCallCount.call(function(err, result) {
if(err) {
console.log(err);
var obj = {"callcount":"-1"};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
} else {
console.log(result);
var obj = {"callcount":result};
res.setHeader('Content-Type', 'application/json');
res.status(200).send(JSON.stringify(obj));
}
});
}
else {
res.setHeader('Content-Type', 'application/json');
res.status(400).send(JSON.stringify({'result' : 'error', 'msg' : 'Please fill in contract address'}));
}
});
getcallcount
runs the corresponding getCallCount()
function at the grounder smart contract.
Running and Testing the Web Service
To start index.js to listen for request, run the following command:
node index.js
I test my web services with Postman. Here's an example of how I tested the creation of the grounder smart contract.
And with the transaction id that the previous step returned, I find out my contract's address.
With my contract address, I can execute, say, the callforhelp
web service as well as other web services like reground
and endground
.
What's Next
In the 4th and final part of this series, I will walk through the codes for the the Arduino codes that make calls to this node.js web service whenever the kid moves.
Much of what I did was learnt from "Try out Ethereum using only nodejs and npm!" on Medium.com. Thank you CodeTract.io for starting me off.
Photo by Philippe Verheyden on Unsplash