導語:本文作者在前幾篇文章中展示了一個簡單的區塊鏈,包括生成塊,驗證資料,廣播通訊等。本文繼續前文,介紹了PoS演演算法的基本原理,並且用golang實現了簡單的PoS區塊鏈。
譯者: ChainGod(孫飛)
原文連結: http://chaingod.io/article/16
在本系列前三篇文章中[1][2][3],我們向大家展示瞭如何透過精煉的Go程式碼實現一個簡單的區塊鏈。包括生成塊,驗證塊資料,廣播通訊等等,這一篇讓我們聚焦在如何實現 PoW演演算法。
PoS簡介
在上一篇文章[3]中,我們討論了工作量證明(Proof of Work),並向您展示瞭如何編寫自己的工作量證明區塊鏈。當前最流行的兩個區塊鏈平臺,比特幣和以太坊都是基於工作量證明的。
但是工作證明的缺點是什麼呢?其中一個主要的問題是電力能源的消耗。為了挖掘更多的比特幣,就需要建立更多的挖礦硬體池,現在在世界各地,挖礦池都在不斷建立中,而且呈現出規模越來越大的趨勢。例如以下這張照片(僅僅是礦池的一角):
挖礦工作需要耗費大量的電力,僅比特幣開採耗費的能源就超過了159個國家的電力能源消耗總和!!這種能源消耗是非常非常不合理的,而且,從技術的角度來看,工作量證明還有其他不足之處:隨著越來越多的人參與到挖礦工作中,共識演演算法的難度就需要提高,難度的提高意味著需要更多、更長時間的挖礦,也意味著區塊和交易需要更長的時間才能得到處理,因此能源的消耗就會越發的高。總之,工作量證明的方式就是一場競賽,你需要更多的計算能力才能有更大的機率贏得比賽。
有很多區塊鏈學者都試圖找到工作量證明的替代品,到目前為止最有希望的就是PoS(權益證明,Proof of Stake)。目前在生產環境,已經有數個區塊鏈平臺使用了PoS,例如Nxt 和Neo。以太坊在不遠的未來也很可能會使用PoS——他們的Casper專案已經在測試網路上執行和測試了。
那麼,到底什麼才是股權證明PoS呢?
在PoW中,節點之間透過hash的計算力來競賽以獲取下一個區塊的記賬權,而在PoS中,塊是已經鑄造好的(這裡沒有“挖礦”的概念,所以我們不用這個詞來證明股份),鑄造的過程是基於每個節點(Node)願意作為抵押的令牌(Token)數量。
這些參與抵押的節點被稱為驗證者(Validator),**註意在本文後續內容中,驗證者和節點的概念是等同的!**令牌的含義對於不同的區塊鏈平臺是不同的,例如,在以太坊中,每個驗證者都將Ether作為抵押品。
如果驗證者願意提供更多的令牌作為抵押品,他們就有更大的機會記賬下一個區塊並獲得獎勵。你可以把獎勵的區塊看作是存款利息,你在銀行存的錢越多,你每月的利息就會越高。
因此,這種共識機制被稱為股權證明PoS。
PoS的缺陷是什麼?
您可能已經猜到,一個擁有大量令牌的驗證者會在建立新塊時根據持有的令牌數量獲得更高的機率。然而,這與我們在工作量證明中看到的並沒有什麼不同:比特幣礦場變得越來越強大,普通人在自己的電腦上開採多年也未必能獲得一個區塊。
因此,許多人認為,使用了PoS後,區塊的分配將更加民主化,因為任何人都可以在自己的筆記本上參與,而不需要建立一個巨大的採礦平臺,他們不需要昂貴的硬體,只需要一定的籌碼,就算籌碼不多,也有一定機率能獲得區塊的記賬權,希望總是有的,你說呢?
從技術和經濟的角度來看,還有其他不利因素。我們不會一一介紹,但這裡有一個很好的介紹。在實際應用中,PoS和PoW都有自己的優點和缺點,因此以太坊的Casper具有兩者混合的特徵。
像往常一樣,瞭解PoS的方法是編寫自己的程式碼,那麼,我們開始吧!
編寫PoS程式碼
我們建議在繼續之前看一下200行Go程式碼編寫區塊鏈Part2[1],因為在接下來的文章中,一些基礎知識不再會介紹,因此這篇文章能幫助你回顧一下。
註意
我們將實現PoS的核心概念,然後因為文章長度有限,因此一些不必要的程式碼獎省去!
-
P2P網路的實現。文中的網路是模擬的,區塊鏈狀態只在其中一個中心化節點持有,而不是每個節點,同時狀態透過該持有節點廣播到其它節點
-
錢包和餘額變動。本文沒有實現一個錢包,持有的令牌數量是透過stdin(標準輸入)輸入的,你可以輸入你想要的任何數量。一個完整的實現會為每個節點分配一個hash地址,併在節點中跟蹤餘額的變動
架構圖
-
我們將有一個中心化的TCP服務節點,其他節點可以連線該伺服器
-
最新的區塊鏈狀態將定期廣播到每個節點
-
每個節點都能提議建立新的區塊
-
基於每個節點的令牌數量,其中一個節點將隨機地(以令牌數作為加權值)作為獲勝者,並且將該區塊新增到區塊鏈中
設定和匯入
在開始寫程式碼之前,我們需要一個環境變數來設定TCP伺服器的埠,首先在工作檔案夾中建立.env檔案,寫入一行配置:
ADDR=9000
我們的Go程式將讀取該檔案,並且暴露出9000埠。同時在工作目錄下,再建立一個main.go檔案。
-
spew 可以把我們的區塊鏈用漂亮的格式列印到終端terminal中
-
godotenv 允許我們從之前建立的.env檔案讀取配置
快速脈搏檢查
如果你讀過我們的其他教程,就會知道我們是一家醫療保健公司,目前要去收集人體脈搏資訊,同時新增到我們的區塊上。把兩個手指放在你的手腕上,數一下你一分鐘能感覺到多少次脈搏,這將是您的BPM整數,我們將在接下來的文章中使用。
全域性變數
現在,讓我們宣告我們需要的所有全域性變數(main.go中)。
-
Block是每個區塊的內容
-
Blockchain是我們的官方區塊鏈,它只是一串經過驗證的區塊集合。每個區塊中的PrevHash與前面塊的Hash相比較,以確保我們的鏈是正確的
-
tempBlocks是臨時儲存單元,在區塊被選出來並新增到BlockChain之前,臨時儲存在這裡
-
candidateBlocks是Block的通道,任何一個節點在提出一個新塊時都將它傳送到這個通道
-
announcements也是一個通道,我們的主Go TCP伺服器將向所有節點廣播最新的區塊鏈
-
mutex是一個標準變數,允許我們控制讀/寫和防止資料競爭
-
validators是節點的儲存map,同時也會儲存每個節點持有的令牌數
基本的區塊鏈函式
在繼續PoS演演算法之前,我們先來實現標準的區塊鏈函式。如果你之前看過200行Go程式碼編寫區塊鏈,那接下來應該更加熟悉。
main.go
這裡先從hash函式開始,calculateHash函式會接受一個string,並且傳回一個SHA256 hash。calculateBlockHash是對一個block進行hash,將一個block的所有欄位連線到一起後,再進行hash。
main.go
generateBlock是用來建立新塊的。每個新塊都有的一個重要欄位是它的hash簽名(透過calculateBlockHash計算的)和上一個連線塊的PrevHash(因此我們可以保持鏈的完整性)。我們還添加了一個Validator欄位,這樣我們就知道了該構建塊的獲勝節點。
main.go
isBlockValid會驗證Block的當前hash和PrevHash,來確保我們的區塊鏈不會被汙染。
節點(驗證者)
當一個驗證者連線到我們的TCP服務,我們需要提供一些函式達到以下標的:
-
輸入令牌的餘額(之前提到過,我們不做錢包等邏輯)
-
接收區塊鏈的最新廣播
-
接收驗證者贏得區塊的廣播資訊
-
將自身節點新增到全域性的驗證者串列中(validators)
-
輸入Block的BPM資料- BPM是每個驗證者的人體脈搏值
-
提議建立一個新的區塊
這些標的,我們用handleConn函式來實現
main.go
第一個Go協程接收並打印出來自TCP伺服器的任何通知,這些通知包含了獲勝驗證者的通知。
io.WriteString(conn, “Enter token balance:”)允許驗證者輸入他持有的令牌數量,然後,該驗證者被分配一個SHA256地址,隨後該驗證者地址和驗證者的令牌數被新增到驗證者串列validators中。
接著我們輸入BPM,驗證者的脈搏值,並建立一個單獨的Go協程來處理這塊兒邏輯,下麵這一行程式碼很重要:
delete(validators, address)
如果驗證者試圖提議一個被汙染(例如偽造)的block,例如包含一個不是整數的BPM,那麼程式會丟擲一個錯誤,我們會立即從我們的驗證器串列validators中刪除該驗證者,他們將不再有資格參與到新塊的鑄造過程同時丟失相應的抵押令牌。
正式因為這種抵押令牌的機制,使得PoS協議是一種更加可靠的機制。如果一個人試圖偽造和破壞,那麼他將被抓住,並且失去所有抵押和未來的權益,因此對於惡意者來說,是非常大的威懾。
接著,我們用generateBlock函式建立一個新的block,然後將其傳送到candidateBlocks通道進行進一步處理。將Block傳送到通道使用的語法:
candidateBlocks
上面程式碼中最後一段的迴圈會週期性的打印出最新的區塊鏈,這樣每個驗證者都能獲知最新的狀態
選擇獲勝者
這裡是PoS的主題邏輯。我們需要編寫程式碼以實現獲勝驗證者的選擇;他們所持有的令牌數量越高,他們就越有可能被選為勝利者。
為了簡化程式碼,我們只會讓提出新塊兒的驗證者參與競爭。在傳統的PoS,一個驗證者即使沒有提出一個新的區塊,也可以被選為勝利者。切記,PoS不是一種確定的定義(演演算法),而是一種概念,因此對於不同的平臺來說,可以有不同的PoS實現。
下麵來看看pickWinner函式:
main.go
每隔30秒,我們選出一個勝利者,這樣對於每個驗證者來說,都有時間提議新的區塊,參與到競爭中來。接著建立一個lotteryPool,它會持有所有驗證者的地址,這些驗證者都有機會成為一個勝利者。然後,對於提議塊的暫存區域,我們會透過if len(temp) > 0來判斷是否已經有了被提議的區塊。
在OUTER FOR迴圈中,要檢查暫存區域是否和lotteryPool中存在同樣的驗證者,如果存在,則跳過。
在以k, ok := setValidators[block.Validator]開始的程式碼塊中,我們確保了從temp中取出來的驗證者都是合法的,即這些驗證者在驗證者串列validators已存在。若合法,則把該驗證者加入到lotteryPool中。
那麼我們怎麼根據這些驗證者持有的令牌數來給予他們合適的隨機權重呢?
首先,用驗證者的令牌填充lotteryPool陣列,例如一個驗證者有100個令牌,那麼在lotteryPool中就將有100個元素填充;如果有1個令牌,那麼將僅填充1個元素。
然後,從lotteryPool中隨機選擇一個元素,元素所屬的驗證者即是勝利者,把勝利驗證者的地址賦值給lotteryWinner。這裡能夠看出來,如果驗證者持有的令牌越多,那麼他在陣列中的元素也越多,他獲勝的機率就越大;同時,持有令牌很少的驗證者,也是有機率獲勝的。
接著我們把獲勝者的區塊新增到整條區塊鏈上,然後通知所有節點關於勝利者的訊息:announcements
最後,清空tempBlocks,以便下次提議的進行。
以上便是PoS一致性演演算法的核心內容,該演演算法簡單、明瞭、公正,所以很酷!
收尾
下麵我們把之前的內容透過程式碼都串聯起來
main.go
這裡從.env檔案開始,然後建立一個創世區塊genesisBlock,形成了區塊鏈。接著啟動了Tcp服務,等待所有驗證者的連線。
啟動了一個Go協程從candidateBlocks通道中獲取提議的區塊,然後填充到臨時緩衝區tempBlocks中,最後啟動了另外一個Go協程來完成pickWinner函式。
最後面的for迴圈,用來接收驗證者節點的連線。
這裡是所有的原始碼[2]:
結果
下麵來執行程式,開啟一個終端視窗,透過go run main.go來啟動整個TCP程式,如我們所料,首先建立了創始區塊genesisBlock。
接著,我們啟動並連線一個驗證者。開啟一個新的終端視窗,透過linux命令nc localhost 9000來連線到之前的TCP服務。然後在命令提示符後輸入一個持有的令牌數額,最後再輸入一個驗證者的脈搏速率BPM。
然後觀察第一個視窗(主程式),可以看到驗證者被分配了地址,而且每次有新的驗證者加入時,都會列印所有的驗證者串列
稍等片刻,檢查下你的新視窗(驗證者),可以看到正在發生的事:我們的程式在花費時間選擇勝利者,然後Boom一聲,一個勝利者就誕生了!
再稍等一下,boom! 我們看到新的區塊鏈被廣播給所有的驗證者視窗,包含了勝利者的區塊和他的BPM資訊。很酷吧!
下一步做什麼
你應該為能透過本教程感到驕傲。大多數區塊鏈的發燒友和許多程式員都聽說過PoS的證明,但他們很多都無法解釋它到底是什麼。你已經做得更深入了,而且實際上已經從頭開始實現了一遍,你離成為下一代區塊鏈技術的專家又近了一步!
因為這是一個教程,我們可以做更多的事情來讓它成為區塊鏈,例如:
-
閱讀我們的PoW,然後結合PoS,看看你是否可以建立一個混合區塊鏈
-
新增時間機制,驗證者根據時間塊來獲得提議新區快的機率。我們這個版本的程式碼讓驗證者可以在任何時候提議新的區塊。
-
新增完整的點對點的能力。這基本上意味著每個驗證者將執行自己的TCP伺服器,並連線到其他的驗證者節點。這裡需要新增邏輯,這樣每個節點都可以找到彼此,這裡[3]有更多的內容。
或者你可以學習一下我們其它的教程。
參考連結:
[2] 200行Go程式碼實現自己的區塊鏈——區塊生成與網路通訊
相關閱讀:
特別推薦:
比特幣、以太坊、ERC20、PoW、PoS、智慧合約、閃電網路……
想深入瞭解及討論這些話題?高可用架構在知識星球(小密圈)建立了區塊鏈學習小組,共同學習區塊鏈包括數字貨幣前沿技術,歡迎點選連結加入。
本文作者 Coral Health,由ChainGod(孫飛)翻譯。轉載譯文請註明出處,技術原創及架構實踐文章,歡迎透過公眾號選單「聯絡我們」進行投稿。
高可用架構
改變網際網路的構建方式
長按二維碼 關註「高可用架構」公眾號