Do you want to build a blockchain?
James Allardice
orangejellyfish
James Allardice
> git clone git@github.com:orangejellyfish/jellycoin
> git checkout boilerplate
James Allardice
James Allardice
index
0
James Allardice
index
timestamp
0
1536846506393
James Allardice
index
timestamp
0
1536846506393
data
foo
James Allardice
export default class Block {
constructor(index, data) {
this.index = index;
this.data = JSON.stringify(data);
this.timestamp = new Date().getTime();
}
}
James Allardice
index
timestamp
0
1536846506393
data
foo
hash
2c26b46b6...
James Allardice
f(x) = y
James Allardice
f(x) = y
digest
"a" = ca978112ca1bbdcafac231b39a23dc4da7...
James Allardice
f(x) = y
"foo" = 2c26b46b68ffc68ff99b453c1d30413413...
64 characters
"a" = ca978112ca1bbdcafac231b39a23dc4da7...
64 characters
James Allardice
f(x) = y
"foo" = 2c26b46b68ffc68ff99b453c1d30413413...
"Foo" = 1cbec737f863e4922cee63cc2ebbfaafcd...
64 characters
"a" = ca978112ca1bbdcafac231b39a23dc4da7...
64 characters
64 characters
James Allardice
import crypto from 'crypto';
export default class Block {
constructor(index, data) {
this.index = index;
this.data = JSON.stringify(data);
this.timestamp = new Date().getTime();
this.hash = this.createHash();
}
createHash() {
return crypto
.createHash('sha256')
.update(this.index + this.data + this.timestamp)
.digest('hex');
}
}
James Allardice
index
timestamp
1
153716836841
data
bar
hash
fcde2b2ed...
previousHash
2c26b46b6...
James Allardice
index
timestamp
1
153716836841
data
bar
hash
fcde2b2ed...
previousHash
2c26b46b6...
index
timestamp
0
1536846506393
data
foo
hash
2c26b46b6...
James Allardice
alice pays bob 100
charlie pays bob 50
dan pays alice 250
4f013941...
ea49c9e5...
0491b0a5...
00000000...
4f013941...
ea49c9e5...
James Allardice
alice pays eve 100
charlie pays bob 50
dan pays alice 250
4f013941...
ea49c9e5...
0491b0a5...
00000000...
4f013941...
ea49c9e5...
James Allardice
alice pays eve 100
charlie pays bob 50
dan pays alice 250
aa190473...
ea49c9e5...
0491b0a5...
00000000...
4f013941...
ea49c9e5...
James Allardice
alice pays eve 100
charlie pays bob 50
dan pays alice 250
aa190473...
ea49c9e5...
0491b0a5...
00000000...
aa190473...
ea49c9e5...
James Allardice
import crypto from 'crypto';
export default class Block {
constructor(index, data, previousHash) {
this.index = index;
this.data = JSON.stringify(data);
this.timestamp = new Date().getTime();
this.previousHash = previousHash;
this.hash = this.createHash();
}
createHash() {
return crypto
.createHash('sha256')
.update(this.index + this.data + this.timestamp + this.previousHash)
.digest('hex');
}
}
James Allardice
export default class Transaction {
constructor(from, to, amount) {
this.from = from;
this.to = to;
this.amount = amount;
}
}
James Allardice
import Block from '../block';
import Transaction from '../transaction';
export default class Blockchain {
constructor(blockchain) {
if (blockchain) {
this.chain = blockchain.chain;
} else {
this.chain = [];
this.createGenesisBlock();
}
}
createGenesisBlock() {
const genesisTransaction = new Transaction(null, 'alice', 50);
const genesisBlock = new Block(0, genesisTransaction, '0'.repeat(64));
this.chain.push(genesisBlock);
}
}
James Allardice
const Block = require('../block');
class Blockchain {
// ...
createBlock(data) {
const previousBlock = this.chain[this.chain.length - 1];
const nextIndex = previousBlock.index + 1;
const nextBlock = new Block(nextIndex, data, previousBlock.hash);
this.chain.push(nextBlock);
return nextBlock;
}
}
James Allardice
const Block = require('../block');
class Blockchain {
// ...
validateBlock(block) {
const previousBlock = this.getLatestBlock();
return block.previousHash === previousBlock.hash
&& block.index === previousBlock.index + 1;
}
}
James Allardice
import Blockchain from './blockchain';
const chain = new Blockchain();
James Allardice
server.get('/blockchain', (req, res) => {
res.json(chain);
});
James Allardice
server.post('/block', (req, res) => {
console.log('\nCreating new block...');
const nextBlock = chain.createBlock(
new Transaction(req.body.from, req.body.to, req.body.amount),
);
return res.sendStatus(201);
});
James Allardice
> curl localhost:1234/blockchain
James Allardice
> curl -XPOST -H 'Content-Type: application/json' -d '{"from": "x", "to": "y", "amount": 100 }' -i localhost:1234/block
James Allardice
Proof of work
James Allardice
"a" = ca978112ca1bbdcafac231b39a...
Proof of work
James Allardice
"a" = ca978112ca1bbdcafac231b39a...
00000...
Proof of work
James Allardice
"a" = ca978112ca1bbdcafac231b39a...
00000...
"a" + 1 = f55ff16f66f43360266b95db6f...
00000...
Proof of work
James Allardice
"a" = ca978112ca1bbdcafac231b39a...
00000...
"a" + 1 = f55ff16f66f43360266b95db6f...
00000...
"a" + 2 = 2c3a4249d77070058649dbd822...
00000...
Proof of work
James Allardice
"a" + 862181 = f2c0efd549955f6bee06bb4266...
00000...
"a" + 862182 = e6d42d1554bfa7a813adfa0784...
00000...
"a" + 862183 = 000008a7d97881343ade2a4fa7...
00000...
Proof of work
James Allardice
export default class Block {
constructor(index, data, previousHash) {
// ...
this.nonce = 0;
}
// ...
findNonce(difficulty) {
const zeroes = '0'.repeat(difficulty);
while (!this.hash.startsWith(zeroes)) {
this.nonce++;
this.hash = this.createHash();
}
}
};
James Allardice
export default class Blockchain {
// ...
createBlock(data) {
const previousBlock = this.chain[this.chain.length - 1];
const nextIndex = previousBlock.index + 1;
const nextBlock = new Block(nextIndex, data, previousBlock.hash);
nextBlock.findNonce(this.difficulty);
this.chain.push(nextBlock);
}
};
James Allardice
P2P
James Allardice
P2P
Find the longest chain
James Allardice
P2P
Find the longest chain
Broadcast new blocks
James Allardice
P2P
Find the longest chain
Broadcast new blocks
Receive and validate new blocks
James Allardice
function handleReceiveBlock(peer, packet) {
// A peer has created a new block. We need to validate the block in
// its own right to ensure the peer has got the correct proof-of-work
// nonce. We also need to check whether we can append the block to the
// chain we currently hold. If our chain is more than one block behind
// we'll have to ask peers to send their latest version.
const newBlock = new Block(packet.data);
console.log('\nReceived new block...');
if (newBlock.isValid()) {
console.log('New block is valid...');
if (chain.validateBlock(newBlock)) {
console.log('New block is valid for our current chain... appending');
return chain.append(newBlock);
}
console.log('New block is invalid for our current chain...');
return broadcast({ message: 'GET_BLOCKCHAIN' });
}
console.log('New block is invalid... ignoring');
}
James Allardice
function handleGetBlockchain(peer) {
console.log('\nPeer requested full chain... sending');
message(peer, { message: 'BLOCKCHAIN', data: chain });
}
James Allardice
function handleReceiveBlockchain(peer, packet) {
// A peer has sent us their current copy of the chain. If theirs is
// longer than ours we can replace ours.
//
// TODO: validate the received chain in full.
//
console.log('\nReceived chain from peer...');
const newChain = new Blockchain(packet.data);
if (newChain.getLatestBlock().index > chain.getLatestBlock().index) {
console.log('Received chain is longer... replacing');
chain = newChain;
} else {
console.log('Received chain is not longer... ignoring');
}
}
James Allardice
James Allardice
What next?
Grouping many transactions into a block
Dynamic difficulty to ensure uniform block mining intervals
Public/private keys, addresses and signing
James Allardice
Thank you!
James Allardice
orangejellyfish
Code: https://github.com/orangejellyfish/jellycoin
Slides: https://slides.com/jamesallardice/do-you-want-to-build-a-blockchain-devconf18