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

前言

注:文末有源码地址

"凡是可以用 JavaScript 来写的应用,最终都会用 JavaScript 来写。" ——Atwood定律

【本文重心】: 如何以最简代码打造一款区块链基于NodeJS如何开发一款区块链公有链

本文将以解读源码的形式,让你有如下收获:

  • 本文最终完成的效果是怎样的?
  • 区块链中的区块是什么意思?
  • 区块是如何构成区块链的?
  • 最终实现上述功能的代码有多简洁?
  • 如何基于NodeJS开发区块链?

最终效果

  1. 如果你想立即看到效果,请按照文末的实践步骤(仅需三步),你将基于NodeJS开发你的第一款区块链可视化的应用。

即: 每隔2秒,一个新的区块被挖出,并缀连到前一个区块的尾部,如此形成区块链,同时打印出来。

 
每隔2秒,一个区块被挖出来
  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();
    const nextHash = CryptoJS.SHA256(nextHeight + previousBlock.hash + nextTimeStamp + blockData) + ''; 
    return new Block(nextHeight, previousBlock.hash, nextTimeStamp, blockData, nextHash);    
  }
}

如上所见,区块的数据结构主要由以下几个字段:

  • height : 当前这个区块的高度(所谓的高度,就是这个区块被挖出来的顺序,第一个,第二个...)
  • previousHash: 前一个区块的hash(你可以简单认为hash就是一个类似身份证号的唯一识别码)
  • timestamp: 即当前时间戳(用来指明当前高度的区块被挖出的时间)
  • data: 区块除了上述区块头中的字段之外,在区块体中需要存放这一小段时间内该链上的事件/交易,这些事件/交易放在data字段中,当该区块被添加到区块链上之后,这些事件、交易就跟着区块被写入了区块链,从此具有了可追溯不可篡改等特性
  • hash:即当前区块将所有的区块头区块体的数据打包后,计算出的一个唯一识别码。
三. 上述生成的区块如何构成区块链的?

其实根据上述区块间缀连的关系,已经知道了区块通过hash前后相连,逐步形成了区块链

下面通过源码解读,区块间如何通过hash相连,又有哪些需要注意的地方?

这就是一个区块链最简最朴实无华且枯燥的样子:

class BlockChain {
  constructor() {
    this.blocks = [];
  }
}

现在我希望将一个有一个的区块逐步缀连到前一个区块上,即用previousHash字段指明前一区块是谁!那么问题来了,第一个区块的previousHash是什么?也就是第一个区块该跟在谁后面?此刻什么都没有,难道跟着上帝么?

没错!就是跟着上帝!即区块链中的上帝,也就是固化在源码中,所谓的创世区块

  getGenesisBlock() {
    return new Block(0, '0', 1595490064640, 'GenesisBlock', '0000000000000000000d87bedef9550a014af9a3af74b791d84d049cc3ca85f4')
  }

如上所见,创世区块做了这么几件事:

  • height: 将高度设定为0
  • previousHash: 将创世区块的前一hash置为0
  • timestamp: 取当前时间戳:1595490064640(即2020-08-03 18:00))
  • data: 区块的数据体中,目前就是朴实无华的一句话,你写什么都OK!
  • hash: 根据上述字段计算出来的一个唯一的识别码

现在有了创世区块了,那么接下来的第一个区块怎么生成呢?源码在此:

  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)
  }

如上可见,后继区块使用到了前一区块的高度height和哈希值hash

当区块链发现有一个新的区块被挖出,且需要添加到该链的尾部时,必须得验证该块是否合乎要求:

  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
  }

由上可见,判断一个新区块能否被添加到该区块链的依据有如下四点:

  • 当前挖出的新区块newBlock和取出的上一区块previousBlock是否是合法的区块?
  • newBlock的高度,是否比previousBlock的高度刚好大1?
  • newblock的前一哈希: previousHash,是否刚好是previousBlock哈希: hash
  • newBlockhash是否是根据该区块的所有信息按固定规范加密生成的?

只有当上述几点全部满足,该新区块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
  }

获取完整源码: Github

快速上手/如何运行

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

总结

本文仅以近乎于Demo的形式,最简的展示了:

  • 何为代码层面区块链
  • 如何以最简代码打造区块链

作者简介

stevekeol

近期已提上日程的计划是打造一款 基于NodeJS的移动端全节点公有链: YunDang-Chain



作者:stevekeol
链接:https://www.jianshu.com/p/b83c1e50c309
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原文地址:https://www.cnblogs.com/zzsdream/p/13488100.html