diff --git a/framework/src/main/java/org/tron/core/db/Manager.java b/framework/src/main/java/org/tron/core/db/Manager.java index 0efa48dc20a..ea1102e0a46 100644 --- a/framework/src/main/java/org/tron/core/db/Manager.java +++ b/framework/src/main/java/org/tron/core/db/Manager.java @@ -999,8 +999,12 @@ public void eraseBlock() { khaosDb.pop(); revokingStore.fastPop(); logger.info("End to erase block: {}.", oldHeadBlock); - oldHeadBlock.getTransactions().forEach(tc -> - poppedTransactions.add(new TransactionCapsule(tc.getInstance()))); + oldHeadBlock.getTransactions().forEach(tc -> { + if (isMultiSignTransaction(tc.getInstance())) { + ownerAddressSet.add(ByteArray.toHexString(tc.getOwnerAddress())); + } + poppedTransactions.add(new TransactionCapsule(tc.getInstance())); + }); Metrics.gaugeInc(MetricKeys.Gauge.MANAGER_QUEUE, oldHeadBlock.getTransactions().size(), MetricLabels.Gauge.QUEUE_POPPED); diff --git a/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java b/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java index 67751230367..50fcafbf16a 100644 --- a/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java +++ b/framework/src/main/java/org/tron/core/net/P2pEventHandlerImpl.java @@ -233,9 +233,6 @@ private boolean checkInvRateLimit(PeerConnection peer, InventoryMessage msg) if (count + currentSize > maxCountIn10s) { logger.warn("Drop TRX inv from {}, window:{}, cur:{}, max:{}", peer.getInetAddress(), count, currentSize, maxCountIn10s); - if (Args.getInstance().isOpenPrintLog()) { - logger.warn("[overload] Drop tx list: {}", msg.getHashList()); - } return false; } } else if (invType == InventoryType.BLOCK) { diff --git a/framework/src/main/java/org/tron/core/net/TronNetDelegate.java b/framework/src/main/java/org/tron/core/net/TronNetDelegate.java index 9d15886f3ab..5b5600f7f80 100644 --- a/framework/src/main/java/org/tron/core/net/TronNetDelegate.java +++ b/framework/src/main/java/org/tron/core/net/TronNetDelegate.java @@ -1,7 +1,6 @@ package org.tron.core.net; import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCED_INTERVAL; -import static org.tron.core.exception.BadBlockException.TypeEnum.CALC_MERKLE_ROOT_FAILED; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -24,6 +23,7 @@ import org.tron.common.prometheus.MetricLabels; import org.tron.common.prometheus.Metrics; import org.tron.common.utils.Sha256Hash; +import org.tron.common.utils.Time; import org.tron.core.ChainBaseManager; import org.tron.core.capsule.BlockCapsule; import org.tron.core.capsule.BlockCapsule.BlockId; @@ -312,12 +312,7 @@ public void processBlock(BlockCapsule block, boolean isSync) throws P2pException | EventBloomException e) { metricsService.failProcessBlock(block.getNum(), e.getMessage()); logger.error("Process block failed, {}, reason: {}", blockId.getString(), e.getMessage()); - if (e instanceof BadBlockException - && ((BadBlockException) e).getType().equals(CALC_MERKLE_ROOT_FAILED)) { - throw new P2pException(TypeEnum.BLOCK_MERKLE_INVALID, e); - } else { - throw new P2pException(TypeEnum.BAD_BLOCK, e); - } + throw new P2pException(TypeEnum.BAD_BLOCK, e); } } } @@ -356,7 +351,7 @@ public void validSignature(BlockCapsule block) throws P2pException { } } - public boolean validBlock(BlockCapsule block) throws P2pException { + public void validBlock(BlockCapsule block) throws P2pException { long time = System.currentTimeMillis(); if (block.getTimeStamp() - time > timeout) { throw new P2pException(TypeEnum.BAD_BLOCK, @@ -368,7 +363,24 @@ public boolean validBlock(BlockCapsule block) throws P2pException { throw new P2pException(TypeEnum.BLOCK_MERKLE_INVALID, e.getMessage()); } validSignature(block); - return witnessScheduleStore.getActiveWitnesses().contains(block.getWitnessAddress()); + } + + public boolean validAdvBlock(PeerConnection peer, BlockCapsule block) throws P2pException { + boolean activeWitness = witnessScheduleStore.getActiveWitnesses() + .contains(block.getWitnessAddress()); + if (!activeWitness) { + logger.warn("Block witness is not active, block: {}, peer: {}", + block.getBlockId().getString(), peer.getInetSocketAddress()); + long now = Time.getCurrentMillis(); + long lastTime = peer.getLastInactiveWitnessBlockTime(); + peer.setLastInactiveWitnessBlockTime(now); + if (now - lastTime < getMaintenanceTimeInterval() / 2) { + throw new P2pException(TypeEnum.BAD_BLOCK, + "Repeated block from an inactive witness: " + block.getBlockId().getString()); + } + } + validBlock(block); + return activeWitness; } public PbftSignCapsule getBlockPbftCommitData(long blockNum) { diff --git a/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java b/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java index b1631979913..5d2ea0c21b6 100644 --- a/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java +++ b/framework/src/main/java/org/tron/core/net/messagehandler/BlockMsgHandler.java @@ -134,14 +134,9 @@ private void check(PeerConnection peer, BlockMessage msg) throws P2pException { private void processBlock(PeerConnection peer, BlockCapsule block) throws P2pException { BlockId blockId = block.getBlockId(); - boolean flag = tronNetDelegate.validBlock(block); - if (!flag) { - logger.warn("Receive a bad block from {}, {}, {}", - peer.getInetSocketAddress(), blockId.getString(), - Hex.toHexString(block.getWitnessAddress().toByteArray())); + if (!tronNetDelegate.validAdvBlock(peer, block)) { return; } - if (!tronNetDelegate.containBlock(block.getParentBlockId())) { logger.warn("Get unlink block {} from {}, head is {}", blockId.getString(), peer.getInetAddress(), tronNetDelegate.getHeadBlockId().getString()); diff --git a/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java b/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java index 149ae69b124..a466e2bd6cc 100644 --- a/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java +++ b/framework/src/main/java/org/tron/core/net/peer/PeerConnection.java @@ -93,6 +93,10 @@ public class PeerConnection { @Getter private volatile long blockRcvTime; + @Getter + @Setter + private volatile long lastInactiveWitnessBlockTime; + /** * Sliding factor of the fetch latency Exponentially Weighted Moving Average (EWMA): * each new sample contributes diff --git a/framework/src/main/java/org/tron/core/net/service/adv/AdvService.java b/framework/src/main/java/org/tron/core/net/service/adv/AdvService.java index 2241ce8d1ce..ba3538f673f 100644 --- a/framework/src/main/java/org/tron/core/net/service/adv/AdvService.java +++ b/framework/src/main/java/org/tron/core/net/service/adv/AdvService.java @@ -169,7 +169,8 @@ public int fastBroadcastTransaction(TransactionMessage msg) { Item item = new Item(msg.getMessageId(), InventoryType.TRX); trxCount.add(); - trxCache.put(item, new TransactionMessage(msg.getTransactionCapsule().getInstance())); + trxCache.put(item, new TransactionMessage( + msg.getTransactionCapsule().getInstance().toBuilder().clearRet().build())); List list = new ArrayList<>(); list.add(msg.getMessageId()); @@ -216,7 +217,8 @@ public void broadcast(Message msg) { TransactionMessage trxMsg = (TransactionMessage) msg; item = new Item(trxMsg.getMessageId(), InventoryType.TRX); trxCount.add(); - trxCache.put(item, new TransactionMessage(trxMsg.getTransactionCapsule().getInstance())); + trxCache.put(item, new TransactionMessage( + trxMsg.getTransactionCapsule().getInstance().toBuilder().clearRet().build())); } else { logger.error("Adv item is neither block nor trx, type: {}", msg.getType()); return; diff --git a/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java b/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java index 75d1ec9f4b5..b8ccb6daf59 100644 --- a/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java +++ b/framework/src/main/java/org/tron/core/net/service/sync/SyncService.java @@ -339,26 +339,21 @@ private synchronized void handleSyncBlock() { } private void processSyncBlock(BlockCapsule block, PeerConnection peerConnection) { - boolean flag = true; - boolean attackFlag = false; BlockId blockId = block.getBlockId(); try { tronNetDelegate.validSignature(block); tronNetDelegate.processBlock(block, true); peerConnection.setBlockRcvTime(System.currentTimeMillis()); pbftDataSyncHandler.processPBFTCommitData(block); - } catch (P2pException p2pException) { - logger.error("Process sync block {} failed, type: {}", - blockId.getString(), p2pException.getType()); - attackFlag = p2pException.getType().equals(TypeEnum.BLOCK_SIGN_INVALID) - || p2pException.getType().equals(TypeEnum.BLOCK_MERKLE_INVALID); - flag = false; } catch (Exception e) { - logger.error("Process sync block {} failed", blockId.getString(), e); - flag = false; - } - - if (attackFlag) { + if (e instanceof P2pException) { + logger.error("Process sync block {} from {} failed, type: {}, reason: {}", + blockId.getString(), peerConnection.getInetSocketAddress(), + ((P2pException) e).getType(), e.getMessage()); + } else { + logger.error("Process sync block {} from {} failed", + blockId.getString(), peerConnection.getInetSocketAddress(), e); + } invalid(blockId, peerConnection); peerConnection.disconnect(ReasonCode.BAD_BLOCK); return; @@ -369,12 +364,8 @@ private void processSyncBlock(BlockCapsule block, PeerConnection peerConnection) if (blockId.equals(bid)) { peer.setBlockBothHave(blockId); peer.getSyncBlockToFetch().remove(bid); - if (flag) { - if (peer.getSyncBlockToFetch().isEmpty() && peer.isFetchAble()) { - syncNext(peer); - } - } else { - peer.disconnect(ReasonCode.BAD_BLOCK); + if (peer.getSyncBlockToFetch().isEmpty() && peer.isFetchAble()) { + syncNext(peer); } } } diff --git a/framework/src/test/java/org/tron/core/db/ManagerTest.java b/framework/src/test/java/org/tron/core/db/ManagerTest.java index 804a29bab63..be6b4cab750 100755 --- a/framework/src/test/java/org/tron/core/db/ManagerTest.java +++ b/framework/src/test/java/org/tron/core/db/ManagerTest.java @@ -928,6 +928,39 @@ public void getVerifyTxsSkipsBlockWhenPermissionTxAlreadyConsumed() throws Excep } } + @Test + public void eraseBlockMarksPermissionUpdateOwnerForVerification() throws Exception { + byte[] owner = ByteArray.fromHexString(Wallet.getAddressPreFixString() + + "548794500882809695A8A687866E76D4271A1ABC"); + AccountContract.AccountPermissionUpdateContract contract = + AccountContract.AccountPermissionUpdateContract.newBuilder() + .setOwnerAddress(ByteString.copyFrom(owner)) + .build(); + TransactionCapsule permissionUpdate = new TransactionCapsule(contract, + ContractType.AccountPermissionUpdateContract); + BlockCapsule rolledBackBlock = new BlockCapsule(1, Sha256Hash.ZERO_HASH.getByteString(), 0, + Arrays.asList(permissionUpdate.getInstance())); + rolledBackBlock.setMerkleRoot(); + + Manager manager = new Manager(); + ChainBaseManager mockedChainManager = mock(ChainBaseManager.class); + DynamicPropertiesStore mockedDynamicPropertiesStore = mock(DynamicPropertiesStore.class); + when(mockedChainManager.getDynamicPropertiesStore()).thenReturn(mockedDynamicPropertiesStore); + when(mockedDynamicPropertiesStore.getLatestBlockHeaderHash()) + .thenReturn(Sha256Hash.wrap(new byte[32])); + when(mockedChainManager.getBlockById(any(Sha256Hash.class))).thenReturn(rolledBackBlock); + ReflectUtils.setFieldValue(manager, "chainBaseManager", mockedChainManager); + ReflectUtils.setFieldValue(manager, "khaosDb", mock(KhaosDatabase.class)); + ReflectUtils.setFieldValue(manager, "revokingStore", mock(RevokingDatabase.class)); + + manager.eraseBlock(); + + @SuppressWarnings("unchecked") + Set ownerAddressSet = + (Set) ReflectUtils.getFieldObject(manager, "ownerAddressSet"); + Assert.assertTrue(ownerAddressSet.contains(ByteArray.toHexString(owner))); + } + @Test public void doNotSwitch() throws ValidateSignatureException, ContractValidateException, ContractExeException, diff --git a/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java b/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java index 38738f78a6d..b43a2d06992 100644 --- a/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java +++ b/framework/src/test/java/org/tron/core/net/TronNetDelegateTest.java @@ -4,6 +4,8 @@ import com.google.protobuf.ByteString; import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.Collections; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -17,9 +19,14 @@ import org.tron.core.capsule.TransactionCapsule; import org.tron.core.config.args.Args; import org.tron.core.db.Manager; +import org.tron.core.exception.BadBlockException; import org.tron.core.exception.P2pException; import org.tron.core.exception.P2pException.TypeEnum; +import org.tron.core.exception.ValidateScheduleException; +import org.tron.core.metrics.MetricsService; +import org.tron.core.net.peer.PeerConnection; import org.tron.core.store.DynamicPropertiesStore; +import org.tron.core.store.WitnessScheduleStore; import org.tron.protos.Protocol.Transaction.Contract.ContractType; import org.tron.protos.contract.BalanceContract.TransferContract; @@ -185,5 +192,127 @@ public void testValidBlockMerkleRoot() throws Exception { } catch (P2pException e) { Assert.assertEquals(TypeEnum.BLOCK_MERKLE_INVALID, e.getType()); } + PeerConnection peer = new PeerConnection(); + WitnessScheduleStore schedule = mock(WitnessScheduleStore.class); + setField(tronNetDelegate, "witnessScheduleStore", schedule); + Mockito.when(schedule.getActiveWitnesses()) + .thenReturn(Collections.singletonList(tampered.getWitnessAddress())); + try { + tronNetDelegate.validAdvBlock(peer, tampered); + Assert.fail("Invalid Merkle root must still be rejected for an active witness"); + } catch (P2pException e) { + Assert.assertEquals(TypeEnum.BLOCK_MERKLE_INVALID, e.getType()); + } + Assert.assertEquals(0, peer.getLastInactiveWitnessBlockTime()); + } + + @Test + public void testValidBlockDoesNotCheckWitnessActivity() throws Exception { + TronNetDelegate delegate = Mockito.spy(new TronNetDelegate()); + WitnessScheduleStore schedule = mock(WitnessScheduleStore.class); + setField(delegate, "witnessScheduleStore", schedule); + BlockCapsule block = new BlockCapsule(1, Sha256Hash.ZERO_HASH, 0, + ByteString.copyFromUtf8("witness")); + block.setMerkleRoot(); + Mockito.doNothing().when(delegate).validSignature(block); + delegate.validBlock(block); + Mockito.verify(schedule, Mockito.never()).getActiveWitnesses(); + } + + @Test + public void testValidAdvBlockChecksSignatureAfterWitnessActivity() throws Exception { + TronNetDelegate delegate = Mockito.spy(new TronNetDelegate()); + WitnessScheduleStore schedule = mock(WitnessScheduleStore.class); + setField(delegate, "witnessScheduleStore", schedule); + Mockito.when(schedule.getActiveWitnesses()).thenReturn(Collections.emptyList()); + Mockito.doReturn(21_600_000L).when(delegate).getMaintenanceTimeInterval(); + BlockCapsule block = new BlockCapsule(1, Sha256Hash.ZERO_HASH, 0, ByteString.EMPTY); + block.setMerkleRoot(); + P2pException failure = new P2pException(TypeEnum.BLOCK_SIGN_INVALID, "invalid signature"); + Mockito.doThrow(failure).when(delegate).validSignature(block); + PeerConnection peer = Mockito.spy(new PeerConnection()); + Mockito.doReturn(new InetSocketAddress("127.0.0.1", 18888)) + .when(peer).getInetSocketAddress(); + try { + delegate.validAdvBlock(peer, block); + Assert.fail("Invalid signature must still be rejected after witness activity handling"); + } catch (P2pException e) { + Assert.assertSame(failure, e); + } + Assert.assertTrue(peer.getLastInactiveWitnessBlockTime() > 0); + } + + @Test + public void testRepeatedInactiveWitnessIsRejectedBeforeBasicValidation() throws Exception { + TronNetDelegate delegate = Mockito.spy(new TronNetDelegate()); + WitnessScheduleStore schedule = mock(WitnessScheduleStore.class); + setField(delegate, "witnessScheduleStore", schedule); + Mockito.when(schedule.getActiveWitnesses()).thenReturn(Collections.emptyList()); + Mockito.doReturn(21_600_000L).when(delegate).getMaintenanceTimeInterval(); + BlockCapsule block = new BlockCapsule(1, Sha256Hash.ZERO_HASH, 0, ByteString.EMPTY); + PeerConnection peer = Mockito.spy(new PeerConnection()); + Mockito.doReturn(new InetSocketAddress("127.0.0.1", 18888)) + .when(peer).getInetSocketAddress(); + peer.setLastInactiveWitnessBlockTime(System.currentTimeMillis()); + + try { + delegate.validAdvBlock(peer, block); + Assert.fail("Repeated inactive witness must be rejected before basic validation"); + } catch (P2pException e) { + Assert.assertEquals(TypeEnum.BAD_BLOCK, e.getType()); + } + Mockito.verify(delegate, Mockito.never()).validBlock(block); + } + + @Test + public void testValidAdvBlockAcceptsActiveWitnessWithoutChangingHistory() throws Exception { + TronNetDelegate delegate = Mockito.spy(new TronNetDelegate()); + WitnessScheduleStore schedule = mock(WitnessScheduleStore.class); + setField(delegate, "witnessScheduleStore", schedule); + BlockCapsule block = new BlockCapsule(1, Sha256Hash.ZERO_HASH, 0, + ByteString.copyFromUtf8("witness")); + block.setMerkleRoot(); + Mockito.doNothing().when(delegate).validSignature(block); + Mockito.when(schedule.getActiveWitnesses()) + .thenReturn(Collections.singletonList(block.getWitnessAddress())); + PeerConnection peer = new PeerConnection(); + peer.setLastInactiveWitnessBlockTime(86_400_000L); + + Assert.assertTrue(delegate.validAdvBlock(peer, block)); + + Mockito.verify(delegate).validSignature(block); + Assert.assertEquals(86_400_000L, peer.getLastInactiveWitnessBlockTime()); + } + + @Test + public void testProcessBlockWrapsScheduleFailureWithCause() throws Exception { + assertProcessBlockFailure(new ValidateScheduleException("invalid scheduled witness")); + } + + @Test + public void testProcessBlockWrapsMerkleFailureWithCause() throws Exception { + assertProcessBlockFailure(new BadBlockException( + BadBlockException.TypeEnum.CALC_MERKLE_ROOT_FAILED, "invalid merkle root")); + } + + private void assertProcessBlockFailure(Exception failure) throws Exception { + TronNetDelegate delegate = new TronNetDelegate(); + Manager manager = mock(Manager.class); + ChainBaseManager chainManager = mock(ChainBaseManager.class); + setField(delegate, "dbManager", manager); + setField(delegate, "chainBaseManager", chainManager); + setField(delegate, "metricsService", mock(MetricsService.class)); + Mockito.when(manager.getBlockedTimer()).thenReturn(new ThreadLocal<>()); + Mockito.when(chainManager.getHeadBlockId()) + .thenReturn(new BlockCapsule.BlockId(Sha256Hash.ZERO_HASH, 0)); + BlockCapsule block = new BlockCapsule(1, Sha256Hash.ZERO_HASH, 0, ByteString.EMPTY); + Mockito.doThrow(failure).when(manager).pushBlock(block); + try { + delegate.processBlock(block, true); + Assert.fail("Expected block validation failure"); + } catch (P2pException e) { + Assert.assertEquals(TypeEnum.BAD_BLOCK, e.getType()); + Assert.assertSame(failure, e.getCause()); + } } } diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/BlockAlreadyKnownCounterTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/BlockAlreadyKnownCounterTest.java index 64e77d77bba..f5d60294b02 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/BlockAlreadyKnownCounterTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/BlockAlreadyKnownCounterTest.java @@ -58,10 +58,11 @@ public class BlockAlreadyKnownCounterTest { private boolean metricsEnabledBefore; @Before - public void setUp() { + public void setUp() throws P2pException { metricsEnabledBefore = CommonParameter.getInstance().isMetricsPrometheusEnable(); CommonParameter.getInstance().setMetricsPrometheusEnable(true); delegate = mock(TronNetDelegate.class); + when(delegate.validAdvBlock(any(), any())).thenReturn(true); handler = new BlockMsgHandler(); ReflectUtils.setFieldValue(handler, "tronNetDelegate", delegate); ReflectUtils.setFieldValue(handler, "advService", mock(AdvService.class)); @@ -90,7 +91,6 @@ public void tearDown() { @Test public void testMatchedRequestAlreadyKnownBlockIncrements() throws P2pException { when(delegate.containBlock(any(BlockId.class))).thenReturn(true); - when(delegate.validBlock(any(BlockCapsule.class))).thenReturn(true); when(delegate.getHeadBlockId()).thenReturn(new BlockId(Sha256Hash.ZERO_HASH, HEAD_NUM)); BlockMessage msg = newBlockMessage(1); @@ -116,7 +116,6 @@ public void testMatchedRequestUnknownForkBelowHeadNotIncremented() throws P2pExc assertNotEquals(msg.getBlockId(), msg.getBlockCapsule().getParentBlockId()); when(delegate.containBlock(msg.getBlockId())).thenReturn(false); when(delegate.containBlock(msg.getBlockCapsule().getParentBlockId())).thenReturn(true); - when(delegate.validBlock(any(BlockCapsule.class))).thenReturn(true); when(delegate.getHeadBlockId()).thenReturn(new BlockId(Sha256Hash.ZERO_HASH, HEAD_NUM)); double before = sample(); @@ -150,7 +149,6 @@ public void testMatchedRequestHeadDuplicateIncrements() throws P2pException { // re-delivery of the current head block id counts: the id is exactly known even // though it is not below head. when(delegate.containBlock(any(BlockId.class))).thenReturn(true); - when(delegate.validBlock(any(BlockCapsule.class))).thenReturn(true); when(delegate.getHeadBlockId()).thenReturn(new BlockId(Sha256Hash.ZERO_HASH, HEAD_NUM)); when(delegate.getActivePeer()).thenReturn(Collections.emptyList()); diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/BlockMsgHandlerTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/BlockMsgHandlerTest.java index 82ea2b6cb57..27454f2f053 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/BlockMsgHandlerTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/BlockMsgHandlerTest.java @@ -152,7 +152,7 @@ public void testProcessBlock() { .put(new Item(blockCapsule0.getBlockId(), InventoryType.BLOCK), System.currentTimeMillis()); - Mockito.doReturn(true).when(tronNetDelegate).validBlock(any(BlockCapsule.class)); + Mockito.doReturn(true).when(tronNetDelegate).validAdvBlock(peer, blockCapsule0); Mockito.doReturn(true).when(tronNetDelegate).containBlock(any(BlockId.class)); Mockito.doReturn(blockCapsule0.getBlockId()).when(tronNetDelegate).getHeadBlockId(); Mockito.doNothing().when(tronNetDelegate).processBlock(any(BlockCapsule.class), anyBoolean()); diff --git a/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java b/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java index d7a6155d968..a694e8c7aed 100644 --- a/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java +++ b/framework/src/test/java/org/tron/core/net/messagehandler/TransactionsMsgHandlerTest.java @@ -188,6 +188,37 @@ public void testProcessMessageAfterClose() throws Exception { } } + @Test + public void testResultClearedBeforeSmartContractQueued() throws Exception { + TransactionsMsgHandler handler = new TransactionsMsgHandler(); + try { + PeerConnection peer = Mockito.mock(PeerConnection.class); + Protocol.Transaction trx = TvmTestUtils.generateTriggerSmartContractAndGetTransaction( + ByteArray.fromHexString("121212a9cf"), + ByteArray.fromHexString("121212a9cf"), + ByteArray.fromHexString("123456"), + 100, 100000000, 0, 0) + .toBuilder() + .addRet(Protocol.Transaction.Result.newBuilder().setFee(123L).build()) + .build(); + TransactionsMessage msg = new TransactionsMessage(Collections.singletonList(trx)); + stubAdvInvRequest(peer, msg); + + handler.processMessage(peer, msg); + + Field queueField = TransactionsMsgHandler.class.getDeclaredField("smartContractQueue"); + queueField.setAccessible(true); + BlockingQueue smartContractQueue = (BlockingQueue) queueField.get(handler); + TransactionsMsgHandler.TrxEvent event = + (TransactionsMsgHandler.TrxEvent) smartContractQueue.poll(); + Assert.assertNotNull(event); + Assert.assertEquals(0, + event.getMsg().getTransactionCapsule().getInstance().getRetCount()); + } finally { + handler.close(); + } + } + @Test public void testRejectedExecution() throws Exception { TransactionsMsgHandler handler = new TransactionsMsgHandler(); diff --git a/framework/src/test/java/org/tron/core/net/services/AdvServiceTest.java b/framework/src/test/java/org/tron/core/net/services/AdvServiceTest.java index 808c5eb09e0..26864a806b9 100644 --- a/framework/src/test/java/org/tron/core/net/services/AdvServiceTest.java +++ b/framework/src/test/java/org/tron/core/net/services/AdvServiceTest.java @@ -69,7 +69,7 @@ public static void after() { } @Test - public void test() { + public void test() throws IOException { testAddInv(); testBroadcast(); testTrxBroadcast(); @@ -123,17 +123,27 @@ private void testBroadcast() { Assert.assertNotNull(peer.getAdvInvSpread().getIfPresent(item)); } - private void testTrxBroadcast() { + private void testTrxBroadcast() throws IOException { Protocol.Transaction trx = Protocol.Transaction.newBuilder() .setRawData( Protocol.Transaction.raw.newBuilder() .setRefBlockNum(1) - .setExpiration(System.currentTimeMillis() + 3000).build()).build(); + .setExpiration(System.currentTimeMillis() + 3000).build()) + .addRet(Protocol.Transaction.Result.newBuilder().setFee(123L).build()) + .build(); CommonParameter.getInstance().setValidContractProtoThreadNum(1); TransactionMessage msg = new TransactionMessage(trx); service.broadcast(msg); Item item = new Item(msg.getMessageId(), InventoryType.TRX); - Assert.assertNotNull(service.getMessage(item)); + TransactionMessage cached = (TransactionMessage) service.getMessage(item); + Assert.assertNotNull(cached); + assertRelayPreservesOriginal(trx, msg, cached); + + TransactionMessage fastMsg = new TransactionMessage(trx); + service.fastBroadcastTransaction(fastMsg); + TransactionMessage fastCached = (TransactionMessage) service.getMessage(item); + Assert.assertNotSame(cached, fastCached); + assertRelayPreservesOriginal(trx, fastMsg, fastCached); Protocol.Transaction expiredTrx = Protocol.Transaction.newBuilder() .setRawData( @@ -148,6 +158,20 @@ private void testTrxBroadcast() { Assert.assertNull(service.getMessage(item1)); } + private void assertRelayPreservesOriginal(Protocol.Transaction original, + TransactionMessage message, TransactionMessage cached) throws IOException { + Assert.assertNotNull(cached); + Assert.assertEquals(original, message.getTransactionCapsule().getInstance()); + Assert.assertArrayEquals(original.toByteArray(), message.getData()); + Assert.assertNotSame(message.getTransactionCapsule(), cached.getTransactionCapsule()); + Protocol.Transaction relay = Protocol.Transaction.parseFrom(cached.getData()); + Assert.assertEquals(0, relay.getRetCount()); + Assert.assertEquals(original.getRawData(), relay.getRawData()); + Assert.assertEquals(original.getSignatureList(), relay.getSignatureList()); + Assert.assertEquals(relay, cached.getTransactionCapsule().getInstance()); + Assert.assertEquals(message.getMessageId(), cached.getMessageId()); + } + @Test public void sameAddressPeersReceiveBroadcastAndDisconnectRetryIndependently() { InetSocketAddress address = new InetSocketAddress("127.0.0.2", 10001); diff --git a/framework/src/test/java/org/tron/core/net/services/BlockFetchFailureTest.java b/framework/src/test/java/org/tron/core/net/services/BlockFetchFailureTest.java new file mode 100644 index 00000000000..e93e3f285a7 --- /dev/null +++ b/framework/src/test/java/org/tron/core/net/services/BlockFetchFailureTest.java @@ -0,0 +1,492 @@ +package org.tron.core.net.services; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.cache.Cache; +import com.google.protobuf.ByteString; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.Collections; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.MockedStatic; +import org.tron.common.TestConstants; +import org.tron.common.utils.ReflectUtils; +import org.tron.common.utils.Sha256Hash; +import org.tron.common.utils.Time; +import org.tron.core.capsule.BlockCapsule; +import org.tron.core.capsule.BlockCapsule.BlockId; +import org.tron.core.config.args.Args; +import org.tron.core.exception.BadBlockException; +import org.tron.core.exception.P2pException; +import org.tron.core.exception.P2pException.TypeEnum; +import org.tron.core.exception.ValidateScheduleException; +import org.tron.core.net.P2pEventHandlerImpl; +import org.tron.core.net.TronNetDelegate; +import org.tron.core.net.message.TronMessage; +import org.tron.core.net.message.adv.BlockMessage; +import org.tron.core.net.message.adv.FetchInvDataMessage; +import org.tron.core.net.messagehandler.BlockMsgHandler; +import org.tron.core.net.peer.Item; +import org.tron.core.net.peer.PeerConnection; +import org.tron.core.net.service.adv.AdvService; +import org.tron.core.net.service.fetchblock.FetchBlockService; +import org.tron.core.net.service.relay.RelayService; +import org.tron.core.net.service.sync.SyncService; +import org.tron.core.services.WitnessProductBlockService; +import org.tron.core.store.WitnessScheduleStore; +import org.tron.p2p.connection.Channel; +import org.tron.protos.Protocol.Inventory.InventoryType; +import org.tron.protos.Protocol.ReasonCode; + +public class BlockFetchFailureTest { + + @ClassRule + public static final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @BeforeClass + public static void init() throws Exception { + Args.setParam(new String[]{"-d", temporaryFolder.newFolder().toString()}, + TestConstants.TEST_CONF); + } + + @AfterClass + public static void destroy() { + Args.clearParam(); + } + + @Test + public void invalidAdvertisedSignatureDoesNotRetryAnotherPeer() throws Exception { + assertAdvertisedBlockDoesNotRetry(TypeEnum.BLOCK_SIGN_INVALID); + } + + @Test + public void invalidAdvertisedMerkleRootDoesNotRetryAnotherPeer() throws Exception { + assertAdvertisedBlockDoesNotRetry(TypeEnum.BLOCK_MERKLE_INVALID); + } + + @Test + public void firstInactiveAdvertisedWitnessIsIgnoredWithoutDisconnecting() throws Exception { + TronNetDelegate delegate = inactiveWitnessDelegate(21_600_000L); + BlockMsgHandler handler = new BlockMsgHandler(); + ReflectUtils.setFieldValue(handler, "tronNetDelegate", delegate); + ReflectUtils.setFieldValue(handler, "fetchBlockService", mock(FetchBlockService.class)); + SyncService sync = mock(SyncService.class); + ReflectUtils.setFieldValue(handler, "syncService", sync); + PeerConnection sender = newPeer(10001); + BlockCapsule block = newBlock(); + block.setMerkleRoot(); + sender.getAdvInvRequest().put(new Item(block.getBlockId(), InventoryType.BLOCK), + System.currentTimeMillis()); + + handler.processMessage(sender, new BlockMessage(block)); + + verify(sender, never()).disconnect(any()); + verify(sync, never()).startSync(sender); + verify(delegate, never()).processBlock(any(BlockCapsule.class), anyBoolean()); + } + + @Test + public void inactiveWitnessIsNotBroadcastWithKnownParent() throws Exception { + assertWitnessBroadcast(false, false); + } + + @Test + public void inactiveWitnessIsNotRelayedWithKnownParent() throws Exception { + assertWitnessBroadcast(false, true); + } + + @Test + public void activeWitnessIsBroadcastWithKnownParent() throws Exception { + assertWitnessBroadcast(true, false); + } + + @Test + public void activeWitnessIsRelayedWithKnownParent() throws Exception { + assertWitnessBroadcast(true, true); + } + + private void assertWitnessBroadcast(boolean active, boolean fastForward) throws Exception { + TronNetDelegate delegate = inactiveWitnessDelegate(21_600_000L); + BlockCapsule block = newBlock(); + if (active) { + WitnessScheduleStore schedule = mock(WitnessScheduleStore.class); + when(schedule.getActiveWitnesses()) + .thenReturn(Collections.singletonList(block.getWitnessAddress())); + ReflectUtils.setFieldValue(delegate, "witnessScheduleStore", schedule); + } + doReturn(true).when(delegate).containBlock(block.getParentBlockId()); + doNothing().when(delegate).processBlock(any(BlockCapsule.class), anyBoolean()); + doReturn(Collections.emptyList()).when(delegate).getActivePeer(); + BlockMsgHandler handler = new BlockMsgHandler(); + AdvService adv = mock(AdvService.class); + RelayService relay = mock(RelayService.class); + SyncService sync = mock(SyncService.class); + ReflectUtils.setFieldValue(handler, "tronNetDelegate", delegate); + ReflectUtils.setFieldValue(handler, "fetchBlockService", mock(FetchBlockService.class)); + ReflectUtils.setFieldValue(handler, "advService", adv); + ReflectUtils.setFieldValue(handler, "relayService", relay); + ReflectUtils.setFieldValue(handler, "syncService", sync); + ReflectUtils.setFieldValue(handler, "witnessProductBlockService", + mock(WitnessProductBlockService.class)); + ReflectUtils.setFieldValue(handler, "fastForward", fastForward); + PeerConnection sender = newPeer(10001); + Item item = new Item(block.getBlockId(), InventoryType.BLOCK); + sender.getAdvInvRequest().put(item, System.currentTimeMillis()); + + handler.processMessage(sender, new BlockMessage(block)); + + verify(adv, times(active && !fastForward ? 1 : 0)).broadcast(any(BlockMessage.class)); + verify(relay, times(active && fastForward ? 1 : 0)).broadcast(any(BlockMessage.class)); + verify(delegate, times(active ? 1 : 0)).processBlock(block, false); + verify(sync, never()).startSync(any()); + verify(sender, never()).disconnect(any()); + Assert.assertFalse(sender.getAdvInvRequest().containsKey(item)); + } + + @Test + public void inactiveWitnessWithinHalfMaintenanceDisconnects() throws Exception { + assertInactiveWitnessTiming(new long[]{86_400_000L, 97_199_999L}, + new boolean[]{false, true}); + } + + @Test + public void inactiveWitnessAtHalfMaintenanceStartsNewWindow() throws Exception { + assertInactiveWitnessTiming(new long[]{86_400_000L, 97_200_000L, 97_200_001L}, + new boolean[]{false, false, true}); + } + + @Test + public void inactiveWitnessAfterHalfMaintenanceStartsNewWindow() throws Exception { + assertInactiveWitnessTiming(new long[]{86_400_000L, 97_200_001L, 97_200_002L}, + new boolean[]{false, false, true}); + } + + @Test + public void inactiveWitnessClockRollbackIsTreatedAsRepeat() throws Exception { + assertInactiveWitnessTiming(new long[]{86_400_000L, 86_399_999L}, + new boolean[]{false, true}); + } + + @Test + public void inactiveWitnessBelowTruncatedHalfMaintenanceDisconnects() throws Exception { + assertInactiveWitnessTiming(new long[]{86_400_000L, 86_440_499L}, + new boolean[]{false, true}, 81_001L); + } + + @Test + public void inactiveWitnessAtTruncatedHalfMaintenanceStartsNewWindow() throws Exception { + assertInactiveWitnessTiming(new long[]{86_400_000L, 86_440_500L}, + new boolean[]{false, false}, 81_001L); + } + + @Test + public void inactiveWitnessTimesAreIndependentForEachPeer() throws Exception { + TronNetDelegate delegate = inactiveWitnessDelegate(21_600_000L); + BlockMsgHandler handler = new BlockMsgHandler(); + ReflectUtils.setFieldValue(handler, "tronNetDelegate", delegate); + ReflectUtils.setFieldValue(handler, "fetchBlockService", mock(FetchBlockService.class)); + ReflectUtils.setFieldValue(handler, "syncService", mock(SyncService.class)); + PeerConnection first = newPeer(10001); + PeerConnection second = newPeer(10002); + BlockMessage message = new BlockMessage(newBlock()); + try (MockedStatic