P2P網(wǎng)絡(luò) - 比特幣開發(fā)指南
原文鏈接: https://bitcoin.org/en/developer-guide#operating-modes
翻譯: terryc007
版本:1.0
比特幣開發(fā)指南
比特幣網(wǎng)絡(luò)協(xié)議允許全節(jié)點一起協(xié)作維持p2p網(wǎng)絡(luò),實現(xiàn)區(qū)塊蚜厉,交易數(shù)據(jù)的交換寒匙。全節(jié)點下載饼丘,并驗證每個個區(qū)塊谣沸,交易玷过,然后在轉(zhuǎn)發(fā)給其他節(jié)點。檔案節(jié)點是一種全節(jié)點卤橄,它會存儲各個區(qū)塊鏈辱志,并為其他節(jié)點提供歷史區(qū)塊服務(wù)练湿。裁剪節(jié)點是一種不會保存這個區(qū)塊鏈的全節(jié)點叙谨。很多SPV客戶端也使用比特幣網(wǎng)絡(luò)協(xié)議來連接全節(jié)點嚼隘。
因為共識規(guī)則不涉及到網(wǎng)絡(luò)寄月,因此比特幣程序可以選擇不同的網(wǎng)絡(luò)辜膝,不同的協(xié)議,比如一些礦工使用高速區(qū)塊轉(zhuǎn)發(fā)網(wǎng)絡(luò)漾肮,一些提供SPV級別安全的錢包厂抖,使用專業(yè)的交易信息服務(wù)器。
為提高一個可實操的比特幣P2P網(wǎng)絡(luò)例子克懊,這個章節(jié)使用比特幣內(nèi)核作為典型的全節(jié)點忱辅,BitcoinJ作為典型SPV客戶端七蜘。這兩個程序都是靈活的,因此這里只討論默認(rèn)他們的行為墙懂。同時橡卤,考慮到隱私,下面例子中的ip地址已經(jīng)被替換成RFC5737預(yù)留的IP地址损搬。
節(jié)點發(fā)現(xiàn)
當(dāng)程序第一啟動時碧库,它并不知道任何活躍節(jié)點的ip地址。為了發(fā)現(xiàn)一些全節(jié)點的ip地址巧勤,他們會查詢硬編碼在比特幣內(nèi)核或BitCoinJ中的嵌灰,一個或多個DNS域名,在返回的結(jié)果中應(yīng)該包含一個或多個DNS A記錄颅悉,里面有一些可接受新連接的全節(jié)點的ip地址沽瞭。 比如,使用Unix命令 dig
:
;;QUESTION SECTION:
;seed.bitcoin.sipa.be. IN A
;; ANSWER SECTION:
seed.bitcoin.sipa.be. 60 IN A 192.0.2.113
seed.bitcoin.sipa.be. 60 IN A 198.51.100.231
seed.bitcoin.sipa.be. 60 IN A 203.0.113.183
[...]
DNS 種子由比特幣社區(qū)成員維護(hù)剩瓶。其中一部分提供動態(tài)DNS種子服務(wù)器驹溃,它通過掃描比特幣網(wǎng)絡(luò),自動獲取活動節(jié)點的ip地址延曙;其他的提供一些靜態(tài)DNS種子吠架,這需要手動更新,不過他們很有可能提供不活躍節(jié)點的ip地址搂鲫。不管是動態(tài)的傍药,還是靜態(tài)的DNS種子,如果節(jié)點在主網(wǎng)上運行在端口號8333魂仍,或在測試網(wǎng)絡(luò)運行在端口號18333拐辽,就會被加入到DNS種子。
DNS種子結(jié)果沒有被授權(quán)擦酌,一個惡意的DNS種子運營者或網(wǎng)絡(luò)中間人攻擊者能返回僅被攻擊者控制的節(jié)點的ip地址俱诸,在攻擊者自己的網(wǎng)絡(luò)中,孤立節(jié)點赊舶,并給他們假的交易睁搭,區(qū)塊數(shù)據(jù)。因為這個原因笼平,程序不應(yīng)該只依賴一個DNS種子园骆。
一但程序連接上比特幣網(wǎng)絡(luò),它的節(jié)點就可以開始發(fā)送寓调,帶有網(wǎng)絡(luò)中其他節(jié)點IP地址锌唾,端口號的addr
消息給其他節(jié)點。這個提供了一個完整的去中心化節(jié)點發(fā)現(xiàn)方法。比特幣內(nèi)核會在本地數(shù)據(jù)庫中保存已知節(jié)點的信息晌涕,通常滋捶,等下一次程序啟動時,它就不需要使用DNS種子余黎,直接可以跟這些節(jié)點連接即可重窟。
然而,節(jié)點通常會離開網(wǎng)絡(luò)或者改變ip地址惧财,這樣程序在啟動時亲族,在需要多次嘗試才有可能連接到比特幣網(wǎng)絡(luò)。這了會增加連接到比特幣網(wǎng)絡(luò)的延遲時間可缚,使得用戶在發(fā)送交易或檢查支付狀態(tài)前霎迫,不得不等待一段時間。
為避免這種延遲帘靡,BitcoinJ總是使用動態(tài)DNS種子知给,來獲取那些被確定為活躍節(jié)點的IP地址。比特幣處內(nèi)核也嘗試在降低延遲描姚,避免使用不必要的DNS節(jié)點中權(quán)衡涩赢。如果比特幣內(nèi)核在它的節(jié)點數(shù)據(jù)庫中有記錄,它就會用11秒時間去連接至少其中一個節(jié)點轩勘,失敗后筒扒,才使用DNS節(jié)點獲取ip地址;如果在11秒內(nèi)成功建立連接绊寻,則不在向DNS種子查詢花墩。
比特幣內(nèi)核跟BitcoinJ在其他特定版本的第一版本發(fā)布時,他們在代碼里面都硬編碼了一些節(jié)點當(dāng)時活躍節(jié)點的IP地址跟端口號澄步。比特幣內(nèi)核內(nèi)置了一個自動回調(diào)選項冰蘑,當(dāng)沒有DNS種子服務(wù)器在60秒內(nèi)回應(yīng)查詢,比特幣內(nèi)核會開始嘗試跟這些節(jié)點連接村缸。
作為一個手段回調(diào)選項祠肥,比特幣內(nèi)核也提供了好幾個命令行連接選項,包括從一個指定節(jié)點梯皿,通過其IP地址獲取一個節(jié)點列表仇箱,或直接跟一個指定節(jié)點,通過IP地址建立持久連接东羹。通過-help
獲取命令行詳情剂桥。BitcoinJ也可以通過編程實現(xiàn)這樣的功能。
資源: Bitcoin種子, 這個程序管理了好幾個比特幣內(nèi)核百姓,BitcoinJ都有用到的DNS種子渊额。比特幣內(nèi)核DNS種子政策。比特幣內(nèi)核垒拢,BitcoinJ里硬編碼的節(jié)點IP地址是使用makeseeds script生成的旬迹。
連接到節(jié)點
通過給遠(yuǎn)程節(jié)點,發(fā)送version
消息跟他節(jié)點建立連接求类,消息里面包括軟件版本號奔垦,區(qū)塊,當(dāng)前時間尸疆。遠(yuǎn)程節(jié)點也返回一個version
消息椿猎。然后,他們再給其他節(jié)點發(fā)送verack
消息寿弱,表示他們已經(jīng)建立連接犯眠。
一但建立連接,客戶端就能給遠(yuǎn)程節(jié)點發(fā)送getaddr
和addr
消息獲取其他節(jié)點信息症革。
客戶端為維持跟節(jié)點的連接筐咧,在其離線前30分鐘,它會給其他節(jié)點發(fā)送一個消息噪矛。如果節(jié)點在90分鐘內(nèi)量蕊,沒有返回消息,那么這個客戶端就認(rèn)為連接已經(jīng)關(guān)閉艇挨。
初始化區(qū)塊下載
在全節(jié)點驗證非確認(rèn)交易残炮,最近挖出的區(qū)塊前,它必須下載缩滨,驗證最佳區(qū)塊鏈上所有的區(qū)塊(從創(chuàng)世區(qū)塊到最頂部的區(qū)塊)势就。這叫做初始化區(qū)塊下載(IBD)或者叫初始化同步。
雖然“初始化” 意味著這個方法只會使用一次脉漏,但可以在任何時候蛋勺,在下載大量區(qū)塊的時候,用到它鸠删,比如當(dāng)一個之前連接過的節(jié)點下線了很長一段時間抱完。這種情況,節(jié)點能使用IBD方法去下載從它最近上線以來刃泡,所有的挖出的區(qū)塊巧娱。
在任何時候,只要比特幣內(nèi)核本地最佳區(qū)塊鏈上烘贴,它最新的區(qū)塊的區(qū)塊頭時間禁添,以及超過了24小時,它就會使用IBD方法獲取新的區(qū)塊桨踪。如果本地最佳區(qū)塊頭鏈老翘,比本地最佳區(qū)塊鏈多出144個區(qū)塊(這也就意味著,本地最佳區(qū)塊鏈已經(jīng)有24小時沒有更新了),比特幣內(nèi)核0.10.0也會使用IBD方法铺峭。
區(qū)塊優(yōu)先
比特幣內(nèi)核(一直到0.9.3版本為止)使用簡單的初始化區(qū)塊下載(IBD)方法墓怀,我們稱之為區(qū)塊優(yōu)先。它的目標(biāo)是從最佳區(qū)塊鏈按序下載區(qū)塊卫键。
節(jié)點第一啟動時傀履,在它本地最佳區(qū)塊鏈只有一個區(qū)塊 — 硬編碼的創(chuàng)世區(qū)塊(區(qū)塊0)。節(jié)點了會選擇一個遠(yuǎn)程節(jié)點(也叫同步節(jié)點)莉炉,并給它發(fā)送一個getblocks
消息钓账,如下圖所示:
在getblocks
消息頭部哈希域,這個新節(jié)點發(fā)這個區(qū)塊僅有的頭哈希 - 創(chuàng)世區(qū)塊(6fe2...0000 內(nèi)部字節(jié)序)絮宁。同時它把停止哈希域全設(shè)置為0以獲取最大返回結(jié)果梆暮。
同步節(jié)點一但收到這個getblocks
消息,它使用第一個哈希頭去它本地最佳區(qū)塊鏈搜索帶有這個哈希頭的區(qū)塊绍昂。如果找到block 0與之匹配啦粹,它就從區(qū)塊1開始,返回500個區(qū)塊清單(getblocks
消息返回的最大個數(shù))治专。它使用inv
消息來發(fā)送這些區(qū)塊清單卖陵。 如下面所示:
存貨清單是一些在比特幣網(wǎng)絡(luò)上獨一無二的身份標(biāo)識信息。每個存貨包含一個類型张峰,一個對象實例的唯一標(biāo)識泪蔫。對于區(qū)塊而言,這個唯一標(biāo)識是區(qū)塊頭的哈希值喘批。
inv
消息中區(qū)塊存貨信息的順序跟其在區(qū)塊鏈中的是一樣的撩荣,因此第一個inv
消息包含了區(qū)塊1到區(qū)塊501存貨信息。(比如饶深,如上面所示餐曹,區(qū)塊1的哈希值是4860...0000)
IBD節(jié)點使用收到存貨清單,通過getdata
消息敌厘,從同步節(jié)點獲取128個區(qū)塊台猴。如下圖所示:
對于區(qū)塊優(yōu)先的節(jié)點而言,按序請求俱两,發(fā)送區(qū)塊是非常重要的饱狂,因為每個區(qū)塊頭會引用它前面的區(qū)塊頭。這就意味IBD節(jié)點宪彩,必須在父區(qū)塊還沒有接收完之前休讳,是不能完全的驗證區(qū)塊的。之所以不能驗證區(qū)塊尿孔,是因為那些沒有收到父區(qū)塊的區(qū)塊是孤塊俊柔;下小節(jié)會詳細(xì)地介紹到筹麸。
一但收到getdata
消息,同步節(jié)點就會把請求的區(qū)塊返回給IBD節(jié)點雏婶。每個區(qū)塊被序列化成區(qū)塊格式物赶,并發(fā)送各自的block
消息。發(fā)送的第一個block
消息(block1)尚骄,如下圖所示块差。
IBD節(jié)點下載每個區(qū)塊侵续,并驗證倔丈,然后獲取下一個還未請求過的區(qū)塊,并維持一個128個區(qū)塊的下載隊列状蜗。當(dāng)它獲取完存貨清單中所有的區(qū)塊后需五,它就發(fā)送另外一個getblocks
消息給同步節(jié)點,以獲取最多500個區(qū)塊的存貨清單轧坎。第二個getblocks
消息包含多個區(qū)塊頭哈希宏邮,如下圖所示:
同步節(jié)點一但收到第二個getblocks
消息,它就按照收到區(qū)塊頭哈希的順序缸血,挨個在它本地最佳的區(qū)塊鏈中尋找匹配的區(qū)塊蜜氨。如果它找到一個匹配的區(qū)塊,它會從該區(qū)塊的下個區(qū)塊開始,返回500個區(qū)塊存貨清單捎泻。 如果沒有找到一個匹配的哈希(除了那個截止哈希外)飒炎,它會假定這個兩個節(jié)點只有block0是一樣的,因此它會發(fā)送一個從block1開始的inv
消息笆豁。
通過這樣的重復(fù)查找郎汪,允許同步節(jié)點發(fā)送有用的存貨清單,即使IDB節(jié)點本地的區(qū)塊鏈?zhǔn)菑耐焦?jié)點的本地區(qū)塊鏈分叉而來的闯狱。IBD節(jié)點上的區(qū)塊離區(qū)塊鏈頂部區(qū)塊越近煞赢,分叉檢測就變的越有用。 [?]
當(dāng)IBD節(jié)點收到第二個inv
消息后哄孤,它會使用getdata
消息請求這些區(qū)塊照筑。同步節(jié)點會給IBD節(jié)點返回block
消息。然后IBD節(jié)點會使用getblocks
消息瘦陈,繼續(xù)請求更多的存貨清單凝危。 不斷的重復(fù)這個過程直到IBD節(jié)點同步完整個區(qū)塊鏈。到這后双饥,IBD節(jié)點通過普通的區(qū)塊廣播(后續(xù)小節(jié)會講到)來接受新的區(qū)塊媒抠。
區(qū)塊優(yōu)先的優(yōu)缺點
區(qū)塊優(yōu)先主要優(yōu)點在于簡單。其主要缺點在于只依賴于一個同步節(jié)點來下載區(qū)塊數(shù)據(jù)咏花。這會帶來幾個影響:
速度受限: 因為所有的請求都指向一個同步節(jié)點趴生,這樣如果同步節(jié)點的上傳帶寬有限阀趴,那么IBD節(jié)點的下載速度就很慢。注意:如果同步節(jié)點離線苍匆,比特幣內(nèi)核會從另外一個同步節(jié)點下載— 但是它仍然一次只從一個同步節(jié)點下載刘急。
下載重起:同步節(jié)點可能給IBD節(jié)點發(fā)送非最佳(但是其他的都有效)區(qū)塊鏈。IBD節(jié)點是無法驗證它到底是不是最佳的區(qū)塊鏈浸踩,直到初始化區(qū)塊下載接近完成時才可以叔汁。這會強制IBD節(jié)點重新從另外一個節(jié)點下載區(qū)塊鏈。開發(fā)者在比特幣內(nèi)核中检碗,在多個不同區(qū)塊高度据块,加了好幾個區(qū)塊鏈檢測點,來幫組IBD節(jié)點監(jiān)測它是否在下載一條非最佳區(qū)塊鏈折剃。 這可以讓IBD節(jié)點盡早的重啟下載另假。
硬盤充滿攻擊:這個跟下載重起關(guān)系很大,如果同步節(jié)點發(fā)送了一個非最佳區(qū)塊鏈怕犁,這條鏈會存儲在硬盤上边篮,浪費磁盤空間,可能會使磁盤上充滿無用的數(shù)據(jù)奏甫。
高內(nèi)存消耗:不管是故意的戈轿,還是意外,同步節(jié)點可能無序地發(fā)送區(qū)塊阵子,這會導(dǎo)致一些孤塊思杯,只有收到并驗證了其父塊后,才能驗證這些孤塊款筑。孤塊在等待驗證期間會一直存在內(nèi)存中智蝠,這會消耗大量內(nèi)存。
在比特幣內(nèi)核0.10.0中奈梳,所有的這些問題在頭部優(yōu)先IBD方法中杈湾,部分或全部的得以解決。
資源: 下面的的表格總結(jié)了這節(jié)提到的消息攘须。點擊消息欄的鏈接可以查看對于消息的參考頁面漆撞。
消息 | getblocks |
inv |
getdata |
block |
---|---|---|---|---|
From→To | IBD→Sync | Sync→IBD | IBD→Sync | Sync→IBD |
內(nèi)容 | 一個/多個頭哈希 | 最多500個區(qū)塊存貨(唯一id) | 一個/多個區(qū)塊存貨 | 一個 序列化區(qū)塊 |
區(qū)塊頭優(yōu)先
比特幣內(nèi)核0.10.0使用區(qū)塊頭優(yōu)先IBD的初始化區(qū)塊下載方法。其目標(biāo)是先下載最佳區(qū)塊鏈頭于宙,部分驗證浮驳,然后并行下載相應(yīng)的區(qū)塊。這解決了幾個區(qū)塊優(yōu)先IBD方法中的問題捞魁。
節(jié)點第一次啟動時至会,它本地最佳區(qū)塊鏈只要一個區(qū)塊 - 硬編碼的創(chuàng)世區(qū)塊(block0)。 它會選擇一個遠(yuǎn)程節(jié)點谱俭,這里我們稱之為同步節(jié)點奉件,然后給同步節(jié)點發(fā)送getheaders
消息宵蛀。如下圖所示:
在getheaders
消息的區(qū)塊頭哈希域,新節(jié)點只發(fā)送了它本地僅有的區(qū)塊頭哈希 - 創(chuàng)世區(qū)塊哈希( 6fe2…0000 內(nèi)部字節(jié)序)县貌。同時會截止哈希域全設(shè)為0术陶,以獲取最多哈希值。
同步節(jié)點一但收到getheaders
消息煤痕,它會取出第一個區(qū)塊頭哈希梧宫,然后用它在本地最佳區(qū)塊鏈中搜索區(qū)塊。如果block0匹配摆碉,同步節(jié)點就會從block1開始塘匣,返回2000個區(qū)塊頭哈希。它以headers
消息的方式兆解,來發(fā)送這些區(qū)塊頭馆铁。如下圖所示:
IBD節(jié)點可以部分驗證區(qū)塊頭跑揉,它是通過確保區(qū)塊頭所有字段遵循共識規(guī)則锅睛,同時區(qū)塊頭的哈希值要低于nBits字段的目標(biāo)閥值來實現(xiàn)。(要完整驗證區(qū)塊历谍,仍需要獲得該區(qū)塊所有交易后才可以)
當(dāng)IBD節(jié)點部分驗證完區(qū)塊頭之后现拒,它可以并行做兩件事情:
-
下載更多的區(qū)塊頭: IBD節(jié)點可以發(fā)送另外一個
getheaders
消息到同步節(jié)點,以獲取最佳區(qū)塊鏈上望侈,下一批2000個區(qū)塊頭印蔬。這些區(qū)塊頭可以被立即驗證,同時不斷的重復(fù)批量發(fā)送請求脱衙,直到從同步節(jié)點收到的headers
消息中所包含的頭少于2000個侥猬,這表示已沒有更多的區(qū)塊頭。在撰寫本文時捐韩,少于200個來回退唠,就可以完成整個區(qū)塊頭同步,大約需要下載32MB數(shù)據(jù)荤胁。一但I(xiàn)BD節(jié)點收到一個少于2000個區(qū)塊頭的
headers
消息瞧预,它就給它所有外連的節(jié)點發(fā)送一個getheaders
消息,看看他們最佳區(qū)塊鏈的情況仅政。通過對比它們返回的消息垢油,它很容易通過它外聯(lián)的節(jié)點,判斷它所下載的區(qū)塊頭是不是屬于最佳區(qū)塊鏈上的圆丹。這就意味一個不誠實的節(jié)點很快會被發(fā)現(xiàn)滩愁,即使不用檢測點(只要IBD節(jié)點連接到至少一個誠實節(jié)點,如果找不到誠實節(jié)點辫封,比特幣內(nèi)核會繼續(xù)提供檢查點)硝枉。 下載區(qū)塊: 當(dāng)IBD繼續(xù)下載區(qū)塊頭時玖瘸,以及完成區(qū)塊頭下載后,IBD節(jié)點會請求并下載每個區(qū)塊檀咙。IBD節(jié)點通過區(qū)塊頭鏈中區(qū)塊的哈希雅倒,來創(chuàng)建
getdata
消息。而在區(qū)塊優(yōu)先中弧可,getdata
中的區(qū)塊頭哈希需要通過inv
消息中的區(qū)塊存貨清單來提供蔑匣。但在區(qū)塊頭先中,就不必從同步節(jié)點獲取區(qū)塊棕诵, 它可以從其他任何全節(jié)點獲取裁良。(雖然并不是所有的全節(jié)點存儲所有的區(qū)塊。) 這就運行它能夠并行獲取區(qū)塊校套,同時避免下載速度受限于單個同步節(jié)點的帶寬速度价脾。
為從更多的節(jié)點加載數(shù)據(jù),比特幣內(nèi)核一次最多從單個節(jié)點獲取16個區(qū)塊笛匙,最多8個外向連接侨把。這就意味采用區(qū)塊頭優(yōu)先的比特幣內(nèi)核, 在IBD階段妹孙,同時最多可以同時發(fā)起128個區(qū)塊請求秋柄。(跟采用區(qū)塊優(yōu)先的比特幣內(nèi)核最大請求數(shù)是一樣的)
比特幣內(nèi)核采用的區(qū)塊頭優(yōu)先模式,采用1024個區(qū)塊為一移動下載時間窗口蠢正,以最大化下載速度骇笔。在下載時間窗口中最低區(qū)塊,是下一個即將被驗證的區(qū)塊嚣崭。 如果輪到區(qū)塊驗證了笨触,但區(qū)塊還未下載完成,比特幣內(nèi)核會至少會等2秒雹舀,等待區(qū)塊從失速節(jié)點下載完成芦劣,如果還沒有下載完成,比特幣內(nèi)核會斷開跟失速節(jié)點的連接葱跋,并嘗試給另外一個節(jié)點連接持寄。比如,如上圖所示娱俺,如果在2秒后稍味,節(jié)點A不能發(fā)送區(qū)塊3,那么節(jié)點A會被斷開連接荠卷。
一但I(xiàn)BD節(jié)點同步完整個區(qū)塊鏈模庐,他會接收來那些通過正常區(qū)塊廣播而來的區(qū)塊,這個會在后面的小節(jié)會講到油宜。
資源: 下面的的表格總結(jié)了這節(jié)提到的消息掂碱。點擊消息欄的鏈接可以查看對于消息的參考頁面怜姿。
消息 | getheaders |
headers |
getdata |
block |
---|---|---|---|---|
發(fā)送→接 | IBD→Sync | Sync→IBD | IBD→Many | Many→IBD |
內(nèi)容 | 一個/多個區(qū)塊頭哈希 | 最大2000個區(qū)塊頭哈希 | 一個/多個從哈希頭派生出來的區(qū)塊存貨清單 | 一個序列化區(qū)塊 |
區(qū)塊廣播
當(dāng)?shù)V工發(fā)現(xiàn)一個新區(qū)塊后,它會使用下面的方法把區(qū)塊廣播給它的節(jié)點:
主動推送區(qū)塊:礦工給它的每個一個全節(jié)點發(fā)送一個帶有新區(qū)塊的
block
消息疼燥。在這種情況沧卢,礦工不采用標(biāo)準(zhǔn)的轉(zhuǎn)發(fā)方法,因為它知道跟它連接的節(jié)點沒有一個正好發(fā)現(xiàn)這個區(qū)塊醉者。-
標(biāo)準(zhǔn)轉(zhuǎn)發(fā)區(qū)塊: 礦工扮演一個標(biāo)準(zhǔn)的轉(zhuǎn)發(fā)節(jié)點但狭,給它每一個節(jié)點(全節(jié)點,SPV)撬即,通過發(fā)送帶有新區(qū)塊存貨訂單的
inv
消息立磁。 通常節(jié)點會有以下返回:每個區(qū)塊優(yōu)先(BF)節(jié)點,想要從全節(jié)點通過
getdata
消息中獲取區(qū)塊信息剥槐。每個區(qū)塊頭優(yōu)先(HF)節(jié)點唱歧,想要從全節(jié)點通過
getheaders
消息獲取區(qū)塊,消息中應(yīng)包括其最佳區(qū)塊鏈上粒竖,最高區(qū)塊頭的哈希頭颅崩,以及可能帶有一些,用于檢測分叉的温圆,在最佳區(qū)塊鏈上的后續(xù)區(qū)塊頭挨摸。然后緊接著發(fā)送一個getdata
消息去請一個完整的區(qū)塊。通過先請求區(qū)塊頭岁歉,一個區(qū)塊頭優(yōu)先的節(jié)點可能會拒絕孤塊,這會在下面小節(jié)會講到膝蜈。-
每個SPV客戶端锅移,通常想通過
getdata
消息獲取默克爾區(qū)塊。礦工根據(jù)每個請求相應(yīng)的給他們返回消息饱搏。 通過
block
消息發(fā)送區(qū)塊非剃,通過headers
消息發(fā)送一個或多個區(qū)塊頭,在0/多個tx消息后推沸,通過merkleblock
消息备绽,發(fā)送與SPV客戶端bloom過濾器相應(yīng)的默克爾區(qū)塊,交易鬓催。-
直接公告區(qū)塊頭:中轉(zhuǎn)節(jié)點可跳過
getheaders
跟inv
消息之間來回切換的方式肺素,獲取新區(qū)塊信息,直接立即發(fā)送一個包含完整新區(qū)塊頭的headers
消息宇驾。HF(區(qū)塊頭優(yōu)先)節(jié)點收到這個消息后倍靡,當(dāng)它在區(qū)塊頭優(yōu)先IBD階段時,它會部分驗證區(qū)塊頭课舍,如果區(qū)塊頭是有限的塌西,它就會通過getdata
消息他挎,來請求整個區(qū)塊內(nèi)容。 中轉(zhuǎn)節(jié)點會給getdata
請求捡需,相應(yīng)的以block
或merkleblock
消息返回完整的办桨,或過濾后的區(qū)塊數(shù)據(jù)。HF節(jié)點在握手連接時站辉,可以通過發(fā)送一個特殊的sendheaders
消息來發(fā)出它更喜歡接收headers
消息崔挖,而非inv
消息的信號。這個區(qū)塊廣播協(xié)議已經(jīng)在BIP130被提議庵寞,自從比特幣內(nèi)核0.12后狸相,都實現(xiàn)了這個協(xié)議。
-
在默認(rèn)情況下捐川,比特幣內(nèi)核使用直接廣播區(qū)塊的方式給那些發(fā)送了sendheaders
信號的節(jié)點廣播區(qū)塊脓鹃,而對其他節(jié)點使用標(biāo)準(zhǔn)區(qū)塊轉(zhuǎn)發(fā)方式。比特幣內(nèi)核接受以上所有方式轉(zhuǎn)發(fā)的區(qū)塊古沥。
全節(jié)點驗證收到的區(qū)塊瘸右,然后使用標(biāo)準(zhǔn)區(qū)塊轉(zhuǎn)發(fā)方式轉(zhuǎn)發(fā)給它的節(jié)點。下面精簡表格岩齿,重點羅列了這個過程中用到的消息(Relay, BH, HF, SPV 分別對應(yīng) 轉(zhuǎn)發(fā)節(jié)點太颤,區(qū)塊優(yōu)先節(jié)點,區(qū)塊頭優(yōu)先節(jié)點盹沈,SPV客戶端龄章;Any - 表示一個使用任何獲取區(qū)塊方法的節(jié)點)
消息 | inv |
getdata |
getheaders |
headers |
---|---|---|---|---|
From→To | Relay→Any | BF→Relay | HF→Relay | Relay→HF |
內(nèi)容 | 新區(qū)塊清單 | 新區(qū)塊清單 | 在HF節(jié)點最佳區(qū)塊頭鏈(BHC)上,一個/多個區(qū)塊頭哈希 | 最多2000個區(qū)塊頭乞封,把HF節(jié)點的BHC跟轉(zhuǎn)發(fā)節(jié)點的BHC連接起來 |
消息 | block |
merkleblock |
tx |
|
From→To | Relay→BF/HF | Relay→SPV | Relay→SPV | |
內(nèi)容 | 新序列化區(qū)塊 | 對新區(qū)塊修改后的默克爾區(qū)塊 | 來自新區(qū)塊做裙,跟bloom過濾器匹配的序列化的交易 |
孤塊
區(qū)塊優(yōu)先節(jié)點可能會下載孤塊。所謂的孤塊就是指之前區(qū)塊頭哈希字段肃晚,所指向的區(qū)塊還未看到锚贱。也就是說,孤塊沒有可知的父區(qū)塊(跟陳腐區(qū)塊不一樣关串,它們有父區(qū)塊拧廊,但是它們不屬于最近區(qū)塊鏈)。
當(dāng)區(qū)塊優(yōu)先節(jié)點下載了一個孤塊晋修,節(jié)點不會立刻去驗證它吧碾,而是給發(fā)送孤塊的節(jié)點(廣播節(jié)點)發(fā)送一個getblocks
消息,這個廣播節(jié)點會返回一個帶有區(qū)塊清單的inv
消息,這個清單里面是節(jié)點丟失區(qū)塊的信息(最多500條);下載節(jié)點使用getdata
消息請求這些區(qū)塊洲炊;然后廣播節(jié)點會以block
消息形式發(fā)送這些區(qū)塊侧戴。然后下載節(jié)點會驗證這些區(qū)塊兄朋,一但之前孤塊的父區(qū)塊下載完成吃环,并被驗證楔敌,下載節(jié)點就會驗證之前的孤塊博脑。
區(qū)塊頭優(yōu)先節(jié)點為避免復(fù)雜添履,它在使用getdata
消息請求一個區(qū)塊前屁倔,經(jīng)常先使用getheaders
消息請求區(qū)塊頭。 廣播節(jié)點會給下載節(jié)點發(fā)送一個headers
消息暮胧,這個消息里面包含了它認(rèn)為的锐借,下載節(jié)點要達(dá)到最佳區(qū)塊鏈頂部,所需要的所有區(qū)塊頭(做多2000條)往衷;每個區(qū)塊頭都會指向它的父區(qū)塊钞翔,因此當(dāng)下載節(jié)點收到block
消息時,該區(qū)塊不應(yīng)該是一個孤塊 — 因為它所有的父塊哈希都已經(jīng)知道(即使他們還未被驗證)席舍。如果在block
消息中布轿,收到的區(qū)塊是孤塊,那么區(qū)塊頭優(yōu)先節(jié)點會立即丟棄它来颤。
然而汰扭,丟棄孤塊意味著,區(qū)塊頭優(yōu)先節(jié)點會忽略掉礦工以主動推送方法發(fā)出的孤塊福铅。
交易廣播
要發(fā)送一個交易到另外一個節(jié)點萝毛,需要發(fā)送一個inv
消息。如果節(jié)點收到一個getdata
消息滑黔,就會使用tx
發(fā)送這個交易笆包。節(jié)點收到這個交易后,如果是一個有效的交易拷沸,也會以同樣的方式轉(zhuǎn)發(fā)交易色查。
內(nèi)存池
全節(jié)點可以跟蹤那些可以打包進(jìn)下一區(qū)塊的未確認(rèn)交易。這對于那些挖取這些或全部交易的礦工來說撞芍,是非常有必要的,但是它對于任何想要跟蹤未被確認(rèn)交易的節(jié)點來說跨扮,也是很有用序无,比如為SPV節(jié)點提供未確認(rèn)交易信息服務(wù)的節(jié)點。
因為在比特幣中衡创,未確認(rèn)交易沒有一種持久狀態(tài)帝嗡,比特幣內(nèi)核會把他們保存在內(nèi)存里,也叫做內(nèi)存池璃氢。當(dāng)一個節(jié)點關(guān)掉后哟玷,除了那些保存到錢包的交易外,其他在內(nèi)存池中的交易全部會丟失。這就意味著巢寡,但節(jié)點重起后喉脖,那些沒有被挖出的未確認(rèn)交易會慢慢的從網(wǎng)絡(luò)中消息掉,或因為內(nèi)存不足時抑月,把其中的一些未確認(rèn)交易從內(nèi)存池中刪除掉树叽。
那些沒有被打包進(jìn)去區(qū)塊的交易會變成陳腐區(qū)塊,它們可能會被重新加到內(nèi)存池中谦絮。如果替代區(qū)塊已包含這些交易题诵,這些從新加入到內(nèi)存池的交易會立即被刪除掉。在比特幣內(nèi)核中层皱,這種情況就是性锭,從最佳區(qū)塊鏈最頂部開始(最高區(qū)塊),把鏈上的陳腐區(qū)塊一個個刪掉叫胖。當(dāng)每個區(qū)塊被刪除時草冈,它的交易會重新加到內(nèi)存池中。刪掉完陳腐區(qū)塊后臭家,在區(qū)塊鏈頂部疲陕,逐個加上替代區(qū)塊。當(dāng)添加完一個區(qū)塊后钉赁,區(qū)塊中確認(rèn)的交易就會從內(nèi)存中刪除掉蹄殃。
SPV客戶端因為不需要轉(zhuǎn)發(fā)交易,所以他們就沒有內(nèi)存池你踩。他們不能獨立的驗證一個交易是否包含在一個區(qū)塊里面诅岩,同時SPV客戶端只能花UTXOs,所以他們不知道哪個交易是合格的带膜,是可以打包到下一個區(qū)塊的吩谦。
作弊節(jié)點
要注意的是,對于區(qū)塊膝藕,交易這兩種廣播式廷,系統(tǒng)有一個機制來懲罰那些作弊節(jié)點。 他們會通過發(fā)送錯誤信息來占用帶寬芭挽,計算資源滑废。如果一個節(jié)點的banscore值大于-banscore=<n>
設(shè)置的閥值,它就會被禁止-bantime=<n>
中所設(shè)置的時長袜爪,這個默認(rèn)是86400秒(24小時)蠕趁。
警告
在早期的比特幣內(nèi)核版本,是允許開發(fā)者辛馆,可信的社區(qū)成員給用戶發(fā)布比特幣警告俺陋,以通知用戶比特幣網(wǎng)絡(luò)出現(xiàn)嚴(yán)重的問題。這個消息系統(tǒng)在比特幣內(nèi)核0.13.0就已經(jīng)作廢掉;然而腊状,內(nèi)部警告诱咏,分叉檢測警告,-alertnofity
功能還保留著寿酌。
聲明:
文中帶有[?]的地方胰苏,表示我對此翻譯明顯感覺不太對的,后續(xù)會不斷修正醇疼。
有些地方可能會翻譯的不好硕并,不地道,甚至錯誤秧荆,如果有發(fā)現(xiàn)倔毙,還請留言,指出乙濒,以便我好修正陕赃,謝謝!