在上一篇文章中,咱們構建了一個很是簡單的數據結構,這是區塊鏈數據庫的本質。咱們能夠經過它們之間的鏈狀關係爲它添加區塊:每一個區塊都連接到前一個塊。咱們的區塊鏈實現有一個重大缺陷:向鏈中添加區塊很容易。區塊鏈和比特幣的核心之一是:添加新區塊是一項艱苦的工做。今天咱們要解決這個缺陷。java
區塊鏈的一個關鍵思想是,必須進行一些艱苦的工做才能將數據放入其中。正是這項艱苦的工做使區塊鏈變得安全和一致。此外,這項艱苦的工做也獲得了回報(這也就是經過挖礦得到幣)。golang
這種機制與現實生活中的機制很是類似:人們必須努力工做,才能得到獎勵並維持生命。在區塊鏈中,網絡的一些參與者(礦工)努力維持網絡,向其添加新區塊,並得到對其工做的獎勵。因爲他們的工做,區塊以安全的方式被整合到區塊鏈中,這保持了整個區塊鏈數據庫的穩定性。值得注意的是,完成工做的人必須證實這一點。算法
這整個「努力工做和證實」的機制被稱爲工做量證實。這很難,由於它須要大量的計算能力:即便是高性能的計算機也沒法快速完成。另外,這個工做的困難度會隨着時間不斷增加,以保持每 10 分鐘出 1 個新塊的速度。在比特幣中,這種工做的目標是找到一個塊的哈希,知足一些要求。這個哈希,也就充當了證實的角色。所以,尋求證實(尋找有效哈希),就是礦工實際要作的事情。數據庫
最後要注意的一點是。工做量證實算法必須知足要求:完成工做很難,但驗證證實很容易。證實一般會交給其餘人,所以對他們而言,驗證它不該該花費太多時間。api
在本段中,咱們將討論哈希。若是您熟悉這個概念,能夠跳過這一部分。安全
哈希是獲取指定數據的哈希的過程。哈希是計算數據的惟一表示。哈希函數是一種獲取任意大小數據並生成固定大小哈希的函數。如下是哈希的一些主要功能:bash
哈希函數普遍用於檢查數據的一致性。除軟件包外,某些軟件提供商還會發布校驗和。下載文件後,您能夠將其提供給哈希函數,並將生成的哈希與軟件開發人員提供的哈希進行比較。網絡
在區塊鏈中,哈希用於保證區塊的一致性。哈希算法的輸入數據包含前一個區塊的哈希,所以沒法(或者至少很是困難)修改鏈中的區塊:必須從新計算其哈希和其後全部區塊的哈希值。數據結構
比特幣使用 Hashcash,一種最初開發用於防止電子郵件垃圾郵件的工做量證實算法。它能夠分爲如下幾個步驟:架構
所以,這是一個暴力算法:你改變計數器,計算一個新的哈希,檢查它,增長計數器,計算一個哈希等。這就是爲何它的計算成本很高。
如今讓咱們仔細看看哈希必須知足的要求。在最初的Hashcash實現中,它的要求是 「一個哈希的前 20 位必須是 0」。在比特幣中,需求會不時調整,由於,儘管計算能力隨着時間的推移而增長,而且愈來愈多的礦工加入網絡,但必須保證每 10 分鐘生成一個塊。
爲了演示這個算法,我從前面的例子中獲取了數據(「我喜歡甜甜圈」)並找到了一個以3個零字節開頭的哈希:
ca07ca是計數器的十六進制值,十進制系統中爲13240266。
好的,咱們已經完成了理論,讓咱們編寫代碼!首先,讓咱們來定義挖掘的難度:
const targetBits = 24複製代碼
在比特幣中,當一個塊被挖出來之後,「target bits」 表明了區塊頭裏存儲的難度,也就是開頭有多少個 0。目前咱們不會實現目標調整算法,所以咱們能夠將難度定義爲全局常量。
24是一個任意數字,咱們的目標是讓一個目標在內存中佔用少於256位。咱們但願差別足夠大,但不要太大,由於差別越大,找到合適的散列越困難。
type ProofOfWork struct {
block *Block
target *big.Int
}
func NewProofOfWork(b *Block) *ProofOfWork {
target := big.NewInt(1)
target.Lsh(target, uint(256-targetBits))
pow := &ProofOfWork{b, target}
return pow
}複製代碼
這裏,咱們構造了 ProofOfWork 結構,裏面存儲了指向一個塊(block
)和一個目標(target
)的指針。這裏的 「目標」 ,也就是前一節中所描述的必要條件。這裏使用了一個 大整數 ,咱們會將哈希與目標進行比較:先把哈希轉換成一個大整數,而後檢測它是否小於目標。
在 NewProofOfWork 函數中,咱們將 big.Int 初始化爲 1,而後左移 256 - targetBits
位。256 是一個 SHA-256 哈希的位數,咱們將要使用的是 SHA-256 哈希算法。target(目標) 的 16 進制形式爲:
0x10000000000000000000000000000000000000000000000000000000000複製代碼
它在內存中佔用29個字節。這裏是與前面例子中的哈希的視覺比較:
0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca複製代碼
第一個哈希值(根據「我喜歡甜甜圈」計算)比目標大,所以它不是有效的工做證實。第二個哈希(計算在「我喜歡donutsca07ca」)小於目標,所以它是一個有效的證據。
您能夠將目標視爲範圍的上邊界:若是數字(哈希)低於邊界,則它是有效的,反之亦然。下降邊界將致使有效數字減小,所以找到有效數字所需的工做更加困難。
如今,咱們須要數據進行哈希處理。咱們準備吧:
func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.Data,
IntToHex(pow.block.Timestamp),
IntToHex(int64(targetBits)),
IntToHex(int64(nonce)),
},
[]byte{},
)
return data
}複製代碼
這個部分很簡單:咱們只是將 target ,nonce 與 Block 進行合併。nonce這裏是上面Hashcash描述的計數器,這是一個加密術語。
好的,全部準備工做都已完成,讓咱們實現PoW算法的核心:
func (pow *ProofOfWork) Run() (int, []byte) {
var hashInt big.Int
var hash [32]byte
nonce := 0
fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
for nonce < maxNonce {
data := pow.prepareData(nonce)
hash = sha256.Sum256(data)
fmt.Printf("\r%x", hash)
hashInt.SetBytes(hash[:])
if hashInt.Cmp(pow.target) == -1 {
break
} else {
nonce++
}
}
fmt.Print("\n\n")
return nonce, hash[:]
}
複製代碼
首先,咱們初始化變量:hashInt,它是整數表示hash; nonce是計數器。接下來,咱們運行一個「無限」循環:它受限於maxNonce, 它等於 math.MaxInt64
;這樣作是爲了不nonce的溢出。雖然咱們的PoW實現的難度過低而不能使計數器溢出,但爲了以防萬一,進行此檢查仍然更好。
在這個循環中,咱們作的事情有:
跟以前所講的同樣簡單。如今咱們能夠移除 Block
的 SetHash
方法,而後修改 NewBlock
函數:
func NewBlock(data string, prevBlockHash []byte) *Block {
block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}
pow := NewProofOfWork(block)
nonce, hash := pow.Run()
block.Hash = hash[:]
block.Nonce = nonce
return block
}
複製代碼
在這裏,你能夠看到 nonce
被保存爲 Block
的一個屬性。這是十分有必要的,由於待會兒咱們對這個工做量進行驗證時會用到 nonce
。Block
結構如今看起來像是這樣:
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
複製代碼
好的!讓咱們運行程序,看看一切是否正常:
Mining the block containing "Genesis Block"
00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Mining the block containing "Send 1 BTC to Ivan"
00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Mining the block containing "Send 2 more BTC to Ivan"
000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe
Prev. hash:
Data: Genesis Block
Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1
Data: Send 1 BTC to Ivan
Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804
Data: Send 2 more BTC to Ivan
Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe複製代碼
好極了!您能夠看到每一個哈希如今以三個零字節開始,而且須要一些時間來獲取這些哈希值。
還有一件事要作:對工做量證實進行驗證。
func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int
data := pow.prepareData(pow.block.Nonce)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])
isValid := hashInt.Cmp(pow.target) == -1
return isValid
}
複製代碼
這就是咱們須要保存的隨機數的地方。
讓咱們再檢查一切都沒問題:
func main() {
...
for _, block := range bc.blocks {
...
pow := NewProofOfWork(block)
fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
fmt.Println()
}
}
複製代碼
Output:
...
Prev. hash:
Data: Genesis Block
Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038
PoW: true
Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038
Data: Send 1 BTC to Ivan
Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b
PoW: true
Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b
Data: Send 2 more BTC to Ivan
Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a
PoW: true
複製代碼
咱們的區塊鏈更接近其實際架構:如今添加區塊須要努力工做,所以能夠進行挖掘。但它仍然缺少一些關鍵特徵:區塊鏈數據庫不是持久的,沒有錢包,地址,交易,也沒有共識機制。全部這些咱們將在將來的文章中實現,如今,快樂的採礦吧!
英文原文:https://jeiwan.cc/posts/building-blockchain-in-go-part-2/
更多文章歡迎訪問 http://www.apexyun.com/
聯繫郵箱:public@space-explore.com
(未經贊成,請勿轉載)