NodeJS blockchain implementation: BrewChain: Chain+WebSockets+HTTP Server

I am going to create BrewChain. A verifiable chain of which member of the team made a brew and when. Protecting the history of events from any swindlers who may attempt to claim they have made more brews than they actually did.

Our application will consist of the following:

Blockchain

Blockchain is essentially a linked list in which each item contains a hash of the previous item.

The fundamental element in this chain is the block. Each block contains data, and a hash of the previous block. This hashed pointer maintains the integrity of the data and makes the list immutable.

To start with we need to determine the essential elements of our block.

(We’ll come to proof of work later on)

const createBlock = (lastBlock, data) =>{
	let newBlock = {
	    timestamp: new Date().getTime()
	  , data: data
	  , index: lastBlock.index+1
	  , previousHash: lastBlock.hash
	};

	newBlock.hash = createHash(newBlock);

	return newBlock;
}

Given the last block and the new data, the function will create and return a new block.

We’d then need a genesis block to begin our chain at element 0.

const genesisBlock = { 
	index: 0
  , timestamp: new Date().getTime()
  , data: 'Our genesis data'
  , previousHash: "-1"
};

genesisBlock.hash = createHash(genesisBlock);
addToChain(genesisBlock);

We can then create our blocks, adding them to the chain.

BrewChain

Taking the above and building a chain, we get the following:

const BrewChain = function() {
    let chain = [];
    let currentBlock = {};
    let genesisBlock = {};

    function init(){
        genesisBlock = { 
            index: 0
          , timestamp: new Date().getTime()
          , data: 'our genesis data'
          , previousHash: "-1"
        };

        genesisBlock.hash = createHash(genesisBlock);
        chain.push(genesisBlock);
        currentBlock = genesisBlock; 	
    }

    function createHash({ timestamp, data, index, previousHash }) {
        return Crypto.createHash('SHA256').update(timestamp+data+index+previousHash).digest('hex');
    }

    function addToChain(block){
        if(checkNewBlockIsValid(block, currentBlock)){	
            chain.push(block);
            currentBlock = block; 
            return true;
        }

        return false;    
    }

    function createBlock(data){
        let newBlock = {
            timestamp: new Date().getTime()
          , data: data
          , index: currentBlock.index+1
          , previousHash: currentBlock.hash
        };

        newBlock.hash = createHash(newBlock);
  
        return newBlock;
    }

    function getLatestBlock(){
        return currentBlock;
    }

    function getTotalBlocks(){
        return chain.length;
    }

    function getChain(){
        return chain;
    }

    function checkNewBlockIsValid(block, previousBlock){
        if(previousBlock.index + 1 !== block.index){
            //Invalid index
            return false;
        }else if (previousBlock.hash !== block.previousHash){
            //The previous hash is incorrect
            return false;
        }else if(!hashIsValid(block)){
            //The hash isn't correct
            return false;
        }
		
        return true;
    }	

    function hashIsValid(block){
        return (createHash(block) == block.hash);
    }

    return {
        init,
        createBlock,
        addToChain,
        checkNewBlockIsValid,
        getLatestBlock,
        getTotalBlocks,
        getChain
    }
};    

let myBrew = new BrewChain();
myBrew.init();

myBrew.addToChain(myBrew.createBlock('The 1st block'));
myBrew.addToChain(myBrew.createBlock('The 2nd block'));

...

(The code in this post is in somewhat rough form while I workout exactly how it is going to work)

This will setup our chain object, creating a genesis block and adding it to the chain.

Our Brewchain contains the ability to create new blocks, add new blocks to our chain and also check the validity of a block.

The test code at the end adds two blocks and then outputs the chain to check that the blocks are successfully being created.

We’ll now begin building our P2P brewNode server..

Brew Node P2P network

Each BrewNode server will have a chain, a web socket server, and an http server for controlling it

We’ll start by creating our node using web sockets..

const BrewNode = function(port){
    let brewSockets = [];
    let brewServer;
    let _port = port
    let chain = new BrewChain();

    function init(){

        chain.init();
		
        brewServer = new WebSocket.Server({ port: _port });
		
        brewServer.on('connection', (connection) => {
            console.log('connection in');
            initConnection(connection);
        });		
    }

    const messageHandler = (connection) =>{
        connection.on('message', (data) => {
            console.log('Message In:');
            const msg = JSON.parse(data);

            console.log(msg.event);
        });
        console.log('message handler setup');
    }

    const broadcastMessage = (message) => {
        console.log('sending to all '+message)
        brewSockets.forEach(node => node.send(JSON.stringify({event: message})))
    }

    const closeConnection = (connection) => {
        console.log('closing connection');
        brewSockets.splice(brewSockets.indexOf(connection),1);
    }

    const initConnection = (connection) => {
        console.log('init connection');

        messageHandler(connection);
		
        brewSockets.push(connection);

        connection.on('error', () => closeConnection(connection));
        connection.on('close', () => closeConnection(connection));
    }

    const createBlock = (teammember) => {
        let newBlock = chain.createBlock(teammember)
        chain.addToChain(newBlock);
    }

    const getStats = () => {
        return {
            blocks: chain.getTotalBlocks()
        }
    }

    const addPeer = (host, port) => {
        let connection = new WebSocket(`ws://${host}:${port}`);

        connection.on('error', (error) =>{
            console.log(error);
        });

        connection.on('open', (msg) =>{
            initConnection(connection);
        });
    }

    return {
        init,
        broadcastMessage,
        addPeer,
        createBlock,
        getStats
    }

}

On init our server sets up a listening socket server, we add a handler for incoming messages and add the incoming connection to an array of socket connections.

We also add some methods for adding a new peer, broadcasting messages to connected peers and removing disconnected peers from our collection of sockets.

The node also contains our BrewChain, a method which will create a block based on a teammembers name and add it to the chain.

Next we’ll add an HTTP server for sending commands to our server , which will also be used by our future UI interface.

const http_port = 3000

let BrewHTTP = function (){
    const app = new express();

    app.use(bodyParser.json());

    app.get('/broadcast', (req, res)=>{
        console.log('broadcasting '+req.query.message)
        node1.broadcastMessage(req.query.message)
        res.send();
    })

    // For testing purposes currently only uses localhost
    app.get('/addNode/:port', (req, res)=>{
        console.log('adding localhost host: '+req.params.port)
        node1.addPeer('localhost', req.params.port)

        res.send();
    })

    app.get('/spawnBrew/:teammember', (req, res)=>{
        let newBlock = node1.createBlock(req.params.teammember);
		
        console.log('block created');

        console.log(node1.getStats())

        res.send();
    })

    app.listen(http_port, () => {
        console.log(`http server up.. ${http_port}`);
    })
}

let httpserver = new BrewHTTP();

(node1 in the above being our BrewNode)

So we now have an HTTP server for invoking actions on our node via the browser.

running the following requests in the browser:

http://localhost:3000/spawnBrew/terry
http://localhost:3000/spawnBrew/barry
http://localhost:3000/spawnBrew/bob

Results in:

brewnode

The next stage of development is to begin communication between our nodes, distributing the chain

Each node needs to do the following:

To do this, lets begin by having it so that when a node generates a block it will dispatch it to all connected nodes.

const BrewNode = function(port){
....
    const messageHandler = (connection) =>{
        connection.on('message', (data) => {
            const msg = JSON.parse(data);
            switch(msg.event){
                case "block":
                    console.log('Block recieved');
                    console.log(msg.message);
                    break;  
                default:  
                    console.log('Unknown message');
            }
        });
    }
....
    const broadcastMessage = (event, message) => {
        brewSockets.forEach(node => node.send(JSON.stringify({ event, message})))
    }
....
    const createBlock = (teammember) => {
        let newBlock = chain.createBlock(teammember)
        chain.addToChain(newBlock);

        broadcastMessage('block', newBlock);

    }
....

To test this is happening, lets load up two brew nodes on different ports.

2nodes

I’ll then run the following url to connect node 2 to node 1

http://localhost:3004/addNode/18078

2nodes2

Running ‘http://localhost:3004/spawnBrew/barry’ should create a block and dispatch it to the connected node

2nodes3

That now works, with node 1 recieving the barry brew block

A quick alteration we must make here is to fix our genesis block to be the same across our nodes.

const BrewChain = function() 
...
    const genesisBlock = { 
        index: 0
      , timestamp: 1511818270000
      , data: 'our genesis data'
      , previousHash: "-1"
    };

Now we are going to process the recieved block(s) and create a consensus between our nodes.

We’ll add some constants for our events and then setup the block processor

...
    const BLOCK = "BLOCK";
    const REQUEST_CHAIN = "REQUEST_CHAIN";
...	

    const messageHandler = (connection) =>{
        connection.on('message', (data) => {
            const msg = JSON.parse(data);
            switch(msg.event){
                case BLOCK:
                    processedRecievedBlock(msg.message);
                    break;  
                default:  
                    console.log('Unknown message');
            }
        });
    }
...
    const processedRecievedBlock = (block) => {

        let currentTopBlock = chain.getLatestBlock();

        // Is the same or older?
        if(block.index <= currentTopBlock.index){
        	console.log('No update needed');
        	return;
        }

        //Is claiming to be the next in the chain
        if(block.previousHash == currentTopBlock.hash){
        	//Attempt the top block to our chain
        	chain.addToChain(block);

        	console.log('New block added');
        	console.log(chain.getLatestBlock());
        }else{
        	// It is ahead.. we are therefore a few behind, request the whole chain
        	console.log('requesting chain');
        	broadcastMessage(REQUEST_CHAIN,"");
        }
    }

Reloading up our two test nodes on different ports and running the following on node 1

http://localhost:3003/spawnBrew/barry
http://localhost:3003/spawnBrew/bob

Node 2 successfully recieves the new blocks and adds them to it’s chain

2nodes4

Next we will add in our request chain functionality

...
	const CHAIN = "CHAIN";
...
    const messageHandler = (connection) =>{
        connection.on('message', (data) => {
            const msg = JSON.parse(data);
            switch(msg.event){
            	case REQUEST_CHAIN:
                    connection.send(JSON.stringify({ event: CHAIN, message: chain.getChain()}));
                    break;                  
                case BLOCK:
                    processedRecievedBlock(msg.message);
                    break;  
                case CHAIN:
                    processedRecievedChain(msg.message);
                    break;  
...
    const processedRecievedChain = (blocks) => {
        let sortedBlocks = blocks.sort((block1, block2) => (block1.index - block2.index))

        //Is the chain valid
        console.log('recieved a chain');
        console.log(sortedBlocks);
    }

Our server now recieves the requested chain, sorted. Before replacing our own chain for this one, we to verify it is a valid chain…

Before we do that though, I am going to implement a basic Proof of Work (for the sake of it)

Proof of work

In replacing our chain with the have the issue of consensus and knowing which chain is the ‘truth’. With blockchain, one way of helping do this is to add in the concept of ‘proof of work’. This requires a computational task to be carried out in order to generate (mine) a new block, the proof of this computation (called a nonce) is then stored in the block.

The purpose of this is to add a time(energy) consuming element/puzzle into the process of creation, which is easy for others to verify as correct.

Without this, it would be possible to spam the chain by creating a lot of blocks quickly and attempt to replace it with a chain that is longer.

The inputs to this computational task are the based on the data of the previous block/block to be formed, and an integer which is our ‘guess’.

A common way of doing this is to iterate, incrementing an integer x. In each iteration we hash our block together with x. The computation is successful when the trailing characters of the resulting hash string have 3 zeroes (or more to increase the difficulty).

Ok, to implement this we’ll begin by adding the nonce property to our genesis block

        genesisBlock = { 
            index: 0
          , timestamp: 1511818270000
          , data: 'our genesis data'
          , previousHash: "-1"
          , nonce: 0
        };

We’ll also alter our create hash function to take into account the nonce

    function createHash({ timestamp, data, index, previousHash, nonce }) {
        return Crypto.createHash('SHA256').update(timestamp+data+index+previousHash+nonce).digest('hex');
    }

Next we will alter our create block method to include a default nonce of 0. We’ll then add a new method proofOfWork, this is going to create a hash of the block. It will then examine to see if the last 3 characters of the blocks hash have 3 trailing zeroes, if not it will increment the nonce within the block and try again until it is successful before finally returning the block.

const BrewChain = function() {
...
    function createBlock(data){
        let newBlock = {
            timestamp: new Date().getTime()
          , data: data
          , index: currentBlock.index+1
          , previousHash: currentBlock.hash
          , nonce: 0
        };

        newBlock = proofOfWork(newBlock);
		
        return newBlock;
    }

    function proofOfWork(block){

        while(true){
            block.hash = createHash(block);
            if(block.hash.slice(-3) === "000"){	
                return block;
            }else{
                block.nonce++;
            }
        }
    }
...    

We can always know that the work has been done by examining that the contents of the block are valid and that the hash of the contents ends with 3 trailing zeroes.

Now we will continue on with our p2p network, checking to see if our recieved chain is valid.

To do this we will add a method to our chain which takes a new chain array. If the first element is our genesis we then iterate over each block in the chain, checking to see that it’s contents are valid and that the hash indicates proof of work took place.

We’ll also add a method for replacing the chain.

...
    function checkNewChainIsValid(newChain){
        if(createHash(newChain[0]) !== genesisBlock.hash ){
            return false;
        }

        let previousBlock = newChain[0];
        let blockIndex = 1;

        while(blockIndex < newChain.length){
            let block = newChain[blockIndex];

            if(block.previousHash !== createHash(previousBlock)){
                return false;
            }

            if(block.hash.slice(-3) !== "000"){	
                return false;
            }

            previousBlock = block;
            blockIndex++;
        }

        return true;
    }
...	
    function replaceChain(newChain){
        chain = newChain;
        currentBlock = chain[chain.length-1];
    }
...    

We’ll now invoke these methods in the brew node. The consensus rule in our brew node is the longest valid chain is the ‘truth’. We’ll therefore do a check to see if new chain is longer than our own and is valid, and if it is, replace our chain with it

...
    const processedRecievedChain = (blocks) => {
        let newChain = blocks.sort((block1, block2) => (block1.index - block2.index))

        if(newChain.length > chain.getTotalBlocks() && chain.checkNewChainIsValid(newChain)){
            chain.replaceChain(newChain);
            console.log('Chain replaced');
        }
    }
...    

The final step is to have a brew node request the chain on startup and on connecting to a new brew node, request the nodes latest block.

...
    const REQUEST_BLOCK = "REQUEST_BLOCK";
...
    case REQUEST_BLOCK:
        requestLatestBlock(connection);    
...
    const requestLatestBlock = (connection) => {
        connection.send(JSON.stringify({ event: BLOCK, message: chain.getLatestBlock()}))   
    }
...
    const initConnection = (connection) => {
        console.log('init connection');

        messageHandler(connection);
        
        requestLatestBlock(connection);

        brewSockets.push(connection);

        connection.on('error', () => closeConnection(connection));
        connection.on('close', () => closeConnection(connection));
    }
...

Our P2P network now has basic functionality, brew nodes will connect, request the latest block and replace the chain if neccesary.

The final step is to create a UI from which the brews can be made passing in the team members name and also view the chain on that node..

To be continued..

(Source available on my github - link on the right)