Skip to content

Instantly share code, notes, and snippets.

@mizchi
Created November 29, 2017 14:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mizchi/5d08bdbe96a77c129eb779053268ff20 to your computer and use it in GitHub Desktop.
Save mizchi/5d08bdbe96a77c129eb779053268ff20 to your computer and use it in GitHub Desktop.
Minimum blockchain implements: referred https://github.com/lhartikk/naivechain/blob/master/main.js
/* @flow */
import SHA256 from 'crypto-js/sha256'
type Block = {
index: number,
previousHash: string,
timestamp: number,
data: string,
hash: string
}
type BlockChain = Block[]
/* initial block chain */
export function getGenesisBlock(): Block {
return createBlock(
0,
'0',
1465154705,
'my genesis block!!',
'816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7'
)
}
export function getLatestBlock(blockchain: BlockChain): Block {
return blockchain[blockchain.length - 1]
}
/* implementation */
export function createBlock(
index: number,
previousHash: string,
timestamp: number,
data: any,
hash: string
): Block {
return {
index,
previousHash,
timestamp,
data,
hash
}
}
export function calculateHashForBlock(block: Block) {
return calculateHash(
block.index,
block.previousHash,
block.timestamp,
block.data
)
}
export function calculateHash(
index: number,
previousHash: string,
timestamp: number,
data: string = ''
): string {
return SHA256(index + previousHash + timestamp + data).toString()
}
export function generateNextBlock(
blockchain: BlockChain,
blockData: string = ''
): Block {
const previousBlock = getLatestBlock(blockchain)
const nextIndex = previousBlock.index + 1
const nextTimestamp = ~~(Date.now() / 1000)
const nextHash = calculateHash(
nextIndex,
previousBlock.hash,
nextTimestamp,
blockData
)
return {
index: nextIndex,
previousHash: previousBlock.hash,
timestamp: nextTimestamp,
data: blockData,
hash: nextHash
}
}
export function isValidNewBlock(
newBlock: Block,
previousBlock: Block
): boolean {
if (previousBlock.index + 1 !== newBlock.index) {
console.log('invalid index')
return false
} else if (previousBlock.hash !== newBlock.previousHash) {
console.log('invalid previoushash')
return false
} else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
console.log(
'invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash
)
return false
}
return true
}
export function isValidChain(blockchainToValidate: BlockChain): boolean {
// check genesis block
if (
JSON.stringify(blockchainToValidate[0]) !==
JSON.stringify(getGenesisBlock())
) {
return false
}
const tempBlocks = [blockchainToValidate[0]]
for (var i = 1; i < blockchainToValidate.length; i++) {
if (isValidNewBlock(blockchainToValidate[i], tempBlocks[i - 1])) {
tempBlocks.push(blockchainToValidate[i])
} else {
return false
}
}
return true
}
export function addBlock(blockchain: BlockChain, newBlock: Block): BlockChain {
if (isValidNewBlock(newBlock, getLatestBlock(blockchain))) {
return blockchain.concat([newBlock])
}
return blockchain
}
miner1 checks receivedBlockchain. Size: 1
miner1 add new block: 642ea53597d4d90eca7cbc9bcdd80217b76d89dae027fb70c9b881b56e9e7710
miner1 add new block: 7eebe4fd8ef821553a8a62d5e85bce6515e954f8420401b6733dafea4c9f0673
miner1 bloadcasted
miner3 checks receivedBlockchain. Size: 3
miner3 accepted received blockchain
miner3 bloadcasted
miner1 checks receivedBlockchain. Size: 3
miner1 add new block: 03cabfbaafaa9a381118cc665f505deb157ef4d49d2969f13749a6a0771e6587
miner1 add new block: f8f903b8954dd8009bc276019b6cc446b31e87cae637f6ec2e99c7c99a496d9b
miner1 bloadcasted
miner2 checks receivedBlockchain. Size: 5
miner2 accepted received blockchain
miner2 add new block: 1feac1e2d90e1252cc1bbb3bf6ec7cab4da25787cf64d03cf600d4c534309168
miner2 add new block: c59e8fa1882dcb30b0ab43c55f0121460abc655883566789b510f59d8165d4b0
miner2 bloadcasted
miner2 checks receivedBlockchain. Size: 7
miner2 bloadcasted
miner3 checks receivedBlockchain. Size: 7
miner3 accepted received blockchain
miner3 add new block: 6e4709bac242febc16827b6d2cf3f515295b74a61439640187418d68071435bf
miner3 add new block: 42ff0c9d57434a7fa444e886f8c3bb5db1bdd2216fb87a83bc141834f4533610
miner3 bloadcasted
miner3 checks receivedBlockchain. Size: 9
miner3 bloadcasted
miner1 checks receivedBlockchain. Size: 9
miner1 accepted received blockchain
miner1 add new block: 0d1200472bf18a419a41fde1ecbb3715177e1e67f7152618840d84c2b6f91b61
miner1 add new block: 4a88da04e21bd5aafa7e874fb048e1ffd28d32059b8e3dd2ee2c628ea19da266
miner1 bloadcasted
miner3 checks receivedBlockchain. Size: 11
miner3 accepted received blockchain
miner3 add new block: 4a82941ec34034446e11b0c0cbd618a420b4416d7e4a41625888a1005ec3a8b0
miner3 add new block: 6f41d94dd78e7296bb396cd8f7193c9232c44bbf7f556ea9c23c588846ebc4ed
miner3 bloadcasted
miner2 checks receivedBlockchain. Size: 13
/* @flow */
import {
getGenesisBlock,
generateNextBlock,
getLatestBlock,
addBlock,
isValidChain,
isValidNewBlock
} from './blockchain'
import range from 'lodash.range'
const blockchain = [getGenesisBlock()]
// basic use
const prev = getLatestBlock(blockchain)
const next1 = generateNextBlock(blockchain, 'foo')
const newBlockchain1 = addBlock(blockchain, next1)
const next2 = generateNextBlock(newBlockchain1, 'bar')
const newBlockchai2 = addBlock(newBlockchain1, next2)
console.log('current', newBlockchai2)
console.log('isValidChain', isValidChain(newBlockchai2))
const wait = n => new Promise(resolve => setTimeout(resolve, n))
// It should not be singleton but easy to test.
let receivedBlockchain = [getGenesisBlock()]
const bloadcast = (name, next) => {
console.log(`${name} bloadcasted`)
receivedBlockchain = next
}
const createMiner = (name: string) => {
let myBlockchain = blockchain.slice()
const accept = () => {
console.log(
`${name} checks receivedBlockchain. Size: ${receivedBlockchain.length}`
)
// console.log(receivedBlockchain.length, myBlockchain.length)
if (receivedBlockchain.length > myBlockchain.length) {
if (isValidChain(receivedBlockchain)) {
console.log(`${name} accepted received blockchain`)
myBlockchain = receivedBlockchain
} else {
console.log('Received blockchain invalid')
}
}
}
const addNewBlock = () => {
const next = generateNextBlock(myBlockchain)
myBlockchain = addBlock(myBlockchain, next)
console.log(`${name} add new block: ${next.hash}`)
}
return async () => {
while (true) {
await wait(Math.random() * 3000)
accept()
range(~~(Math.random() * 3)).forEach(_ => addNewBlock())
bloadcast(name, myBlockchain)
}
}
}
// competitive miners
createMiner('miner1')()
createMiner('miner2')()
createMiner('miner3')()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment