最簡區塊鏈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
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM