1. 程式人生 > >從零開始學習比特幣開發(九)--P2P 網路建立之訊息處理中篇

從零開始學習比特幣開發(九)--P2P 網路建立之訊息處理中篇

P2P 網路的建立是在系統啟動的第 12 步,最後時刻呼叫 CConnman::Start 方法開始的。

恭喜你越來越接近比特幣的核心了,在上篇中,我們主要講解了比特幣的訊息處理執行緒,接下來,在下篇中,將以具體的比特幣訊息即比特幣協義分析為主。針對比特幣的協義,為了從邏輯上進行理解,我們並沒有完全按照程式碼的順序,而是按照某個具體的訊息的 請求----響應 模式來進行分析。

下面我們來看比特幣協義相關的程式碼。

1、節點握手處理

1.1、接收 version 訊息

節點作為伺服器,處理客戶端節點發送的版本請求。

版本訊息是每個對等節點都要傳送的訊息,並且是最先發送、只能傳送一次的訊息,對等節點雙方都要傳送這個訊息和下面的確認訊息,只有雙方都發送過版本訊息,並且收到確認訊息,對等節點間才可以進行後續訊息傳送。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 1621 行。具體處理如下:

  1. 檢查對等節點的版本欄位是否已經設定,如果已經設定,即遠端對等節點已經發送過版本訊息,那麼:在開啟 BIP 161 情況下發送拒絕訊息;對遠端對待節點進行處罰。

     if (pfrom->nVersion != 0)
     {
         if (enable_bip61) {
             connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_DUPLICATE, std::string("Duplicate version message")));
         }
         LOCK(cs_main);
         Misbehaving(pfrom->GetId(), 1);
         return false;
     }
    

    Misbehaving 方法進行處理,具體處理如下:

    • 檢查是否要增加節點的不良積分,如果不是,即增加的積分數量為 0,則直接退出。
    • 取得節點的狀態物件。如果不存在,則直接退出。
    • 把節點的狀態物件的不良積分加上要增加的不良積分。
    • 比較節點的不良積分與預設的或使用者通過 -banscore 引數指定的不良積分進行比較。如果在增加當前不良積分後大於等於設定的不良積分,並且增加之前小於設定的不良積分,那麼設定狀態物件為應該禁止,即設定 fShouldBan 屬性為真。

    這個方法的程式碼如下:

     void Misbehaving(NodeId pnode, int howmuch, const std::string& message) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
     {
         if (howmuch == 0)
             return;
       
         CNodeState *state = State(pnode);
         if (state == nullptr)
             return;
       
         state->nMisbehavior += howmuch;
         int banscore = gArgs.GetArg("-banscore", DEFAULT_BANSCORE_THRESHOLD);
         std::string message_prefixed = message.empty() ? "" : (": " + message);
         if (state->nMisbehavior >= banscore && state->nMisbehavior - howmuch < banscore)
         {
             LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d) BAN THRESHOLD EXCEEDED%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed);
             state->fShouldBan = true;
         } else
             LogPrint(BCLog::NET, "%s: %s peer=%d (%d -> %d)%s\n", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed);
     }
    
  2. 從輸入流中取得遠端對等節點發送的版本資訊、支援的服務資訊、傳送時間、顯示地址。

     vRecv >> nVersion >> nServiceInt >> nTime >> addrMe;
     nSendVersion = std::min(nVersion, PROTOCOL_VERSION);
     nServices = ServiceFlags(nServiceInt);
    
  3. 如果對等節點是出站的,呼叫 CConnman 物件的 SetServices 方法,設定對等節點所支援的服務。

     if (!pfrom->fInbound)
     {
         connman->SetServices(pfrom->addr, nServices);
     }
    

    方法內部最終會獲取節點的地址資訊物件,然後設定其支援的服務屬性。

  4. 如果對等節點是出站的,且不是臨時的試探者,且不是手動指定的,且與本地服務不匹配,那麼:在開啟 BIP 161 情況下發送拒絕訊息;然後設定遠端對待節點為斷開;然後返回假。

     if (!pfrom->fInbound && !pfrom->fFeeler && !pfrom->m_manual_connection && !HasAllDesirableServiceFlags(nServices))
     {
         LogPrint(BCLog::NET, "peer=%d does not offer the expected services (%08x offered, %08x expected); disconnecting\n", pfrom->GetId(), nServices, GetDesirableServiceFlags(nServices));
         if (enable_bip61) {
             connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_NONSTANDARD,strprintf("Expected to offer services %08x", GetDesirableServiceFlags(nServices))));
         }
         pfrom->fDisconnect = true;
         return false;
     }
    
  5. 如果傳送的版本的小於協義規定的最小版本 MIN_PEER_PROTO_VERSION,那麼:在開啟 BIP 161 情況下發送拒絕訊息;然後設定遠端對待節點為斷開;然後返回假。

     if (nVersion < MIN_PEER_PROTO_VERSION) {
         // disconnect from peers older than this proto version
         LogPrint(BCLog::NET, "peer=%d using obsolete version %i; disconnecting\n", pfrom->GetId(), nVersion);
         if (enable_bip61) {
             connman->PushMessage(pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE,
                                strprintf("Version must be %d or greater", MIN_PEER_PROTO_VERSION)));
         }
         pfrom->fDisconnect = true;
         return false;
     }
    
  6. 如果輸入流不為空,則從流中依次取得 addrFrom、nNonce、strSubVer(客戶端字串)、nStartingHeight(客戶端區塊鏈的高度)、fRelay等資訊。

     if (!vRecv.empty())
         vRecv >> addrFrom >> nNonce;
     if (!vRecv.empty()) {
         vRecv >> LIMITED_STRING(strSubVer, MAX_SUBVERSION_LENGTH);
         cleanSubVer = SanitizeString(strSubVer);
     }
     if (!vRecv.empty()) {
         vRecv >> nStartingHeight;
     }
     if (!vRecv.empty())
         vRecv >> fRelay;
    
  7. 如果對等節點節點是入站節點,且連線到自身,那麼設定遠端對待節點為斷開,並返回真。

     if (pfrom->fInbound && !connman->CheckIncomingNonce(nNonce))
     {
         LogPrintf("connected to self at %s, disconnecting\n", pfrom->addr.ToString());
         pfrom->fDisconnect = true;
         return true;
     }
    
  8. 如果對等節點是入站節點,且其地址是可路由的,那麼呼叫 SeenLocal 方法進行處理。

     if (pfrom->fInbound && addrMe.IsRoutable())
     {
         SeenLocal(addrMe);
     }
    

    SeenLocal 方法中,如果這個地址在 mapLocalHost 集合中存在,那麼設定其對應的 LocalServiceInfo 物件的 nScore 加1。如晨不存在,則直接返回真。

     bool SeenLocal(const CService& addr)
     {
         {
             LOCK(cs_mapLocalHost);
             if (mapLocalHost.count(addr) == 0)
                 return false;
             mapLocalHost[addr].nScore++;
         }
         return true;
     }
    
  9. 如果是對等節點是入站節點,則呼叫 PushNodeVersion 方法,傳送自身的版本資訊給遠端對等節點

    節點在收到遠端對待節點發送來的版本訊息,並且經過檢查沒問題之後,自身傳送一個版本訊息給對遠端對待節點。

  10. 呼叫 CConnman 物件的 PushMessage 方法,傳送版本確認包

    因為當前的 version 訊息,是別的節點請求我們的,當我們允許其連線時,傳送版本確認包。注意,只有在雙方都發送版本確認包之後,雙方才可以互相傳送訊息。

  11. 設定對等節點的服務屬性、儲存地址、對等節點執行的客戶端、對等節點區塊鏈的高度、版本等。如果對等節點隔離見證服務,則設定對等節點對應的狀態物件的相關屬性為真。

     pfrom->nServices = nServices;
     pfrom->SetAddrLocal(addrMe);
     {
         LOCK(pfrom->cs_SubVer);
         pfrom->strSubVer = strSubVer;
         pfrom->cleanSubVer = cleanSubVer;
     }
     pfrom->nStartingHeight = nStartingHeight;
     pfrom->fClient = (!(nServices & NODE_NETWORK) && !(nServices & NODE_NETWORK_LIMITED));
     pfrom->m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED));
     pfrom->fRelayTxes = fRelay;
     pfrom->SetSendVersion(nSendVersion);
     pfrom->nVersion = nVersion;
    
  12. 呼叫 UpdatePreferredDownload 方法,將對等節點設為可能的首先下載節點。

    如果節點是出站的或者在白名單中,並且可以提供區塊服務,並且 fOneShot 屬性為假,即可作為首選下載節點。

  13. 如果對等節點不是入站節點,進行如下處理。

    • 如果對等節點不是孤立的,並且不需要進行IBD下載(呼叫 IsInitialBlockDownload 函式進行判斷,通常第一次啟動或在常時間離線,比如24小時,有大師區塊需要下載時,本方法返回真),那麼:

      • 呼叫 GetLocalAddress 方法,獲取對該對等節點來說是最佳的地址。
      • 如果找到的地址是可路由的,那麼呼叫對等節點的 PushAddress 方法,把找到的地址儲存在 vAddrToSend 集合中。
      • 否則,呼叫 IsPeerAddrLocalGood 測試遠端對等節點看到的我們的外部IP是否可以路由。如果可以路由,那麼呼叫對等節點的 PushAddress 方法,把地址儲存在 vAddrToSend 集合中。

      以上程式碼如下:

      if (fListen && !IsInitialBlockDownload())
      {
          CAddress addr = GetLocalAddress(&pfrom->addr, pfrom->GetLocalServices());
          FastRandomContext insecure_rand;
          if (addr.IsRoutable())
          {
              LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
              pfrom->PushAddress(addr, insecure_rand);
          } else if (IsPeerAddrLocalGood(pfrom)) {
              addr.SetIP(addrMe);
              LogPrint(BCLog::NET, "ProcessMessages: advertising address %s\n", addr.ToString());
              pfrom->PushAddress(addr, insecure_rand);
          }
      }
      
    • 如果需要,比如:本地儲存的遠端地址少於 1000個,那麼調PushMessage 方法,請求遠端節點發送更多的地址,即傳送getaddr 訊息。然後把請求地址的標誌設定為真。

      if (pfrom->fOneShot || pfrom->nVersion >= CADDR_TIME_VERSION || connman->GetAddressCount() < 1000)
      {
          connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR));
          pfrom->fGetAddr = true;
      }
      
    • 呼叫 MarkAddressGood 方法,儲存遠端對等節點,表明它是可訪問的。

      connman->MarkAddressGood(pfrom->addr);
      
  14. 如果遠端對等節點的版本小於 70012,則傳送一個 alert 訊息。

     if (pfrom->nVersion <= 70012) {
         CDataStream finalAlert(ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01ffffff7f00000000ffffff7f00ffffff7f002f555247454e543a20416c657274206b657920636f6d70726f6d697365642c2075706772616465207265717569726564004630440220653febd6410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3abd5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fecaae66ecf689bf71b50"), SER_NETWORK, PROTOCOL_VERSION);
         connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert));
     }
    
  15. 如果節點是臨時引導節點,則斷開節點,即設定節點的斷開屬性為真。

     if (pfrom->fFeeler) {
         assert(pfrom->fInbound == false);
         pfrom->fDisconnect = true;
     }
    
  16. 版本訊息處理完成,返回真。

1.2、接收 verack 訊息

節點作為客戶端,處理伺服器節點發送的版本響應訊息,即版本確認訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 1805 行。具體處理過程如下:

  1. 設定接收到的版本確認訊息中的版本號。

     pfrom->SetRecvVersion(std::min(pfrom->nVersion.load(), PROTOCOL_VERSION));
    
  2. 如果對等節點不是入站節點,設定對等節點的狀態物件的當前連線屬性為真。

     if (!pfrom->fInbound) {
         LOCK(cs_main);
         State(pfrom->GetId())->fCurrentlyConnected = true;
     }
    
  3. 如果對等節點的版本大於支援使用區塊頭部來公告區塊的最小版本(SENDHEADERS_VERSION = 70012),那麼:

    呼叫 PushMessage 方法傳送 sendheaders 訊息,通知遠端對等節點我們更願意通過 headers 訊息來接收新區塊的公告,而不是 inv 訊息

    這樣以後當有新區塊需要公告時,遠端對等就會通過 headers 訊息把區塊頭部先發送給我們,當我們再次請求時才會傳送完整的區塊。

  4. 如果對等節點的版本大於支援緊湊區塊的最小版本(SHORT_IDS_BLOCKS_VERSION = 70014),那麼分兩種情況處理。

    • 第一種情況,如果同時支援閃電網路,那麼給對等節點發送一個緊湊區塊版本為 2 的 sendcmpct 訊息
    • 第二種情況,如果不支援閃電網路,那麼給對等節點發送一個緊湊區塊版本為 1 的 sendcmpct 訊息

    無論哪一種情況,遠端對等節點以後都會向本節點發送緊湊區塊。

    程式碼如下:

    if (pfrom->nVersion >= SHORT_IDS_BLOCKS_VERSION) {
        bool fAnnounceUsingCMPCTBLOCK = false;
        uint64_t nCMPCTBLOCKVersion = 2;
        if (pfrom->GetLocalServices() & NODE_WITNESS)
            connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
        nCMPCTBLOCKVersion = 1;
        connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::SENDCMPCT, fAnnounceUsingCMPCTBLOCK, nCMPCTBLOCKVersion));
    }
    
  5. 設定對等節點完全成功連線的標誌為真,然後返回真。

     pfrom->fSuccessfullyConnected = true;
     return true;
    

只有在對等節點雙方都各自發送版本訊息和確認訊息之後,雙方才真正建立起連線關係,才可以進行後續的互動,比如請求資料訊息等。

2、保持連線的處理

因為在比特幣網路中,任何一個節點都可以隨時加入網路,也可以隨時離開網路,所以兩個連線的節點需要定時互相傳送 pingpong 來確保接點可以連線,如果在特定的時間內沒有 ping 訊息,節點即可認為連線已經斷開。

2.1、ping 訊息

這個訊息比較簡單,不作具體解釋,程式碼如下:

if (strCommand == NetMsgType::PING) {
    if (pfrom->nVersion > BIP0031_VERSION)
    {
        uint64_t nonce = 0;
        vRecv >> nonce;
        connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::PONG, nonce));
    }
    return true;
}

2.1、pong 訊息

這個訊息也比較簡單,不作具體解釋,程式碼如下:

if (strCommand == NetMsgType::PONG) {
    int64_t pingUsecEnd = nTimeReceived;
    uint64_t nonce = 0;
    size_t nAvail = vRecv.in_avail();
    bool bPingFinished = false;
    std::string sProblem;
    if (nAvail >= sizeof(nonce)) {
        vRecv >> nonce;
        if (pfrom->nPingNonceSent != 0) {
            if (nonce == pfrom->nPingNonceSent) {
                bPingFinished = true;
                int64_t pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart;
                if (pingUsecTime > 0) {
                    pfrom->nPingUsecTime = pingUsecTime;
                    pfrom->nMinPingUsecTime = std::min(pfrom->nMinPingUsecTime.load(), pingUsecTime);
                } else {
                    sProblem = "Timing mishap";
                }
            } else {
                sProblem = "Nonce mismatch";
                if (nonce == 0) {
                    bPingFinished = true;
                    sProblem = "Nonce zero";
                }
            }
        } else {
            sProblem = "Unsolicited pong without ping";
        }
    } else {
        bPingFinished = true;
        sProblem = "Short payload";
    }
    if (bPingFinished) {
        pfrom->nPingNonceSent = 0;
    }
    return true;
}

3、獲取更多地址的處理

如果對等節點需要更多地址時,會發送 getaddr 訊息請求遠端對等節點發送更多的地址,當遠端對等節點收到請求後,會通過傳送 addr 訊息傳遞更多的地址。

3.1、getaddr 訊息

節點作為伺服器,響應客戶端節點發送的請求地址訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 2728 行。具體處理如下:

  1. 如果對等節點不是入站節點,則忽略該請求,並返回。

     if (!pfrom->fInbound) {
         return true;
     }
    
  2. 如果對等節點已傳送過請求地址,即遠端對等節點重複請求地址,則忽略該請求,並返回。

     if (pfrom->fSentAddr) {
         return true;
     }
    
  3. 設定對等節點已傳送過請求地址標誌為真。清空對等節點的 vAddrToSend 集合。

     pfrom->fSentAddr = true;
     pfrom->vAddrToSend.clear();
    
  4. 呼叫 GetAddresses 方法,返回要傳送的地址。

    從地址管理器隨機找到 N 個地址,N不能大於最大值 2500,並且這些地址的狀態都比較好。

  5. 遍歷要傳送的節點,呼叫對等節點的 PushAddress 方法,把要傳送的地址儲存到 vAddrToSend 向量中。

    由執行緒進行定時傳送 addr 訊息

  6. 返回真。

3.2、addr 訊息

節點作為客戶端,響應伺服器節點返回的地址。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 1849 行。具體處理如下:

  1. 從輸入流中取得遠端對等節點發送的地址列表儲存到 vAddr 向量中。

     std::vector<CAddress> vAddr;
     vRecv >> vAddr;
    
  2. 如果遠端對等節點的版本小於 31402(在這種版本比較老的情況下,我們只在初始時接收 DNS 種子伺服器傳送的地址),並且本儲存的地址已經超過 1000,則直接返回真。

     if (pfrom->nVersion < CADDR_TIME_VERSION && connman->GetAddressCount() > 1000)
         return true;
    
  3. 如果遠端對等節點發送的地址超過 1000,呼叫 Misbehaving 方法,對遠端節點進行懲罰,可能導致其被禁止傳送。

     if (vAddr.size() > 1000)
     {
         LOCK(cs_main);
         Misbehaving(pfrom->GetId(), 20, strprintf("message addr size() = %u", vAddr.size()));
         return false;
     }
    
  4. 遍歷所有的地址列表,進行如下處理:

    • 如果執行緒被中止,則返回真。

    • 如果代表的節點不支援 NODE_NETWORKNODE_NETWORK_LIMITED 兩者之一的服務,則處理下一個。

      if (!MayHaveUsefulAddressDB(addr.nServices) && !HasAllDesirableServiceFlags(addr.nServices))
          continue;
      
    • 設定地址的時間屬性

      if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60)
          addr.nTime = nNow - 5 * 24 * 60 * 60;
      
    • 呼叫對等節點的 AddAddressKnown 方法,把當前地址儲存到已知地址 addrKnown 中。

    • 如果地址是可路由的,則加入 vAddrOk 列表中。

      if (fReachable)
          vAddrOk.push_back(addr);
      
  5. 呼叫 CConnman::AddNewAddresses 方法,儲存 vAddrOk 列表中的地址。

    AddNewAddresses 方法最終把地址列表儲存在 CAddrMan 物件的 mapInfo 屬性中。

  6. 如果傳送的地址數量少於 1000,設定對等節點的獲取地址標誌為假。以便以後再次獲取地址。如果對等節點的 fOneShot 屬性為真,則設定對等節點的斷開連線標誌為真。

     if (vAddr.size() < 1000)
         pfrom->fGetAddr = false;
     if (pfrom->fOneShot)
         pfrom->fDisconnect = true;
    
  7. 返回真。

4、sendheaders 訊息

節點作為伺服器,響應客戶端節點發送的更願意接收頭部而不是區塊體的設定訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 1899 行。這個訊息的處理比較簡單,只把節點對應的狀態物件的 fPreferHeaders 屬性為真。

程式碼如下:

if (strCommand == NetMsgType::SENDHEADERS) {
    LOCK(cs_main);
    State(pfrom->GetId())->fPreferHeaders = true;
    return true;
}

5、sendcmpct 訊息

節點作為伺服器,響應客戶端節點發送的接收緊湊區塊而不是普通區塊的設定訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 1905 行。

if (strCommand == NetMsgType::SENDCMPCT) { 為開始,具體如下:

  1. 從輸入流中取得 fAnnounceUsingCMPCTBLOCKnCMPCTBLOCKVersion 等引數。

     vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion;
    
  2. 如果緊湊區塊版本 nCMPCTBLOCKVersion 等於 1 ,或者節點可以響應包含隔離見證的區塊和交易請求,且 nCMPCTBLOCKVersion 等於2,那麼進行下面的處理。

     if (nCMPCTBLOCKVersion == 1 || ((pfrom->GetLocalServices() & NODE_WITNESS) && nCMPCTBLOCKVersion == 2)) {
         LOCK(cs_main);
         if (!State(pfrom->GetId())->fProvidesHeaderAndIDs) {
             State(pfrom->GetId())->fProvidesHeaderAndIDs = true;
             State(pfrom->GetId())->fWantsCmpctWitness = nCMPCTBLOCKVersion == 2;
         }
         if (State(pfrom->GetId())->fWantsCmpctWitness == (nCMPCTBLOCKVersion == 2)) // ignore later version announces
             State(pfrom->GetId())->fPreferHeaderAndIDs = fAnnounceUsingCMPCTBLOCK;
         if (!State(pfrom->GetId())->fSupportsDesiredCmpctVersion) {
             if (pfrom->GetLocalServices() & NODE_WITNESS)
                 State(pfrom->GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 2);
             else
                 State(pfrom->GetId())->fSupportsDesiredCmpctVersion = (nCMPCTBLOCKVersion == 1);
         }
     }
    
  3. 返回真。

6、feefilter 訊息

節點作為伺服器,響應客戶端節點發送的費率過濾設定訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 2911 行。

if (strCommand == NetMsgType::FEEFILTER) { 為開始,這個處理比較簡單,程式碼如下:

if (strCommand == NetMsgType::FEEFILTER) {
    CAmount newFeeFilter = 0;
    vRecv >> newFeeFilter;
    if (MoneyRange(newFeeFilter)) {
        {
            LOCK(pfrom->cs_feeFilter);
            pfrom->minFeeFilter = newFeeFilter;
        }
        LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->GetId());
    }
    return true;
}

其中 MoneyRange 方法檢查費率引數是否在 0 到 2100 萬比特幣之間。

7、filterload 訊息

節點作為伺服器,響應 SPV 節點發送的布隆過濾設定訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 2851 行。

if (strCommand == NetMsgType::FILTERLOAD) { 為開始,具體如下:

  1. 從輸入流中取得過濾器引數。

     CBloomFilter filter;
     vRecv >> filter;
    
  2. 呼叫布隆過濾器的 IsWithinSizeConstraints 方法,檢查過濾器的是否超過限制區間。如果超過,則呼叫 Misbehaving 方法,對遠端對等節點進行設定,可能導致其被禁止。如果不超過,則:

    • 生成一個新的 CBloomFilter 過濾器物件,並設定節點的過濾器屬性 pfilter 為新生成的物件。
    • 呼叫節點過濾器的 UpdateEmptyFull 方法,重置其內部屬性 vData
    • 設定中繼交易屬性 fRelayTxes 為真。

    以上程式碼如下:

    if (!filter.IsWithinSizeConstraints())
    {
        LOCK(cs_main);
        Misbehaving(pfrom->GetId(), 100);
    }
    else
    {
        LOCK(pfrom->cs_filter);
        pfrom->pfilter.reset(new CBloomFilter(filter));
        pfrom->pfilter->UpdateEmptyFull();
        pfrom->fRelayTxes = true;
    }
    
  3. 返回真。

8、filteradd 訊息

節點作為伺服器,響應 SPV 節點發送的增加布隆過濾訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 2871 行。

if (strCommand == NetMsgType::FILTERADD) { 為開始,具體如下:

  1. 從輸入流中取得要增加的過濾器。

     std::vector<unsigned char> vData;
     vRecv >> vData;
    
  2. 如果傳送的位元組數量大於 520,則設定變數 bad 為真。如果不大於,則進行下面的判斷。

    如果已經發送過 filterload 訊息,則把新的過濾器儲存到過濾器集合中。否則,設定變數 bad 為真。

    程式碼如下:

    bool bad = false;
    if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) {
        bad = true;
    } else {
        LOCK(pfrom->cs_filter);
        if (pfrom->pfilter) {
            pfrom->pfilter->insert(vData);
        } else {
            bad = true;
        }
    }
    
  3. 如果變數為真,呼叫 Misbehaving 方法,懲罰節點。

  4. 返回真。

9、filterclear

節點作為伺服器,響應 SPV 節點發送的增加布隆過濾訊息。

程式碼在 net_processing.cpp 檔案中的 ProcessMessage 方法的 2895 行。

if (strCommand == NetMsgType::FILTERCLEAR) { 為開始,這個訊息處理比較簡單,程式碼如下,可以自己理解。

if (strCommand == NetMsgType::FILTERCLEAR) {
    LOCK(pfrom->cs_filter);
    if (pfrom->GetLocalServices() & NODE_BLOOM) {
        pfrom->pfilter.reset(new CBloomFilter());
    }
    pfrom->fRelayTxes = true;
    return true;
}

我是區小白,Ulord全球社群聯盟(優得社群)核心區塊鏈技術開發者,深入研究比特幣,以太坊,EOS Dash,Rsk,Java, Nodejs,PHP,Python,C++ 我希望能聚集更多區塊鏈開發者,一起學習共同進步。p