From b0f901d9676f2f3ea8c8bcba56ca22e8b14cfe53 Mon Sep 17 00:00:00 2001 From: Fabio Di Fabio Date: Wed, 21 Aug 2024 16:48:15 +0200 Subject: [PATCH] Honor block number or tag parameter in eth_estimateGas and eth_createAccessList Signed-off-by: Fabio Di Fabio --- .../internal/methods/AbstractEstimateGas.java | 70 +++++-- .../internal/methods/EthCreateAccessList.java | 72 ++----- .../internal/methods/EthEstimateGas.java | 111 +++++------ .../methods/EthCreateAccessListTest.java | 142 +++++++++----- .../internal/methods/EthEstimateGasTest.java | 180 ++++++++++-------- ...stimateGas_from_contract_withBlockNum.json | 21 ++ ...stimateGas_from_contract_withBlockTag.json | 21 ++ ...imateGas_insufficientGas_withBlockNum.json | 21 ++ ...eth_estimateGas_transfer_withBlockTag.json | 21 ++ 9 files changed, 403 insertions(+), 256 deletions(-) create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json create mode 100644 ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json 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 ef8ca5b316d..4cbfb818dbe 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 @@ -14,15 +14,19 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; + 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.parameters.BlockParameter; 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.JsonRpcError; 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.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -34,32 +38,68 @@ import java.util.Optional; -public abstract class AbstractEstimateGas implements JsonRpcMethod { +public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod { private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D; - protected final BlockchainQueries blockchainQueries; protected final TransactionSimulator transactionSimulator; public AbstractEstimateGas( final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) { - this.blockchainQueries = blockchainQueries; + super(blockchainQueries); this.transactionSimulator = transactionSimulator; } - protected BlockHeader blockHeader() { - final Blockchain theChain = blockchainQueries.getBlockchain(); - - // Optimistically get the block header for the chain head without taking a lock, - // but revert to the safe implementation if it returns an empty optional. (It's - // possible the chain head has been updated but the block is still being persisted - // to storage/cache under the lock). - return theChain - .getBlockHeader(theChain.getChainHeadHash()) - .or(() -> theChain.getBlockHeaderSafe(theChain.getChainHeadHash())) - .orElse(null); + @Override + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { + try { + return request.getOptionalParameter(1, BlockParameter.class).orElse(BlockParameter.LATEST); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } + } + + protected 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()); + } + return getBlockchainQueries().getBlockHeaderByNumber(blockNumber); + } + + protected Optional validateBlockHeader( + final Optional maybeBlockHeader) { + if (maybeBlockHeader.isEmpty()) { + return Optional.of(RpcErrorType.BLOCK_NOT_FOUND); + } + + final var blockHeader = maybeBlockHeader.get(); + if (!getBlockchainQueries() + .getWorldStateArchive() + .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { + return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE); + } + 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) { return new CallParameter( 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 2be5047a24a..2b8e79a81e9 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 @@ -14,15 +14,11 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; - import org.hyperledger.besu.datatypes.AccessListEntry; 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.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; 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; @@ -52,44 +48,29 @@ public String getName() { } @Override - public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); - final BlockHeader blockHeader = blockHeader(); - final Optional jsonRpcError = validateBlockHeader(blockHeader); - if (jsonRpcError.isPresent()) { - return errorResponse(requestContext, jsonRpcError.get()); - } + protected Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter jsonCallParameter, + final BlockHeader blockHeader) { final AccessListSimulatorResult maybeResult = processTransaction(jsonCallParameter, blockHeader); // if the call accessList is different from the simulation result, calculate gas and return - if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.getTracer())) { + if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.tracer())) { final AccessListSimulatorResult result = processTransactionWithAccessListOverride( - jsonCallParameter, blockHeader, maybeResult.getTracer().getAccessList()); + jsonCallParameter, blockHeader, maybeResult.tracer().getAccessList()); return createResponse(requestContext, result); } else { return createResponse(requestContext, maybeResult); } } - private Optional validateBlockHeader(final BlockHeader blockHeader) { - if (blockHeader == null) { - return Optional.of(RpcErrorType.INTERNAL_ERROR); - } - if (!blockchainQueries - .getWorldStateArchive() - .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { - return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE); - } - return Optional.empty(); - } - - private JsonRpcResponse createResponse( + private Object createResponse( final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) { return result - .getResult() - .map(createResponse(requestContext, result.getTracer())) - .orElse(errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); + .result() + .map(createResponse(requestContext, result.tracer())) + .orElseGet(() -> errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); } private TransactionValidationParams transactionValidationParams( @@ -117,14 +98,12 @@ private boolean shouldProcessWithAccessListOverride( return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get()); } - private Function createResponse( + private Function createResponse( final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) { return result -> result.isSuccessful() - ? new JsonRpcSuccessResponse( - request.getRequest().getId(), - new CreateAccessListResult( - operationTracer.getAccessList(), processEstimateGas(result, operationTracer))) + ? new CreateAccessListResult( + operationTracer.getAccessList(), processEstimateGas(result, operationTracer)) : errorResponse(request, result); } @@ -138,8 +117,7 @@ private AccessListSimulatorResult processTransaction( final AccessListOperationTracer tracer = AccessListOperationTracer.create(); final Optional result = - transactionSimulator.process( - callParams, transactionValidationParams, tracer, blockHeader.getNumber()); + transactionSimulator.process(callParams, transactionValidationParams, tracer, blockHeader); return new AccessListSimulatorResult(result, tracer); } @@ -156,7 +134,7 @@ private AccessListSimulatorResult processTransactionWithAccessListOverride( final Optional result = transactionSimulator.process( - callParameter, transactionValidationParams, tracer, blockHeader.getNumber()); + callParameter, transactionValidationParams, tracer, blockHeader); return new AccessListSimulatorResult(result, tracer); } @@ -176,22 +154,6 @@ protected CallParameter overrideAccessList( Optional.ofNullable(accessListEntries)); } - private static class AccessListSimulatorResult { - final Optional result; - final AccessListOperationTracer tracer; - - public AccessListSimulatorResult( - final Optional result, final AccessListOperationTracer tracer) { - this.result = result; - this.tracer = tracer; - } - - public Optional getResult() { - return result; - } - - public AccessListOperationTracer getTracer() { - return tracer; - } - } + private record AccessListSimulatorResult( + Optional result, AccessListOperationTracer tracer) {} } 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 ca084ba0fbb..c713ea20a7b 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 @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * 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 @@ -14,13 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; - 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.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +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; @@ -38,7 +35,6 @@ import org.slf4j.LoggerFactory; public class EthEstimateGas extends AbstractEstimateGas { - private static final Logger LOG = LoggerFactory.getLogger(EthEstimateGas.class); public EthEstimateGas( @@ -52,19 +48,10 @@ public String getName() { } @Override - public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final JsonCallParameter callParams = validateAndGetCallParams(requestContext); - - final BlockHeader blockHeader = blockHeader(); - if (blockHeader == null) { - LOG.error("Chain head block not found"); - return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); - } - if (!blockchainQueries - .getWorldStateArchive() - .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { - return errorResponse(requestContext, RpcErrorType.WORLD_STATE_UNAVAILABLE); - } + protected Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter callParams, + final BlockHeader blockHeader) { final CallParameter modifiedCallParams = overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit()); @@ -72,44 +59,48 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); final EstimateGasOperationTracer operationTracer = new EstimateGasOperationTracer(); - - var gasUsed = - executeSimulation( - blockHeader, modifiedCallParams, operationTracer, isAllowExceedingBalance); - - if (gasUsed.isEmpty()) { - LOG.error("gasUsed is empty after simulating transaction."); - return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); - } - - // if the transaction is invalid or doesn't have enough gas with the max it never will! - if (gasUsed.get().isInvalid() || !gasUsed.get().isSuccessful()) { - return errorResponse(requestContext, gasUsed.get()); + final var transactionValidationParams = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(isAllowExceedingBalance) + .build(); + + LOG.debug("Processing transaction with params: {}", modifiedCallParams); + final var maybeResult = + transactionSimulator.process( + modifiedCallParams, transactionValidationParams, operationTracer, blockHeader); + + final Optional maybeErrorResponse = + validateSimulationResult(requestContext, maybeResult); + if (maybeErrorResponse.isPresent()) { + return maybeErrorResponse.get(); } - var low = gasUsed.get().result().getEstimateGasUsedByTransaction(); - var lowResult = - executeSimulation( - blockHeader, + final var result = maybeResult.get(); + long low = result.result().getEstimateGasUsedByTransaction(); + final var lowResult = + transactionSimulator.process( overrideGasLimitAndPrice(callParams, low), + transactionValidationParams, operationTracer, - isAllowExceedingBalance); + blockHeader); if (lowResult.isPresent() && lowResult.get().isSuccessful()) { - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(low)); + return Quantity.create(low); } - var high = processEstimateGas(gasUsed.get(), operationTracer); - var mid = high; - while (low + 1 < high) { - mid = (high + low) / 2; + long high = processEstimateGas(result, operationTracer); + long mid; + while (low + 1 < high) { + mid = (low + high) / 2; var binarySearchResult = - executeSimulation( - blockHeader, + transactionSimulator.process( overrideGasLimitAndPrice(callParams, mid), + transactionValidationParams, operationTracer, - isAllowExceedingBalance); + blockHeader); + if (binarySearchResult.isEmpty() || !binarySearchResult.get().isSuccessful()) { low = mid; } else { @@ -117,21 +108,23 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } } - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(high)); + return Quantity.create(high); } - private Optional executeSimulation( - final BlockHeader blockHeader, - final CallParameter modifiedCallParams, - final EstimateGasOperationTracer operationTracer, - final boolean allowExceedingBalance) { - return transactionSimulator.process( - modifiedCallParams, - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(allowExceedingBalance) - .build(), - operationTracer, - blockHeader.getNumber()); + private Optional validateSimulationResult( + final JsonRpcRequestContext requestContext, + final Optional maybeResult) { + if (maybeResult.isEmpty()) { + LOG.error("No result after simulating transaction."); + return Optional.of( + new JsonRpcErrorResponse( + requestContext.getRequest().getId(), RpcErrorType.INTERNAL_ERROR)); + } + + // if the transaction is invalid or doesn't have enough gas with the max it never will! + if (maybeResult.get().isInvalid() || !maybeResult.get().isSuccessful()) { + return Optional.of(errorResponse(requestContext, maybeResult.get())); + } + return Optional.empty(); } } 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 8812a1e0a6a..f7c0c31914f 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 @@ -16,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -24,7 +24,6 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -70,7 +69,9 @@ public class EthCreateAccessListTest { private final String METHOD = "eth_createAccessList"; private EthCreateAccessList method; - @Mock private BlockHeader blockHeader; + @Mock private BlockHeader latestBlockHeader; + @Mock private BlockHeader finalizedBlockHeader; + @Mock private BlockHeader genesisBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -80,16 +81,18 @@ public class EthCreateAccessListTest { public void setUp() { when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); - when(blockchain.getChainHeadHash()) - .thenReturn( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3")); - when(blockchain.getBlockHeader( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3"))) - .thenReturn(Optional.of(blockHeader)); - when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); - when(blockHeader.getNumber()).thenReturn(1L); + when(blockchainQueries.headBlockNumber()).thenReturn(2L); + when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader)); + when(blockchainQueries.getBlockHeaderByNumber(1L)) + .thenReturn(Optional.of(finalizedBlockHeader)); + when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(genesisBlockHeader.getNumber()).thenReturn(0L); + when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(finalizedBlockHeader.getNumber()).thenReturn(1L); + when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); + when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(latestBlockHeader.getNumber()).thenReturn(2L); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthCreateAccessList(blockchainQueries, transactionSimulator); @@ -105,18 +108,22 @@ private JsonRpcRequestContext ethCreateAccessListRequest(final CallParameter cal new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter})); } + private JsonRpcRequestContext ethCreateAccessListRequest( + final CallParameter callParameter, final String blockParam) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter, blockParam})); + } + @Test public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -124,21 +131,19 @@ public void shouldUseGasPriceParameterWhenIsPresent() { final Wei gasPrice = Wei.of(1000); final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(gasPrice)); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = ethCreateAccessListRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); - mockTransactionSimulatorResult(false, false, 1L); + mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader); Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) @@ -150,29 +155,25 @@ public void shouldReturnErrorWhenWorldStateIsNotAvailable() { when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(false, false, 1L); + mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(false, true, 1L); + mockTransactionSimulatorResult(false, true, 1L, latestBlockHeader); final String errorReason = "0x00"; final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, errorReason)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -182,12 +183,10 @@ public void shouldReturnEmptyAccessListIfNoParameterAndWithoutAccessedStorage() new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); final JsonRpcRequestContext request = ethCreateAccessListRequest(eip1559TransactionCallParameter()); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -204,11 +203,11 @@ public void shouldReturnAccessListIfNoParameterAndWithAccessedStorage() { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -223,11 +222,9 @@ public void shouldReturnEmptyAccessListIfNoAccessedStorage() { ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam)); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -244,11 +241,11 @@ public void shouldReturnAccessListIfParameterAndSameAccessedStorage() { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -268,11 +265,51 @@ public void shouldReturnAccessListIfWithParameterAndDifferentAccessedStorage() { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); + } + + @Test + public void shouldReturnAccessListWhenBlockTagParamIsPresent() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(eip1559TransactionCallParameter(), "finalized"); + // Create a list with one access list entry + final List expectedAccessList = createAccessList(); + + // expect a list with the mocked access list + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); + final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); + + // Set TransactionSimulator.process response + mockTransactionSimulatorResult(true, false, 1L, finalizedBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(finalizedBlockHeader)); + } + + @Test + public void shouldReturnAccessListWhenBlockNumberParamIsPresent() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(eip1559TransactionCallParameter(), "0x0"); + // Create a list with one access list entry + final List expectedAccessList = createAccessList(); + + // expect a list with the mocked access list + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); + final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); + + // Set TransactionSimulator.process response + mockTransactionSimulatorResult(true, false, 1L, genesisBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(genesisBlockHeader)); } private JsonRpcResponse responseWithMockTracer( @@ -292,9 +329,12 @@ private AccessListOperationTracer createMockTracer( } private void mockTransactionSimulatorResult( - final boolean isSuccessful, final boolean isReverted, final long estimateGas) { + final boolean isSuccessful, + final boolean isReverted, + final long estimateGas, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); - when(transactionSimulator.process(any(), any(), any(), anyLong())) + when(transactionSimulator.process(any(), any(), any(), eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); 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 a6f5008b29e..f85a0813efd 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 @@ -22,7 +22,6 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -66,7 +65,9 @@ public class EthEstimateGasTest { private EthEstimateGas method; - @Mock private BlockHeader blockHeader; + @Mock private BlockHeader latestBlockHeader; + @Mock private BlockHeader finalizedBlockHeader; + @Mock private BlockHeader genesisBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -76,16 +77,18 @@ public class EthEstimateGasTest { public void setUp() { when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); - when(blockchain.getChainHeadHash()) - .thenReturn( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3")); - when(blockchain.getBlockHeader( - Hash.fromHexString( - "0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3"))) - .thenReturn(Optional.of(blockHeader)); - when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); - when(blockHeader.getNumber()).thenReturn(1L); + when(blockchainQueries.headBlockNumber()).thenReturn(2L); + when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader)); + when(blockchainQueries.getBlockHeaderByNumber(1L)) + .thenReturn(Optional.of(finalizedBlockHeader)); + when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(genesisBlockHeader.getNumber()).thenReturn(0L); + when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(finalizedBlockHeader.getNumber()).thenReturn(1L); + when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); + when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(latestBlockHeader.getNumber()).thenReturn(2L); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthEstimateGas(blockchainQueries, transactionSimulator); @@ -104,15 +107,13 @@ public void shouldReturnErrorWhenTransientLegacyTransactionProcessorReturnsEmpty eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(latestBlockHeader))) .thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -122,28 +123,24 @@ public void shouldReturnErrorWhenTransientEip1559TransactionProcessorReturnsEmpt eq(modifiedEip1559TransactionCallParameter()), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(latestBlockHeader))) .thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, true, false); + mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -151,20 +148,19 @@ public void shouldUseGasPriceParameterWhenIsPresent() { final Wei gasPrice = Wei.of(1000); final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(gasPrice)); - mockTransientProcessorResultGasEstimate(1L, true, gasPrice, Optional.empty()); + mockTransientProcessorResultGasEstimate( + 1L, true, gasPrice, Optional.empty(), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) .hasMessageContaining("gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas"); @@ -174,12 +170,10 @@ public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction public void shouldReturnGasEstimateWhenTransientEip1559TransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); - mockTransientProcessorResultGasEstimate(1L, true, false); + mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -187,28 +181,24 @@ public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction shouldReturnGasEstimateErrorWhenTransientLegacyTransactionProcessorReturnsResultFailure() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenTransientEip1559TransactionProcessorReturnsResultFailure() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -217,7 +207,8 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); mockTransientProcessorResultTxInvalidReason( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, - "transaction up-front cost 10 exceeds transaction sender account balance 5"); + "transaction up-front cost 10 exceeds transaction sender account balance 5", + latestBlockHeader); final ValidationResult validationResult = ValidationResult.invalid( @@ -226,9 +217,7 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -236,7 +225,8 @@ public void shouldReturnErrorWhenEip1559TransactionProcessorReturnsTxInvalidReas final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); mockTransientProcessorResultTxInvalidReason( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, - "transaction up-front cost 10 exceeds transaction sender account balance 5"); + "transaction up-front cost 10 exceeds transaction sender account balance 5", + latestBlockHeader); final ValidationResult validationResult = ValidationResult.invalid( TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, @@ -244,9 +234,7 @@ public void shouldReturnErrorWhenEip1559TransactionProcessorReturnsTxInvalidReas final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -254,21 +242,21 @@ public void shouldReturnErrorWhenWorldStateIsNotAvailable() { when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); JsonRpcResponse theResponse = method.response(request); - Assertions.assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, "0x00")); @@ -278,7 +266,7 @@ public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted"); @@ -296,7 +284,8 @@ public void shouldReturnErrorReasonWhenTransactionReverted() { + "00002545524332303a207472616e736665722066726f6d20746865207a65726f20" + "61646472657373000000000000000000000000000000000000000000000000000000"; - mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(executionRevertedReason)); + mockTransientProcessorTxReverted( + 1L, false, Bytes.fromHexString(executionRevertedReason), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -307,7 +296,7 @@ public void shouldReturnErrorReasonWhenTransactionReverted() { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted: ERC20: transfer from the zero address"); @@ -323,7 +312,8 @@ public void shouldReturnABIDecodeErrorReasonWhenInvalidRevertReason() { "0x08c379a000000000000000000000000000000000000000000000000000000000" + "123451234512345123451234512345123451234512345123451234512345123451"; - mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(invalidRevertReason)); + mockTransientProcessorTxReverted( + 1L, false, Bytes.fromHexString(invalidRevertReason), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -334,7 +324,7 @@ public void shouldReturnABIDecodeErrorReasonWhenInvalidRevertReason() { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted: ABI decode error"); @@ -344,7 +334,7 @@ public void shouldReturnABIDecodeErrorReasonWhenInvalidRevertReason() { public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); method.response(request); @@ -357,14 +347,14 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { .isAllowExceedingBalance(true) .build()), any(OperationTracer.class), - eq(1L)); + eq(latestBlockHeader)); } @Test public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() { final JsonRpcRequestContext request = ethEstimateGasRequest(legacyTransactionCallParameter(Wei.ZERO, true)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); method.response(request); @@ -377,7 +367,7 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() { .isAllowExceedingBalance(false) .build()), any(OperationTracer.class), - eq(1L)); + eq(latestBlockHeader)); } @Test @@ -385,22 +375,44 @@ public void shouldIncludeHaltReasonWhenExecutionHalts() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); mockTransientProcessorResultTxInvalidReason( - TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); + TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION", latestBlockHeader); final ValidationResult validationResult = ValidationResult.invalid(TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void shouldUseBlockTagParamWhenPresent() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "finalized"); + mockTransientProcessorResultGasEstimate(1L, true, false, finalizedBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void shouldUseBlockNumberParamWhenPresent() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "0x0"); + mockTransientProcessorResultGasEstimate(1L, true, false, genesisBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } private void mockTransientProcessorResultTxInvalidReason( - final TransactionInvalidReason reason, final String validationFailedErrorMessage) { + final TransactionInvalidReason reason, + final String validationFailedErrorMessage, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = - getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty()); + getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty(), blockHeader); when(mockTxSimResult.getValidationResult()) .thenReturn( validationFailedErrorMessage == null @@ -409,45 +421,55 @@ private void mockTransientProcessorResultTxInvalidReason( } private void mockTransientProcessorTxReverted( - final long estimateGas, final boolean isSuccessful, final Bytes revertReason) { + final long estimateGas, + final boolean isSuccessful, + final Bytes revertReason, + final BlockHeader blockHeader) { mockTransientProcessorResultGasEstimate( - estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason)); + estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason), blockHeader); } private void mockTransientProcessorResultGasEstimate( - final long estimateGas, final boolean isSuccessful, final boolean isReverted) { + final long estimateGas, + final boolean isSuccessful, + final boolean isReverted, + final BlockHeader blockHeader) { mockTransientProcessorResultGasEstimate( estimateGas, isSuccessful, Wei.ZERO, - isReverted ? Optional.of(Bytes.of(0)) : Optional.empty()); + isReverted ? Optional.of(Bytes.of(0)) : Optional.empty(), + blockHeader); } private void mockTransientProcessorResultGasEstimate( final long estimateGas, final boolean isSuccessful, final Wei gasPrice, - final Optional revertReason) { - getMockTransactionSimulatorResult(isSuccessful, estimateGas, gasPrice, revertReason); + final Optional revertReason, + final BlockHeader blockHeader) { + getMockTransactionSimulatorResult( + isSuccessful, estimateGas, gasPrice, revertReason, blockHeader); } private TransactionSimulatorResult getMockTransactionSimulatorResult( final boolean isSuccessful, final long estimateGas, final Wei gasPrice, - final Optional revertReason) { + final Optional revertReason, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); when(transactionSimulator.process( eq(modifiedLegacyTransactionCallParameter(gasPrice)), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); when(transactionSimulator.process( eq(modifiedEip1559TransactionCallParameter()), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); @@ -523,4 +545,10 @@ private JsonRpcRequestContext ethEstimateGasRequest(final CallParameter callPara return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter})); } + + private JsonRpcRequestContext ethEstimateGasRequest( + final CallParameter callParameter, final String blockParam) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter, blockParam})); + } } diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json new file mode 100644 index 00000000000..aa9731f74f4 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "data": "0x123456" + }, + "0x1" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x52d4" + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json new file mode 100644 index 00000000000..992a7d77283 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "data": "0x123456" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5238" + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json new file mode 100644 index 00000000000..6dd337509ef --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "gas": "0x1" + }, + "0xa" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5208" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json new file mode 100644 index 00000000000..d909af9eedb --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "from": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to": "0x8888f1f195afa192cfee860698584c030f4c9db1", + "value": "0x1" + }, + "earliest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5208" + }, + "statusCode": 200 +} \ No newline at end of file