请选择 进入手机版 | 继续访问电脑版
开启辅助访问
链路首页链路财经目前收录 币种 : 4908 交易所 : 310钱包 : 16 24H 交易量 : $11,304,958,159 总市值 : $226,427,114,804
2018
11/10
22:13
分享
评论
  • P2P网络建立之消息处理4

    BLOCK COMMUNITY



    11、IBD 时,区块优先节点获取区块消息

    在 IBD 期间,节点需要下载大量的区块来使本节点的区块链达到最佳高度。这又分为区块优先和头部优先两种情况,前面讲了头部优先下载,这是当前的主流方式。下面简要讲解下区块优先方式,这是老的客户端工作方式。

    11.1、getblocks 消息

    作为服务器,响应客户端节点发送的请求区块消息。


    代码在 net_processing.cpp 文件中的 ProcessMessage 方法的 2006 行。

    以 if (strCommand == NetMsgType::GETBLOCKS) { 为标志,具体如下:

    1. 从输入流中取得区块定位器和结束区块的哈希值。

      CBlockLocator locator; uint256 hashStop; vRecv >> locator >> hashStop;
    2. 如果客户端请求的定位器 vHave 数量超过规定的最大值,则设置客户端节点断开,并返回真。

      if (locator.vHave.size() > MAX_LOCATOR_SZ) { pfrom->fDisconnect = true; return true; }
    3. 调用 ActivateBestChain 方法,激活最佳的区块链。

    4. 调用 FindForkInGlobalIndex 方法,查找主链上客户端请求的最后一个区块的哈希值。

      const CBlockIndex* pindex = FindForkInGlobalIndex(chainActive, locator);

      这个方法的实现比较简单:

      CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) { AssertLockHeld(cs_main); for (const uint256& hash : locator.vHave) { CBlockIndex* pindex = LookupBlockIndex(hash); if (pindex) { if (chain.Contains(pindex)) return pindex; if (pindex->GetAncestor(chain.Height()) == chain.Tip()) { return chain.Tip(); } } } return chain.Genesis(); }
    5. 如果需要的最后一个区块存在,那么找到它的下一个区块。if (pindex) pindex = chainActive.Next(pindex);

    6. 进行 for 循环,直到链的最顶端,或达到指定的结束区块。具体处理如下:以上逻辑的代码如下:

      int nLimit = 500; for (; pindex; pindex = chainActive.Next(pindex)) { if (pindex->GetBlockHash() == hashStop) { LogPrint(BCLog::NET, " getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString()); break; } const int nPrunedBlocksLikelyToHave = MIN_BLOCKS_TO_KEEP - 3600 / chainparams.GetConsensus().nPowTargetSpacing; if (fPruneMode && (!(pindex->nStatus & BLOCK_HAVE_DATA) || pindex->nHeight <= chainActive.Tip()->nHeight - nPrunedBlocksLikelyToHave)) { break; } pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash())); if (--nLimit <= 0) { pfrom->hashContinue = pindex->GetBlockHash(); break; } }
      1. 如果当前区块就是要停止的区块,则退出循环。

      2. 如果处于修剪模式下,并且当前区块索引不在硬盘上或者当前区块索引代码的区块的高度与区块链顶部区块的高度之差大于根据规定计算出来的值,则退出循环。

      3. 根据当前的区块索引生成库存对象,并保存到节点的 vInventoryBlockToSend 集合中。

      4. 如果达到当前最大的区块数量(当前500),则设置节点的继续位置为当前区块的哈希值。

    7. 返回真。

    注:inv 消息由后台线程定时发送。

    12、紧凑区块中的交易消息处理

    12.1、getblocktxn 消息

    代码在 net_processing.cpp 文件中的 ProcessMessage 方法的 2074 行。


    以 if (strCommand == NetMsgType::GETBLOCKTXN) { 为标志,具体如下:

    1. 从输入流中取得请求对象。

      BlockTransactionsRequest req; vRecv >> req;
    2. 如果请求的交易所在的区块就是最近的区块,则设置变量 recent_block。如果变量不空,则调用 SendBlockTransactions 方法,发送区块的交易,然后返回真。

      std::shared_ptr recent_block; { LOCK(cs_most_recent_block); if (most_recent_block_hash == req.blockhash) recent_block = most_recent_block; } if (recent_block) { SendBlockTransactions(*recent_block, req, pfrom, connman); return true; }
    `SendBlockTransactions` 方法中构造一个响应对象,把区块中的所有交易加入到响应对象的 `txn` 集合中,然后调用 `PushMessage` 方法,发送 `blocktxn` 消息。
    1. 调用 LookupBlockIndex 方法,查找客户端请求的区块索引。如果不存在请求的索引,或者索引对应的区块数据不在硬盘上,则直接返回。

      const CBlockIndex* pindex = LookupBlockIndex(req.blockhash); if (!pindex || !(pindex->nStatus & BLOCK_HAVE_DATA)) { return true; }
    2. 如果请求区块的高度与区块链顶部的高度之差大于规定的最大深度 10 (MAX_BLOCKTXN_DEPTH),则生成一个库存对象,并加入节点的 vRecvGetData 集合中,然后返回真。vRecvGetData 集合中的数据由定时器定时处理。

      if (pindex->nHeight < chainActive.Height() - MAX_BLOCKTXN_DEPTH) { CInv inv; inv.type = State(pfrom->GetId())->fWantsCmpctWitness ? MSG_WITNESS_BLOCK : MSG_BLOCK; inv.hash = req.blockhash; pfrom->vRecvGetData.push_back(inv); return true; }
    3. 以上情况都不是,则从硬盘中加载区块,并进行发送。

      CBlock block; bool ret = ReadBlockFromDisk(block, pindex, chainparams.GetConsensus()); SendBlockTransactions(block, req, pfrom, connman);
    4. 返回真。

    12.2、blocktxn 消息

    作为客户端,处理服务器节点发送的区块中的交易。


    代码在 net_processing.cpp 文件中的 ProcessMessage 方法的 2598 行。

    以 if (strCommand == NetMsgType::BLOCKTXN && !fImporting && !fReindex) 为标志,具体如下:

    1. 从输入流中取得服务器发送的交易。

      BlockTransactions resp; vRecv >> resp;
    2. 如果接收到的交易所在的区块不存在于 mapBlocksInFlight 集合中,或者不是当前节点,则直接退出,并返回真。

      std::shared_ptr pblock = std::make_shared(); bool fBlockRead = false;
    3. 从部分下载的区块中填充区块对象。如果服务器发送的区块是无效的,那么调用 Misbehaving 方法,惩罚远程节点;否则,如果不能处理区块数据,则生成一个库存对象,然后调用 PushMessage 消息,重新发送 getdata 消息;否则,即发送的区块是OK的,则调用 MarkBlockAsReceived 方法,设置节点状态对象的相关属性,同时把接收到的区块相关内容保存在 mapBlockSource 集合中。

      PartiallyDownloadedBlock& partialBlock = *it->second.second->partialBlock; ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn); if (status == READ_STATUS_INVALID) { MarkBlockAsReceived(resp.blockhash); // Reset in-flight state in case of whitelist Misbehaving(pfrom->GetId(), 100, strprintf("Peer %d sent us invalid compact block/non-matching block transactions\n", pfrom->GetId())); return true; } else if (status == READ_STATUS_FAILED) { std::vector invs; invs.push_back(CInv(MSG_BLOCK | GetFetchFlags(pfrom), resp.blockhash)); connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETDATA, invs)); } else { MarkBlockAsReceived(resp.blockhash); // it is now an empty pointer fBlockRead = true; mapBlockSource.emplace(resp.blockhash, std::make_pair(pfrom->GetId(), false)); }
    4. 如果区块数据可以读取,那么调用 ProcessNewBlock 方法,处理收到的区块。如果这个区块是新的,则修改节点的最后一次接收到区块的时间,否则从 mapBlockSource 集合中,移除区块对应的数据。

      if (fBlockRead) { bool fNewBlock = false; ProcessNewBlock(chainparams, pblock, /*fForceProcessing=*/true, &fNewBlock); if (fNewBlock) { pfrom->nLastBlockTime = GetTime(); } else { LOCK(cs_main); mapBlockSource.erase(pblock->GetHash()); } }
    5. 返回真。

    13、mempool 消息

    作为服务器,处理客户端节点发送的设置内存池消息。


    代码在 net_processing.cpp 文件中的 ProcessMessage 方法的 2598 行。

    以 if (strCommand == NetMsgType::MEMPOOL) { 为标志,具体如下:

    1. 如果节点没有能力也没有意愿启用 bloom-filtered 连接,并且节点也不在白名单中,则断开节点,并返回真。

      if (!(pfrom->GetLocalServices() & NODE_BLOOM) && !pfrom->fWhitelisted) { pfrom->fDisconnect = true; return true; }
    2. 如果内存池请求达到限额,并且节点也不在白名单中,则断开节点,并返回真。

      if (connman->OutboundTargetReached(false) && !pfrom->fWhitelisted) { pfrom->fDisconnect = true; return true; }
    3. 设置节点可以发送内存池消息,并返回真。

      pfrom->fSendMempool = true; return true;




    ·END·
     

    区块公社

    欢迎加入开发者社区

    与技术大咖一起成长

主题帖 52 关注 0 粉丝 0
情感指数

链路大数据分析置信度 18.6 %

TA的主题帖
主题相关
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表