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
21 changes: 14 additions & 7 deletions actuator/src/main/java/org/tron/core/actuator/VMActuator.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,9 @@ private void create()
byte[] ops = newSmartContract.getBytecode().toByteArray();
rootInternalTx = new InternalTransaction(trx, trxType);

long maxCpuTimeOfOneTx = rootRepository.getDynamicPropertiesStore()
.getMaxCpuTimeOfOneTx() * VMConstant.ONE_THOUSAND;
long thisTxCPULimitInUs = (long) (maxCpuTimeOfOneTx * getCpuLimitInUsRatio());
long thisTxCPULimitInUs = calculateCpuLimitInUs(isConstantCall,
rootRepository.getDynamicPropertiesStore().getMaxCpuTimeOfOneTx(),
getCpuLimitInUsRatio(), CommonParameter.getInstance().getConstantCallTimeoutMs());
long vmStartInUs = System.nanoTime() / VMConstant.ONE_THOUSAND;
long vmShouldEndInUs = vmStartInUs + thisTxCPULimitInUs;
ProgramInvoke programInvoke = ProgramInvokeFactory
Expand Down Expand Up @@ -512,10 +512,9 @@ private void call()
energyLimit = getTotalEnergyLimit(creator, caller, contract, feeLimit, callValue);
}

long maxCpuTimeOfOneTx = rootRepository.getDynamicPropertiesStore()
.getMaxCpuTimeOfOneTx() * VMConstant.ONE_THOUSAND;
long thisTxCPULimitInUs =
(long) (maxCpuTimeOfOneTx * getCpuLimitInUsRatio());
long thisTxCPULimitInUs = calculateCpuLimitInUs(isConstantCall,
rootRepository.getDynamicPropertiesStore().getMaxCpuTimeOfOneTx(),
getCpuLimitInUsRatio(), CommonParameter.getInstance().getConstantCallTimeoutMs());
long vmStartInUs = System.nanoTime() / VMConstant.ONE_THOUSAND;
long vmShouldEndInUs = vmStartInUs + thisTxCPULimitInUs;
ProgramInvoke programInvoke = ProgramInvokeFactory
Expand Down Expand Up @@ -692,6 +691,14 @@ private double getCpuLimitInUsRatio() {
return cpuLimitRatio;
}

static long calculateCpuLimitInUs(boolean isConstantCall, long maxCpuTimeOfOneTxMs,
double cpuLimitInUsRatio, long constantCallTimeoutMs) {
if (isConstantCall && constantCallTimeoutMs > 0L) {
return constantCallTimeoutMs * VMConstant.ONE_THOUSAND;
}
return (long) (maxCpuTimeOfOneTxMs * VMConstant.ONE_THOUSAND * cpuLimitInUsRatio);
}

public long getTotalEnergyLimitWithFixRatio(AccountCapsule creator, AccountCapsule caller,
TriggerSmartContract contract, long feeLimit, long callValue)
throws ContractValidateException {
Expand Down
23 changes: 23 additions & 0 deletions actuator/src/test/java/org/tron/core/actuator/VMActuatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.tron.core.actuator;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class VMActuatorTest {

@Test
public void testConstantCallUsesConfiguredTimeoutVerbatim() {
assertEquals(123_000L, VMActuator.calculateCpuLimitInUs(true, 80L, 5.0, 123L));
}

@Test
public void testConstantCallWithoutConfiguredTimeoutUsesNetworkDeadline() {
assertEquals(400_000L, VMActuator.calculateCpuLimitInUs(true, 80L, 5.0, 0L));
}

@Test
public void testNonConstantCallIgnoresConfiguredTimeout() {
assertEquals(400_000L, VMActuator.calculateCpuLimitInUs(false, 80L, 5.0, 123L));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ public class CommonParameter {
@Getter
@Setter
public double maxTimeRatio = calcMaxTimeRatio();
/**
* Max TVM execution time (ms) for constant calls — covers
* triggerconstantcontract, triggersmartcontract dispatched to view/pure
* functions, estimateenergy, eth_call, eth_estimateGas, and any other
* RPC routed through Wallet#callConstantContract. 0 = use the same
* deadline as block processing (current behaviour). When operators set
* this in config the value must be positive and fit VM deadline conversion;
* validated at config-load in VmConfig.
*/
@Getter
@Setter
public long constantCallTimeoutMs = 0L;
@Getter
@Setter
public boolean saveInternalTx;
Expand Down
34 changes: 31 additions & 3 deletions common/src/main/java/org/tron/core/config/args/VmConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigBeanFactory;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

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

private static final String CONSTANT_CALL_TIMEOUT_MS_KEY = "constantCallTimeoutMs";
static final long MAX_CONSTANT_CALL_TIMEOUT_MS = Long.MAX_VALUE / 1_000L;

private boolean supportConstant = false;
private long maxEnergyLimitForConstant = 100_000_000L;
private int lruCacheSize = 500;
Expand All @@ -27,6 +32,11 @@ public class VmConfig {
private boolean saveInternalTx = false;
private boolean saveFeaturedInternalTx = false;
private boolean saveCancelAllUnfreezeV2Details = false;
// Excluded from ConfigBeanFactory binding (no setter): the property is
// intentionally absent from reference.conf so {@code Config#hasPath} alone
// signals operator opt-in. Bound manually in {@link #fromConfig}.
@Setter(AccessLevel.NONE)
private long constantCallTimeoutMs = 0L;

/**
* Create VmConfig from the "vm" section of the application config.
Expand All @@ -36,11 +46,11 @@ public class VmConfig {
public static VmConfig fromConfig(Config config) {
Config vmSection = config.getConfig("vm");
VmConfig vmConfig = ConfigBeanFactory.create(vmSection, VmConfig.class);
vmConfig.postProcess();
vmConfig.postProcess(vmSection);
return vmConfig;
}

private void postProcess() {
private void postProcess(Config vmSection) {
// clamp maxEnergyLimitForConstant
if (maxEnergyLimitForConstant < 3_000_000L) {
maxEnergyLimitForConstant = 3_000_000L;
Expand All @@ -60,5 +70,23 @@ private void postProcess() {
logger.warn("Configuring [vm.saveCancelAllUnfreezeV2Details] won't work as "
+ "vm.saveInternalTx or vm.saveFeaturedInternalTx is off.");
}

// constantCallTimeoutMs is excluded from ConfigBeanFactory binding (no
// setter) and intentionally absent from reference.conf, so hasPath alone
// tells us whether the operator opted in. Only positive values that can be
// safely converted to microseconds are valid.
if (vmSection.hasPath(CONSTANT_CALL_TIMEOUT_MS_KEY)) {
long value = vmSection.getLong(CONSTANT_CALL_TIMEOUT_MS_KEY);
if (value <= 0L) {
throw new IllegalArgumentException(
"vm.constantCallTimeoutMs must be > 0 when configured, got " + value);
}
if (value > MAX_CONSTANT_CALL_TIMEOUT_MS) {
throw new IllegalArgumentException(
"vm.constantCallTimeoutMs must be <= " + MAX_CONSTANT_CALL_TIMEOUT_MS
+ " to fit VM deadline conversion, got " + value);
}
constantCallTimeoutMs = value;
}
}
}
64 changes: 64 additions & 0 deletions common/src/test/java/org/tron/core/config/args/VmConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,68 @@ public void testEstimateEnergyMaxRetryBoundaryValues() {
assertEquals(3, VmConfig.fromConfig(
withRef("vm { estimateEnergyMaxRetry = 3 }")).getEstimateEnergyMaxRetry());
}

// ===========================================================================
// Constant-call timeout (issue #6681). The validation rule: any positive
// value that fits VM deadline conversion is accepted, but zero/negative is
// rejected ONLY when the operator explicitly set the property in their
// config. Absence keeps the in-Java default (0L = "share the
// block-processing deadline").
// ===========================================================================

@Test
public void testConstantCallTimeoutDefaultWhenAbsent() {
// No path in the config, no entry in reference.conf -> default 0L kept,
// no validation triggered.
VmConfig vm = VmConfig.fromConfig(withRef());
assertEquals(0L, vm.getConstantCallTimeoutMs());
}

@Test
public void testConstantCallTimeoutAcceptsAnyPositiveValue() {
assertEquals(1L, VmConfig.fromConfig(
withRef("vm { constantCallTimeoutMs = 1 }")).getConstantCallTimeoutMs());
assertEquals(50L, VmConfig.fromConfig(
withRef("vm { constantCallTimeoutMs = 50 }")).getConstantCallTimeoutMs());
assertEquals(500L, VmConfig.fromConfig(
withRef("vm { constantCallTimeoutMs = 500 }")).getConstantCallTimeoutMs());
assertEquals(5_000L, VmConfig.fromConfig(
withRef("vm { constantCallTimeoutMs = 5000 }")).getConstantCallTimeoutMs());
}

@Test
public void testConstantCallTimeoutZeroRejectedWhenExplicitlyConfigured() {
// Operator wrote `= 0` in config -> treated as a misconfiguration even
// though it equals the in-Java default. Forces an explicit positive value.
try {
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = 0 }"));
org.junit.Assert.fail("expected IllegalArgumentException for explicit 0");
} catch (IllegalArgumentException ex) {
org.junit.Assert.assertTrue(ex.getMessage(),
ex.getMessage().contains("constantCallTimeoutMs"));
}
}

@Test
public void testConstantCallTimeoutNegativeRejected() {
try {
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = -1 }"));
org.junit.Assert.fail("expected IllegalArgumentException for negative ms");
} catch (IllegalArgumentException ex) {
org.junit.Assert.assertTrue(ex.getMessage(),
ex.getMessage().contains("constantCallTimeoutMs"));
}
}

@Test
public void testConstantCallTimeoutOverflowRejected() {
long value = VmConfig.MAX_CONSTANT_CALL_TIMEOUT_MS + 1L;
try {
VmConfig.fromConfig(withRef("vm { constantCallTimeoutMs = " + value + " }"));
org.junit.Assert.fail("expected IllegalArgumentException for overflowing ms");
} catch (IllegalArgumentException ex) {
org.junit.Assert.assertTrue(ex.getMessage(),
ex.getMessage().contains("deadline conversion"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ private static void applyVmConfig(VmConfig vm) {
PARAMETER.saveInternalTx = vm.isSaveInternalTx();
PARAMETER.saveFeaturedInternalTx = vm.isSaveFeaturedInternalTx();
PARAMETER.saveCancelAllUnfreezeV2Details = vm.isSaveCancelAllUnfreezeV2Details();
PARAMETER.constantCallTimeoutMs = vm.getConstantCallTimeoutMs();
}

// Old applyStorageConfig removed — merged into applyStorageConfig()
Expand Down
12 changes: 12 additions & 0 deletions framework/src/main/resources/config.conf
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,18 @@ vm = {

# Indicates the max retry time for executing transaction in estimating energy. Default 3.
# estimateEnergyMaxRetry = 3

# Max TVM execution time (ms) for constant calls — applies to
# triggerconstantcontract, triggersmartcontract dispatched to view/pure
# functions, estimateenergy, eth_call, eth_estimateGas, and any other RPC
# routed through the constant-call path. When set, must be a positive
# integer that fits VM deadline conversion and is used verbatim as the
# per-call deadline (no clamp against the network's maxCpuTimeOfOneTx).
# Omit the property entirely to keep the default behaviour of sharing the
# block-processing deadline. Migration note: if previously running --debug
# to extend constant calls, switch to this option (--debug also extends
# block-processing, which is unsafe; see issue #6266).
# constantCallTimeoutMs = 100
}

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