Skip to content

Commit d8252da

Browse files
committed
test(vm): cover constant-call timeout policy, config, and throttle
VmTimeoutPolicy: deadline math, never-shrink-below-network, and cap-engagement gate. VmConfig: 0 sentinel, [80, 500] range. Wallet: reflective test of constantCallSemaphore lazy-init and permit count.
1 parent 007b96d commit d8252da

6 files changed

Lines changed: 293 additions & 12 deletions

File tree

common/src/main/java/org/tron/common/parameter/CommonParameter.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ public class CommonParameter {
6868
@Setter
6969
public long constantCallTimeoutMs = 0L;
7070
/**
71-
* Max concurrent constant-call executions. Cap is engaged only when
72-
* constantCallTimeoutMs exceeds the baseline default; nodes leaving the
73-
* timeout at its default see no behaviour change.
71+
* Max concurrent constant-call executions. The cap engages strictly when
72+
* constantCallTimeoutMs exceeds the network's current maxCpuTimeOfOneTx;
73+
* nodes leaving constantCallTimeoutMs at its default see no throttling.
7474
*/
7575
@Getter
7676
@Setter

common/src/main/resources/reference.conf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -717,8 +717,11 @@ vm = {
717717
# block-processing, which is unsafe; see issue #6266).
718718
constantCallTimeoutMs = 0
719719

720-
# Max concurrent constant-call executions. Engages only when
721-
# constantCallTimeoutMs is raised above 80.
720+
# Max concurrent constant-call executions. The cap engages strictly when
721+
# constantCallTimeoutMs exceeds the network's current maxCpuTimeOfOneTx;
722+
# nodes leaving constantCallTimeoutMs at 0 see no throttling. Note: a
723+
# committee proposal that lowers maxCpuTimeOfOneTx may newly engage the
724+
# cap on nodes that previously satisfied configured <= network.
722725
constantCallMaxConcurrency = 10
723726
}
724727

common/src/test/java/org/tron/core/config/args/VmConfigTest.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,66 @@ public void testEstimateEnergyMaxRetryBoundaryValues() {
8888
assertEquals(3, VmConfig.fromConfig(
8989
withRef("vm { estimateEnergyMaxRetry = 3 }")).getEstimateEnergyMaxRetry());
9090
}
91+
92+
// ===========================================================================
93+
// Boundary tests for constant-call timeout (issue #6681)
94+
// ===========================================================================
95+
96+
@Test
97+
public void testConstantCallTimeoutDefaults() {
98+
VmConfig vm = VmConfig.fromConfig(withRef());
99+
assertEquals(0L, vm.getConstantCallTimeoutMs());
100+
assertEquals(10, vm.getConstantCallMaxConcurrency());
101+
}
102+
103+
@Test
104+
public void testConstantCallTimeoutValidOverride() {
105+
VmConfig vm = VmConfig.fromConfig(
106+
withRef("vm { constantCallTimeoutMs = 200, constantCallMaxConcurrency = 4 }"));
107+
assertEquals(200L, vm.getConstantCallTimeoutMs());
108+
assertEquals(4, vm.getConstantCallMaxConcurrency());
109+
}
110+
111+
@Test
112+
public void testConstantCallTimeoutBoundaryValues() {
113+
assertEquals(80L, VmConfig.fromConfig(
114+
withRef("vm { constantCallTimeoutMs = 80 }")).getConstantCallTimeoutMs());
115+
assertEquals(500L, VmConfig.fromConfig(
116+
withRef("vm { constantCallTimeoutMs = 500 }")).getConstantCallTimeoutMs());
117+
assertEquals(0L, VmConfig.fromConfig(
118+
withRef("vm { constantCallTimeoutMs = 0 }")).getConstantCallTimeoutMs());
119+
}
120+
121+
@Test
122+
public void testConstantCallTimeoutBelowBaselineRejected() {
123+
try {
124+
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = 50 }"));
125+
org.junit.Assert.fail("expected IllegalArgumentException for ms < baseline");
126+
} catch (IllegalArgumentException ex) {
127+
org.junit.Assert.assertTrue(ex.getMessage(),
128+
ex.getMessage().contains("constantCallTimeoutMs"));
129+
}
130+
}
131+
132+
@Test
133+
public void testConstantCallTimeoutAboveMaxRejected() {
134+
try {
135+
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = 1000 }"));
136+
org.junit.Assert.fail("expected IllegalArgumentException for ms > max");
137+
} catch (IllegalArgumentException ex) {
138+
org.junit.Assert.assertTrue(ex.getMessage(),
139+
ex.getMessage().contains("constantCallTimeoutMs"));
140+
}
141+
}
142+
143+
@Test
144+
public void testConstantCallMaxConcurrencyZeroRejected() {
145+
try {
146+
VmConfig.fromConfig(withRef("vm { constantCallMaxConcurrency = 0 }"));
147+
org.junit.Assert.fail("expected IllegalArgumentException for cap = 0");
148+
} catch (IllegalArgumentException ex) {
149+
org.junit.Assert.assertTrue(ex.getMessage(),
150+
ex.getMessage().contains("constantCallMaxConcurrency"));
151+
}
152+
}
91153
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package org.tron.core.vm;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
public class VmTimeoutPolicyTest {
8+
9+
private static final long START = 1_000_000L; // arbitrary microseconds
10+
private static final long MAX_CPU_MS = 80L;
11+
private static final double RATIO = 1.0;
12+
13+
@Test
14+
public void blockProcessingUsesMaxCpuTimes1000ByRatio() {
15+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
16+
false, START, MAX_CPU_MS, RATIO,
17+
VmTimeoutPolicy.CONSTANT_CALL_TIMEOUT_UNSET);
18+
assertEquals(START + 80_000L, deadline);
19+
}
20+
21+
@Test
22+
public void constantCallWithUnsetFallsBackToMaxCpu() {
23+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
24+
true, START, MAX_CPU_MS, RATIO,
25+
VmTimeoutPolicy.CONSTANT_CALL_TIMEOUT_UNSET);
26+
assertEquals(START + 80_000L, deadline);
27+
}
28+
29+
@Test
30+
public void constantCallWithOverrideUsesConfiguredTimeout() {
31+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
32+
true, START, MAX_CPU_MS, RATIO, 200L);
33+
assertEquals(START + 200_000L, deadline);
34+
}
35+
36+
@Test
37+
public void constantCallNeverShrinksBelowNetwork() {
38+
// Network raised to 100 ms (committee proposal), operator configures 80.
39+
// Effective budget must remain at network value, not 80.
40+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
41+
true, START, 100L, RATIO, 80L);
42+
assertEquals(START + 100_000L, deadline);
43+
}
44+
45+
@Test
46+
public void constantCallOverrideIgnoresCpuLimitRatio() {
47+
// Constant-call deadline is contract-locked to ET_PRE_TYPE, whose ratio is
48+
// always 1.0. Pin the invariant: even if a non-1.0 ratio reaches the
49+
// policy, the override branch must use the configured ms verbatim. If a
50+
// future refactor decouples ET_PRE_TYPE from constant calls and starts
51+
// forwarding a real ratio here, this assertion will catch the silent loss.
52+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
53+
true, START, MAX_CPU_MS, 5.0, 200L);
54+
assertEquals(START + 200_000L, deadline);
55+
}
56+
57+
@Test
58+
public void blockProcessingIgnoresConstantCallOverride() {
59+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
60+
false, START, MAX_CPU_MS, RATIO, 500L);
61+
// Even if operator sets a long constant-call timeout, block processing
62+
// remains gated by maxCpuTimeOfOneTx * ratio. Issue #6681 makes this
63+
// a hard guarantee, not a convention.
64+
assertEquals(START + 80_000L, deadline);
65+
}
66+
67+
@Test
68+
public void blockProcessingAppliesRatio() {
69+
long deadline = VmTimeoutPolicy.computeVmShouldEndInUs(
70+
false, START, MAX_CPU_MS, 0.5, 200L);
71+
assertEquals(START + 40_000L, deadline);
72+
}
73+
74+
// ===========================================================================
75+
// Cap-engagement gate (issue #6681). The cap is engaged strictly when the
76+
// operator's configured timeout exceeds the network's current
77+
// maxCpuTimeOfOneTx; raising the network value shifts the threshold.
78+
// ===========================================================================
79+
80+
@Test
81+
public void capDisengagedAtSentinel() {
82+
org.junit.Assert.assertFalse(VmTimeoutPolicy.isCapEngaged(0L, 80L));
83+
}
84+
85+
@Test
86+
public void capDisengagedWhenConfiguredEqualsNetwork() {
87+
org.junit.Assert.assertFalse(VmTimeoutPolicy.isCapEngaged(80L, 80L));
88+
}
89+
90+
@Test
91+
public void capEngagedJustAboveNetwork() {
92+
org.junit.Assert.assertTrue(VmTimeoutPolicy.isCapEngaged(81L, 80L));
93+
}
94+
95+
@Test
96+
public void capDisengagedWhenNetworkRaisedAboveConfigured() {
97+
// Network committee raised maxCpuTimeOfOneTx to 100; operator's
98+
// previously-set 80 is now below the network value -> no longer
99+
// an "extension" -> cap stays disengaged.
100+
org.junit.Assert.assertFalse(VmTimeoutPolicy.isCapEngaged(80L, 100L));
101+
}
102+
103+
@Test
104+
public void capEngagedAboveRaisedNetwork() {
105+
// Network at 100; operator configures 120 to extend further -> cap on.
106+
org.junit.Assert.assertTrue(VmTimeoutPolicy.isCapEngaged(120L, 100L));
107+
}
108+
}

framework/src/main/resources/config.conf

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -713,15 +713,17 @@ vm = {
713713

714714
# -- Constant-call timeout (issue #6681) --
715715
# Max TVM execution time (ms) for constant calls (triggerconstantcontract /
716-
# eth_call). 0 = use the same deadline as block processing. Allowed values:
717-
# 0 or in [80, 500]. Raising this value engages a concurrency cap (see
718-
# constantCallMaxConcurrency). If you previously ran with --debug to extend
719-
# constant calls, switch to this option: --debug also extends block-processing
720-
# time which can desync the node (see issue #6266).
716+
# eth_call). 0 = use the same deadline as block processing. When set, must
717+
# be in [80, 500]. Migration note: if previously running --debug to extend
718+
# constant calls, switch to this option (--debug also extends block-processing,
719+
# which is unsafe; see issue #6266).
721720
# constantCallTimeoutMs = 0
722721

723-
# Max concurrent constant-call executions. Engages only when
724-
# constantCallTimeoutMs is raised above 80. Default: 10.
722+
# Max concurrent constant-call executions. The cap engages strictly when
723+
# constantCallTimeoutMs exceeds the network's current maxCpuTimeOfOneTx;
724+
# nodes leaving constantCallTimeoutMs at 0 see no throttling. Note: a
725+
# committee proposal that lowers maxCpuTimeOfOneTx may newly engage the
726+
# cap on nodes that previously satisfied configured <= network. Default: 10.
725727
# constantCallMaxConcurrency = 10
726728
}
727729

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package org.tron.core;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertFalse;
5+
import static org.junit.Assert.assertSame;
6+
import static org.junit.Assert.assertTrue;
7+
8+
import java.lang.reflect.Field;
9+
import java.lang.reflect.Method;
10+
import java.util.concurrent.Semaphore;
11+
12+
import org.junit.After;
13+
import org.junit.Before;
14+
import org.junit.Test;
15+
import org.tron.common.parameter.CommonParameter;
16+
17+
/**
18+
* Verifies the lazy-init contract for {@code Wallet.constantCallSemaphore}:
19+
* permit count is taken from {@code CommonParameter.constantCallMaxConcurrency}
20+
* at first call, and subsequent calls return the same instance. Together with
21+
* {@link org.tron.core.vm.VmTimeoutPolicyTest} (which covers
22+
* {@code isCapEngaged}), this is the integration-level coverage for the
23+
* constant-call throttle wired up in {@code Wallet.callConstantContract}.
24+
*/
25+
public class WalletConstantCallThrottleTest {
26+
27+
private static final Field SEMAPHORE_FIELD;
28+
private static final Method GET_SEMAPHORE_METHOD;
29+
30+
static {
31+
try {
32+
SEMAPHORE_FIELD = Wallet.class.getDeclaredField("constantCallSemaphore");
33+
SEMAPHORE_FIELD.setAccessible(true);
34+
GET_SEMAPHORE_METHOD = Wallet.class.getDeclaredMethod("getConstantCallSemaphore");
35+
GET_SEMAPHORE_METHOD.setAccessible(true);
36+
} catch (NoSuchFieldException | NoSuchMethodException e) {
37+
throw new ExceptionInInitializerError(e);
38+
}
39+
}
40+
41+
private int savedMaxConcurrency;
42+
43+
@Before
44+
public void setUp() throws Exception {
45+
savedMaxConcurrency = CommonParameter.getInstance().getConstantCallMaxConcurrency();
46+
SEMAPHORE_FIELD.set(null, null);
47+
}
48+
49+
@After
50+
public void tearDown() throws Exception {
51+
CommonParameter.getInstance().setConstantCallMaxConcurrency(savedMaxConcurrency);
52+
SEMAPHORE_FIELD.set(null, null);
53+
}
54+
55+
private static Semaphore invokeFactory() throws Exception {
56+
return (Semaphore) GET_SEMAPHORE_METHOD.invoke(null);
57+
}
58+
59+
@Test
60+
public void permitCountTracksConfiguredMaxConcurrency() throws Exception {
61+
CommonParameter.getInstance().setConstantCallMaxConcurrency(3);
62+
63+
Semaphore sem = invokeFactory();
64+
65+
assertEquals(3, sem.availablePermits());
66+
}
67+
68+
@Test
69+
public void factoryIsIdempotent() throws Exception {
70+
CommonParameter.getInstance().setConstantCallMaxConcurrency(2);
71+
72+
Semaphore first = invokeFactory();
73+
Semaphore second = invokeFactory();
74+
75+
assertSame(first, second);
76+
}
77+
78+
@Test
79+
public void permitCountFrozenAtFirstCall() throws Exception {
80+
CommonParameter.getInstance().setConstantCallMaxConcurrency(1);
81+
Semaphore sem = invokeFactory();
82+
83+
// Mutating the parameter after the semaphore has been created must not
84+
// resize it — confirms that operators cannot widen the cap on a live node.
85+
CommonParameter.getInstance().setConstantCallMaxConcurrency(99);
86+
87+
assertSame(sem, invokeFactory());
88+
assertEquals(1, sem.availablePermits());
89+
}
90+
91+
@Test
92+
public void semaphoreSaturatesAtConfiguredCap() throws Exception {
93+
CommonParameter.getInstance().setConstantCallMaxConcurrency(2);
94+
Semaphore sem = invokeFactory();
95+
96+
assertTrue(sem.tryAcquire());
97+
assertTrue(sem.tryAcquire());
98+
assertFalse("3rd acquire must fail when cap=2", sem.tryAcquire());
99+
100+
sem.release();
101+
assertTrue("permit must replenish after release", sem.tryAcquire());
102+
103+
sem.release();
104+
sem.release();
105+
}
106+
}

0 commit comments

Comments
 (0)