Skip to content

Commit a2364fd

Browse files
Merge pull request #6719 from tronprotocol/feat/constant-call-timeout
feat(vm): add node-level vm.constantCallTimeoutMs for constant calls
2 parents e178733 + 057bf4e commit a2364fd

7 files changed

Lines changed: 157 additions & 10 deletions

File tree

actuator/src/main/java/org/tron/core/actuator/VMActuator.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,9 @@ private void create()
398398
byte[] ops = newSmartContract.getBytecode().toByteArray();
399399
rootInternalTx = new InternalTransaction(trx, trxType);
400400

401-
long maxCpuTimeOfOneTx = rootRepository.getDynamicPropertiesStore()
402-
.getMaxCpuTimeOfOneTx() * VMConstant.ONE_THOUSAND;
403-
long thisTxCPULimitInUs = (long) (maxCpuTimeOfOneTx * getCpuLimitInUsRatio());
401+
long thisTxCPULimitInUs = calculateCpuLimitInUs(isConstantCall,
402+
rootRepository.getDynamicPropertiesStore().getMaxCpuTimeOfOneTx(),
403+
getCpuLimitInUsRatio(), CommonParameter.getInstance().getConstantCallTimeoutMs());
404404
long vmStartInUs = System.nanoTime() / VMConstant.ONE_THOUSAND;
405405
long vmShouldEndInUs = vmStartInUs + thisTxCPULimitInUs;
406406
ProgramInvoke programInvoke = ProgramInvokeFactory
@@ -512,10 +512,9 @@ private void call()
512512
energyLimit = getTotalEnergyLimit(creator, caller, contract, feeLimit, callValue);
513513
}
514514

515-
long maxCpuTimeOfOneTx = rootRepository.getDynamicPropertiesStore()
516-
.getMaxCpuTimeOfOneTx() * VMConstant.ONE_THOUSAND;
517-
long thisTxCPULimitInUs =
518-
(long) (maxCpuTimeOfOneTx * getCpuLimitInUsRatio());
515+
long thisTxCPULimitInUs = calculateCpuLimitInUs(isConstantCall,
516+
rootRepository.getDynamicPropertiesStore().getMaxCpuTimeOfOneTx(),
517+
getCpuLimitInUsRatio(), CommonParameter.getInstance().getConstantCallTimeoutMs());
519518
long vmStartInUs = System.nanoTime() / VMConstant.ONE_THOUSAND;
520519
long vmShouldEndInUs = vmStartInUs + thisTxCPULimitInUs;
521520
ProgramInvoke programInvoke = ProgramInvokeFactory
@@ -692,6 +691,14 @@ private double getCpuLimitInUsRatio() {
692691
return cpuLimitRatio;
693692
}
694693

694+
static long calculateCpuLimitInUs(boolean isConstantCall, long maxCpuTimeOfOneTxMs,
695+
double cpuLimitInUsRatio, long constantCallTimeoutMs) {
696+
if (isConstantCall && constantCallTimeoutMs > 0L) {
697+
return constantCallTimeoutMs * VMConstant.ONE_THOUSAND;
698+
}
699+
return (long) (maxCpuTimeOfOneTxMs * VMConstant.ONE_THOUSAND * cpuLimitInUsRatio);
700+
}
701+
695702
public long getTotalEnergyLimitWithFixRatio(AccountCapsule creator, AccountCapsule caller,
696703
TriggerSmartContract contract, long feeLimit, long callValue)
697704
throws ContractValidateException {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.tron.core.actuator;
2+
3+
import static org.junit.Assert.assertEquals;
4+
5+
import org.junit.Test;
6+
7+
public class VMActuatorTest {
8+
9+
@Test
10+
public void testConstantCallUsesConfiguredTimeoutVerbatim() {
11+
assertEquals(123_000L, VMActuator.calculateCpuLimitInUs(true, 80L, 5.0, 123L));
12+
}
13+
14+
@Test
15+
public void testConstantCallWithoutConfiguredTimeoutUsesNetworkDeadline() {
16+
assertEquals(400_000L, VMActuator.calculateCpuLimitInUs(true, 80L, 5.0, 0L));
17+
}
18+
19+
@Test
20+
public void testNonConstantCallIgnoresConfiguredTimeout() {
21+
assertEquals(400_000L, VMActuator.calculateCpuLimitInUs(false, 80L, 5.0, 123L));
22+
}
23+
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,18 @@ public class CommonParameter {
7171
@Getter
7272
@Setter
7373
public double maxTimeRatio = calcMaxTimeRatio();
74+
/**
75+
* Max TVM execution time (ms) for constant calls — covers
76+
* triggerconstantcontract, triggersmartcontract dispatched to view/pure
77+
* functions, estimateenergy, eth_call, eth_estimateGas, and any other
78+
* RPC routed through Wallet#callConstantContract. 0 = use the same
79+
* deadline as block processing (current behaviour). When operators set
80+
* this in config the value must be positive and fit VM deadline conversion;
81+
* validated at config-load in VmConfig.
82+
*/
83+
@Getter
84+
@Setter
85+
public long constantCallTimeoutMs = 0L;
7486
@Getter
7587
@Setter
7688
public boolean saveInternalTx;

common/src/main/java/org/tron/core/config/args/VmConfig.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
import com.typesafe.config.Config;
44
import com.typesafe.config.ConfigBeanFactory;
5+
import lombok.AccessLevel;
56
import lombok.Getter;
67
import lombok.Setter;
78
import lombok.extern.slf4j.Slf4j;
89

910
/**
1011
* VM configuration bean. Field names match config.conf keys under the "vm" section.
11-
* Bound automatically via ConfigBeanFactory — no manual key constants needed.
12+
* Most fields are bound automatically via ConfigBeanFactory; opt-in fields that
13+
* must stay absent from reference.conf are bound manually after hasPath checks.
1214
*/
1315
@Slf4j
1416
@Getter
1517
@Setter
1618
public class VmConfig {
1719

20+
private static final String CONSTANT_CALL_TIMEOUT_MS_KEY = "constantCallTimeoutMs";
21+
static final long MAX_CONSTANT_CALL_TIMEOUT_MS = Long.MAX_VALUE / 1_000L;
22+
1823
private boolean supportConstant = false;
1924
private long maxEnergyLimitForConstant = 100_000_000L;
2025
private int lruCacheSize = 500;
@@ -27,6 +32,11 @@ public class VmConfig {
2732
private boolean saveInternalTx = false;
2833
private boolean saveFeaturedInternalTx = false;
2934
private boolean saveCancelAllUnfreezeV2Details = false;
35+
// Excluded from ConfigBeanFactory binding (no setter): the property is
36+
// intentionally absent from reference.conf so {@code Config#hasPath} alone
37+
// signals operator opt-in. Bound manually in {@link #fromConfig}.
38+
@Setter(AccessLevel.NONE)
39+
private long constantCallTimeoutMs = 0L;
3040

3141
/**
3242
* Create VmConfig from the "vm" section of the application config.
@@ -36,11 +46,11 @@ public class VmConfig {
3646
public static VmConfig fromConfig(Config config) {
3747
Config vmSection = config.getConfig("vm");
3848
VmConfig vmConfig = ConfigBeanFactory.create(vmSection, VmConfig.class);
39-
vmConfig.postProcess();
49+
vmConfig.postProcess(vmSection);
4050
return vmConfig;
4151
}
4252

43-
private void postProcess() {
53+
private void postProcess(Config vmSection) {
4454
// clamp maxEnergyLimitForConstant
4555
if (maxEnergyLimitForConstant < 3_000_000L) {
4656
maxEnergyLimitForConstant = 3_000_000L;
@@ -60,5 +70,23 @@ private void postProcess() {
6070
logger.warn("Configuring [vm.saveCancelAllUnfreezeV2Details] won't work as "
6171
+ "vm.saveInternalTx or vm.saveFeaturedInternalTx is off.");
6272
}
73+
74+
// constantCallTimeoutMs is excluded from ConfigBeanFactory binding (no
75+
// setter) and intentionally absent from reference.conf, so hasPath alone
76+
// tells us whether the operator opted in. Only positive values that can be
77+
// safely converted to microseconds are valid.
78+
if (vmSection.hasPath(CONSTANT_CALL_TIMEOUT_MS_KEY)) {
79+
long value = vmSection.getLong(CONSTANT_CALL_TIMEOUT_MS_KEY);
80+
if (value <= 0L) {
81+
throw new IllegalArgumentException(
82+
"vm.constantCallTimeoutMs must be > 0 when configured, got " + value);
83+
}
84+
if (value > MAX_CONSTANT_CALL_TIMEOUT_MS) {
85+
throw new IllegalArgumentException(
86+
"vm.constantCallTimeoutMs must be <= " + MAX_CONSTANT_CALL_TIMEOUT_MS
87+
+ " to fit VM deadline conversion, got " + value);
88+
}
89+
constantCallTimeoutMs = value;
90+
}
6391
}
6492
}

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,68 @@ public void testEstimateEnergyMaxRetryBoundaryValues() {
8888
assertEquals(3, VmConfig.fromConfig(
8989
withRef("vm { estimateEnergyMaxRetry = 3 }")).getEstimateEnergyMaxRetry());
9090
}
91+
92+
// ===========================================================================
93+
// Constant-call timeout (issue #6681). The validation rule: any positive
94+
// value that fits VM deadline conversion is accepted, but zero/negative is
95+
// rejected ONLY when the operator explicitly set the property in their
96+
// config. Absence keeps the in-Java default (0L = "share the
97+
// block-processing deadline").
98+
// ===========================================================================
99+
100+
@Test
101+
public void testConstantCallTimeoutDefaultWhenAbsent() {
102+
// No path in the config, no entry in reference.conf -> default 0L kept,
103+
// no validation triggered.
104+
VmConfig vm = VmConfig.fromConfig(withRef());
105+
assertEquals(0L, vm.getConstantCallTimeoutMs());
106+
}
107+
108+
@Test
109+
public void testConstantCallTimeoutAcceptsAnyPositiveValue() {
110+
assertEquals(1L, VmConfig.fromConfig(
111+
withRef("vm { constantCallTimeoutMs = 1 }")).getConstantCallTimeoutMs());
112+
assertEquals(50L, VmConfig.fromConfig(
113+
withRef("vm { constantCallTimeoutMs = 50 }")).getConstantCallTimeoutMs());
114+
assertEquals(500L, VmConfig.fromConfig(
115+
withRef("vm { constantCallTimeoutMs = 500 }")).getConstantCallTimeoutMs());
116+
assertEquals(5_000L, VmConfig.fromConfig(
117+
withRef("vm { constantCallTimeoutMs = 5000 }")).getConstantCallTimeoutMs());
118+
}
119+
120+
@Test
121+
public void testConstantCallTimeoutZeroRejectedWhenExplicitlyConfigured() {
122+
// Operator wrote `= 0` in config -> treated as a misconfiguration even
123+
// though it equals the in-Java default. Forces an explicit positive value.
124+
try {
125+
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = 0 }"));
126+
org.junit.Assert.fail("expected IllegalArgumentException for explicit 0");
127+
} catch (IllegalArgumentException ex) {
128+
org.junit.Assert.assertTrue(ex.getMessage(),
129+
ex.getMessage().contains("constantCallTimeoutMs"));
130+
}
131+
}
132+
133+
@Test
134+
public void testConstantCallTimeoutNegativeRejected() {
135+
try {
136+
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = -1 }"));
137+
org.junit.Assert.fail("expected IllegalArgumentException for negative ms");
138+
} catch (IllegalArgumentException ex) {
139+
org.junit.Assert.assertTrue(ex.getMessage(),
140+
ex.getMessage().contains("constantCallTimeoutMs"));
141+
}
142+
}
143+
144+
@Test
145+
public void testConstantCallTimeoutOverflowRejected() {
146+
long value = VmConfig.MAX_CONSTANT_CALL_TIMEOUT_MS + 1L;
147+
try {
148+
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = " + value + " }"));
149+
org.junit.Assert.fail("expected IllegalArgumentException for overflowing ms");
150+
} catch (IllegalArgumentException ex) {
151+
org.junit.Assert.assertTrue(ex.getMessage(),
152+
ex.getMessage().contains("deadline conversion"));
153+
}
154+
}
91155
}

framework/src/main/java/org/tron/core/config/args/Args.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ private static void applyVmConfig(VmConfig vm) {
195195
PARAMETER.saveInternalTx = vm.isSaveInternalTx();
196196
PARAMETER.saveFeaturedInternalTx = vm.isSaveFeaturedInternalTx();
197197
PARAMETER.saveCancelAllUnfreezeV2Details = vm.isSaveCancelAllUnfreezeV2Details();
198+
PARAMETER.constantCallTimeoutMs = vm.getConstantCallTimeoutMs();
198199
}
199200

200201
// Old applyStorageConfig removed — merged into applyStorageConfig()

framework/src/main/resources/config.conf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,18 @@ vm = {
732732

733733
# Indicates the max retry time for executing transaction in estimating energy. Default 3.
734734
# estimateEnergyMaxRetry = 3
735+
736+
# Max TVM execution time (ms) for constant calls — applies to
737+
# triggerconstantcontract, triggersmartcontract dispatched to view/pure
738+
# functions, estimateenergy, eth_call, eth_estimateGas, and any other RPC
739+
# routed through the constant-call path. When set, must be a positive
740+
# integer that fits VM deadline conversion and is used verbatim as the
741+
# per-call deadline (no clamp against the network's maxCpuTimeOfOneTx).
742+
# Omit the property entirely to keep the default behaviour of sharing the
743+
# block-processing deadline. Migration note: if previously running --debug
744+
# to extend constant calls, switch to this option (--debug also extends
745+
# block-processing, which is unsafe; see issue #6266).
746+
# constantCallTimeoutMs = 100
735747
}
736748

737749
# These parameters are designed for private chain testing only and cannot be freely switched on or off in production systems.

0 commit comments

Comments
 (0)