2018-06-07

工作量證明

在上一節(jié),我們構(gòu)造了一個(gè)非常簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu) -- 區(qū)塊卡儒,它也是整個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)的核心霹疫。目前所完成的區(qū)塊鏈原型舌界,已經(jīng)可以通過(guò)鏈?zhǔn)疥P(guān)系把區(qū)塊相互關(guān)聯(lián)起來(lái):每個(gè)塊都與前一個(gè)塊相關(guān)聯(lián)。

但是忘蟹,當(dāng)前實(shí)現(xiàn)的區(qū)塊鏈有一個(gè)巨大的缺陷:向鏈中加入?yún)^(qū)塊太容易飒房,也太廉價(jià)了。而區(qū)塊鏈和比特幣的其中一個(gè)核心就是媚值,要想加入新的區(qū)塊狠毯,必須先完成一些非常困難的工作。在本文褥芒,我們將會(huì)彌補(bǔ)這個(gè)缺陷嚼松。

工作量證明

區(qū)塊鏈的一個(gè)關(guān)鍵點(diǎn)就是,一個(gè)人必須經(jīng)過(guò)一系列困難的工作锰扶,才能將數(shù)據(jù)放入到區(qū)塊鏈中献酗。正是由于這種困難的工作,才保證了區(qū)塊鏈的安全和一致少辣。此外凌摄,完成這個(gè)工作的人,也會(huì)獲得相應(yīng)獎(jiǎng)勵(lì)(這也就是通過(guò)挖礦獲得幣)漓帅。

這個(gè)機(jī)制與生活現(xiàn)象非常類(lèi)似:一個(gè)人必須通過(guò)努力工作锨亏,才能夠獲得回報(bào)或者獎(jiǎng)勵(lì),用以支撐他們的生活忙干。在區(qū)塊鏈中器予,是通過(guò)網(wǎng)絡(luò)中的參與者(礦工)不斷的工作來(lái)支撐起了整個(gè)網(wǎng)絡(luò)。礦工不斷地向區(qū)塊鏈中加入新塊捐迫,然后獲得相應(yīng)的獎(jiǎng)勵(lì)乾翔。在這種機(jī)制的作用下,新生成的區(qū)塊能夠被安全地加入到區(qū)塊鏈中,它維護(hù)了整個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)的穩(wěn)定性反浓。值得注意的是萌丈,完成了這個(gè)工作的人必須要證明這一點(diǎn),即他必須要證明他的確完成了這些工作雷则。

整個(gè) “努力工作并進(jìn)行證明” 的機(jī)制辆雾,就叫做工作量證明(proof-of-work)。要想完成工作非常地不容易月劈,因?yàn)檫@需要大量的計(jì)算能力:即便是高性能計(jì)算機(jī)度迂,也無(wú)法在短時(shí)間內(nèi)快速完成。另外猜揪,這個(gè)工作的困難度會(huì)隨著時(shí)間不斷增長(zhǎng)惭墓,以保持每 10 分鐘出 1 個(gè)新塊的速度。在比特幣中而姐,這個(gè)工作就是找到一個(gè)塊的哈希腊凶,同時(shí)這個(gè)哈希滿(mǎn)足了一些必要條件。這個(gè)哈希毅人,也就充當(dāng)了證明的角色吭狡。因此,尋求證明(尋找有效哈希)丈莺,就是礦工實(shí)際要做的事情划煮。

哈希計(jì)算

在本節(jié),我們會(huì)討論哈希計(jì)算缔俄。如果你已經(jīng)熟悉了這個(gè)概念弛秋,可以直接跳過(guò)。

獲得指定數(shù)據(jù)的一個(gè)哈希值的過(guò)程俐载,就叫做哈希計(jì)算蟹略。一個(gè)哈希,就是對(duì)所計(jì)算數(shù)據(jù)的一個(gè)唯一表示遏佣。對(duì)于一個(gè)哈希函數(shù)挖炬,輸入任意大小的數(shù)據(jù),它會(huì)輸出一個(gè)固定大小的哈希值状婶。下面是哈希的幾個(gè)關(guān)鍵特性:

  1. 無(wú)法從一個(gè)哈希值恢復(fù)原始數(shù)據(jù)意敛。也就是說(shuō),哈希并不是加密膛虫。
  2. 對(duì)于特定的數(shù)據(jù)草姻,只能有一個(gè)哈希,并且這個(gè)哈希是唯一的稍刀。
  3. 即使是僅僅改變輸入數(shù)據(jù)中的一個(gè)字節(jié)撩独,也會(huì)導(dǎo)致輸出一個(gè)完全不同的哈希。

<figure id="fig2.2.1" style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; margin: 1.5em 0px; padding: 10px 0px; font-size: 16px; break-inside: avoid; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.2px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
hashing

<figcaption style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; clear: left; margin: 0.75em 0px; text-align: center; font-style: italic; line-height: 1.5em; font-size: inherit;">2.2.1 -- hashing</figcaption>

</figure>

哈希函數(shù)被廣泛用于檢測(cè)數(shù)據(jù)的一致性。軟件提供者常常在除了提供軟件包以外综膀,還會(huì)發(fā)布校驗(yàn)和澳迫。當(dāng)下載完一個(gè)文件以后,你可以用哈希函數(shù)對(duì)下載好的文件計(jì)算一個(gè)哈希僧须,并與作者提供的哈希進(jìn)行比較纲刀,以此來(lái)保證文件下載的完整性。

在區(qū)塊鏈中担平,哈希被用于保證一個(gè)塊的一致性。哈希算法的輸入數(shù)據(jù)包含了前一個(gè)塊的哈希锭部,因此使得不太可能(或者暂论,至少很困難)去修改鏈中的一個(gè)塊:因?yàn)槿绻粋€(gè)人想要修改前面一個(gè)塊的哈希,那么他必須要重新計(jì)算這個(gè)塊以及后面所有塊的哈希拌禾。

Hashcash

比特幣使用 Hashcash 取胎,一個(gè)最初用來(lái)防止垃圾郵件的工作量證明算法。它可以被分解為以下步驟:

  1. 取一些公開(kāi)的數(shù)據(jù)(比如湃窍,如果是 email 的話闻蛀,它可以是接收者的郵件地址;在比特幣中您市,它是區(qū)塊頭)
  2. 給這個(gè)公開(kāi)數(shù)據(jù)添加一個(gè)計(jì)數(shù)器觉痛。計(jì)數(shù)器默認(rèn)從 0 開(kāi)始
  3. data(數(shù)據(jù))counter(計(jì)數(shù)器) 組合到一起,獲得一個(gè)哈希
  4. 檢查哈希是否符合一定的條件:
    1. 如果符合條件茵休,結(jié)束
    2. 如果不符合薪棒,增加計(jì)數(shù)器,重復(fù)步驟 3-4

因此榕莺,這是一個(gè)暴力算法:改變計(jì)數(shù)器俐芯,計(jì)算新的哈希,檢查钉鸯,增加計(jì)數(shù)器吧史,計(jì)算哈希,檢查唠雕,如此往復(fù)贸营。這也是為什么說(shuō)它的計(jì)算成本很高,因?yàn)檫@一步需要如此反復(fù)不斷地計(jì)算和檢查及塘。

現(xiàn)在莽使,讓我們來(lái)仔細(xì)看一下一個(gè)哈希要滿(mǎn)足的必要條件。在原始的 Hashcash 實(shí)現(xiàn)中笙僚,它的要求是 “一個(gè)哈希的前 20 位必須是 0”芳肌。在比特幣中,這個(gè)要求會(huì)隨著時(shí)間而不斷變化。因?yàn)榘凑赵O(shè)計(jì)亿笤,必須保證每 10 分鐘生成一個(gè)塊翎迁,而不論計(jì)算能力會(huì)隨著時(shí)間增長(zhǎng),或者是會(huì)有越來(lái)越多的礦工進(jìn)入網(wǎng)絡(luò)净薛,所以需要?jiǎng)討B(tài)調(diào)整這個(gè)必要條件汪榔。

為了闡釋這一算法,我從前一個(gè)例子(“I like donuts”)中取得數(shù)據(jù)肃拜,并且找到了一個(gè)前 3 個(gè)字節(jié)是全是 0 的哈希痴腌。

<figure id="fig2.2.2" style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; margin: 1.5em 0px; padding: 10px 0px; font-size: 16px; break-inside: avoid; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.2px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">[圖片上傳中...(image-53a576-1528343535478-2)]

<figcaption style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; clear: left; margin: 0.75em 0px; text-align: center; font-style: italic; line-height: 1.5em; font-size: inherit;">2.2.2 -- a hash that starts with 3 zero-bytes</figcaption>

</figure>

ca07ca 是計(jì)數(shù)器的 16 進(jìn)制值,十進(jìn)制的話是 13240266.

實(shí)現(xiàn)

好了燃领,完成了理論層面士聪,來(lái)動(dòng)手寫(xiě)代碼吧!首先猛蔽,定義挖礦的難度值:

const targetBits = 24

在比特幣中剥悟,當(dāng)一個(gè)塊被挖出來(lái)以后,“target bits” 代表了區(qū)塊頭里存儲(chǔ)的難度曼库,也就是開(kāi)頭有多少個(gè) 0区岗。這里的 24 指的是算出來(lái)的哈希前 24 位必須是 0,如果用 16 進(jìn)制表示毁枯,就是前 6 位必須是 0慈缔,這一點(diǎn)從最后的輸出可以看出來(lái)。目前我們并不會(huì)實(shí)現(xiàn)一個(gè)動(dòng)態(tài)調(diào)整目標(biāo)的算法后众,所以將難度定義為一個(gè)全局的常量即可胀糜。

24 其實(shí)是一個(gè)可以任意取的數(shù)字,其目的只是為了有一個(gè)目標(biāo)(target)而已蒂誉,這個(gè)目標(biāo)占據(jù)不到 256 位的內(nèi)存空間教藻。同時(shí),我們想要有足夠的差異性右锨,但是又不至于大的過(guò)分括堤,因?yàn)椴町愋栽酱螅驮诫y找到一個(gè)合適的哈希绍移。

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
}

這里悄窃,我們構(gòu)造了 ProofOfWork 結(jié)構(gòu),里面存儲(chǔ)了指向一個(gè)塊(block)和一個(gè)目標(biāo)(target)的指針蹂窖。這里的 “目標(biāo)” 轧抗,也就是前一節(jié)中所描述的必要條件。這里使用了一個(gè) 大整數(shù) 瞬测,我們會(huì)將哈希與目標(biāo)進(jìn)行比較:先把哈希轉(zhuǎn)換成一個(gè)大整數(shù)横媚,然后檢測(cè)它是否小于目標(biāo)纠炮。

NewProofOfWork 函數(shù)中,我們將 big.Int 初始化為 1灯蝴,然后左移 256 - targetBits 位恢口。256 是一個(gè) SHA-256 哈希的位數(shù),我們將要使用的是 SHA-256 哈希算法穷躁。target(目標(biāo)) 的 16 進(jìn)制形式為:

0x10000000000000000000000000000000000000000000000000000000000

它在內(nèi)存上占據(jù)了 29 個(gè)字節(jié)耕肩。下面是與前面例子哈希的形式化比較:

0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3
0000010000000000000000000000000000000000000000000000000000000000
0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca

第一個(gè)哈希(基于 “I like donuts” 計(jì)算)比目標(biāo)要大,因此它并不是一個(gè)有效的工作量證明问潭。第二個(gè)哈希(基于 “I like donutsca07ca” 計(jì)算)比目標(biāo)要小猿诸,所以是一個(gè)有效的證明。

譯者注:上面的形式化比較有些“言不符實(shí)”狡忙,其實(shí)它應(yīng)該并非由 “I like donuts” 而來(lái)两芳,但是原文表達(dá)的意思是沒(méi)問(wèn)題的,可能是疏忽而已去枷。下面是我做的一個(gè)小實(shí)驗(yàn):

package main

import (
    "crypto/sha256"
    "fmt"
    "math/big"
)

func main() {

    data1 := []byte("I like donuts")
    data2 := []byte("I like donutsca07ca")
    targetBits := 24
    target := big.NewInt(1)
    target.Lsh(target, uint(256-targetBits))
    fmt.Printf("%x\n", sha256.Sum256(data1))
    fmt.Printf("%64x\n", target)
    fmt.Printf("%x\n", sha256.Sum256(data2))

}

輸出:

<figure id="fig2.2.3" style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; margin: 1.5em 0px; padding: 10px 0px; font-size: 16px; break-inside: avoid; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.2px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
experiment

<figcaption style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; clear: left; margin: 0.75em 0px; text-align: center; font-style: italic; line-height: 1.5em; font-size: inherit;">2.2.3 -- experiment</figcaption>

</figure>

你可以把目標(biāo)想象為一個(gè)范圍的上界:如果一個(gè)數(shù)(由哈希轉(zhuǎn)換而來(lái))比上界要小,那么是有效的是复,反之無(wú)效删顶。因?yàn)橐蟊壬辖缫。詴?huì)導(dǎo)致有效數(shù)字并不會(huì)很多淑廊。因此逗余,也就需要通過(guò)一些困難的工作(一系列反復(fù)地計(jì)算),才能找到一個(gè)有效的數(shù)字季惩。

現(xiàn)在录粱,我們需要有數(shù)據(jù)來(lái)進(jìn)行哈希,準(zhǔn)備數(shù)據(jù):

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
}

這個(gè)部分比較直觀:只需要將 target 画拾,nonce 與 Block 進(jìn)行合并啥繁。這里的 nonce,就是上面 Hashcash 所提到的計(jì)數(shù)器青抛,它是一個(gè)密碼學(xué)術(shù)語(yǔ)旗闽。

很好,到這里蜜另,所有的準(zhǔn)備工作就完成了适室,下面來(lái)實(shí)現(xiàn) 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)
        hashInt.SetBytes(hash[:])

        if hashInt.Cmp(pow.target) == -1 {
            fmt.Printf("\r%x", hash)
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")

    return nonce, hash[:]
}

首先我們對(duì)變量進(jìn)行初始化:

  • HashInthash 的整形表示;
  • nonce 是計(jì)數(shù)器举瑰。

然后開(kāi)始一個(gè) “無(wú)限” 循環(huán):maxNonce 對(duì)這個(gè)循環(huán)進(jìn)行了限制, 它等于 math.MaxInt64捣辆,這是為了避免 nonce 可能出現(xiàn)的溢出。盡管我們 PoW 的難度很小此迅,以至于計(jì)數(shù)器其實(shí)不太可能會(huì)溢出汽畴,但最好還是以防萬(wàn)一檢查一下旧巾。

在這個(gè)循環(huán)中,我們做的事情有:

  1. 準(zhǔn)備數(shù)據(jù)
  2. 用 SHA-256 對(duì)數(shù)據(jù)進(jìn)行哈希
  3. 將哈希轉(zhuǎn)換成一個(gè)大整數(shù)
  4. 將這個(gè)大整數(shù)與目標(biāo)進(jìn)行比較

跟之前所講的一樣簡(jiǎn)單≌現(xiàn)在我們可以移除 BlockSetHash 方法菠齿,然后修改 NewBlock 函數(shù):

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 的一個(gè)屬性坐昙。這是十分有必要的绳匀,因?yàn)榇龝?huì)兒我們對(duì)這個(gè)工作量進(jìn)行驗(yàn)證時(shí)會(huì)用到 nonceBlock 結(jié)構(gòu)現(xiàn)在看起來(lái)像是這樣:

type Block struct {
    Timestamp     int64
    Data          []byte
    PrevBlockHash []byte
    Hash          []byte
    Nonce         int
}

好了炸客!現(xiàn)在讓我們來(lái)運(yùn)行一下是否正常工作:

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

成功了疾棵!你可以看到每個(gè)哈希都是 3 個(gè)字節(jié)的 0 開(kāi)始,并且獲得這些哈希需要花費(fèi)一些時(shí)間痹仙。

還剩下一件事情需要做是尔,對(duì)工作量證明進(jìn)行驗(yàn)證:

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
}

這里,就是我們就用到了上面保存的 nonce开仰。

再來(lái)檢測(cè)一次是否正常工作:

func main() {
    ...

    for _, block := range bc.blocks {
        ...
        pow := NewProofOfWork(block)
        fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
        fmt.Println()
    }
}

輸出:

...

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

從下圖可以看出拟枚,這次我們產(chǎn)生三個(gè)塊花費(fèi)了一分多鐘,比沒(méi)有工作量證明之前慢了很多(也就是成本高了很多):

<figure id="fig2.2.4" style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; margin: 1.5em 0px; padding: 10px 0px; font-size: 16px; break-inside: avoid; color: rgb(51, 51, 51); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.2px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">
output

<figcaption style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; text-size-adjust: none; -webkit-font-smoothing: antialiased; display: block; clear: left; margin: 0.75em 0px; text-align: center; font-style: italic; line-height: 1.5em; font-size: inherit;">2.2.4 -- output</figcaption>

</figure>

總結(jié)

我們離真正的區(qū)塊鏈又進(jìn)了一步:現(xiàn)在需要經(jīng)過(guò)一些困難的工作才能加入新的塊众弓,因此挖礦就有可能了恩溅。但是,它仍然缺少一些至關(guān)重要的特性:區(qū)塊鏈數(shù)據(jù)庫(kù)并不是持久化的谓娃,沒(méi)有錢(qián)包脚乡,地址,交易滨达,也沒(méi)有共識(shí)機(jī)制奶稠。不過(guò),所有的這些捡遍,我們都會(huì)在接下來(lái)的文章中實(shí)現(xiàn)锌订,現(xiàn)在,愉快地挖礦吧稽莉!

參考:

block.go文件

package block

import (
"time"

)

type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}
/*
在這里可以看到noce 被保存為Block的一個(gè)屬性瀑志,這是十分有必要的
待會(huì)我們工作量進(jìn)行驗(yàn)證時(shí)會(huì)用到nonce。Block 結(jié)構(gòu)現(xiàn)在看起來(lái)像是這樣

*/

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

}

/*
跟之前所講的一樣簡(jiǎn)單∥鄹眩現(xiàn)在我們可以移除 Block 的 SetHash 方法劈猪,然后修改 NewBlock 函數(shù):
type Block struct {
Timestamp int64
Data []byte
PrevBlockHash []byte
Hash []byte
Nonce int
}

//SetHash calculates and sets block hash
func (b *Block) SetHash() {
timestamp := []byte(strconv.FormatInt(b.Timestamp, 10))
headers := bytes.Join([][]byte{b.PrevBlockHash, b.Data, timestamp}, []byte{})
hash := sha256.Sum256(headers)

b.Hash = hash[:]

}
*/
func NewGenesisBlock() *Block {
return NewBlock("這是創(chuàng)世區(qū)塊鏈", []byte{})
}

Blockchain.go文件

package block

type Blockchain struct {
Blocks []*Block
}

func (bc *Blockchain) AddBlock(data string) {
prevBlock := bc.Blocks[len(bc.Blocks)-1]
newBlock := NewBlock(data, prevBlock.Hash)
bc.Blocks = append(bc.Blocks, newBlock)
}

func NewBlockchain() Blockchain {
return &Blockchain{[]
Block{NewGenesisBlock()}}
}

ProofOfWork.go文件

package block

import (
"bytes"
"crypto/sha256"
"fmt"
"math"
"math/big"
"kongyxueyuan.com/PublicChain/part_2/utils"
)

var (
maxNonce = math.MaxInt64
)

const targetBits = 16

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

}

func (pow *ProofOfWork) prepareData(nonce int) []byte {
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.Data,
utils.IntToHex(pow.block.Timestamp),
utils.IntToHex(int64(targetBits)),
utils.IntToHex(int64(nonce)),
},
[]byte{},
)

return data

}

func (pow ProofOfWork) Run() (int, []byte) {
/

HashInt 是hash的整形表示
noce是計(jì)數(shù)器
然后開(kāi)始一個(gè)無(wú)限循環(huán)maxNoce 對(duì)這個(gè)循環(huán)進(jìn)行限制,
它等于max.Maxint64,這是為了避免noce可能出現(xiàn)的益處良拼,
盡管我們pow的難度很小战得,以至于計(jì)數(shù)器不太可能會(huì)溢出,但是最好還是以防萬(wàn)一

 */
var hashInt big.Int
var hash [32]byte
nonce := 0

fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data)
/*
1.準(zhǔn)備數(shù)據(jù)
2庸推。用SHA-256對(duì)數(shù)據(jù)進(jìn)行哈希
3常侦。將哈辖奖互贊換成一個(gè)大整數(shù)
4。將這個(gè)大證書(shū)與目標(biāo)進(jìn)行比較

 */
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[:]

}
/*4

這是最后一件事情聋亡,對(duì)工作證明的驗(yàn)證:如下函數(shù)
*/
func (pow *ProofOfWork) Validate() bool {
var hashInt big.Int

data := pow.prepareData(pow.block.Nonce)
fmt.Println("=========================")
fmt.Printf("%x\n",data)
hash := sha256.Sum256(data)
hashInt.SetBytes(hash[:])

isValid := hashInt.Cmp(pow.target) == -1

return isValid

}

工具類(lèi)方法通用

package utils

import (
"bytes"
"encoding/binary"
"log"
)

// IntToHex converts an int64 to a byte array
func IntToHex(num int64) []byte {
buff := new(bytes.Buffer)
err := binary.Write(buff, binary.BigEndian, num)
if err != nil {
log.Panic(err)
}

return buff.Bytes()

}

主函數(shù)文件main.go

package main

import (
"fmt"
"strconv"
//"kongyixueyuan_1802/PublicChain/part_2/block"
"kongyxueyuan.com/PublicChain/part_2/block"
)

func main() {
bc := block.NewBlockchain()

bc.AddBlock("Send 1 BTC to 冉冉")
bc.AddBlock("Send 2 more BTC to 冉冉")

for _, Block := range bc.Blocks {
    fmt.Printf("Prev. hash: %x\n", Block.PrevBlockHash)
    fmt.Printf("Data: %s\n", Block.Data)
    fmt.Printf("Hash: %x\n", Block.Hash)

    pow := block.NewProofOfWork(Block)
    fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate()))
    fmt.Println()
}

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肘习,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坡倔,更是在濱河造成了極大的恐慌漂佩,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罪塔,死亡現(xiàn)場(chǎng)離奇詭異投蝉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)征堪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)瘩缆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人佃蚜,你說(shuō)我怎么就攤上這事庸娱。” “怎么了谐算?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵涌韩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我氯夷,道長(zhǎng),這世上最難降的妖魔是什么靶擦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任腮考,我火速辦了婚禮,結(jié)果婚禮上玄捕,老公的妹妹穿的比我還像新娘踩蔚。我一直安慰自己,他們只是感情好枚粘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布馅闽。 她就那樣靜靜地躺著,像睡著了一般馍迄。 火紅的嫁衣襯著肌膚如雪福也。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天攀圈,我揣著相機(jī)與錄音暴凑,去河邊找鬼。 笑死赘来,一個(gè)胖子當(dāng)著我的面吹牛现喳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼姿现,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纹腌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起灸促,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诫欠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后腿宰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體呕诉,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年吃度,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了甩挫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡椿每,死狀恐怖伊者,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情间护,我是刑警寧澤亦渗,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站汁尺,受9級(jí)特大地震影響法精,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜痴突,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一搂蜓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辽装,春花似錦帮碰、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拓巧,卻和暖如春斯碌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肛度。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工输拇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贤斜。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓策吠,卻偏偏與公主長(zhǎng)得像逛裤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子猴抹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容