NodeJSブロックチェーンの実践(1)Nodejsは簡易ブロックチェーンを構築する.


ブロックチェーンの目的の一つは、私たちが必要とする「価値ある」情報を保存し、変更することができないようにすることです.これらの情報はすべて「ブロック」という構造の中に保存されています.ビットコインを例にとると、価値のある情報は「取引」と見なされ、すべての取引はブロックに格納され、ブロックのhash、タイムスタンプなどを通じて情報の遡及及び変更不可性が実現される.
ここでまず実現したのは簡単なブロックチェーンで、ビットコインのような成熟したブロックチェーンではなく、取引構造、検証及びUTXOにはしばらく触れません.
一、ブロック(block)
まず、ブロッククラスを作成します.私達のブロックには一部のキー情報しか含まれていません.その構造は以下の通りです.
class Block{
  constructor(data){
     this.hash = "",  //      Hash 
     this.height = 0,  //        
     this.body = data,  //            
     this.time = 0,  //    ,        
     this.previousBlockHash = ""。//       Hash 
  }
}
ここで、hash、height、timeおよびprevious BlockHashはビットコイン仕様でブロックヘッダに属し、ブロックヘッダは個々のデータ構造であり、一方、body部分は取引情報または他のデータを保存するために使用される.
二、ブロックチェーン(blockchain)
まず、ブロックはどこにありますか?ブロックを直接配列に入れることができますが、これは明らかに私たちがほしいものではありません.ここではleveldbを下のデータストア方式として選択した.
const level = require('level');
const chainDB = './.data/blockchain';
const db = level(chainDB);
次に、blockchainの実例はどのような機能が必要ですか?
1、get BlockHeight():現在のブロックの高さ、すなわち当該ブロックチェーンの高さを取得する.2、get BlockByHeight:ブロックの高さでブロックを取得する;3、getBlockByHash:ブロックハッシュによってブロックを取得する.4、validateBlock:ある高さのブロックが有効かどうかを検証する;5、validateCharin():チェーン全体のブロックが有効かどうかを検証する;
上記のいくつかの関数により、ブロックチェーンの基本的な説明を定義し、現在のブロックの高さから現在のブロックのhashと前のブロックのhashを取得し、さらにブロックチェーン全体を前に遡ることができます.
これからどうやって実現しますか?
まず、Blockchainには以下の方法が含まれています.
class Blockchain {
  addBlock();
  getBlockHeight();
  getBlockByHeight();
  getBlockByHash();
  validateBlock();
  validateChain();
}
私たちは、システムの中でブロックchainの一例しかないことを保証します.ES 6文法classのstaticキーワードを使って、スタティックな方法を設定してBlockchainのインスタンスを保存します.ES 6のクラスでは、staticのキーワードを加えた方法はインスタンスに引き継がれず、Blockchain.get Instance()のようなクラスだけで呼び出されます.
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);    
}
get BlockHeight()
count関数を再帰的に呼び出すことにより、創始ブロックから最後のブロックまでカウントし、新たなブロックの高さを得る.
getBlockHeight() {
    let count = function (key) {
       return db.get(key)
            .then(() => count(key + 1))
            .catch(() => key);
    };
    return count(0);
}
get BlockByHeight()
getBlockByHeight(){
    return db.get(height).then((value) => JSON.parse(value));
}
addBlock(newBlock)
新しいブロックのprevious BlockHashは前のブロックの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);
}
get BlockByHash()
get BlockHeight()に似た考え方で再帰することができ、このブロックのhashが入力のパラメータhashに等しいかどうかを比較することができる.しかし、もう一つの選択があります.leveldbは、データベースの項目を一つ一つ読み取るためのdb.create ReadStream()方法を提供してくれました.
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)
ブロックを検証すると、当該ブロックのshを再生成し、当該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:
'+blockHash+'<>'+validBlockHash); return false } }).catch(function(err){ console.log('Not found!', err); }) }
validateCharin()
チェーン全体のブロックを検証します.これは必ず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('
-------Errors Detected!-------
') console.log('Block errors = ' + errorLog.length); console.log('Blocks: '+ errorLog); } else { console.log('No errors detected'); } } }) } }) } }).catch(function(err){ console.log('Not found!', err); }) }
締め括りをつける
上の簡単なコードを通じて、私達は簡単なブロックチェーン構造を実現しました.これはprvate chainで、p 2 pネットワークがなく、採掘に必要な共通認識アルゴリズムがありません.私たちが注目するのは基本的なブロックチェーンの保存構造、すなわち有効データはブロックに格納され、各ブロックはhash値によってチェーンに関連しています.ブロックの高さを通じてデータベースleveldbからブロックのデータを読み取ることができます.