1. 首页 > 快讯

从零开始学习比特币开发(九):P2P 网络建立之消息处理中篇

P2P 网络的建立是在系统启动的第 12 步,最后时刻调用 方法开始的。

恭喜你越来越接近比特的核心了,在上篇中,我们主要讲解了比特币的消息处理线程,下面的,在下篇中,将以具体的比特币消息即比特币协义分析为主。针对比特币的协义,为了从逻辑上进行理解,我们并没有完全按照代码的顺序,而是按照某个具体的消息的 模式来进行分析。

下面的我们来看比特币协义相关的代码。

节点作为服务器,处理客户端节点发送的版本请求。

版本消息是每个对等节点都要发送的消息,并且是最先发送、只能发送一次的消息,对等节点双方都要发送这个消息和下面的的确认消息,只有双方都发送过版本消息,并且收到确认消息,对等节点间才可以进行后续消息发送。

代码在 文件中的 方法的 1621 行。具体处理如下:

  • 检查对等节点的版本字段是否已经设置,如果已经设置,即远程对等节点已经发送过版本消息,那么:在开启 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; }

    方法进行处理,具体处理如下:

    • 检查是否要增加节点的不良积分,如果不是,即增加的积分数量为 0,则直接退出。
    • 取得节点的状态对象。如果不存在,则直接退出。
    • 把节点的状态对象的不良积分加上要增加的不良积分。
    • 比较节点的不良积分与默认的或用户通过 参数指定的不良积分进行比较。如果在增加当前不良积分后大于等于设置的不良积分,并且增加之前小于设置的不良积分,那么设置状态对象为应该禁止,即设置 属性为真。

    这个方法的代码如下:

    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 ", __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 ", __func__, state->name, pnode, state->nMisbehavior-howmuch, state->nMisbehavior, message_prefixed); }
  • 从输入流中取得远程对等节点发送的版本信息、支持的服务信息、发送时间、显示地址。 vRecv >> nVersion >> nServiceInt >> nTime >> addrMe; nSendVersion=std::min(nVersion, PROTOCOL_VERSION); nServices=ServiceFlags(nServiceInt);
  • 如果对等节点是出站的,调用 对象的 方法,设置对等节点所支持的服务。 if (!pfrom->fInbound) { ? ? connman->SetServices(pfrom->addr, nServices); }

    方法内部最终会获取节点的地址信息对象,然后设置其支持的服务属性。

  • 如果对等节点是出站的,且不是临时的试探者,且不是手动指定的,且与本地服务不匹配,那么:在开启 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", 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; }
  • 如果发送的版本的小于协义规定的最小版本 ,那么:在开启 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", 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; }
  • 如果输入流不为空,则从流中依次取得 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;
  • 如果对等节点节点是入站节点,且连接到自身,那么设置远程对待节点为断开,并返回真。 if (pfrom->fInbound && !connman->CheckIncomingNonce(nNonce)) { ? ? LogPrintf("connected to self at %s, disconnecting", pfrom->addr.ToString()); ? ? pfrom->fDisconnect=true; ? ? return true; }
  • 如果对等节点是入站节点,且其地址是可路由的,那么调用 方法进行处理。 if (pfrom->fInbound && addrMe.IsRoutable()) { ? ? SeenLocal(addrMe); }

    在 方法中,如果这个地址在 集合中存在,那么设置其对应的 对象的 加1。如晨不存在,则直接返回真。

    bool SeenLocal(const CService& addr) { ? ? { ? ? ? ? LOCK(cs_mapLocalHost); ? ? ? ? if (mapLocalHost.count(addr)==0) ? ? ? ? ? ? return false; ? ? ? ? mapLocalHost[addr].nScore++; ? ? } ? ? return true; }
  • 如果是对等节点是入站节点,则调用 方法,发送自身的版本信息给远程对等节点。节点在收到远程对待节点发送来的版本消息,并且经过检查没问题之后,自身发送一个版本消息给对远程对待节点。
  • 调用 对象的 方法,发送版本确认包。因为当前的 消息,是别的节点请求我们的,当我们允许其连接时,发送版本确认包。注意,只有在双方都发送版本确认包之后,双方才可以互相发送消息。
  • 设置对等节点的服务属性、保存地址、对等节点运行的客户端、对等节点区块链的高度、版本等。如果对等节点隔离见证服务,则设置对等节点对应的状态对象的相关属性为真。 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;
  • 调用 方法,将对等节点设为可能的首先下载节点。如果节点是出站的或者在白名单中,并且可以提供区块服务,并且 属性为假,即可作为首选下载节点。
  • 如果对等节点不是入站节点,进行如下处理。
    • 如果对等节点不是孤立的,并且不需要进行IBD下载(调用 函数进行判断,通常第一次启动或在常时间离线,比如24小时,有区块需要下载时,本方法返回真),那么:
      • 调用 方法,获取对该对等节点来说是最佳的地址。
      • 如果找到的地址是可路由的,那么调用对等节点的 方法,把找到的地址保存在 集合中。
      • 否则,调用 测试远程对等节点看到的我们的外部IP是否可以路由。如果可以路由,那么调用对等节点的 方法,把地址保存在 集合中。

      上面这些代码如下:

      if (fListen && !IsInitialBlockDownload()) { ? CAddress addr=GetLocalAddress(&pfrom->addr, pfrom->GetLocalServices()); ? FastRandomContext insecure_rand; ? if (addr.IsRoutable()) ? { ? ? ? LogPrint(BCLog::NET, "ProcessMessages: advertising address %s ", addr.ToString()); ? ? ? pfrom->PushAddress(addr, insecure_rand); ? } else if (IsPeerAddrLocalGood(pfrom)) { ? ? ? addr.SetIP(addrMe); ? ? ? LogPrint(BCLog::NET, "ProcessMessages: advertising address %s ", addr.ToString()); ? ? ? pfrom->PushAddress(addr, insecure_rand); ? } }
    • 如果需要,比如:本地保存的远程地址少于 1000个,那么调用 方法,请求远程节点发送更多的地址,即发送 消息。然后把请求地址的标志设置为真。 if (pfrom->fOneShot || pfrom->nVersion >=CADDR_TIME_VERSION || connman->GetAddressCount() < 1000) { ? connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR)); ? pfrom->fGetAddr=true; }
    • 调用 方法,保存远程对等节点,表明它是可访问的。 connman->MarkAddressGood(pfrom->addr);
  • 如果远程对等节点的版本小于 70012,则发送一个 消息。 if (pfrom->nVersion <=70012) { ? ? CDataStream finalAlert(ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01ffffff7f00000000ffffff7f00ffffff7f002f555247454e543a20416c657274206b657920636f6d70726f6d697365642c2075706772616465207265717569726564004630440220653febd6410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3abd5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fecaae66ecf689bf71b50"), SER_NETWORK, PROTOCOL_VERSION); ? ? connman->PushMessage(pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert)); }
  • 如果节点是临时引导节点,则断开节点,即设置节点的断开属性为真。 if (pfrom->fFeeler) { ? ? assert(pfrom->fInbound==false); ? ? pfrom->fDisconnect=true; }
  • 版本消息处理完成,返回真。
  • 节点作为客户端,处理服务器节点发送的版本响应消息,即版本确认消息。

    代码在 文件中的 方法的 1805 行。具体处理过程如下:

  • 设置接收到的版本确认消息中的版本号。 pfrom->SetRecvVersion(std::min(pfrom->nVersion.load(), PROTOCOL_VERSION));
  • 如果对等节点不是入站节点,设置对等节点的状态对象的当前连接属性为真。 if (!pfrom->fInbound) { ? ? LOCK(cs_main); ? ? State(pfrom->GetId())->fCurrentlyConnected=true; }
  • 如果对等节点的版本大于支持使用区块头部来公告区块的最小版本(),那么:调用 方法发送 消息,通知远程对等节点我们更愿意通过 消息来接收新区块的公告,而不是 消息。

    这样以后当有新区块需要公告时,远程对等就会通过 消息把区块头部先发送给我们,当我们再次请求时才会发送完整的区块。

  • 如果对等节点的版本大于支持紧凑区块的最小版本(),那么分两种情况处理。
    • 第一种情况,如果同时支持闪电网络,那么给对等节点发送一个紧凑区块版本为 2 的 消息。
    • 第二种情况,如果不支持闪电网络,那么给对等节点发送一个紧凑区块版本为 1 的 消息。

    无论哪一种情况,远程对等节点以后都会向本节点发送紧凑区块。

    代码如下:

    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)); }
  • 设置对等节点完全成功连接的标志为真,然后返回真。 pfrom->fSuccessfullyConnected=true; return true;
  • 只有在对等节点双方都各自发送版本消息和确认消息之后,双方才真正建立起连接关系,才可以进行后续的交互,比如请求数据消息等。

    因为在比特币网络中,任何一个节点都可以随时加入网络,也可以随时离开网络,所以两个连接的节点需要定时互相发送 和 来确保接点可以连接,如果在特定的时间内没有 消息,节点即可认为连接已经断开。

    这个消息比较简单,不作具体友们,代码如下:

    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; }

    这个消息也比较简单,不作具体友们,代码如下:

    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; }

    本文采摘于网络,不代表本站立场,转载联系作者并注明出处:http://www.fjxmta.com/kuaixun/46247.html

    联系我们

    在线咨询:点击这里给我发消息

    微信号:wx123456