本文 mirror 链接:使用 golang 從零開始搭建基於 UTXO 模型的區塊鏈(二、項目重構 + PoW)
前言#
在上一章中我們了解了區塊是什麼以及區塊與區塊鏈之間的關係。在這一章中我們將此項目重構,並拓寬區塊的頭部信息,並講解區塊如何通過共識機制合法的被添加進區塊鏈中。
項目重構#
在上一章中,我們所有的代碼都寫在了 main.go 中,這顯然不利於我們繼續構建項目。我們希望 main.go 只用於最後啟動我們的區塊鏈系統,為此我們需要將設計的區塊與區塊鏈移植其它文件夾中。重構部分就不做過多講解了。
結構:
utils 包負責放置一些工具方法,隨拿隨用;constcoe 放置全局常量;blockchain 種放置區塊鏈實現的主體。
我們把之前的 int64 轉 byte 函數放到 utils 裡
package utils
import (
"encoding/binary"
)
func Int64ToByte(num int64) []byte {
var buf = make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(num))
return buf
}
constcoe 裡面先存上難度係數,這個後面會詳細說
package constcoe
const (
Difficulty = 12
)
block.go 和 blockchain.go 把之前寫的構造體和函數放入
//block.go
package blockchain
import (
"bytes"
"crypto/sha256"
"lighteningchain/utils"
"time"
)
type Block struct {
Timestamp int64
Hash []byte
PrevHash []byte
Data []byte
}
func (b *Block) SetHash() {
information := bytes.Join([][]byte{utils.Int64ToByte(b.Timestamp), b.PrevHash, b.Data}, []byte{})
hash := sha256.Sum256(information)
b.Hash = hash[:]
}
func CreateBlock(prevhash, data []byte) *Block {
block := Block{time.Now().Unix(), []byte{}, prevhash, data}
block.SetHash()
return &block
}
func GenesisBlock() *Block {
genesisWords := "HelloWorld"
return CreateBlock([]byte{}, []byte(genesisWords))
}
//blockchain.go
package blockchain
type BlockChain struct {
Blocks []*Block
}
func (bc *BlockChain) AddBlock(data string) {
newBlock := CreateBlock(bc.Blocks[len(bc.Blocks)-1].Hash, []byte(data))
bc.Blocks = append(bc.Blocks, newBlock)
}
func CreateBlockChain() *BlockChain {
blockchain := BlockChain{}
blockchain.Blocks = append(blockchain.Blocks, GenesisBlock())
return &blockchain
}
main.go 中,就剩下這些了
package main
import (
"fmt"
"lighteningchain/blockchain"
"time"
)
func main() {
blockchain := blockchain.CreateBlockChain()
time.Sleep(time.Second)
blockchain.AddBlock("This is first Block after Genesis")
time.Sleep(time.Second)
blockchain.AddBlock("This is second!")
time.Sleep(time.Second)
blockchain.AddBlock("Awesome!")
time.Sleep(time.Second)
for num, block := range blockchain.Blocks {
fmt.Printf("number:%d Timestamp: %d\n", num, block.Timestamp)
fmt.Printf("number:%d hash: %x\n", num, block.Hash)
fmt.Printf("number:%d Previous hash: %x\n", num, block.PrevHash)
fmt.Printf("number:%d data: %s\n", num, block.Data)
}
}
試著運行一下,運行成功!項目重構完成,這樣對以後開發方便多了
區塊鏈共識機制#
所謂 “共識機制” 是通過特殊節點的投票,在很短的時間內完成對交易的驗證和確認,對一筆交易,如果利益不相干的若干個節點能夠達成共識,我們就可以認為全網對此也能夠達成共識。再通俗一點來講,如果中國一名微博大 V、美國一名虛擬幣玩家、一名非洲留學生和一名歐洲旅行者互不相識,但他們都一致認為你是個好人,那麼基本上就可以斷定你這人還不壞。
pow 共識機制看下圖
接下來就用我們的閃電鏈來實現這個共識機制
添加 Nonce#
nonce 就是上圖中我們要找的那個隨機數,這是能證明你工作量的最關鍵的部分。首先在區塊上添加頭部信息
type Block struct {
Timestamp int64
Hash []byte //區塊hash值就是其ID
PrevHash []byte
Data []byte
Nonce int64
Target []byte
}
接下來會有一些函數報錯,我們後面再改
POW 實現#
在 proofofwork.go 中,首先實現一個獲取 target 函數,這個函數可以方便我們以後在分佈式系統中反復獲取 target
func (b *Block) GetTarget() []byte {
target := big.NewInt(1)
target.Lsh(target, uint(256-constcoe.Difficulty))
return target.Bytes()
}
Lsh 函數就是向左移位,difficulty 越小,移的越多,目標難度值越大,哈希取值落在的空間就更多就越容易找到符合條件的 nonce。
下面我們進行尋找 nonce 的計算
func (b *Block) GetDataBaseNonce(nonce int64) []byte {
data := bytes.Join([][]byte{
utils.Int64ToByte(b.Timestamp),
b.PrevHash,
utils.Int64ToByte(nonce),
b.Target,
b.Data,
},
[]byte{},
)
return data
}
func (b *Block) FindNonce() int64 {
var intHash big.Int
var intTarget big.Int
intTarget.SetBytes(b.Target)
var hash [32]byte
var nonce int64
nonce = 0
for nonce < math.MaxInt64 {
data := b.GetDataBaseNonce(nonce)
hash = sha256.Sum256(data)
intHash.SetBytes(hash[:])
if intHash.Cmp(&intTarget) == -1 {
break
} else {
nonce++
}
}
return nonce
}
可以看到,神秘的 nonce 不過是從 0 開始取的整數而已,隨著不斷嘗試,每次失敗 nonce 就加 1 直到由當前 nonce 得到的區塊哈希轉化為數值小於目標難度值為止。
那麼區塊鏈如何知道這個分佈式系統中你這個系統算出來的就是對的呢?下面我們需要寫一個驗證函數
func (b *Block) ValidatePoW() bool {
var intHash big.Int
var intTarget big.Int
var hash [32]byte
intTarget.SetBytes(b.Target)
data := b.GetDataBaseNonce(b.Nonce)
hash = sha256.Sum256(data)
intHash.SetBytes(hash[:])
if intHash.Cmp(&intTarget) == -1 {
return true
}
return false
}
pow 我們實現完了,接下來回到 block.go 中做點小修改
func (b *Block) SetHash() {
information := bytes.Join([][]byte{utils.Int64ToByte(b.Timestamp),
b.PrevHash, b.Target, utils.Int64ToByte(b.Nonce), b.Data}, []byte{})
hash := sha256.Sum256(information) //軟件包sha256 實現 FIPS 180-4 中定義的 SHA224 和 SHA256 哈希算法。
b.Hash = hash[:]
}
func CreateBlock(prevhash []byte, data []byte) *Block {
block := Block{time.Now().Unix(), []byte{},
prevhash, data, 0, []byte{}}
block.Target = block.GetTarget()
block.Nonce = block.FindNonce()
block.SetHash() //所有數據添加好後再計算hash
return &block
}
一切完成!
調試#
打開 main.go,我們加一行輸出來驗證 pow 是否成功
package main
import (
"fmt"
"lighteningchain/blockchain"
"time"
)
func main() {
blockchain := blockchain.CreateBlockChain()
time.Sleep(time.Second)
blockchain.AddBlock("This is first Block after Genesis")
time.Sleep(time.Second)
blockchain.AddBlock("This is second!")
time.Sleep(time.Second)
blockchain.AddBlock("Awesome!")
time.Sleep(time.Second)
for num, block := range blockchain.Blocks {
fmt.Printf("number:%d Timestamp: %d\n", num, block.Timestamp)
fmt.Printf("number:%d hash: %x\n", num, block.Hash)
fmt.Printf("number:%d Previous hash: %x\n", num, block.PrevHash)
fmt.Printf("number:%d data: %s\n", num, block.Data)
fmt.Printf("number:%d nonce:%d\n", num, block.Nonce)
fmt.Println("POW validation:", block.ValidatePoW())
}
}
點擊運行,輸出
number:0 Timestamp: 1677654426
number:0 hash: 51c810ee37b56f26baaf27ad8c8c271c1e383dcf75c6b8baaca059a9e621ac67
number:0 Previous hash:
number:0 data: HelloWorld!
number:0 nonce:14014
POW validation: true
number:1 Timestamp: 1677654427
number:1 hash: 059131a889810a8484bc072d0bcd7ecba3011a509ab6bc460c7a892357621f82
number:1 Previous hash: 51c810ee37b56f26baaf27ad8c8c271c1e383dcf75c6b8baaca059a9e621ac67
number:1 data: This is first Block after Genesis
number:1 nonce:1143
POW validation: true
number:2 Timestamp: 1677654428
number:2 hash: 055263bd8eea37b526e45b097b1f837c108ab2fc88f26bbf567a4fa9598cadb9
number:2 Previous hash: 059131a889810a8484bc072d0bcd7ecba3011a509ab6bc460c7a892357621f82
number:2 data: This is second!
number:2 nonce:10091
POW validation: true
number:3 Timestamp: 1677654429
number:3 hash: d0b5a049c2780c01e2e66cc23934267c528df80a3bcc69180a3f2231cf08d87f
number:3 Previous hash: 055263bd8eea37b526e45b097b1f837c108ab2fc88f26bbf567a4fa9598cadb9
number:3 data: Awesome!
number:3 nonce:592
POW validation: true
成功!
總結#
本章講解了 PoW 共識機制,需要重點理解 nonce 與目標難度值,以及 pow 的實現。下一章中我們將會實現區塊中的數據信息存儲方式,以及 UTXO 模型。
另外,現在區塊鏈主流的共識已經從 PoW 改為 PoS 了,以後有時間我再改進一下