Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,8 @@ public void execute(Object object) throws ContractExeException {
throw e;
}

VM.play(program, OperationRegistry.getTable());
// Prepare the table once for this execution and all nested calls.
VM.play(program, OperationRegistry.prepareAndGetTable(isConstantCall));
result = program.getResult();

if (VMConfig.allowEnergyAdjustment()) {
Expand Down
8 changes: 8 additions & 0 deletions actuator/src/main/java/org/tron/core/vm/Operation.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,12 @@ public void execute(Program program) {
public boolean isEnabled() {
return enabled.getAsBoolean();
}

public Operation adjustCost(Function<Program, Long> newCost) {
return new Operation(opcode, require, ret, newCost, action, enabled);
}

public Operation adjustAction(Consumer<Program> newAction) {
return new Operation(opcode, require, ret, cost, newAction, enabled);
}
}
187 changes: 94 additions & 93 deletions actuator/src/main/java/org/tron/core/vm/OperationRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,45 @@

public class OperationRegistry {

private static final Operation DEFAULT_MLOAD = new Operation(
Op.MLOAD, 1, 1, EnergyCost::getMloadCost, OperationActions::mLoadAction);

private static final Operation DEFAULT_MSTORE = new Operation(
Op.MSTORE, 2, 0, EnergyCost::getMStoreCost, OperationActions::mStoreAction);

private static final Operation DEFAULT_MSTORE8 = new Operation(
Op.MSTORE8, 2, 0, EnergyCost::getMStore8Cost, OperationActions::mStore8Action);

private static final Operation ADJUSTED_MLOAD =
DEFAULT_MLOAD.adjustCost(EnergyCost::getMloadCost2);

private static final Operation ADJUSTED_MSTORE =
DEFAULT_MSTORE.adjustCost(EnergyCost::getMStoreCost2);

private static final Operation ADJUSTED_MSTORE8 =
DEFAULT_MSTORE8.adjustCost(EnergyCost::getMStore8Cost2);

private static final Operation DEFAULT_VOTEWITNESS = new Operation(
Op.VOTEWITNESS, 4, 1, EnergyCost::getVoteWitnessCost,
OperationActions::voteWitnessAction, VMConfig::allowTvmVote);

private static final Operation ADJUSTED_VOTEWITNESS =
DEFAULT_VOTEWITNESS.adjustCost(EnergyCost::getVoteWitnessCost2);

private static final Operation OSAKA_VOTEWITNESS =
DEFAULT_VOTEWITNESS.adjustCost(EnergyCost::getVoteWitnessCost3);

private static final Operation DEFAULT_SUICIDE = new Operation(
Op.SUICIDE, 1, 0, EnergyCost::getSuicideCost, OperationActions::suicideAction);

private static final Operation ADJUSTED_SUICIDE =
DEFAULT_SUICIDE.adjustCost(EnergyCost::getSuicideCost2);

private static final Operation RESTRICTED_SUICIDE =
DEFAULT_SUICIDE.adjustCost(EnergyCost::getSuicideCost3)
.adjustAction(OperationActions::suicideAction2);

public enum Version {
TRON_V1_0,
TRON_V1_1,
TRON_V1_2,
TRON_V1_3,
TRON_V1_4,
TRON_V1_5,
// add more
// TRON_V2,
Expand All @@ -21,13 +54,21 @@ public enum Version {

private static final Map<Version, JumpTable> tableMap = new HashMap<>();

// The newest version in use. Bump this when a newer operation set is added,
// together with newLatestOperationSet() below.
private static final Version LATEST_VERSION = Version.TRON_V1_5;

static {
tableMap.put(Version.TRON_V1_0, newTronV10OperationSet());
tableMap.put(Version.TRON_V1_1, newTronV11OperationSet());
tableMap.put(Version.TRON_V1_2, newTronV12OperationSet());
tableMap.put(Version.TRON_V1_3, newTronV13OperationSet());
tableMap.put(Version.TRON_V1_4, newTronV14OperationSet());
tableMap.put(Version.TRON_V1_5, newTronV15OperationSet());
tableMap.put(LATEST_VERSION, newLatestOperationSet());
}

// Constant calls get a dedicated instance of the newest table, isolated from
// the shared consensus table above.
private static final JumpTable CONSTANT_CALL_TABLE = newLatestOperationSet();

// The single place that decides which operation set is the newest.
private static JumpTable newLatestOperationSet() {
return newTronV15OperationSet();
}

public static JumpTable newTronV10OperationSet() {
Expand Down Expand Up @@ -74,28 +115,22 @@ public static JumpTable newTronV15OperationSet() {
// Just for warming up class to avoid out_of_time
public static void init() {}

public static JumpTable getTable() {
// always get the table which has the newest version
JumpTable table = tableMap.get(Version.TRON_V1_5);

// next make the corresponding changes, exclude activating opcode
if (VMConfig.allowHigherLimitForMaxCpuTimeOfOneTx()) {
adjustMemOperations(table);
}

if (VMConfig.allowEnergyAdjustment()) {
adjustForFairEnergy(table);
}

if (VMConfig.allowTvmSelfdestructRestriction()) {
adjustSelfdestruct(table);
}
public static JumpTable prepareAndGetTable(boolean isConstantCall) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice change — just to confirm, are these config-based adjustments expected to stay stable once this table has been prepared?

JumpTable table = getTable(isConstantCall);
// Apply configuration-dependent changes once at the top level.
adjustTable(table);
return table;
}

if (VMConfig.allowTvmOsaka()) {
adjustVoteWitnessCost(table);
}
public static JumpTable getTable(boolean isConstantCall) {
return isConstantCall ? CONSTANT_CALL_TABLE : tableMap.get(LATEST_VERSION);
}

return table;
private static void adjustTable(JumpTable table) {
// Make the corresponding changes, excluding opcode activation.
adjustMemOperations(table);
adjustVoteWitness(table);
adjustSelfdestruct(table);
}

public static JumpTable newBaseOperationSet() {
Expand Down Expand Up @@ -331,20 +366,11 @@ public static JumpTable newBaseOperationSet() {
EnergyCost::getBaseTierCost,
OperationActions::popAction));

table.set(new Operation(
Op.MLOAD, 1, 1,
EnergyCost::getMloadCost,
OperationActions::mLoadAction));
table.set(DEFAULT_MLOAD);

table.set(new Operation(
Op.MSTORE, 2, 0,
EnergyCost::getMStoreCost,
OperationActions::mStoreAction));
table.set(DEFAULT_MSTORE);

table.set(new Operation(
Op.MSTORE8, 2, 0,
EnergyCost::getMStore8Cost,
OperationActions::mStore8Action));
table.set(DEFAULT_MSTORE8);

table.set(new Operation(
Op.SLOAD, 1, 1,
Expand Down Expand Up @@ -449,10 +475,7 @@ public static JumpTable newBaseOperationSet() {
EnergyCost::getRevertCost,
OperationActions::revertAction));

table.set(new Operation(
Op.SUICIDE, 1, 0,
EnergyCost::getSuicideCost,
OperationActions::suicideAction));
table.set(DEFAULT_SUICIDE);

return table;
}
Expand Down Expand Up @@ -570,11 +593,7 @@ public static void appendFreezeOperations(JumpTable table) {
public static void appendVoteOperations(JumpTable table) {
BooleanSupplier proposal = VMConfig::allowTvmVote;

table.set(new Operation(
Op.VOTEWITNESS, 4, 1,
EnergyCost::getVoteWitnessCost,
OperationActions::voteWitnessAction,
proposal));
table.set(DEFAULT_VOTEWITNESS);

table.set(new Operation(
Op.WITHDRAWREWARD, 0, 1,
Expand All @@ -593,23 +612,6 @@ public static void appendLondonOperations(JumpTable table) {
proposal));
}

public static void adjustMemOperations(JumpTable table) {
table.set(new Operation(
Op.MLOAD, 1, 1,
EnergyCost::getMloadCost2,
OperationActions::mLoadAction));

table.set(new Operation(
Op.MSTORE, 2, 0,
EnergyCost::getMStoreCost2,
OperationActions::mStoreAction));

table.set(new Operation(
Op.MSTORE8, 2, 0,
EnergyCost::getMStore8Cost2,
OperationActions::mStore8Action));
}

public static void appendFreezeV2Operations(JumpTable table) {
BooleanSupplier proposal = VMConfig::allowTvmFreezeV2;

Expand Down Expand Up @@ -664,19 +666,6 @@ public static void appendShangHaiOperations(JumpTable table) {
proposal));
}

public static void adjustForFairEnergy(JumpTable table) {
table.set(new Operation(
Op.VOTEWITNESS, 4, 1,
EnergyCost::getVoteWitnessCost2,
OperationActions::voteWitnessAction,
VMConfig::allowTvmVote));

table.set(new Operation(
Op.SUICIDE, 1, 0,
EnergyCost::getSuicideCost2,
OperationActions::suicideAction));
}

public static void appendCancunOperations(JumpTable table) {
BooleanSupplier proposal = VMConfig::allowTvmCancun;
BooleanSupplier tvmBlobProposal = VMConfig::allowTvmBlob;
Expand Down Expand Up @@ -722,18 +711,30 @@ public static void appendOsakaOperations(JumpTable table) {
proposal));
}

public static void adjustSelfdestruct(JumpTable table) {
table.set(new Operation(
Op.SUICIDE, 1, 0,
EnergyCost::getSuicideCost3,
OperationActions::suicideAction2));
public static void adjustMemOperations(JumpTable table) {
boolean adjusted = VMConfig.allowHigherLimitForMaxCpuTimeOfOneTx();
table.set(adjusted ? ADJUSTED_MLOAD : DEFAULT_MLOAD);
table.set(adjusted ? ADJUSTED_MSTORE : DEFAULT_MSTORE);
table.set(adjusted ? ADJUSTED_MSTORE8 : DEFAULT_MSTORE8);
}

public static void adjustVoteWitnessCost(JumpTable table) {
table.set(new Operation(
Op.VOTEWITNESS, 4, 1,
EnergyCost::getVoteWitnessCost3,
OperationActions::voteWitnessAction,
VMConfig::allowTvmVote));
public static void adjustVoteWitness(JumpTable table) {
if (VMConfig.allowTvmOsaka()) {
table.set(OSAKA_VOTEWITNESS);
} else if (VMConfig.allowEnergyAdjustment()) {
table.set(ADJUSTED_VOTEWITNESS);
} else {
table.set(DEFAULT_VOTEWITNESS);
}
}

public static void adjustSelfdestruct(JumpTable table) {
if (VMConfig.allowTvmSelfdestructRestriction()) {
table.set(RESTRICTED_SUICIDE);
} else if (VMConfig.allowEnergyAdjustment()) {
table.set(ADJUSTED_SUICIDE);
} else {
table.set(DEFAULT_SUICIDE);
}
}
}
6 changes: 4 additions & 2 deletions actuator/src/main/java/org/tron/core/vm/program/Program.java
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,8 @@ this, new DataWord(newAddress), getContractAddress(), value, DataWord.ZERO(),
if (VMConfig.allowTvmCompatibleEvm()) {
program.setContractVersion(getContractVersion());
}
VM.play(program, OperationRegistry.getTable());
// Reuse the table prepared by the top-level execution.
VM.play(program, OperationRegistry.getTable(isConstantCall()));
createResult = program.getResult();
getTrace().merge(program.getTrace());
// always commit nonce
Expand Down Expand Up @@ -1146,7 +1147,8 @@ this, new DataWord(contextAddress),
program.setContractVersion(invoke.getDeposit()
.getContract(codeAddress).getContractVersion());
}
VM.play(program, OperationRegistry.getTable());
// Reuse the table prepared by the top-level execution.
VM.play(program, OperationRegistry.getTable(isConstantCall()));
callResult = program.getResult();

getTrace().merge(program.getTrace());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.tron.common.runtime;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.same;

import java.lang.reflect.Field;
import java.util.Collections;
Expand All @@ -13,19 +15,70 @@
import org.tron.common.runtime.vm.LogInfo;
import org.tron.core.actuator.VMActuator;
import org.tron.core.db.TransactionContext;
import org.tron.core.vm.JumpTable;
import org.tron.core.vm.OperationRegistry;
import org.tron.core.vm.VM;
import org.tron.core.vm.config.VMConfig;
import org.tron.core.vm.program.Program;
import org.tron.core.vm.repository.Repository;

public class VMActuatorMockTest {

@BeforeClass
public static void init() {
// warm up the registry so VM.play(..., OperationRegistry.getTable()) arg eval is safe
// Warm up the registry before VM execution timing starts.
OperationRegistry.init();
}

@Test
public void constantCallUsesDedicatedJumpTable() throws Exception {
try (MockedStatic<VM> vmMock = Mockito.mockStatic(VM.class)) {
Program program = Mockito.mock(Program.class);
Mockito.when(program.getResult()).thenReturn(new ProgramResult());

VMActuator actuator = new VMActuator(true);
Field f = VMActuator.class.getDeclaredField("program");
f.setAccessible(true);
f.set(actuator, program);

TransactionContext context = Mockito.mock(TransactionContext.class);
Mockito.when(context.getProgramResult()).thenReturn(new ProgramResult());

actuator.execute(context);

JumpTable transactionTable = OperationRegistry.getTable(false);
JumpTable constantCallTable = OperationRegistry.getTable(true);
vmMock.verify(() -> VM.play(any(), same(constantCallTable)));
vmMock.verify(() -> VM.play(any(), argThat(table -> table != transactionTable)));
}
}

@Test
public void nonConstantCallUsesSharedJumpTable() throws Exception {
try (MockedStatic<VM> vmMock = Mockito.mockStatic(VM.class)) {
Program program = Mockito.mock(Program.class);
Mockito.when(program.getResult()).thenReturn(new ProgramResult());

VMActuator actuator = new VMActuator(false);
Field f = VMActuator.class.getDeclaredField("program");
f.setAccessible(true);
f.set(actuator, program);

Field repositoryField = VMActuator.class.getDeclaredField("rootRepository");
repositoryField.setAccessible(true);
repositoryField.set(actuator, Mockito.mock(Repository.class));

TransactionContext context = Mockito.mock(TransactionContext.class);
Mockito.when(context.getProgramResult()).thenReturn(new ProgramResult());

actuator.execute(context);

// The non-constant call must receive the shared consensus table itself.
JumpTable shared = OperationRegistry.getTable(false);
vmMock.verify(() -> VM.play(any(), same(shared)));
}
}

private void runCatchPathTest(Throwable thrownByVm, boolean osakaOn, int expectedSize)
throws Exception {
boolean prevOsaka = VMConfig.allowTvmOsaka();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public class OperationsTest extends BaseTest {

private ProgramInvokeMockImpl invoke;
private Program program;
private final JumpTable jumpTable = OperationRegistry.getTable();
private final JumpTable jumpTable = OperationRegistry.prepareAndGetTable(false);
@Autowired
private Wallet wallet;

Expand Down
Loading