Skip to content

Instantly share code, notes, and snippets.

@jdkanani
Last active August 20, 2018 10:25
Show Gist options
  • Save jdkanani/e3a540d3e957ca55ce46582015f9253c to your computer and use it in GitHub Desktop.
Save jdkanani/e3a540d3e957ca55ce46582015f9253c to your computer and use it in GitHub Desktop.
testchain - Javascript test blockchain (testing purpose only)

Quick start

(set up two connected nodes and mine 1 block)

npm install
HTTP_PORT=3001 P2P_PORT=6001 npm start
HTTP_PORT=3002 P2P_PORT=6002 PEERS=ws://localhost:6001 npm start
curl -H "Content-type:application/json" --data '{"data" : "Some data to the first block"}' http://localhost:3001/mineBlock

HTTP API

Get blockchain
curl http://localhost:3001/blocks
Create block
curl -H "Content-type:application/json" --data '{"data" : "Some data to the first block"}' http://localhost:3001/mineBlock
Add peer
curl -H "Content-type:application/json" --data '{"peer" : "ws://localhost:6001"}' http://localhost:3001/addPeer

Query connected peers

curl http://localhost:3001/peers
'use strict';
var crypto = require("crypto-js");
var express = require("express");
var bodyParser = require("body-parser");
var WebSocket = require("ws");
// HTTP port
var httpPort = process.env.HTTP_PORT || 3000;
var p2pPort = process.env.P2P_PORT || 6000;
var peers = process.env.PEERS ? process.env.PEERS.split(',') : [];
// Blockchain (in memory) - array of blocks
var blockchain = [];
var sockets = [];
var MessageType = {
QUERY_LATEST: 0,
QUERY_ALL: 1,
RESPONSE_BLOCKCHAIN: 2
};
// Block
class Block {
constructor(index, previousHash, timestamp, data, hash) {
this.index = index;
this.previousHash = previousHash.toString();
this.timestamp = timestamp;
this.data = data;
this.hash = hash.toString();
}
calculateHash() {
return chain.calculateHash(this.index, this.previousHash, this.timestamp, this.data);
}
}
// chain object
var chain = {
calculateHash: (index, previousHash, timestamp, data) => {
return crypto.SHA256(index + previousHash + timestamp + data).toString();
},
calculateHashBlock: (block) => {
return crypto.SHA256(block.index + block.previousHash + block.timestamp + block.data).toString();
},
// Block number
blockNumber: () => {
return blockchain.length - 1;
},
// Latest block
latestBlock: () => {
return blockchain[blockchain.length - 1];
},
// Generate genesis block
generateGenesisBlock: () => {
return new Block(0, "0", 1465154705, "First test blockchain", "3f52d6da5790b8bb7fb000cfa395cee4a087b3a855987b53edf47d3f1b2b2200");
},
// Generate block
generateBlock: (blockData) => {
var previousBlock = chain.latestBlock();
var index = previousBlock.index + 1;
var timestamp = new Date().getTime() / 1000;
var hash = chain.calculateHash(index, previousBlock.hash, timestamp, blockData);
return new Block(index, previousBlock.hash, timestamp, blockData, hash);
},
addBlock: (newBlock) => {
if (chain.isNewBlockValid(newBlock, chain.latestBlock())) {
blockchain.push(newBlock);
}
},
isNewBlockValid: (newBlock, previousBlock) => {
if (previousBlock.index + 1 !== newBlock.index) {
console.log('invalid index');
return false;
}
if (previousBlock.hash !== newBlock.previousHash) {
console.log('invalid previoushash');
return false;
}
var newBlockHash = chain.calculateHashBlock(newBlock);
if (newBlockHash !== newBlock.hash) {
console.log(typeof (newBlock.hash) + ' ' + typeof newBlockHash);
console.log('invalid hash: ' + newBlockHash + ' ' + newBlock.hash);
return false;
}
return true;
},
isValidChain: (blockchainToValidate) => {
if (JSON.stringify(blockchainToValidate[0]) !== JSON.stringify(chain.generateGenesisBlock())) {
return false;
}
var tempBlocks = [blockchainToValidate[0]];
for (var i = 1; i < blockchainToValidate.length; i++) {
if (chain.isNewBlockValid(blockchainToValidate[i], tempBlocks[i - 1])) {
tempBlocks.push(blockchainToValidate[i]);
} else {
return false;
}
}
return true;
},
replaceChain: (newBlocks) => {
if (chain.isValidChain(newBlocks) && newBlocks.length > blockchain.length) {
console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');
blockchain = newBlocks;
wobj.broadcast(wobj.responseLatestMsg());
} else {
console.log('Received blockchain invalid');
}
}
};
// WS Object
var wobj = {
p2pServer: () => {
var server = new WebSocket.Server({port: p2pPort});
server.on('connection', ws => wobj.initConnection(ws));
console.log('Listening websocket p2p port on: ' + p2pPort);
},
initConnection: (ws) => {
sockets.push(ws);
wobj.messageHandler(ws);
wobj.errorHandler(ws);
wobj.write(ws, wobj.queryChainLengthMsg());
},
queryChainLengthMsg: () => {
return {
'type': MessageType.QUERY_LATEST
}
},
queryAllMsg: () => {
return {
'type': MessageType.QUERY_ALL
}
},
responseChainMsg: () => {
return {
'type': MessageType.RESPONSE_BLOCKCHAIN, 'data': JSON.stringify(blockchain)
}
},
responseLatestMsg: () => {
return {
'type': MessageType.RESPONSE_BLOCKCHAIN,
'data': JSON.stringify([chain.latestBlock()])
}
},
write: (ws, message) => ws.send(JSON.stringify(message)),
broadcast: (message) => sockets.forEach(socket => wobj.write(socket, message)),
messageHandler: (ws) => {
ws.on('message', (data) => {
var message = JSON.parse(data);
console.log('Received message' + JSON.stringify(message));
switch (message.type) {
case MessageType.QUERY_LATEST:
wobj.write(ws, wobj.responseLatestMsg());
break;
case MessageType.QUERY_ALL:
wobj.write(ws, wobj.responseChainMsg());
break;
case MessageType.RESPONSE_BLOCKCHAIN:
wobj.handleBlockchainResponse(message);
break;
}
});
},
errorHandler: (ws) => {
var closeConnection = (ws) => {
console.log('connection failed to peer: ' + ws.url);
sockets.splice(sockets.indexOf(ws), 1);
};
ws.on('close', () => closeConnection(ws));
ws.on('error', () => closeConnection(ws));
},
handleBlockchainResponse: (message) => {
var receivedBlocks = JSON.parse(message.data).sort((b1, b2) => (b1.index - b2.index));
var latestBlockReceived = receivedBlocks[receivedBlocks.length - 1];
var latestBlockHeld = chain.latestBlock();
if (latestBlockReceived.index > latestBlockHeld.index) {
console.log('blockchain possibly behind. We got: ' + latestBlockHeld.index + ' Peer got: ' + latestBlockReceived.index);
if (latestBlockHeld.hash === latestBlockReceived.previousHash) {
console.log("We can append the received block to our chain");
blockchain.push(latestBlockReceived);
wobj.broadcast(wobj.responseLatestMsg());
} else if (receivedBlocks.length === 1) {
console.log("We have to query the chain from our peer");
wobj.broadcast(wobj.queryAllMsg());
} else {
console.log("Received blockchain is longer than current blockchain");
chain.replaceChain(receivedBlocks);
}
} else {
console.log('received blockchain is not longer than received blockchain. Do nothing');
}
},
connectToPeers: (newPeers) => {
newPeers.forEach((peer) => {
var ws = new WebSocket(peer);
ws.on('open', () => wobj.initConnection(ws));
ws.on('error', () => {
console.log('connection failed')
});
});
}
};
// HTTP server
var httpServer = () => {
var app = express();
app.use(bodyParser.json());
// Get all blocks
app.get('/blocks', (req, res) => {
res.send(JSON.stringify(blockchain))
});
app.post('/mineBlock', (req, res) => {
var newBlock = chain.generateBlock(req.body.data);
chain.addBlock(newBlock);
wobj.broadcast(wobj.responseLatestMsg());
console.log('block added: ' + JSON.stringify(newBlock));
res.send();
});
app.get('/peers', (req, res) => {
res.send(sockets.map(s => s._socket.remoteAddress + ':' + s._socket.remotePort));
});
app.post('/addPeer', (req, res) => {
wobj.connectToPeers([req.body.peer]);
res.send();
});
app.listen(httpPort, () => console.log('Listening http on port: ' + httpPort));
};
// add genesis block
blockchain.push(chain.generateGenesisBlock());
// start HTTP server
httpServer();
// connect to peers
wobj.connectToPeers(peers);
// Init p2p server
wobj.p2pServer();
{
"name": "testchain",
"version": "1.0.0",
"main": "main.js",
"repository": "https://gist.github.com/jdkanani/e3a540d3e957ca55ce46582015f9253c",
"author": "Jaynti Kanani <jdkanani@gmail.com>",
"license": "MIT",
"scripts": {
"start": "node main.js"
},
"dependencies": {
"body-parser": "^1.17.2",
"crypto-js": "^3.1.9-1",
"express": "^4.15.3",
"ws": "^3.0.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment