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中读取区块的数据。

你可能感兴趣的:(nodejs区块链)