Skip to content

Improve chain inventory generating logic #5376

@jwrct

Description

@jwrct

Rationale

After completing the handshake with the peer, if it is found that the peer's blockchain is longer than the local one, according to the principle of the longest chain, the block synchronization process will be triggered. The message interaction during the synchronization process is shown in the following figure.
image
After receiving the SyncBlockChainMessage packet from node A, node B calculates the missing blocks of node A and sends the list of missing block IDs to node A through the ChainInventoryMessage packet. The code logic for generating the list is as follows:

private LinkedList<BlockId> getLostBlockIds(List<BlockId> blockIds) throws P2pException {
  BlockId unForkId = null;
  for (int i = blockIds.size() - 1; i >= 0; i--) {
    if (tronNetDelegate.containBlockInMainChain(blockIds.get(i))) {
      unForkId = blockIds.get(i);
      break;
    }
  }
  
  if (unForkId == null) {
    throw new P2pException(TypeEnum.SYNC_FAILED, "unForkId is null");
  }
  
  BlockId headID = tronNetDelegate.getHeadBlockId();
  long headNum = headID.getNum();
  
  long len = Math.min(headNum, unForkId.getNum() + NetConstants.SYNC_FETCH_BATCH_NUM);
  
  LinkedList<BlockId> ids = new LinkedList<>();
  for (long i = unForkId.getNum(); i <= len; i++) {
    if (i == headNum) {
      ids.add(headID);
    } else {
      BlockId id = tronNetDelegate.getBlockIdByNum(i);
      ids.add(id);
    }
  }
  return ids;
}

From the above code, we can see that the maximum common block height is determined and found from the chain summary by node B according to SyncBlockChainMessage sent by node A. And then node B obtains blockIds from the common block heights and adds them to the inventory, with the height increasing by 1 each time, up to 2000. If node B switches the chain during the inventory generation process, it may incur the generated inventory not matching the chain summary since the maximum common block height might change with the chain switching, causing the first blockId in the inventory may not be in the chain summary.

When node A handles inventory message, it includes the following validation logic:

if (!peer.getSyncChainRequested().getKey().contains(blockIds.get(0))) {
      throw new P2pException(TypeEnum.BAD_MESSAGE, "unlinked block, my head: "
          + peer.getSyncChainRequested().getKey().getLast().getString()
          + ", peer: " + blockIds.get(0).getString());
  }

When verifying the inventory message, node A will check if the first blockId in the inventory is included in the sent chain summary. Otherwise, an 'unlinked block' exception will be reported. Although the occurrence of concurrent scenarios of switching forked chains and generating synchronized inventory is very small in the production environment, it is still necessary to optimize the handling of such scenarios.

Implementation

In order not to affect the performance of switching forked chains, it is not possible to solve this problem by adding synchronization control during the chain cutting and inventory generation process. It can be optimized by retrying in the inventory generation process, that is, after generating the inventory, check if the first blockId in the inventory is in the chain summary. If it is not, retry generating the inventory.

Are you willing to implement this feature?
yes

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions