使用Go構建區塊鏈 第2部分:工做量證實

Introduction

在上一篇文章中,咱們構建了一個很是簡單的數據結構,這是區塊鏈數據庫的本質。咱們能夠經過它們之間的鏈狀關係爲它添加區塊:每一個區塊都連接到前一個塊。咱們的區塊鏈實現有一個重大缺陷:向鏈中添加區塊很容易。區塊鏈和比特幣的核心之一是:添加新區塊是一項艱苦的工做。今天咱們要解決這個缺陷。java

Proof-of-Work(工做量證實)

區塊鏈的一個關鍵思想是,必須進行一些艱苦的工做才能將數據放入其中。正是這項艱苦的工做使區塊鏈變得安全和一致。此外,這項艱苦的工做也獲得了回報(這也就是經過挖礦得到幣)。golang

這種機制與現實生活中的機制很是類似:人們必須努力工做,才能得到獎勵並維持生命。在區塊鏈中,網絡的一些參與者(礦工)努力維持網絡,向其添加新區塊,並得到對其工做的獎勵。因爲他們的工做,區塊以安全的方式被整合到區塊鏈中,這保持了整個區塊鏈數據庫的穩定性。值得注意的是,完成工做的人必須證實這一點。算法

這整個「努力工做和證實」的機制被稱爲工做量證實。這很難,由於它須要大量的計算能力:即便是高性能的計算機也沒法快速完成。另外,這個工做的困難度會隨着時間不斷增加,以保持每 10 分鐘出 1 個新塊的速度。在比特幣中,這種工做的目標是找到一個塊的哈希,知足一些要求。這個哈希,也就充當了證實的角色。所以,尋求證實(尋找有效哈希),就是礦工實際要作的事情。數據庫

最後要注意的一點是。工做量證實算法必須知足要求:完成工做很難,但驗證證實很容易。證實一般會交給其餘人,所以對他們而言,驗證它不該該花費太多時間。api

Hashing

在本段中,咱們將討論哈希。若是您熟悉這個概念,能夠跳過這一部分。安全

哈希是獲取指定數據的哈希的過程。哈希是計算數據的惟一表示。哈希函數是一種獲取任意大小數據並生成固定大小哈希的函數。如下是哈希的一些主要功能:bash

  1. 沒法從哈希中恢復原始數據。所以,哈希不是加密。
  2. 某些數據只能有一個哈希值,哈希值是惟一的。
  3. 改變輸入數據中的一個字節將致使徹底不一樣的哈希。


哈希函數普遍用於檢查數據的一致性。除軟件包外,某些軟件提供商還會發布校驗和。下載文件後,您能夠將其提供給哈希函數,並將生成的哈希與軟件開發人員提供的哈希進行比較。網絡

在區塊鏈中,哈希用於保證區塊的一致性。哈希算法的輸入數據包含前一個區塊的哈希,所以沒法(或者至少很是困難)修改鏈中的區塊:必須從新計算其哈希和其後全部區塊的哈希值。數據結構

Hashcash

比特幣使用 Hashcash,一種最初開發用於防止電子郵件垃圾郵件的工做量證實算法。它能夠分爲如下幾個步驟:架構

  1. 拿一些公開的數據(若是是電子郵件,它是接收者的電子郵件地址;對於比特幣,它是塊頭)。
  2. 添加一個計數器。計數器從0開始。
  3. data(數據)counter(計數器) 組合到一塊兒,得到一個哈希
  4. 檢查哈希符合某些要求。
    1. 若是確實如此,那就完成了。
    2. 若是沒有,請增長計數器並重復步驟3和4。

所以,這是一個暴力算法:你改變計數器,計算一個新的哈希,檢查它,增長計數器,計算一個哈希等。這就是爲何它的計算成本很高。

如今讓咱們仔細看看哈希必須知足的要求。在最初的Hashcash實現中,它的要求是 「一個哈希的前 20 位必須是 0」。在比特幣中,需求會不時調整,由於,儘管計算能力隨着時間的推移而增長,而且愈來愈多的礦工加入網絡,但必須保證每 10 分鐘生成一個塊。

爲了演示這個算法,我從前面的例子中獲取了數據(「我喜歡甜甜圈」)並找到了一個以3個零字節開頭的哈希:


ca07ca是計數器的十六進制值,十進制系統中爲13240266。

Implementation

好的,咱們已經完成了理論,讓咱們編寫代碼!首先,讓咱們來定義挖掘的難度:

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實現的難度過低而不能使計數器溢出,但爲了以防萬一,進行此檢查仍然更好。

在這個循環中,咱們作的事情有:

  1. 準備數據
  2. 用 SHA-256 對數據進行哈希
  3. 將哈希轉換成一個大整數
  4. 將這個大整數與目標進行比較

跟以前所講的同樣簡單。如今咱們能夠移除 BlockSetHash 方法,而後修改 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 的一個屬性。這是十分有必要的,由於待會兒咱們對這個工做量進行驗證時會用到 nonceBlock 結構如今看起來像是這樣:

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
複製代碼

Conclusion

咱們的區塊鏈更接近其實際架構:如今添加區塊須要努力工做,所以能夠進行挖掘。但它仍然缺少一些關鍵特徵:區塊鏈數據庫不是持久的,沒有錢包,地址,交易,也沒有共識機制。全部這些咱們將在將來的文章中實現,如今,快樂的採礦吧!


英文原文:https://jeiwan.cc/posts/building-blockchain-in-go-part-2/


更多文章歡迎訪問 http://www.apexyun.com/

聯繫郵箱:public@space-explore.com

(未經贊成,請勿轉載)

相關文章
相關標籤/搜索