Advertisement

NodeJS区块链实践(1)Nodejs搭建简易区块链

阅读量:

区块链的目的之一是让我们所需要的“有价值”的信息得以保存且不可更改,这些信息都储存在一个叫做“区块(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中读取区块的数据。

全部评论 (0)

还没有任何评论哟~