diff --git a/CHANGELOG.md b/CHANGELOG.md index 6aca416dbfc..e4486dfbfb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Fast Sync ### Additions and Improvements +- Proper support for `pending` block tag when calling `eth_estimateGas` and `eth_createAccessList` [#7951](https://github.com/hyperledger/besu/pull/7951) ### Bug fixes - Correct default parameters for frontier transactions in `eth_call` and `eth_estimateGas` [#7965](https://github.com/hyperledger/besu/pull/7965) @@ -68,7 +69,6 @@ - Add histogram to Prometheus metrics system [#7944](https://github.com/hyperledger/besu/pull/7944) - Improve newPayload and FCU logs [#7961](https://github.com/hyperledger/besu/pull/7961) - ### Bug fixes - Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825) - Fix CVE-2024-47535 [7878](https://github.com/hyperledger/besu/pull/7878) diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index 091c1534d35..3f0e0848c70 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -396,9 +396,14 @@ TransactionSimulator provideTransactionSimulator( final Blockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, + final MiningConfiguration miningConfiguration, final ApiConfiguration apiConfiguration) { return new TransactionSimulator( - blockchain, worldStateArchive, protocolSchedule, apiConfiguration.getGasCap()); + blockchain, + worldStateArchive, + protocolSchedule, + miningConfiguration, + apiConfiguration.getGasCap()); } @Provides diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index fdce6a08e4a..086850fa415 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -626,7 +626,11 @@ public BesuController build() { transactionSimulator = new TransactionSimulator( - blockchain, worldStateArchive, protocolSchedule, apiConfiguration.getGasCap()); + blockchain, + worldStateArchive, + protocolSchedule, + miningConfiguration, + apiConfiguration.getGasCap()); final var consensusContext = createConsensusContext(blockchain, worldStateArchive, protocolSchedule); diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java index 4dc411ee0be..5244229c1df 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -35,11 +34,6 @@ /** TransactionSimulationServiceImpl */ @Unstable public class TransactionSimulationServiceImpl implements TransactionSimulationService { - private static final TransactionValidationParams SIMULATOR_ALLOWING_EXCEEDING_BALANCE = - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(true) - .build(); private Blockchain blockchain; private TransactionSimulator transactionSimulator; @@ -57,46 +51,50 @@ public void init(final Blockchain blockchain, final TransactionSimulator transac this.transactionSimulator = transactionSimulator; } - @Override - public Optional simulate( - final Transaction transaction, - final Hash blockHash, - final OperationTracer operationTracer, - final boolean isAllowExceedingBalance) { - return simulate( - transaction, Optional.empty(), blockHash, operationTracer, isAllowExceedingBalance); - } - @Override public Optional simulate( final Transaction transaction, final Optional maybeAccountOverrides, - final Hash blockHash, + final Optional maybeBlockHash, final OperationTracer operationTracer, final boolean isAllowExceedingBalance) { final CallParameter callParameter = CallParameter.fromTransaction(transaction); - final var maybeBlockHeader = - blockchain.getBlockHeader(blockHash).or(() -> blockchain.getBlockHeaderSafe(blockHash)); + if (maybeBlockHash.isPresent()) { + final Hash blockHash = maybeBlockHash.get(); + + final var maybeBlockHeader = + blockchain.getBlockHeader(blockHash).or(() -> blockchain.getBlockHeaderSafe(blockHash)); + + if (maybeBlockHeader.isEmpty()) { + return Optional.of( + new TransactionSimulationResult( + transaction, + TransactionProcessingResult.invalid( + ValidationResult.invalid(TransactionInvalidReason.BLOCK_NOT_FOUND)))); + } - if (maybeBlockHeader.isEmpty()) { - return Optional.of( - new TransactionSimulationResult( - transaction, - TransactionProcessingResult.invalid( - ValidationResult.invalid(TransactionInvalidReason.BLOCK_NOT_FOUND)))); + return transactionSimulator + .process( + callParameter, + isAllowExceedingBalance + ? TransactionValidationParams.transactionSimulatorAllowExceedingBalance() + : TransactionValidationParams.transactionSimulator(), + operationTracer, + maybeBlockHeader.get()) + .map(res -> new TransactionSimulationResult(transaction, res.result())); } return transactionSimulator - .process( + .processOnPending( callParameter, maybeAccountOverrides, isAllowExceedingBalance - ? SIMULATOR_ALLOWING_EXCEEDING_BALANCE + ? TransactionValidationParams.transactionSimulatorAllowExceedingBalance() : TransactionValidationParams.transactionSimulator(), operationTracer, - maybeBlockHeader.get()) + transactionSimulator.simulatePendingBlockHeader()) .map(res -> new TransactionSimulationResult(transaction, res.result())); } } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index 9c2bf04bce7..377757a322b 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -154,7 +154,7 @@ public void extraDataCreatedOnEpochBlocksContainsValidators() { } @Test - public void extraDataForNonEpochBlocksDoesNotContainValidaors() { + public void extraDataForNonEpochBlocksDoesNotContainValidators() { final Bytes vanityData = generateRandomVanityData(); final MiningConfiguration miningConfiguration = createMiningConfiguration(vanityData); diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index cc7066f17ee..a9de97ae18a 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -387,7 +387,7 @@ private static ControllerAndState createControllerAndFinalState( final boolean useFixedBaseFee, final List qbftForks) { - final MiningConfiguration miningParams = + final MiningConfiguration miningConfiguration = ImmutableMiningConfiguration.builder() .mutableInitValues( MutableInitValues.builder() @@ -445,7 +445,8 @@ private static ControllerAndState createControllerAndFinalState( final BftValidatorOverrides validatorOverrides = convertBftForks(qbftForks); final TransactionSimulator transactionSimulator = - new TransactionSimulator(blockChain, worldStateArchive, protocolSchedule, 0L); + new TransactionSimulator( + blockChain, worldStateArchive, protocolSchedule, miningConfiguration, 0L); final BlockValidatorProvider blockValidatorProvider = BlockValidatorProvider.forkingValidatorProvider( @@ -496,7 +497,7 @@ private static ControllerAndState createControllerAndFinalState( protocolContext, protocolSchedule, forksSchedule, - miningParams, + miningConfiguration, localAddress, BFT_EXTRA_DATA_ENCODER, ethScheduler); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java index 5b561856ecf..de3e9cf1703 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.consensus.qbft.validator; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; @@ -94,10 +93,7 @@ private Optional callFunction( final CallParameter callParams = new CallParameter(null, contractAddress, -1, null, null, payload); final TransactionValidationParams transactionValidationParams = - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(true) - .build(); + TransactionValidationParams.transactionSimulatorAllowExceedingBalance(); return transactionSimulator.process( callParams, transactionValidationParams, OperationTracer.NO_TRACING, blockNumber); } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java index f9918036ecb..63554577c4b 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java @@ -97,12 +97,13 @@ public JsonRpcTestMethodsFactory(final BlockchainImporter importer) { final BlockImporter blockImporter = protocolSpec.getBlockImporter(); blockImporter.importBlock(context, block, HeaderValidationMode.FULL); } + final var miningConfiguration = MiningConfiguration.newDefault(); this.blockchainQueries = - new BlockchainQueries( - protocolSchedule, blockchain, stateArchive, MiningConfiguration.newDefault()); + new BlockchainQueries(protocolSchedule, blockchain, stateArchive, miningConfiguration); this.transactionSimulator = - new TransactionSimulator(blockchain, stateArchive, protocolSchedule, 0L); + new TransactionSimulator( + blockchain, stateArchive, protocolSchedule, miningConfiguration, 0L); } public JsonRpcTestMethodsFactory( @@ -115,15 +116,14 @@ public JsonRpcTestMethodsFactory( this.stateArchive = stateArchive; this.context = context; this.protocolSchedule = importer.getProtocolSchedule(); + final var miningConfiguration = MiningConfiguration.newDefault(); this.blockchainQueries = new BlockchainQueries( - importer.getProtocolSchedule(), - blockchain, - stateArchive, - MiningConfiguration.newDefault()); + importer.getProtocolSchedule(), blockchain, stateArchive, miningConfiguration); this.synchronizer = mock(Synchronizer.class); this.transactionSimulator = - new TransactionSimulator(blockchain, stateArchive, protocolSchedule, 0L); + new TransactionSimulator( + blockchain, stateArchive, protocolSchedule, miningConfiguration, 0L); } public JsonRpcTestMethodsFactory( @@ -138,14 +138,13 @@ public JsonRpcTestMethodsFactory( this.context = context; this.synchronizer = synchronizer; this.protocolSchedule = importer.getProtocolSchedule(); + final var miningConfiguration = MiningConfiguration.newDefault(); this.blockchainQueries = new BlockchainQueries( - importer.getProtocolSchedule(), - blockchain, - stateArchive, - MiningConfiguration.newDefault()); + importer.getProtocolSchedule(), blockchain, stateArchive, miningConfiguration); this.transactionSimulator = - new TransactionSimulator(blockchain, stateArchive, protocolSchedule, 0L); + new TransactionSimulator( + blockchain, stateArchive, protocolSchedule, miningConfiguration, 0L); } public BlockchainQueries getBlockchainQueries() { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java index 945727fda24..f49fa0a4c8f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java @@ -26,7 +26,6 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.LogWithMetadata; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; @@ -359,14 +358,9 @@ private Optional executeCall(final DataFetchingEnvironment environme data, Optional.empty()); - ImmutableTransactionValidationParams.Builder transactionValidationParams = - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()); - transactionValidationParams.isAllowExceedingBalance(true); - return transactionSimulator.process( param, - transactionValidationParams.build(), + TransactionValidationParams.transactionSimulatorAllowExceedingBalance(), OperationTracer.NO_TRACING, (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult.map( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index 8e826caf9e4..dc61b3bb27e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -16,10 +16,12 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; +import org.hyperledger.besu.datatypes.AccountOverrideMap; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; @@ -28,6 +30,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.CallParameter; @@ -35,9 +38,12 @@ import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer; +import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Optional; +import com.google.common.annotations.VisibleForTesting; + public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod { private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D; @@ -60,7 +66,53 @@ protected BlockParameter blockParameter(final JsonRpcRequestContext request) { } } - protected Optional blockHeader(final long blockNumber) { + protected abstract Object simulate( + final JsonRpcRequestContext requestContext, + final CallParameter callParams, + final long gasLimit, + final TransactionSimulationFunction simulationFunction); + + @Override + protected Object pendingResult(final JsonRpcRequestContext requestContext) { + final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); + final var validationParams = getTransactionValidationParams(jsonCallParameter); + final var maybeStateOverrides = getAddressAccountOverrideMap(requestContext); + final var pendingBlockHeader = transactionSimulator.simulatePendingBlockHeader(); + final TransactionSimulationFunction simulationFunction = + (cp, op) -> + transactionSimulator.processOnPending( + cp, maybeStateOverrides, validationParams, op, pendingBlockHeader); + return simulate( + requestContext, jsonCallParameter, pendingBlockHeader.getGasLimit(), simulationFunction); + } + + @Override + protected Object resultByBlockNumber( + final JsonRpcRequestContext requestContext, final long blockNumber) { + final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); + final Optional maybeBlockHeader = blockHeader(blockNumber); + final Optional jsonRpcError = validateBlockHeader(maybeBlockHeader); + if (jsonRpcError.isPresent()) { + return errorResponse(requestContext, jsonRpcError.get()); + } + return resultByBlockHeader(requestContext, jsonCallParameter, maybeBlockHeader.get()); + } + + private Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter jsonCallParameter, + final BlockHeader blockHeader) { + final var validationParams = getTransactionValidationParams(jsonCallParameter); + final var maybeStateOverrides = getAddressAccountOverrideMap(requestContext); + final TransactionSimulationFunction simulationFunction = + (cp, op) -> + transactionSimulator.process( + cp, maybeStateOverrides, validationParams, op, blockHeader); + return simulate( + requestContext, jsonCallParameter, blockHeader.getGasLimit(), simulationFunction); + } + + private Optional blockHeader(final long blockNumber) { if (getBlockchainQueries().headBlockNumber() == blockNumber) { // chain head header if cached, and we can return it form memory return Optional.of(getBlockchainQueries().getBlockchain().getChainHeadHeader()); @@ -68,8 +120,7 @@ protected Optional blockHeader(final long blockNumber) { return getBlockchainQueries().getBlockHeaderByNumber(blockNumber); } - protected Optional validateBlockHeader( - final Optional maybeBlockHeader) { + private Optional validateBlockHeader(final Optional maybeBlockHeader) { if (maybeBlockHeader.isEmpty()) { return Optional.of(RpcErrorType.BLOCK_NOT_FOUND); } @@ -83,25 +134,7 @@ protected Optional validateBlockHeader( return Optional.empty(); } - @Override - protected Object resultByBlockNumber( - final JsonRpcRequestContext requestContext, final long blockNumber) { - final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); - final Optional maybeBlockHeader = blockHeader(blockNumber); - final Optional jsonRpcError = validateBlockHeader(maybeBlockHeader); - if (jsonRpcError.isPresent()) { - return errorResponse(requestContext, jsonRpcError.get()); - } - return resultByBlockHeader(requestContext, jsonCallParameter, maybeBlockHeader.get()); - } - - protected abstract Object resultByBlockHeader( - final JsonRpcRequestContext requestContext, - final JsonCallParameter jsonCallParameter, - final BlockHeader blockHeader); - - protected CallParameter overrideGasLimitAndPrice( - final JsonCallParameter callParams, final long gasLimit) { + protected CallParameter overrideGasLimit(final CallParameter callParams, final long gasLimit) { return new CallParameter( callParams.getChainId(), callParams.getFrom(), @@ -142,7 +175,7 @@ protected JsonRpcErrorResponse errorResponse( final ValidationResult validationResult = result.getValidationResult(); if (validationResult != null && !validationResult.isValid()) { - if (validationResult.getErrorMessage().length() > 0) { + if (!validationResult.getErrorMessage().isEmpty()) { return errorResponse(request, JsonRpcError.from(validationResult)); } return errorResponse( @@ -170,4 +203,29 @@ protected JsonRpcErrorResponse errorResponse( final JsonRpcRequestContext request, final JsonRpcError jsonRpcError) { return new JsonRpcErrorResponse(request.getRequest().getId(), jsonRpcError); } + + protected static TransactionValidationParams getTransactionValidationParams( + final JsonCallParameter callParams) { + final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); + + return isAllowExceedingBalance + ? TransactionValidationParams.transactionSimulatorAllowExceedingBalance() + : TransactionValidationParams.transactionSimulator(); + } + + @VisibleForTesting + protected Optional getAddressAccountOverrideMap( + final JsonRpcRequestContext request) { + try { + return request.getOptionalParameter(2, AccountOverrideMap.class); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcRequestException( + "Invalid account overrides parameter (index 2)", RpcErrorType.INVALID_CALL_PARAMS, e); + } + } + + protected interface TransactionSimulationFunction { + Optional simulate( + CallParameter callParams, OperationTracer operationTracer); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java index 36991796b51..b7ec1449e95 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java @@ -33,7 +33,6 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.debug.TraceOptions; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; @@ -117,9 +116,7 @@ protocolSchedule, transactionTrace, block, new AtomicInteger(), false) } protected TransactionValidationParams buildTransactionValidationParams() { - return ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .build(); + return TransactionValidationParams.transactionSimulator(); } protected TraceOptions buildTraceOptions(final Set traceTypes) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java index ef5c8c7a6cf..39a5c7487da 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java @@ -35,7 +35,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -169,20 +168,18 @@ private JsonRpcErrorResponse errorResponse( private TransactionValidationParams buildTransactionValidationParams( final BlockHeader header, final JsonCallParameter callParams) { - ImmutableTransactionValidationParams.Builder transactionValidationParams = - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()); - + final boolean isAllowExceedingBalance; // if it is not set explicitly whether we want a strict check of the balance or not. this will // be decided according to the provided parameters if (callParams.isMaybeStrict().isEmpty()) { - transactionValidationParams.isAllowExceedingBalance( - isAllowExceedingBalanceAutoSelection(header, callParams)); + isAllowExceedingBalance = isAllowExceedingBalanceAutoSelection(header, callParams); + } else { - transactionValidationParams.isAllowExceedingBalance( - !callParams.isMaybeStrict().orElse(Boolean.FALSE)); + isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); } - return transactionValidationParams.build(); + return isAllowExceedingBalance + ? TransactionValidationParams.transactionSimulatorAllowExceedingBalance() + : TransactionValidationParams.transactionSimulator(); } private boolean isAllowExceedingBalanceAutoSelection( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java index 2b8e79a81e9..7d4ba313cf1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java @@ -18,13 +18,9 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; -import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; @@ -48,20 +44,24 @@ public String getName() { } @Override - protected Object resultByBlockHeader( + protected Object simulate( final JsonRpcRequestContext requestContext, - final JsonCallParameter jsonCallParameter, - final BlockHeader blockHeader) { - final AccessListSimulatorResult maybeResult = - processTransaction(jsonCallParameter, blockHeader); + final CallParameter callParams, + final long gasLimit, + final TransactionSimulationFunction simulationFunction) { + + final AccessListOperationTracer tracer = AccessListOperationTracer.create(); + final Optional firstResult = + simulationFunction.simulate(overrideGasLimit(callParams, gasLimit), tracer); + // if the call accessList is different from the simulation result, calculate gas and return - if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.tracer())) { + if (shouldProcessWithAccessListOverride(callParams, tracer)) { final AccessListSimulatorResult result = processTransactionWithAccessListOverride( - jsonCallParameter, blockHeader, maybeResult.tracer().getAccessList()); + callParams, gasLimit, tracer.getAccessList(), simulationFunction); return createResponse(requestContext, result); } else { - return createResponse(requestContext, maybeResult); + return createResponse(requestContext, new AccessListSimulatorResult(firstResult, tracer)); } } @@ -73,16 +73,8 @@ private Object createResponse( .orElseGet(() -> errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); } - private TransactionValidationParams transactionValidationParams( - final boolean isAllowExceedingBalance) { - return ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(isAllowExceedingBalance) - .build(); - } - private boolean shouldProcessWithAccessListOverride( - final JsonCallParameter parameters, final AccessListOperationTracer tracer) { + final CallParameter parameters, final AccessListOperationTracer tracer) { // if empty, transaction did not access any storage, does not need to reprocess if (tracer.getAccessList().isEmpty()) { @@ -107,39 +99,23 @@ private Function createResponse( : errorResponse(request, result); } - private AccessListSimulatorResult processTransaction( - final JsonCallParameter jsonCallParameter, final BlockHeader blockHeader) { - final TransactionValidationParams transactionValidationParams = - transactionValidationParams(!jsonCallParameter.isMaybeStrict().orElse(Boolean.FALSE)); - - final CallParameter callParams = - overrideGasLimitAndPrice(jsonCallParameter, blockHeader.getGasLimit()); - - final AccessListOperationTracer tracer = AccessListOperationTracer.create(); - final Optional result = - transactionSimulator.process(callParams, transactionValidationParams, tracer, blockHeader); - return new AccessListSimulatorResult(result, tracer); - } - private AccessListSimulatorResult processTransactionWithAccessListOverride( - final JsonCallParameter jsonCallParameter, - final BlockHeader blockHeader, - final List accessList) { - final TransactionValidationParams transactionValidationParams = - transactionValidationParams(!jsonCallParameter.isMaybeStrict().orElse(Boolean.FALSE)); + final CallParameter callParameter, + final long gasLimit, + final List accessList, + final TransactionSimulationFunction simulationFunction) { final AccessListOperationTracer tracer = AccessListOperationTracer.create(); - final CallParameter callParameter = - overrideAccessList(jsonCallParameter, blockHeader.getGasLimit(), accessList); + final CallParameter modifiedCallParameter = + overrideAccessList(callParameter, gasLimit, accessList); final Optional result = - transactionSimulator.process( - callParameter, transactionValidationParams, tracer, blockHeader); + simulationFunction.simulate(modifiedCallParameter, tracer); return new AccessListSimulatorResult(result, tracer); } - protected CallParameter overrideAccessList( - final JsonCallParameter callParams, + private CallParameter overrideAccessList( + final CallParameter callParams, final long gasLimit, final List accessListEntries) { return new CallParameter( @@ -151,7 +127,7 @@ protected CallParameter overrideAccessList( callParams.getMaxFeePerGas(), callParams.getValue(), callParams.getPayload(), - Optional.ofNullable(accessListEntries)); + Optional.of(accessListEntries)); } private record AccessListSimulatorResult( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java index 2352a5a8c37..05d5f78e0ff 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java @@ -14,19 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import org.hyperledger.besu.datatypes.AccountOverrideMap; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; -import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; @@ -34,7 +27,6 @@ import java.util.Optional; -import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,33 +44,17 @@ public String getName() { } @Override - protected Object resultByBlockHeader( + protected Object simulate( final JsonRpcRequestContext requestContext, - final JsonCallParameter callParams, - final BlockHeader blockHeader) { - - final CallParameter modifiedCallParams = - overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit()); - Optional maybeStateOverrides = getAddressAccountOverrideMap(requestContext); - // TODO implement for block overrides - - final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); + final CallParameter callParams, + final long gasLimit, + final TransactionSimulationFunction simulationFunction) { final EstimateGasOperationTracer operationTracer = new EstimateGasOperationTracer(); - final var transactionValidationParams = - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(isAllowExceedingBalance) - .build(); - LOG.debug("Processing transaction with params: {}", modifiedCallParams); + LOG.debug("Processing transaction with params: {}", callParams); final var maybeResult = - transactionSimulator.process( - modifiedCallParams, - maybeStateOverrides, - transactionValidationParams, - operationTracer, - blockHeader); + simulationFunction.simulate(overrideGasLimit(callParams, gasLimit), operationTracer); final Optional maybeErrorResponse = validateSimulationResult(requestContext, maybeResult); @@ -89,12 +65,7 @@ protected Object resultByBlockHeader( final var result = maybeResult.get(); long low = result.result().getEstimateGasUsedByTransaction(); final var lowResult = - transactionSimulator.process( - overrideGasLimitAndPrice(callParams, low), - maybeStateOverrides, - transactionValidationParams, - operationTracer, - blockHeader); + simulationFunction.simulate(overrideGasLimit(callParams, low), operationTracer); if (lowResult.isPresent() && lowResult.get().isSuccessful()) { return Quantity.create(low); @@ -106,12 +77,7 @@ protected Object resultByBlockHeader( while (low + 1 < high) { mid = (low + high) / 2; var binarySearchResult = - transactionSimulator.process( - overrideGasLimitAndPrice(callParams, mid), - maybeStateOverrides, - transactionValidationParams, - operationTracer, - blockHeader); + simulationFunction.simulate(overrideGasLimit(callParams, mid), operationTracer); if (binarySearchResult.isEmpty() || !binarySearchResult.get().isSuccessful()) { low = mid; @@ -139,15 +105,4 @@ private Optional validateSimulationResult( } return Optional.empty(); } - - @VisibleForTesting - protected Optional getAddressAccountOverrideMap( - final JsonRpcRequestContext request) { - try { - return request.getOptionalParameter(2, AccountOverrideMap.class); - } catch (JsonRpcParameter.JsonRpcParameterException e) { - throw new InvalidJsonRpcRequestException( - "Invalid account overrides parameter (index 2)", RpcErrorType.INVALID_CALL_PARAMS, e); - } - } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java index 1601241db62..aba51eb7a69 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java @@ -116,8 +116,7 @@ protected Object resultByBlockNumber( .getAndMapWorldState( blockHeader.getBlockHash(), ws -> { - final WorldUpdater updater = - transactionSimulator.getEffectiveWorldStateUpdater(blockHeader, ws); + final WorldUpdater updater = transactionSimulator.getEffectiveWorldStateUpdater(ws); try { Arrays.stream(transactionsAndTraceTypeParameters) .forEachOrdered( @@ -158,6 +157,11 @@ private JsonNode getSingleCallResult( final Set traceTypes = traceTypeParameter.getTraceTypes(); final DebugOperationTracer tracer = new DebugOperationTracer(buildTraceOptions(traceTypes), false); + final var miningBeneficiary = + protocolSchedule + .getByBlockHeader(header) + .getMiningBeneficiaryCalculator() + .calculateBeneficiary(header); final Optional maybeSimulatorResult = transactionSimulator.processWithWorldUpdater( callParameter, @@ -165,7 +169,8 @@ private JsonNode getSingleCallResult( buildTransactionValidationParams(), tracer, header, - worldUpdater); + worldUpdater, + miningBeneficiary); LOG.trace("Executing {} call for transaction {}", traceTypeParameter, callParameter); if (maybeSimulatorResult.isEmpty()) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java index 4aad65f43c8..967a803f99d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.DefaultSyncStatus; +import org.hyperledger.besu.ethereum.core.ImmutableMiningConfiguration; import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.core.Transaction; @@ -141,6 +142,7 @@ public void setupTest() throws Exception { blockchain, blockchainSetupUtil.getWorldArchive(), blockchainSetupUtil.getProtocolSchedule(), + ImmutableMiningConfiguration.newDefault(), 0L); service = diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java index 75f84e1333f..0da11a03528 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java @@ -175,6 +175,7 @@ protected Map getRpcMethods( blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive(), blockchainSetupUtil.getProtocolSchedule(), + miningConfiguration, 0L); return new JsonRpcMethodsFactory() diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java index f7c0c31914f..5046152a6fb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java @@ -72,6 +72,7 @@ public class EthCreateAccessListTest { @Mock private BlockHeader latestBlockHeader; @Mock private BlockHeader finalizedBlockHeader; @Mock private BlockHeader genesisBlockHeader; + @Mock private BlockHeader pendingBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -93,6 +94,9 @@ public void setUp() { when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); when(latestBlockHeader.getNumber()).thenReturn(2L); + when(pendingBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(pendingBlockHeader.getNumber()).thenReturn(3L); + when(transactionSimulator.simulatePendingBlockHeader()).thenReturn(pendingBlockHeader); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthCreateAccessList(blockchainQueries, transactionSimulator); @@ -139,6 +143,18 @@ public void shouldUseGasPriceParameterWhenIsPresent() { assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } + @Test + public void pendingBlockTagEstimateOnPendingBlock() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO), "pending"); + mockTransactionSimulatorResult(true, false, 1L, pendingBlockHeader); + + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = @@ -186,7 +202,8 @@ public void shouldReturnEmptyAccessListIfNoParameterAndWithoutAccessedStorage() mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); + verify(transactionSimulator, times(1)) + .process(any(), eq(Optional.empty()), any(), any(), eq(latestBlockHeader)); } @Test @@ -207,7 +224,8 @@ public void shouldReturnAccessListIfNoParameterAndWithAccessedStorage() { assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); + verify(transactionSimulator, times(2)) + .process(any(), eq(Optional.empty()), any(), any(), eq(latestBlockHeader)); } @Test @@ -224,7 +242,8 @@ public void shouldReturnEmptyAccessListIfNoAccessedStorage() { // Set TransactionSimulator.process response mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); + verify(transactionSimulator, times(1)) + .process(any(), eq(Optional.empty()), any(), any(), eq(latestBlockHeader)); } @Test @@ -245,7 +264,8 @@ public void shouldReturnAccessListIfParameterAndSameAccessedStorage() { assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); + verify(transactionSimulator, times(1)) + .process(any(), eq(Optional.empty()), any(), any(), eq(latestBlockHeader)); } @Test @@ -269,7 +289,8 @@ public void shouldReturnAccessListIfWithParameterAndDifferentAccessedStorage() { assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); + verify(transactionSimulator, times(2)) + .process(any(), eq(Optional.empty()), any(), any(), eq(latestBlockHeader)); } @Test @@ -289,7 +310,8 @@ public void shouldReturnAccessListWhenBlockTagParamIsPresent() { assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(finalizedBlockHeader)); + verify(transactionSimulator, times(2)) + .process(any(), eq(Optional.empty()), any(), any(), eq(finalizedBlockHeader)); } @Test @@ -309,7 +331,8 @@ public void shouldReturnAccessListWhenBlockNumberParamIsPresent() { assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(genesisBlockHeader)); + verify(transactionSimulator, times(2)) + .process(any(), eq(Optional.empty()), any(), any(), eq(genesisBlockHeader)); } private JsonRpcResponse responseWithMockTracer( @@ -328,14 +351,21 @@ private AccessListOperationTracer createMockTracer( return tracer; } + @SuppressWarnings("ReferenceEquality") private void mockTransactionSimulatorResult( final boolean isSuccessful, final boolean isReverted, final long estimateGas, final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); - when(transactionSimulator.process(any(), any(), any(), eq(blockHeader))) - .thenReturn(Optional.of(mockTxSimResult)); + if (blockHeader == pendingBlockHeader) { + when(transactionSimulator.processOnPending( + any(), eq(Optional.empty()), any(), any(), eq(blockHeader))) + .thenReturn(Optional.of(mockTxSimResult)); + } else { + when(transactionSimulator.process(any(), eq(Optional.empty()), any(), any(), eq(blockHeader))) + .thenReturn(Optional.of(mockTxSimResult)); + } final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getRevertReason()) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java index 7c770f08a2d..74c04082453 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java @@ -38,7 +38,6 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -70,6 +69,7 @@ public class EthEstimateGasTest { @Mock private BlockHeader latestBlockHeader; @Mock private BlockHeader finalizedBlockHeader; @Mock private BlockHeader genesisBlockHeader; + @Mock private BlockHeader pendingBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -91,6 +91,9 @@ public void setUp() { when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); when(latestBlockHeader.getNumber()).thenReturn(2L); + when(pendingBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(pendingBlockHeader.getNumber()).thenReturn(3L); + when(transactionSimulator.simulatePendingBlockHeader()).thenReturn(pendingBlockHeader); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthEstimateGas(blockchainQueries, transactionSimulator); @@ -376,11 +379,7 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { .process( eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), eq(Optional.empty()), // no account overrides - eq( - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(true) - .build()), + eq(TransactionValidationParams.transactionSimulatorAllowExceedingBalance()), any(OperationTracer.class), eq(latestBlockHeader)); } @@ -397,11 +396,7 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() { .process( eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), eq(Optional.empty()), // no account overrides - eq( - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(false) - .build()), + eq(TransactionValidationParams.transactionSimulator()), any(OperationTracer.class), eq(latestBlockHeader)); } @@ -432,6 +427,17 @@ public void shouldUseBlockTagParamWhenPresent() { assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } + @Test + public void pendingBlockTagEstimateOnPendingBlock() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "pending"); + mockTransientProcessorResultGasEstimate(1L, true, false, pendingBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + @Test public void shouldUseBlockNumberParamWhenPresent() { final JsonRpcRequestContext request = @@ -488,6 +494,7 @@ private void mockTransientProcessorResultGasEstimate( isSuccessful, estimateGas, gasPrice, revertReason, blockHeader); } + @SuppressWarnings("ReferenceEquality") private TransactionSimulatorResult getMockTransactionSimulatorResult( final boolean isSuccessful, final long estimateGas, @@ -495,21 +502,37 @@ private TransactionSimulatorResult getMockTransactionSimulatorResult( final Optional revertReason, final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); - when(transactionSimulator.process( - eq(modifiedLegacyTransactionCallParameter(gasPrice)), - eq(Optional.empty()), // no account overrides - any(TransactionValidationParams.class), - any(OperationTracer.class), - eq(blockHeader))) - .thenReturn(Optional.of(mockTxSimResult)); - when(transactionSimulator.process( - eq(modifiedEip1559TransactionCallParameter()), - eq(Optional.empty()), // no account overrides - any(TransactionValidationParams.class), - any(OperationTracer.class), - eq(blockHeader))) - .thenReturn(Optional.of(mockTxSimResult)); - + if (blockHeader == pendingBlockHeader) { + when(transactionSimulator.processOnPending( + eq(modifiedLegacyTransactionCallParameter(gasPrice)), + eq(Optional.empty()), // no account overrides + any(TransactionValidationParams.class), + any(OperationTracer.class), + eq(blockHeader))) + .thenReturn(Optional.of(mockTxSimResult)); + when(transactionSimulator.processOnPending( + eq(modifiedEip1559TransactionCallParameter()), + eq(Optional.empty()), // no account overrides + any(TransactionValidationParams.class), + any(OperationTracer.class), + eq(blockHeader))) + .thenReturn(Optional.of(mockTxSimResult)); + } else { + when(transactionSimulator.process( + eq(modifiedLegacyTransactionCallParameter(gasPrice)), + eq(Optional.empty()), // no account overrides + any(TransactionValidationParams.class), + any(OperationTracer.class), + eq(blockHeader))) + .thenReturn(Optional.of(mockTxSimResult)); + when(transactionSimulator.process( + eq(modifiedEip1559TransactionCallParameter()), + eq(Optional.empty()), // no account overrides + any(TransactionValidationParams.class), + any(OperationTracer.class), + eq(blockHeader))) + .thenReturn(Optional.of(mockTxSimResult)); + } final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getRevertReason()).thenReturn(revertReason); 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 c4a3f89aaa3..50eb978df1e 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 @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.blockcreation; +import static org.hyperledger.besu.ethereum.core.BlockHeaderBuilder.createPending; import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent; import org.hyperledger.besu.datatypes.Address; @@ -29,7 +30,6 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; -import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; @@ -41,15 +41,12 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; -import org.hyperledger.besu.ethereum.mainnet.DifficultyCalculator; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; -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; @@ -60,7 +57,6 @@ import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; -import java.math.BigInteger; import java.util.List; import java.util.Optional; import java.util.concurrent.CancellationException; @@ -198,12 +194,15 @@ protected BlockCreationResult createBlock( protocolSchedule.getForNextBlockHeader(parentHeader, timestamp); final ProcessableBlockHeader processableBlockHeader = - createPendingBlockHeader( - timestamp, - maybePrevRandao, - maybeParentBeaconBlockRoot, - newProtocolSpec, - parentHeader); + createPending( + newProtocolSpec, + parentHeader, + miningConfiguration, + timestamp, + maybePrevRandao, + maybeParentBeaconBlockRoot) + .buildProcessableBlockHeader(); + final Address miningBeneficiary = miningBeneficiaryCalculator.getMiningBeneficiary(processableBlockHeader.getNumber()); @@ -421,52 +420,6 @@ private List selectOmmers() { return Lists.newArrayList(); } - private ProcessableBlockHeader createPendingBlockHeader( - final long timestamp, - final Optional maybePrevRandao, - final Optional maybeParentBeaconBlockRoot, - final ProtocolSpec protocolSpec, - final BlockHeader parentHeader) { - final long newBlockNumber = parentHeader.getNumber() + 1; - long gasLimit = - protocolSpec - .getGasLimitCalculator() - .nextGasLimit( - parentHeader.getGasLimit(), - miningConfiguration.getTargetGasLimit().orElse(parentHeader.getGasLimit()), - newBlockNumber); - - final DifficultyCalculator difficultyCalculator = protocolSpec.getDifficultyCalculator(); - final BigInteger difficulty = difficultyCalculator.nextDifficulty(timestamp, parentHeader); - - final Wei baseFee = - Optional.of(protocolSpec.getFeeMarket()) - .filter(FeeMarket::implementsBaseFee) - .map(BaseFeeMarket.class::cast) - .map( - feeMarket -> - feeMarket.computeBaseFee( - newBlockNumber, - parentHeader.getBaseFee().orElse(Wei.ZERO), - parentHeader.getGasUsed(), - feeMarket.targetGasUsed(parentHeader))) - .orElse(null); - - final Bytes32 prevRandao = maybePrevRandao.orElse(null); - final Bytes32 parentBeaconBlockRoot = maybeParentBeaconBlockRoot.orElse(null); - return BlockHeaderBuilder.create() - .parentHash(parentHeader.getHash()) - .coinbase(miningConfiguration.getCoinbase().orElseThrow()) - .difficulty(Difficulty.of(difficulty)) - .number(newBlockNumber) - .gasLimit(gasLimit) - .timestamp(timestamp) - .baseFee(baseFee) - .prevRandao(prevRandao) - .parentBeaconBlockRoot(parentBeaconBlockRoot) - .buildProcessableBlockHeader(); - } - @Override public void cancel() { isCancelled.set(true); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java index fb464088bf3..381785995ac 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java @@ -22,10 +22,14 @@ import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.mainnet.DifficultyCalculator; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.evm.log.LogsBloomFilter; import java.time.Instant; +import java.util.Optional; import java.util.OptionalLong; import org.apache.tuweni.bytes.Bytes; @@ -158,6 +162,55 @@ public static BlockHeaderBuilder fromBuilder(final BlockHeaderBuilder fromBuilde return toBuilder; } + public static BlockHeaderBuilder createPending( + final ProtocolSpec protocolSpec, + final BlockHeader parentHeader, + final MiningConfiguration miningConfiguration, + final long timestamp, + final Optional maybePrevRandao, + final Optional maybeParentBeaconBlockRoot) { + + final long newBlockNumber = parentHeader.getNumber() + 1; + final long gasLimit = + protocolSpec + .getGasLimitCalculator() + .nextGasLimit( + parentHeader.getGasLimit(), + miningConfiguration.getTargetGasLimit().orElse(parentHeader.getGasLimit()), + newBlockNumber); + + final DifficultyCalculator difficultyCalculator = protocolSpec.getDifficultyCalculator(); + final var difficulty = + Difficulty.of(difficultyCalculator.nextDifficulty(timestamp, parentHeader)); + + final Wei baseFee; + if (protocolSpec.getFeeMarket().implementsBaseFee()) { + final var baseFeeMarket = (BaseFeeMarket) protocolSpec.getFeeMarket(); + baseFee = + baseFeeMarket.computeBaseFee( + newBlockNumber, + parentHeader.getBaseFee().orElse(Wei.ZERO), + parentHeader.getGasUsed(), + baseFeeMarket.targetGasUsed(parentHeader)); + } else { + baseFee = null; + } + + final Bytes32 prevRandao = maybePrevRandao.orElse(null); + final Bytes32 parentBeaconBlockRoot = maybeParentBeaconBlockRoot.orElse(null); + + return BlockHeaderBuilder.create() + .parentHash(parentHeader.getHash()) + .coinbase(miningConfiguration.getCoinbase().orElseThrow()) + .difficulty(difficulty) + .number(newBlockNumber) + .gasLimit(gasLimit) + .timestamp(timestamp) + .baseFee(baseFee) + .prevRandao(prevRandao) + .parentBeaconBlockRoot(parentBeaconBlockRoot); + } + public BlockHeader buildBlockHeader() { validateBlockHeader(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index 2db290925fc..20650bc74aa 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -196,4 +196,25 @@ public Optional getTargetBlobsPerBlock() { public String toLogString() { return getNumber() + " (time: " + getTimestamp() + ")"; } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("ProcessableBlockHeader{"); + sb.append("number=").append(number).append(", "); + sb.append("parentHash=").append(parentHash).append(", "); + sb.append("coinbase=").append(coinbase).append(", "); + sb.append("difficulty=").append(difficulty).append(", "); + sb.append("gasLimit=").append(gasLimit).append(", "); + sb.append("timestamp=").append(timestamp).append(", "); + sb.append("baseFee=").append(baseFee).append(", "); + sb.append("mixHashOrPrevRandao=").append(mixHashOrPrevRandao).append(", "); + if (parentBeaconBlockRoot != null) { + sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", "); + } + if (targetBlobsPerBlock != null) { + sb.append("targetBlobsPerBlock=").append(targetBlobsPerBlock); + } + return sb.append("}").toString(); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java index be2185b09a7..78d6497335a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java @@ -35,6 +35,9 @@ public interface TransactionValidationParams { TransactionValidationParams transactionSimulatorParams = ImmutableTransactionValidationParams.of(false, false, false, false, false, true); + TransactionValidationParams transactionSimulatorAllowExceedingBalanceParams = + ImmutableTransactionValidationParams.of(false, true, false, false, false, true); + @Value.Default default boolean isAllowFutureNonce() { return false; @@ -69,6 +72,10 @@ static TransactionValidationParams transactionSimulator() { return transactionSimulatorParams; } + static TransactionValidationParams transactionSimulatorAllowExceedingBalance() { + return transactionSimulatorAllowExceedingBalanceParams; + } + static TransactionValidationParams processingBlock() { return processingBlockParams; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index e4db6aafb15..c4558ab14dc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -28,9 +28,10 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; @@ -84,16 +85,19 @@ public class TransactionSimulator { private final Blockchain blockchain; private final WorldStateArchive worldStateArchive; private final ProtocolSchedule protocolSchedule; + private final MiningConfiguration miningConfiguration; private final long rpcGasCap; public TransactionSimulator( final Blockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, + final MiningConfiguration miningConfiguration, final long rpcGasCap) { this.blockchain = blockchain; this.worldStateArchive = worldStateArchive; this.protocolSchedule = protocolSchedule; + this.miningConfiguration = miningConfiguration; this.rpcGasCap = rpcGasCap; } @@ -141,14 +145,82 @@ public Optional process( blockHeader); } + public Optional processOnPending( + final CallParameter callParams, + final Optional maybeStateOverrides, + final TransactionValidationParams transactionValidationParams, + final OperationTracer operationTracer, + final ProcessableBlockHeader pendingBlockHeader) { + + try (final MutableWorldState disposableWorldState = + duplicateWorldStateAtParent(pendingBlockHeader.getParentHash())) { + WorldUpdater updater = getEffectiveWorldStateUpdater(disposableWorldState); + + // in order to trace the state diff we need to make sure that + // the world updater always has a parent + if (operationTracer instanceof DebugOperationTracer) { + updater = updater.parentUpdater().isPresent() ? updater : updater.updater(); + } + + return processWithWorldUpdater( + callParams, + maybeStateOverrides, + transactionValidationParams, + operationTracer, + pendingBlockHeader, + updater, + Address.ZERO); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public ProcessableBlockHeader simulatePendingBlockHeader() { + final long timestamp = System.currentTimeMillis(); + final var chainHeadHeader = blockchain.getChainHeadHeader(); + final ProtocolSpec protocolSpec = + protocolSchedule.getForNextBlockHeader(chainHeadHeader, timestamp); + + final var simulatedBlockHeader = + BlockHeaderBuilder.createPending( + protocolSpec, + chainHeadHeader, + miningConfiguration, + timestamp, + Optional.empty(), + Optional.empty()) + .buildProcessableBlockHeader(); + + LOG.trace("Simulated block header: {}", simulatedBlockHeader); + + return simulatedBlockHeader; + } + + private MutableWorldState duplicateWorldStateAtParent(final Hash parentHash) { + final var parentHeader = + blockchain + .getBlockHeader(parentHash) + .orElseThrow( + () -> + new IllegalStateException("Block with hash " + parentHash + " not available")); + + final Hash parentStateRoot = parentHeader.getStateRoot(); + return worldStateArchive + .getMutable(parentHeader, false) + .orElseThrow( + () -> + new IllegalArgumentException( + "World state not available for block " + + parentHeader.getNumber() + + " with state root " + + parentStateRoot)); + } + public Optional processAtHead(final CallParameter callParams) { final var chainHeadHash = blockchain.getChainHeadHash(); return process( callParams, - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(true) - .build(), + TransactionValidationParams.transactionSimulatorAllowExceedingBalance(), OperationTracer.NO_TRACING, (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult, blockchain @@ -209,7 +281,7 @@ public Optional process( try (final MutableWorldState ws = getWorldState(header)) { - WorldUpdater updater = getEffectiveWorldStateUpdater(header, ws); + WorldUpdater updater = getEffectiveWorldStateUpdater(ws); // in order to trace the state diff we need to make sure that // the world updater always has a parent @@ -217,6 +289,12 @@ public Optional process( updater = updater.parentUpdater().isPresent() ? updater : updater.updater(); } + final var miningBeneficiary = + protocolSchedule + .getByBlockHeader(header) + .getMiningBeneficiaryCalculator() + .calculateBeneficiary(header); + return preWorldStateCloseGuard.apply( ws, processWithWorldUpdater( @@ -225,7 +303,8 @@ public Optional process( transactionValidationParams, operationTracer, header, - updater)); + updater, + miningBeneficiary)); } catch (final Exception e) { return Optional.empty(); @@ -267,21 +346,25 @@ public Optional processWithWorldUpdater( final Optional maybeStateOverrides, final TransactionValidationParams transactionValidationParams, final OperationTracer operationTracer, - final BlockHeader header, - final WorldUpdater updater) { - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(header); + final ProcessableBlockHeader processableHeader, + final WorldUpdater updater, + final Address miningBeneficiary) { + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(processableHeader); final Address senderAddress = callParams.getFrom() != null ? callParams.getFrom() : DEFAULT_FROM; - BlockHeader blockHeaderToProcess = header; - - if (transactionValidationParams.isAllowExceedingBalance() && header.getBaseFee().isPresent()) { + final ProcessableBlockHeader blockHeaderToProcess; + if (transactionValidationParams.isAllowExceedingBalance() + && processableHeader.getBaseFee().isPresent()) { blockHeaderToProcess = - BlockHeaderBuilder.fromHeader(header) + new BlockHeaderBuilder() + .populateFrom(processableHeader) .baseFee(Wei.ZERO) .blockHeaderFunctions(protocolSpec.getBlockHeaderFunctions()) - .buildBlockHeader(); + .buildProcessableBlockHeader(); + } else { + blockHeaderToProcess = processableHeader; } if (maybeStateOverrides.isPresent()) { for (Address accountToOverride : maybeStateOverrides.get().keySet()) { @@ -315,7 +398,7 @@ public Optional processWithWorldUpdater( buildTransaction( callParams, transactionValidationParams, - header, + processableHeader, senderAddress, nonce, simulationGasCap, @@ -330,9 +413,7 @@ public Optional processWithWorldUpdater( updater, blockHeaderToProcess, transaction, - protocolSpec - .getMiningBeneficiaryCalculator() - .calculateBeneficiary(blockHeaderToProcess), + miningBeneficiary, new CachingBlockHashLookup(blockHeaderToProcess, blockchain), false, transactionValidationParams, @@ -395,7 +476,7 @@ private long calculateSimulationGasCap( private Optional buildTransaction( final CallParameter callParams, final TransactionValidationParams transactionValidationParams, - final BlockHeader header, + final ProcessableBlockHeader processableHeader, final Address senderAddress, final long nonce, final long gasLimit, @@ -435,11 +516,11 @@ private Optional buildTransaction( maxFeePerBlobGas = callParams.getMaxFeePerBlobGas().orElse(blobGasPrice); } - if (shouldSetGasPrice(callParams, header)) { + if (shouldSetGasPrice(callParams, processableHeader)) { transactionBuilder.gasPrice(gasPrice); } - if (shouldSetMaxFeePerGas(callParams, header)) { + if (shouldSetMaxFeePerGas(callParams, processableHeader)) { transactionBuilder.maxFeePerGas(maxFeePerGas).maxPriorityFeePerGas(maxPriorityFeePerGas); } @@ -463,8 +544,7 @@ private Optional buildTransaction( return Optional.ofNullable(transaction); } - public WorldUpdater getEffectiveWorldStateUpdater( - final BlockHeader header, final MutableWorldState publicWorldState) { + public WorldUpdater getEffectiveWorldStateUpdater(final MutableWorldState publicWorldState) { return publicWorldState.updater(); } @@ -490,7 +570,8 @@ public Optional doesAddressExist( return Optional.of(worldState.get(address) != null); } - private boolean shouldSetGasPrice(final CallParameter callParams, final BlockHeader header) { + private boolean shouldSetGasPrice( + final CallParameter callParams, final ProcessableBlockHeader header) { if (header.getBaseFee().isEmpty()) { return true; } @@ -499,7 +580,8 @@ private boolean shouldSetGasPrice(final CallParameter callParams, final BlockHea return callParams.getMaxPriorityFeePerGas().isEmpty() && callParams.getMaxFeePerGas().isEmpty(); } - private boolean shouldSetMaxFeePerGas(final CallParameter callParams, final BlockHeader header) { + private boolean shouldSetMaxFeePerGas( + final CallParameter callParams, final ProcessableBlockHeader header) { if (protocolSchedule.getChainId().isEmpty()) { return false; } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java index 8406d1e4c31..233d16ca2ff 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java @@ -15,8 +15,10 @@ package org.hyperledger.besu.ethereum.transaction; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.tracing.OperationTracer.NO_TRACING; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -32,12 +34,14 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; @@ -52,7 +56,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.math.BigInteger; @@ -99,10 +103,13 @@ public class TransactionSimulatorTest { @BeforeEach public void setUp() { + final var miningConfiguration = MiningConfiguration.newDefault().setCoinbase(Address.ZERO); this.transactionSimulator = - new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule, 0); + new TransactionSimulator( + blockchain, worldStateArchive, protocolSchedule, miningConfiguration, 0); this.cappedTransactionSimulator = - new TransactionSimulator(blockchain, worldStateArchive, protocolSchedule, GAS_CAP); + new TransactionSimulator( + blockchain, worldStateArchive, protocolSchedule, miningConfiguration, GAS_CAP); } @Test @@ -182,6 +189,43 @@ public void shouldReturnSuccessfulResultWhenProcessingIsSuccessful() { verifyTransactionWasProcessed(expectedTransaction); } + @Test + public void simulateOnPendingBlockWorks() { + final CallParameter callParameter = eip1559TransactionCallParameter(); + + final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + + final Transaction expectedTransaction = + Transaction.builder() + .type(TransactionType.EIP1559) + .chainId(BigInteger.ONE) + .nonce(1L) + .gasLimit(blockHeader.getGasLimit()) + .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) + .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(callParameter.getPayload()) + .signature(FAKE_SIGNATURE) + .build(); + mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL); + + final Optional result = + transactionSimulator.processOnPending( + callParameter, + Optional.empty(), + TransactionValidationParams.transactionSimulator(), + NO_TRACING, + transactionSimulator.simulatePendingBlockHeader()); + + assertThat(result.get().isSuccessful()).isTrue(); + verifyTransactionWasProcessed(expectedTransaction); + } + @Test public void shouldSetGasPriceToZeroWhenExceedingBalanceAllowed() { final CallParameter callParameter = legacyTransactionCallParameter(Wei.ONE); @@ -209,7 +253,7 @@ public void shouldSetGasPriceToZeroWhenExceedingBalanceAllowed() { transactionSimulator.process( callParameter, ImmutableTransactionValidationParams.builder().isAllowExceedingBalance(true).build(), - OperationTracer.NO_TRACING, + NO_TRACING, 1L); verifyTransactionWasProcessed(expectedTransaction); @@ -245,7 +289,7 @@ public void shouldSetFeePerGasToZeroWhenExceedingBalanceAllowed() { transactionSimulator.process( callParameter, ImmutableTransactionValidationParams.builder().isAllowExceedingBalance(true).build(), - OperationTracer.NO_TRACING, + NO_TRACING, 1L); verifyTransactionWasProcessed(expectedTransaction); @@ -279,7 +323,7 @@ public void shouldNotSetGasPriceToZeroWhenExceedingBalanceIsNotAllowed() { transactionSimulator.process( callParameter, ImmutableTransactionValidationParams.builder().isAllowExceedingBalance(false).build(), - OperationTracer.NO_TRACING, + NO_TRACING, 1L); verifyTransactionWasProcessed(expectedTransaction); @@ -314,7 +358,7 @@ public void shouldNotSetFeePerGasToZeroWhenExceedingBalanceIsNotAllowed() { transactionSimulator.process( callParameter, ImmutableTransactionValidationParams.builder().isAllowExceedingBalance(false).build(), - OperationTracer.NO_TRACING, + NO_TRACING, 1L); verifyTransactionWasProcessed(expectedTransaction); @@ -600,10 +644,7 @@ public void shouldCapGasLimitWhenOriginalTransactionExceedsGasCap() { // call process with original transaction cappedTransactionSimulator.process( - callParameter, - TransactionValidationParams.transactionSimulator(), - OperationTracer.NO_TRACING, - 1L); + callParameter, TransactionValidationParams.transactionSimulator(), NO_TRACING, 1L); // expect overwritten transaction to be processed verifyTransactionWasProcessed(expectedTransaction); @@ -638,10 +679,7 @@ public void shouldUseProvidedGasLimitWhenBelowRpcCapGas() { // call process with original transaction cappedTransactionSimulator.process( - callParameter, - TransactionValidationParams.transactionSimulator(), - OperationTracer.NO_TRACING, - 1L); + callParameter, TransactionValidationParams.transactionSimulator(), NO_TRACING, 1L); // expect overwritten transaction to be processed verifyTransactionWasProcessed(expectedTransaction); @@ -677,10 +715,7 @@ public void shouldUseRpcGasCapWhenGasLimitNoPresent() { // call process with original transaction cappedTransactionSimulator.process( - callParameter, - TransactionValidationParams.transactionSimulator(), - OperationTracer.NO_TRACING, - 1L); + callParameter, TransactionValidationParams.transactionSimulator(), NO_TRACING, 1L); // expect transaction with the original gas limit to be processed verifyTransactionWasProcessed(expectedTransaction); @@ -799,6 +834,8 @@ private void mockBlockchainForBlockHeader(final BlockHeader blockHeader) { when(blockchain.getBlockHeader(blockHeader.getNumber())).thenReturn(Optional.of(blockHeader)); when(blockchain.getBlockHeader(blockHeader.getBlockHash())) .thenReturn(Optional.of(blockHeader)); + when(blockchain.getChainHeadHash()).thenReturn(blockHeader.getHash()); + when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); } private void mockProtocolSpecForProcessWithWorldUpdater() { @@ -806,11 +843,15 @@ private void mockProtocolSpecForProcessWithWorldUpdater() { final BlockHashProcessor blockHashProcessor = mock(BlockHashProcessor.class); when(protocolSchedule.getChainId()).thenReturn(Optional.of(BigInteger.ONE)); when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(protocolSpec); when(protocolSpec.getTransactionProcessor()).thenReturn(transactionProcessor); when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); when(protocolSpec.getBlockHeaderFunctions()).thenReturn(blockHeaderFunctions); when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0)); when(protocolSpec.getBlockHashProcessor()).thenReturn(blockHashProcessor); + when(protocolSpec.getGasCalculator()).thenReturn(new FrontierGasCalculator()); + when(protocolSpec.getGasLimitCalculator()).thenReturn(GasLimitCalculator.constant()); + when(protocolSpec.getDifficultyCalculator()).thenReturn((time, parent) -> BigInteger.TEN); } private void mockProcessorStatusForTransaction( diff --git a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningControllerTest.java b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningControllerTest.java index a9c97b1cc4e..fa0608a031c 100644 --- a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningControllerTest.java +++ b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningControllerTest.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; @@ -70,7 +71,8 @@ private NodeSmartContractPermissioningController setupController( genesisState.writeStateTo(worldArchive.getMutable()); final TransactionSimulator ts = - new TransactionSimulator(blockchain, worldArchive, protocolSchedule, 0L); + new TransactionSimulator( + blockchain, worldArchive, protocolSchedule, MiningConfiguration.newDefault(), 0L); final Address contractAddress = Address.fromHexString(contractAddressString); when(metricsSystem.createCounter( diff --git a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java index 1fd4f58a4bd..d9746cfcf5f 100644 --- a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java +++ b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.MiningConfiguration; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -69,7 +70,8 @@ private TransactionSmartContractPermissioningController setupController( genesisState.writeStateTo(worldArchive.getMutable()); final TransactionSimulator ts = - new TransactionSimulator(blockchain, worldArchive, protocolSchedule, 0L); + new TransactionSimulator( + blockchain, worldArchive, protocolSchedule, MiningConfiguration.newDefault(), 0L); final Address contractAddress = Address.fromHexString(contractAddressString); when(metricsSystem.createCounter( diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java index 20b2db47cb3..a94c098bf8d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java @@ -47,15 +47,16 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o * @return the access list */ public List getAccessList() { - final List list = new ArrayList<>(); if (warmedUpStorage != null && !warmedUpStorage.isEmpty()) { + final List list = new ArrayList<>(warmedUpStorage.size()); warmedUpStorage .rowMap() .forEach( (address, storageKeys) -> list.add(new AccessListEntry(address, new ArrayList<>(storageKeys.keySet())))); + return list; } - return list; + return List.of(); } /** diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 13b0912a9da..f8608f89325 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -71,7 +71,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'dsbVupAvtmZlEeEeVDtk+VrzGFvyKxHgQntaMtOq5TY=' + knownHash = 'TPCo4SZ61OrJxRAa2SIcAIOAOjVTdRw+UOeHMuiJP84=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java index d5fc0a34979..4736f099f53 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java @@ -26,35 +26,92 @@ /** Transaction simulation service interface */ @Unstable public interface TransactionSimulationService extends BesuService { + /** - * Simulate transaction execution at the block identified by the hash + * Simulate transaction execution at the block identified by the hash if present, otherwise on the + * pending block, with optional state overrides that can be applied before the simulation. * * @param transaction tx - * @param blockHash the hash of the block + * @param accountOverrides state overrides to apply to this simulation + * @param maybeBlockHash optional hash of the block, empty to simulate on pending block * @param operationTracer the tracer * @param isAllowExceedingBalance should ignore the sender balance during the simulation? * @return the result of the simulation */ Optional simulate( Transaction transaction, - Hash blockHash, + Optional accountOverrides, + Optional maybeBlockHash, OperationTracer operationTracer, boolean isAllowExceedingBalance); + /** + * Simulate transaction execution at the block identified by the hash if present, otherwise on the + * pending block + * + * @param transaction tx + * @param maybeBlockHash optional hash of the block, empty to simulate on pending block + * @param operationTracer the tracer + * @param isAllowExceedingBalance should ignore the sender balance during the simulation? + * @return the result of the simulation + */ + default Optional simulate( + final Transaction transaction, + final Optional maybeBlockHash, + final OperationTracer operationTracer, + final boolean isAllowExceedingBalance) { + return simulate( + transaction, Optional.empty(), maybeBlockHash, operationTracer, isAllowExceedingBalance); + } + /** * Simulate transaction execution at the block identified by the hash * * @param transaction tx + * @param blockHash then hash of the block + * @param operationTracer the tracer + * @param isAllowExceedingBalance should ignore the sender balance during the simulation? + * @return the result of the simulation + * @deprecated use {@link #simulate(Transaction, Optional, OperationTracer, boolean)} + */ + @Deprecated(since = "24.12", forRemoval = true) + default Optional simulate( + final Transaction transaction, + final Hash blockHash, + final OperationTracer operationTracer, + final boolean isAllowExceedingBalance) { + return simulate( + transaction, + Optional.empty(), + Optional.of(blockHash), + operationTracer, + isAllowExceedingBalance); + } + + /** + * Simulate transaction execution at the block identified by the hash, with optional state + * overrides that can be applied before the simulation. + * + * @param transaction tx * @param accountOverrides state overrides to apply to this simulation * @param blockHash the hash of the block * @param operationTracer the tracer * @param isAllowExceedingBalance should ignore the sender balance during the simulation? * @return the result of the simulation + * @deprecated use {@link #simulate(Transaction, Optional, Optional, OperationTracer, boolean)} */ - Optional simulate( - Transaction transaction, - Optional accountOverrides, - Hash blockHash, - OperationTracer operationTracer, - boolean isAllowExceedingBalance); + @Deprecated(since = "24.12", forRemoval = true) + default Optional simulate( + final Transaction transaction, + final Optional accountOverrides, + final Hash blockHash, + final OperationTracer operationTracer, + final boolean isAllowExceedingBalance) { + return simulate( + transaction, + accountOverrides, + Optional.of(blockHash), + operationTracer, + isAllowExceedingBalance); + } }