Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/src/main/java/org/tron/core/config/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public class NetConstants {
public static final int MSG_CACHE_DURATION_IN_BLOCKS = 5;
public static final int MAX_BLOCK_FETCH_PER_PEER = 100;
public static final int MAX_TRX_FETCH_PER_PEER = 1000;
public static final int MAX_SYNC_CHAIN_IDS = 30;
}

public class DatabaseConstants {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -153,6 +154,12 @@ public boolean isAdvInv(PeerConnection peer, FetchInvDataMessage msg) {

private void check(PeerConnection peer, FetchInvDataMessage fetchInvDataMsg,
boolean isAdv) throws P2pException {
List<Sha256Hash> hashList = fetchInvDataMsg.getHashList();
if (hashList.size() != new HashSet<>(hashList).size()) {
throw new P2pException(TypeEnum.BAD_MESSAGE,
"FetchInvData contains duplicate hashes, size: " + hashList.size());
}

MessageTypes type = fetchInvDataMsg.getInvMessageType();

if (type == MessageTypes.TRX) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ private boolean check(PeerConnection peer, SyncBlockChainMessage msg) throws P2p
throw new P2pException(TypeEnum.BAD_MESSAGE, "SyncBlockChain blockIds is empty");
}

if (blockIds.size() > NetConstants.MAX_SYNC_CHAIN_IDS) {
throw new P2pException(TypeEnum.BAD_MESSAGE,
"SyncBlockChain blockIds size " + blockIds.size()
+ " exceeds limit " + NetConstants.MAX_SYNC_CHAIN_IDS);
}

BlockId firstId = blockIds.get(0);
if (!tronNetDelegate.containBlockInMainChain(firstId)) {
logger.warn("Sync message from peer {} without the first block: {}",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.tron.core.net.messagehandler;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
Expand All @@ -10,6 +13,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.tron.common.es.ExecutorServiceManager;
import org.tron.common.utils.Sha256Hash;
import org.tron.core.ChainBaseManager;
import org.tron.core.config.args.Args;
import org.tron.core.exception.P2pException;
Expand Down Expand Up @@ -99,8 +103,15 @@ public void processMessage(PeerConnection peer, TronMessage msg) throws P2pExcep
}

private void check(PeerConnection peer, TransactionsMessage msg) throws P2pException {
for (Transaction trx : msg.getTransactions().getTransactionsList()) {
Item item = new Item(new TransactionMessage(trx).getMessageId(), InventoryType.TRX);
List<Transaction> list = msg.getTransactions().getTransactionsList();
Set<Sha256Hash> seen = new HashSet<>(list.size() * 2);
for (Transaction trx : list) {
Sha256Hash id = new TransactionMessage(trx).getMessageId();
if (!seen.add(id)) {
throw new P2pException(TypeEnum.BAD_MESSAGE,
"TransactionsMessage contains duplicate transaction: " + id);
}
Item item = new Item(id, InventoryType.TRX);
if (!peer.getAdvInvRequest().containsKey(item)) {
throw new P2pException(TypeEnum.BAD_MESSAGE,
"trx: " + msg.getMessageId() + " without request.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.tron.common.utils.Sha256Hash;
import org.tron.core.capsule.BlockCapsule;
import org.tron.core.config.Parameter;
import org.tron.core.exception.P2pException;
import org.tron.core.net.P2pRateLimiter;
import org.tron.core.net.TronNetDelegate;
import org.tron.core.net.message.adv.BlockMessage;
Expand Down Expand Up @@ -128,12 +129,42 @@ public void testSyncFetchCheck() {
}
}

@Test
public void testDuplicateHashRejected() throws Exception {
FetchInvDataMsgHandler handler = new FetchInvDataMsgHandler();
PeerConnection peer = Mockito.mock(PeerConnection.class);
AdvService advService = Mockito.mock(AdvService.class);
TronNetDelegate tronNetDelegate = Mockito.mock(TronNetDelegate.class);

ReflectUtils.setFieldValue(handler, "advService", advService);
ReflectUtils.setFieldValue(handler, "tronNetDelegate", tronNetDelegate);

Sha256Hash hash = Sha256Hash.ZERO_HASH;
List<Sha256Hash> hashList = new LinkedList<>();
hashList.add(hash);
hashList.add(hash); // duplicate

FetchInvDataMessage msg = new FetchInvDataMessage(hashList,
Protocol.Inventory.InventoryType.TRX);

Cache<Item, Long> advInvSpread = CacheBuilder.newBuilder()
.maximumSize(20000).expireAfterWrite(1, TimeUnit.HOURS).build();
advInvSpread.put(new Item(hash, Protocol.Inventory.InventoryType.TRX), 1L);
Mockito.when(peer.getAdvInvSpread()).thenReturn(advInvSpread);

try {
handler.processMessage(peer, msg);
Assert.fail("Expected P2pException for duplicate hash");
} catch (P2pException e) {
Assert.assertEquals(P2pException.TypeEnum.BAD_MESSAGE, e.getType());
}
}

@Test
public void testRateLimiter() {
BlockCapsule.BlockId blockId = new BlockCapsule.BlockId(Sha256Hash.ZERO_HASH, 10000L);
List<Sha256Hash> blockIds = new LinkedList<>();
for (int i = 0; i <= 100; i++) {
blockIds.add(blockId);
blockIds.add(new BlockCapsule.BlockId(Sha256Hash.ZERO_HASH, (long) i));
}
FetchInvDataMessage msg =
new FetchInvDataMessage(blockIds, Protocol.Inventory.InventoryType.BLOCK);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
import org.junit.rules.TemporaryFolder;
import org.tron.common.TestConstants;
import org.tron.common.application.TronApplicationContext;
import org.tron.common.utils.Sha256Hash;
import org.tron.core.capsule.BlockCapsule;
import org.tron.core.capsule.BlockCapsule.BlockId;
import org.tron.core.config.DefaultConfig;
import org.tron.core.config.args.Args;
import org.tron.core.exception.P2pException;
import org.tron.core.net.TronNetDelegate;
import org.tron.core.net.message.sync.BlockInventoryMessage;
import org.tron.core.net.message.sync.SyncBlockChainMessage;
import org.tron.core.net.peer.PeerConnection;
Expand Down Expand Up @@ -108,6 +110,56 @@ public void testProcessMessage() throws Exception {
Assert.assertEquals(1, list.size());
}

@Test
public void testBlockIdsExceedsLimit() throws Exception {
List<BlockId> blockIds = new ArrayList<>();
// genesis block as first (in main chain), then 30 more = 31 total → exceeds limit
BlockId genesis = context.getBean(
TronNetDelegate.class).getGenesisBlockId();
blockIds.add(genesis);
for (int i = 1; i <= 30; i++) {
blockIds.add(new BlockId(Sha256Hash.ZERO_HASH, i));
}
SyncBlockChainMessage msg = new SyncBlockChainMessage(blockIds);

try {
Method checkMethod = SyncBlockChainMsgHandler.class
.getDeclaredMethod("check", PeerConnection.class, SyncBlockChainMessage.class);
checkMethod.setAccessible(true);
checkMethod.invoke(handler, peer, msg);
Assert.fail("Expected P2pException for oversized blockIds");
} catch (InvocationTargetException e) {
Assert.assertTrue(e.getCause() instanceof P2pException);
Assert.assertEquals(P2pException.TypeEnum.BAD_MESSAGE,
((P2pException) e.getCause()).getType());
}
}

@Test
public void testBlockIdsAtLimit() throws Exception {
List<BlockId> blockIds = new ArrayList<>();
BlockId genesis = context.getBean(
TronNetDelegate.class).getGenesisBlockId();
blockIds.add(genesis);
for (int i = 1; i < 30; i++) {
blockIds.add(new BlockId(Sha256Hash.ZERO_HASH, i));
}
// exactly 30 → should not throw for length check
SyncBlockChainMessage msg = new SyncBlockChainMessage(blockIds);

Method checkMethod = SyncBlockChainMsgHandler.class
.getDeclaredMethod("check", PeerConnection.class, SyncBlockChainMessage.class);
checkMethod.setAccessible(true);
// does not throw P2pException due to length (may return false for other checks — that's fine)
try {
checkMethod.invoke(handler, peer, msg);
} catch (InvocationTargetException e) {
Assert.assertFalse("Should not fail with BAD_MESSAGE for length at limit",
e.getCause() instanceof P2pException
&& ((P2pException) e.getCause()).getMessage().contains("exceeds limit"));
}
}

@AfterClass
public static void destroy() {
for (PeerConnection p : PeerManager.getPeers()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import org.tron.common.TestConstants;
import org.tron.common.runtime.TvmTestUtils;
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.ReflectUtils;
import org.tron.core.config.args.Args;
import org.tron.core.exception.P2pException;
import org.tron.core.net.TronNetDelegate;
import org.tron.core.net.message.adv.TransactionMessage;
import org.tron.core.net.message.adv.TransactionsMessage;
Expand Down Expand Up @@ -132,6 +134,52 @@ public void testProcessMessage() {
}
}

@Test
public void testDuplicateTransactionRejected() throws Exception {
TransactionsMsgHandler handler = new TransactionsMsgHandler();
handler.init();
try {
PeerConnection peer = Mockito.mock(PeerConnection.class);

// Build a transaction
BalanceContract.TransferContract transferContract = BalanceContract.TransferContract
.newBuilder()
.setAmount(10)
.setOwnerAddress(ByteString.copyFrom(ByteArray.fromHexString("121212a9cf")))
.setToAddress(ByteString.copyFrom(ByteArray.fromHexString("232323a9cf")))
.build();
Protocol.Transaction trx = Protocol.Transaction.newBuilder()
.setRawData(Protocol.Transaction.raw.newBuilder()
.addContract(Protocol.Transaction.Contract.newBuilder()
.setType(Protocol.Transaction.Contract.ContractType.TransferContract)
.setParameter(Any.pack(transferContract)).build())
.build())
.build();

// Same trx twice → duplicate
Protocol.Transactions transactions = Protocol.Transactions.newBuilder()
.addTransactions(trx)
.addTransactions(trx)
.build();
TransactionsMessage msg = new TransactionsMessage(transactions.getTransactionsList());

TransactionMessage trxMsg = new TransactionMessage(trx);
Item item = new Item(trxMsg.getMessageId(), Protocol.Inventory.InventoryType.TRX);
Map<Item, Long> advInvRequest = new ConcurrentHashMap<>();
advInvRequest.put(item, System.currentTimeMillis());
Mockito.when(peer.getAdvInvRequest()).thenReturn(advInvRequest);

try {
handler.processMessage(peer, msg);
Assert.fail("Expected P2pException for duplicate transaction");
} catch (P2pException e) {
Assert.assertEquals(P2pException.TypeEnum.BAD_MESSAGE, e.getType());
}
} finally {
handler.close();
}
}

class TrxEvent {

@Getter
Expand Down