diff --git a/README.md b/README.md index 575409b3a9..be84b44150 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ The TRON network is mainly divided into: - **Private Networks** Customized TRON networks set up by private entities for testing, development, or specific use cases. -Network selection is performed by specifying the appropriate configuration file upon full-node startup. Mainnet configuration: [config.conf](framework/src/main/resources/config.conf); Nile testnet configuration: [config-nile.conf](https://github.com/tron-nile-testnet/nile-testnet/blob/master/framework/src/main/resources/config-nile.conf) +Network selection is performed by specifying the appropriate configuration file upon full-node startup. Built-in configuration template: [reference.conf](common/src/main/resources/reference.conf); Mainnet configuration: [config.conf](framework/src/main/resources/config.conf); Nile testnet configuration: [config-nile.conf](https://github.com/tron-nile-testnet/nile-testnet/blob/master/framework/src/main/resources/config-nile.conf) ### 1. Join the TRON main network Launch a main-network full node with the built-in default configuration: diff --git a/common/src/main/resources/reference.conf b/common/src/main/resources/reference.conf index 549e280bbe..a0609930a4 100644 --- a/common/src/main/resources/reference.conf +++ b/common/src/main/resources/reference.conf @@ -25,17 +25,6 @@ # Key naming rules (required for ConfigBeanFactory auto-binding): # - Use standard camelCase: maxConnections, syncFetchBatchNum, etc. # -# Keys that cannot auto-bind (handled via normalizeNonStandardKeys() or manual reads): -# -# 1. committee.pBFTExpireNum / committee.allowPBFT — normalized to camelCase in -# CommitteeConfig.normalizeNonStandardKeys() before ConfigBeanFactory binding. -# -# 2. node.isOpenFullTcpDisconnect — normalized to "openFullTcpDisconnect" in -# NodeConfig.normalizeNonStandardKeys() before ConfigBeanFactory binding. -# -# 3. node.shutdown.BlockTime/BlockHeight/BlockCount — optional PascalCase nested keys; -# read manually in NodeConfig.fromConfig() after ConfigBeanFactory binding. -# # ============================================================================= net { 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 5f1540b672..23050f5218 100644 --- a/framework/src/main/java/org/tron/core/net/TronNetDelegate.java +++ b/framework/src/main/java/org/tron/core/net/TronNetDelegate.java @@ -111,7 +111,9 @@ public class TronNetDelegate { @PostConstruct public void init() { hitThread = new Thread(() -> { - LockSupport.park(); + while (!hitDown && !Thread.currentThread().isInterrupted()) { + LockSupport.park(); + } // to Guarantee Some other thread invokes unpark with the current thread as the target if (hitDown && exit) { System.exit(0); diff --git a/framework/src/main/java/org/tron/program/SolidityNode.java b/framework/src/main/java/org/tron/program/SolidityNode.java index 9dbe92fb78..5683809d7e 100644 --- a/framework/src/main/java/org/tron/program/SolidityNode.java +++ b/framework/src/main/java/org/tron/program/SolidityNode.java @@ -185,6 +185,10 @@ private Block getBlockByNum(long blockNum) { sleep(exceptionSleepTime); } } catch (Exception e) { + if (!flag || tronNetDelegate.isHitDown()) { + logger.info("getBlockByNum stopped during shutdown, block: {}.", blockNum); + break; + } logger.error("Failed to get block: {}, reason: {}.", blockNum, e.getMessage()); sleep(exceptionSleepTime); } diff --git a/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java b/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java index 0d14d6fbc2..3fe4933d82 100755 --- a/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java +++ b/framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java @@ -45,6 +45,7 @@ import org.tron.common.zksnark.LibrustzcashParam.IvkToPkdParams; import org.tron.common.zksnark.LibrustzcashParam.OutputProofParams; import org.tron.common.zksnark.LibrustzcashParam.SpendSigParams; +import org.tron.consensus.dpos.DposSlot; import org.tron.core.Wallet; import org.tron.core.actuator.Actuator; import org.tron.core.actuator.ActuatorCreator; @@ -143,11 +144,13 @@ public class ShieldedReceiveTest extends BaseTest { private Wallet wallet; @Resource private TransactionUtil transactionUtil; + @Resource + private DposSlot dposSlot; private static boolean init; static { - Args.setParam(new String[]{"--output-directory", dbPath(), "-w"}, SHIELD_CONF); + Args.setParam(new String[] {"--output-directory", dbPath(), "-w"}, SHIELD_CONF); ADDRESS_ONE_PRIVATE_KEY = getRandomPrivateKey(); FROM_ADDRESS = getHexAddressByPrivateKey(ADDRESS_ONE_PRIVATE_KEY); } @@ -331,7 +334,7 @@ public void testBroadcastBeforeAllowZksnark() //Add public address sign transactionCap = TransactionUtils.addTransactionSign(transactionCap.getInstance(), - ADDRESS_ONE_PRIVATE_KEY, chainBaseManager.getAccountStore()); + ADDRESS_ONE_PRIVATE_KEY, chainBaseManager.getAccountStore()); try { dbManager.pushTransaction(transactionCap); } catch (Exception e) { @@ -433,7 +436,7 @@ public String[] generateSpendAndOutputParams() throws ZksnarkException, BadItemE boolean ok2 = JLibrustzcash.librustzcashSaplingCheckOutput(checkOutputParams); Assert.assertTrue(ok2); - return new String[]{ByteArray.toHexString(checkSpendParamsData), + return new String[] {ByteArray.toHexString(checkSpendParamsData), ByteArray.toHexString(dataToBeSigned), ByteArray.toHexString(checkOutputParams.encode())}; } @@ -2402,10 +2405,13 @@ public void pushSameSkAndScanAndSpend() throws Exception { assert ecKey != null; byte[] witnessAddress = ecKey.getAddress(); WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(witnessAddress)); + // Stop the consensus task before modifying the witness schedule: DposTask uses the same + // localwitness key and would otherwise race to produce blocks at the same slot, + // triggering fork resolution and making the test slow. + consensusService.stop(); chainBaseManager.addWitness(ByteString.copyFrom(witnessAddress)); - //sometimes generate block failed, try several times. - long time = System.currentTimeMillis(); + long time = nextScheduledTime(witnessCapsule.getAddress()); Block block = getSignedBlock(witnessCapsule.getAddress(), time, privateKey); dbManager.pushBlock(new BlockCapsule(block)); @@ -2451,15 +2457,16 @@ public void pushSameSkAndScanAndSpend() throws Exception { boolean ok = dbManager.pushTransaction(transactionCap); Assert.assertTrue(ok); - Thread.sleep(500); //package transaction to block - block = getSignedBlock(witnessCapsule.getAddress(), time + 3000, privateKey); + block = getSignedBlock(witnessCapsule.getAddress(), + nextScheduledTime(witnessCapsule.getAddress()), privateKey); dbManager.pushBlock(new BlockCapsule(block)); BlockCapsule blockCapsule3 = new BlockCapsule(wallet.getNowBlock()); - Assert.assertEquals("blocknum != 2", 2, blockCapsule3.getNum()); + Assert.assertEquals("blocknum != 3", 3, blockCapsule3.getNum()); - block = getSignedBlock(witnessCapsule.getAddress(), time + 6000, privateKey); + block = getSignedBlock(witnessCapsule.getAddress(), + nextScheduledTime(witnessCapsule.getAddress()), privateKey); dbManager.pushBlock(new BlockCapsule(block)); // scan note by ivk @@ -2526,6 +2533,20 @@ public void pushSameSkAndScanAndSpend() throws Exception { Assert.assertTrue(ok2); } + // Returns the earliest timestamp at which witnessAddr is the DPoS-scheduled producer, + // relative to the current chain head. Using this avoids relying on the genesis-only + // bypass in validBlock() (latestBlockHeaderNumber == 0) when prior tests have pushed blocks. + private long nextScheduledTime(ByteString witnessAddr) { + int size = chainBaseManager.getWitnessScheduleStore().getActiveWitnesses().size(); + for (long slot = 1; slot <= size; slot++) { + if (dposSlot.getScheduledWitness(slot).equals(witnessAddr)) { + return dposSlot.getTime(slot); + } + } + throw new IllegalStateException("No scheduled slot for witness within " + + size + " slots: " + ByteArray.toHexString(witnessAddr.toByteArray())); + } + @Test public void decodePaymentAddressIgnoreCase() { String addressLower = diff --git a/framework/src/test/java/org/tron/program/SolidityNodeTest.java b/framework/src/test/java/org/tron/program/SolidityNodeTest.java index 7842eed848..7b6c479fc1 100755 --- a/framework/src/test/java/org/tron/program/SolidityNodeTest.java +++ b/framework/src/test/java/org/tron/program/SolidityNodeTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -329,6 +330,46 @@ public void testGetBlockByNumWhenClosed() throws Exception { } } + /** + * getBlockByNum() must break immediately — without a 1-second sleep — when a + * gRPC exception is thrown while flag races to false (the P3 shutdown-race fix). + * The invocation time is measured directly so the assertion is independent of + * Spring-context startup overhead. + */ + @Test(timeout = 5000) + public void testGetBlockByNumNoErrorOnExceptionDuringShutdown() throws Exception { + Method m = SolidityNode.class.getDeclaredMethod("getBlockByNum", long.class); + m.setAccessible(true); + Field clientField = getField("databaseGrpcClient"); + Object origClient = clientField.get(solidityNode); + try { + DatabaseGrpcClient mockClient = mock(DatabaseGrpcClient.class); + // flag races to false inside the gRPC call — exact close() race + Mockito.when(mockClient.getBlock(42L)).thenAnswer(inv -> { + setFlag(false); + throw new RuntimeException("channel closed during shutdown"); + }); + clientField.set(solidityNode, mockClient); + + long start = System.currentTimeMillis(); + InvocationTargetException t = assertThrows(InvocationTargetException.class, () -> { + m.invoke(solidityNode, 42L); + }); + assertTrue(t.getCause() instanceof RuntimeException); + assertEquals("SolidityNode is closing.", t.getCause().getMessage()); + long elapsed = System.currentTimeMillis() - start; + // Without the fix the catch sleeps exceptionSleepTime (1000 ms) before + // re-checking the while condition. With the fix it breaks immediately. + assertTrue("Expected break without sleep (<500 ms), got " + elapsed + " ms", + elapsed < 500); + // No retry: exactly one gRPC call must be made. + Mockito.verify(mockClient, Mockito.times(1)).getBlock(42L); + } finally { + setFlag(true); + clientField.set(solidityNode, origClient); + } + } + // ── getLastSolidityBlockNum() ───────────────────────────────────────────────── /**