區(qū)塊鏈難理解?200行代碼教你寫(xiě)一個(gè)自己的區(qū)塊鏈!
“區(qū)塊鏈”三個(gè)字,無(wú)疑是近一年來(lái)最火的投資概念。隨著比特幣等區(qū)塊鏈資產(chǎn)價(jià)格的大幅飆升,普通投資者對(duì)區(qū)塊鏈,以及數(shù)字貨幣投資的興趣越來(lái)越大,突然間,似乎全世界都在談?wù)搮^(qū)塊鏈、比特幣。
區(qū)塊鏈就像一次對(duì)全人類(lèi)經(jīng)濟(jì)層面的入侵,這種入侵,比互聯(lián)網(wǎng)的入侵,可能還要徹底,人們開(kāi)始轉(zhuǎn)移的,并不是簡(jiǎn)單的信息以及消費(fèi)習(xí)慣,而是對(duì)資產(chǎn)的重新認(rèn)識(shí)和選擇。
區(qū)塊鏈難理解?這里有一篇初學(xué)者指南
我并不明白為什么人們會(huì)覺(jué)得要理解區(qū)塊鏈會(huì)有點(diǎn)難,而我也想知道為什么自己并沒(méi)有。
那是在 2013 年,我第一次聽(tīng)說(shuō)有比特幣這個(gè)東西(是的,知道的太晚啦)。我窮得連一個(gè)都買(mǎi)不起,看到這兒你也許已經(jīng)對(duì)此文無(wú)愛(ài)了。
后來(lái),我想要去了解一下它所依賴的底層技術(shù),也就是區(qū)塊鏈。 不過(guò)因?yàn)樘α?,所以遲遲沒(méi)有開(kāi)始(那就并不存在什么開(kāi)始不開(kāi)始了)。
“區(qū)塊鏈”到底是什么?
區(qū)塊鏈其實(shí)是兩個(gè)東西:一個(gè)是區(qū)塊,一個(gè)是鏈。說(shuō)玄虛一點(diǎn),就是一鏈子的區(qū)塊。
因?yàn)樗谴嬖谟谟?jì)算機(jī)中的東西,所以我們可以厘定它的一個(gè)物理形態(tài)是啥樣子的,就是數(shù)字信息被分成一個(gè)一個(gè)區(qū)塊然后把這些區(qū)塊鏈接起來(lái)。
舉個(gè)例子,下圖中的方塊,每一個(gè)都表示一個(gè)國(guó)家,而且每一個(gè)都包含了對(duì)應(yīng)國(guó)家的城市名稱(chēng)。
等等,其實(shí)還有更多東西來(lái)著。這里的每一個(gè)方塊都是一個(gè)叫做哈希的東西。一個(gè)哈希就是一串字符 (比如 “1hi515AHA5H” )。
哈希是根據(jù)方塊里面所包含的信息來(lái)得到的。U.S.A 的方塊擁有 New York, Los Angeles,還有 Chicago 這幾個(gè)城市,所以它的哈希就是像 “NYLAC” 的東西了 (技術(shù)上其實(shí)遠(yuǎn)非如此,但你理會(huì)精要就行了)。
每一個(gè)接續(xù)的方塊都會(huì)包含前一個(gè)方塊的哈希,所以這個(gè)就是(強(qiáng)制性的)將它們綁到一起的紐帶。
如果有人擅自篡改了第一個(gè)方塊,加入了城市 Boston,那么新的哈希就會(huì)是 “NYLACB”。
然而后面接續(xù)的 India 這個(gè)方塊已經(jīng)存著的哈希還是 “NYLAC”,這種不匹配就會(huì)把鏈條打斷。所以哈希的目的就是確保沒(méi)有人可以篡改區(qū)塊。
那如果有人修改了一個(gè)方塊的內(nèi)容,然后把后面的接續(xù)方塊的哈希也一并更新會(huì)如何呢?
這也是有可能的,不過(guò)有一件事情我還沒(méi)有告訴你。區(qū)塊鏈的數(shù)據(jù)并不只是存在于僅僅一臺(tái)計(jì)算機(jī)里面。一臺(tái)計(jì)算機(jī)里面的區(qū)塊鏈數(shù)據(jù)并不能騙到人,因?yàn)樗鼤?huì)被復(fù)制到網(wǎng)絡(luò)中每一個(gè)用戶的計(jì)算機(jī)里面去。
如果你加入了一個(gè)區(qū)塊鏈網(wǎng)絡(luò),那么你的計(jì)算機(jī)就會(huì)去下載這些區(qū)塊數(shù)據(jù),如果有人篡改了他擁有的版本,整個(gè)網(wǎng)絡(luò)也會(huì)考慮占多數(shù)的人的計(jì)算機(jī)上所擁有的版本才是正確的。
還有一件事,在一個(gè)區(qū)塊鏈網(wǎng)絡(luò)中,不僅是數(shù)據(jù),就連整個(gè)系統(tǒng)的程序都被復(fù)制到了所有的電腦中。
大多數(shù)互聯(lián)網(wǎng)應(yīng)用都是集中化的,比如 Facebook,她的數(shù)據(jù)和程序都被放在了她的服務(wù)器上,你的計(jì)算機(jī)會(huì)從 Facebook 的服務(wù)器上獲取到你一個(gè)人需要知道的信息。
但在區(qū)塊鏈的世界理,就沒(méi)有存在于中心的東西,它依賴的是用戶的計(jì)算機(jī)來(lái)容納自己的程序。這就意味著,如果整個(gè)區(qū)塊鏈網(wǎng)絡(luò)中的每一臺(tái)電腦都關(guān)機(jī)了,那么這個(gè)區(qū)塊鏈系統(tǒng)就死翹翹了。
公共區(qū)塊鏈
這是不是就意味著區(qū)塊鏈系統(tǒng)其實(shí)就是由一群心懷善意自愿讓他們的計(jì)算機(jī)保持運(yùn)行的人來(lái)組成的呢? 還有這些防篡改的區(qū)塊是用來(lái)干嘛的呢?
區(qū)塊鏈網(wǎng)絡(luò)的功效不勝枚舉。比特幣是一種數(shù)據(jù)貨幣和一個(gè)支付系統(tǒng)。它所有的防篡改區(qū)塊中所保存的就是全部交易的分類(lèi)賬。那些貢獻(xiàn)了他們自己的計(jì)算機(jī)的人被稱(chēng)為礦工。系統(tǒng)會(huì)給他們提供比特幣作為獎(jiǎng)勵(lì)。
Ethereum 有一項(xiàng)附加功能。它可以承載你的代碼,從頭開(kāi)始發(fā)展出一個(gè)區(qū)塊鏈系統(tǒng)。
而要構(gòu)建一個(gè)屬于你自己的系統(tǒng)也許會(huì)非常地困難(記住這得看有人為你犧牲他們的計(jì)算機(jī)運(yùn)行能力才行哦)。Ethereum 就維護(hù)著這些耗損巨大的運(yùn)算能力,而你則需要為這些計(jì)算消耗買(mǎi)單。
區(qū)塊鏈應(yīng)用并不非得是支付系統(tǒng)或者加密貨幣。它可以是任何東西,像是一個(gè)社交網(wǎng)絡(luò),一個(gè)像 LiveEdu 這樣的學(xué)習(xí)平臺(tái),等等。
私有區(qū)塊鏈
Bitcoin,Ethereum 等等這些都是公共區(qū)塊鏈的例子,任何人都可以成為其中的一分子。
那如果我們想要有一個(gè)私有的區(qū)塊鏈網(wǎng)絡(luò)該如何呢?有些人想要一個(gè)私有的區(qū)塊鏈?zhǔn)窍敫陕锬兀磕蔷蛠?lái)瞧瞧下面的故事吧。
Mark 和 Sara
Mark 已經(jīng)五個(gè)月沒(méi)交房租了,當(dāng) Sara 找他要的時(shí)候,他就說(shuō)晚點(diǎn)會(huì)給她。她付不起律師費(fèi),而法院強(qiáng)制執(zhí)行訴訟就需要 8 個(gè)月甚至一年,所以唯一的選擇就是去說(shuō)服 Mark。
Joe 的生意
Joe 是一個(gè)商人,他經(jīng)常要跟不同的公司做生意。幾個(gè)月之前他和一家零售商簽了一份合同,盡管合同條款都已經(jīng)履約了,可零售商卻拒絕付款。
這幫人利用法律制度中的漏洞來(lái)游說(shuō) Joe,想以此達(dá)到少付錢(qián)的目的。Joe 在這以前就是有這方面經(jīng)驗(yàn)的,在某些情況下,他會(huì)找法院求助,但這樣做所耗費(fèi)的時(shí)間和金錢(qián)卻要損失他自己的利潤(rùn)。
我們?cè)撊绾螏椭?Sara 和 Joe 呢?
我們是不是能在其他地方解決這個(gè)問(wèn)題呢? 在 Sara 遇到的這種情況中,我們需要讓 Mark 按月支付房租,這其實(shí)就是一個(gè)基于時(shí)間的觸發(fā)機(jī)制。你的日歷程序使用這樣的觸發(fā)器來(lái)給你提供預(yù)設(shè)事件的通知。
在 Joe 遇到的場(chǎng)景中,一旦合約中的條款都滿足了,當(dāng)事人就得付款,這其實(shí)就是一個(gè)基于條件的觸發(fā)機(jī)制。你想想上次從 Amazon 買(mǎi)電子書(shū)的時(shí)候,是不是得先確認(rèn)付款了,Amazon 才會(huì)把電子書(shū)發(fā)給你?
重點(diǎn)是,計(jì)算機(jī)程序會(huì)始終如一的執(zhí)行諸如此類(lèi)的指令。當(dāng)你點(diǎn)擊著這篇文章,向下滾動(dòng),諸如這類(lèi)的操作,它也會(huì)照著執(zhí)行不誤。為了能幫助到 Sara ,我們需要將合同的條款轉(zhuǎn)變成代碼。
Sara 和 Mark 之間所訂立的智能合同的偽代碼
- If today’s date is 30th and rent is not paid then
- Transfer $500 from Mark’s account to Sara’s account
可是我們?cè)谀膬翰渴疬@些代碼呢? 它就應(yīng)該被部署到所有參與者的計(jì)算機(jī)上。Sara 的還有 Mark 的銀行都會(huì)是這一個(gè)私有區(qū)塊鏈網(wǎng)絡(luò)的一部分。
Joe 和 Sara 會(huì)簽署一份編碼的協(xié)議(也就是智能合同),然后這份協(xié)議會(huì)被分發(fā)到網(wǎng)絡(luò)中去,Mark 的和 Sara 的銀行都會(huì)有一份拷貝。
在每個(gè)月的 30 號(hào),當(dāng)時(shí)鐘跳動(dòng)到 12 點(diǎn)整,協(xié)議好的金額就會(huì)從 Mark 的賬戶轉(zhuǎn)移到 Sara 的賬戶上去。Joe 也開(kāi)始使用智能合同來(lái)強(qiáng)制讓他的客戶支付協(xié)議好的貨款。
- Sara 高興了,因?yàn)樗僖膊挥萌┬?Mark 會(huì)不會(huì)如約付房租了。Joe 也高興,因?yàn)樗膊挥谜曳ㄔ阂f(shuō)法了,省下這些精力,他可以繼續(xù)發(fā)展自己的生意了。
私有區(qū)塊鏈只限于業(yè)務(wù)中涉及到的相關(guān)各方,因此 Joe 不會(huì)是 Sara 和 Mark 所屬區(qū)塊鏈網(wǎng)絡(luò)的一部分。
前行之路
現(xiàn)在你對(duì)此是不是已經(jīng)有點(diǎn)概念了?如果還是不理解,看看小編之前發(fā)布的文章技術(shù)人再不懂區(qū)塊鏈,你就OUT了?不過(guò)下面這篇文章也能讓你秒懂區(qū)塊鏈。
區(qū)塊鏈與裸照:一個(gè)去中心化的色情網(wǎng)站是什么樣的?
“區(qū)塊鏈”概念已火,雖然大部分人對(duì)“區(qū)塊鏈”好奇,甚至眼饞,但不少還處于不求甚解的懵逼階段.....正好最近我一直在研究區(qū)塊鏈,同時(shí)也見(jiàn)了幾個(gè)圈內(nèi)人深聊了下,就想為大家寫(xiě)一個(gè)“入門(mén)級(jí)”的區(qū)塊鏈介紹文章。
為了通俗易懂,我決定不惜自毀清譽(yù),用充滿荷爾蒙的比喻。因?yàn)榧祭顺泵看未_實(shí)都性感得讓人荷爾蒙爆炸啊,性(huang)感(bao)內(nèi)容開(kāi)始。
以前,大家想看陳老師的裸照,都要去一個(gè)叫 1025 的網(wǎng)站,這就是中心化。
后來(lái),1025 網(wǎng)站被和諧掉了,大部分猥瑣男們傻逼了沒(méi)有網(wǎng)站看片了,因?yàn)樗麄兲嘈胖行幕M織了,還天天被 1025 彈窗“皇家澳門(mén)賭場(chǎng)”的小廣告真是活該啊。
不過(guò)沒(méi)關(guān)系,陳老師的 2100 張裸照,幸存在 100 萬(wàn)個(gè)猥瑣男的電腦里,除非地球毀滅,不然陳老師的裸照不可能絕跡。這就是去中心化,數(shù)據(jù)分布式存儲(chǔ)。
后來(lái),有個(gè)叫“中本粗”的超級(jí)猥瑣男,是陳老師 2100 張裸照的超級(jí)發(fā)騷友。為了2100張照片永遠(yuǎn)不消失,為世人所享用,他做了一個(gè)互聯(lián)網(wǎng)共享文件夾“陳老師plus”。
如果猥瑣男們想獲取“陳老師plus”2100 張的觀看權(quán),就必須加入一個(gè)電子協(xié)議中:不得復(fù)制、修改、P 任何“陳老師plus”中的照片,用戶在“陳老師plus”發(fā)生的任何行為,都會(huì)按時(shí)間戳記錄!
例如,“小張?jiān)?2018 年 1 月 9 日中午 12:00,查看了編號(hào)為 103 的照片,并在 13:00 刪除了編號(hào) 1-100 的 100 張照片.....”
小張的行為被記錄并廣播給其他 100 萬(wàn)個(gè)猥瑣男,“陳老師plus”的 2100 張照片會(huì)得到保護(hù),小張電腦中“陳老師plus”會(huì)按時(shí)間戳中最新記錄,同步其他 100 萬(wàn)個(gè)猥瑣男的電腦里的數(shù)據(jù),復(fù)原小張電腦中的數(shù)據(jù)...
小張永遠(yuǎn)別想對(duì)“陳老師plus”搞修改破壞,且所有行為都同步記錄在其他猥瑣男的電腦里。
這就是區(qū)塊鏈,數(shù)據(jù)分散存儲(chǔ),去中心化,按時(shí)間戳廣播記錄所有行為,無(wú)法修改、破壞數(shù)據(jù)源或造假,除非同一時(shí)刻炸掉 100 萬(wàn)個(gè)猥瑣男的電腦,或互聯(lián)網(wǎng)消失,或世界毀滅.....
當(dāng)然,也有唯一一種特殊情況,可以增加“陳老師plus”文件夾中的照片,這種情況叫做“區(qū)塊鏈共識(shí)層”,顧名思義,這是 100 萬(wàn)猥瑣男達(dá)成的增加照片共識(shí),你不能瞎增加的。
中本粗最初設(shè)定協(xié)議時(shí),把“猥瑣男們可以用 X 相機(jī),在每年 XX 時(shí)間,拍陳老師的裸照,前 100 張可以添加進(jìn)入‘陳老師plus’中增加作為文件夾照片”,那么,“陳老師plus”每年就可以增長(zhǎng) 100 張照片了。
當(dāng)然,你還可以給照片估價(jià)嘛,發(fā)行“陳老師plus”幣。因?yàn)椋?ldquo;陳老師plus”中每張照片都是不可造假破壞的,所以具有唯一性,還有單獨(dú)編號(hào),我們就給每一張照片估價(jià),它不就值錢(qián)了嗎?就像現(xiàn)實(shí)世界中無(wú)法復(fù)制的名畫(huà)一樣??!
怎么估值?就進(jìn)行所謂的 ICO(Initial Coin Offering)啊,就是我和李哭來(lái)老師成立一個(gè)基金,舉行一張發(fā)布會(huì),就說(shuō)我們給這 2100 張照片估值個(gè) 1.05 億!每張照片 5 萬(wàn)!
我們先丟 5050 萬(wàn)進(jìn)去認(rèn)購(gòu)前 1100 張,其他猥瑣男可以眾籌 5000 萬(wàn)買(mǎi)剩下的 1000 張照片,不想要了?賣(mài)給我和李哭來(lái)基金就行,我們認(rèn)它值錢(qián)啊,我們這么牛逼不會(huì)騙你的。
自從有了區(qū)塊鏈——我們?cè)僖膊慌?1025 們作惡,給我們彈窗小廣告,給我們下病毒了;再也不怕陳老師的照片丟失被破壞了,1984 老大哥復(fù)活都做不到.....
當(dāng)然,也有煩惱,就是有炒名畫(huà)的現(xiàn)在來(lái)炒“陳老師plus”照片了,把價(jià)格搞得很高。
還有的更猥瑣,為了賺錢(qián),自己拍了一堆裸照按照這個(gè)模式弄了個(gè)“某某老師plus”東施效顰收割韭菜,這就叫山寨幣,現(xiàn)在大概快一百種了吧。
看到這里,你肯定能明白區(qū)塊鏈了......最后,教大家怎么用 200 行 Go 代碼寫(xiě)一個(gè)自己的區(qū)塊鏈!
只用 200 行 Go 代碼寫(xiě)一個(gè)自己的區(qū)塊鏈
這篇文章就是幫助你使用 Go 語(yǔ)言來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的區(qū)塊鏈,用不到 200 行代碼來(lái)揭示區(qū)塊鏈的原理!
“用不到 200 行 Go 代碼就能實(shí)現(xiàn)一個(gè)自己的區(qū)塊鏈!” 聽(tīng)起來(lái)有意思嗎?有什么能比開(kāi)發(fā)一個(gè)自己的區(qū)塊鏈更好的學(xué)習(xí)實(shí)踐方法呢?那我們就一起來(lái)實(shí)踐下!
因?yàn)槲覀兪且患覐氖箩t(yī)療健康領(lǐng)域的科技公司,所以我們采用人類(lèi)平靜時(shí)的心跳數(shù)據(jù)(BPM 心率)作為這篇文章中的示例數(shù)據(jù)。
讓我們先來(lái)統(tǒng)計(jì)一下你一分鐘內(nèi)的心跳數(shù),然后記下來(lái),這個(gè)數(shù)字可能會(huì)在接下來(lái)的內(nèi)容中用到。
通過(guò)本文,你將可以做到:
- 創(chuàng)建自己的區(qū)塊鏈
- 理解 hash 函數(shù)是如何保持區(qū)塊鏈的完整性
- 如何創(chuàng)造并添加新的塊
- 多個(gè)節(jié)點(diǎn)如何競(jìng)爭(zhēng)生成塊
- 通過(guò)瀏覽器來(lái)查看整個(gè)鏈
- 所有其他關(guān)于區(qū)塊鏈的基礎(chǔ)知識(shí)
但是,對(duì)于比如工作量證明算法(PoW)以及權(quán)益證明算法(PoS)這類(lèi)的共識(shí)算法文章中將不會(huì)涉及。
同時(shí)為了讓你更清楚得查看區(qū)塊鏈以及塊的添加,我們將網(wǎng)絡(luò)交互的過(guò)程簡(jiǎn)化了,關(guān)于 P2P 網(wǎng)絡(luò)比如“全網(wǎng)廣播”這個(gè)過(guò)程等內(nèi)容將在下一篇文章中補(bǔ)上。讓我們開(kāi)始吧!
設(shè)置
我們假設(shè)你已經(jīng)具備一點(diǎn) Go 語(yǔ)言的開(kāi)發(fā)經(jīng)驗(yàn)。在安裝和配置 Go 開(kāi)發(fā)環(huán)境后之后,我們還要獲取以下一些依賴:
- go get github.com/davecgh/go-spew/spew
spew 可以幫助我們?cè)?console 中直接查看 struct 和 slice 這兩種數(shù)據(jù)結(jié)構(gòu)。
- go get github.com/gorilla/mux
Gorilla 的 mux 包非常流行, 我們用它來(lái)寫(xiě) Web handler。
- go get github.com/joho/godotenv
godotenv 可以幫助我們讀取項(xiàng)目根目錄中的 .env 配置文件,這樣我們就不用將 http port 之類(lèi)的配置硬編碼進(jìn)代碼中了。比如像這樣:
- ADDR=8080
接下來(lái),我們創(chuàng)建一個(gè) main.go 文件。之后我們的大部分工作都圍繞這個(gè)文件,讓我開(kāi)始編碼吧!
導(dǎo)入依賴
我們將所有的依賴包以聲明的方式導(dǎo)入進(jìn)去:
- package main
- import (
- "crypto/sha256"
- "encoding/hex"
- "encoding/json"
- "io"
- "log"
- "net/http"
- "os"
- "time"
- "github.com/davecgh/go-spew/spew"
- "github.com/gorilla/mux"
- "github.com/joho/godotenv"
- )
數(shù)據(jù)模型
接著我們來(lái)定義一個(gè)結(jié)構(gòu)體,它代表組成區(qū)塊鏈的每一個(gè)塊的數(shù)據(jù)模型:
- type Block struct {
- Index int
- Timestamp string
- BPM int
- Hash string
- PrevHash string
- }
- Index 是這個(gè)塊在整個(gè)鏈中的位置。
- Timestamp 顯而易見(jiàn)就是塊生成時(shí)的時(shí)間戳。
- Hash 是這個(gè)塊通過(guò) SHA256 算法生成的散列值。
- PrevHash 代表前一個(gè)塊的 SHA256 散列值。
- BPM 每分鐘心跳數(shù),也就是心率。還記得文章開(kāi)頭說(shuō)到的嗎?
接著,我們?cè)俣x一個(gè)結(jié)構(gòu)表示整個(gè)鏈,最簡(jiǎn)單的表示形式就是一個(gè) Block 的 slice:
- var Blockchain []Block
我們使用散列算法(SHA256)來(lái)確定和維護(hù)鏈中塊和塊正確的順序,確保每一個(gè)塊的 PrevHash 值等于前一個(gè)塊中的 Hash 值,這樣就以正確的塊順序構(gòu)建出鏈:
散列和生成塊
我們?yōu)槭裁葱枰⒘??主要是兩個(gè)原因:
- 在節(jié)省空間的前提下去唯一標(biāo)識(shí)數(shù)據(jù)。散列是用整個(gè)塊的數(shù)據(jù)計(jì)算得出,在我們的例子中,將整個(gè)塊的數(shù)據(jù)通過(guò) SHA256 計(jì)算成一個(gè)定長(zhǎng)不可偽造的字符串。
- 維持鏈的完整性。通過(guò)存儲(chǔ)前一個(gè)塊的散列值,我們就能夠確保每個(gè)塊在鏈中的正確順序。任何對(duì)數(shù)據(jù)的篡改都將改變散列值,同時(shí)也就破壞了鏈。
以我們從事的醫(yī)療健康領(lǐng)域?yàn)槔热缬幸粋€(gè)惡意的第三方為了調(diào)整“人壽險(xiǎn)”的價(jià)格,而修改了一個(gè)或若干個(gè)塊中的代表不健康的 BPM 值,那么整個(gè)鏈都變得不可信了。
我們接著寫(xiě)一個(gè)函數(shù),用來(lái)計(jì)算給定的數(shù)據(jù)的 SHA256 散列值:
- func calculateHash(block Block) string {
- record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash
- h := sha256.New()
- h.Write([]byte(record))
- hashed := h.Sum(nil)
- return hex.EncodeToString(hashed)
- }
這個(gè) calculateHash 函數(shù)接受一個(gè)塊,通過(guò)塊中的 Index,Timestamp,BPM,以及 PrevHash 值來(lái)計(jì)算出 SHA256 散列值。
接下來(lái)我們就能便攜一個(gè)生成塊的函數(shù):
- func generateBlock(oldBlock Block, BPM int) (Block, error) {
- var newBlock Block
- t := time.Now()
- newBlock.Index = oldBlock.Index + 1
- newBlock.Timestamp = t.String()
- newBlock.BPM = BPM
- newBlock.PrevHash = oldBlock.Hash
- newBlock.Hash = calculateHash(newBlock)
- return newBlock, nil
- }
其中,Index 是從給定的前一塊的 Index 遞增得出,時(shí)間戳是直接通過(guò) time.Now() 函數(shù)來(lái)獲得的,Hash 值通過(guò)前面的 calculateHash 函數(shù)計(jì)算得出,PrevHash 則是給定的前一個(gè)塊的 Hash 值。
校驗(yàn)塊
搞定了塊的生成,接下來(lái)我們需要有函數(shù)幫我們判斷一個(gè)塊是否有被篡改。檢查 Index 來(lái)看這個(gè)塊是否正確得遞增,檢查 PrevHash 與前一個(gè)塊的 Hash 是否一致,再來(lái)通過(guò) calculateHash 檢查當(dāng)前塊的 Hash 值是否正確。
通過(guò)這幾步我們就能寫(xiě)出一個(gè)校驗(yàn)函數(shù):
- func isBlockValid(newBlock, oldBlock Block) bool {
- if oldBlock.Index+1 != newBlock.Index {
- return false
- }
- if oldBlock.Hash != newBlock.PrevHash {
- return false
- }
- if calculateHash(newBlock) != newBlock.Hash {
- return false
- }
- return true
- }
除了校驗(yàn)塊以外,我們還會(huì)遇到一個(gè)問(wèn)題:兩個(gè)節(jié)點(diǎn)都生成塊并添加到各自的鏈上,那我們應(yīng)該以誰(shuí)為準(zhǔn)?這里的細(xì)節(jié)我們留到下一篇文章,這里先讓我們記住一個(gè)原則:始終選擇最長(zhǎng)的鏈。
通常來(lái)說(shuō),更長(zhǎng)的鏈表示它的數(shù)據(jù)(狀態(tài))是更新的,所以我們需要一個(gè)函數(shù)能幫我們將本地的過(guò)期的鏈切換成最新的鏈:
- func replaceChain(newBlocks []Block) {
- if len(newBlocks) > len(Blockchain) {
- Blockchain = newBlocks
- }
- }
到這一步,我們基本就把所有重要的函數(shù)完成了。接下來(lái),我們需要一個(gè)方便直觀的方式來(lái)查看我們的鏈,包括數(shù)據(jù)及狀態(tài)。通過(guò)瀏覽器查看 Web 頁(yè)面可能是最合適的方式!
Web 服務(wù)
我猜你一定對(duì)傳統(tǒng)的 Web 服務(wù)及開(kāi)發(fā)非常熟悉,所以這部分你肯定一看就會(huì)。
借助 Gorilla/mux 包,我們先寫(xiě)一個(gè)函數(shù)來(lái)初始化我們的 Web 服務(wù):
- func run() error {
- mux := makeMuxRouter()
- httpAddr := os.Getenv("ADDR")
- log.Println("Listening on ", os.Getenv("ADDR"))
- s := &http.Server{
- Addr: ":" + httpAddr,
- Handler: mux,
- ReadTimeout: 10 * time.Second,
- WriteTimeout: 10 * time.Second,
- MaxHeaderBytes: 1 << 20,
- }
- if err := s.ListenAndServe(); err != nil {
- return err
- }
- return nil
- }
其中的端口號(hào)是通過(guò)前面提到的 .env 來(lái)獲得,再添加一些基本的配置參數(shù),這個(gè) web 服務(wù)就已經(jīng)可以 listen and serve 了!
接下來(lái)我們?cè)賮?lái)定義不同 endpoint 以及對(duì)應(yīng)的 handler。例如,對(duì)“/”的 GET 請(qǐng)求我們可以查看整個(gè)鏈,“/”的 POST 請(qǐng)求可以創(chuàng)建塊。
- func makeMuxRouter() http.Handler {
- muxRouter := mux.NewRouter()
- muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
- muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
- return muxRouter
- }
GET 請(qǐng)求的 handler:
- func handleGetBlockchain(w http.ResponseWriter, r *http.Request) {
- bytes, err := json.MarshalIndent(Blockchain, "", " ")
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- io.WriteString(w, string(bytes))
- }
為了簡(jiǎn)化,我們直接以 JSON 格式返回整個(gè)鏈,你可以在瀏覽器中訪問(wèn) localhost:8080 或者 127.0.0.1:8080 來(lái)查看(這里的 8080 就是你在 .env 中定義的端口號(hào) ADDR)。
POST 請(qǐng)求的 handler 稍微有些復(fù)雜,我們先來(lái)定義一下 POST 請(qǐng)求的 payload:
- type Message struct {
- BPM int
- }
再看看 handler 的實(shí)現(xiàn):
- func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
- var m Message
- decoder := json.NewDecoder(r.Body)
- if err := decoder.Decode(&m); err != nil {
- respondWithJSON(w, r, http.StatusBadRequest, r.Body)
- return
- }
- defer r.Body.Close()
- newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)
- if err != nil {
- respondWithJSON(w, r, http.StatusInternalServerError, m)
- return
- }
- if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
- newBlockchain := append(Blockchain, newBlock)
- replaceChain(newBlockchain)
- spew.Dump(Blockchain)
- }
- respondWithJSON(w, r, http.StatusCreated, newBlock)
- }
我們的 POST 請(qǐng)求體中可以使用上面定義的 payload,比如:
- {"BPM":75}
還記得前面我們寫(xiě)的 generateBlock 這個(gè)函數(shù)嗎?它接受一個(gè)“前一個(gè)塊”參數(shù),和一個(gè) BPM 值。
POST handler 接受請(qǐng)求后就能獲得請(qǐng)求體中的 BPM 值,接著借助生成塊的函數(shù)以及校驗(yàn)塊的函數(shù)就能生成一個(gè)新的塊了!
除此之外,你也可以:
- 使用 spew.Dump 這個(gè)函數(shù)可以以非常美觀和方便閱讀的方式將 struct、slice 等數(shù)據(jù)打印在控制臺(tái)里,方便我們調(diào)試。
- 測(cè)試 POST 請(qǐng)求時(shí),可以使用 POSTMAN 這個(gè) chrome 插件,相比 curl它更直觀和方便。
POST 請(qǐng)求處理完之后,無(wú)論創(chuàng)建塊成功與否,我們需要返回客戶端一個(gè)響應(yīng):
- func respondWithJSON(w http.ResponseWriter, r *http.Request, code int, payload interface{}) {
- response, err := json.MarshalIndent(payload, "", " ")
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- w.Write([]byte("HTTP 500: Internal Server Error"))
- return
- }
- w.WriteHeader(code)
- w.Write(response)
- }
快要大功告成了
接下來(lái),我們把這些關(guān)于區(qū)塊鏈的函數(shù),Web 服務(wù)的函數(shù)“組裝”起來(lái):
- func main() {
- err := godotenv.Load()
- if err != nil {
- log.Fatal(err)
- }
- go func() {
- t := time.Now()
- genesisBlock := Block{0, t.String(), 0, "", ""}
- spew.Dump(genesisBlock)
- Blockchain = append(Blockchain, genesisBlock)
- }()
- log.Fatal(run())
- }
這里的 genesisBlock (創(chuàng)世塊)是 main 函數(shù)中最重要的部分,通過(guò)它來(lái)初始化區(qū)塊鏈,畢竟第一個(gè)塊的 PrevHash 是空的。
哦耶!完成了
你們可以從這里獲得完整的代碼:Github repo[1]
讓我們來(lái)啟動(dòng)它:
- go run main.go
在終端中,我們可以看到 web 服務(wù)器啟動(dòng)的日志信息,并且打印出了創(chuàng)世塊的信息:
接著我們打開(kāi)瀏覽器,訪問(wèn) localhost:8080 這個(gè)地址,我們可以看到頁(yè)面中展示了當(dāng)前整個(gè)區(qū)塊鏈的信息(當(dāng)然,目前只有一個(gè)創(chuàng)世塊):
接著,我們?cè)偻ㄟ^(guò) POSTMAN 來(lái)發(fā)送一些 POST 請(qǐng)求:
刷新剛才的頁(yè)面,現(xiàn)在的鏈中多了一些塊,正是我們剛才生成的,同時(shí)你們可以看到,塊的順序和散列值都正確。
下一步
剛剛我們完成了一個(gè)自己的區(qū)塊鏈,雖然很簡(jiǎn)單(陋),但它具備塊生成、散列計(jì)算、塊校驗(yàn)等基本能力。
接下來(lái)你就可以繼續(xù)深入的學(xué)習(xí)區(qū)塊鏈的其他重要知識(shí),比如工作量證明、權(quán)益證明這樣的共識(shí)算法,或者是智能合約、Dapp、側(cè)鏈等等。
目前這個(gè)實(shí)現(xiàn)中不包括任何 P2P 網(wǎng)絡(luò)的內(nèi)容,我們會(huì)在下一篇文章中補(bǔ)充這部分內(nèi)容,當(dāng)然,我們鼓勵(lì)你在這個(gè)基礎(chǔ)上自己實(shí)踐一遍!