From 76af4fa58e0397863d4ac384d13b350e365b9d9c Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 25 Jun 2026 21:13:50 +0800 Subject: [PATCH 1/2] fix(vm): self-dispatch vote-witness cost by proposal flag getVoteWitnessCost3 falls back to cost2 when Osaka is off, and cost2 to the legacy cost when energy adjustment is off. The energy then stays correct even if a stale cost function lingers in the shared jump table after a reorg, or is read by a constant call whose view has the proposal off. --- .../java/org/tron/core/vm/EnergyCost.java | 8 +++ .../runtime/vm/VoteWitnessCost3Test.java | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/actuator/src/main/java/org/tron/core/vm/EnergyCost.java b/actuator/src/main/java/org/tron/core/vm/EnergyCost.java index 3641548b3e5..a2c4b59fdc5 100644 --- a/actuator/src/main/java/org/tron/core/vm/EnergyCost.java +++ b/actuator/src/main/java/org/tron/core/vm/EnergyCost.java @@ -365,6 +365,10 @@ public static long getVoteWitnessCost(Program program) { } public static long getVoteWitnessCost2(Program program) { + if (!VMConfig.allowEnergyAdjustment()) { + return getVoteWitnessCost(program); + } + Stack stack = program.getStack(); long oldMemSize = program.getMemSize(); DataWord amountArrayLength = stack.get(stack.size() - 1).clone(); @@ -388,6 +392,10 @@ public static long getVoteWitnessCost2(Program program) { } public static long getVoteWitnessCost3(Program program) { + if (!VMConfig.allowTvmOsaka()) { + return getVoteWitnessCost2(program); + } + Stack stack = program.getStack(); long oldMemSize = program.getMemSize(); BigInteger amountArrayLength = stack.get(stack.size() - 1).value(); diff --git a/framework/src/test/java/org/tron/common/runtime/vm/VoteWitnessCost3Test.java b/framework/src/test/java/org/tron/common/runtime/vm/VoteWitnessCost3Test.java index 75b11f4ab9d..2c7aa238033 100644 --- a/framework/src/test/java/org/tron/common/runtime/vm/VoteWitnessCost3Test.java +++ b/framework/src/test/java/org/tron/common/runtime/vm/VoteWitnessCost3Test.java @@ -1,7 +1,9 @@ package org.tron.common.runtime.vm; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -142,11 +144,14 @@ public void testLargeArrayLengthOverflow() { zeroOffset, largeLength, 0); boolean overflowCaught = false; + VMConfig.initAllowTvmOsaka(1); // cost3 self-dispatches; exercise the v3 (BigInteger) path try { EnergyCost.getVoteWitnessCost3(program); } catch (Program.OutOfMemoryException e) { // cost3 should detect memory overflow via checkMemorySize overflowCaught = true; + } finally { + VMConfig.initAllowTvmOsaka(0); } assertTrue("cost3 should throw memoryOverflow for huge array length", overflowCaught); } @@ -161,10 +166,13 @@ public void testLargeOffsetOverflow() { new DataWord(0), new DataWord(1), 0); boolean overflowCaught = false; + VMConfig.initAllowTvmOsaka(1); // cost3 self-dispatches; exercise the v3 (BigInteger) path try { EnergyCost.getVoteWitnessCost3(program); } catch (Program.OutOfMemoryException e) { overflowCaught = true; + } finally { + VMConfig.initAllowTvmOsaka(0); } assertTrue("cost3 should throw memoryOverflow for huge offset", overflowCaught); } @@ -239,4 +247,49 @@ public void testOperationRegistryWithOsaka() { VMConfig.initAllowTvmOsaka(0); } } + + @Test + public void testCost3FallsBackToCost2WhenOsakaOff() { + // cost3 self-dispatches: with osaka off it delegates to cost2, so a cost3 left in the shared + // jump table (e.g. read by a constant call whose view has osaka off) still charges v2 energy. + String maxHex = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; + DataWord huge = new DataWord(maxHex); + DataWord zero = new DataWord(0); + + VMConfig.initAllowTvmOsaka(0); + long viaCost3 = + EnergyCost.getVoteWitnessCost3(mockProgram(zero, new DataWord(1), zero, huge, 0)); + long viaCost2 = + EnergyCost.getVoteWitnessCost2(mockProgram(zero, new DataWord(1), zero, huge, 0)); + assertEquals("cost3 must equal cost2 when osaka is off", viaCost2, viaCost3); + + // sanity: with osaka on, cost3 instead runs v3 and detects the overflow that cost2 wraps away. + VMConfig.initAllowTvmOsaka(1); + try { + EnergyCost.getVoteWitnessCost3(mockProgram(zero, new DataWord(1), zero, huge, 0)); + fail("cost3 with osaka on must overflow-throw on the huge length"); + } catch (Program.OutOfMemoryException expected) { + // expected + } finally { + VMConfig.initAllowTvmOsaka(0); + } + } + + @Test + public void testCost2FallsBackToLegacyWhenEnergyAdjustmentOff() { + // cost2 self-dispatches: with energyAdjustment off it delegates to the legacy cost. + VMConfig.initAllowEnergyAdjustment(0); + try { + long viaCost2 = EnergyCost.getVoteWitnessCost2(mockProgram(0, 2, 128, 2, 0)); + long viaLegacy = EnergyCost.getVoteWitnessCost(mockProgram(0, 2, 128, 2, 0)); + assertEquals("cost2 must equal legacy cost when energyAdjustment is off", + viaLegacy, viaCost2); + + // sanity: with energyAdjustment on, cost2 differs from the legacy cost for this input. + VMConfig.initAllowEnergyAdjustment(1); + assertNotEquals(viaLegacy, EnergyCost.getVoteWitnessCost2(mockProgram(0, 2, 128, 2, 0))); + } finally { + VMConfig.initAllowEnergyAdjustment(1); + } + } } From 1f8e8e58bbc74bcc88ddaa01ef92e4a6f0187999 Mon Sep 17 00:00:00 2001 From: Asuka Date: Thu, 25 Jun 2026 21:13:52 +0800 Subject: [PATCH 2/2] fix(vm): isolate constant-call config view to prevent global pollution Constant calls bound to a lagging solidity/PBFT snapshot loaded their flags into the process-global VMConfig, racing block processing during a proposal's activation-to-solidification window and risking a fork. Route a constant call's config into a thread-local snapshot; the block/broadcast path keeps writing (and reading) the global. --- .../org/tron/core/actuator/VMActuator.java | 2 +- .../org/tron/core/vm/config/ConfigLoader.java | 66 ++--- .../org/tron/core/vm/config/VMConfig.java | 226 ++++++++++-------- .../src/main/java/org/tron/core/Wallet.java | 11 +- .../runtime/vm/VMConfigIsolationTest.java | 108 +++++++++ 5 files changed, 278 insertions(+), 135 deletions(-) create mode 100644 framework/src/test/java/org/tron/common/runtime/vm/VMConfigIsolationTest.java diff --git a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java index f604013325e..d785951027b 100644 --- a/actuator/src/main/java/org/tron/core/actuator/VMActuator.java +++ b/actuator/src/main/java/org/tron/core/actuator/VMActuator.java @@ -120,7 +120,7 @@ public void validate(Object object) throws ContractValidateException { } // Load Config - ConfigLoader.load(context.getStoreFactory()); + ConfigLoader.load(context.getStoreFactory(), isConstantCall); // Warm up registry class OperationRegistry.init(); trx = context.getTrxCap().getInstance(); diff --git a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java index 881eb861bea..35480935742 100644 --- a/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java +++ b/actuator/src/main/java/org/tron/core/vm/config/ConfigLoader.java @@ -13,40 +13,48 @@ public class ConfigLoader { //only for unit test public static boolean disable = false; - public static void load(StoreFactory storeFactory) { + // isolate=true: a constant call bound to a non-HEAD (solidity/PBFT) snapshot installs its + // snapshot into a thread-local view instead of the process-wide global, so it cannot pollute + // the flags the block-processing path reads concurrently. + public static void load(StoreFactory storeFactory, boolean isolate) { if (!disable) { DynamicPropertiesStore ds = storeFactory.getChainBaseManager().getDynamicPropertiesStore(); VMConfig.setVmTrace(CommonParameter.getInstance().isVmTrace()); if (ds != null) { VMConfig.initVmHardFork(checkForEnergyLimit(ds)); - VMConfig.initAllowMultiSign(ds.getAllowMultiSign()); - VMConfig.initAllowTvmTransferTrc10(ds.getAllowTvmTransferTrc10()); - VMConfig.initAllowTvmConstantinople(ds.getAllowTvmConstantinople()); - VMConfig.initAllowTvmSolidity059(ds.getAllowTvmSolidity059()); - VMConfig.initAllowShieldedTRC20Transaction(ds.getAllowShieldedTRC20Transaction()); - VMConfig.initAllowTvmIstanbul(ds.getAllowTvmIstanbul()); - VMConfig.initAllowTvmFreeze(ds.getAllowTvmFreeze()); - VMConfig.initAllowTvmVote(ds.getAllowTvmVote()); - VMConfig.initAllowTvmLondon(ds.getAllowTvmLondon()); - VMConfig.initAllowTvmCompatibleEvm(ds.getAllowTvmCompatibleEvm()); - VMConfig.initAllowHigherLimitForMaxCpuTimeOfOneTx( - ds.getAllowHigherLimitForMaxCpuTimeOfOneTx()); - VMConfig.initAllowTvmFreezeV2(ds.supportUnfreezeDelay() ? 1 : 0); - VMConfig.initAllowOptimizedReturnValueOfChainId( - ds.getAllowOptimizedReturnValueOfChainId()); - VMConfig.initAllowDynamicEnergy(ds.getAllowDynamicEnergy()); - VMConfig.initDynamicEnergyThreshold(ds.getDynamicEnergyThreshold()); - VMConfig.initDynamicEnergyIncreaseFactor(ds.getDynamicEnergyIncreaseFactor()); - VMConfig.initDynamicEnergyMaxFactor(ds.getDynamicEnergyMaxFactor()); - VMConfig.initAllowTvmShangHai(ds.getAllowTvmShangHai()); - VMConfig.initAllowEnergyAdjustment(ds.getAllowEnergyAdjustment()); - VMConfig.initAllowStrictMath(ds.getAllowStrictMath()); - VMConfig.initAllowTvmCancun(ds.getAllowTvmCancun()); - VMConfig.initDisableJavaLangMath(ds.getConsensusLogicOptimization()); - VMConfig.initAllowTvmBlob(ds.getAllowTvmBlob()); - VMConfig.initAllowTvmSelfdestructRestriction(ds.getAllowTvmSelfdestructRestriction()); - VMConfig.initAllowTvmOsaka(ds.getAllowTvmOsaka()); - VMConfig.initAllowHardenResourceCalculation(ds.getAllowHardenResourceCalculation()); + VMConfig.Snapshot snapshot = new VMConfig.Snapshot(); + snapshot.allowMultiSign = ds.getAllowMultiSign() == 1; + snapshot.allowTvmTransferTrc10 = ds.getAllowTvmTransferTrc10() == 1; + snapshot.allowTvmConstantinople = ds.getAllowTvmConstantinople() == 1; + snapshot.allowTvmSolidity059 = ds.getAllowTvmSolidity059() == 1; + snapshot.allowShieldedTRC20Transaction = ds.getAllowShieldedTRC20Transaction() == 1; + snapshot.allowTvmIstanbul = ds.getAllowTvmIstanbul() == 1; + snapshot.allowTvmFreeze = ds.getAllowTvmFreeze() == 1; + snapshot.allowTvmVote = ds.getAllowTvmVote() == 1; + snapshot.allowTvmLondon = ds.getAllowTvmLondon() == 1; + snapshot.allowTvmCompatibleEvm = ds.getAllowTvmCompatibleEvm() == 1; + snapshot.allowHigherLimitForMaxCpuTimeOfOneTx = + ds.getAllowHigherLimitForMaxCpuTimeOfOneTx() == 1; + snapshot.allowTvmFreezeV2 = ds.supportUnfreezeDelay(); + snapshot.allowOptimizedReturnValueOfChainId = ds.getAllowOptimizedReturnValueOfChainId() == 1; + snapshot.allowDynamicEnergy = ds.getAllowDynamicEnergy() == 1; + snapshot.dynamicEnergyThreshold = ds.getDynamicEnergyThreshold(); + snapshot.dynamicEnergyIncreaseFactor = ds.getDynamicEnergyIncreaseFactor(); + snapshot.dynamicEnergyMaxFactor = ds.getDynamicEnergyMaxFactor(); + snapshot.allowTvmShanghai = ds.getAllowTvmShangHai() == 1; + snapshot.allowEnergyAdjustment = ds.getAllowEnergyAdjustment() == 1; + snapshot.allowStrictMath = ds.getAllowStrictMath() == 1; + snapshot.allowTvmCancun = ds.getAllowTvmCancun() == 1; + snapshot.disableJavaLangMath = ds.getConsensusLogicOptimization() == 1; + snapshot.allowTvmBlob = ds.getAllowTvmBlob() == 1; + snapshot.allowTvmSelfdestructRestriction = ds.getAllowTvmSelfdestructRestriction() == 1; + snapshot.allowTvmOsaka = ds.getAllowTvmOsaka() == 1; + snapshot.allowHardenResourceCalculation = ds.getAllowHardenResourceCalculation() == 1; + if (isolate) { + VMConfig.setLocalSnapshot(snapshot); + } else { + VMConfig.setGlobalSnapshot(snapshot); + } } } } diff --git a/common/src/main/java/org/tron/core/vm/config/VMConfig.java b/common/src/main/java/org/tron/core/vm/config/VMConfig.java index 94c1e50284e..304ced33698 100644 --- a/common/src/main/java/org/tron/core/vm/config/VMConfig.java +++ b/common/src/main/java/org/tron/core/vm/config/VMConfig.java @@ -13,57 +13,74 @@ public class VMConfig { @Setter private static boolean vmTrace = false; - private static boolean ALLOW_TVM_TRANSFER_TRC10 = false; - - private static boolean ALLOW_TVM_CONSTANTINOPLE = false; - - private static boolean ALLOW_MULTI_SIGN = false; - - private static boolean ALLOW_TVM_SOLIDITY_059 = false; - - private static boolean ALLOW_SHIELDED_TRC20_TRANSACTION = false; - - private static boolean ALLOW_TVM_ISTANBUL = false; - - private static boolean ALLOW_TVM_FREEZE = false; - - private static boolean ALLOW_TVM_VOTE = false; - - private static boolean ALLOW_TVM_LONDON = false; - - private static boolean ALLOW_TVM_COMPATIBLE_EVM = false; - - private static boolean ALLOW_HIGHER_LIMIT_FOR_MAX_CPU_TIME_OF_ONE_TX = false; - - private static boolean ALLOW_TVM_FREEZE_V2 = false; - - private static boolean ALLOW_OPTIMIZED_RETURN_VALUE_OF_CHAIN_ID = false; - - private static boolean ALLOW_DYNAMIC_ENERGY = false; - - private static long DYNAMIC_ENERGY_THRESHOLD = 0L; - - private static long DYNAMIC_ENERGY_INCREASE_FACTOR = 0L; - - private static long DYNAMIC_ENERGY_MAX_FACTOR = 0L; - - private static boolean ALLOW_TVM_SHANGHAI = false; - - private static boolean ALLOW_ENERGY_ADJUSTMENT = false; - - private static boolean ALLOW_STRICT_MATH = false; - - private static boolean ALLOW_TVM_CANCUN = false; - - private static Boolean DISABLE_JAVA_LANG_MATH = false; - - private static boolean ALLOW_TVM_BLOB = false; - - private static boolean ALLOW_TVM_SELFDESTRUCT_RESTRICTION = false; - - private static boolean ALLOW_TVM_OSAKA = false; - - private static boolean ALLOW_HARDEN_RESOURCE_CALCULATION = false; + /** + * Snapshot of all chain/store-derived VM config flags. The block-processing (HEAD) path + * installs it as the process-wide {@link #globalSnapshot}; a constant call executing against a + * non-HEAD (solidity/PBFT) snapshot installs its own view into {@link #localSnapshot} so it never + * overwrites the flags the consensus path relies on. + */ + public static class Snapshot { + public boolean allowTvmTransferTrc10; + public boolean allowTvmConstantinople; + public boolean allowMultiSign; + public boolean allowTvmSolidity059; + public boolean allowShieldedTRC20Transaction; + public boolean allowTvmIstanbul; + public boolean allowTvmFreeze; + public boolean allowTvmVote; + public boolean allowTvmLondon; + public boolean allowTvmCompatibleEvm; + public boolean allowHigherLimitForMaxCpuTimeOfOneTx; + public boolean allowTvmFreezeV2; + public boolean allowOptimizedReturnValueOfChainId; + public boolean allowDynamicEnergy; + public long dynamicEnergyThreshold; + public long dynamicEnergyIncreaseFactor; + public long dynamicEnergyMaxFactor; + public boolean allowTvmShanghai; + public boolean allowEnergyAdjustment; + public boolean allowStrictMath; + public boolean allowTvmCancun; + public boolean disableJavaLangMath; + public boolean allowTvmBlob; + public boolean allowTvmSelfdestructRestriction; + public boolean allowTvmOsaka; + public boolean allowHardenResourceCalculation; + } + + // HEAD / block-processing config, written by the consensus path; read by everyone with no + // thread-local override. volatile so a wholesale install is safely published across threads. + private static volatile Snapshot globalSnapshot = new Snapshot(); + + // Per-thread override used only by constant calls bound to a non-HEAD (solidity/PBFT) snapshot. + private static final ThreadLocal localSnapshot = new ThreadLocal<>(); + + private static Snapshot current() { + Snapshot local = localSnapshot.get(); + return local != null ? local : globalSnapshot; + } + + /** + * Install the process-wide (HEAD / block-processing) config and drop any thread-local view. + */ + public static void setGlobalSnapshot(Snapshot snapshot) { + globalSnapshot = snapshot; + localSnapshot.remove(); + } + + /** + * Install a thread-local config view for a constant call executing against a non-HEAD snapshot. + */ + public static void setLocalSnapshot(Snapshot snapshot) { + localSnapshot.set(snapshot); + } + + /** + * Drop the thread-local config view so this thread falls back to the global config. + */ + public static void clearLocalSnapshot() { + localSnapshot.remove(); + } private VMConfig() { } @@ -80,108 +97,111 @@ public static void initVmHardFork(boolean pass) { CommonParameter.ENERGY_LIMIT_HARD_FORK = pass; } + // The init* setters below mutate the global (HEAD) config in place. They are kept for tests and + // legacy callers; production config loading goes through ConfigLoader -> setGlobalSnapshot, which + // publishes a fresh Snapshot wholesale via the volatile field. public static void initAllowMultiSign(long allow) { - ALLOW_MULTI_SIGN = allow == 1; + globalSnapshot.allowMultiSign = allow == 1; } public static void initAllowTvmTransferTrc10(long allow) { - ALLOW_TVM_TRANSFER_TRC10 = allow == 1; + globalSnapshot.allowTvmTransferTrc10 = allow == 1; } public static void initAllowTvmConstantinople(long allow) { - ALLOW_TVM_CONSTANTINOPLE = allow == 1; + globalSnapshot.allowTvmConstantinople = allow == 1; } public static void initAllowTvmSolidity059(long allow) { - ALLOW_TVM_SOLIDITY_059 = allow == 1; + globalSnapshot.allowTvmSolidity059 = allow == 1; } public static void initAllowShieldedTRC20Transaction(long allow) { - ALLOW_SHIELDED_TRC20_TRANSACTION = allow == 1; + globalSnapshot.allowShieldedTRC20Transaction = allow == 1; } public static void initAllowTvmIstanbul(long allow) { - ALLOW_TVM_ISTANBUL = allow == 1; + globalSnapshot.allowTvmIstanbul = allow == 1; } public static void initAllowTvmFreeze(long allow) { - ALLOW_TVM_FREEZE = allow == 1; + globalSnapshot.allowTvmFreeze = allow == 1; } public static void initAllowTvmVote(long allow) { - ALLOW_TVM_VOTE = allow == 1; + globalSnapshot.allowTvmVote = allow == 1; } public static void initAllowTvmLondon(long allow) { - ALLOW_TVM_LONDON = allow == 1; + globalSnapshot.allowTvmLondon = allow == 1; } public static void initAllowTvmCompatibleEvm(long allow) { - ALLOW_TVM_COMPATIBLE_EVM = allow == 1; + globalSnapshot.allowTvmCompatibleEvm = allow == 1; } public static void initAllowHigherLimitForMaxCpuTimeOfOneTx(long allow) { - ALLOW_HIGHER_LIMIT_FOR_MAX_CPU_TIME_OF_ONE_TX = allow == 1; + globalSnapshot.allowHigherLimitForMaxCpuTimeOfOneTx = allow == 1; } public static void initAllowTvmFreezeV2(long allow) { - ALLOW_TVM_FREEZE_V2 = allow == 1; + globalSnapshot.allowTvmFreezeV2 = allow == 1; } public static void initAllowOptimizedReturnValueOfChainId(long allow) { - ALLOW_OPTIMIZED_RETURN_VALUE_OF_CHAIN_ID = allow == 1; + globalSnapshot.allowOptimizedReturnValueOfChainId = allow == 1; } public static void initAllowDynamicEnergy(long allow) { - ALLOW_DYNAMIC_ENERGY = allow == 1; + globalSnapshot.allowDynamicEnergy = allow == 1; } public static void initDynamicEnergyThreshold(long threshold) { - DYNAMIC_ENERGY_THRESHOLD = threshold; + globalSnapshot.dynamicEnergyThreshold = threshold; } public static void initDynamicEnergyIncreaseFactor(long increaseFactor) { - DYNAMIC_ENERGY_INCREASE_FACTOR = increaseFactor; + globalSnapshot.dynamicEnergyIncreaseFactor = increaseFactor; } public static void initDynamicEnergyMaxFactor(long maxFactor) { - DYNAMIC_ENERGY_MAX_FACTOR = maxFactor; + globalSnapshot.dynamicEnergyMaxFactor = maxFactor; } public static void initAllowTvmShangHai(long allow) { - ALLOW_TVM_SHANGHAI = allow == 1; + globalSnapshot.allowTvmShanghai = allow == 1; } public static void initAllowEnergyAdjustment(long allow) { - ALLOW_ENERGY_ADJUSTMENT = allow == 1; + globalSnapshot.allowEnergyAdjustment = allow == 1; } public static void initAllowStrictMath(long allow) { - ALLOW_STRICT_MATH = allow == 1; + globalSnapshot.allowStrictMath = allow == 1; } public static void initAllowTvmCancun(long allow) { - ALLOW_TVM_CANCUN = allow == 1; + globalSnapshot.allowTvmCancun = allow == 1; } public static void initDisableJavaLangMath(long allow) { - DISABLE_JAVA_LANG_MATH = allow == 1; + globalSnapshot.disableJavaLangMath = allow == 1; } public static void initAllowTvmBlob(long allow) { - ALLOW_TVM_BLOB = allow == 1; + globalSnapshot.allowTvmBlob = allow == 1; } public static void initAllowTvmSelfdestructRestriction(long allow) { - ALLOW_TVM_SELFDESTRUCT_RESTRICTION = allow == 1; + globalSnapshot.allowTvmSelfdestructRestriction = allow == 1; } public static void initAllowTvmOsaka(long allow) { - ALLOW_TVM_OSAKA = allow == 1; + globalSnapshot.allowTvmOsaka = allow == 1; } public static void initAllowHardenResourceCalculation(long allow) { - ALLOW_HARDEN_RESOURCE_CALCULATION = allow == 1; + globalSnapshot.allowHardenResourceCalculation = allow == 1; } public static boolean getEnergyLimitHardFork() { @@ -189,106 +209,106 @@ public static boolean getEnergyLimitHardFork() { } public static boolean allowTvmTransferTrc10() { - return ALLOW_TVM_TRANSFER_TRC10; + return current().allowTvmTransferTrc10; } public static boolean allowTvmConstantinople() { - return ALLOW_TVM_CONSTANTINOPLE; + return current().allowTvmConstantinople; } public static boolean allowMultiSign() { - return ALLOW_MULTI_SIGN; + return current().allowMultiSign; } public static boolean allowTvmSolidity059() { - return ALLOW_TVM_SOLIDITY_059; + return current().allowTvmSolidity059; } public static boolean allowShieldedTRC20Transaction() { - return ALLOW_SHIELDED_TRC20_TRANSACTION; + return current().allowShieldedTRC20Transaction; } public static boolean allowTvmIstanbul() { - return ALLOW_TVM_ISTANBUL; + return current().allowTvmIstanbul; } public static boolean allowTvmFreeze() { - return ALLOW_TVM_FREEZE; + return current().allowTvmFreeze; } public static boolean allowTvmVote() { - return ALLOW_TVM_VOTE; + return current().allowTvmVote; } public static boolean allowTvmLondon() { - return ALLOW_TVM_LONDON; + return current().allowTvmLondon; } public static boolean allowTvmCompatibleEvm() { - return ALLOW_TVM_COMPATIBLE_EVM; + return current().allowTvmCompatibleEvm; } public static boolean allowHigherLimitForMaxCpuTimeOfOneTx() { - return ALLOW_HIGHER_LIMIT_FOR_MAX_CPU_TIME_OF_ONE_TX; + return current().allowHigherLimitForMaxCpuTimeOfOneTx; } public static boolean allowTvmFreezeV2() { - return ALLOW_TVM_FREEZE_V2; + return current().allowTvmFreezeV2; } public static boolean allowOptimizedReturnValueOfChainId() { - return ALLOW_OPTIMIZED_RETURN_VALUE_OF_CHAIN_ID; + return current().allowOptimizedReturnValueOfChainId; } public static boolean allowDynamicEnergy() { - return ALLOW_DYNAMIC_ENERGY; + return current().allowDynamicEnergy; } public static long getDynamicEnergyThreshold() { - return DYNAMIC_ENERGY_THRESHOLD; + return current().dynamicEnergyThreshold; } public static long getDynamicEnergyIncreaseFactor() { - return DYNAMIC_ENERGY_INCREASE_FACTOR; + return current().dynamicEnergyIncreaseFactor; } public static long getDynamicEnergyMaxFactor() { - return DYNAMIC_ENERGY_MAX_FACTOR; + return current().dynamicEnergyMaxFactor; } public static boolean allowTvmShanghai() { - return ALLOW_TVM_SHANGHAI; + return current().allowTvmShanghai; } public static boolean allowEnergyAdjustment() { - return ALLOW_ENERGY_ADJUSTMENT; + return current().allowEnergyAdjustment; } public static boolean allowStrictMath() { - return ALLOW_STRICT_MATH; + return current().allowStrictMath; } public static boolean allowTvmCancun() { - return ALLOW_TVM_CANCUN; + return current().allowTvmCancun; } public static boolean disableJavaLangMath() { - return DISABLE_JAVA_LANG_MATH; + return current().disableJavaLangMath; } public static boolean allowTvmBlob() { - return ALLOW_TVM_BLOB; + return current().allowTvmBlob; } public static boolean allowTvmSelfdestructRestriction() { - return ALLOW_TVM_SELFDESTRUCT_RESTRICTION; + return current().allowTvmSelfdestructRestriction; } public static boolean allowTvmOsaka() { - return ALLOW_TVM_OSAKA; + return current().allowTvmOsaka; } public static boolean allowHardenResourceCalculation() { - return ALLOW_HARDEN_RESOURCE_CALCULATION; + return current().allowHardenResourceCalculation; } } diff --git a/framework/src/main/java/org/tron/core/Wallet.java b/framework/src/main/java/org/tron/core/Wallet.java index 079b8e6f3e9..ac54cb2b7ff 100755 --- a/framework/src/main/java/org/tron/core/Wallet.java +++ b/framework/src/main/java/org/tron/core/Wallet.java @@ -202,6 +202,7 @@ import org.tron.core.store.VotesStore; import org.tron.core.store.WitnessStore; import org.tron.core.utils.TransactionUtil; +import org.tron.core.vm.config.VMConfig; import org.tron.core.vm.program.Program; import org.tron.core.zen.ShieldedTRC20ParametersBuilder; import org.tron.core.zen.ShieldedTRC20ParametersBuilder.ShieldedTRC20ParametersType; @@ -3157,8 +3158,14 @@ public Transaction callConstantContract(TransactionCapsule trxCap, StoreFactory.getInstance(), true, false); VMActuator vmActuator = new VMActuator(true); - vmActuator.validate(context); - vmActuator.execute(context); + try { + vmActuator.validate(context); + vmActuator.execute(context); + } finally { + // constant call runs on a pooled RPC worker; drop its thread-local VM config view so it + // can never leak into a later (block/broadcast) execution on the same thread. + VMConfig.clearLocalSnapshot(); + } ProgramResult result = context.getProgramResult(); if (!isEstimating && result.getException() != null diff --git a/framework/src/test/java/org/tron/common/runtime/vm/VMConfigIsolationTest.java b/framework/src/test/java/org/tron/common/runtime/vm/VMConfigIsolationTest.java new file mode 100644 index 00000000000..845db6dd6af --- /dev/null +++ b/framework/src/test/java/org/tron/common/runtime/vm/VMConfigIsolationTest.java @@ -0,0 +1,108 @@ +package org.tron.common.runtime.vm; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.tron.core.vm.config.VMConfig; + +public class VMConfigIsolationTest { + + // Tests mutate the process-wide static VMConfig; snapshot it before each test and restore it + // after so this class never pollutes other VM tests sharing the same JVM fork (forkEvery=100). + private VMConfig.Snapshot savedGlobal; + + @Before + public void snapshotConfig() { + VMConfig.clearLocalSnapshot(); + savedGlobal = snapshotGlobal(); + } + + @After + public void restoreConfig() { + VMConfig.clearLocalSnapshot(); + VMConfig.setGlobalSnapshot(savedGlobal); + } + + /** + * A constant call's thread-local config view must not pollute the global config that the + * (concurrent) block-processing path reads. This is the core Problem-2 guarantee. + */ + @Test + public void testLocalConfigDoesNotPolluteGlobalAcrossThreads() throws InterruptedException { + VMConfig.initAllowTvmOsaka(1); // global (HEAD) view: activated + assertTrue(VMConfig.allowTvmOsaka()); // no thread-local -> reads global + + VMConfig.Snapshot local = new VMConfig.Snapshot(); + local.allowTvmOsaka = false; // simulate a not-yet-solidified snapshot + VMConfig.setLocalSnapshot(local); + + // this thread now sees its own (solidity) view... + assertFalse(VMConfig.allowTvmOsaka()); + + // ...but another thread (e.g. block processing) must still see the global HEAD value. + AtomicBoolean otherThreadSaw = new AtomicBoolean(false); + Thread t = new Thread(() -> otherThreadSaw.set(VMConfig.allowTvmOsaka())); + t.start(); + t.join(); + assertTrue("global config must be unaffected by another thread's local view", + otherThreadSaw.get()); + + // after dropping the local view, this thread falls back to the global value again. + VMConfig.clearLocalSnapshot(); + assertTrue(VMConfig.allowTvmOsaka()); + } + + /** + * A block/broadcast load (setGlobalSnapshot) must drop any thread-local view, so the consensus + * path can never read a constant call's leaked snapshot left on the same pooled worker thread. + */ + @Test + public void testSetGlobalConfigDropsLocalView() { + VMConfig.Snapshot local = new VMConfig.Snapshot(); + local.allowTvmOsaka = true; + VMConfig.setLocalSnapshot(local); + assertTrue(VMConfig.allowTvmOsaka()); + + VMConfig.Snapshot head = new VMConfig.Snapshot(); + head.allowTvmOsaka = false; + VMConfig.setGlobalSnapshot(head); + assertFalse("setGlobalSnapshot must drop the thread-local view", VMConfig.allowTvmOsaka()); + } + + // Deep-copy the current global config through the public getters (no thread-local set here, so + // the getters read the global) so @After can restore the exact prior state. + private static VMConfig.Snapshot snapshotGlobal() { + VMConfig.Snapshot snapshot = new VMConfig.Snapshot(); + snapshot.allowTvmTransferTrc10 = VMConfig.allowTvmTransferTrc10(); + snapshot.allowTvmConstantinople = VMConfig.allowTvmConstantinople(); + snapshot.allowMultiSign = VMConfig.allowMultiSign(); + snapshot.allowTvmSolidity059 = VMConfig.allowTvmSolidity059(); + snapshot.allowShieldedTRC20Transaction = VMConfig.allowShieldedTRC20Transaction(); + snapshot.allowTvmIstanbul = VMConfig.allowTvmIstanbul(); + snapshot.allowTvmFreeze = VMConfig.allowTvmFreeze(); + snapshot.allowTvmVote = VMConfig.allowTvmVote(); + snapshot.allowTvmLondon = VMConfig.allowTvmLondon(); + snapshot.allowTvmCompatibleEvm = VMConfig.allowTvmCompatibleEvm(); + snapshot.allowHigherLimitForMaxCpuTimeOfOneTx = VMConfig.allowHigherLimitForMaxCpuTimeOfOneTx(); + snapshot.allowTvmFreezeV2 = VMConfig.allowTvmFreezeV2(); + snapshot.allowOptimizedReturnValueOfChainId = VMConfig.allowOptimizedReturnValueOfChainId(); + snapshot.allowDynamicEnergy = VMConfig.allowDynamicEnergy(); + snapshot.dynamicEnergyThreshold = VMConfig.getDynamicEnergyThreshold(); + snapshot.dynamicEnergyIncreaseFactor = VMConfig.getDynamicEnergyIncreaseFactor(); + snapshot.dynamicEnergyMaxFactor = VMConfig.getDynamicEnergyMaxFactor(); + snapshot.allowTvmShanghai = VMConfig.allowTvmShanghai(); + snapshot.allowEnergyAdjustment = VMConfig.allowEnergyAdjustment(); + snapshot.allowStrictMath = VMConfig.allowStrictMath(); + snapshot.allowTvmCancun = VMConfig.allowTvmCancun(); + snapshot.disableJavaLangMath = VMConfig.disableJavaLangMath(); + snapshot.allowTvmBlob = VMConfig.allowTvmBlob(); + snapshot.allowTvmSelfdestructRestriction = VMConfig.allowTvmSelfdestructRestriction(); + snapshot.allowTvmOsaka = VMConfig.allowTvmOsaka(); + snapshot.allowHardenResourceCalculation = VMConfig.allowHardenResourceCalculation(); + return snapshot; + } +}