Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d63e96e
Migrate wei operations to EVM v2 (first commit)
ahamlat Apr 13, 2026
ff6d03e
Update SelfBalance benchmarks
ahamlat Apr 13, 2026
de6a840
spotless
ahamlat Apr 13, 2026
436c308
Address comments.
ahamlat Apr 15, 2026
b7d8cab
Merge branch 'main' into wei-opcodes-on-evm-v2
ahamlat Apr 15, 2026
edcc531
Add Javadoc
ahamlat Apr 15, 2026
f969516
Add unit tests
ahamlat Apr 16, 2026
648b921
Merge branch 'main' into wei-opcodes-on-evm-v2
ahamlat Apr 16, 2026
3ecd7d3
spotless
ahamlat Apr 16, 2026
8009532
Update datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java
ahamlat Apr 17, 2026
3cdf3af
Update evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.…
ahamlat Apr 17, 2026
310ffbf
Update evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.…
ahamlat Apr 17, 2026
e38f455
Update datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java
ahamlat Apr 17, 2026
6a30d3f
Merge branch 'main' into wei-opcodes-on-evm-v2
ahamlat Apr 17, 2026
c406646
Apply refactoring changes
ahamlat Apr 17, 2026
0820192
remove basefee field
ahamlat Apr 20, 2026
bb8e572
Merge branch 'main' into wei-opcodes-on-evm-v2
ahamlat Apr 20, 2026
8ed8bfb
Fix merge issue
ahamlat Apr 20, 2026
5c79af7
Address more comments
ahamlat Apr 20, 2026
5acec25
Remove not used field
ahamlat Apr 20, 2026
1826010
spotless
ahamlat Apr 20, 2026
98b66d1
Undo Balance operation change and add more unit tests
ahamlat Apr 21, 2026
89ab0d2
Merge branch 'main' into wei-opcodes-on-evm-v2
ahamlat Apr 21, 2026
aec17bc
Update AddOperationV2 after merge with main
ahamlat Apr 21, 2026
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
22 changes: 22 additions & 0 deletions datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java
Comment thread
ahamlat marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
*/
package org.hyperledger.besu.datatypes;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.math.BigInteger;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Locale;

Expand All @@ -25,6 +28,9 @@
/** A particular quantity of Wei, the Ethereum currency. */
public final class Wei extends BaseUInt256Value<Wei> implements Quantity {

private static final VarHandle LONG_BE =
MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);

/** The constant ZERO. */
public static final Wei ZERO = of(0);

Expand Down Expand Up @@ -125,6 +131,22 @@ public static Wei fromEth(final long eth) {
return Wei.of(BigInteger.valueOf(eth).multiply(BigInteger.TEN.pow(18)));
}

/**
* Writes the 4 big-endian long limbs into the target array at the given offset. This enables
* zero-allocation transfer to the EVM operand stack.
Comment thread
ahamlat marked this conversation as resolved.
Outdated
*
* @param target the target long array (typically the EVM stack)
* @param off the offset into the array where u3 should be written
*/
public void writeLimbs(final long[] target, final int off) {
Comment thread
ahamlat marked this conversation as resolved.
Outdated
// TODO store this representation at build time when switching from v1 to v2
Comment thread
ahamlat marked this conversation as resolved.
Outdated
final byte[] b = toArrayUnsafe();
target[off] = (long) LONG_BE.get(b, 0);
target[off + 1] = (long) LONG_BE.get(b, 8);
target[off + 2] = (long) LONG_BE.get(b, 16);
target[off + 3] = (long) LONG_BE.get(b, 24);
}

@Override
public BigInteger getAsBigInteger() {
return toBigInteger();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@
import org.hyperledger.besu.evm.operation.SelfBalanceOperation;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import org.apache.tuweni.bytes.Bytes;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
Expand All @@ -44,6 +41,7 @@
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 = 2, time = 3, timeUnit = TimeUnit.SECONDS)
Expand All @@ -52,65 +50,41 @@
@BenchmarkMode(Mode.AverageTime)
public class SelfBalanceOperationBenchmark {

private static final int NUMBER_ADDRESSES = 20000;
private static final Random RANDOM_GENERATOR = new Random();
private SelfBalanceOperation operation;
private MessageFrame[] frames;
private int executingFrameIndex = 0;

private MessageFrame createMessageFrame(
final Blockchain blockchain,
final WorldUpdater worldUpdater,
final ExecutionContextTestFixture executionContextTestFixture,
final Address address) {
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
return new MessageFrameTestFixture()
.address(address)
.worldUpdater(worldUpdater)
.blockHeader(blockHeader)
.executionContextTestFixture(executionContextTestFixture)
.blockchain(blockchain)
.build();
}

private WorldUpdater createWorldUpdater(final Blockchain blockchain, final Address[] addresses)
throws IOException {
final WorldUpdater worldStateUpdater;
try (WorldStateArchive worldStateArchive = createBonsaiInMemoryWorldStateArchive(blockchain)) {
worldStateUpdater = worldStateArchive.getWorldState().updater();
}
for (Address address : addresses) {
worldStateUpdater.getOrCreate(address).setBalance(Wei.of(1));
}
worldStateUpdater.commit();
return worldStateUpdater;
}
private MessageFrame frame;

@Setup
public void prepare() throws Exception {
operation = new SelfBalanceOperation(mock(GasCalculator.class));
final Blockchain blockchain = mock(Blockchain.class);
final Address[] addresses = new Address[NUMBER_ADDRESSES];
for (int j = 0; j < addresses.length; j++) {
final byte[] address = new byte[Address.SIZE];
RANDOM_GENERATOR.nextBytes(address);
addresses[j] = Address.wrap(Bytes.wrap(address));
final Address address = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef");

final WorldUpdater worldUpdater;
try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) {
worldUpdater = archive.getWorldState().updater();
}
frames = new MessageFrame[NUMBER_ADDRESSES];
final WorldUpdater worldUpdater = createWorldUpdater(blockchain, addresses);
worldUpdater.getOrCreate(address).setBalance(Wei.of(1));
worldUpdater.commit();

final ExecutionContextTestFixture executionContextTestFixture =
ExecutionContextTestFixture.create();
for (int i = 0; i < NUMBER_ADDRESSES; i++) {
frames[i] =
createMessageFrame(blockchain, worldUpdater, executionContextTestFixture, addresses[0]);
}
final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader();
frame =
new MessageFrameTestFixture()
.address(address)
.worldUpdater(worldUpdater)
.blockHeader(blockHeader)
.executionContextTestFixture(executionContextTestFixture)
.blockchain(blockchain)
.build();

// Pre-warm: force trie path into memory
worldUpdater.get(address);
}

@Benchmark
public void executeOperation() {
final MessageFrame executingFrame = frames[executingFrameIndex++];
operation.execute(executingFrame, null);
executingFrame.popStackItem();
executingFrameIndex = executingFrameIndex % NUMBER_ADDRESSES;
public void executeOperation(final Blackhole blackhole) {
blackhole.consume(operation.execute(frame, null));
frame.popStackItem();
}
}
Comment thread
ahamlat marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive;
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.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;
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.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.v2.operation.SelfBalanceOperationV2;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

import java.util.concurrent.TimeUnit;

import org.apache.tuweni.bytes.Bytes32;
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 = 2, time = 3, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@BenchmarkMode(Mode.AverageTime)
public class SelfBalanceOperationBenchmarkV2 {

private SelfBalanceOperationV2 operation;
private MessageFrame frame;

@Setup
public void setup() throws Exception {
operation = new SelfBalanceOperationV2(mock(GasCalculator.class));
final Blockchain blockchain = mock(Blockchain.class);
final Address address = Address.fromHexString("0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef");

final WorldUpdater worldUpdater;
try (WorldStateArchive archive = createBonsaiInMemoryWorldStateArchive(blockchain)) {
worldUpdater = archive.getWorldState().updater();
}
worldUpdater.getOrCreate(address).setBalance(Wei.of(1));
worldUpdater.commit();

frame =
MessageFrame.builder()
.enableEvmV2(true)
.worldUpdater(worldUpdater)
.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)
.contract(address)
.inputData(Bytes32.ZERO)
.sender(Address.ZERO)
.value(Wei.ZERO)
.apparentValue(Wei.ZERO)
.code(Code.EMPTY_CODE)
.completer(__ -> {})
.build();

// Pre-warm: force trie path into memory
Comment thread
ahamlat marked this conversation as resolved.
worldUpdater.get(address);
}

@Benchmark
public void executeOperation(final Blackhole blackhole) {
blackhole.consume(operation.execute(frame, null));
frame.setTopV2(frame.stackTopV2() - 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public class ProcessableBlockHeader
protected final long timestamp;
// base fee is included for post EIP-1559 blocks
protected final Wei baseFee;
Comment thread
ahamlat marked this conversation as resolved.
Outdated
// store optional base fee to compute it at build time
Comment thread
ahamlat marked this conversation as resolved.
protected final Optional<Wei> maybeBaseFee;
// prevRandao is included for post-merge blocks
protected final Bytes32 mixHashOrPrevRandao;
// parentBeaconBlockRoot is included for Cancun
Expand All @@ -67,6 +69,7 @@ protected ProcessableBlockHeader(
this.gasLimit = gasLimit;
this.timestamp = timestamp;
this.baseFee = baseFee;
this.maybeBaseFee = Optional.ofNullable(baseFee);
this.mixHashOrPrevRandao = mixHashOrPrevRandao;
this.parentBeaconBlockRoot = parentBeaconBlockRoot;
this.slotNumber = slotNumber;
Expand Down Expand Up @@ -149,7 +152,7 @@ public long getTimestamp() {
*/
@Override
public Optional<Wei> getBaseFee() {
return Optional.ofNullable(baseFee);
return maybeBaseFee;
}

/**
Expand Down Expand Up @@ -216,7 +219,9 @@ public String toString() {
sb.append("difficulty=").append(difficulty).append(", ");
sb.append("gasLimit=").append(gasLimit).append(", ");
sb.append("timestamp=").append(timestamp).append(", ");
sb.append("baseFee=").append(baseFee).append(", ");
if (maybeBaseFee.isPresent()) {
sb.append("baseFee=").append(maybeBaseFee.get()).append(", ");
}
sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", ");
if (parentBeaconBlockRoot != null) {
sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", ");
Expand Down
12 changes: 12 additions & 0 deletions evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public enum Type {
// significant)
private final long[] stackDataV2;
private int stackTopV2;
private final int stackMaxSizeV2;
private Bytes output = Bytes.EMPTY;
private Bytes returnData = Bytes.EMPTY;
private Code createdCode = null;
Expand Down Expand Up @@ -282,6 +283,7 @@ private MessageFrame(
this.stack = new OperandStack(txValues.maxStackSize());
this.stackDataV2 = enableEvmV2 ? new long[txValues.maxStackSize() * 4] : null;
this.stackTopV2 = 0;
this.stackMaxSizeV2 = txValues.maxStackSize();
this.pc = 0;
this.recipient = recipient;
this.contract = contract;
Expand Down Expand Up @@ -522,6 +524,16 @@ public boolean stackHasItems(final int n) {
return stackTopV2 >= n;
}

/**
* Returns true if the stack has space for {@code n} more items.
*
* @param n the number of additional items
* @return true if the stack can accommodate n more items
*/
public boolean stackHasSpace(final int n) {
Comment thread
ahamlat marked this conversation as resolved.
Outdated
return stackTopV2 + n <= stackMaxSizeV2;
}

// ---------------------------------------------------------------------------
// endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,17 @@
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. */
/** The Abstract fixed cost operation for V2 (long[] stack) operations. */
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;

Expand All @@ -62,24 +53,14 @@ protected AbstractFixedCostOperationV2(
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);
Comment thread
ahamlat marked this conversation as resolved.
}

@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;
Comment thread
siladu marked this conversation as resolved.
if (frame.getRemainingGas() < gasCost) {
return outOfGasResponse;
}
return executeFixedCostOperation(frame, evm);
}

/**
Expand Down
Loading
Loading