本章源代碼地址:[https://github.com/daleboy/blockchain2]
# 前言
區塊鏈的一個關鍵點就是,一個人必須經過一系列困難的工作,才能將數據放入到區塊鏈中。正是這種困難的工作,才使得區塊鏈是安全和一致的。此外,完成這個工作的人也會獲得獎勵(這也就是通過挖礦獲得幣)。第一版的區塊鏈加入區塊太容易了,這會導致上鏈變得輕而易舉,那么各種無意義的區塊將充斥于區塊鏈,使得區塊鏈無意義地膨脹,反而會擠壓有效區塊的空間和計算資源。
整個 “努力工作并進行證明” 的機制,就叫做工作量證明(proof-of-work)。尋求證明(尋找有效哈希),就是實際要做的事情,在比特幣中,稱為“挖礦”。
# 哈希計算
在區塊鏈中,哈希被用于保證一個塊的一致性。哈希算法的輸入數據包含了前一個塊的哈希,因此使得不太可能(或者,至少很困難)去修改鏈中的一個塊:因為如果一個人想要修改前面一個塊的哈希,那么他必須要重新計算這個塊以及后面所有塊的哈希。
比特幣使用[Hashcash](https://link.jianshu.com/?t=https://en.wikipedia.org/wiki/Hashcash),一個最初用來防止垃圾郵件的工作量證明算法。它可以被分解為以下步驟:
1. 取一些公開的數據(比如,如果是 email 的話,它可以是接收者的郵件地址;在比特幣中,它是區塊頭)
2. 給這個公開數據添加一個計數器。計數器默認從 0 開始
3. 將 data(數據) 和 counter(計數器) 組合到一起,獲得一個哈希
4. 檢查哈希是否符合一定的條件: 1.如果符合條件,結束 2.如果不符合,增加計數器,重復步驟 3-4
因此,這是一個暴力算法:改變計數器,計算一個新的哈希,檢查,增加計數器,計算一個哈希,檢查,如此反復。這也是為什么說它是在計算上是非常昂貴的,因為這一步需要如此反復不斷地計算和檢查。
現在,讓我們來仔細看一下一個哈希要滿足的必要條件。在原始的 Hashcash 實現中,它的要求是 “一個哈希的前 20 位必須是 0”。在比特幣中,這個要求會隨著時間而不斷變化。因為按照設計,必須保證每 10 分鐘生成一個塊,而不論計算能力會隨著時間增長,或者是會有越來越多的礦工進入網絡,所以需要動態調整這個必要條件。
# POW實現
我們看看一個第一版的block的結構:

(1)Timestamp,為時間戳,容易獲得
(2)Data,為實際有效信息,上鏈前由上鏈者提交
(3)PrevBlockHash,上一個區塊的哈希,已經存在,直接獲得
(4)Hash,本區塊的哈希
顯然要增加創建區塊的難度,只有在計算本區塊的哈希上做文章,為此,區塊的結構添加一個計數器nonce。假設將挖礦難度設定為24bit為0,那么挖礦者在獲得上鏈者提交的交易信息后,通過不斷調整計數器,計算Timestamp+Data+PrevBlockHash+nonce的哈希,直到該哈希的前三個字節全是0(16進制)的哈希,然后將block提交到鏈上。
當然,在礦工將block提交到鏈上之前,檢驗節點可以進行驗證,所挖的礦是否是合格的。本版本的POW實現提供驗證方法,在主程序中可以進行驗證。
新的block結構:

## **1、proofofwork.go**
POW即挖礦,其實現是關鍵。我們首先要定義挖礦難度系數:

即:要求區塊計算出的哈希值轉為大整數后,其前24bit為0
挖礦當然是以包含交易數據的區塊和需要滿足的條件為基礎來工作,我們定義下這個工作基礎,即結構體ProofOfWork:

這里的必要條件,等價于前面定義的targetBits。
接下來,我們需要構建一個pow實例,用于后面挖礦用。這里主要是在構建pow實例時候確定target條件。

代碼的注釋非常清楚,將數字1左移256-targetBits位,即為target,這個target擴展到寬度為256位后,前置0為23位,第24位為1,所以只要礦工挖礦中找到的哈希值轉為大整數后小于target,那么前24位必為0,滿足挖礦條件要求!
我們開始要準備挖礦了,首先需要準備計算哈希的數據:

我們開發了一個IntToHex函數,將整型轉為byte數組。詳細見utils.go。
需要注意,這次計算哈希,新增了一個nonce。
接下來是關鍵的挖礦核心算法:

挖礦中,通過遞增計數器nonce(為防止溢出,定義了一個maxNonce),修改data,直到找到的哈希值符合條件,返回找到的哈希值和對應的nonce。
這里可能需要計算非常多次,如果處理不好,不僅耗費cpu,還耗費內存。在循環中,我們以hash數組為參數調用函數時候,是通過哈希創建的切片進行,相當于引用數組,而不是直接調用hash數組,就是為了減小內存的開銷。
當然,挖礦也可能失敗,即沒有挖出滿足條件的區塊,因此,需要對挖出的區塊進行驗證:

一般來說,只有通過驗證的區塊,才可以加入到區塊鏈中。
## **2、block.go**
需要對block進行修改,去掉SetHash,修改NewBlock:

區塊不再是直接創建,而是通過挖礦產生。
當然創始區塊也是通過挖礦產生:

## **3、blockchain.go**
這個跟上一版本沒有變化。
當然AddBlock需要改進,這里是挖礦+加入區塊鏈一起完成,最好是在加入區塊鏈之前,先驗證下。
## **4、main.go**
好了,我們現在開始進行測試了,看看是否能夠驗證我們的想法:

運行后,需要些時間:

可以看到,無論是挖出的創始區塊,還是普通區塊,哈希值均為64位(16進制),前面六位的值是0,也就是二進制的24個0,滿足我們最開始設置的挖礦條件。
- 重要更新說明
- linechain發布
- linechain新版設計
- 引言一
- 引言二
- 引言三
- vs-code設置及開發環境設置
- BoltDB數據庫應用
- 關于Go語言、VS-code的一些Tips
- 區塊鏈的架構
- 網絡通信與區塊鏈
- 單元測試
- 比特幣腳本語言
- 關于區塊鏈的一些概念
- 區塊鏈組件
- 區塊鏈第一版:基本原型
- 區塊鏈第二版:增加工作量證明
- 區塊鏈第三版:持久化
- 區塊鏈第四版:交易
- 區塊鏈第五版:實現錢包
- 區塊鏈第六版:實現UTXO集
- 區塊鏈第七版:網絡
- 階段小結
- 區塊鏈第八版:P2P
- P2P網絡架構
- 區塊鏈網絡層
- P2P區塊鏈最簡體驗
- libp2p建立P2P網絡的關鍵概念
- 區塊鏈結構層設計與實現
- 用戶交互層設計與實現
- 網絡層設計與實現
- 建立節點發現機制
- 向區塊鏈網絡請求區塊信息
- 向區塊鏈網絡發布消息
- 運行區塊鏈
- LineChain
- 系統運行流程
- Multihash
- 區塊鏈網絡的節點發現機制深入探討
- DHT
- Bootstrap
- 連接到所有引導節點
- Advertise
- 搜索其它peers
- 連接到搜到的其它peers
- 區塊鏈網絡的消息訂發布-訂閱機制深入探討
- LineChain:適用于智能合約編程的腳本語言支持
- LineChain:解決分叉問題
- LineChain:多重簽名
- libp2p升級到v0.22版本
- 以太坊基礎
- 重溫以太坊的樹結構
- 世界狀態樹
- (智能合約)賬戶存儲樹
- 交易樹
- 交易收據樹
- 小結
- 以太坊的存儲結構
- 以太坊狀態數據庫
- MPT
- 以太坊POW共識算法
- 智能合約存儲
- Polygon Edge
- block結構
- transaction數據結構
- 數據結構小結
- 關于本區塊鏈的一些說明
- UML工具-PlantUML
- libp2p介紹
- JSON-RPC
- docker制作:啟動多個應用系統
- Dockerfile
- docker-entrypoint.sh
- supervisord.conf
- docker run
- nginx.conf
- docker基礎操作整理
- jupyter計算交互環境
- git技巧一
- git技巧二
- 使用github項目的最佳實踐
- windows下package管理工具