Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 0 additions & 11 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ public class TronNetDelegate {
@PostConstruct
public void init() {
hitThread = new Thread(() -> {
LockSupport.park();
while (!hitDown && !Thread.currentThread().isInterrupted()) {
LockSupport.park();
}
Comment thread
317787106 marked this conversation as resolved.
// to Guarantee Some other thread invokes unpark with the current thread as the target
if (hitDown && exit) {
System.exit(0);
Expand Down
8 changes: 6 additions & 2 deletions framework/src/main/java/org/tron/program/SolidityNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private void getBlock() {
logger.info("getBlock interrupted, exiting.");
return;
} catch (Exception e) {
if (!flag) {
if (!flag || tronNetDelegate.isHitDown()) {
logger.info("getBlock stopped during shutdown, last block: {}.", blockNum);
return;
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -202,7 +206,7 @@ private long getLastSolidityBlockNum() {
blockNum, remoteBlockNum, System.currentTimeMillis() - time);
return blockNum;
} catch (Exception e) {
if (!flag) {
if (!flag || tronNetDelegate.isHitDown()) {
logger.info("getLastSolidityBlockNum stopped during shutdown.");
return 0;
}
Expand Down
279 changes: 157 additions & 122 deletions framework/src/test/java/org/tron/core/zksnark/ShieldedReceiveTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import java.lang.reflect.Field;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.HashSet;
Expand Down Expand Up @@ -45,6 +46,8 @@
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.consensus.dpos.DposTask;
import org.tron.core.Wallet;
import org.tron.core.actuator.Actuator;
import org.tron.core.actuator.ActuatorCreator;
Expand Down Expand Up @@ -140,14 +143,16 @@ public class ShieldedReceiveTest extends BaseTest {
@Resource
private ConsensusService consensusService;
@Resource
private DposTask dposTask;
@Resource
private Wallet wallet;
@Resource
private TransactionUtil transactionUtil;
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);
}
Expand Down Expand Up @@ -331,7 +336,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) {
Expand Down Expand Up @@ -433,7 +438,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())};
}
Expand Down Expand Up @@ -2402,128 +2407,158 @@ public void pushSameSkAndScanAndSpend() throws Exception {
assert ecKey != null;
byte[] witnessAddress = ecKey.getAddress();
WitnessCapsule witnessCapsule = new WitnessCapsule(ByteString.copyFrom(witnessAddress));
chainBaseManager.addWitness(ByteString.copyFrom(witnessAddress));

//sometimes generate block failed, try several times.
long time = System.currentTimeMillis();
Block block = getSignedBlock(witnessCapsule.getAddress(), time, privateKey);
dbManager.pushBlock(new BlockCapsule(block));

//create transactions
chainBaseManager.getDynamicPropertiesStore().saveAllowShieldedTransaction(1);
chainBaseManager.getDynamicPropertiesStore().saveTotalShieldedPoolValue(1000 * 1000000L);
ZenTransactionBuilder builder = new ZenTransactionBuilder(wallet);

// generate spend proof
SpendingKey sk = SpendingKey
.decode("ff2c06269315333a9207f817d2eca0ac555ca8f90196976324c7756504e7c9ee");
ExpandedSpendingKey expsk = sk.expandedSpendingKey();
byte[] senderOvk = expsk.getOvk();
PaymentAddress address = sk.defaultAddress();
Note note = new Note(address, 1000 * 1000000L);
IncrementalMerkleVoucherContainer voucher = createSimpleMerkleVoucherContainer(note.cm());
byte[] anchor = voucher.root().getContent().toByteArray();
chainBaseManager.getMerkleContainer()
.putMerkleTreeIntoStore(anchor, voucher.getVoucherCapsule().getTree());
builder.addSpend(expsk, note, anchor, voucher);

// generate output proof
SpendingKey sk2 = SpendingKey.random();
FullViewingKey fullViewingKey = sk2.fullViewingKey();
IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey();

byte[] memo = org.tron.keystore.Wallet.generateRandomBytes(512);

//send coin to 2 different address generated by same sk
DiversifierT d1 = DiversifierT.random();
PaymentAddress paymentAddress1 = incomingViewingKey.address(d1).get();
builder.addOutput(senderOvk, paymentAddress1,
(1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo);

DiversifierT d2 = DiversifierT.random();
PaymentAddress paymentAddress2 = incomingViewingKey.address(d2).get();
builder.addOutput(senderOvk, paymentAddress2,
(1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo);

TransactionCapsule transactionCap = builder.build();
// 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();
Comment thread
317787106 marked this conversation as resolved.
try {
chainBaseManager.addWitness(ByteString.copyFrom(witnessAddress));

long time = nextScheduledTime(witnessCapsule.getAddress());
Block block = getSignedBlock(witnessCapsule.getAddress(), time, privateKey);
dbManager.pushBlock(new BlockCapsule(block));

//create transactions
chainBaseManager.getDynamicPropertiesStore().saveAllowShieldedTransaction(1);
chainBaseManager.getDynamicPropertiesStore().saveTotalShieldedPoolValue(1000 * 1000000L);
ZenTransactionBuilder builder = new ZenTransactionBuilder(wallet);

// generate spend proof
SpendingKey sk = SpendingKey
.decode("ff2c06269315333a9207f817d2eca0ac555ca8f90196976324c7756504e7c9ee");
ExpandedSpendingKey expsk = sk.expandedSpendingKey();
byte[] senderOvk = expsk.getOvk();
PaymentAddress address = sk.defaultAddress();
Note note = new Note(address, 1000 * 1000000L);
IncrementalMerkleVoucherContainer voucher = createSimpleMerkleVoucherContainer(note.cm());
byte[] anchor = voucher.root().getContent().toByteArray();
chainBaseManager.getMerkleContainer()
.putMerkleTreeIntoStore(anchor, voucher.getVoucherCapsule().getTree());
builder.addSpend(expsk, note, anchor, voucher);

// generate output proof
SpendingKey sk2 = SpendingKey.random();
FullViewingKey fullViewingKey = sk2.fullViewingKey();
IncomingViewingKey incomingViewingKey = fullViewingKey.inViewingKey();

byte[] memo = org.tron.keystore.Wallet.generateRandomBytes(512);

//send coin to 2 different address generated by same sk
DiversifierT d1 = DiversifierT.random();
PaymentAddress paymentAddress1 = incomingViewingKey.address(d1).get();
builder.addOutput(senderOvk, paymentAddress1,
(1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo);

DiversifierT d2 = DiversifierT.random();
PaymentAddress paymentAddress2 = incomingViewingKey.address(d2).get();
builder.addOutput(senderOvk, paymentAddress2,
(1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2, memo);

byte[] trxId = transactionCap.getTransactionId().getBytes();
boolean ok = dbManager.pushTransaction(transactionCap);
Assert.assertTrue(ok);
TransactionCapsule transactionCap = builder.build();

Thread.sleep(500);
//package transaction to block
block = getSignedBlock(witnessCapsule.getAddress(), time + 3000, privateKey);
dbManager.pushBlock(new BlockCapsule(block));

BlockCapsule blockCapsule3 = new BlockCapsule(wallet.getNowBlock());
Assert.assertEquals("blocknum != 2", 2, blockCapsule3.getNum());

block = getSignedBlock(witnessCapsule.getAddress(), time + 6000, privateKey);
dbManager.pushBlock(new BlockCapsule(block));

// scan note by ivk
byte[] receiverIvk = incomingViewingKey.getValue();
DecryptNotes notes1 = wallet.scanNoteByIvk(0, 100, receiverIvk);
Assert.assertEquals(2, notes1.getNoteTxsCount());

// scan note by ivk and mark
DecryptNotesMarked notes3 = wallet.scanAndMarkNoteByIvk(0, 100, receiverIvk,
fullViewingKey.getAk(), fullViewingKey.getNk());
Assert.assertEquals(2, notes3.getNoteTxsCount());

// scan note by ovk
DecryptNotes notes2 = wallet.scanNoteByOvk(0, 100, senderOvk);
Assert.assertEquals(2, notes2.getNoteTxsCount());

// to spend received note above.
ZenTransactionBuilder builder2 = new ZenTransactionBuilder(wallet);

//query merkleinfo
OutputPointInfo.Builder request = OutputPointInfo.newBuilder();
for (int i = 0; i < notes1.getNoteTxsCount(); i++) {
OutputPoint.Builder outPointBuild = OutputPoint.newBuilder();
outPointBuild.setHash(ByteString.copyFrom(trxId));
outPointBuild.setIndex(i);
request.addOutPoints(outPointBuild.build());
}
request.setBlockNum(1);
IncrementalMerkleVoucherInfo merkleVoucherInfo = wallet
.getMerkleTreeVoucherInfo(request.build());

//build spend proof. allow only one note in spend
ExpandedSpendingKey expsk2 = sk2.expandedSpendingKey();
for (int i = 0; i < 1; i++) {
org.tron.api.GrpcAPI.Note grpcNote = notes1.getNoteTxs(i).getNote();
PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(grpcNote.getPaymentAddress());
Note note2 = new Note(paymentAddress.getD(),
paymentAddress.getPkD(),
grpcNote.getValue(),
grpcNote.getRcm().toByteArray()
);
byte[] trxId = transactionCap.getTransactionId().getBytes();
boolean ok = dbManager.pushTransaction(transactionCap);
Assert.assertTrue(ok);

Thread.sleep(500);
//package transaction to block
long expectedBlockNum = chainBaseManager.getDynamicPropertiesStore()
.getLatestBlockHeaderNumber() + 1;
block = getSignedBlock(witnessCapsule.getAddress(),
nextScheduledTime(witnessCapsule.getAddress()), privateKey);
dbManager.pushBlock(new BlockCapsule(block));

BlockCapsule blockCapsule3 = new BlockCapsule(wallet.getNowBlock());
Assert.assertEquals("unexpected block number", expectedBlockNum, blockCapsule3.getNum());

block = getSignedBlock(witnessCapsule.getAddress(),
nextScheduledTime(witnessCapsule.getAddress()), privateKey);
dbManager.pushBlock(new BlockCapsule(block));

// scan note by ivk
byte[] receiverIvk = incomingViewingKey.getValue();
DecryptNotes notes1 = wallet.scanNoteByIvk(0, 100, receiverIvk);
Assert.assertEquals(2, notes1.getNoteTxsCount());

// scan note by ivk and mark
DecryptNotesMarked notes3 = wallet.scanAndMarkNoteByIvk(0, 100, receiverIvk,
fullViewingKey.getAk(), fullViewingKey.getNk());
Assert.assertEquals(2, notes3.getNoteTxsCount());

// scan note by ovk
DecryptNotes notes2 = wallet.scanNoteByOvk(0, 100, senderOvk);
Assert.assertEquals(2, notes2.getNoteTxsCount());

// to spend received note above.
ZenTransactionBuilder builder2 = new ZenTransactionBuilder(wallet);

//query merkleinfo
OutputPointInfo.Builder request = OutputPointInfo.newBuilder();
for (int i = 0; i < notes1.getNoteTxsCount(); i++) {
OutputPoint.Builder outPointBuild = OutputPoint.newBuilder();
outPointBuild.setHash(ByteString.copyFrom(trxId));
outPointBuild.setIndex(i);
request.addOutPoints(outPointBuild.build());
}
request.setBlockNum(1);
IncrementalMerkleVoucherInfo merkleVoucherInfo = wallet
.getMerkleTreeVoucherInfo(request.build());

//build spend proof. allow only one note in spend
ExpandedSpendingKey expsk2 = sk2.expandedSpendingKey();
for (int i = 0; i < 1; i++) {
org.tron.api.GrpcAPI.Note grpcNote = notes1.getNoteTxs(i).getNote();
PaymentAddress paymentAddress = KeyIo.decodePaymentAddress(grpcNote.getPaymentAddress());
Note note2 = new Note(paymentAddress.getD(),
paymentAddress.getPkD(),
grpcNote.getValue(),
grpcNote.getRcm().toByteArray()
);

IncrementalMerkleVoucherContainer voucher2 =
new IncrementalMerkleVoucherContainer(
new IncrementalMerkleVoucherCapsule(merkleVoucherInfo.getVouchers(i)));
byte[] anchor2 = voucher2.root().getContent().toByteArray();
builder2.addSpend(expsk2, note2, anchor2, voucher2);
}

IncrementalMerkleVoucherContainer voucher2 =
new IncrementalMerkleVoucherContainer(
new IncrementalMerkleVoucherCapsule(merkleVoucherInfo.getVouchers(i)));
byte[] anchor2 = voucher2.root().getContent().toByteArray();
builder2.addSpend(expsk2, note2, anchor2, voucher2);
//build output proof
SpendingKey sk3 = SpendingKey.random();
FullViewingKey fvk3 = sk3.fullViewingKey();
IncomingViewingKey ivk3 = fvk3.inViewingKey();

DiversifierT d3 = DiversifierT.random();
PaymentAddress paymentAddress3 = incomingViewingKey.address(d3).get();
byte[] memo3 = org.tron.keystore.Wallet.generateRandomBytes(512);
builder2.addOutput(expsk2.getOvk(), paymentAddress3,
(1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2 - wallet
.getShieldedTransactionFee(), memo3);

TransactionCapsule transactionCap2 = builder2.build();
boolean ok2 = dbManager.pushTransaction(transactionCap2);
Assert.assertTrue(ok2);
} finally {
// DposTask.init() does not reset isRunning (it stays false after stop()), so force it back
// to true via reflection before restarting.
Field isRunning = DposTask.class.getDeclaredField("isRunning");
isRunning.setAccessible(true);
isRunning.set(dposTask, true);
consensusService.start();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] pushSameSkAndScanAndSpend still appears to leak state into subsequent tests through the consensus background task.

ShieldedReceiveTest.java (line 2409) calls consensusService.stop(), and ShieldedReceiveTest.java (line 2537) attempts to restore it with consensusService.start() in the finally block.

However, DposTask.stop() sets isRunning = false (DposTask.java:82), while DposTask.init() does not reset it back to true (DposTask.java:46). As a result, after start() creates a new executor, the newly submitted runnable immediately exits because isRunning is still false, so block production is not actually resumed.

This means the test can still affect subsequent tests running in the same Spring context.

Consider either:

  • resetting isRunning = true in DposTask.init() before creating the executor, or
  • isolating this test in a dedicated context/fixture instead of stopping and restarting a shared singleton service.

Copy link
Copy Markdown
Collaborator Author

@317787106 317787106 Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, other testcases don't use consensusService. But reset DposTask's isRunning by reflection in finally block is still ok.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] The test appears to work around a real production issue via reflection rather than fixing the root cause.

DposTask.stop() sets isRunning = false, but DposTask.init() never resets it (DposTask.java:43,84). As a result, any ConsensusService.stop() → start() cycle leaves block production permanently disabled—not just in tests, but in production as well.

The actual fix seems to be a single line:

isRunning = true;

at the beginning of DposTask.init() (or start()).

That would also allow the test to simply call stop()/start() without resorting to reflection.

As written, the underlying defect remains, and the test reaches into a private volatile field via reflection. This is fairly brittle—a field rename could silently break the test at runtime.

Recommend fixing DposTask itself and removing the reflection from the test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main code should not be modified solely to make the test cases pass.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concern is not about modifying production code for the sake of a test. The test exposed a potential production issue: any ConsensusService.stop() → start() cycle leaves block production permanently disabled.

If such a lifecycle transition cannot occur in production, then this can be considered a test-only concern and safely ignored. Otherwise, it may indicate a real bug in the service lifecycle handling.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This lifecycle transition ConsensusService.stop() → start() cannot occur in production. So there is no need to change it.

}
}

// 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);
}
}

//build output proof
SpendingKey sk3 = SpendingKey.random();
FullViewingKey fvk3 = sk3.fullViewingKey();
IncomingViewingKey ivk3 = fvk3.inViewingKey();

DiversifierT d3 = DiversifierT.random();
PaymentAddress paymentAddress3 = incomingViewingKey.address(d3).get();
byte[] memo3 = org.tron.keystore.Wallet.generateRandomBytes(512);
builder2.addOutput(expsk2.getOvk(), paymentAddress3,
(1000 * 1000000L - wallet.getShieldedTransactionFee()) / 2 - wallet
.getShieldedTransactionFee(), memo3);

TransactionCapsule transactionCap2 = builder2.build();
boolean ok2 = dbManager.pushTransaction(transactionCap2);
Assert.assertTrue(ok2);
throw new IllegalStateException("No scheduled slot for witness within "
+ size + " slots: " + ByteArray.toHexString(witnessAddr.toByteArray()));
}

@Test
Expand Down
Loading
Loading