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
22 changes: 22 additions & 0 deletions actuator/src/main/java/org/tron/core/vm/PrecompiledContracts.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static java.util.Arrays.copyOfRange;
import static org.tron.common.math.Maths.max;
import static org.tron.common.math.Maths.min;
import static org.tron.common.math.StrictMathWrapper.multiplyExact;
import static org.tron.common.math.StrictMathWrapper.subtractExact;
import static org.tron.common.runtime.vm.DataWord.WORD_SIZE;
import static org.tron.common.utils.BIUtil.addSafely;
import static org.tron.common.utils.BIUtil.isLessThan;
Expand Down Expand Up @@ -427,6 +429,14 @@ private static byte[] extractBytes(byte[] data, int offset, int len) {
return Arrays.copyOfRange(data, offset, offset + len);
}

private static boolean isValidAbiEncoding(byte[] data, int headerWords, int itemWords) {
if (data == null || data.length % WORD_SIZE != 0) {
return false;
}
long tail = subtractExact(data.length, multiplyExact(headerWords, WORD_SIZE));
return tail > 0 && tail % multiplyExact(itemWords, WORD_SIZE) == 0;
}

public abstract static class PrecompiledContract {

protected static final byte[] DATA_FALSE = new byte[WORD_SIZE];
Expand Down Expand Up @@ -1020,6 +1030,8 @@ public static class ValidateMultiSign extends PrecompiledContract {

private static final int ENGERYPERSIGN = 1500;
private static final int MAX_SIZE = 5;
private static final int ABI_HEADER_WORDS = 5;
private static final int ABI_ITEM_WORDS = 5;


@Override
Expand All @@ -1031,6 +1043,10 @@ public long getEnergyForData(byte[] data) {

@Override
public Pair<Boolean, byte[]> execute(byte[] rawData) {
if (VMConfig.allowTvmOsaka()
&& !isValidAbiEncoding(rawData, ABI_HEADER_WORDS, ABI_ITEM_WORDS)) {
return Pair.of(false, EMPTY_BYTE_ARRAY);
}
DataWord[] words = DataWord.parseArray(rawData);
byte[] address = words[0].toTronAddress();
int permissionId = words[1].intValueSafe();
Expand Down Expand Up @@ -1103,6 +1119,8 @@ public static class BatchValidateSign extends PrecompiledContract {
private static final String workersName = "validate-sign-contract";
private static final int ENGERYPERSIGN = 1500;
private static final int MAX_SIZE = 16;
private static final int ABI_HEADER_WORDS = 5;
private static final int ABI_ITEM_WORDS = 6;

static {
workers = ExecutorServiceManager.newFixedThreadPool(workersName,
Expand Down Expand Up @@ -1130,6 +1148,10 @@ public Pair<Boolean, byte[]> execute(byte[] data) {

private Pair<Boolean, byte[]> doExecute(byte[] data)
throws InterruptedException, ExecutionException {
if (VMConfig.allowTvmOsaka()
&& !isValidAbiEncoding(data, ABI_HEADER_WORDS, ABI_ITEM_WORDS)) {
return Pair.of(false, EMPTY_BYTE_ARRAY);
}
DataWord[] words = DataWord.parseArray(data);
byte[] hash = words[0].getData();

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));
}
}
17 changes: 17 additions & 0 deletions chainbase/src/main/java/org/tron/core/capsule/BlockCapsule.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

package org.tron.core.capsule;

import static org.tron.core.exception.BadBlockException.TypeEnum.CALC_MERKLE_ROOT_FAILED;

import com.google.common.primitives.Longs;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
Expand All @@ -37,6 +39,7 @@
import org.tron.common.utils.Time;
import org.tron.core.capsule.utils.MerkleTree;
import org.tron.core.config.Parameter.ChainConstant;
import org.tron.core.exception.BadBlockException;
import org.tron.core.exception.BadItemException;
import org.tron.core.exception.ValidateSignatureException;
import org.tron.core.store.AccountStore;
Expand All @@ -49,6 +52,7 @@
public class BlockCapsule implements ProtoCapsule<Block> {

public boolean generatedByMyself = false;
private volatile boolean merkleValidated = false;
@Getter
@Setter
private TransactionRetCapsule result;
Expand Down Expand Up @@ -225,6 +229,19 @@ public Sha256Hash calcMerkleRoot() {
return MerkleTree.getInstance().createTree(ids).getRoot().getHash();
}

public void validateMerkleRoot() throws BadBlockException {
if (merkleValidated) {
return;
}
Sha256Hash actual = calcMerkleRoot();
if (!actual.equals(getMerkleRoot())) {
throw new BadBlockException(CALC_MERKLE_ROOT_FAILED,
String.format("merkle root mismatch for block %d: expected %s, actual %s",
getNum(), getMerkleRoot(), actual));
}
merkleValidated = true;
}

public void setMerkleRoot() {
BlockHeader.raw blockHeaderRaw =
this.block.getBlockHeader().getRawData().toBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,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 Expand Up @@ -492,6 +504,9 @@ public class CommonParameter {
public long pendingTransactionTimeout;
@Getter
@Setter
public int maxTrxCacheSize;
@Getter
@Setter
public boolean nodeMetricsEnable = false;
@Getter
@Setter
Expand Down
1 change: 1 addition & 0 deletions common/src/main/java/org/tron/core/config/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public class NetConstants {
public static final int MSG_CACHE_DURATION_IN_BLOCKS = 5;
public static final int MAX_BLOCK_FETCH_PER_PEER = 100;
public static final int MAX_TRX_FETCH_PER_PEER = 1000;
public static final int MAX_SYNC_CHAIN_IDS = 30;
}

public class DatabaseConstants {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class NodeConfig {
private ChannelConfig channel = new ChannelConfig();
private int maxTransactionPendingSize = 2000;
private long pendingTransactionTimeout = 60000;
private int maxTrxCacheSize = 50_000;
private int agreeNodeCount = 0;
private boolean openHistoryQueryWhenLiteFN = false;
private boolean unsolidifiedBlockCheck = false;
Expand Down Expand Up @@ -498,6 +499,11 @@ private void postProcess() {
if (dynamicConfig.checkInterval <= 0) {
dynamicConfig.checkInterval = 600;
}

// maxTrxCacheSize: minimum 2000
if (maxTrxCacheSize < 2000) {
maxTrxCacheSize = 2000;
}
}

// ===========================================================================
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;
}
}
}
2 changes: 2 additions & 0 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ node {
receiveTcpMinDataLength = 2048
maxTransactionPendingSize = 2000
pendingTransactionTimeout = 60000
# total cached trx across handler queues + pending + rePush
maxTrxCacheSize = 50000

# Consensus agreement
agreeNodeCount = 0
Expand Down
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"));
}
}
}
Loading
Loading