最简区块链Demo | 基于NodeJS实现区块链公有链

2020-08-04  本文已影响0人  stevekeol

背景

对于大多数开发者,区块链行业不容易在第一时间形成初步的思维印象。本文旨在在最短时间内,给与刚入门的开发者一个最简的区块链Demo。

最终效果

最终效果:每隔2s模拟一个区块的产生

实现流程

1. 构造一个模拟 区块 的类
class Block {
  /**
   * 构造函数
   * @param {Number} height 
   * @param {String} previousHash 
   * @param {Number} timestamp 
   * @param {*} data 
   * @param {String} hash 
   */
  constructor(height, previousHash, timestamp, data, hash) {
    this.height = height
    this.previousHash = previousHash + ''
    this.timestamp = timestamp
    this.data = data
    this.hash = hash + ''
  }

  static generateBlock(blockData, previousBlock) {
    const nextHeight = previousBlock.height + 1;
    const nextTimeStamp = new Date().getTime();
    //暂时忽略MerkelRoot和Nonce
    const nextHash = CryptoJS.SHA256(nextHeight + previousBlock.hash + nextTimeStamp + blockData) + ''; 
    return new Block(nextHeight, previousBlock.hash, nextTimeStamp, blockData, nextHash);    
  }
}
2. 构造一个模拟 区块链 的类

区块链类拥有的功能:

class BlockChain {

  /**
   * 如果指定在历史BlockChain上继续增加区块,则从本地存储中取出;否则默认创建新的区块链
   * @param { string } historyChain
   */
  constructor(historyChain) {
    this.blocks = [this.getGenesisBlock()]
  }

  // constructor(historyChain) {
  //   if( historyChain) {
  //     let blocks = this.getHistoryChain(historyChain);
  //     this.blocks = blocks ? [blocks] : [this.getGenesisBlock()];
  //   } else {
  //     this.blocks = [this.getGenesisBlock()]
  //   }
  // }

  /**
   * 将区块链异步保存到文件中
   */
  async saveHistoryChain(file, block) {
    await fs.appendFile(file, JSON.stringify(block), err => {
      if (err) throw err;
      console.log(`Height: ${block.height} is saved.`);
    });    
  }

  /**
   * 从文件中同步读出历史区块数据
   */
  // getHistoryChain() {
  //   return fs.readFileSync('historyChain.txt', 'utf-8');
  // }  

  /**
   * 创建区块链起源块, 此块是硬编码(取比特币高度:642022, 对应的时间是2020-08-03 17:00)
   */
  getGenesisBlock() {
    return new Block(0, '0', 1595490064640, 'GenesisBlock', '0000000000000000000d87bedef9550a014af9a3af74b791d84d049cc3ca85f4')
  }

  /**
   * 根据信息计算hash值
   */
  calcuteHash(height, previousHash, timestamp, data) {
    return CryptoJS.SHA256(height + previousHash + timestamp + data) + ''
  }

  /**
   * 得到区块链中最后一个块节点
   */
  getLatestBlock() {
    return this.blocks[this.blocks.length - 1]
  }

  /**
   * 计算当前链表的下一个区块
   * @param {*} blockData 
   */
  generateNextBlock(blockData) {
    const previousBlock = this.getLatestBlock()
    const nextIndex = previousBlock.height + 1
    const nextTimeStamp = new Date().getTime()
    const nextHash = this.calcuteHash(nextIndex, previousBlock.hash, nextTimeStamp, blockData)
    return new Block(nextIndex, previousBlock.hash, nextTimeStamp, blockData, nextHash)
  }

  /**
   * 判断新加入的块是否合法
   * @param {Block} newBlock 
   * @param {Block} previousBlock 
   */
  isValidNewBlock(newBlock, previousBlock) {
    if(
      !(newBlock instanceof Block) ||
      !(previousBlock instanceof Block)
    ) {
      return false
    }

    // 判断height
    if(newBlock.height !== previousBlock.height + 1) { 
      return false
    }

    // 判断hash值
    if(newBlock.previousHash !== previousBlock.hash) { 
      return false
    }

    // 计算新块的hash值是否符合规则
    if(this.calcuteHash(newBlock.height, newBlock.previousHash, newBlock.timestamp, newBlock.data) !== newBlock.hash) { 
      return false
    }

    return true
  }
  
  /**
   * 向区块链添加新节点
   * @param {Block} newBlock 
   */
  addBlock(newBlock) {
    if(this.isValidNewBlock(newBlock, this.getLatestBlock())) {
      this.blocks.push(newBlock)
      return true  
    }
    return false
  }

  /**
   * 判断新插入的区块链是否合法而且可以覆盖原来的节点
   * @param {Array} newChain 
   */
  isValidNewChain(newChain) {
    if(Array.isArray(newChain) === false || newChain.length === 0) {
      return false
    }

    let newChainLength = newChain.length,
      firstBlock = newChain[0]

    // 硬编码的起源块不能改变
    if(firstBlock.height === 0) {
      return false
    }

    // 移植新的链的长度 <= 现有链的长度
    // 新的链不可信
    if(newChainLength + firstBlock.height <= this.blocks.length) {
      return false
    }

    // 下面检查新的链能否移植
    // 以及新的链的每个节点是否符合规则
    if(!this.isValidNewBlock(firstBlock, this.blocks[firstBlock.height - 1])) {
      return false
    }

    for(let i = 1; i < newChainLength; ++i) {
      if(!this.isValidNewBlock(newChain[i], newChain[i - 1])) {
        return false
      }
    }

    return true
  }

  /**
   * 插入新链表
   * @param {Array} newChain 
   */
  addChain(newChain) {
    if(this.isValidNewChain(newChain)) {
      const height = newChain[0].height
      this.blocks.splice(height)
      this.blocks = this.blocks.concat(newChain)
      return true
    }
    return false
  }

  //打印该区块链的所有区块
  printBlockChain() {
    console.table(this.blocks);
  }

  //打印该区块链的最新区块
  printLastBlock() {
    console.table(this.blocks[this.blocks.length - 1]);
  }  
}
3. 测试上述代码
//生成模拟的区块体交易数据
function generateBlockData() {
  const dataList = ['Zhangjie is cool', 'Pengxiaohua is cool', 'ChenZiqiang is cool', 'Fangguojun is cool', 'Lulina is beautiful', 'Maqicheng is cool', 'Wangchuanshuo is cool', 'Linshaoyuan is beautiful', 'Lulina is beautiful'];
  return dataList[Math.random() * dataList.length >> 0];
}

function mockBlocks() {
  //实例化一个区块链
  const blockChain = new BlockChain('testNet');

  blockChain.printLastBlock();

  let newBlock;
  setInterval(() => {
    newBlock = Block.generateBlock(generateBlockData(), blockChain.getLatestBlock());
    blockChain.addBlock(newBlock);
    blockChain.printBlockChain();
  }, 2000);

}

//开启模拟区块
mockBlocks();

完整源码

基于NodeJS的完整可运行的最简区块链Demo

运行方式

作者简介

stevekeol】毕业于三墩职业技术学院的一名loser。求索于区块链行业四五载,拥有近乎信仰级别的热爱。近期已提上日程的计划是打造一款 基于NodeJS的全节点公有链: YunDang-Chain。如需交流,参考 项目源码

上一篇下一篇

猜你喜欢

热点阅读