美文网首页
最简区块链Demo | 基于NodeJS实现区块链公有链

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

作者: stevekeol | 来源:发表于2020-08-04 17:29 被阅读0次

    背景

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

    最终效果

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

    实现流程

    1. 构造一个模拟 区块 的类
    • 每个区块拥有区块头和区块体两部分;

    • 区块头有如下几个关键字段:height - 高度,previousHash - 上一个区块的哈希值,timestamp - 当前时间戳, Nonce - 一个随机数,MerkelRoot - 梅克尔树根的哈希值,hash - 当前区块的哈希;

    • 区块体data中包括了:在当前这一段时间内发生的,并且被加入到该区块的交易;

    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. 构造一个模拟 区块链 的类

    区块链类拥有的功能:

    • 创建 “创世区块”;

    • 根据当前区块的各个字段和区块体的交易数据,计算对应的哈希值;

    • 得到区块链中最后一个块节点;

    • 计算当前链表的下一个区块;

    • 判断新加入的块是否合法;

    • 判断height是否连续且自增;

    • 判断前后区块的哈希是否相连;

    • 向区块链新增节点;

    • 插入新的短链表时判断是否合法,如何插入;

    • 附加功能:保存/展示区块链的数据;

    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. 测试上述代码
    • 模拟生成区块体中的交易数据;

    • 新建一个区块链条;

    • 定时(每隔2秒)模拟生成一个区块,并添加到区块链中;

    • 同时打印该区块链

    //生成模拟的区块体交易数据
    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

    运行方式

    • 下载源码: git clone https://github.com/stevekeol/YunDang-Chain
    • 切换至对应目录,并安装依赖项: cd yundang-chain/demo && npm install
    • 运行: npm start

    作者简介

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

    相关文章

      网友评论

          本文标题:最简区块链Demo | 基于NodeJS实现区块链公有链

          本文链接:https://www.haomeiwen.com/subject/borirktx.html