精彩文章免费看

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

前言

注:文末有 源码地址

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

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

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

  • 本文最终完成的效果是怎样的?
  • 区块链中的区块是什么意思?
  • 区块是如何构成区块链的?
  • 最终实现上述功能的代码有多简洁?
  • 如何基于NodeJS开发区块链?
  • 如果你想立即看到效果,请按照文末的实践步骤(仅需三步),你将基于NodeJS开发你的第一款区块链可视化的应用。
  • 即: 每隔2秒,一个新的 区块 被挖出,并缀连到前一个区块的尾部,如此形成 区块链 ,同时打印出来。
    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) {
          !(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哈希: hashnewBlockhash是否是根据该区块的所有信息按固定规范加密生成的?

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

    我们将带你手把手实现它,参考 项目源码ReadMe,其中有个人微信进群方式

    最后编辑于:2020-11-09 16:48