From 3f762dc6e430c4d449d79f1c78f5768cd437a690 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Tue, 25 Jun 2024 18:45:23 +1000 Subject: [PATCH 1/9] Initial implementation of system call for withdrawal requests Signed-off-by: Gabriel-Trintinalia --- .../blockcreation/AbstractBlockCreator.java | 10 +- .../mainnet/AbstractBlockProcessor.java | 13 +- .../mainnet/MainnetTransactionProcessor.java | 2 +- .../ethereum/mainnet/SystemCallProcessor.java | 132 ++++++++++++++++++ .../requests/DepositRequestProcessor.java | 11 +- .../mainnet/requests/RequestProcessor.java | 10 +- .../requests/RequestProcessorCoordinator.java | 20 ++- .../requests/WithdrawalRequestProcessor.java | 88 +++++++++++- .../hyperledger/besu/evmtool/T8nExecutor.java | 10 +- 9 files changed, 281 insertions(+), 15 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index 15f3b16d89a..981c247ee57 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -51,6 +51,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -244,7 +245,14 @@ protected BlockCreationResult createBlock( Optional> maybeRequests = requestProcessor.flatMap( processor -> - processor.process(disposableWorldState, transactionResults.getReceipts())); + processor.process( + processableBlockHeader, + disposableWorldState, + newProtocolSpec, + transactionResults.getReceipts(), + new CachingBlockHashLookup( + processableBlockHeader, protocolContext.getBlockchain()), + operationTracer)); throwIfStopped(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 888bc848f15..35c90cde695 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -107,6 +107,7 @@ public BlockProcessingResult processBlock( final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader); protocolSpec.getBlockHashProcessor().processBlockHashes(blockchain, worldState, blockHeader); + final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); for (final Transaction transaction : transactions) { if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) { @@ -115,7 +116,6 @@ public BlockProcessingResult processBlock( final WorldUpdater worldStateUpdater = worldState.updater(); - final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); @@ -197,7 +197,16 @@ public BlockProcessingResult processBlock( protocolSpec.getRequestProcessorCoordinator(); Optional> maybeRequests = Optional.empty(); if (requestProcessor.isPresent()) { - maybeRequests = requestProcessor.get().process(worldState, receipts); + maybeRequests = + requestProcessor + .get() + .process( + blockHeader, + worldState, + protocolSpec, + receipts, + blockHashLookup, + OperationTracer.NO_TRACING); } if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index d982265f242..53801d14cd3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -563,7 +563,7 @@ public void process(final MessageFrame frame, final OperationTracer operationTra executor.process(frame, operationTracer); } - private AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { + public AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { return switch (type) { case MESSAGE_CALL -> messageCallProcessor; case CONTRACT_CREATION -> contractCreationProcessor; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java new file mode 100644 index 00000000000..f114e877c2e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java @@ -0,0 +1,132 @@ +/* + * 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.ethereum.mainnet; + +import static org.hyperledger.besu.evm.frame.MessageFrame.DEFAULT_MAX_STACK_SIZE; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.CodeV0; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Deque; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class SystemCallProcessor { + + /** The system address */ + static final Address SYSTEM_ADDRESS = + Address.fromHexString("0xfffffffffffffffffffffffffffffffffffffffe"); + + private final MainnetTransactionProcessor mainnetTransactionProcessor; + + public SystemCallProcessor(final MainnetTransactionProcessor mainnetTransactionProcessor) { + this.mainnetTransactionProcessor = mainnetTransactionProcessor; + } + + /** + * Processes a system call to a specified address, using the provided world state, block header, + * operation tracer, and block hash lookup. + * + * @param callAddress the address to call. + * @param worldState the current world state. + * @param blockHeader the current block header. + * @param operationTracer the operation tracer for tracing EVM operations. + * @param blockHashLookup the block hash lookup function. + * @return the output data from the call, or an empty byte array if the call did not complete + * successfully. + */ + public Bytes process( + final Address callAddress, + final WorldUpdater worldState, + final ProcessableBlockHeader blockHeader, + final OperationTracer operationTracer, + final BlockHashOperation.BlockHashLookup blockHashLookup) { + + final AbstractMessageProcessor messageProcessor = + mainnetTransactionProcessor.getMessageProcessor(MessageFrame.Type.MESSAGE_CALL); + final MessageFrame initialFrame = + createCallFrame(callAddress, worldState, blockHeader, blockHashLookup); + + return processFrame(initialFrame, messageProcessor, operationTracer, worldState.updater()); + } + + private Bytes processFrame( + final MessageFrame frame, + final AbstractMessageProcessor processor, + final OperationTracer tracer, + final WorldUpdater updater) { + + if (!frame.getCode().isValid()) { + return Bytes.EMPTY; + } + + Deque stack = frame.getMessageFrameStack(); + while (!stack.isEmpty()) { + processor.process(stack.peekFirst(), tracer); + } + + if (frame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + updater.commit(); + return frame.getOutputData(); + } + + return Bytes.EMPTY; + } + + private MessageFrame createCallFrame( + final Address callAddress, + final WorldUpdater worldState, + final ProcessableBlockHeader blockHeader, + final BlockHashOperation.BlockHashLookup blockHashLookup) { + + final WorldUpdater updater = worldState.updater(); + final Optional maybeContract = Optional.ofNullable(worldState.get(callAddress)); + final AbstractMessageProcessor processor = + mainnetTransactionProcessor.getMessageProcessor(MessageFrame.Type.MESSAGE_CALL); + + return MessageFrame.builder() + .maxStackSize(DEFAULT_MAX_STACK_SIZE) + .worldUpdater(updater) + .initialGas(30_000_000L) + .originator(SYSTEM_ADDRESS) + .gasPrice(Wei.ZERO) + .blobGasPrice(Wei.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .blockValues(blockHeader) + .completer(__ -> {}) + .miningBeneficiary(Address.ZERO) // Confirm this + .type(MessageFrame.Type.MESSAGE_CALL) + .address(callAddress) + .contract(callAddress) + .inputData(Bytes.EMPTY) + .sender(SYSTEM_ADDRESS) + .blockHashLookup(blockHashLookup) + .code( + maybeContract + .map(c -> processor.getCodeFromEVM(c.getCodeHash(), c.getCode())) + .orElse(CodeV0.EMPTY_CODE)) + .build(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java index ce3ed6a5f65..9bd1700c238 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java @@ -17,9 +17,13 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.DepositRequest; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.encoding.DepositRequestDecoder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Collections; import java.util.List; @@ -40,7 +44,12 @@ public DepositRequestProcessor(final Address depositContractAddress) { @Override public Optional> process( - final MutableWorldState ignored, final List transactionReceipts) { + final ProcessableBlockHeader blockHeader, + final MutableWorldState mutableWorldState, + final ProtocolSpec protocolSpec, + final List transactionReceipts, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final OperationTracer operationTrace) { if (depositContractAddress.isEmpty()) { return Optional.empty(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java index d09b3c47d18..73d9c551c06 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java @@ -15,14 +15,22 @@ package org.hyperledger.besu.ethereum.mainnet.requests; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.List; import java.util.Optional; public interface RequestProcessor { Optional> process( + final ProcessableBlockHeader blockHeader, final MutableWorldState mutableWorldState, - final List transactionReceipts); + final ProtocolSpec protocolSpec, + final List transactionReceipts, + final BlockHashLookup blockHashLookup, + final OperationTracer operationTrace); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java index b72674b4d24..f9bc08d8c07 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java @@ -16,8 +16,12 @@ import org.hyperledger.besu.datatypes.RequestType; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.ArrayList; import java.util.List; @@ -40,10 +44,22 @@ private RequestProcessorCoordinator( } public Optional> process( - final MutableWorldState mutableWorldState, final List receipts) { + final ProcessableBlockHeader blockHeader, + final MutableWorldState mutableWorldState, + final ProtocolSpec protocolSpec, + final List transactionReceipts, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final OperationTracer operationTrace) { List requests = null; for (RequestProcessor requestProcessor : processors.values()) { - var r = requestProcessor.process(mutableWorldState, receipts); + var r = + requestProcessor.process( + blockHeader, + mutableWorldState, + protocolSpec, + transactionReceipts, + blockHashLookup, + operationTrace); if (r.isPresent()) { if (requests == null) { requests = new ArrayList<>(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java index 9803f23f3f9..98c8488099e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java @@ -14,25 +14,101 @@ */ package org.hyperledger.besu.ethereum.mainnet.requests; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.WithdrawalRequest; -import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.SystemCallProcessor; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; + public class WithdrawalRequestProcessor implements RequestProcessor { + private static final Address WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = + Address.fromHexString("0x00A3ca265EBcb825B45F985A16CEFB49958cE017"); + private static final int ADDRESS_BYTES = 20; + private static final int PUBLIC_KEY_BYTES = 48; + private static final int AMOUNT_BYTES = 8; + private static final int WITHDRAWAL_REQUEST_BYTES_SIZE = + ADDRESS_BYTES + PUBLIC_KEY_BYTES + AMOUNT_BYTES; + + /** + * Processes a system call and convert the result to withdrawal requests + * + * @param blockHeader The block header being processed. + * @param mutableWorldState The mutable world state. + * @param protocolSpec The protocol specification. + * @param transactionReceipts A list of transaction receipts. + * @param blockHashLookup A lookup function for block hashes. + * @param operationTracer A tracer for EVM operations. + * @return An {@link Optional} containing a list of {@link WithdrawalRequest} objects if any are + * found, or an empty {@link Optional} if none are found. + */ @Override public Optional> process( + final ProcessableBlockHeader blockHeader, final MutableWorldState mutableWorldState, - final List transactionReceipts) { - - List withdrawalRequests = - WithdrawalRequestContractHelper.popWithdrawalRequestsFromQueue(mutableWorldState).stream() - .toList(); + final ProtocolSpec protocolSpec, + final List transactionReceipts, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final OperationTracer operationTracer) { + SystemCallProcessor systemCallProcessor = + new SystemCallProcessor(protocolSpec.getTransactionProcessor()); + Bytes systemCallOutput = + systemCallProcessor.process( + WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, + mutableWorldState.updater(), + blockHeader, + operationTracer, + blockHashLookup); + List withdrawalRequests = parseWithdrawalRequests(systemCallOutput); return Optional.of(withdrawalRequests); } + + /** + * Parses the provided bytes into a list of {@link WithdrawalRequest} objects. + * + * @param bytes The bytes representing withdrawal requests. + * @return A list of parsed {@link WithdrawalRequest} objects. + */ + private List parseWithdrawalRequests(final Bytes bytes) { + final List withdrawalRequests = new ArrayList<>(); + if (bytes == null || bytes.isEmpty()) { + return withdrawalRequests; + } + int count = bytes.size() / WITHDRAWAL_REQUEST_BYTES_SIZE; + for (int i = 0; i < count; i++) { + Bytes requestBytes = + bytes.slice(i * WITHDRAWAL_REQUEST_BYTES_SIZE, WITHDRAWAL_REQUEST_BYTES_SIZE); + withdrawalRequests.add(parseSingleWithdrawalRequest(requestBytes)); + } + return withdrawalRequests; + } + + /** + * Parses a single withdrawal request from the given bytes. + * + * @param requestBytes The bytes representing a single withdrawal request. + * @return A {@link WithdrawalRequest} object parsed from the provided bytes. + */ + private WithdrawalRequest parseSingleWithdrawalRequest(final Bytes requestBytes) { + final Address sourceAddress = Address.wrap(requestBytes.slice(0, ADDRESS_BYTES)); + final BLSPublicKey validatorPublicKey = + BLSPublicKey.wrap(requestBytes.slice(ADDRESS_BYTES, PUBLIC_KEY_BYTES)); + final UInt64 amount = + UInt64.fromBytes(requestBytes.slice(ADDRESS_BYTES + PUBLIC_KEY_BYTES, AMOUNT_BYTES)); + return new WithdrawalRequest(sourceAddress, validatorPublicKey, GWei.of(amount)); + } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java index ccabce833a6..cdf4a2045f4 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java @@ -51,6 +51,7 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.log.Log; @@ -465,7 +466,14 @@ static T8nResult runTest( var requestProcessorCoordinator = protocolSpec.getRequestProcessorCoordinator(); if (requestProcessorCoordinator.isPresent()) { var rpc = requestProcessorCoordinator.get(); - Optional> maybeRequests = rpc.process(worldState, receipts); + Optional> maybeRequests = + rpc.process( + blockHeader, + worldState, + protocolSpec, + receipts, + new CachingBlockHashLookup(blockHeader, blockchain), + OperationTracer.NO_TRACING); Hash requestRoot = BodyValidation.requestsRoot(maybeRequests.orElse(List.of())); resultObject.put("requestsRoot", requestRoot.toHexString()); From 5044e907e3e3f63fa9b38578793f18bddbb0312f Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Wed, 26 Jun 2024 09:42:40 +1000 Subject: [PATCH 2/9] Fix validations Signed-off-by: Gabriel-Trintinalia --- .../AbstractBlockCreatorTest.java | 3 ++- .../ethereum/mainnet/SystemCallProcessor.java | 23 +++++++++++-------- .../requests/WithdrawalRequestProcessor.java | 15 +++++++++--- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java index 856bf874b20..6d925f9e336 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java @@ -135,7 +135,8 @@ void findDepositRequestsFromReceipts() { final List expectedDepositRequests = List.of(expectedDepositRequest); var depositRequestsFromReceipts = - new DepositRequestProcessor(DEFAULT_DEPOSIT_CONTRACT_ADDRESS).process(null, receipts); + new DepositRequestProcessor(DEFAULT_DEPOSIT_CONTRACT_ADDRESS) + .process(null, null, null, receipts, null, null); assertThat(depositRequestsFromReceipts.get()).isEqualTo(expectedDepositRequests); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java index f114e877c2e..1c00215f18b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java @@ -53,8 +53,7 @@ public SystemCallProcessor(final MainnetTransactionProcessor mainnetTransactionP * @param blockHeader the current block header. * @param operationTracer the operation tracer for tracing EVM operations. * @param blockHashLookup the block hash lookup function. - * @return the output data from the call, or an empty byte array if the call did not complete - * successfully. + * @return the output data from the call */ public Bytes process( final Address callAddress, @@ -63,12 +62,18 @@ public Bytes process( final OperationTracer operationTracer, final BlockHashOperation.BlockHashLookup blockHashLookup) { + // if no code exists at CALL_ADDRESS, the call must fail silently + final Account maybeContract = worldState.get(callAddress); + if (maybeContract == null) { + return null; + } + final AbstractMessageProcessor messageProcessor = mainnetTransactionProcessor.getMessageProcessor(MessageFrame.Type.MESSAGE_CALL); final MessageFrame initialFrame = createCallFrame(callAddress, worldState, blockHeader, blockHashLookup); - return processFrame(initialFrame, messageProcessor, operationTracer, worldState.updater()); + return processFrame(initialFrame, messageProcessor, operationTracer, worldState); } private Bytes processFrame( @@ -78,7 +83,7 @@ private Bytes processFrame( final WorldUpdater updater) { if (!frame.getCode().isValid()) { - return Bytes.EMPTY; + throw new RuntimeException("System the call did not execute to completion"); } Deque stack = frame.getMessageFrameStack(); @@ -91,23 +96,23 @@ private Bytes processFrame( return frame.getOutputData(); } - return Bytes.EMPTY; + // the call must execute to completion + throw new RuntimeException("System the call did not execute to completion"); } private MessageFrame createCallFrame( final Address callAddress, - final WorldUpdater worldState, + final WorldUpdater worldUpdater, final ProcessableBlockHeader blockHeader, final BlockHashOperation.BlockHashLookup blockHashLookup) { - final WorldUpdater updater = worldState.updater(); - final Optional maybeContract = Optional.ofNullable(worldState.get(callAddress)); + final Optional maybeContract = Optional.ofNullable(worldUpdater.get(callAddress)); final AbstractMessageProcessor processor = mainnetTransactionProcessor.getMessageProcessor(MessageFrame.Type.MESSAGE_CALL); return MessageFrame.builder() .maxStackSize(DEFAULT_MAX_STACK_SIZE) - .worldUpdater(updater) + .worldUpdater(worldUpdater) .initialGas(30_000_000L) .originator(SYSTEM_ADDRESS) .gasPrice(Wei.ZERO) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java index 98c8488099e..cda2e10c6dc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.mainnet.SystemCallProcessor; import org.hyperledger.besu.evm.operation.BlockHashOperation; import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayList; import java.util.List; @@ -64,17 +65,22 @@ public Optional> process( final BlockHashOperation.BlockHashLookup blockHashLookup, final OperationTracer operationTracer) { + WorldUpdater updater = mutableWorldState.updater(); + if (updater.get(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS) == null) { + return Optional.empty(); + } + SystemCallProcessor systemCallProcessor = new SystemCallProcessor(protocolSpec.getTransactionProcessor()); Bytes systemCallOutput = systemCallProcessor.process( WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - mutableWorldState.updater(), + updater, blockHeader, operationTracer, blockHashLookup); List withdrawalRequests = parseWithdrawalRequests(systemCallOutput); - return Optional.of(withdrawalRequests); + return Optional.ofNullable(withdrawalRequests); } /** @@ -84,8 +90,11 @@ public Optional> process( * @return A list of parsed {@link WithdrawalRequest} objects. */ private List parseWithdrawalRequests(final Bytes bytes) { + if (bytes == null) { + return null; + } final List withdrawalRequests = new ArrayList<>(); - if (bytes == null || bytes.isEmpty()) { + if (bytes.isEmpty()) { return withdrawalRequests; } int count = bytes.size() / WITHDRAWAL_REQUEST_BYTES_SIZE; From cce5d8f824fb84a2f0128ee306020fddbfa15e21 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Tue, 2 Jul 2024 19:09:59 +1200 Subject: [PATCH 3/9] Update ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java Co-authored-by: Justin Florentine Signed-off-by: Gabriel-Trintinalia --- .../hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java index 1c00215f18b..e5c3ae21cf3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java @@ -97,7 +97,7 @@ private Bytes processFrame( } // the call must execute to completion - throw new RuntimeException("System the call did not execute to completion"); + throw new RuntimeException("System call did not execute to completion"); } private MessageFrame createCallFrame( From 6eaaa1a9f41d9ab7b79c865d32d64ec6d4319caf Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Tue, 2 Jul 2024 19:10:20 +1200 Subject: [PATCH 4/9] Update ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java Co-authored-by: Sally MacFarlane Signed-off-by: Gabriel-Trintinalia --- .../hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java index e5c3ae21cf3..a0b4ad5aa20 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java @@ -83,7 +83,7 @@ private Bytes processFrame( final WorldUpdater updater) { if (!frame.getCode().isValid()) { - throw new RuntimeException("System the call did not execute to completion"); + throw new RuntimeException("System call did not execute to completion - opcode invalid"); } Deque stack = frame.getMessageFrameStack(); From 4c7fed342df73f76607a51795167dafb45c3973b Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Tue, 2 Jul 2024 19:16:38 +1200 Subject: [PATCH 5/9] remove helper Signed-off-by: Gabriel-Trintinalia --- .../WithdrawalRequestContractHelper.java | 197 ------------------ .../requests/WithdrawalRequestValidator.java | 6 +- ...ithdrawalRequestValidatorTestFixtures.java | 2 +- 3 files changed, 4 insertions(+), 201 deletions(-) delete mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelper.java diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelper.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelper.java deleted file mode 100644 index 6bb0e81a75e..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelper.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.ethereum.mainnet; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.WithdrawalRequest; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.util.ArrayList; -import java.util.List; - -import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; -import org.apache.tuweni.units.bigints.UInt64; - -/** - * Helper for interacting with the Validator Withdrawal Request Contract - * (https://eips.ethereum.org/EIPS/eip-7002) - * - *

TODO: Please note that this is not the spec-way of interacting with the Validator Withdrawal - * Request contract. See https://github.com/hyperledger/besu/issues/6918 for more information. - */ -public class WithdrawalRequestContractHelper { - - public static final Address WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = - Address.fromHexString("0x00A3ca265EBcb825B45F985A16CEFB49958cE017"); - - /** private constructor to prevent instantiations */ - private WithdrawalRequestContractHelper() {} - - @VisibleForTesting - // Storage slot to store the difference between number of withdrawal requests since last block and - // target withdrawal requests - // per block - static final UInt256 EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT = UInt256.valueOf(0L); - - @VisibleForTesting - // Storage slot to store the number of withdrawal requests added since last block - static final UInt256 WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT = UInt256.valueOf(1L); - - @VisibleForTesting - static final UInt256 WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT = UInt256.valueOf(2L); - - @VisibleForTesting - static final UInt256 WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT = UInt256.valueOf(3L); - - private static final UInt256 WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET = UInt256.valueOf(4L); - - // How many slots each withdrawal request occupies in the account state - private static final int WITHDRAWAL_REQUEST_STORAGE_SLOT_SIZE = 3; - - public static final int MAX_WITHDRAWAL_REQUESTS_PER_BLOCK = 16; - - private static final int TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK = 2; - - private static final UInt256 INITIAL_EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT = - UInt256.valueOf(1181); - - // TODO-lucas Add MIN_WITHDRAWAL_REQUEST_FEE and WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION - - /* - Pop the expected list of withdrawal requests from the smart contract, updating the queue pointers and other - control variables in the contract state. - */ - public static List popWithdrawalRequestsFromQueue( - final MutableWorldState mutableWorldState) { - final WorldUpdater worldUpdater = mutableWorldState.updater(); - final MutableAccount account = worldUpdater.getAccount(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS); - if (account == null || Hash.EMPTY.equals(account.getCodeHash())) { - return List.of(); - } - - final List withdrawalRequests = dequeueWithdrawalRequests(account); - updateExcessWithdrawalRequests(account); - resetWithdrawalRequestsCount(account); - - worldUpdater.commit(); - - return withdrawalRequests; - } - - private static List dequeueWithdrawalRequests(final MutableAccount account) { - final UInt256 queueHeadIndex = - account.getStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT); - final UInt256 queueTailIndex = - account.getStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT); - - final List withdrawalRequests = - peekExpectedWithdrawalRequests(account, queueHeadIndex, queueTailIndex); - - final UInt256 newQueueHeadIndex = queueHeadIndex.plus(withdrawalRequests.size()); - if (newQueueHeadIndex.equals(queueTailIndex)) { - // Queue is empty, reset queue pointers - account.setStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, UInt256.valueOf(0L)); - account.setStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, UInt256.valueOf(0L)); - } else { - account.setStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, newQueueHeadIndex); - } - - return withdrawalRequests; - } - - /* - ;; Each stack element has the following layout: - ;; - ;; A: addr - ;; 0x00 | 00 00 00 00 00 00 00 00 00 00 00 00 aa aa aa aa - ;; 0x10 | aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa - ;; - ;; B: pk[0:32] - ;; 0x00 | bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb - ;; 0x10 | bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb bb - ;; - ;; C: pk[32:48] ++ am[0:8] -> pk2_am - ;; 0x00 | cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc - ;; 0x10 | dd dd dd dd dd dd dd dd 00 00 00 00 00 00 00 00 - ;; - ;; To get these three stack elements into the correct contiguous format, it is - ;; necessary to combine them in the follow form: - ;; - ;; (A[12:32] ++ B[0:12], B[12:32] ++ C[0:12], C[12:24]) - */ - private static List peekExpectedWithdrawalRequests( - final Account account, final UInt256 queueHeadIndex, final UInt256 queueTailIndex) { - final long numRequestsInQueue = queueTailIndex.subtract(queueHeadIndex).toLong(); - final long numRequestsDequeued = - Long.min(numRequestsInQueue, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK); - - final List withdrawalRequests = new ArrayList<>(); - - for (int i = 0; i < numRequestsDequeued; i++) { - final UInt256 queueStorageSlot = - WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET.plus( - queueHeadIndex.plus(i).multiply(WITHDRAWAL_REQUEST_STORAGE_SLOT_SIZE)); - final Address sourceAddress = - Address.wrap(account.getStorageValue(queueStorageSlot).toBytes().slice(12, 20)); - final BLSPublicKey validatorPublicKey = - BLSPublicKey.wrap( - Bytes.concatenate( - account - .getStorageValue(queueStorageSlot.plus(1)) - .toBytes() - .slice(0, 32), // no need to slice - account.getStorageValue(queueStorageSlot.plus(2)).toBytes().slice(0, 16))); - final UInt64 amount = - UInt64.fromBytes(account.getStorageValue(queueStorageSlot.plus(2)).slice(16, 8)); - - withdrawalRequests.add( - new WithdrawalRequest(sourceAddress, validatorPublicKey, GWei.of(amount))); - } - - return withdrawalRequests; - } - - private static void updateExcessWithdrawalRequests(final MutableAccount account) { - UInt256 previousExcessRequests = - account.getStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT); - - if (previousExcessRequests.equals(INITIAL_EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT)) { - previousExcessRequests = UInt256.ZERO; - } - - final UInt256 requestsCount = account.getStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT); - - UInt256 newExcessRequests = UInt256.valueOf(0L); - if (previousExcessRequests.plus(requestsCount).toLong() - > TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK) { - newExcessRequests = - previousExcessRequests.plus(requestsCount).subtract(TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK); - } - - account.setStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, newExcessRequests); - } - - private static void resetWithdrawalRequestsCount(final MutableAccount account) { - account.setStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, UInt256.valueOf(0L)); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java index d5f04e6ef74..fc108b798f3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.WithdrawalRequest; -import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper; import java.util.Collections; import java.util.List; @@ -32,6 +31,8 @@ public class WithdrawalRequestValidator implements RequestValidator { + public static final int MAX_WITHDRAWAL_REQUESTS_PER_BLOCK = 16; + private static final Logger LOG = LoggerFactory.getLogger(WithdrawalRequestValidator.class); private boolean validateWithdrawalRequestParameter( @@ -51,8 +52,7 @@ private boolean validateWithdrawalRequestsInBlock( .orElse(Collections.emptyList()); // TODO Do we need to allow for customization? (e.g. if the value changes in the next fork) - if (withdrawalRequestsInBlock.size() - > WithdrawalRequestContractHelper.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) { + if (withdrawalRequestsInBlock.size() > MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) { LOG.warn( "Block {} has more than the allowed maximum number of withdrawal requests", blockHash); return false; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java index 7b8b440bb64..e719810c282 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.ethereum.mainnet; -import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK; +import static org.hyperledger.besu.ethereum.mainnet.requests.WithdrawalRequestValidator.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BLSPublicKey; From ede6bc6a858d6991d00164f66fa4515508e7689e Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Tue, 2 Jul 2024 19:20:04 +1200 Subject: [PATCH 6/9] fix spotless Signed-off-by: Gabriel-Trintinalia --- .../src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java index 15ca0213244..2b8098ff276 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java @@ -51,8 +51,8 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedAccount; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.log.Log; From 4851d1f4698427ac3646e1d8f914484559adcee5 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Wed, 3 Jul 2024 14:57:10 +1000 Subject: [PATCH 7/9] Accept PR recommendations Signed-off-by: Gabriel-Trintinalia --- .../blockcreation/AbstractBlockCreator.java | 22 +- .../AbstractBlockCreatorTest.java | 3 +- .../mainnet/AbstractBlockProcessor.java | 21 +- .../AbstractSystemCallRequestProcessor.java | 111 ++++++++++ .../requests/DepositRequestProcessor.java | 16 +- .../requests/ProcessRequestContext.java | 32 +++ .../mainnet/requests/RequestProcessor.java | 14 +- .../requests/RequestProcessorCoordinator.java | 23 +- .../requests/WithdrawalRequestProcessor.java | 88 ++------ .../mainnet/SystemCallProcessorTest.java | 113 ++++++++++ .../WithdrawalRequestContractHelperTest.java | 205 ------------------ .../hyperledger/besu/evmtool/T8nExecutor.java | 6 +- 12 files changed, 310 insertions(+), 344 deletions(-) create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java create mode 100644 ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java create mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java delete mode 100644 ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelperTest.java diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index 981c247ee57..78c32842ff7 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -50,6 +50,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.account.MutableAccount; @@ -242,17 +243,18 @@ protected BlockCreationResult createBlock( // EIP-7685: process EL requests final Optional requestProcessor = newProtocolSpec.getRequestProcessorCoordinator(); + + ProcessRequestContext context = + new ProcessRequestContext( + processableBlockHeader, + disposableWorldState, + newProtocolSpec, + transactionResults.getReceipts(), + new CachingBlockHashLookup(processableBlockHeader, protocolContext.getBlockchain()), + operationTracer); + Optional> maybeRequests = - requestProcessor.flatMap( - processor -> - processor.process( - processableBlockHeader, - disposableWorldState, - newProtocolSpec, - transactionResults.getReceipts(), - new CachingBlockHashLookup( - processableBlockHeader, protocolContext.getBlockchain()), - operationTracer)); + requestProcessor.flatMap(processor -> processor.process(context)); throwIfStopped(); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java index 6d925f9e336..68d13987bb3 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java @@ -77,6 +77,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket; import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestProcessor; import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -136,7 +137,7 @@ void findDepositRequestsFromReceipts() { var depositRequestsFromReceipts = new DepositRequestProcessor(DEFAULT_DEPOSIT_CONTRACT_ADDRESS) - .process(null, null, null, receipts, null, null); + .process(new ProcessRequestContext(null, null, null, receipts, null, null)); assertThat(depositRequestsFromReceipts.get()).isEqualTo(expectedDepositRequests); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 35c90cde695..8ecfa453d9f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -197,16 +198,16 @@ public BlockProcessingResult processBlock( protocolSpec.getRequestProcessorCoordinator(); Optional> maybeRequests = Optional.empty(); if (requestProcessor.isPresent()) { - maybeRequests = - requestProcessor - .get() - .process( - blockHeader, - worldState, - protocolSpec, - receipts, - blockHashLookup, - OperationTracer.NO_TRACING); + ProcessRequestContext context = + new ProcessRequestContext( + blockHeader, + worldState, + protocolSpec, + receipts, + blockHashLookup, + OperationTracer.NO_TRACING); + + maybeRequests = requestProcessor.get().process(context); } if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java new file mode 100644 index 00000000000..94c790f4a73 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java @@ -0,0 +1,111 @@ +/* + * 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.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.mainnet.SystemCallProcessor; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Abstract base class for processing system call requests. + * + * @param The type of request to be processed. + */ +public abstract class AbstractSystemCallRequestProcessor + implements RequestProcessor { + + /** + * Processes a system call and converts the result into requests of type T. + * + * @param context The request context being processed. + * @return An {@link Optional} containing a list of {@link T} objects if any are found, or an + * empty {@link Optional} if none are found. + */ + @Override + public Optional> process(final ProcessRequestContext context) { + WorldUpdater updater = context.mutableWorldState().updater(); + + // Check if the system call address is deployed + if (updater.get(getCallAddress()) == null) { + String error = String.format("System call address %s is not deployed", getCallAddress()); + throw new RuntimeException(error); + } + + // Process the system call + SystemCallProcessor systemCallProcessor = + new SystemCallProcessor(context.protocolSpec().getTransactionProcessor()); + Bytes systemCallOutput = + systemCallProcessor.process( + getCallAddress(), + updater, + context.blockHeader(), + context.operationTracer(), + context.blockHashLookup()); + + // Parse the system call output into requests + return Optional.ofNullable(parseRequests(systemCallOutput)); + } + + /** + * Parses the provided bytes into a list of {@link T} objects. + * + * @param bytes The bytes representing requests. + * @return A list of parsed {@link T} objects. + */ + protected List parseRequests(final Bytes bytes) { + if (bytes == null) { + return null; + } + final List requests = new ArrayList<>(); + if (bytes.isEmpty()) { + return requests; + } + int count = bytes.size() / getRequestBytesSize(); + for (int i = 0; i < count; i++) { + Bytes requestBytes = bytes.slice(i * getRequestBytesSize(), getRequestBytesSize()); + requests.add(parseRequest(requestBytes)); + } + return requests; + } + + /** + * Parses a single request from the provided bytes. + * + * @param requestBytes The bytes representing a single request. + * @return A parsed {@link T} object. + */ + protected abstract T parseRequest(final Bytes requestBytes); + + /** + * Gets the call address for the specific request type. + * + * @return The call address. + */ + protected abstract Address getCallAddress(); + + /** + * Gets the size of the bytes representing a single request. + * + * @return The size of the bytes representing a single request. + */ + protected abstract int getRequestBytesSize(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java index 9bd1700c238..8902ecc510a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java @@ -16,14 +16,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.DepositRequest; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.encoding.DepositRequestDecoder; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.evm.operation.BlockHashOperation; -import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Collections; import java.util.List; @@ -43,17 +38,12 @@ public DepositRequestProcessor(final Address depositContractAddress) { } @Override - public Optional> process( - final ProcessableBlockHeader blockHeader, - final MutableWorldState mutableWorldState, - final ProtocolSpec protocolSpec, - final List transactionReceipts, - final BlockHashOperation.BlockHashLookup blockHashLookup, - final OperationTracer operationTrace) { + public Optional> process(final ProcessRequestContext context) { if (depositContractAddress.isEmpty()) { return Optional.empty(); } - List depositRequests = findDepositRequestsFromReceipts(transactionReceipts); + List depositRequests = + findDepositRequestsFromReceipts(context.transactionReceipts()); return Optional.of(depositRequests); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java new file mode 100644 index 00000000000..63f4a8d5144 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java @@ -0,0 +1,32 @@ +/* + * 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.ethereum.mainnet.requests; + +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; +import org.hyperledger.besu.evm.tracing.OperationTracer; + +import java.util.List; + +public record ProcessRequestContext( + ProcessableBlockHeader blockHeader, + MutableWorldState mutableWorldState, + ProtocolSpec protocolSpec, + List transactionReceipts, + BlockHashLookup blockHashLookup, + OperationTracer operationTracer) {} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java index 73d9c551c06..55f3cd41788 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java @@ -14,23 +14,11 @@ */ package org.hyperledger.besu.ethereum.mainnet.requests; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; -import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.List; import java.util.Optional; public interface RequestProcessor { - Optional> process( - final ProcessableBlockHeader blockHeader, - final MutableWorldState mutableWorldState, - final ProtocolSpec protocolSpec, - final List transactionReceipts, - final BlockHashLookup blockHashLookup, - final OperationTracer operationTrace); + Optional> process(final ProcessRequestContext context); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java index f9bc08d8c07..b98274729d5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java @@ -15,13 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet.requests; import org.hyperledger.besu.datatypes.RequestType; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Request; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.evm.operation.BlockHashOperation; -import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.ArrayList; import java.util.List; @@ -43,23 +37,10 @@ private RequestProcessorCoordinator( this.processors = processors; } - public Optional> process( - final ProcessableBlockHeader blockHeader, - final MutableWorldState mutableWorldState, - final ProtocolSpec protocolSpec, - final List transactionReceipts, - final BlockHashOperation.BlockHashLookup blockHashLookup, - final OperationTracer operationTrace) { + public Optional> process(final ProcessRequestContext context) { List requests = null; for (RequestProcessor requestProcessor : processors.values()) { - var r = - requestProcessor.process( - blockHeader, - mutableWorldState, - protocolSpec, - transactionReceipts, - blockHashLookup, - operationTrace); + var r = requestProcessor.process(context); if (r.isPresent()) { if (requests == null) { requests = new ArrayList<>(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java index cda2e10c6dc..b230a6d6103 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java @@ -17,27 +17,18 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BLSPublicKey; import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; -import org.hyperledger.besu.ethereum.core.Request; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.WithdrawalRequest; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.ethereum.mainnet.SystemCallProcessor; -import org.hyperledger.besu.evm.operation.BlockHashOperation; -import org.hyperledger.besu.evm.tracing.OperationTracer; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt64; -public class WithdrawalRequestProcessor implements RequestProcessor { - private static final Address WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = +/** Processor for handling withdrawal requests. */ +public class WithdrawalRequestProcessor + extends AbstractSystemCallRequestProcessor { + + public static final Address WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS = Address.fromHexString("0x00A3ca265EBcb825B45F985A16CEFB49958cE017"); + private static final int ADDRESS_BYTES = 20; private static final int PUBLIC_KEY_BYTES = 48; private static final int AMOUNT_BYTES = 8; @@ -45,74 +36,33 @@ public class WithdrawalRequestProcessor implements RequestProcessor { ADDRESS_BYTES + PUBLIC_KEY_BYTES + AMOUNT_BYTES; /** - * Processes a system call and convert the result to withdrawal requests + * Gets the call address for withdrawal requests. * - * @param blockHeader The block header being processed. - * @param mutableWorldState The mutable world state. - * @param protocolSpec The protocol specification. - * @param transactionReceipts A list of transaction receipts. - * @param blockHashLookup A lookup function for block hashes. - * @param operationTracer A tracer for EVM operations. - * @return An {@link Optional} containing a list of {@link WithdrawalRequest} objects if any are - * found, or an empty {@link Optional} if none are found. + * @return The call address. */ @Override - public Optional> process( - final ProcessableBlockHeader blockHeader, - final MutableWorldState mutableWorldState, - final ProtocolSpec protocolSpec, - final List transactionReceipts, - final BlockHashOperation.BlockHashLookup blockHashLookup, - final OperationTracer operationTracer) { - - WorldUpdater updater = mutableWorldState.updater(); - if (updater.get(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS) == null) { - return Optional.empty(); - } - - SystemCallProcessor systemCallProcessor = - new SystemCallProcessor(protocolSpec.getTransactionProcessor()); - Bytes systemCallOutput = - systemCallProcessor.process( - WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, - updater, - blockHeader, - operationTracer, - blockHashLookup); - List withdrawalRequests = parseWithdrawalRequests(systemCallOutput); - return Optional.ofNullable(withdrawalRequests); + protected Address getCallAddress() { + return WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS; } /** - * Parses the provided bytes into a list of {@link WithdrawalRequest} objects. + * Gets the size of the bytes representing a single withdrawal request. * - * @param bytes The bytes representing withdrawal requests. - * @return A list of parsed {@link WithdrawalRequest} objects. + * @return The size of the bytes representing a single withdrawal request. */ - private List parseWithdrawalRequests(final Bytes bytes) { - if (bytes == null) { - return null; - } - final List withdrawalRequests = new ArrayList<>(); - if (bytes.isEmpty()) { - return withdrawalRequests; - } - int count = bytes.size() / WITHDRAWAL_REQUEST_BYTES_SIZE; - for (int i = 0; i < count; i++) { - Bytes requestBytes = - bytes.slice(i * WITHDRAWAL_REQUEST_BYTES_SIZE, WITHDRAWAL_REQUEST_BYTES_SIZE); - withdrawalRequests.add(parseSingleWithdrawalRequest(requestBytes)); - } - return withdrawalRequests; + @Override + protected int getRequestBytesSize() { + return WITHDRAWAL_REQUEST_BYTES_SIZE; } /** - * Parses a single withdrawal request from the given bytes. + * Parses a single withdrawal request from the provided bytes. * * @param requestBytes The bytes representing a single withdrawal request. - * @return A {@link WithdrawalRequest} object parsed from the provided bytes. + * @return A parsed {@link WithdrawalRequest} object. */ - private WithdrawalRequest parseSingleWithdrawalRequest(final Bytes requestBytes) { + @Override + protected WithdrawalRequest parseRequest(final Bytes requestBytes) { final Address sourceAddress = Address.wrap(requestBytes.slice(0, ADDRESS_BYTES)); final BLSPublicKey validatorPublicKey = BLSPublicKey.wrap(requestBytes.slice(ADDRESS_BYTES, PUBLIC_KEY_BYTES)); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java new file mode 100644 index 00000000000..e1d3906e734 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java @@ -0,0 +1,113 @@ +/* + * 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.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; +import org.hyperledger.besu.evm.processor.MessageCallProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SystemCallProcessorTest { + private static final Address CALL_ADDRESS = Address.fromHexString("0x1"); + private static final Bytes EXPECTED_OUTPUT = Bytes.fromHexString("0x01"); + private ProcessableBlockHeader mockBlockHeader; + private MainnetTransactionProcessor mockTransactionProcessor; + private BlockHashOperation.BlockHashLookup mockBlockHashLookup; + private AbstractMessageProcessor mockMessageCallProcessor; + + @BeforeEach + public void setUp() { + mockBlockHeader = mock(ProcessableBlockHeader.class); + mockTransactionProcessor = mock(MainnetTransactionProcessor.class); + mockMessageCallProcessor = mock(MessageCallProcessor.class); + mockBlockHashLookup = mock(BlockHashOperation.BlockHashLookup.class); + when(mockTransactionProcessor.getMessageProcessor(any())).thenReturn(mockMessageCallProcessor); + } + + @Test + void shouldProcessSuccessfully() { + doAnswer( + invocation -> { + MessageFrame messageFrame = invocation.getArgument(0); + messageFrame.setOutputData(EXPECTED_OUTPUT); + messageFrame.getMessageFrameStack().pop(); + messageFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); + return null; + }) + .when(mockMessageCallProcessor) + .process(any(), any()); + final MutableWorldState worldState = createWorldState(CALL_ADDRESS); + Bytes actualOutput = processSystemCall(worldState); + assertThat(actualOutput).isEqualTo(EXPECTED_OUTPUT); + } + + @Test + void shouldThrowExceptionOnFailedExecution() { + doAnswer( + invocation -> { + MessageFrame messageFrame = invocation.getArgument(0); + messageFrame.getMessageFrameStack().pop(); + messageFrame.setState(MessageFrame.State.COMPLETED_FAILED); + return null; + }) + .when(mockMessageCallProcessor) + .process(any(), any()); + final MutableWorldState worldState = createWorldState(CALL_ADDRESS); + var exception = assertThrows(RuntimeException.class, () -> processSystemCall(worldState)); + assertThat(exception.getMessage()).isEqualTo("System call did not execute to completion"); + } + + @Test + void shouldReturnNullWhenContractDoesNotExist() { + final MutableWorldState worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState(); + Bytes actualOutput = processSystemCall(worldState); + assertThat(actualOutput).isNull(); + } + + Bytes processSystemCall(final MutableWorldState worldState) { + SystemCallProcessor systemCallProcessor = new SystemCallProcessor(mockTransactionProcessor); + return systemCallProcessor.process( + CALL_ADDRESS, + worldState.updater(), + mockBlockHeader, + OperationTracer.NO_TRACING, + mockBlockHashLookup); + } + + private MutableWorldState createWorldState(final Address address) { + final MutableWorldState worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState(); + final WorldUpdater updater = worldState.updater(); + updater.getOrCreate(address); + updater.commit(); + return worldState; + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelperTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelperTest.java deleted file mode 100644 index 7e1e8571350..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestContractHelperTest.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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.ethereum.mainnet; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; -import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT; -import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT; -import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS; -import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT; -import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestContractHelper.WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.WithdrawalRequest; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.util.List; -import java.util.stream.IntStream; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.bytes.Bytes48; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class WithdrawalRequestContractHelperTest { - - private MutableWorldState worldState; - private MutableAccount contract; - - @BeforeEach - void setUp() { - worldState = createInMemoryWorldStateArchive().getMutable(); - } - - @Test - void popWithdrawalRequestsFromQueue_ReadWithdrawalRequestsCorrectly() { - final List validatorWithdrawalRequests = - List.of(createExit(), createExit(), createExit()); - loadContractStorage(worldState, validatorWithdrawalRequests); - - final List poppedWithdrawalRequests = - WithdrawalRequestContractHelper.popWithdrawalRequestsFromQueue(worldState); - - assertThat(poppedWithdrawalRequests).isEqualTo(validatorWithdrawalRequests); - } - - @Test - void - popWithdrawalRequestsFromQueue_whenContractCodeIsEmpty_ReturnsEmptyListOfWithdrawalRequests() { - // Create account with empty code - final WorldUpdater updater = worldState.updater(); - updater.createAccount(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS); - updater.commit(); - - assertThat(WithdrawalRequestContractHelper.popWithdrawalRequestsFromQueue(worldState)) - .isEmpty(); - } - - @Test - void popWithdrawalRequestsFromQueue_WhenMoreWithdrawalRequests_UpdatesQueuePointers() { - // Loading contract with more than 16 WithdrawalRequests - final List validatorWithdrawalRequests = - IntStream.range(0, 30).mapToObj(__ -> createExit()).toList(); - loadContractStorage(worldState, validatorWithdrawalRequests); - // After loading the contract, the WithdrawalRequests count since last block should match the - // size of the list - assertContractStorageValue( - WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, validatorWithdrawalRequests.size()); - - final List poppedWithdrawalRequests = - WithdrawalRequestContractHelper.popWithdrawalRequestsFromQueue(worldState); - assertThat(poppedWithdrawalRequests).hasSize(16); - - // Check that queue pointers were updated successfully (head advanced to index 16) - assertContractStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 16); - assertContractStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 30); - - // We had 30 WithdrawalRequests in the queue, and target per block is 2, so we have 28 excess - assertContractStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, 28); - - // We always reset the WithdrawalRequests count after processing the queue - assertContractStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, 0); - } - - @Test - void popWithdrawalRequestsFromQueue_WhenNoMoreWithdrawalRequests_ZeroQueuePointers() { - final List withdrawalRequests = - List.of(createExit(), createExit(), createExit()); - loadContractStorage(worldState, withdrawalRequests); - // After loading the contract, the exit count since last block should match the size of the list - assertContractStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, withdrawalRequests.size()); - - final List poppedWithdrawalRequests = - WithdrawalRequestContractHelper.popWithdrawalRequestsFromQueue(worldState); - assertThat(poppedWithdrawalRequests).hasSize(3); - - // Check that queue pointers were updated successfully (head and tail zero because queue is - // empty) - assertContractStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0); - assertContractStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0); - - // We had 3 WithdrawalRequests in the queue, target per block is 2, so we have 1 excess - assertContractStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, 1); - - // We always reset the WithdrawalRequests count after processing the queue - assertContractStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, 0); - } - - @Test - void popWithdrawalRequestsFromQueue_WhenNoWithdrawalRequests_DoesNothing() { - // Loading contract with 0 WithdrawalRequests - loadContractStorage(worldState, List.of()); - // After loading storage, we have the WithdrawalRequests count as zero because no - // WithdrawalRequests were added - assertContractStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, 0); - - final List poppedWithdrawalRequests = - WithdrawalRequestContractHelper.popWithdrawalRequestsFromQueue(worldState); - assertThat(poppedWithdrawalRequests).isEmpty(); - - // Check that queue pointers are correct (head and tail are zero) - assertContractStorageValue(WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0); - assertContractStorageValue(WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0); - - // We had 0 WithdrawalRequests in the queue, and target per block is 2, so we have 0 excess - assertContractStorageValue(EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, 0); - - // We always reset the exit count after processing the queue - assertContractStorageValue(WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, 0); - } - - private void assertContractStorageValue(final UInt256 slot, final int expectedValue) { - assertContractStorageValue(slot, UInt256.valueOf(expectedValue)); - } - - private void assertContractStorageValue(final UInt256 slot, final UInt256 expectedValue) { - assertThat(worldState.get(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS).getStorageValue(slot)) - .isEqualTo(expectedValue); - } - - private void loadContractStorage( - final MutableWorldState worldState, final List withdrawalRequests) { - final WorldUpdater updater = worldState.updater(); - contract = updater.getOrCreate(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS); - - contract.setCode( - Bytes.fromHexString( - "0x61013680600a5f395ff33373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b36603014156101325760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061013257600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460ed5780604402838201600302600401805490600101805490600101549160601b8160a01c17835260601b8160a01c17826020015260601b906040015260010160a6565b910180921460fe5790600255610109565b90505f6002555f6003555b5f546001546002828201116101205750505f610126565b01600290035b5f555f6001556044025ff35b5f5ffd")); - // excess requests - contract.setStorageValue(UInt256.valueOf(0), UInt256.valueOf(0)); - // requests count - contract.setStorageValue(UInt256.valueOf(1), UInt256.valueOf(withdrawalRequests.size())); - // requests queue head pointer - contract.setStorageValue(UInt256.valueOf(2), UInt256.valueOf(0)); - // requests queue tail pointer - contract.setStorageValue(UInt256.valueOf(3), UInt256.valueOf(withdrawalRequests.size())); - - int offset = 4; - for (int i = 0; i < withdrawalRequests.size(); i++) { - final WithdrawalRequest request = withdrawalRequests.get(i); - // source_account - contract.setStorageValue( - // set account to slot, with 12 bytes padding on the left - UInt256.valueOf(offset++), - UInt256.fromBytes( - Bytes.concatenate( - Bytes.fromHexString("0x000000000000000000000000"), request.getSourceAddress()))); - // validator_pubkey - contract.setStorageValue( - UInt256.valueOf(offset++), UInt256.fromBytes(request.getValidatorPubkey().slice(0, 32))); - contract.setStorageValue( - // set public key to slot, with 16 bytes padding on the right - UInt256.valueOf(offset++), - UInt256.fromBytes( - Bytes.concatenate( - request.getValidatorPubkey().slice(32, 16), - request.getAmount().toBytes(), // 8 bytes for amount - Bytes.fromHexString("0x0000000000000000")))); - } - updater.commit(); - } - - private WithdrawalRequest createExit() { - return new WithdrawalRequest( - Address.extract(Bytes32.random()), BLSPublicKey.wrap(Bytes48.random()), GWei.ONE); - } -} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java index 2b8098ff276..32ffbe10934 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nExecutor.java @@ -42,6 +42,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; import org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.referencetests.BonsaiReferenceTestWorldState; @@ -512,14 +513,15 @@ static T8nResult runTest( var requestProcessorCoordinator = protocolSpec.getRequestProcessorCoordinator(); if (requestProcessorCoordinator.isPresent()) { var rpc = requestProcessorCoordinator.get(); - Optional> maybeRequests = - rpc.process( + ProcessRequestContext context = + new ProcessRequestContext( blockHeader, worldState, protocolSpec, receipts, new CachingBlockHashLookup(blockHeader, blockchain), OperationTracer.NO_TRACING); + Optional> maybeRequests = rpc.process(context); Hash requestRoot = BodyValidation.requestsRoot(maybeRequests.orElse(List.of())); resultObject.put("requestsRoot", requestRoot.toHexString()); From ff6a90fce52b94d183107605645a74754ac148fc Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Wed, 3 Jul 2024 15:02:35 +1000 Subject: [PATCH 8/9] Accept PR recommendations Signed-off-by: Gabriel-Trintinalia --- .../besu/ethereum/mainnet/SystemCallProcessor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java index a0b4ad5aa20..f74de79442f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java @@ -31,8 +31,11 @@ import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SystemCallProcessor { + private static final Logger LOG = LoggerFactory.getLogger(SystemCallProcessor.class); /** The system address */ static final Address SYSTEM_ADDRESS = @@ -65,6 +68,7 @@ public Bytes process( // if no code exists at CALL_ADDRESS, the call must fail silently final Account maybeContract = worldState.get(callAddress); if (maybeContract == null) { + LOG.trace("System call address not found {}", callAddress); return null; } From 87cdc4c47578a7415748416927c1f0ccfc3a5f2d Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Thu, 4 Jul 2024 10:11:37 +1000 Subject: [PATCH 9/9] Do not throw exception if contract does not exist Signed-off-by: Gabriel-Trintinalia --- .../AbstractSystemCallRequestProcessor.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java index 94c790f4a73..a7d959f4b98 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java @@ -17,7 +17,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.mainnet.SystemCallProcessor; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayList; import java.util.List; @@ -37,32 +36,24 @@ public abstract class AbstractSystemCallRequestProcessor * Processes a system call and converts the result into requests of type T. * * @param context The request context being processed. - * @return An {@link Optional} containing a list of {@link T} objects if any are found, or an - * empty {@link Optional} if none are found. + * @return An {@link Optional} containing a list of {@link T} objects if any are found */ @Override public Optional> process(final ProcessRequestContext context) { - WorldUpdater updater = context.mutableWorldState().updater(); - // Check if the system call address is deployed - if (updater.get(getCallAddress()) == null) { - String error = String.format("System call address %s is not deployed", getCallAddress()); - throw new RuntimeException(error); - } - - // Process the system call SystemCallProcessor systemCallProcessor = new SystemCallProcessor(context.protocolSpec().getTransactionProcessor()); + Bytes systemCallOutput = systemCallProcessor.process( getCallAddress(), - updater, + context.mutableWorldState().updater(), context.blockHeader(), context.operationTracer(), context.blockHashLookup()); - // Parse the system call output into requests - return Optional.ofNullable(parseRequests(systemCallOutput)); + List requests = parseRequests(systemCallOutput); + return Optional.ofNullable(requests); } /**