eip-7709 implement BLOCKHASH opcode from system contract state (#7971)

* eip-7709 implement BLOCKHASH opcode from system contract state

Signed-off-by: Luis Pinto <luis.pinto@consensys.net>

* fixup! eip-7709 implement BLOCKHASH opcode from system contract state

 reimplement blockhashlookup with MessageFrame instead of WorldUpdater

Signed-off-by: Luis Pinto <luis.pinto@consensys.net>

* fixup! eip-7709 implement BLOCKHASH opcode from system contract state

 address review comments

Signed-off-by: Luis Pinto <luis.pinto@consensys.net>

* fixup! eip-7709 implement BLOCKHASH opcode from system contract state

 add comment about unused BlockHashProcessor

Signed-off-by: Luis Pinto <luis.pinto@consensys.net>

---------

Signed-off-by: Luis Pinto <luis.pinto@consensys.net>
This commit is contained in:
Luis Pinto
2024-12-20 10:29:25 +00:00
committed by GitHub
parent c72d02ea38
commit b3b33da540
67 changed files with 701 additions and 258 deletions

View File

@@ -0,0 +1,28 @@
/*
* Copyright contributors to Hyperledger 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.blockhash;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.frame.MessageFrame;
import java.util.function.BiFunction;
/**
* Function that gets the block hash, passed in as part of TxValues.
*
* <p>Arg is the current executing message frame. The Result is the Hash, which may be zero based on
* lookup rules.
*/
public interface BlockHashLookup extends BiFunction<MessageFrame, Long, Hash> {}

View File

@@ -15,7 +15,6 @@
package org.hyperledger.besu.evm.fluent;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup;
import org.hyperledger.besu.collections.trie.BytesTrieSet;
import org.hyperledger.besu.datatypes.Address;
@@ -26,6 +25,7 @@ import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.EvmSpecVersion;
import org.hyperledger.besu.evm.MainnetEVMs;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.code.CodeV0;
import org.hyperledger.besu.evm.contractvalidation.ContractValidationRule;
import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule;
@@ -72,7 +72,7 @@ public class EVMExecutor {
private Wei ethValue = Wei.ZERO;
private Code code = CodeV0.EMPTY_CODE;
private BlockValues blockValues = new SimpleBlockValues();
private BlockHashLookup blockHashLookup = n -> null;
private BlockHashLookup blockHashLookup = (__, ___) -> null;
private Optional<List<VersionedHash>> versionedHashes = Optional.empty();
private OperationTracer tracer = OperationTracer.NO_TRACING;
private boolean requireDeposit = true;

View File

@@ -25,13 +25,13 @@ import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.Code;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.internal.MemoryEntry;
import org.hyperledger.besu.evm.internal.OperandStack;
import org.hyperledger.besu.evm.internal.ReturnStack;
import org.hyperledger.besu.evm.internal.StorageEntry;
import org.hyperledger.besu.evm.internal.UnderflowException;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup;
import org.hyperledger.besu.evm.operation.Operation;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;

View File

@@ -14,14 +14,13 @@
*/
package org.hyperledger.besu.evm.frame;
import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup;
import org.hyperledger.besu.collections.undo.UndoScalar;
import org.hyperledger.besu.collections.undo.UndoSet;
import org.hyperledger.besu.collections.undo.UndoTable;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.VersionedHash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import java.util.Deque;
import java.util.List;

View File

@@ -16,30 +16,15 @@ package org.hyperledger.besu.evm.operation;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.EVM;
import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import java.util.function.Function;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
/** The Block hash operation. */
public class BlockHashOperation extends AbstractFixedCostOperation {
/**
* Function that gets the block hash, passed in as part of TxValues.
*
* <p>Arg is the current block number. The Result is the Hash, which may be zero based on lookup
* rules.
*/
public interface BlockHashLookup extends Function<Long, Hash> {}
/** Frontier maximum relative block delta */
public static final int MAX_RELATIVE_BLOCK = 256;
public class BlockHashOperation extends AbstractOperation {
private static final int MAX_BLOCK_ARG_SIZE = 8;
/**
@@ -48,36 +33,36 @@ public class BlockHashOperation extends AbstractFixedCostOperation {
* @param gasCalculator the gas calculator
*/
public BlockHashOperation(final GasCalculator gasCalculator) {
super(0x40, "BLOCKHASH", 1, 1, gasCalculator, gasCalculator.getBlockHashOperationGasCost());
super(0x40, "BLOCKHASH", 1, 1, gasCalculator);
}
@Override
public Operation.OperationResult executeFixedCostOperation(
final MessageFrame frame, final EVM evm) {
public OperationResult execute(final MessageFrame frame, final EVM evm) {
final long cost = gasCalculator().getBlockHashOperationGasCost();
if (frame.getRemainingGas() < cost) {
return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS);
}
// Make sure we can convert to long
final Bytes blockArg = frame.popStackItem().trimLeadingZeros();
// Short-circuit if value is unreasonably large
if (blockArg.size() > MAX_BLOCK_ARG_SIZE) {
frame.pushStackItem(UInt256.ZERO);
return successResponse;
frame.pushStackItem(Hash.ZERO);
return new OperationResult(cost, null);
}
final long soughtBlock = blockArg.toLong();
final BlockValues blockValues = frame.getBlockValues();
final long currentBlockNumber = blockValues.getNumber();
final BlockHashLookup blockHashLookup = frame.getBlockHashLookup();
final Hash blockHash = blockHashLookup.apply(frame, blockArg.toLong());
frame.pushStackItem(blockHash);
// If the current block is the genesis block or the sought block is
// not within the last 256 completed blocks, zero is returned.
if (currentBlockNumber == 0
|| soughtBlock >= currentBlockNumber
|| soughtBlock < (currentBlockNumber - MAX_RELATIVE_BLOCK)) {
frame.pushStackItem(Bytes32.ZERO);
} else {
final BlockHashLookup blockHashLookup = frame.getBlockHashLookup();
final Hash blockHash = blockHashLookup.apply(soughtBlock);
frame.pushStackItem(blockHash);
}
return new OperationResult(cost, null);
}
return successResponse;
/**
* Cost of the opcode execution.
*
* @return the cost
*/
protected long cost() {
return gasCalculator().getBlockHashOperationGasCost();
}
}

View File

@@ -89,7 +89,7 @@ class CodeV0Test {
.blockValues(mock(BlockValues.class))
.completer(f -> {})
.miningBeneficiary(Address.ZERO)
.blockHashLookup(l -> Hash.EMPTY)
.blockHashLookup((__, ___) -> Hash.EMPTY)
.build();
frame.setPC(CURRENT_PC);

View File

@@ -182,7 +182,7 @@ class EVMExecutorTest {
.number(1)
.timestamp(100L)
.gasLimit(15_000_000L)
.blockHashLookup(number -> Hash.ZERO)
.blockHashLookup((__, ___) -> Hash.ZERO)
.versionedHashes(Optional.empty())
.precompileContractRegistry(new PrecompileContractRegistry())
.requireDeposit(false)

View File

@@ -48,7 +48,7 @@ class MessageFrameTest {
.blobGasPrice(Wei.ONE)
.blockValues(new ToyBlockValues())
.miningBeneficiary(Address.ZERO)
.blockHashLookup((l) -> Hash.ZERO)
.blockHashLookup((__, ___) -> Hash.ZERO)
.type(MessageFrame.Type.MESSAGE_CALL)
.initialGas(1)
.address(Address.ZERO)

View File

@@ -37,7 +37,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator;
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
import org.hyperledger.besu.evm.tracing.OperationTracer;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
@@ -167,7 +166,7 @@ class AbstractCreateOperationTest {
.code(evm.getCodeUncached(SIMPLE_CREATE))
.completer(__ -> {})
.address(Address.fromHexString(SENDER))
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockHashLookup((__, ___) -> Hash.ZERO)
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)

View File

@@ -17,9 +17,10 @@ package org.hyperledger.besu.evm.operation;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.evm.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator;
import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup;
import org.hyperledger.besu.evm.testutils.FakeBlockValues;
import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder;
@@ -29,75 +30,90 @@ import org.apache.tuweni.units.bigints.UInt256;
import org.junit.jupiter.api.Test;
class BlockHashOperationTest {
private static final long ENOUGH_GAS = 30_000_000L;
private static final int MAXIMUM_COMPLETE_BLOCKS_BEHIND = 256;
private final BlockHashOperation blockHashOperation =
new BlockHashOperation(new FrontierGasCalculator());
@Test
void shouldReturnZeroWhenArgIsBiggerThanALong() {
assertBlockHash(
Bytes32.fromHexString("F".repeat(64)), Bytes32.ZERO, 100, n -> Hash.EMPTY_LIST_HASH);
}
@Test
void shouldReturnZeroWhenCurrentBlockIsGenesis() {
assertBlockHash(Bytes32.ZERO, Bytes32.ZERO, 0, block -> Hash.EMPTY_LIST_HASH);
}
@Test
void shouldReturnZeroWhenRequestedBlockAheadOfCurrent() {
assertBlockHash(250, Bytes32.ZERO, 100, block -> Hash.EMPTY_LIST_HASH);
}
@Test
void shouldReturnZeroWhenRequestedBlockTooFarBehindCurrent() {
final int requestedBlock = 10;
// Our block is the one after the chain head (it's a new block), hence the + 1.
final int importingBlockNumber = MAXIMUM_COMPLETE_BLOCKS_BEHIND + requestedBlock + 1;
assertBlockHash(
requestedBlock, Bytes32.ZERO, importingBlockNumber, block -> Hash.EMPTY_LIST_HASH);
}
@Test
void shouldReturnZeroWhenRequestedBlockGreaterThanImportingBlock() {
assertBlockHash(101, Bytes32.ZERO, 100, block -> Hash.EMPTY_LIST_HASH);
}
@Test
void shouldReturnZeroWhenRequestedBlockEqualToImportingBlock() {
assertBlockHash(100, Bytes32.ZERO, 100, block -> Hash.EMPTY_LIST_HASH);
Bytes32.fromHexString("F".repeat(64)),
Bytes32.ZERO,
100,
(__, ___) -> Hash.EMPTY_LIST_HASH,
ENOUGH_GAS);
}
@Test
void shouldReturnBlockHashUsingLookupFromFrameWhenItIsWithinTheAllowedRange() {
final Hash blockHash = Hash.hash(Bytes.fromHexString("0x1293487297"));
assertBlockHash(100, blockHash, 200, block -> block == 100 ? blockHash : Hash.EMPTY_LIST_HASH);
assertBlockHash(
100,
blockHash,
200,
(__, block) -> block == 100 ? blockHash : Hash.EMPTY_LIST_HASH,
ENOUGH_GAS);
}
@Test
void shouldFailWithInsufficientGas() {
assertFailure(
Bytes32.fromHexString("0x64"),
ExceptionalHaltReason.INSUFFICIENT_GAS,
200,
(__, ___) -> Hash.hash(Bytes.fromHexString("0x1293487297")),
1);
}
private void assertBlockHash(
final long requestedBlock,
final Bytes32 expectedOutput,
final long currentBlockNumber,
final BlockHashLookup blockHashLookup) {
final BlockHashLookup blockHashLookup,
final long initialGas) {
assertBlockHash(
UInt256.valueOf(requestedBlock), expectedOutput, currentBlockNumber, blockHashLookup);
UInt256.valueOf(requestedBlock),
expectedOutput,
currentBlockNumber,
blockHashLookup,
initialGas);
}
private void assertBlockHash(
final Bytes32 input,
final Bytes32 expectedOutput,
final long currentBlockNumber,
final BlockHashLookup blockHashLookup) {
final BlockHashLookup blockHashLookup,
final long initialGas) {
final MessageFrame frame =
new TestMessageFrameBuilder()
.blockHashLookup(blockHashLookup)
.blockValues(new FakeBlockValues(currentBlockNumber))
.pushStackItem(UInt256.fromBytes(input))
.initialGas(initialGas)
.build();
blockHashOperation.execute(frame, null);
final Bytes result = frame.popStackItem();
assertThat(result).isEqualTo(expectedOutput);
assertThat(frame.stackSize()).isZero();
}
private void assertFailure(
final Bytes32 input,
final ExceptionalHaltReason haltReason,
final long currentBlockNumber,
final BlockHashLookup blockHashLookup,
final long initialGas) {
final MessageFrame frame =
new TestMessageFrameBuilder()
.blockHashLookup(blockHashLookup)
.blockValues(new FakeBlockValues(currentBlockNumber))
.pushStackItem(UInt256.fromBytes(input))
.initialGas(initialGas)
.build();
Operation.OperationResult operationResult = blockHashOperation.execute(frame, null);
assertThat(operationResult.getHaltReason()).isEqualTo(haltReason);
assertThat(frame.stackSize()).isOne();
}
}

View File

@@ -33,7 +33,6 @@ import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.operation.Operation.OperationResult;
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
@@ -151,7 +150,7 @@ public class Create2OperationTest {
.code(evm.getCodeUncached(codeBytes))
.completer(__ -> {})
.address(Address.fromHexString(sender))
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockHashLookup((__, ___) -> Hash.ZERO)
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)
@@ -263,7 +262,7 @@ public class Create2OperationTest {
.code(evm.getCodeUncached(SIMPLE_CREATE))
.completer(__ -> {})
.address(Address.fromHexString(SENDER))
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockHashLookup((__, ___) -> Hash.ZERO)
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)

View File

@@ -32,7 +32,6 @@ import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder;
@@ -260,7 +259,7 @@ class CreateOperationTest {
.code(evm.getCodeUncached(SIMPLE_CREATE))
.completer(__ -> {})
.address(Address.fromHexString(SENDER))
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockHashLookup((__, ___) -> Hash.ZERO)
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)

View File

@@ -33,7 +33,6 @@ import org.hyperledger.besu.evm.code.CodeInvalid;
import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.log.Log;
import org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts;
import org.hyperledger.besu.evm.processor.ContractCreationProcessor;
@@ -147,7 +146,7 @@ class EofCreateOperationTest {
.code(code)
.completer(__ -> {})
.address(Address.fromHexString(SENDER))
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockHashLookup((__, ___) -> Hash.ZERO)
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)

View File

@@ -43,7 +43,7 @@ public class PushOperationTest {
.blobGasPrice(Wei.ONE)
.blockValues(new ToyBlockValues())
.miningBeneficiary(Address.ZERO)
.blockHashLookup((l) -> Hash.ZERO)
.blockHashLookup((__, ___) -> Hash.ZERO)
.type(MessageFrame.Type.MESSAGE_CALL)
.initialGas(1)
.address(Address.ZERO)

View File

@@ -30,7 +30,6 @@ import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator;
import org.hyperledger.besu.evm.internal.EvmConfiguration;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
import org.apache.tuweni.bytes.Bytes;
@@ -82,7 +81,7 @@ public class SelfDestructOperationTest {
.code(evm.getCodeUncached(SELFDESTRUCT_CODE))
.completer(__ -> {})
.address(originatorAddress)
.blockHashLookup(n -> Hash.hash(Words.longBytes(n)))
.blockHashLookup((__, ___) -> Hash.ZERO)
.blockValues(mock(BlockValues.class))
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)

View File

@@ -65,7 +65,7 @@ public class Benchmarks {
.code(CodeV0.EMPTY_CODE)
.completer(__ -> {})
.address(Address.ZERO)
.blockHashLookup(n -> null)
.blockHashLookup((__, ___) -> null)
.blockValues(new SimpleBlockValues())
.gasPrice(Wei.ZERO)
.miningBeneficiary(Address.ZERO)

View File

@@ -20,11 +20,11 @@ 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.blockhash.BlockHashLookup;
import org.hyperledger.besu.evm.code.CodeV0;
import org.hyperledger.besu.evm.frame.BlockValues;
import org.hyperledger.besu.evm.frame.MessageFrame;
import org.hyperledger.besu.evm.internal.Words;
import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup;
import org.hyperledger.besu.evm.toy.ToyWorld;
import org.hyperledger.besu.evm.worldstate.WorldUpdater;
@@ -167,7 +167,8 @@ public class TestMessageFrameBuilder {
.blockValues(blockValues.orElseGet(() -> new FakeBlockValues(1337)))
.completer(c -> {})
.miningBeneficiary(Address.ZERO)
.blockHashLookup(blockHashLookup.orElse(number -> Hash.hash(Words.longBytes(number))))
.blockHashLookup(
blockHashLookup.orElse((__, number) -> Hash.hash(Words.longBytes(number))))
.maxStackSize(maxStackSize)
.isStatic(isStatic)
.build();

View File

@@ -189,7 +189,7 @@ public class EvmToyCommand implements Runnable {
.blockValues(new ToyBlockValues())
.completer(c -> {})
.miningBeneficiary(Address.ZERO)
.blockHashLookup(n -> null)
.blockHashLookup((__, ___) -> null)
.build();
final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompileContractRegistry);