Btcd區(qū)塊在P2P網(wǎng)絡(luò)上的傳播(引子)

前面的系列文章中我們介紹了Bitcoin網(wǎng)絡(luò)中節(jié)點對區(qū)塊的存取機制侠草,本文開始我們將介紹Btcd節(jié)點如何組成P2P網(wǎng)絡(luò)以及區(qū)塊如何在P2P網(wǎng)絡(luò)中傳播。區(qū)塊在網(wǎng)絡(luò)上的傳遞過程涉及節(jié)點之間的連接管理、地址管理房蝉、Peer節(jié)點的管理和Peer之間同步區(qū)塊的協(xié)議等問題颊郎,與之相關(guān)的代碼在如下幾個包或者文件中:

  • btcd/wire: 實現(xiàn)了Bitcoin網(wǎng)絡(luò)協(xié)議,定義了Peers之間的協(xié)議消息他炊、消息格式及包的封裝和解析等;
  • btcd/peer: 實現(xiàn)了P2P網(wǎng)絡(luò)中Peer之間維持連接及收發(fā)wire協(xié)議消息的機制;
  • btcd/connmgr: 管理Peer節(jié)點之間的TCP連接争剿,包括監(jiān)聽本地端口等待其他節(jié)點連接和主動連接Peer節(jié)點等等;
  • btcd/addrmgr: 收集、存儲Bitcoin網(wǎng)絡(luò)上的節(jié)點的地址痊末,隨機從地址集中選擇路由可達的地址建立Peer連接蚕苇,這些地址包括IPv4、IPv6及洋蔥地址(.onion address)等;
  • btcd/server.go: Btcd節(jié)點啟動后執(zhí)行的主要入口凿叠,定義了server及serverPeer類型涩笤,負責(zé)初始化和啟動connmgr、addrmgr盒件,以及響應(yīng)Peer的協(xié)議消息等蹬碧。

這些模塊之間的關(guān)系如下圖所示:

接下來,我們將逐一閱讀和分析各個包中的代碼炒刁。我們首先來了解P2P網(wǎng)絡(luò)是如何組網(wǎng)恩沽,然后再進一步了解Bitcoin網(wǎng)絡(luò)協(xié)議的實現(xiàn)。btcd/peer是實現(xiàn)Bitcoin P2P網(wǎng)絡(luò)的核心模塊翔始,我們先從它開始介紹飒筑。

Clone完btcd/peer的代碼后,我們可以發(fā)現(xiàn)它包含如下一些文件:

  • peer.go: 包含了幾乎全部的Peer相關(guān)邏輯實現(xiàn)代碼绽昏;
  • mruinvmap.go: 實現(xiàn)了一個簡易的緩存Inverntory的LRU Cache协屡。請注意,雖然文件名由mru開頭全谤,但它實際上實現(xiàn)的是LRU Cache肤晓。Inventory是節(jié)點向Peer節(jié)點回復(fù)或者通知的己方已經(jīng)知道的Tx或者Block,它通過inv協(xié)議消息以Inventory Vectors的形式發(fā)往Peer節(jié)點,我們將在介紹網(wǎng)絡(luò)協(xié)議時進一步介紹它补憾。之所以要使用緩存漫萄,是為了防止向Peer重復(fù)發(fā)送已經(jīng)發(fā)送過的Inventory;
  • mrunoncemap.go: 與mruinvmap.go類似,它實現(xiàn)了一個緩存nonce值的LRU Cache盈匾。這里的nonce值是一個64位隨機整數(shù)值腾务,用于填充Peer間握手交換Version信息的nonce字段,nonce字段用來判斷欲連接的Peer是否就是自己:節(jié)點發(fā)出Version消息時削饵,會填入一個隨機的nonce值岩瘦,連接不同Peer節(jié)點時,節(jié)點會緩存所有為Version消息生成的nonce值窿撬;如果節(jié)點收到了一個Version消息且其中的nonce值是自己緩存的nonce中的一個启昧,那么可以判斷這個Version消息由自己發(fā)送給自己了;
  • log.go: 提供一些log方法;
  • doc.go: 包btcd/peer的doc文件;
  • example_test.go、export_test.go劈伴、mruinvmap_test.go密末、mrunoncemap_test.go、peer_test.go: 定義一些測試方法;

其中主要的類型為Peer跛璧、Config和MessageListeners严里,Peer類型定義了Peer相關(guān)的屬性和方法,Config類型定義了與Peer相關(guān)的配置追城,MessageListeners定義了響應(yīng)Peer消息的回調(diào)函數(shù)田炭。它們定義的成員字段比較多,我們不打算一一介紹漓柑,將在分析具體代碼時解釋其字段的意義教硫。我們先從創(chuàng)建Peer對象的newPeerBase()方法入手來分析Peer:

//btcd/peer/peer.go

// newPeerBase returns a new base bitcoin peer based on the inbound flag.  This
// is used by the NewInboundPeer and NewOutboundPeer functions to perform base
// setup needed by both types of peers.
func newPeerBase(origCfg *Config, inbound bool) *Peer {
    // Default to the max supported protocol version if not specified by the
    // caller.
    cfg := *origCfg // Copy to avoid mutating caller.

    ......

    p := Peer{
        inbound:         inbound,
        knownInventory:  newMruInventoryMap(maxKnownInventory),
        stallControl:    make(chan stallControlMsg, 1), // nonblocking sync
        outputQueue:     make(chan outMsg, outputBufferSize),
        sendQueue:       make(chan outMsg, 1),   // nonblocking sync
        sendDoneQueue:   make(chan struct{}, 1), // nonblocking sync
        outputInvChan:   make(chan *wire.InvVect, outputBufferSize),
        inQuit:          make(chan struct{}),
        queueQuit:       make(chan struct{}),
        outQuit:         make(chan struct{}),
        quit:            make(chan struct{}),
        cfg:             cfg, // Copy so caller can't mutate.
        services:        cfg.Services,
        protocolVersion: cfg.ProtocolVersion,
    }
    return &p
}

從上面創(chuàng)建Peer的代碼中可以看出,Peer中的關(guān)鍵字段包括:

  • inbound: 用于指示Peer是inbound還是outbound辆布。如果當(dāng)前節(jié)點主動連接Peer瞬矩,則Peer為OutbandPeer;如果Peer主動連接當(dāng)前節(jié)點锋玲,則Peer為InboundPeer;
  • knownInventory: 已經(jīng)發(fā)送給Peer的Inventory的緩存;
  • stallControl: 帶緩沖的stallControlMsg chan景用,在收、發(fā)消息的goroutine和超時控制goroutine之間通信惭蹂,后面會進一步介紹;
  • outputQueue: 帶緩沖的outMsg chan伞插,實現(xiàn)了一個發(fā)送隊列;
  • sendQueue: 緩沖大小為1的outMsg chan,用于將outputQueue中的outMsg按加入發(fā)送隊列的順序發(fā)送給Peer;
  • sendDoneQueue: 帶緩沖的channel盾碗,用于通知維護發(fā)送隊列的goroutine上一個消息已經(jīng)發(fā)送完成媚污,應(yīng)該取下一條消息發(fā)送;
  • outputInvChan: 實現(xiàn)發(fā)送inv消息的發(fā)送隊列,該隊列以10s為周期向Peer發(fā)送inv消息;
  • inQuit: 用于通知收消息的goroutine已經(jīng)退出;
  • outQuit: 用于通知發(fā)消息的goroutine已經(jīng)退出廷雅,當(dāng)收耗美、發(fā)消息的goroutine均退出時京髓,超時控制goroutine也將退出;
  • quit:用于通知所有處理事務(wù)的goroutine退出;
  • cfg: 與Peer相關(guān)的Config,其中比較重要是的Config中的MessageListeners商架,指明了處理從Peer收到的消息的響應(yīng)函數(shù);
  • services: 用于記錄Peer支持的服務(wù)堰怨,如: SFNodeNetwork表明Peer是一個全節(jié)點,SFNodeGetUTXO表明Peer支持getutxos和utxos命令蛇摸,SFNodeBloom表明Peer支持Bloom過濾;
  • protocolVersion:用于記錄Peer所用的協(xié)議版本;

上述有些字段與Peer實現(xiàn)的消息收發(fā)機制有關(guān)系备图,讀者可能會有些疑惑,我們將在下面詳細介紹赶袄。同時揽涮,這里涉及到了Go中的channelgoroutine,它們是golang簡化并發(fā)編程的關(guān)鍵工具弃鸦,剛開始接觸Go的讀者可能覺得不好理解绞吁,也會對理解Peer的代碼造成一些障礙幢痘。由于篇幅原因唬格,本文不深入介紹Go的并發(fā)編程,讀者可以翻閱相關(guān)書籍颜说。為了方便大家理解代碼购岗,我們引用golang官方對它的并發(fā)機制的總結(jié)作一個啟示:

Do not communicate by sharing memory; instead, share memory by communicating.

channel就是用來在goroutine之間進行通信的數(shù)據(jù)類型。接下來门粪,我們來看Peer的start()方法以進一步了解它的實現(xiàn):

// start begins processing input and output messages.
func (p *Peer) start() error {
    log.Tracef("Starting peer %s", p)

    negotiateErr := make(chan error)
    go func() {
        if p.inbound {
            negotiateErr <- p.negotiateInboundProtocol()
        } else {
            negotiateErr <- p.negotiateOutboundProtocol()
        }
    }()

    // Negotiate the protocol within the specified negotiateTimeout.
    select {
    case err := <-negotiateErr:
        if err != nil {
            return err
        }
    case <-time.After(negotiateTimeout):
        return errors.New("protocol negotiation timeout")
    }
    log.Debugf("Connected to %s", p.Addr())

    // The protocol has been negotiated successfully so start processing input
    // and output messages.
    go p.stallHandler()
    go p.inHandler()
    go p.queueHandler()
    go p.outHandler()
    go p.pingHandler()

    // Send our verack message now that the IO processing machinery has started.
    p.QueueMessage(wire.NewMsgVerAck(), nil)
    return nil
}

上述代碼主要包含:

  1. 起了一個goroutine來與Peer交換Version消息喊积,調(diào)用goroutine與新的goroutine通過negotiateErr channel同步,調(diào)用goroutine阻塞等待Version握手完成;
  2. 如果Version握手失敗或者超時玄妈,則返回錯誤乾吻,Peer關(guān)系建立失敗;
  3. 如果握手成功,則啟動5個新的goroutine來收發(fā)消息拟蜻。其中绎签,stallHandler()用于處理消息超時,inHandler()用于接收Peer消息酝锅,queueHandler()用于維護消息發(fā)送列隊诡必,outHandler用于向Peer發(fā)送消息,pingHandler()用于向Peer周期性地發(fā)送心跳搔扁;
  4. 最后爸舒,向Peer發(fā)送verack消息,雙方完成握手稿蹲。

Peer start()成功后扭勉,節(jié)點間的Peer關(guān)系便成功建立,可以進一步交換其他協(xié)議消息了苛聘,如果節(jié)點與不同的其他節(jié)點建立了Peer關(guān)系剖效,其他節(jié)點又與新的節(jié)點建立Peer關(guān)系嫉入,所有節(jié)點會逐漸形成一張P2P網(wǎng)絡(luò)。然而璧尸,節(jié)點在交換Version時從對方獲取了哪些信息咒林,為什么要等到Version交換完成后Peer關(guān)系才能正常建立?各個Handler又是如何實現(xiàn)收發(fā)消息的呢爷光?我們將在下一篇文章《Btcd區(qū)塊在P2P網(wǎng)絡(luò)上的傳播之Peer》中詳細介紹垫竞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蛀序,隨后出現(xiàn)的幾起案子欢瞪,更是在濱河造成了極大的恐慌,老刑警劉巖徐裸,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遣鼓,死亡現(xiàn)場離奇詭異,居然都是意外死亡重贺,警方通過查閱死者的電腦和手機骑祟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來气笙,“玉大人次企,你說我怎么就攤上這事∏逼裕” “怎么了缸棵?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谭期。 經(jīng)常有香客問我堵第,道長,這世上最難降的妖魔是什么隧出? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任踏志,我火速辦了婚禮,結(jié)果婚禮上鸳劳,老公的妹妹穿的比我還像新娘狰贯。我一直安慰自己,他們只是感情好赏廓,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布涵紊。 她就那樣靜靜地躺著,像睡著了一般幔摸。 火紅的嫁衣襯著肌膚如雪摸柄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天既忆,我揣著相機與錄音驱负,去河邊找鬼嗦玖。 笑死,一個胖子當(dāng)著我的面吹牛跃脊,可吹牛的內(nèi)容都是我干的宇挫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酪术,長吁一口氣:“原來是場噩夢啊……” “哼器瘪!你這毒婦竟也來了池凄?” 一聲冷哼從身側(cè)響起面哼,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揭朝,沒想到半個月后庐舟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欣除,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年挪略,在試婚紗的時候發(fā)現(xiàn)自己被綠了历帚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘟檩,死狀恐怖抹缕,靈堂內(nèi)的尸體忽然破棺而出澈蟆,到底是詐尸還是另有隱情墨辛,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布趴俘,位于F島的核電站睹簇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏寥闪。R本人自食惡果不足惜太惠,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疲憋。 院中可真熱鬧凿渊,春花似錦、人聲如沸缚柳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秋忙。三九已至彩掐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灰追,已是汗流浹背堵幽。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工狗超, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朴下。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓努咐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親殴胧。 傳聞我的和親對象是個殘疾皇子麦撵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)溃肪,斷路器免胃,智...
    卡卡羅2017閱讀 134,660評論 18 139
  • 一、快速術(shù)語檢索 比特幣地址:(例如:1DSrfJdB2AnWaFNgSbv3MZC2m74996JafV)由一串...
    不如假如閱讀 15,973評論 4 87
  • 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial惫撰,后續(xù)如有更新都會在 GitH...
    liuchengxu閱讀 2,417評論 6 8
  • 姓名:沈丹萍 公司:寧波大發(fā)化纖有限公司 《六項精進》289期學(xué)員 組名:反省二組 【日精進打卡第8天】 【知~...
    好運到來閱讀 134評論 0 0
  • “何為孤寂羔沙?”“清風(fēng)、艷陽厨钻、無笑意” “可否具體扼雏?” “左擁、右抱夯膀、無情欲” “可否再具體诗充?” “不得你”
    小太陽在等花開閱讀 248評論 0 1