Bitcoin Core P2P網絡層


原文鏈接:http://www.lotushy.com/?p=115

數據結構

在任意給定時刻,一個節點總是連接到多個其他節點。默認情況下,一個節點連接到8個其他節點(鏈出),並允許多達125個鏈入節點連接進來。

相關常量定義在net.h文件中。

節點集合則由全局變量vNodes維護。

CNode用於表示一個節點。

CNode包含許多屬性,其中大部分屬性都與底層鏈路(如套接字,字節流等)有關。

CNode的關鍵屬性如下:

  • nServices: 通常被稱為“服務位”。這是一個bitmap數據結構,用於表示peer提供的服務種類。具體種類如下。

    • NODE_NONE: 初值
    • NODE_NETWORK: 全節點。SPV節點和其他類型的輕節點不設置該標識。
    • NODE_GETUTXO: 該節點能夠響應getutxo協議請求。Bitcoin Core節點不支持此功能。此功能由一個由Bitcoin XT支持。BIP64
    • NODE_BLOOM:
    • NODE_WITNESS: 表示可以向節點詢問塊和交易,包括見證數據。應該是只支持隔離見證。
    • NODE_XTHIN: 表示節點支持Xtreme Thinblocks
    • NODE_NETWORK_LIMITED: 表示節點僅支持最近的288年區塊(即兩天的數據)BIP159
  • fClient: 表示節點是否是SPV節點

  • fWhiteListed: 是否是白名單節點。如果是的話,該節點不會因為的行為被屏避掉。

  • vSendMsg: 隊列中待發送的消息隊列

  • vRecvMsg: 我們從Peer接收到的消息隊列

節點發現和節點連接

地址管理

地址管理器用於管理節點的IP地址和端口。(見addrman.h)

設計目標:

  • 將地址表保存在內存中,並將整個表異步轉儲到peers.dat。
  • 確保沒有(本地化的)攻擊者可以用它的節點/地址填充整個表格。

為此:

  • 地址以Bucket的方式組織,即地址被放到桶里。
    • 尚未嘗試過的地址進入1024個“new”桶。
    • 已知可訪問的節點地址進入256個“tried”桶。
      • 每個地址范圍隨機選擇8個桶
      • 根據完整地址從實際存儲桶中選擇實際存儲桶。
      • 當為一個完整的桶添加一個新的好地址時,一個隨機選擇的entry(偏向於最近嘗試過的)會被彈出,回到“新”桶。
    • 分組選擇基於密碼散列,使用隨機生成的256位密鑰,這不應該被攻擊者觀察到。
    • 多個索引用於提升性能。

** 時間戳 **

地址管理器也要持續跟蹤每個節點的最近活躍情況。時間戳僅在一個地址上更新,並且在時間戳超過20分鍾時保存到數據庫。 通過理解時間戳的作用,將更加清楚的理解為什么要保持時間戳。

節點發現

節點發現從鏈路層來講,就是發現一個節點的IP地址和端口號。有多種方式:

  • 地址數據庫,即peers.dat文件
    • 在節點啟動時讀入並加載到節點管理器中。該方法在節點首次運行時是不支持的。因為peers.dat文件還不存在。
  • 用戶指定(--addnode, --connect)
  • DNS種子(DNS seeding)
    • 僅當peers.dat文件為空時,DNS種子才會被使用。
    • 默認DNS種子有6個:seed.bitcoin.sipa.be, dnsseed.bluematt.me, dnsseed.bitcoin.dashjr.org, seed.bitcoinstats.com, seed.bitcoin.jonasschnelli.ch, seed.btc.petertodd.org
  • 硬編碼的種子
    • 如果DNS seeding失敗,客戶端使用硬編碼的種子。[chainparamsseeds.h] 這些地址僅用作最后的手段。理想情況是盡快遠離種子節點,以避免過載這些節點。DNS種子和硬編碼的種子的時間戳均為0,這樣可以避免此類地址被廣播到網絡中或響應getaddr
  • 從其他Peer獲取(getaddraddr消息)
    • 節點間通過getaddraddr交換IP地址信息。
    • 但是,“addr”消息也可能會不請自來,因為節點在以下情況時無償地發布地址:
      • 當節點轉發地址時
      • 周期性廣播節點自己的地址(每24小時)
      • 當連接建立時(響應version消息時帶回)
    • 節點發送getaddr消息的時機
      • 在響應version消息,並且節點自身的地址量小於1000時。
    • 當接收到addr時:
      • 發送消息的節點版本太老,並且我們已經有1000個節點地址了,該消息被忽略。
      • 如果節點是當前版本,並且嘗試向我們發送超過1000個節點地址,則該節點會被懲罰。
      • 如果該地址24小時內已經發現,並且當前時間戳超過60分種,則時間戳更新為60分種。
      • 如果該地址24小時內沒有出現過,而時間戳是24小時之前,則更新為24小時。
    • 當響應getaddr消息時:
      • 該節點計算出在過去3個小時內有多少個地址有時間戳。
      • 它發送這些地址,但如果有超過2500個地址,它隨機選擇2500。
      • 它清除我們認為遠程節點所擁有的地址列表,這將觸發發送到節點的刷新。 請參閱SendMessages。
    • 地址中繼:
      • 一旦新地址被添加
        • 地址的時間戳在10分鍾之內
        • addr消息包括10個地址或少一些
        • fGetAddr==false
        • 該地址是可路由的
        • 滿足以上條件,則新地址會被隨機的發送出去

節點連接

節點鏈接由線程ThreadOpenConnections管理。它負責選擇可用的地址,建立鏈接並在適當的時候釋放鏈接。

而inbound鏈接則由ThreadSocketHandler負責處理[ThreadMessageHandler]。

對於inbound鏈接,系統通過select執行IO操作。所以,其支持的inbound數量不會太多,目前,是125個。

插口(Sockets)和消息

Socket線程 (net.cpp)

消息線程

ProcessMessages (net_processing.cpp)

ProcessMessages是net_processing.cpp中的處理和驗證交易和區塊等相關代碼代碼的入口點,同時它也處理getaddr, addr等比特幣協議相關的消息。該方法主要完成消息的復制並調用ProcessMessage處理消息。

ProcessMessage基本上是一個大型的“開關”,它根據消息類型[NetMsgType]來采取行動。

消息類型列表如下[protocol.cpp]:

namespace NetMsgType {
const char *VERSION="version";
const char *VERACK="verack";
const char *ADDR="addr";
const char *INV="inv";
const char *GETDATA="getdata";
const char *MERKLEBLOCK="merkleblock";
const char *GETBLOCKS="getblocks";
const char *GETHEADERS="getheaders";
const char *TX="tx";
const char *HEADERS="headers";
const char *BLOCK="block";
const char *GETADDR="getaddr";
const char *MEMPOOL="mempool";
const char *PING="ping";
const char *PONG="pong";
const char *NOTFOUND="notfound";
const char *FILTERLOAD="filterload";
const char *FILTERADD="filteradd";
const char *FILTERCLEAR="filterclear";
const char *REJECT="reject";
const char *SENDHEADERS="sendheaders";
const char *FEEFILTER="feefilter";
const char *SENDCMPCT="sendcmpct";
const char *CMPCTBLOCK="cmpctblock";
const char *GETBLOCKTXN="getblocktxn";
const char *BLOCKTXN="blocktxn";
}

SendMessages (main.cpp)

SendMessages創建消息並在peer的vSendMsg隊列(雙端隊列或C ++中的“deque”)中對其進行排隊。vSendMsg對象基本上只是序列化的數據。

Locks

P2P層主要的鎖有:

  • cs_vNodes: 控制對CNode對象的訪問
  • cs_vSend: 控制對節點的發送緩存的訪問
  • cs_vRecvMsg: 控制對節點的接收緩存的訪問
  • cs_inventory

拒絕服務的防范措施

一旦發現異常行為的節點,則直接ban掉。

DoS預防框架在2011年引入。

具體見:https://github.com/bitcoin/bitcoin/pull/517

** 被禁節點 **

被禁節點存儲在setBanned中。setBanned是map類型的數據結構,定義為typedef std::map<CSubNet, CBanEntry> banmap_t

默認情況下,一個節點被禁止24小時,但可以使用-bantime選項進行配置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM