diff --git a/app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index 354386f0491..8d928c5a98b 100644 --- a/app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/app/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -2987,6 +2987,7 @@ private String generateConfigurationOverview() { .setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation()) .setWorldStateUpdateMode(unstableEvmOptions.toDomainObject().worldUpdaterMode()) .setEnabledOpcodeOptimizations(unstableEvmOptions.toDomainObject().enableOptimizedOpcodes()) + .setEvmV2(unstableEvmOptions.toDomainObject().enableEvmV2()) .setPluginContext(this.besuPluginContext) .setHistoryExpiryPruneEnabled(getDataStorageConfiguration().getHistoryExpiryPruneEnabled()) .setBlobDBSettings(rocksDBPlugin.getBlobDBSettings()); diff --git a/app/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java b/app/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java index 7d6352ec3bf..0f9a72cfea6 100644 --- a/app/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java +++ b/app/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java @@ -65,6 +65,7 @@ public class ConfigurationOverviewBuilder { private TransactionPoolConfiguration.Implementation txPoolImplementation; private EvmConfiguration.WorldUpdaterMode worldStateUpdateMode; private boolean enabledOpcodeOptimizations; + private boolean evmV2 = false; private Map environment; private BesuPluginContextImpl besuPluginContext; private boolean isHistoryExpiryPruneEnabled = false; @@ -320,6 +321,17 @@ public ConfigurationOverviewBuilder setEnabledOpcodeOptimizations( return this; } + /** + * Sets whether the experimental EVM v2 (long[] stack) is enabled. + * + * @param evmV2 true if --Xevm-go-fast / --Xevm-v2 is enabled + * @return the builder + */ + public ConfigurationOverviewBuilder setEvmV2(final boolean evmV2) { + this.evmV2 = evmV2; + return this; + } + /** * Sets the engine jwt file path. * @@ -513,7 +525,11 @@ public String build() { lines.add("Using " + worldStateUpdateMode + " worldstate update mode"); - lines.add("Opcode optimizations " + (enabledOpcodeOptimizations ? "enabled" : "disabled")); + if (evmV2) { + lines.add("Experimental EVM v2 (long[] stack) enabled"); + } else { + lines.add("Opcode optimizations " + (enabledOpcodeOptimizations ? "enabled" : "disabled")); + } if (isParallelTxProcessingEnabled) { lines.add("Parallel transaction processing enabled"); diff --git a/app/src/main/java/org/hyperledger/besu/cli/options/EvmOptions.java b/app/src/main/java/org/hyperledger/besu/cli/options/EvmOptions.java index 8bbdc2c0d0d..371dd61775c 100644 --- a/app/src/main/java/org/hyperledger/besu/cli/options/EvmOptions.java +++ b/app/src/main/java/org/hyperledger/besu/cli/options/EvmOptions.java @@ -32,6 +32,9 @@ public class EvmOptions implements CLIOptions { /** The constant OPTIMIZED_OP_CODES. */ public static final String OPTIMIZED_OP_CODES = "--Xevm-optimized-opcodes"; + /** The constant EVM_V2. */ + public static final String EVM_V2 = "--Xevm-v2"; + /** Default constructor. */ EvmOptions() {} @@ -72,10 +75,18 @@ public static EvmOptions create() { arity = "1") private boolean enableOptimizedOpcodes = true; + @CommandLine.Option( + names = {EVM_V2, "--Xevm-go-fast"}, + description = "Enable experimental EVM v2 with long[] stack representation (default: false)", + fallbackValue = "false", + hidden = true, + arity = "1") + private boolean enableEvmV2 = false; + @Override public EvmConfiguration toDomainObject() { return new EvmConfiguration( - jumpDestCacheWeightKilobytes, worldstateUpdateMode, enableOptimizedOpcodes); + jumpDestCacheWeightKilobytes, worldstateUpdateMode, enableOptimizedOpcodes, enableEvmV2); } @Override diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java new file mode 100644 index 00000000000..c9e16edbd0e --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/AddOperationBenchmarkV2.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.v2.AddOperationV2; + +public class AddOperationBenchmarkV2 extends BinaryOperationBenchmarkV2 { + + @Override + protected Operation.OperationResult invoke(final MessageFrame frame) { + return AddOperationV2.staticOperation(frame, frame.stackDataV2()); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java new file mode 100644 index 00000000000..fa062fd5fb0 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BenchmarkHelperV2.java @@ -0,0 +1,104 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Random; + +import org.apache.tuweni.bytes.Bytes32; + +public class BenchmarkHelperV2 { + /** + * Creates a minimal {@link MessageFrame} suitable for opcode benchmarks. + * + *

The frame is configured with mocked dependencies and deterministic zero/default values. + * + * @return a message-call frame ready to use in benchmark setup + */ + public static MessageFrame createMessageCallFrame() { + return MessageFrame.builder() + .enableEvmV2(true) + .worldUpdater(mock(WorldUpdater.class)) + .originator(Address.ZERO) + .gasPrice(Wei.ONE) + .blobGasPrice(Wei.ONE) + .blockValues(mock(BlockValues.class)) + .miningBeneficiary(Address.ZERO) + .blockHashLookup((__, ___) -> Hash.ZERO) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(Long.MAX_VALUE) + .address(Address.ZERO) + .contract(Address.ZERO) + .inputData(Bytes32.ZERO) + .sender(Address.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(Code.EMPTY_CODE) + .completer(__ -> {}) + .build(); + } + + /** + * Fills an array with random UInt256 values. + * + * @param pool the destination array + */ + public static void fillUInt256Pool(final org.hyperledger.besu.evm.UInt256[] pool) { + final Random random = new Random(); + for (int i = 0; i < pool.length; i++) { + final byte[] a = new byte[1 + random.nextInt(32)]; + random.nextBytes(a); + pool[i] = bytesToUInt256(a); + } + } + + /** + * Converts a byte array to UInt256 (left-pads to 32 bytes). + * + * @param bytes the byte array + * @return the UInt256 value + */ + static org.hyperledger.besu.evm.UInt256 bytesToUInt256(final byte[] bytes) { + final byte[] padded = new byte[32]; + System.arraycopy(bytes, 0, padded, 32 - bytes.length, bytes.length); + return org.hyperledger.besu.evm.UInt256.fromBytesBE(padded); + } + + /** + * Pushes a UInt256 value onto the frame's stack by writing limbs directly. + * + * @param frame the message frame + * @param value the UInt256 value to push + */ + static void pushUInt256(final MessageFrame frame, final org.hyperledger.besu.evm.UInt256 value) { + final long[] s = frame.stackDataV2(); + final int top = frame.stackTopV2(); + final int dst = top << 2; + s[dst] = value.u3(); + s[dst + 1] = value.u2(); + s[dst + 2] = value.u1(); + s[dst + 3] = value.u0(); + frame.setTopV2(top + 1); + } +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryOperationBenchmarkV2.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryOperationBenchmarkV2.java new file mode 100644 index 00000000000..c5e1f5262c7 --- /dev/null +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/v2/BinaryOperationBenchmarkV2.java @@ -0,0 +1,71 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.vm.operations.v2; + +import org.hyperledger.besu.evm.UInt256; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.Operation; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Thread) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(value = TimeUnit.NANOSECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@BenchmarkMode(Mode.AverageTime) +public abstract class BinaryOperationBenchmarkV2 { + + protected static final int SAMPLE_SIZE = 30_000; + + protected UInt256[] aPool; + protected UInt256[] bPool; + protected int index; + protected MessageFrame frame; + + @Setup() + public void setUp() { + frame = BenchmarkHelperV2.createMessageCallFrame(); + aPool = new UInt256[SAMPLE_SIZE]; + bPool = new UInt256[SAMPLE_SIZE]; + BenchmarkHelperV2.fillUInt256Pool(aPool); + BenchmarkHelperV2.fillUInt256Pool(bPool); + index = 0; + } + + @Benchmark + public void executeOperation(final Blackhole blackhole) { + BenchmarkHelperV2.pushUInt256(frame, bPool[index]); + BenchmarkHelperV2.pushUInt256(frame, aPool[index]); + + blackhole.consume(invoke(frame)); + + frame.setTopV2(frame.stackTopV2() - 1); + + index = (index + 1) % SAMPLE_SIZE; + } + + protected abstract Operation.OperationResult invoke(MessageFrame frame); +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index d64720fecab..47ae1fdc74c 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -326,7 +326,13 @@ void execute(final InputStream input, final PrintWriter output, final String[] a addForkHelp(commandLine.getSubcommands().get("t8n")); addForkHelp(commandLine.getSubcommands().get("t8n-server")); - commandLine.setExecutionStrategy(new CommandLine.RunLast()); + commandLine.setExecutionStrategy( + parseResult -> { + if (daggerOptions.isEvmV2Enabled()) { + out.println("EVM v2 (long[] stack) enabled"); + } + return new CommandLine.RunLast().execute(parseResult); + }); commandLine.execute(args); } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java index 6e5ad098913..0f254d90d8e 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java @@ -142,11 +142,29 @@ BlockParameter provideBlockParameter() { arity = "1") private boolean enableOptimizedOpcodes = true; + @CommandLine.Option( + names = {"--Xevm-go-fast", "--Xevm-v2"}, + description = "Enable experimental EVM v2 with long[] stack representation (default: false)", + fallbackValue = "false", + defaultValue = "false", + hidden = true, + arity = "1") + private boolean enableEvmV2 = false; + @Provides @Singleton EvmConfiguration provideEvmConfiguration() { return new EvmConfiguration( - jumpDestCacheWeightKilobytes, worldstateUpdateMode, enableOptimizedOpcodes); + jumpDestCacheWeightKilobytes, worldstateUpdateMode, enableOptimizedOpcodes, enableEvmV2); + } + + /** + * Returns whether experimental EVM v2 is enabled. Used by the tool to emit a startup notice. + * + * @return true if EVM v2 is enabled + */ + public boolean isEvmV2Enabled() { + return enableEvmV2; } /** Default constructor for the EvmToolCommandOptionsModule class. */ diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index 9752e7b841b..83dfef073b8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -84,6 +84,7 @@ import org.hyperledger.besu.evm.operation.VirtualOperation; import org.hyperledger.besu.evm.operation.XorOperation; import org.hyperledger.besu.evm.operation.XorOperationOptimized; +import org.hyperledger.besu.evm.operation.v2.AddOperationV2; import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Optional; @@ -216,6 +217,10 @@ public Optional getChainId() { // // Please benchmark before refactoring. public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { + if (evmConfiguration.enableEvmV2()) { + runToHaltV2(frame, tracing); + return; + } evmSpecVersion.maybeWarnVersion(); var operationTracer = tracing == OperationTracer.NO_TRACING ? null : tracing; @@ -439,6 +444,70 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { } } + /** + * EVM v2 execution loop using long[] stack representation. Only opcodes explicitly listed in the + * switch are handled via the v2 path; all others fall through to the v1 operation registry. This + * skeleton stub establishes the dispatch structure for incremental v2 operation rollout. + */ + // Note: like runToHalt, this is performance-critical code. Benchmark before refactoring. + private void runToHaltV2(final MessageFrame frame, final OperationTracer tracing) { + evmSpecVersion.maybeWarnVersion(); + + var operationTracer = tracing == OperationTracer.NO_TRACING ? null : tracing; + byte[] code = frame.getCode().getBytes().toArrayUnsafe(); + Operation[] operationArray = operations.getOperations(); + while (frame.getState() == MessageFrame.State.CODE_EXECUTING) { + Operation currentOperation; + int opcode; + int pc = frame.getPC(); + try { + opcode = code[pc] & 0xff; + currentOperation = operationArray[opcode]; + } catch (ArrayIndexOutOfBoundsException aiiobe) { + opcode = 0; + currentOperation = endOfScriptStop; + } + frame.setCurrentOperation(currentOperation); + if (operationTracer != null) { + operationTracer.tracePreExecution(frame); + } + + OperationResult result; + try { + result = + switch (opcode) { + case 0x01 -> AddOperationV2.staticOperation(frame, frame.stackDataV2()); + // TODO: implement remaining opcodes in v2; until then fall through to v1 + default -> { + frame.setCurrentOperation(currentOperation); + yield currentOperation.execute(frame, this); + } + }; + } catch (final OverflowException oe) { + result = OVERFLOW_RESPONSE; + } catch (final UnderflowException ue) { + result = UNDERFLOW_RESPONSE; + } + final ExceptionalHaltReason haltReason = result.getHaltReason(); + if (haltReason != null) { + LOG.trace("MessageFrame evaluation halted because of {}", haltReason); + frame.setExceptionalHaltReason(Optional.of(haltReason)); + frame.setState(MessageFrame.State.EXCEPTIONAL_HALT); + } else if (frame.decrementRemainingGas(result.getGasCost()) < 0) { + frame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); + frame.setState(MessageFrame.State.EXCEPTIONAL_HALT); + } + if (frame.getState() == MessageFrame.State.CODE_EXECUTING) { + final int currentPC = frame.getPC(); + final int opSize = result.getPcIncrement(); + frame.setPC(currentPC + opSize); + } + if (operationTracer != null) { + operationTracer.tracePostExecution(frame, result); + } + } + } + /** * Get Operations (unsafe) * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index bdce7e34c13..a90da960e38 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -204,6 +204,10 @@ public enum Type { private int pc; private final Memory memory = new Memory(); private final OperandStack stack; + // EVM v2 stack: 4 longs per 256-bit word (index 0 = most significant, index 3 = least + // significant) + private final long[] stackDataV2; + private int stackTopV2; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; private Code createdCode = null; @@ -249,6 +253,7 @@ public static Builder builder() { } private MessageFrame( + final boolean enableEvmV2, final Type type, final WorldUpdater worldUpdater, final long initialGas, @@ -271,6 +276,8 @@ private MessageFrame( this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; this.stack = new OperandStack(txValues.maxStackSize()); + this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null; + this.stackTopV2 = 0; this.pc = 0; this.recipient = recipient; this.contract = contract; @@ -471,6 +478,49 @@ public int stackSize() { return stack.size(); } + // region --- EVM v2 long[] stack operations --- + // --------------------------------------------------------------------------- + + /** + * Returns the backing long[] array of the operand stack. + * + * @return the raw data array + */ + public long[] stackDataV2() { + return stackDataV2; + } + + /** + * Returns the current stack top (item count, 0 = empty). + * + * @return the item count + */ + public int stackTopV2() { + return stackTopV2; + } + + /** + * Sets the stack top (item count). Used after StackMath operations. + * + * @param newTop the new item count + */ + public void setTopV2(final int newTop) { + this.stackTopV2 = newTop; + } + + /** + * Returns true if the stack has at least {@code n} items. + * + * @param n the number of items required + * @return true if the stack contains at least n items + */ + public boolean stackHasItems(final int n) { + return stackTopV2 >= n; + } + + // --------------------------------------------------------------------------- + // endregion + /** * Returns whether the message frame is static or not. * @@ -1424,6 +1474,8 @@ public static class Builder { private Optional> versionedHashes = Optional.empty(); + private boolean enableEvmV2 = false; + /** Instantiates a new Builder. */ public Builder() { // constructor added to deal with JavaDoc linting rules. @@ -1717,6 +1769,17 @@ public Builder versionedHashes(final Optional> versionedHash return this; } + /** + * Sets whether the experimental EVM v2 (long[] stack) is enabled. + * + * @param enableEvmV2 true to enable EVM v2 + * @return the builder + */ + public Builder enableEvmV2(final boolean enableEvmV2) { + this.enableEvmV2 = enableEvmV2; + return this; + } + private void validate() { if (parentMessageFrame == null) { checkState(worldUpdater != null, "Missing message frame world updater"); @@ -1776,6 +1839,7 @@ public MessageFrame build() { MessageFrame messageFrame = new MessageFrame( + enableEvmV2, type, updater, initialGas, diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java index 0b8783aa11a..2cc15f14703 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java @@ -25,6 +25,7 @@ * @param jumpDestCacheWeightKB the jump destination cache weight in kb * @param worldUpdaterMode the world updater mode * @param enableOptimizedOpcodes enable optimized implementation of certain opcodes in the EVM + * @param enableEvmV2 enable experimental EVM v2 with long[] stack representation * @param evmStackSize the maximum evm stack size * @param maxCodeSizeOverride An optional override of the maximum code size set by the EVM fork * @param maxInitcodeSizeOverride An optional override of the maximum initcode size set by the EVM @@ -34,6 +35,7 @@ public record EvmConfiguration( long jumpDestCacheWeightKB, WorldUpdaterMode worldUpdaterMode, boolean enableOptimizedOpcodes, + boolean enableEvmV2, Integer evmStackSize, Optional maxCodeSizeOverride, Optional maxInitcodeSizeOverride) { @@ -50,7 +52,7 @@ public enum WorldUpdaterMode { /** The constant DEFAULT. */ public static final EvmConfiguration DEFAULT = - new EvmConfiguration(32_000L, WorldUpdaterMode.STACKED, true); + new EvmConfiguration(32_000L, WorldUpdaterMode.STACKED, true, false); /** * Create an EVM Configuration without any overrides @@ -67,6 +69,30 @@ public EvmConfiguration( jumpDestCacheWeightKilobytes, worldstateUpdateMode, enableOptimizedOpcodes, + false, + MessageFrame.DEFAULT_MAX_STACK_SIZE, + Optional.empty(), + Optional.empty()); + } + + /** + * Create an EVM Configuration without any overrides, with explicit EVM v2 flag + * + * @param jumpDestCacheWeightKilobytes the jump dest cache weight (in kibibytes) + * @param worldstateUpdateMode the world update mode + * @param enableOptimizedOpcodes enabled opcode optimizations + * @param enableEvmV2 enable experimental EVM v2 with long[] stack representation + */ + public EvmConfiguration( + final Long jumpDestCacheWeightKilobytes, + final WorldUpdaterMode worldstateUpdateMode, + final boolean enableOptimizedOpcodes, + final boolean enableEvmV2) { + this( + jumpDestCacheWeightKilobytes, + worldstateUpdateMode, + enableOptimizedOpcodes, + enableEvmV2, MessageFrame.DEFAULT_MAX_STACK_SIZE, Optional.empty(), Optional.empty()); @@ -98,6 +124,7 @@ public EvmConfiguration overrides( jumpDestCacheWeightKB, worldUpdaterMode, enableOptimizedOpcodes, + enableEvmV2, newEvmStackSize.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE), newMaxCodeSize.isPresent() ? Optional.of(newMaxCodeSize.getAsInt()) : Optional.empty(), newMaxInitcodeSize.isPresent() diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java new file mode 100644 index 00000000000..95e059d9605 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AbstractFixedCostOperationV2.java @@ -0,0 +1,93 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation.v2; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.OverflowException; +import org.hyperledger.besu.evm.internal.UnderflowException; +import org.hyperledger.besu.evm.operation.AbstractOperation; + +/** The Abstract fixed cost operation. */ +abstract class AbstractFixedCostOperationV2 extends AbstractOperation { + + /** Shared underflow response for static operation methods. */ + static final OperationResult UNDERFLOW_RESPONSE = + new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + + /** The Success response. */ + protected final OperationResult successResponse; + + /** The Out of gas response. */ + protected final OperationResult outOfGasResponse; + + private final OperationResult underflowResponse; + private final OperationResult overflowResponse; + + /** The Gas cost. */ + protected final long gasCost; + + /** + * Instantiates a new Abstract fixed cost operation. + * + * @param opcode the opcode + * @param name the name + * @param stackItemsConsumed the stack items consumed + * @param stackItemsProduced the stack items produced + * @param gasCalculator the gas calculator + * @param fixedCost the fixed cost + */ + protected AbstractFixedCostOperationV2( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator, + final long fixedCost) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + gasCost = fixedCost; + successResponse = new OperationResult(gasCost, null); + outOfGasResponse = new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_GAS); + underflowResponse = + new OperationResult(gasCost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + overflowResponse = new OperationResult(gasCost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); + } + + @Override + public final OperationResult execute(final MessageFrame frame, final EVM evm) { + try { + if (frame.getRemainingGas() < gasCost) { + return outOfGasResponse; + } else { + return executeFixedCostOperation(frame, evm); + } + } catch (final UnderflowException ufe) { + return underflowResponse; + } catch (final OverflowException ofe) { + return overflowResponse; + } + } + + /** + * Execute fixed cost operation. + * + * @param frame the frame + * @param evm the evm + * @return the operation result + */ + protected abstract OperationResult executeFixedCostOperation(MessageFrame frame, EVM evm); +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java new file mode 100644 index 00000000000..4c866bcf61f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/v2/AddOperationV2.java @@ -0,0 +1,62 @@ +/* + * Copyright contributors to Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation.v2; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.Operation; + +/** + * EVM v2 ADD operation using long[] stack representation. + * + *

Each 256-bit word is stored as four longs: index 0 = most significant 64 bits, index 3 = least + * significant 64 bits. Addition is performed with carry propagation from least to most significant + * word, with overflow silently truncated (mod 2^256). + */ +public class AddOperationV2 extends AbstractFixedCostOperationV2 { + + @SuppressWarnings("UnusedVariable") + private static final Operation.OperationResult ADD_SUCCESS = + new Operation.OperationResult(3, null); + + /** + * Instantiates a new Add operation. + * + * @param gasCalculator the gas calculator + */ + public AddOperationV2(final GasCalculator gasCalculator) { + super(0x01, "ADD", 2, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + return staticOperation(frame, frame.stackDataV2()); + } + + /** + * Execute the ADD opcode on the v2 long[] stack. + * + * @param frame the message frame + * @param stackData the stack operands as a long[] array + * @return the operation result + */ + @SuppressWarnings("DoNotCallSuggester") + public static Operation.OperationResult staticOperation( + final MessageFrame frame, final long[] stackData) { + throw new UnsupportedOperationException("ADD operation not yet implemented for evm v2"); + } +}