如何使用Go構建權益區塊鏈證明
本文轉載自微信公眾號「區塊鏈研究實驗室」,作者鏈三豐。轉載本文請聯系區塊鏈研究實驗室公眾號。
隨著區塊鏈給世界市場帶來的革命,在作出預測之前了解基礎知識至關重要。在本文中,我們將探討權益證明的基礎知識,該證明是一種區塊鏈協議,類似于一種在區塊鏈中偽造新區塊的彩票方法。
本文的主要目標如下:
- 了解區塊鏈領域的當前性能趨勢。
- 通過GoLang中的一個工作示例學習權益證明。
- 升級您的計算機科學和Go編程技能。
這將是一個有趣的過程,讓我們開始編寫代碼。
了解權益證明
股權證明(PoS)的基礎實際上很簡單。當然,該系統的核心組件是區塊鏈本身。簡而言之,區塊鏈是一個不變的分類賬,每個單獨的區塊都是從之前的區塊以密碼方式構建的。您永遠都無法更改區塊鏈的任何部分,因為連接到網絡的每個人都可以輕松看到更改并駁斥您的區塊鏈版本。
在過程中創造新的塊是由您的區塊鏈協議定義,比特幣是基于工作量證明(PoW)協議構建的,當中需要越來越多的計算能力才能通過數學過程驗證以前的交易,所以每次您驗證區塊中包含的交易列表時,都會以比特幣的形式獲得獎勵。
因此,交易歷史的證明在于您所從事的工作量,而完成這項工作的人稱為“礦工”。PoW的一個日益嚴重的問題是,隨著時間的流逝,解決這些數學難題所需的巨大計算能力。
權益證明在根本上是不同的,所以您無需在核算和擴展區塊鏈上具有計算能力,而可以在區塊鏈網絡上“占用”一定數量的令牌(不一定是加密貨幣)。通常情況可以通過創建自己的“節點”來完成的,以方便您參與區塊鏈生態系統。
如果節點采取勤懇的工作態度,您將有更大的機會在區塊鏈中創造一個新的區塊,并獲得原始返還的獎勵。被選擇偽造下一個區塊的可能性也與您在網絡上投入的令牌數量成比例增加;反之如果采取散惰的工作態度,您的賭注可能會受到處罰甚至被完全撤回。這種獎勵和懲罰方法旨在促進區塊鏈中的誠實工作,而沒有與工作量證明相關的計算可擴展性瓶頸。
現在我們已經有了PoS與PoW的概念概念,讓我們繼續在Go中編寫一個有效的PoS示例。
Go中的權益證明
導入“區塊鏈”
首先,除了定義自定義對象類型外,我們還需要在項目中包含一些Go包。這是您需要的軟件包-我們將使用math/rand,crypto/sha256以及encoding/hex用于加密區塊鏈方法。當然,這errors是Go的標準!
- package main
- import (
- "crypto/sha256"
- "encoding/hex"
- "errors"
- "fmt"
- "log"
- math "math/rand"
- "time"
- )
接下來是我們的自定義數據類型。使用Go可以使此超級簡單structs。在這里我們有3個習慣types,第一個是PoSNetwork我們有一個Blockchain字段,該字段是對實例的引用的數組Block struct。我們將通過BlockchainHead字段跟蹤最近添加的塊,并且還將有一系列對的引用以Node struct用作Validators。
- type PoSNetwork struct {
- Blockchain []*Block
- BlockchainHead *Block
- Validators []*Node
- }
- type Node struct {
- Stake int
- Address string
- }
- type Block struct {
- Timestamp string
- PrevHash string
- Hash string
- ValidatorAddr string
- }
該Node結構將具有一個Stake字段,該字段代表它添加到網絡中的令牌數量。該地址將是一個隨機生成的字符串,以便我們可以跟蹤哪個節點成功驗證了下一個塊。
最后,Block struct將包含跟蹤區塊鏈所需的信息。Timestamp創建該塊時,我們將有一個for,來自上一個塊的前一個哈希,Hash表示自身以及Node驗證此塊的的地址-所有type string。
逐塊建造區塊鏈磚
這是我們構建區塊鏈的第一種方法。首先,在函數簽名中,將此方法附加到,PoSNetwork struct并將該結構引用為n。然后,我們Node將對a的引用作為唯一參數。我們將返回一個新的Block引用數組來表示new Blockchain,對a的引用Block將是new BlockchainHead,以及可能出現error毛病的可能。
您會看到,ValidateBlockchain()我們甚至在嘗試添加任何內容之前就立即調用了該方法。稍后我們將進行驗證,但是只要知道我們發現要更改的區塊鏈,就會知道有邏輯要懲罰a Node。
- func (n PoSNetwork) GenerateNewBlock(Validator *Node) ([]*Block, *Block, error) {
- if err := n.ValidateBlockchain(); err != nil {
- Validator.Stake -= 10
- return n.Blockchain, n.BlockchainHead, err
- }
- currentTime := time.Now().String()
- newBlock := &Block {
- Timestamp: currentTime,
- PrevHash: n.BlockchainHead.Hash,
- Hash: NewBlockHash(n.BlockchainHead),
- ValidatorAddr: Validator.Address,
- }
- if err := n.ValidateBlockCandidate(newBlock); err != nil {
- Validator.Stake -= 10
- return n.Blockchain, n.BlockchainHead, err
- } else {
- n.Blockchain = append(n.Blockchain, newBlock)
- }
- return n.Blockchain, newBlock, nil
- }
在檢查Blockchain是否完好無損之后,我們獲得了系統的當前時間,將其存儲為Timestamp實例化new時的時間Block。我們還附上了Hash以前值,您可以輕松訪問BlockchainHead。之后我們將在NewBlockHash()上調用方法BlockchainHead,并將輸入的地址分配Node為我們的驗證器地址。
一旦新塊的字段被填滿,我們調用ValidateBlockCandidate()上新Block,看看有沒有錯誤。如果有,我們返回一層。如果沒有,我們將把新塊附加到區塊鏈上。
但是,NewBlockHash()是如何工作的呢?我們有兩個函數來完成為每個塊創建唯一Hash的任務。函數 New Block Hash ()只是獲取 Block 的所有信息,并將其連接成一個字符串傳遞給 new Hash ()。
- func NewBlockHash(block *Block) string {
- blockInfo := block.Timestamp + block.PrevHash + block.Hash + block.ValidatorAddr
- return newHash(blockInfo)
- }
- func newHash(s string) string {
- h := sha256.New()
- h.Write([]byte(s))
- hashed := h.Sum(nil)
- return hex.EncodeToString(hashed)
- }
接下來,newHash()將利用該crypto/sha256包創建一個存儲為的新SHA256對象h。然后,我們將輸入字符串s轉換為字節數組,并將其寫入h。最后,我們使用h.Sum()讓h進入的格式,我們可以調用hex.EncodeToString(),使我們有一個string為我們的最終輸出。
驗證我們的區塊鏈
如果你不能驗證,則區塊鏈就沒有用。在這里,我們將ValidateBlockchain()方法附加到PoSNetwork結構,并返回一個可能的錯誤。如果區塊鏈是空的或者只有一個區塊,我們無法確保它是正確的所以我們只返回nil。
接下來,我們評估區塊鏈中每對塊之間的三個獨立條件。第一個檢查是前一個塊的哈希值是否等于當前塊為它的前一哈希值存儲的值。
- func (n PoSNetwork) ValidateBlockchain() error {
- if len(n.Blockchain) <= 1 {
- return nil
- }
- currBlockIdx := len(n.Blockchain)-1
- prevBlockIdx := len(n.Blockchain)-2
- for prevBlockIdx >= 0 {
- currBlock := n.Blockchain[currBlockIdx]
- prevBlock := n.Blockchain[prevBlockIdx]
- if currBlock.PrevHash != prevBlock.Hash {
- return errors.New("blockchain has inconsistent hashes")
- }
- if currBlock.Timestamp <= prevBlock.Timestamp {
- return errors.New("blockchain has inconsistent timestamps")
- }
- if NewBlockHash(prevBlock) != currBlock.Hash {
- return errors.New("blockchain has inconsistent hash generation")
- }
- currBlockIdx--
- prevBlockIdx--
- }
- return nil
- }
我們還要檢查是否在任何點上一個塊的時間戳比當前塊新。如果當前的Block是在2020年制造的,但之前的Blot是在2021年制造,此時就知道出問題了。
最后,我們還要直接計算Hash前一個的Block,我們仍然會取回Hash當前的Block。如果滿足這些條件中的任何一個,則error由于我們的區塊鏈處于篡改狀態,我們將返回。
現在我們已經驗證了整個區塊鏈,我們需要確保要添加的下一個Block也是有效的。遵循與上述相同的條件檢查,僅適用于要添加的單個新塊。
- func (n PoSNetwork) ValidateBlockCandidate(newBlock *Block) error {
- if n.BlockchainHead.Hash != newBlock.PrevHash {
- return errors.New("blockchain HEAD hash is not equal to new block previous hash")
- }
- if n.BlockchainHead.Timestamp >= newBlock.Timestamp {
- return errors.New("blockchain HEAD timestamp is greater than or equal to new block timestamp")
- }
- if NewBlockHash(n.BlockchainHead) != newBlock.Hash {
- return errors.New("new block hash of blockchain HEAD does not equal new block hash")
- }
- return nil
- }
現在我們已經完成了向區塊鏈添加新區塊以及驗證其正確性的步驟。那么,我們如何決定何時添加新塊?
這就是我們的驗證器起作用的地方,對于每個與網絡有利益關系的節點,我們將通過抽獎方法隨機選擇一個節點,以偽造區塊鏈中的下一個區塊并獲得獎勵。
首先,我們首先需要一個Node。要添加新的Node給我們的PoSNetwork,我們稱之為NewNode()它接受的初始股份Node,并返回一個新的數組Node引用。這里沒有什么幻想,我們只是追加到n.Validators數組并調用randAddress()以為new生成唯一地址Node。
- func (n PoSNetwork) NewNode(stake int) []*Node {
- newNode := &Node{
- Stake: stake,
- Address: randAddress(),
- }
- n.Validators = append(n.Validators, newNode)
- return n.Validators
- }
- func randAddress() string {
- b := make([]byte, 16)
- _, _ = math.Read(b)
- return fmt.Sprintf("%x", b)
- }
那么,我們如何實際選擇獲勝者呢?有一點概率和統計信息!在該SelectWinner()方法中,我們首先通過覆蓋范圍找到網絡內持有的全部股份n.Validators。我們還將所有權益大于零的節點添加到數組中winnerPool以進行可能的選擇。
如果發現winnerPool為空,則返回error。。然后,我們使用該Intn()方法選擇一個中獎號碼,該方法將從0到我們的總投注額之間選擇一個隨機數。
- func (n PoSNetwork) SelectWinner() (*Node, error) {
- var winnerPool []*Node
- totalStake := 0
- for _, node := range n.Validators {
- if node.Stake > 0 {
- winnerPool = append(winnerPool, node)
- totalStake += node.Stake
- }
- }
- if winnerPool == nil {
- return nil, errors.New("there are no nodes with stake in the network")
- }
- winnerNumber := math.Intn(totalStake)
- tmp := 0
- for _, node := range n.Validators {
- tmp += node.Stake
- if winnerNumber < tmp {
- return node, nil
- }
- }
- return nil, errors.New("a winner should have been picked but wasn't")
- }
最后一部分是概率發揮作用的地方。為了使每個節點都有Stake與網絡中的總數成正比的獲勝機會,我們將Stake電流的增量累加Node到tmp變量中。如果在任何時候獲勝的數字小于tmp,Node則被選為我們的獲勝者。
匯集全部
我們現在所需要的就是將我們的函數和數據類型綁定在一起。我們在main()函數中做好所有的事情,第一步是設置一個隨機種子與當前時間作為我們的輸入。不要使用時間作為隨機種子的輸入,因為它實際上會在解碼哈希輸出時引入安全漏洞。
在這個示例中,我們需要實例化一個新的ProofStake網絡,其中有一個被稱為Genesis塊,也就是我們所知的塊。區塊鏈中的第一個區塊。一旦我們這樣做,我們也設置網絡的BlockchainHead等于第一個塊。
- func main() {
- // set random seed
- math.Seed(time.Now().UnixNano())
- // generate an initial PoS network including a blockchain with a genesis block.
- genesisTime := time.Now().String()
- pos := &PoSNetwork{
- Blockchain: []*Block{
- {
- Timestamp: genesisTime,
- PrevHash: "",
- Hash: newHash(genesisTime),
- ValidatorAddr: "",
- },
- },
- }
- pos.BlockchainHead = pos.Blockchain[0]
- // instantiate nodes to act as validators in our network
- pos.Validators = pos.NewNode(60)
- pos.Validators = pos.NewNode(40)
- // build 5 additions to the blockchain
- for i := 0; i < 5; i++ {
- winner, err := pos.SelectWinner()
- if err != nil {
- log.Fatal(err)
- }
- winner.Stake += 10
- pos.Blockchain, pos.BlockchainHead, err = pos.GenerateNewBlock(winner)
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Round ", i)
- fmt.Println("\tAddress:", pos.Validators[0].Address, "-Stake:", pos.Validators[0].Stake)
- fmt.Println("\tAddress:", pos.Validators[1].Address, "-Stake:", pos.Validators[1].Stake)
- }
- pos.PrintBlockchainInfo()
- }
然后,我們添加兩個節點的網絡作為驗證器與60和40令牌作為他們的初始股份。在五次迭代中,我們將為區塊鏈選擇一個新的贏家,如果有任何錯誤,我們的程序將崩潰-因為做原型我們通過新選擇的贏家產生一個新的塊,并打印出每一輪的每個節點的總樁。
最后,我們將打印出我們新制作的區塊鏈:
- $ go run main.go
- Round 0
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 70
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 40
- Round 1
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 70
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Round 2
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 80
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Round 3
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 90
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Round 4
- Address: f8d44bb083078de97b8428f4f9548130 -Stake: 100
- Address: de6ae18584f02b3388569191a04a4b4a -Stake: 50
- Block 0 Info:
- Timestamp: 2021-04-12 MDT m=+0.000120025
- Previous Hash:
- Hash: c5d04de14efed52ce84889c6382f9d307d5b98093d93a84b419478
- Validator Address:
- Block 1 Info:
- Timestamp: 2021-04-12 MDT m=+0.000277288
- Previous Hash: c5d04de14efed52ce84889c6382f9d307d5b98093d93a
- Hash: d58e90a75b71ac62ef938fc0148314a7f864ad50bd702f959e2d27
- Validator Address: f8d44bb083078de97b8428f4f9548130
- Block 2 Info:
- Timestamp: 2021-04-12 MDT m=+0.000306562
- Previous Hash: d58e90a75b71ac62ef938fc0148314a7f864ad50bd702
- Hash: e6bfdd6c2c869607e2d9a81b84ddf4478756fedff78a03746cde11
- Validator Address: de6ae18584f02b3388569191a04a4b4a
- Block 3 Info:
- Timestamp: 2021-04-12 MDT m=+0.000321755
- Previous Hash: e6bfdd6c2c869607e2d9a81b84ddf4478756fedff78a0
- Hash: 8e3dbacc4a610b1665658bc9e7238963eda0d5bbbf3ce809e8fa6e
- Validator Address: f8d44bb083078de97b8428f4f9548130
- Block 4 Info:
- Timestamp: 2021-04-12 MDT m=+0.000333024
- Previous Hash: 8e3dbacc4a610b1665658bc9e7238963eda0d5bbbf3ce
- Hash: 22760f8deb96c354a4050a3c48741be062bccfa9c51571c170065a
- Validator Address: f8d44bb083078de97b8428f4f9548130
- Block 5 Info:
- Timestamp: 2021-04-12 MDT m=+0.000347521
- Previous Hash: 22760f8deb96c354a4050a3c48741be062bccfa9c5157
- Hash: d2a5047f7d8a7696c1d0fb9ec49b56d2e71bbcedaaebc83a18b7a5
- Validator Address: f8d44bb083078de97b8428f4f9548130























