区块链的目的之一是让我们所需要的“有价值”的信息得以保存且不可更改,这些信息都储存在一个叫做“区块(block)”的结构中。以比特币为例,被认为是有价值的信息是“交易”,所有的交易储存在区块中,通过区块的hash、时间戳等实现信息的可追溯以及不可更改性。

我们这里首先实现的是一个简易的区块链,并不是像比特币这样成熟的区块链,暂不涉及交易结构、验证以及UTXO。

一、区块(block)

首先,创建一个区块(Block)类。我们的区块仅包含了一部分关键信息,它的结构如下:
class Block{ constructor(data){ this.hash = "", // 当前区块的Hash值 this.height = 0,
// 当前区块的高度 this.body = data, // 区块实际存储的有效信息 this.time = 0, // 时间戳,即区块创建的时间
this.previousBlockHash = ""。// 前一个区块的Hash值 } }

这里,hash、height、time和previousBlockHash在比特币规范中属于区块头(header),区块头是一个单独的数据结构,而body部分用于储存交易信息或者其他数据。

二、区块链(blockchain)

首先,我们要把区块存在哪里?我们可以把区块直接放进一个数组(let blockchain =
arr[]),但是这显然不是我们想要的。这里我们选择leveldb作为底层的数据储存方案。
const level = require('level'); const chainDB = './.data/blockchain'; const db
= level(chainDB);
然后,我们要思考一下,一个blockchain实例需要具有哪些功能?

1、getBlockHeight():获取当前区块的高度,即该区块链的高度;
2、getBlockByHeight(height):通过区块高度来获取区块;
3、getBlockByHash(hash):通过区块哈希来获取区块;
4、validateBlock(height):验证某高度的区块是否有效;
5、validateChain():验证整条链的每个区块是否均有效;


通过上面几个函数,我们定义了对一个区块链(blockchain)的基本描述,我们通过当前区块的高度获取当前区块的hash和前一个区块的hash,进而可以向前回溯出整个区块链。

下面我们看看怎么实现?

首先,Blockchain要包括如下的方法:
class Blockchain { addBlock(); getBlockHeight(); getBlockByHeight();
getBlockByHash(); validateBlock(); validateChain(); }

我们要保证系统中只有一个Blockchain实例,可以使用ES6语法class的static关键词来设定一个静态方法来储存Blockchain实例。在ES6的class中,加了static关键词的方法不会被实例所继承,只能通过class来调用,如Blockchain.getInstance()。
static getInstance() { if (!Blockchain.instance) { Blockchain.instance = new
Blockchain(); return Blockchain.instance.getBlockHeight() .then((height) => {
if (height === 0) { const initialBlock = new Block('First block of the
blockchain'); return Blockchain.instance.addBlock(initialBlock); } }) .then(()
=> Blockchain.instance) } return Promise.resolve(Blockchain.instance); }
getBlockHeight()

通过递归调用count函数,从创始区块一直计数到最后一个区块,得到待增新区块的高度。
getBlockHeight() { let count = function (key) { return db.get(key) .then(() =>
count(key + 1)) .catch(() => key); }; return count(0); }
getBlockByHeight()
getBlockByHeight(){ return db.get(height).then((value) => JSON.parse(value)); }
addBlock(newBlock)

新区块的previousBlockHash应等于前一个区块的Hash,这样就将两个区块链接到了一起。
addBlock(newBlock) { return this.getBlockHeight() .then((height) => { let
PrevBlock; newBlock.height = height; // 新区块的高度 newBlock.time = new
Date().getTime().toString().slice(0, -3); // 新区块时间戳 // 得到新区块的previousBlockHash
if (height > 0) { PrevBlock = this.getBlock(height - 1) .then((previousBlock)
=> { newBlock.previousBlockHash = previousBlock.hash; }); } return
Promise.all([PrevBlock]) .then(() => { newBlock.hash =
SHA256(JSON.stringify(newBlock)).toString(); return db.put(height,
JSON.stringify(newBlock)); }); }) .then(() => Blockchain.instance); }
getBlockByHash()

可以通过类似于getBlockHeight()
的思路进行递归,比较该区块的hash于输入的参数hash是否相等。然而,我们还有另外一种选择。leveldb给我提供了db.createReadStream()方法来逐个读取数据库中的条目。
getBlockByHash(hash){ return new Promise((resolve, reject) => { let block;
db.createReadStream() .on("data", (data) => { if(data.key != 'height'){ let
value = JSON.parse(data.value); let blockHash = value.hash; if (blockHash ==
hash) { block = value; } } }) .on("error", (err) => { reject(err); })
.on("close", ()=>{ resolve(block); // 如果没有满足条件的,则block值为undefined,在下一步就会抛出错误 })
}) }
validateBlock(blockHeight)

验证一个区块,就是要重新生成该区块的hash,比较该hash与区块本身hash属性的值是否相等。
validateBlock(blockHeight){ return db.get(blockHeight).then(function(value){
let block = JSON.parse(value); let blockHash = block.hash; // remove block hash
to test block integrity block.hash = ''; // generate block hash let
validBlockHash = SHA256(JSON.stringify(block)).toString(); // Compare if
(blockHash === validBlockHash) { return true } else { console.log('Block
#'+blockHeight+' invalid hash:\n'+blockHash+'<>'+validBlockHash); return false
} }).catch(function(err){ console.log('Not found!', err); }) }
validateChain()

验证整个链的所有区块,这就肯定要用到for循环。
validateChain(){ let errorLog = []; this.getBlockHeight().then(height =>{ for
(var i = 0; i <= height; i++) { this.getBlock(i).then(block => { // validate
block let h = block.height this.validateBlock(h).then(val => { if(!val)
errorLog.push(h) }) // compare blocks hash link let hash = block.hash; let n =
block.height + 1; if( n <= value ) { db.get(n, (err, val) => { let nextBlock =
JSON.parse(val) let preHash = nextBlock.previousBlockHash if(hash !== preHash)
{ errorLog.push(n-1); } if(n == value) { if(errorLog.length>0){
console.log('\n-------Errors Detected!-------\n') console.log('Block errors = '
+ errorLog.length); console.log('Blocks: '+ errorLog); } else { console.log('No
errors detected'); } } }) } }) } }).catch(function(err){ console.log('Not
found!', err); }) }
总结

通过上面简单的代码,我们实现了一个简单的区块链结构,这是一个private
chain,没有p2p网络,没有挖矿需要的共识算法。我们关注的是基本的区块链的储存结构,即有效数据储存在block中,每一个block通过hash值关联成一条链,我们通过区块的高度即可从数据库leveldb中读取区块的数据。