Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5d4db4d
test(framework): fix test quality issues in framework unit tests
Little-Peony Apr 6, 2026
ea477ce
test(plugins): fix test quality issues in plugins unit tests
Little-Peony Apr 6, 2026
4321e3d
test(actuator): remove obsolete moreThanFrozenNumber test in FreezeBa…
Little-Peony Apr 6, 2026
d92db81
test(framework): fix test cleanup and solidity node shutdown
Little-Peony Apr 7, 2026
61aadd4
test(framework): replace bare Executors with ExecutorServiceManager a…
Little-Peony Apr 7, 2026
f3dfd3c
test(framework): unify app context fixtures in service tests
Little-Peony Apr 7, 2026
8b721fc
style(test): fix import order to satisfy checkstyle
Little-Peony Apr 8, 2026
85dddb6
chore(config): remove /.dev/ from .gitignore
Little-Peony Apr 8, 2026
ed551ee
style(test): fix import order in SolidityNodeTest
Little-Peony Apr 8, 2026
50998e6
test(framework): use assertSame for exception identity check in Solid…
Little-Peony Apr 8, 2026
036abb9
fix(net): guard relay nodes in peer connection
Little-Peony Apr 8, 2026
c4bb315
test(framework,plugins): fix flaky tests in bandwidth, stats and db-move
Little-Peony Apr 8, 2026
e9cdd01
refactor(framework): promote relayNodes to instance field in PeerConn…
Little-Peony Apr 8, 2026
b173311
refactor(tvm): optimize energy cost calculation for vote witness opcode
yanghang8612 Mar 31, 2026
21b16ea
fix(framework): replace while(true) with while(flag) in SolidityNode
Little-Peony Apr 13, 2026
669b1f2
fix(framework): add null/0 return after while(flag) loops in Solidity…
Little-Peony Apr 13, 2026
ed42e92
fix(test): add comments
Little-Peony Apr 13, 2026
f5a42f8
test(framework): address PR review comments on test code quality
Little-Peony Apr 14, 2026
bb8894a
fix: use Assert.assertThrows to replace try/catch
Little-Peony Apr 14, 2026
3881b87
fix: pr review
Little-Peony Apr 14, 2026
5d5e96f
fix(actuator): correct protobuf unpack types in getOwnerAddress()
halibobo1205 Apr 9, 2026
e11fce0
feat(protocol): add protoLint check for enum validation (#6631)
0xbigapple Apr 16, 2026
7bd3373
test(framework): replace Assert.fail with throws Exception in test me…
Little-Peony Apr 16, 2026
cb7e038
Merge branch 'develop' into fix_tests_v2
Little-Peony Apr 20, 2026
61a0e3c
Merge branch 'develop' into fix_tests_v2
Sunny6889 Apr 22, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class BackupServer implements AutoCloseable {

private BackupManager backupManager;

private Channel channel;
private volatile Channel channel;

private volatile boolean shutdown = false;

Expand Down Expand Up @@ -77,6 +77,10 @@ public void initChannel(NioDatagramChannel ch)

logger.info("Backup server started, bind port {}", port);

// If close() was called while bind() was in progress, channel was not yet visible
if (shutdown) {
Copy link
Copy Markdown
Collaborator

@bladehan1 bladehan1 Apr 22, 2026

Choose a reason for hiding this comment

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

[SHOULD] The close()/start() race is only partially closed, and the remaining control flow is confusing.

  1. The new if (shutdown) break; at line 81 only covers a narrow window — the case where close() has already set shutdown=true but has not yet read channel. The other window is still open: if close() runs while b.bind(port).sync() is still in progress, close() sees the previous iteration's (already-closed) channel (or null) and its channel.close().await(10s) is a no-op on the channel that is about to be bound. The newly-bound channel is then closed indirectly by group.shutdownGracefully().sync() in start()'s finally, meaning there are two distinct close paths with two different timeout semantics (10s vs. group's default quiet period).

  2. Ordering in close(): backupManager.stop() runs before channel.close(). Any in-flight UDP datagram already picked up by the event loop will still be dispatched through the pipeline into a stopped BackupManager. The usual order is to stop ingress (channel) first, then business state (backupManager).

  3. channel.close().await(10, SECONDS) is effectively redundant — ExecutorServiceManager.shutdownAndAwaitTermination(executor, name) already waits for start() to return, and start() only returns after group.shutdownGracefully().sync() has closed the channel. Its only real job is to wake up the channel.closeFuture().sync() in start(); a fire-and-forget channel.close() would do that.

  4. The while (!shutdown) + logger.warn("Restart backup server ...") branch is practically dead code for a UDP NioDatagramChannel: closeFuture() only completes on close() (→ shutdown=true → break) or on an exception (→ outer catch → finally). There is no tested path that loops back and rebinds, so the three shutdown checks + restart loop read as defensive code that will never run.

Suggestion: drop the restart loop and extra if (shutdown) break; guards, call channel.close() (fire-and-forget) in close(), rely on ExecutorServiceManager.shutdownAndAwaitTermination(executor, name) as the single sync barrier, and stop backupManager only after the executor has terminated.

break;
}
channel.closeFuture().sync();
if (shutdown) {
logger.info("Shutdown backup BackupServer");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
@Scope("prototype")
public class PeerConnection {

private static List<InetSocketAddress> relayNodes = Args.getInstance().getFastForwardNodes();

@Getter
private PeerStatistics peerStatistics = new PeerStatistics();

Expand Down Expand Up @@ -163,10 +161,16 @@ public class PeerConnection {
private volatile boolean needSyncFromUs = true;
@Getter
private P2pRateLimiter p2pRateLimiter = new P2pRateLimiter();
@Getter
private List<InetSocketAddress> relayNodes;
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.

[NIT] Three small follow-ups on the relayNodes rewrite

Moving relayNodes from a class-load-time static field to a lazy instance field is a meaningful behaviour change — it avoids capturing an empty Args.getInstance().getFastForwardNodes() before Spring wiring completes. The new shape is race-free today, but three small improvements would make the intent durable:

  1. Document why lazy. Without a one-line comment above the null-check, a future reader may "optimise" it back to a static or eager field and silently reintroduce the original empty-list bug. The if (relayNodes == null) guard also lets tests pre-inject a value via ReflectUtils.setFieldValue before setChannel runs; that contract is worth stating.

  2. Tighten publication. The safety argument is non-local: it relies on (a) PeerConnection being @Scope("prototype"), (b) PeerManager.add(...) being public static synchronized and being the only caller of setChannel, and (c) PeerManager.peers being a Collections.synchronizedList so that readers obtained via getPeers() inherit a lock release/acquire edge. If any of those change, the field is neither volatile nor final and @Getter already exposes it publicly, so a concurrent reader could observe a stale null with no data-race detector to warn about it. Prefer either volatile, a single unconditional assignment (no guard), or — best — final initialised at declaration / in the constructor, since the prototype bean is created after Spring finishes parsing Args.

  3. Narrow the getter. @Getter on a field that exists to support tests exposes it as a public API. @VisibleForTesting (from com.google.common.annotations) or @Getter(AccessLevel.PACKAGE) signals intent and keeps future changes to the field free of external consumers.

Suggestion: add a one-line comment above the null-check explaining the Args-init ordering + test-injection contract; make the field final (or at least volatile); and narrow the getter's visibility.


public void setChannel(Channel channel) {
this.channel = channel;
if (relayNodes.stream().anyMatch(n -> n.getAddress().equals(channel.getInetAddress()))) {
if (this.relayNodes == null) {
this.relayNodes = Args.getInstance().getFastForwardNodes();
}
if (relayNodes != null
&& relayNodes.stream().anyMatch(n -> n.getAddress().equals(channel.getInetAddress()))) {
this.isRelayPeer = true;
}
this.nodeStatistics = TronStatsManager.getNodeStatistics(channel.getInetAddress());
Expand Down
38 changes: 32 additions & 6 deletions framework/src/main/java/org/tron/program/SolidityNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import static org.tron.core.config.Parameter.ChainConstant.BLOCK_PRODUCED_INTERVAL;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
Expand All @@ -11,6 +13,7 @@
import org.tron.common.application.ApplicationFactory;
import org.tron.common.application.TronApplicationContext;
import org.tron.common.client.DatabaseGrpcClient;
import org.tron.common.es.ExecutorServiceManager;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.prometheus.Metrics;
import org.tron.core.ChainBaseManager;
Expand Down Expand Up @@ -39,6 +42,9 @@ public class SolidityNode {

private volatile boolean flag = true;

private ExecutorService getBlockEs;
private ExecutorService processBlockEs;

public SolidityNode(Manager dbManager) {
this.dbManager = dbManager;
this.chainBaseManager = dbManager.getChainBaseManager();
Expand Down Expand Up @@ -72,13 +78,25 @@ public static void start() {
appT.startup();
SolidityNode node = new SolidityNode(appT.getDbManager());
node.run();
appT.blockUntilShutdown();
awaitShutdown(appT, node);
}

static void awaitShutdown(Application appT, SolidityNode node) {
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.

[NIT] Signal test-only visibility

awaitShutdown(Application, SolidityNode) is package-private purely to support the two new mock-based tests. Marking it @VisibleForTesting communicates that intent and discourages accidental external use.

Suggestion: add @VisibleForTesting (from com.google.common.annotations).

try {
appT.blockUntilShutdown();
} finally {
// SolidityNode is created manually rather than managed by Spring/Application,
// so its executors must be shut down explicitly on exit.
node.shutdown();
}
}

private void run() {
try {
new Thread(this::getBlock).start();
new Thread(this::processBlock).start();
getBlockEs = ExecutorServiceManager.newSingleThreadExecutor("solid-get-block");
processBlockEs = ExecutorServiceManager.newSingleThreadExecutor("solid-process-block");
getBlockEs.execute(this::getBlock);
processBlockEs.execute(this::processBlock);
logger.info("Success to start solid node, ID: {}, remoteBlockNum: {}.", ID.get(),
remoteBlockNum);
} catch (Exception e) {
Expand All @@ -88,6 +106,12 @@ private void run() {
}
}

public void shutdown() {
flag = false;
ExecutorServiceManager.shutdownAndAwaitTermination(getBlockEs, "solid-get-block");
Copy link
Copy Markdown
Collaborator

@bladehan1 bladehan1 Apr 22, 2026

Choose a reason for hiding this comment

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

Re: @halibobo1205 #3077488830 — concrete findings on the shutdown-time verification you asked for:

[SHOULD] shutdown() worst-case latency can reach 240s

Both shutdownAndAwaitTermination calls are sequential, and each waits up to 60s + 60s (see common/src/main/java/org/tron/common/es/ExecutorServiceManager.java). If a worker is blocked inside databaseGrpcClient.getBlock(...) (no deadline set), Thread.interrupt() is not guaranteed to unblock the underlying Netty I/O, so the total shutdown time can reach 240s.

The current SolidityNodeTest uses a mocked node so CI is not affected, but any integration test or operational restart that runs the real worker will see this latency.

Suggestion: run the two shutdownAndAwaitTermination calls in parallel (the two pools are independent), and track a follow-up to add a gRPC deadline on DatabaseGrpcClient.getBlock.

ExecutorServiceManager.shutdownAndAwaitTermination(processBlockEs, "solid-process-block");
}

private void getBlock() {
long blockNum = ID.incrementAndGet();
while (flag) {
Expand Down Expand Up @@ -137,7 +161,7 @@ private void loopProcessBlock(Block block) {
}

private Block getBlockByNum(long blockNum) {
while (true) {
while (flag) {
try {
long time = System.currentTimeMillis();
Block block = databaseGrpcClient.getBlock(blockNum);
Expand All @@ -155,10 +179,11 @@ private Block getBlockByNum(long blockNum) {
sleep(exceptionSleepTime);
}
}
return null;
}

private long getLastSolidityBlockNum() {
while (true) {
while (flag) {
try {
long time = System.currentTimeMillis();
long blockNum = databaseGrpcClient.getDynamicProperties().getLastSolidityBlockNum();
Expand All @@ -171,6 +196,7 @@ private long getLastSolidityBlockNum() {
sleep(exceptionSleepTime);
}
}
return 0;
}

public void sleep(long time) {
Expand All @@ -193,4 +219,4 @@ private void resolveCompatibilityIssueIfUsingFullNodeDatabase() {
chainBaseManager.getDynamicPropertiesStore().saveLatestSolidifiedBlockNum(headBlockNum);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.tron.common;

import io.grpc.ManagedChannel;
import java.util.concurrent.TimeUnit;
import org.tron.common.application.ApplicationFactory;
import org.tron.common.application.TronApplicationContext;
import org.tron.core.config.DefaultConfig;

/**
* Shared class-level fixture for tests that manually manage a TronApplicationContext.
*/
public class ClassLevelAppContextFixture {

private TronApplicationContext context;

public TronApplicationContext createContext() {
context = new TronApplicationContext(DefaultConfig.class);
return context;
}

public TronApplicationContext createAndStart() {
createContext();
startApp();
return context;
}

public void startApp() {
ApplicationFactory.create(context).startup();
}

public TronApplicationContext getContext() {
return context;
}

public void close() {
if (context != null) {
context.close();
context = null;
}
}

public static void shutdownChannel(ManagedChannel channel) {
if (channel == null) {
return;
}
try {
channel.shutdown();
if (!channel.awaitTermination(5, TimeUnit.SECONDS)) {
channel.shutdownNow();
}
} catch (InterruptedException e) {
channel.shutdownNow();
Thread.currentThread().interrupt();
}
}

public static void shutdownChannels(ManagedChannel... channels) {
for (ManagedChannel channel : channels) {
shutdownChannel(channel);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class BackupServerTest {
public TemporaryFolder temporaryFolder = new TemporaryFolder();

@Rule
public Timeout globalTimeout = Timeout.seconds(60);
public Timeout globalTimeout = Timeout.seconds(90);
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.

[QUESTION] Why does this test need 90s?

The globalTimeout goes 60s → 90s and the method-level @Test(timeout=60_000) is removed. If this is driven by the 60s+60s wait inside ExecutorServiceManager.shutdownAndAwaitTermination, say so in a comment; otherwise the +30s is unexplained and future maintainers won't know how to tune it.

Suggestion: add a short comment explaining the new upper bound, or tighten the shutdown path so 60s still fits.

private BackupServer backupServer;

@Before
Expand All @@ -43,7 +43,7 @@ public void tearDown() {
Args.clearParam();
}

@Test(timeout = 60_000)
@Test
public void test() throws InterruptedException {
backupServer.initServer();
// wait for the server to start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ public synchronized void testEventParser() {

for (int i = 0; i < entryArr.size(); i++) {
JSONObject e = entryArr.getJSONObject(i);
System.out.println(e.getString("name"));
if (e.getString("name") != null) {
if (e.getString("name").equalsIgnoreCase("eventBytesL")) {
entry = e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public synchronized void testEventParser() {

ABI.Entry entry = null;
for (ABI.Entry e : abi.getEntrysList()) {
System.out.println(e.getName());
if (e.getName().equalsIgnoreCase("eventBytesL")) {
entry = e;
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package org.tron.common.logsfilter;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.tron.common.es.ExecutorServiceManager;
import org.tron.common.logsfilter.nativequeue.NativeMessageQueue;
import org.zeromq.SocketType;
import org.zeromq.ZContext;
Expand All @@ -13,6 +17,21 @@ public class NativeMessageQueueTest {
public String dataToSend = "################";
public String topic = "testTopic";

private ExecutorService subscriberExecutor;

@After
public void tearDown() {
if (subscriberExecutor != null) {
Copy link
Copy Markdown
Collaborator

@bladehan1 bladehan1 Apr 22, 2026

Choose a reason for hiding this comment

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

[SHOULD] Use the project's standard shutdown helper

Every other test in this PR routes executor shutdown through ExecutorServiceManager.shutdownAndAwaitTermination(...), but this @After calls subscriberExecutor.shutdownNow() + awaitTermination(2, SECONDS) directly. ZMQ's subscriber.recv() does not always respond to Thread.interrupt() within 2s, so the subscriber thread may leak across test runs and occasionally interfere with the next test.

Suggestion: replace the manual shutdown block with ExecutorServiceManager.shutdownAndAwaitTermination(subscriberExecutor, "zmq-subscriber") to align with the rest of the codebase.

subscriberExecutor.shutdownNow();
try {
subscriberExecutor.awaitTermination(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
subscriberExecutor = null;
}
}

@Test
public void invalidBindPort() {
boolean bRet = NativeMessageQueue.getInstance().start(-1111, 0);
Expand All @@ -39,22 +58,23 @@ public void publishTrigger() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}

NativeMessageQueue.getInstance().publishTrigger(dataToSend, topic);

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}

NativeMessageQueue.getInstance().stop();
}

public void startSubscribeThread() {
Thread thread = new Thread(() -> {
subscriberExecutor = ExecutorServiceManager.newSingleThreadExecutor("zmq-subscriber");
subscriberExecutor.execute(() -> {
try (ZContext context = new ZContext()) {
ZMQ.Socket subscriber = context.createSocket(SocketType.SUB);

Expand All @@ -70,6 +90,5 @@ public void startSubscribeThread() {
// ZMQ.Socket will be automatically closed when ZContext is closed
}
});
thread.start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ public void setUp() {
public void testSetAndGetBlockHash() {
blockFilterCapsule
.setBlockHash("e58f33f9baf9305dc6f82b9f1934ea8f0ade2defb951258d50167028c780351f");
System.out.println(blockFilterCapsule);
Assert.assertEquals("e58f33f9baf9305dc6f82b9f1934ea8f0ade2defb951258d50167028c780351f",
blockFilterCapsule.getBlockHash());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.tron.common.BaseTest;
import org.tron.common.parameter.CommonParameter;
import org.tron.common.runtime.RuntimeImpl;
import org.tron.common.runtime.TvmTestUtils;
import org.tron.common.utils.Commons;
Expand Down Expand Up @@ -153,8 +154,13 @@ public void testSuccess() {

@Test
public void testSuccessNoBandd() {
boolean originalDebug = CommonParameter.getInstance().isDebug();
try {
byte[] contractAddress = createContract();
// Enable debug mode to bypass CPU time limit check in Program.checkCPUTimeLimit().
// Without this, the heavy contract execution (setCoin) may exceed the time threshold
// on slow machines and cause the test to fail non-deterministically.
CommonParameter.getInstance().setDebug(true);
TriggerSmartContract triggerContract = TvmTestUtils.createTriggerContract(contractAddress,
"setCoin(uint256)", "50", false,
0, Commons.decodeFromBase58Check(TriggerOwnerTwoAddress));
Expand Down Expand Up @@ -185,6 +191,8 @@ public void testSuccessNoBandd() {
balance);
} catch (TronException e) {
Assert.assertNotNull(e);
} finally {
CommonParameter.getInstance().setDebug(originalDebug);
}
}

Expand Down Expand Up @@ -254,4 +262,4 @@ public void testMaxContractResultSize() {
}
Assert.assertEquals(2, maxSize);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -289,17 +289,9 @@ public void testWithCallerEnergyChangedInTx() throws Exception {

TVMTestResult result = freezeForOther(userA, contractAddr, userA, frozenBalance, 1);

System.out.println(result.getReceipt().getEnergyUsageTotal());
System.out.println(accountStore.get(userA));
System.out.println(accountStore.get(owner));

clearDelegatedExpireTime(contractAddr, userA);

result = unfreezeForOther(userA, contractAddr, userA, 1);

System.out.println(result.getReceipt().getEnergyUsageTotal());
System.out.println(accountStore.get(userA));
System.out.println(accountStore.get(owner));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ public void testZeroLengthOneArray() {
// witness array zero, amount array non-zero
Program program = mockProgram(0, 0, 64, 1, 0);
long cost = EnergyCost.getVoteWitnessCost3(program);
// witnessArraySize = 0 * 32 + 32 = 32, witnessMemNeeded = 0 + 32 = 32
// amountArraySize = 1 * 32 + 32 = 64, amountMemNeeded = 64 + 64 = 128
// memWords = 128 / 32 = 4
// memEnergy = 3 * 4 + 4 * 4 / 512 = 12
assertEquals(30012, cost);
Expand Down
Loading
Loading