使用Go语言从零编写PoS区块链
導(dǎo)語:本文作者在前幾篇文章中展示了一個(gè)簡單的區(qū)塊鏈,包括生成塊,驗(yàn)證數(shù)據(jù),廣播通信等。本文繼續(xù)前文,介紹了PoS算法的基本原理,并且用golang實(shí)現(xiàn)了簡單的PoS區(qū)塊鏈。
譯者: ChainGod(孫飛)
原文鏈接: http://chaingod.io/article/16
在本系列前三篇文章中[1][2][3],我們向大家展示了如何通過精煉的Go代碼實(shí)現(xiàn)一個(gè)簡單的區(qū)塊鏈。包括生成塊,驗(yàn)證塊數(shù)據(jù),廣播通信等等,這一篇讓我們聚焦在如何實(shí)現(xiàn) PoW算法。
PoS簡介
在上一篇文章[3]中,我們討論了工作量證明(Proof of Work),并向您展示了如何編寫自己的工作量證明區(qū)塊鏈。當(dāng)前最流行的兩個(gè)區(qū)塊鏈平臺(tái),比特幣和以太坊都是基于工作量證明的。
但是工作證明的缺點(diǎn)是什么呢?其中一個(gè)主要的問題是電力能源的消耗。為了挖掘更多的比特幣,就需要建立更多的挖礦硬件池,現(xiàn)在在世界各地,挖礦池都在不斷建立中,而且呈現(xiàn)出規(guī)模越來越大的趨勢。例如以下這張照片(僅僅是礦池的一角):
挖礦工作需要耗費(fèi)大量的電力,僅比特幣開采耗費(fèi)的能源就超過了159個(gè)國家的電力能源消耗總和!!這種能源消耗是非常非常不合理的,而且,從技術(shù)的角度來看,工作量證明還有其他不足之處:隨著越來越多的人參與到挖礦工作中,共識(shí)算法的難度就需要提高,難度的提高意味著需要更多、更長時(shí)間的挖礦,也意味著區(qū)塊和交易需要更長的時(shí)間才能得到處理,因此能源的消耗就會(huì)越發(fā)的高。總之,工作量證明的方式就是一場競賽,你需要更多的計(jì)算能力才能有更大的概率贏得比賽。
有很多區(qū)塊鏈學(xué)者都試圖找到工作量證明的替代品,到目前為止最有希望的就是PoS(權(quán)益證明,Proof of Stake)。目前在生產(chǎn)環(huán)境,已經(jīng)有數(shù)個(gè)區(qū)塊鏈平臺(tái)使用了PoS,例如Nxt?和Neo。以太坊在不遠(yuǎn)的未來也很可能會(huì)使用PoS——他們的Casper項(xiàng)目已經(jīng)在測試網(wǎng)絡(luò)上運(yùn)行和測試了。
?
那么,到底什么才是股權(quán)證明PoS呢?
在PoW中,節(jié)點(diǎn)之間通過hash的計(jì)算力來競賽以獲取下一個(gè)區(qū)塊的記賬權(quán),而在PoS中,塊是已經(jīng)鑄造好的(這里沒有“挖礦”的概念,所以我們不用這個(gè)詞來證明股份),鑄造的過程是基于每個(gè)節(jié)點(diǎn)(Node)愿意作為抵押的令牌(Token)數(shù)量。
這些參與抵押的節(jié)點(diǎn)被稱為驗(yàn)證者(Validator),**注意在本文后續(xù)內(nèi)容中,驗(yàn)證者和節(jié)點(diǎn)的概念是等同的!**令牌的含義對(duì)于不同的區(qū)塊鏈平臺(tái)是不同的,例如,在以太坊中,每個(gè)驗(yàn)證者都將Ether作為抵押品。
如果驗(yàn)證者愿意提供更多的令牌作為抵押品,他們就有更大的機(jī)會(huì)記賬下一個(gè)區(qū)塊并獲得獎(jiǎng)勵(lì)。你可以把獎(jiǎng)勵(lì)的區(qū)塊看作是存款利息,你在銀行存的錢越多,你每月的利息就會(huì)越高。
因此,這種共識(shí)機(jī)制被稱為股權(quán)證明PoS。
?
PoS的缺陷是什么?
您可能已經(jīng)猜到,一個(gè)擁有大量令牌的驗(yàn)證者會(huì)在創(chuàng)建新塊時(shí)根據(jù)持有的令牌數(shù)量獲得更高的概率。然而,這與我們?cè)诠ぷ髁孔C明中看到的并沒有什么不同:比特幣礦場變得越來越強(qiáng)大,普通人在自己的電腦上開采多年也未必能獲得一個(gè)區(qū)塊。
因此,許多人認(rèn)為,使用了PoS后,區(qū)塊的分配將更加民主化,因?yàn)槿魏稳硕伎梢栽谧约旱墓P記本上參與,而不需要建立一個(gè)巨大的采礦平臺(tái),他們不需要昂貴的硬件,只需要一定的籌碼,就算籌碼不多,也有一定概率能獲得區(qū)塊的記賬權(quán),希望總是有的,你說呢?
從技術(shù)和經(jīng)濟(jì)的角度來看,還有其他不利因素。我們不會(huì)一一介紹,但這里有一個(gè)很好的介紹。在實(shí)際應(yīng)用中,PoS和PoW都有自己的優(yōu)點(diǎn)和缺點(diǎn),因此以太坊的Casper具有兩者混合的特征。
像往常一樣,了解PoS的方法是編寫自己的代碼,那么,我們開始吧!
?
編寫PoS代碼
我們建議在繼續(xù)之前看一下200行Go代碼編寫區(qū)塊鏈Part2[1],因?yàn)樵诮酉聛淼奈恼轮?#xff0c;一些基礎(chǔ)知識(shí)不再會(huì)介紹,因此這篇文章能幫助你回顧一下。
注意
我們將實(shí)現(xiàn)PoS的核心概念,然后因?yàn)槲恼麻L度有限,因此一些不必要的代碼獎(jiǎng)省去!
-
P2P網(wǎng)絡(luò)的實(shí)現(xiàn)。文中的網(wǎng)絡(luò)是模擬的,區(qū)塊鏈狀態(tài)只在其中一個(gè)中心化節(jié)點(diǎn)持有,而不是每個(gè)節(jié)點(diǎn),同時(shí)狀態(tài)通過該持有節(jié)點(diǎn)廣播到其它節(jié)點(diǎn)
-
錢包和余額變動(dòng)。本文沒有實(shí)現(xiàn)一個(gè)錢包,持有的令牌數(shù)量是通過stdin(標(biāo)準(zhǔn)輸入)輸入的,你可以輸入你想要的任何數(shù)量。一個(gè)完整的實(shí)現(xiàn)會(huì)為每個(gè)節(jié)點(diǎn)分配一個(gè)hash地址,并在節(jié)點(diǎn)中跟蹤余額的變動(dòng)
架構(gòu)圖
-
我們將有一個(gè)中心化的TCP服務(wù)節(jié)點(diǎn),其他節(jié)點(diǎn)可以連接該服務(wù)器
-
最新的區(qū)塊鏈狀態(tài)將定期廣播到每個(gè)節(jié)點(diǎn)
-
每個(gè)節(jié)點(diǎn)都能提議建立新的區(qū)塊
-
基于每個(gè)節(jié)點(diǎn)的令牌數(shù)量,其中一個(gè)節(jié)點(diǎn)將隨機(jī)地(以令牌數(shù)作為加權(quán)值)作為獲勝者,并且將該區(qū)塊添加到區(qū)塊鏈中
設(shè)置和導(dǎo)入
在開始寫代碼之前,我們需要一個(gè)環(huán)境變量來設(shè)置TCP服務(wù)器的端口,首先在工作文件夾中創(chuàng)建.env文件,寫入一行配置:
ADDR=9000
我們的Go程序?qū)⒆x取該文件,并且暴露出9000端口。同時(shí)在工作目錄下,再創(chuàng)建一個(gè)main.go文件。
-
spew?可以把我們的區(qū)塊鏈用漂亮的格式打印到終端terminal中
-
godotenv?允許我們從之前創(chuàng)建的.env文件讀取配置
快速脈搏檢查
如果你讀過我們的其他教程,就會(huì)知道我們是一家醫(yī)療保健公司,目前要去收集人體脈搏信息,同時(shí)添加到我們的區(qū)塊上。把兩個(gè)手指放在你的手腕上,數(shù)一下你一分鐘能感覺到多少次脈搏,這將是您的BPM整數(shù),我們將在接下來的文章中使用。
全局變量
現(xiàn)在,讓我們聲明我們需要的所有全局變量(main.go中)。
-
Block是每個(gè)區(qū)塊的內(nèi)容
-
Blockchain是我們的官方區(qū)塊鏈,它只是一串經(jīng)過驗(yàn)證的區(qū)塊集合。每個(gè)區(qū)塊中的PrevHash與前面塊的Hash相比較,以確保我們的鏈?zhǔn)钦_的
-
tempBlocks是臨時(shí)存儲(chǔ)單元,在區(qū)塊被選出來并添加到BlockChain之前,臨時(shí)存儲(chǔ)在這里
-
candidateBlocks是Block的通道,任何一個(gè)節(jié)點(diǎn)在提出一個(gè)新塊時(shí)都將它發(fā)送到這個(gè)通道
-
announcements也是一個(gè)通道,我們的主Go TCP服務(wù)器將向所有節(jié)點(diǎn)廣播最新的區(qū)塊鏈
-
mutex是一個(gè)標(biāo)準(zhǔn)變量,允許我們控制讀/寫和防止數(shù)據(jù)競爭
-
validators是節(jié)點(diǎn)的存儲(chǔ)map,同時(shí)也會(huì)保存每個(gè)節(jié)點(diǎn)持有的令牌數(shù)
基本的區(qū)塊鏈函數(shù)
在繼續(xù)PoS算法之前,我們先來實(shí)現(xiàn)標(biāo)準(zhǔn)的區(qū)塊鏈函數(shù)。如果你之前看過200行Go代碼編寫區(qū)塊鏈,那接下來應(yīng)該更加熟悉。
main.go
這里先從hash函數(shù)開始,calculateHash函數(shù)會(huì)接受一個(gè)string,并且返回一個(gè)SHA256 hash。calculateBlockHash是對(duì)一個(gè)block進(jìn)行hash,將一個(gè)block的所有字段連接到一起后,再進(jìn)行hash。
main.go
generateBlock是用來創(chuàng)建新塊的。每個(gè)新塊都有的一個(gè)重要字段是它的hash簽名(通過calculateBlockHash計(jì)算的)和上一個(gè)連接塊的PrevHash(因此我們可以保持鏈的完整性)。我們還添加了一個(gè)Validator字段,這樣我們就知道了該構(gòu)建塊的獲勝節(jié)點(diǎn)。
main.go
isBlockValid會(huì)驗(yàn)證Block的當(dāng)前hash和PrevHash,來確保我們的區(qū)塊鏈不會(huì)被污染。
節(jié)點(diǎn)(驗(yàn)證者)
當(dāng)一個(gè)驗(yàn)證者連接到我們的TCP服務(wù),我們需要提供一些函數(shù)達(dá)到以下目標(biāo):
-
輸入令牌的余額(之前提到過,我們不做錢包等邏輯)
-
接收區(qū)塊鏈的最新廣播
-
接收驗(yàn)證者贏得區(qū)塊的廣播信息
-
將自身節(jié)點(diǎn)添加到全局的驗(yàn)證者列表中(validators)
-
輸入Block的BPM數(shù)據(jù)- BPM是每個(gè)驗(yàn)證者的人體脈搏值
-
提議創(chuàng)建一個(gè)新的區(qū)塊
這些目標(biāo),我們用handleConn函數(shù)來實(shí)現(xiàn)
main.go
第一個(gè)Go協(xié)程接收并打印出來自TCP服務(wù)器的任何通知,這些通知包含了獲勝驗(yàn)證者的通知。
io.WriteString(conn, “Enter token balance:”)允許驗(yàn)證者輸入他持有的令牌數(shù)量,然后,該驗(yàn)證者被分配一個(gè)SHA256地址,隨后該驗(yàn)證者地址和驗(yàn)證者的令牌數(shù)被添加到驗(yàn)證者列表validators中。
接著我們輸入BPM,驗(yàn)證者的脈搏值,并創(chuàng)建一個(gè)單獨(dú)的Go協(xié)程來處理這塊兒邏輯,下面這一行代碼很重要:
delete(validators, address)
如果驗(yàn)證者試圖提議一個(gè)被污染(例如偽造)的block,例如包含一個(gè)不是整數(shù)的BPM,那么程序會(huì)拋出一個(gè)錯(cuò)誤,我們會(huì)立即從我們的驗(yàn)證器列表validators中刪除該驗(yàn)證者,他們將不再有資格參與到新塊的鑄造過程同時(shí)丟失相應(yīng)的抵押令牌。
正式因?yàn)檫@種抵押令牌的機(jī)制,使得PoS協(xié)議是一種更加可靠的機(jī)制。如果一個(gè)人試圖偽造和破壞,那么他將被抓住,并且失去所有抵押和未來的權(quán)益,因此對(duì)于惡意者來說,是非常大的威懾。
接著,我們用generateBlock函數(shù)創(chuàng)建一個(gè)新的block,然后將其發(fā)送到candidateBlocks通道進(jìn)行進(jìn)一步處理。將Block發(fā)送到通道使用的語法:
candidateBlocks <- newBlock
上面代碼中最后一段的循環(huán)會(huì)周期性的打印出最新的區(qū)塊鏈,這樣每個(gè)驗(yàn)證者都能獲知最新的狀態(tài)
?
選擇獲勝者
這里是PoS的主題邏輯。我們需要編寫代碼以實(shí)現(xiàn)獲勝驗(yàn)證者的選擇;他們所持有的令牌數(shù)量越高,他們就越有可能被選為勝利者。
為了簡化代碼,我們只會(huì)讓提出新塊兒的驗(yàn)證者參與競爭。在傳統(tǒng)的PoS,一個(gè)驗(yàn)證者即使沒有提出一個(gè)新的區(qū)塊,也可以被選為勝利者。切記,PoS不是一種確定的定義(算法),而是一種概念,因此對(duì)于不同的平臺(tái)來說,可以有不同的PoS實(shí)現(xiàn)。
下面來看看pickWinner函數(shù):
main.go
每隔30秒,我們選出一個(gè)勝利者,這樣對(duì)于每個(gè)驗(yàn)證者來說,都有時(shí)間提議新的區(qū)塊,參與到競爭中來。接著創(chuàng)建一個(gè)lotteryPool,它會(huì)持有所有驗(yàn)證者的地址,這些驗(yàn)證者都有機(jī)會(huì)成為一個(gè)勝利者。然后,對(duì)于提議塊的暫存區(qū)域,我們會(huì)通過if len(temp) > 0來判斷是否已經(jīng)有了被提議的區(qū)塊。
在OUTER FOR循環(huán)中,要檢查暫存區(qū)域是否和lotteryPool中存在同樣的驗(yàn)證者,如果存在,則跳過。
在以k, ok := setValidators[block.Validator]開始的代碼塊中,我們確保了從temp中取出來的驗(yàn)證者都是合法的,即這些驗(yàn)證者在驗(yàn)證者列表validators已存在。若合法,則把該驗(yàn)證者加入到lotteryPool中。
那么我們?cè)趺锤鶕?jù)這些驗(yàn)證者持有的令牌數(shù)來給予他們合適的隨機(jī)權(quán)重呢?
首先,用驗(yàn)證者的令牌填充lotteryPool數(shù)組,例如一個(gè)驗(yàn)證者有100個(gè)令牌,那么在lotteryPool中就將有100個(gè)元素填充;如果有1個(gè)令牌,那么將僅填充1個(gè)元素。
然后,從lotteryPool中隨機(jī)選擇一個(gè)元素,元素所屬的驗(yàn)證者即是勝利者,把勝利驗(yàn)證者的地址賦值給lotteryWinner。這里能夠看出來,如果驗(yàn)證者持有的令牌越多,那么他在數(shù)組中的元素也越多,他獲勝的概率就越大;同時(shí),持有令牌很少的驗(yàn)證者,也是有概率獲勝的。
接著我們把獲勝者的區(qū)塊添加到整條區(qū)塊鏈上,然后通知所有節(jié)點(diǎn)關(guān)于勝利者的消息:announcements <- “\nwinning validator: “ + lotteryWinner + “\n”。
最后,清空tempBlocks,以便下次提議的進(jìn)行。
以上便是PoS一致性算法的核心內(nèi)容,該算法簡單、明了、公正,所以很酷!
?
收尾
下面我們把之前的內(nèi)容通過代碼都串聯(lián)起來
main.go
這里從.env文件開始,然后創(chuàng)建一個(gè)創(chuàng)世區(qū)塊genesisBlock,形成了區(qū)塊鏈。接著啟動(dòng)了Tcp服務(wù),等待所有驗(yàn)證者的連接。
啟動(dòng)了一個(gè)Go協(xié)程從candidateBlocks通道中獲取提議的區(qū)塊,然后填充到臨時(shí)緩沖區(qū)tempBlocks中,最后啟動(dòng)了另外一個(gè)Go協(xié)程來完成pickWinner函數(shù)。
最后面的for循環(huán),用來接收驗(yàn)證者節(jié)點(diǎn)的連接。
這里是所有的源代碼[2]:
?
結(jié)果
下面來運(yùn)行程序,打開一個(gè)終端窗口,通過go run main.go來啟動(dòng)整個(gè)TCP程序,如我們所料,首先創(chuàng)建了創(chuàng)始區(qū)塊genesisBlock。
接著,我們啟動(dòng)并連接一個(gè)驗(yàn)證者。打開一個(gè)新的終端窗口,通過linux命令nc localhost 9000來連接到之前的TCP服務(wù)。然后在命令提示符后輸入一個(gè)持有的令牌數(shù)額,最后再輸入一個(gè)驗(yàn)證者的脈搏速率BPM。
然后觀察第一個(gè)窗口(主程序),可以看到驗(yàn)證者被分配了地址,而且每次有新的驗(yàn)證者加入時(shí),都會(huì)打印所有的驗(yàn)證者列表
稍等片刻,檢查下你的新窗口(驗(yàn)證者),可以看到正在發(fā)生的事:我們的程序在花費(fèi)時(shí)間選擇勝利者,然后Boom一聲,一個(gè)勝利者就誕生了!
再稍等一下,boom! 我們看到新的區(qū)塊鏈被廣播給所有的驗(yàn)證者窗口,包含了勝利者的區(qū)塊和他的BPM信息。很酷吧!
下一步做什么
你應(yīng)該為能通過本教程感到驕傲。大多數(shù)區(qū)塊鏈的發(fā)燒友和許多程序員都聽說過PoS的證明,但他們很多都無法解釋它到底是什么。你已經(jīng)做得更深入了,而且實(shí)際上已經(jīng)從頭開始實(shí)現(xiàn)了一遍,你離成為下一代區(qū)塊鏈技術(shù)的專家又近了一步!
因?yàn)檫@是一個(gè)教程,我們可以做更多的事情來讓它成為區(qū)塊鏈,例如:
-
閱讀我們的PoW,然后結(jié)合PoS,看看你是否可以創(chuàng)建一個(gè)混合區(qū)塊鏈
-
添加時(shí)間機(jī)制,驗(yàn)證者根據(jù)時(shí)間塊來獲得提議新區(qū)快的概率。我們這個(gè)版本的代碼讓驗(yàn)證者可以在任何時(shí)候提議新的區(qū)塊。
-
添加完整的點(diǎn)對(duì)點(diǎn)的能力。這基本上意味著每個(gè)驗(yàn)證者將運(yùn)行自己的TCP服務(wù)器,并連接到其他的驗(yàn)證者節(jié)點(diǎn)。這里需要添加邏輯,這樣每個(gè)節(jié)點(diǎn)都可以找到彼此,這里[3]有更多的內(nèi)容。
或者你可以學(xué)習(xí)一下我們其它的教程。
?
參考鏈接:
[1]?只用200行Go代碼寫一個(gè)自己的區(qū)塊鏈!
[2]?200行Go代碼實(shí)現(xiàn)自己的區(qū)塊鏈——區(qū)塊生成與網(wǎng)絡(luò)通信
[3]?200行Go代碼實(shí)現(xiàn)區(qū)塊鏈 —— 挖礦算法
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的使用Go语言从零编写PoS区块链的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 事件溯源|日志记录-一个基础的微服务模式
- 下一篇: Kubernetes大集群怎么管?基于监