-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(net): correct sync completion and chain summary request timeouts #7000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release_v4.8.3
Are you sure you want to change the base?
Changes from all commits
eb67cb2
8ff4838
c208141
8b9b11c
b578da1
4e46755
541988e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| import org.apache.commons.collections4.CollectionUtils; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Component; | ||
| import org.tron.common.utils.Pair; | ||
| import org.tron.core.capsule.BlockCapsule.BlockId; | ||
| import org.tron.core.config.Parameter.ChainConstant; | ||
| import org.tron.core.config.Parameter.NetConstants; | ||
|
|
@@ -18,6 +19,7 @@ | |
| import org.tron.core.exception.P2pException.TypeEnum; | ||
| import org.tron.core.net.TronNetDelegate; | ||
| import org.tron.core.net.message.TronMessage; | ||
| import org.tron.core.net.message.handshake.HelloMessage; | ||
| import org.tron.core.net.message.sync.ChainInventoryMessage; | ||
| import org.tron.core.net.peer.PeerConnection; | ||
| import org.tron.core.net.peer.TronState; | ||
|
|
@@ -40,7 +42,8 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep | |
|
|
||
| ChainInventoryMessage chainInventoryMessage = (ChainInventoryMessage) msg; | ||
|
|
||
| check(peer, chainInventoryMessage); | ||
| Pair<Deque<BlockId>, Long> requested = peer.getSyncChainRequested(); | ||
| check(peer, requested, chainInventoryMessage); | ||
|
|
||
| peer.setFetchAble(false); | ||
|
|
||
|
|
@@ -51,6 +54,7 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep | |
| Deque<BlockId> blockIdWeGet = new LinkedList<>(chainInventoryMessage.getBlockIds()); | ||
|
|
||
| if (blockIdWeGet.size() == 1 && tronNetDelegate.containBlock(blockIdWeGet.peek())) { | ||
| peer.setRemainNum(0); | ||
| peer.setTronState(TronState.SYNC_COMPLETED); | ||
| peer.setNeedSyncFromPeer(false); | ||
| return; | ||
|
|
@@ -98,11 +102,17 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep | |
| } | ||
| } | ||
|
|
||
| private void check(PeerConnection peer, ChainInventoryMessage msg) throws P2pException { | ||
| if (peer.getSyncChainRequested() == null) { | ||
| private void check(PeerConnection peer, Pair<Deque<BlockId>, Long> requested, | ||
| ChainInventoryMessage msg) throws P2pException { | ||
| if (requested == null) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "not send syncBlockChainMsg"); | ||
| } | ||
|
|
||
| HelloMessage hello = peer.getHelloMessageReceive(); | ||
| if (hello == null) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "hello message not received"); | ||
| } | ||
|
|
||
| List<BlockId> blockIds = msg.getBlockIds(); | ||
| if (CollectionUtils.isEmpty(blockIds)) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "blockIds is empty"); | ||
|
|
@@ -112,7 +122,8 @@ private void check(PeerConnection peer, ChainInventoryMessage msg) throws P2pExc | |
| throw new P2pException(TypeEnum.BAD_MESSAGE, "big blockIds size: " + blockIds.size()); | ||
| } | ||
|
|
||
| if (msg.getRemainNum() != 0 && blockIds.size() < NetConstants.SYNC_FETCH_BATCH_NUM) { | ||
| if (msg.getRemainNum() < 0 | ||
| || (msg.getRemainNum() != 0 && blockIds.size() < NetConstants.SYNC_FETCH_BATCH_NUM)) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, | ||
| "remain: " + msg.getRemainNum() + ", blockIds size: " + blockIds.size()); | ||
| } | ||
|
|
@@ -124,9 +135,9 @@ private void check(PeerConnection peer, ChainInventoryMessage msg) throws P2pExc | |
| } | ||
| } | ||
|
|
||
| if (!peer.getSyncChainRequested().getKey().contains(blockIds.get(0))) { | ||
| if (!requested.getKey().contains(blockIds.get(0))) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "unlinked block, my head: " | ||
| + peer.getSyncChainRequested().getKey().getLast().getString() | ||
| + requested.getKey().getLast().getString() | ||
| + ", peer: " + blockIds.get(0).getString()); | ||
| } | ||
|
|
||
|
|
@@ -137,11 +148,20 @@ private void check(PeerConnection peer, ChainInventoryMessage msg) throws P2pExc | |
| long maxFutureNum = | ||
| maxRemainTime / BLOCK_PRODUCED_INTERVAL + tronNetDelegate.getSolidBlockId().getNum(); | ||
| long lastNum = blockIds.get(blockIds.size() - 1).getNum(); | ||
| if (lastNum + msg.getRemainNum() > maxFutureNum) { | ||
| if (lastNum > maxFutureNum || msg.getRemainNum() > maxFutureNum - lastNum) { | ||
| throw new P2pException(TypeEnum.BAD_MESSAGE, "lastNum: " + lastNum + " + remainNum: " | ||
| + msg.getRemainNum() + " > futureMaxNum: " + maxFutureNum); | ||
| } | ||
| } | ||
|
|
||
| if (blockIds.size() == 1) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [SHOULD] Apply the HELLO head check to multi-block terminal responses as well. The head check is currently limited to single-block responses, which still allows a peer to return The known blocks are then popped and passed to Keep the existing single-block rule, but for all responses with Add regression cases for two- and three-block responses, asserting |
||
| long lastNum = blockIds.get(0).getNum(); | ||
| long helloHeadNum = hello.getHeadBlockId().getNum(); | ||
| if (lastNum < helloHeadNum) { | ||
| throw new P2pException(TypeEnum.SYNC_FAILED, | ||
| "Single-block response height " + lastNum + " is below hello head " + helloHeadNum); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package org.tron.core.net; | ||
|
|
||
| import static org.mockito.ArgumentMatchers.any; | ||
| import static org.mockito.Mockito.doNothing; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.spy; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| import java.net.InetSocketAddress; | ||
| import org.tron.common.utils.ReflectUtils; | ||
| import org.tron.common.utils.Sha256Hash; | ||
| import org.tron.core.capsule.BlockCapsule.BlockId; | ||
| import org.tron.core.net.message.handshake.HelloMessage; | ||
| import org.tron.core.net.peer.PeerConnection; | ||
| import org.tron.p2p.connection.Channel; | ||
| import org.tron.protos.Protocol; | ||
|
|
||
| public final class PeerSyncTestSupport { | ||
|
|
||
| private PeerSyncTestSupport() { | ||
| } | ||
|
|
||
| public static BlockId blockId(long number) { | ||
| return new BlockId(Sha256Hash.ZERO_HASH, number); | ||
| } | ||
|
|
||
| public static HelloMessage helloMessage(long headNum) throws Exception { | ||
| return new HelloMessage(Protocol.HelloMessage.newBuilder() | ||
| .setHeadBlockId(Protocol.HelloMessage.BlockId.newBuilder() | ||
| .setHash(blockId(headNum).getByteString()).setNumber(headNum)) | ||
| .build().toByteArray()); | ||
| } | ||
|
|
||
| public static PeerConnection peer(int port) { | ||
| PeerConnection peer = spy(new PeerConnection()); | ||
| Channel channel = mock(Channel.class); | ||
| InetSocketAddress address = new InetSocketAddress("127.0.0.1", port); | ||
| when(channel.getInetSocketAddress()).thenReturn(address); | ||
| when(channel.getInetAddress()).thenReturn(address.getAddress()); | ||
| ReflectUtils.setFieldValue(peer, "channel", channel); | ||
| doNothing().when(peer).sendMessage(any()); | ||
| doNothing().when(peer).disconnect(any()); | ||
| return peer; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[SHOULD] A single known block can still prematurely terminate synchronization despite being inconsistent with the peer’s advertised height.
If the peer advertises
head=200in HELLO and the local node sends summary[0,50,100], a response containing only[50]withremainNum=0still passescheck(). It then clearssyncChainRequested, setsSYNC_COMPLETED, and setsneedSyncFromPeer=false. WhenneedSyncFromUs=false,isSyncFinish()also becomestrue, so the connection immediately exits both the response-timeout check and the 30-second sync-no-progress check.Please handle terminal responses that are inconsistent with the height previously advertised by the peer, while preserving appropriate no-progress handling and compatibility with normal responses from honestly lagging peers. Simply rejecting responses that end before the last block in the summary is insufficient:
[100], remainNum=0produces the same result.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 4e46755.
check()now rejects single-block responses below the peer’s advertised HELLO head before clearing the pending request or changing synchronization state. Both[50], remainNum=0and[100], remainNum=0therefore trigger aSYNC_FAILdisconnect when HELLO advertised height 200.Normal responses from lagging peers remain accepted—for example, HELLO=50 followed by
[50], 0when our local head has advanced to 100. A response observed during a temporary rollback can also trigger disconnection; usingSYNC_FAILallows recovery through a fresh handshake without the one-hourBAD_PROTOCOLban.Regression tests cover rejection, unchanged synchronization state, the actual disconnect reason, and compatibility with lagging peers. All 58 related tests and both Checkstyle checks passed locally.