Honor block number or tag parameter in eth_estimateGas and eth_createAccessList (#7502)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/7586/head
Fabio Di Fabio 3 months ago committed by GitHub
parent dc6324c8d0
commit dad05d407e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 70
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java
  3. 72
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java
  4. 111
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java
  5. 142
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java
  6. 180
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java
  7. 21
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json
  8. 21
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json
  9. 21
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json
  10. 21
      ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json

@ -12,6 +12,7 @@
### Bug fixes ### Bug fixes
- Layered txpool: do not send notifications when moving tx between layers [#7539](https://github.com/hyperledger/besu/pull/7539) - Layered txpool: do not send notifications when moving tx between layers [#7539](https://github.com/hyperledger/besu/pull/7539)
- Layered txpool: fix for unsent drop notifications on remove [#7538](https://github.com/hyperledger/besu/pull/7538) - Layered txpool: fix for unsent drop notifications on remove [#7538](https://github.com/hyperledger/besu/pull/7538)
- Honor block number or tag parameter in eth_estimateGas and eth_createAccessList [#7502](https://github.com/hyperledger/besu/pull/7502)
## 24.9.0 ## 24.9.0

@ -14,15 +14,19 @@
*/ */
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; 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.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; 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.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.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.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; 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.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; 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.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
@ -34,32 +38,68 @@ import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer;
import java.util.Optional; 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; private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D;
protected final BlockchainQueries blockchainQueries;
protected final TransactionSimulator transactionSimulator; protected final TransactionSimulator transactionSimulator;
public AbstractEstimateGas( public AbstractEstimateGas(
final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) { final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) {
this.blockchainQueries = blockchainQueries; super(blockchainQueries);
this.transactionSimulator = transactionSimulator; this.transactionSimulator = transactionSimulator;
} }
protected BlockHeader blockHeader() { @Override
final Blockchain theChain = blockchainQueries.getBlockchain(); protected BlockParameter blockParameter(final JsonRpcRequestContext request) {
try {
// Optimistically get the block header for the chain head without taking a lock, return request.getOptionalParameter(1, BlockParameter.class).orElse(BlockParameter.LATEST);
// but revert to the safe implementation if it returns an empty optional. (It's } catch (JsonRpcParameter.JsonRpcParameterException e) {
// possible the chain head has been updated but the block is still being persisted throw new InvalidJsonRpcParameters(
// to storage/cache under the lock). "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e);
return theChain }
.getBlockHeader(theChain.getChainHeadHash()) }
.or(() -> theChain.getBlockHeaderSafe(theChain.getChainHeadHash()))
.orElse(null); protected Optional<BlockHeader> 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<RpcErrorType> validateBlockHeader(
final Optional<BlockHeader> 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<BlockHeader> maybeBlockHeader = blockHeader(blockNumber);
final Optional<RpcErrorType> 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( protected CallParameter overrideGasLimitAndPrice(
final JsonCallParameter callParams, final long gasLimit) { final JsonCallParameter callParams, final long gasLimit) {
return new CallParameter( return new CallParameter(

@ -14,15 +14,11 @@
*/ */
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; 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.AccessListEntry;
import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; 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.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; 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.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
@ -52,44 +48,29 @@ public class EthCreateAccessList extends AbstractEstimateGas {
} }
@Override @Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { protected Object resultByBlockHeader(
final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); final JsonRpcRequestContext requestContext,
final BlockHeader blockHeader = blockHeader(); final JsonCallParameter jsonCallParameter,
final Optional<RpcErrorType> jsonRpcError = validateBlockHeader(blockHeader); final BlockHeader blockHeader) {
if (jsonRpcError.isPresent()) {
return errorResponse(requestContext, jsonRpcError.get());
}
final AccessListSimulatorResult maybeResult = final AccessListSimulatorResult maybeResult =
processTransaction(jsonCallParameter, blockHeader); processTransaction(jsonCallParameter, blockHeader);
// if the call accessList is different from the simulation result, calculate gas and return // 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 = final AccessListSimulatorResult result =
processTransactionWithAccessListOverride( processTransactionWithAccessListOverride(
jsonCallParameter, blockHeader, maybeResult.getTracer().getAccessList()); jsonCallParameter, blockHeader, maybeResult.tracer().getAccessList());
return createResponse(requestContext, result); return createResponse(requestContext, result);
} else { } else {
return createResponse(requestContext, maybeResult); return createResponse(requestContext, maybeResult);
} }
} }
private Optional<RpcErrorType> validateBlockHeader(final BlockHeader blockHeader) { private Object createResponse(
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(
final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) { final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) {
return result return result
.getResult() .result()
.map(createResponse(requestContext, result.getTracer())) .map(createResponse(requestContext, result.tracer()))
.orElse(errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); .orElseGet(() -> errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR));
} }
private TransactionValidationParams transactionValidationParams( private TransactionValidationParams transactionValidationParams(
@ -117,14 +98,12 @@ public class EthCreateAccessList extends AbstractEstimateGas {
return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get()); return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get());
} }
private Function<TransactionSimulatorResult, JsonRpcResponse> createResponse( private Function<TransactionSimulatorResult, Object> createResponse(
final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) { final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) {
return result -> return result ->
result.isSuccessful() result.isSuccessful()
? new JsonRpcSuccessResponse( ? new CreateAccessListResult(
request.getRequest().getId(), operationTracer.getAccessList(), processEstimateGas(result, operationTracer))
new CreateAccessListResult(
operationTracer.getAccessList(), processEstimateGas(result, operationTracer)))
: errorResponse(request, result); : errorResponse(request, result);
} }
@ -138,8 +117,7 @@ public class EthCreateAccessList extends AbstractEstimateGas {
final AccessListOperationTracer tracer = AccessListOperationTracer.create(); final AccessListOperationTracer tracer = AccessListOperationTracer.create();
final Optional<TransactionSimulatorResult> result = final Optional<TransactionSimulatorResult> result =
transactionSimulator.process( transactionSimulator.process(callParams, transactionValidationParams, tracer, blockHeader);
callParams, transactionValidationParams, tracer, blockHeader.getNumber());
return new AccessListSimulatorResult(result, tracer); return new AccessListSimulatorResult(result, tracer);
} }
@ -156,7 +134,7 @@ public class EthCreateAccessList extends AbstractEstimateGas {
final Optional<TransactionSimulatorResult> result = final Optional<TransactionSimulatorResult> result =
transactionSimulator.process( transactionSimulator.process(
callParameter, transactionValidationParams, tracer, blockHeader.getNumber()); callParameter, transactionValidationParams, tracer, blockHeader);
return new AccessListSimulatorResult(result, tracer); return new AccessListSimulatorResult(result, tracer);
} }
@ -176,22 +154,6 @@ public class EthCreateAccessList extends AbstractEstimateGas {
Optional.ofNullable(accessListEntries)); Optional.ofNullable(accessListEntries));
} }
private static class AccessListSimulatorResult { private record AccessListSimulatorResult(
final Optional<TransactionSimulatorResult> result; Optional<TransactionSimulatorResult> result, AccessListOperationTracer tracer) {}
final AccessListOperationTracer tracer;
public AccessListSimulatorResult(
final Optional<TransactionSimulatorResult> result, final AccessListOperationTracer tracer) {
this.result = result;
this.tracer = tracer;
}
public Optional<TransactionSimulatorResult> getResult() {
return result;
}
public AccessListOperationTracer getTracer() {
return tracer;
}
}
} }

@ -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 * 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 * the License. You may obtain a copy of the License at
@ -14,13 +14,10 @@
*/ */
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; 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.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; 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.parameters.JsonCallParameter;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
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.response.RpcErrorType;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
@ -38,7 +35,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class EthEstimateGas extends AbstractEstimateGas { public class EthEstimateGas extends AbstractEstimateGas {
private static final Logger LOG = LoggerFactory.getLogger(EthEstimateGas.class); private static final Logger LOG = LoggerFactory.getLogger(EthEstimateGas.class);
public EthEstimateGas( public EthEstimateGas(
@ -52,19 +48,10 @@ public class EthEstimateGas extends AbstractEstimateGas {
} }
@Override @Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { protected Object resultByBlockHeader(
final JsonCallParameter callParams = validateAndGetCallParams(requestContext); final JsonRpcRequestContext requestContext,
final JsonCallParameter callParams,
final BlockHeader blockHeader = blockHeader(); final 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);
}
final CallParameter modifiedCallParams = final CallParameter modifiedCallParams =
overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit()); overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit());
@ -72,44 +59,48 @@ public class EthEstimateGas extends AbstractEstimateGas {
final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE);
final EstimateGasOperationTracer operationTracer = new EstimateGasOperationTracer(); final EstimateGasOperationTracer operationTracer = new EstimateGasOperationTracer();
final var transactionValidationParams =
var gasUsed = ImmutableTransactionValidationParams.builder()
executeSimulation( .from(TransactionValidationParams.transactionSimulator())
blockHeader, modifiedCallParams, operationTracer, isAllowExceedingBalance); .isAllowExceedingBalance(isAllowExceedingBalance)
.build();
if (gasUsed.isEmpty()) {
LOG.error("gasUsed is empty after simulating transaction."); LOG.debug("Processing transaction with params: {}", modifiedCallParams);
return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); final var maybeResult =
} transactionSimulator.process(
modifiedCallParams, transactionValidationParams, operationTracer, blockHeader);
// if the transaction is invalid or doesn't have enough gas with the max it never will!
if (gasUsed.get().isInvalid() || !gasUsed.get().isSuccessful()) { final Optional<JsonRpcErrorResponse> maybeErrorResponse =
return errorResponse(requestContext, gasUsed.get()); validateSimulationResult(requestContext, maybeResult);
if (maybeErrorResponse.isPresent()) {
return maybeErrorResponse.get();
} }
var low = gasUsed.get().result().getEstimateGasUsedByTransaction(); final var result = maybeResult.get();
var lowResult = long low = result.result().getEstimateGasUsedByTransaction();
executeSimulation( final var lowResult =
blockHeader, transactionSimulator.process(
overrideGasLimitAndPrice(callParams, low), overrideGasLimitAndPrice(callParams, low),
transactionValidationParams,
operationTracer, operationTracer,
isAllowExceedingBalance); blockHeader);
if (lowResult.isPresent() && lowResult.get().isSuccessful()) { 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); long high = processEstimateGas(result, operationTracer);
var mid = high; long mid;
while (low + 1 < high) {
mid = (high + low) / 2;
while (low + 1 < high) {
mid = (low + high) / 2;
var binarySearchResult = var binarySearchResult =
executeSimulation( transactionSimulator.process(
blockHeader,
overrideGasLimitAndPrice(callParams, mid), overrideGasLimitAndPrice(callParams, mid),
transactionValidationParams,
operationTracer, operationTracer,
isAllowExceedingBalance); blockHeader);
if (binarySearchResult.isEmpty() || !binarySearchResult.get().isSuccessful()) { if (binarySearchResult.isEmpty() || !binarySearchResult.get().isSuccessful()) {
low = mid; low = mid;
} else { } else {
@ -117,21 +108,23 @@ public class EthEstimateGas extends AbstractEstimateGas {
} }
} }
return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(high)); return Quantity.create(high);
} }
private Optional<TransactionSimulatorResult> executeSimulation( private Optional<JsonRpcErrorResponse> validateSimulationResult(
final BlockHeader blockHeader, final JsonRpcRequestContext requestContext,
final CallParameter modifiedCallParams, final Optional<TransactionSimulatorResult> maybeResult) {
final EstimateGasOperationTracer operationTracer, if (maybeResult.isEmpty()) {
final boolean allowExceedingBalance) { LOG.error("No result after simulating transaction.");
return transactionSimulator.process( return Optional.of(
modifiedCallParams, new JsonRpcErrorResponse(
ImmutableTransactionValidationParams.builder() requestContext.getRequest().getId(), RpcErrorType.INTERNAL_ERROR));
.from(TransactionValidationParams.transactionSimulator()) }
.isAllowExceedingBalance(allowExceedingBalance)
.build(), // if the transaction is invalid or doesn't have enough gas with the max it never will!
operationTracer, if (maybeResult.get().isInvalid() || !maybeResult.get().isSuccessful()) {
blockHeader.getNumber()); return Optional.of(errorResponse(requestContext, maybeResult.get()));
}
return Optional.empty();
} }
} }

@ -16,7 +16,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; 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.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -24,7 +24,6 @@ import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.AccessListEntry;
import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
@ -70,7 +69,9 @@ public class EthCreateAccessListTest {
private final String METHOD = "eth_createAccessList"; private final String METHOD = "eth_createAccessList";
private EthCreateAccessList method; 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 Blockchain blockchain;
@Mock private BlockchainQueries blockchainQueries; @Mock private BlockchainQueries blockchainQueries;
@Mock private TransactionSimulator transactionSimulator; @Mock private TransactionSimulator transactionSimulator;
@ -80,16 +81,18 @@ public class EthCreateAccessListTest {
public void setUp() { public void setUp() {
when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive);
when(blockchain.getChainHeadHash()) when(blockchainQueries.headBlockNumber()).thenReturn(2L);
.thenReturn( when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader));
Hash.fromHexString( when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader));
"0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3")); when(blockchainQueries.getBlockHeaderByNumber(1L))
when(blockchain.getBlockHeader( .thenReturn(Optional.of(finalizedBlockHeader));
Hash.fromHexString( when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE);
"0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3"))) when(genesisBlockHeader.getNumber()).thenReturn(0L);
.thenReturn(Optional.of(blockHeader)); when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE);
when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); when(finalizedBlockHeader.getNumber()).thenReturn(1L);
when(blockHeader.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); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true);
method = new EthCreateAccessList(blockchainQueries, transactionSimulator); method = new EthCreateAccessList(blockchainQueries, transactionSimulator);
@ -105,18 +108,22 @@ public class EthCreateAccessListTest {
new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter})); 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 @Test
public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO));
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L));
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -124,21 +131,19 @@ public class EthCreateAccessListTest {
final Wei gasPrice = Wei.of(1000); final Wei gasPrice = Wei.of(1000);
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(gasPrice)); ethCreateAccessListRequest(legacyTransactionCallParameter(gasPrice));
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L));
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); ethCreateAccessListRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10))));
mockTransactionSimulatorResult(false, false, 1L); mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader);
Assertions.assertThatThrownBy(() -> method.response(request)) Assertions.assertThatThrownBy(() -> method.response(request))
.isInstanceOf(InvalidJsonRpcParameters.class) .isInstanceOf(InvalidJsonRpcParameters.class)
@ -150,29 +155,25 @@ public class EthCreateAccessListTest {
when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false);
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO));
mockTransactionSimulatorResult(false, false, 1L); mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
public void shouldReturnErrorWhenTransactionReverted() { public void shouldReturnErrorWhenTransactionReverted() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO));
mockTransactionSimulatorResult(false, true, 1L); mockTransactionSimulatorResult(false, true, 1L, latestBlockHeader);
final String errorReason = "0x00"; final String errorReason = "0x00";
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, errorReason)); new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, errorReason));
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -182,12 +183,10 @@ public class EthCreateAccessListTest {
new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L));
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethCreateAccessListRequest(eip1559TransactionCallParameter()); ethCreateAccessListRequest(eip1559TransactionCallParameter());
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison() verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader));
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong());
} }
@Test @Test
@ -204,11 +203,11 @@ public class EthCreateAccessListTest {
final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); final AccessListOperationTracer tracer = createMockTracer(expectedAccessList);
// Set TransactionSimulator.process response // Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
Assertions.assertThat(responseWithMockTracer(request, tracer)) assertThat(responseWithMockTracer(request, tracer))
.usingRecursiveComparison() .usingRecursiveComparison()
.isEqualTo(expectedResponse); .isEqualTo(expectedResponse);
verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader));
} }
@Test @Test
@ -223,11 +222,9 @@ public class EthCreateAccessListTest {
ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam)); ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam));
// Set TransactionSimulator.process response // Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison() verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader));
.isEqualTo(expectedResponse);
verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong());
} }
@Test @Test
@ -244,11 +241,11 @@ public class EthCreateAccessListTest {
final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); final AccessListOperationTracer tracer = createMockTracer(expectedAccessList);
// Set TransactionSimulator.process response // Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
Assertions.assertThat(responseWithMockTracer(request, tracer)) assertThat(responseWithMockTracer(request, tracer))
.usingRecursiveComparison() .usingRecursiveComparison()
.isEqualTo(expectedResponse); .isEqualTo(expectedResponse);
verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader));
} }
@Test @Test
@ -268,11 +265,51 @@ public class EthCreateAccessListTest {
final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); final AccessListOperationTracer tracer = createMockTracer(expectedAccessList);
// Set TransactionSimulator.process response // Set TransactionSimulator.process response
mockTransactionSimulatorResult(true, false, 1L); mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader);
Assertions.assertThat(responseWithMockTracer(request, tracer)) 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<AccessListEntry> 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<AccessListEntry> 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() .usingRecursiveComparison()
.isEqualTo(expectedResponse); .isEqualTo(expectedResponse);
verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(genesisBlockHeader));
} }
private JsonRpcResponse responseWithMockTracer( private JsonRpcResponse responseWithMockTracer(
@ -292,9 +329,12 @@ public class EthCreateAccessListTest {
} }
private void mockTransactionSimulatorResult( 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); 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)); .thenReturn(Optional.of(mockTxSimResult));
final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class);
when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas);

@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
@ -66,7 +65,9 @@ public class EthEstimateGasTest {
private EthEstimateGas method; 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 Blockchain blockchain;
@Mock private BlockchainQueries blockchainQueries; @Mock private BlockchainQueries blockchainQueries;
@Mock private TransactionSimulator transactionSimulator; @Mock private TransactionSimulator transactionSimulator;
@ -76,16 +77,18 @@ public class EthEstimateGasTest {
public void setUp() { public void setUp() {
when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive);
when(blockchain.getChainHeadHash()) when(blockchainQueries.headBlockNumber()).thenReturn(2L);
.thenReturn( when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader));
Hash.fromHexString( when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader));
"0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3")); when(blockchainQueries.getBlockHeaderByNumber(1L))
when(blockchain.getBlockHeader( .thenReturn(Optional.of(finalizedBlockHeader));
Hash.fromHexString( when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE);
"0x3f07a9c83155594c000642e7d60e8a8a00038d03e9849171a05ed0e2d47acbb3"))) when(genesisBlockHeader.getNumber()).thenReturn(0L);
.thenReturn(Optional.of(blockHeader)); when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE);
when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); when(finalizedBlockHeader.getNumber()).thenReturn(1L);
when(blockHeader.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); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true);
method = new EthEstimateGas(blockchainQueries, transactionSimulator); method = new EthEstimateGas(blockchainQueries, transactionSimulator);
@ -104,15 +107,13 @@ public class EthEstimateGasTest {
eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)),
any(TransactionValidationParams.class), any(TransactionValidationParams.class),
any(OperationTracer.class), any(OperationTracer.class),
eq(1L))) eq(latestBlockHeader)))
.thenReturn(Optional.empty()); .thenReturn(Optional.empty());
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -122,28 +123,24 @@ public class EthEstimateGasTest {
eq(modifiedEip1559TransactionCallParameter()), eq(modifiedEip1559TransactionCallParameter()),
any(TransactionValidationParams.class), any(TransactionValidationParams.class),
any(OperationTracer.class), any(OperationTracer.class),
eq(1L))) eq(latestBlockHeader)))
.thenReturn(Optional.empty()); .thenReturn(Optional.empty());
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultGasEstimate(1L, true, false); mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader);
final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L));
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -151,20 +148,19 @@ public class EthEstimateGasTest {
final Wei gasPrice = Wei.of(1000); final Wei gasPrice = Wei.of(1000);
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(gasPrice)); 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)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L));
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); ethEstimateGasRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10))));
mockTransientProcessorResultGasEstimate(1L, false, false); mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader);
Assertions.assertThatThrownBy(() -> method.response(request)) Assertions.assertThatThrownBy(() -> method.response(request))
.isInstanceOf(InvalidJsonRpcParameters.class) .isInstanceOf(InvalidJsonRpcParameters.class)
.hasMessageContaining("gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas"); .hasMessageContaining("gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas");
@ -174,12 +170,10 @@ public class EthEstimateGasTest {
public void public void
shouldReturnGasEstimateWhenTransientEip1559TransactionProcessorReturnsResultSuccess() { shouldReturnGasEstimateWhenTransientEip1559TransactionProcessorReturnsResultSuccess() {
final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter());
mockTransientProcessorResultGasEstimate(1L, true, false); mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader);
final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L));
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -187,28 +181,24 @@ public class EthEstimateGasTest {
shouldReturnGasEstimateErrorWhenTransientLegacyTransactionProcessorReturnsResultFailure() { shouldReturnGasEstimateErrorWhenTransientLegacyTransactionProcessorReturnsResultFailure() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultGasEstimate(1L, false, false); mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
public void public void
shouldReturnGasEstimateErrorWhenTransientEip1559TransactionProcessorReturnsResultFailure() { shouldReturnGasEstimateErrorWhenTransientEip1559TransactionProcessorReturnsResultFailure() {
final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter());
mockTransientProcessorResultGasEstimate(1L, false, false); mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -217,7 +207,8 @@ public class EthEstimateGasTest {
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultTxInvalidReason( mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, 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<TransactionInvalidReason> validationResult = final ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid( ValidationResult.invalid(
@ -226,9 +217,7 @@ public class EthEstimateGasTest {
final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -236,7 +225,8 @@ public class EthEstimateGasTest {
final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter());
mockTransientProcessorResultTxInvalidReason( mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, 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<TransactionInvalidReason> validationResult = final ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid( ValidationResult.invalid(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
@ -244,9 +234,7 @@ public class EthEstimateGasTest {
final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.usingRecursiveComparison()
.isEqualTo(expectedResponse);
} }
@Test @Test
@ -254,21 +242,21 @@ public class EthEstimateGasTest {
when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false);
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultGasEstimate(1L, false, false); mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE);
JsonRpcResponse theResponse = method.response(request); JsonRpcResponse theResponse = method.response(request);
Assertions.assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
} }
@Test @Test
public void shouldReturnErrorWhenTransactionReverted() { public void shouldReturnErrorWhenTransactionReverted() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultGasEstimate(1L, false, true); mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, "0x00")); new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, "0x00"));
@ -278,7 +266,7 @@ public class EthEstimateGasTest {
final JsonRpcResponse actualResponse = method.response(request); final JsonRpcResponse actualResponse = method.response(request);
Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage())
.isEqualTo("Execution reverted"); .isEqualTo("Execution reverted");
@ -296,7 +284,8 @@ public class EthEstimateGasTest {
+ "00002545524332303a207472616e736665722066726f6d20746865207a65726f20" + "00002545524332303a207472616e736665722066726f6d20746865207a65726f20"
+ "61646472657373000000000000000000000000000000000000000000000000000000"; + "61646472657373000000000000000000000000000000000000000000000000000000";
mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(executionRevertedReason)); mockTransientProcessorTxReverted(
1L, false, Bytes.fromHexString(executionRevertedReason), latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse( new JsonRpcErrorResponse(
@ -307,7 +296,7 @@ public class EthEstimateGasTest {
final JsonRpcResponse actualResponse = method.response(request); final JsonRpcResponse actualResponse = method.response(request);
Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage())
.isEqualTo("Execution reverted: ERC20: transfer from the zero address"); .isEqualTo("Execution reverted: ERC20: transfer from the zero address");
@ -323,7 +312,8 @@ public class EthEstimateGasTest {
"0x08c379a000000000000000000000000000000000000000000000000000000000" "0x08c379a000000000000000000000000000000000000000000000000000000000"
+ "123451234512345123451234512345123451234512345123451234512345123451"; + "123451234512345123451234512345123451234512345123451234512345123451";
mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(invalidRevertReason)); mockTransientProcessorTxReverted(
1L, false, Bytes.fromHexString(invalidRevertReason), latestBlockHeader);
final JsonRpcResponse expectedResponse = final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse( new JsonRpcErrorResponse(
@ -334,7 +324,7 @@ public class EthEstimateGasTest {
final JsonRpcResponse actualResponse = method.response(request); final JsonRpcResponse actualResponse = method.response(request);
Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse);
assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage())
.isEqualTo("Execution reverted: ABI decode error"); .isEqualTo("Execution reverted: ABI decode error");
@ -344,7 +334,7 @@ public class EthEstimateGasTest {
public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultGasEstimate(1L, false, true); mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader);
method.response(request); method.response(request);
@ -357,14 +347,14 @@ public class EthEstimateGasTest {
.isAllowExceedingBalance(true) .isAllowExceedingBalance(true)
.build()), .build()),
any(OperationTracer.class), any(OperationTracer.class),
eq(1L)); eq(latestBlockHeader));
} }
@Test @Test
public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() { public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(legacyTransactionCallParameter(Wei.ZERO, true)); ethEstimateGasRequest(legacyTransactionCallParameter(Wei.ZERO, true));
mockTransientProcessorResultGasEstimate(1L, false, true); mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader);
method.response(request); method.response(request);
@ -377,7 +367,7 @@ public class EthEstimateGasTest {
.isAllowExceedingBalance(false) .isAllowExceedingBalance(false)
.build()), .build()),
any(OperationTracer.class), any(OperationTracer.class),
eq(1L)); eq(latestBlockHeader));
} }
@Test @Test
@ -385,22 +375,44 @@ public class EthEstimateGasTest {
final JsonRpcRequestContext request = final JsonRpcRequestContext request =
ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO));
mockTransientProcessorResultTxInvalidReason( mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION", latestBlockHeader);
final ValidationResult<TransactionInvalidReason> validationResult = final ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid(TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); ValidationResult.invalid(TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION");
final JsonRpcError rpcError = JsonRpcError.from(validationResult); final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
Assertions.assertThat(method.response(request)) assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse);
.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( private void mockTransientProcessorResultTxInvalidReason(
final TransactionInvalidReason reason, final String validationFailedErrorMessage) { final TransactionInvalidReason reason,
final String validationFailedErrorMessage,
final BlockHeader blockHeader) {
final TransactionSimulatorResult mockTxSimResult = final TransactionSimulatorResult mockTxSimResult =
getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty()); getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty(), blockHeader);
when(mockTxSimResult.getValidationResult()) when(mockTxSimResult.getValidationResult())
.thenReturn( .thenReturn(
validationFailedErrorMessage == null validationFailedErrorMessage == null
@ -409,45 +421,55 @@ public class EthEstimateGasTest {
} }
private void mockTransientProcessorTxReverted( 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( mockTransientProcessorResultGasEstimate(
estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason)); estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason), blockHeader);
} }
private void mockTransientProcessorResultGasEstimate( 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( mockTransientProcessorResultGasEstimate(
estimateGas, estimateGas,
isSuccessful, isSuccessful,
Wei.ZERO, Wei.ZERO,
isReverted ? Optional.of(Bytes.of(0)) : Optional.empty()); isReverted ? Optional.of(Bytes.of(0)) : Optional.empty(),
blockHeader);
} }
private void mockTransientProcessorResultGasEstimate( private void mockTransientProcessorResultGasEstimate(
final long estimateGas, final long estimateGas,
final boolean isSuccessful, final boolean isSuccessful,
final Wei gasPrice, final Wei gasPrice,
final Optional<Bytes> revertReason) { final Optional<Bytes> revertReason,
getMockTransactionSimulatorResult(isSuccessful, estimateGas, gasPrice, revertReason); final BlockHeader blockHeader) {
getMockTransactionSimulatorResult(
isSuccessful, estimateGas, gasPrice, revertReason, blockHeader);
} }
private TransactionSimulatorResult getMockTransactionSimulatorResult( private TransactionSimulatorResult getMockTransactionSimulatorResult(
final boolean isSuccessful, final boolean isSuccessful,
final long estimateGas, final long estimateGas,
final Wei gasPrice, final Wei gasPrice,
final Optional<Bytes> revertReason) { final Optional<Bytes> revertReason,
final BlockHeader blockHeader) {
final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class);
when(transactionSimulator.process( when(transactionSimulator.process(
eq(modifiedLegacyTransactionCallParameter(gasPrice)), eq(modifiedLegacyTransactionCallParameter(gasPrice)),
any(TransactionValidationParams.class), any(TransactionValidationParams.class),
any(OperationTracer.class), any(OperationTracer.class),
eq(1L))) eq(blockHeader)))
.thenReturn(Optional.of(mockTxSimResult)); .thenReturn(Optional.of(mockTxSimResult));
when(transactionSimulator.process( when(transactionSimulator.process(
eq(modifiedEip1559TransactionCallParameter()), eq(modifiedEip1559TransactionCallParameter()),
any(TransactionValidationParams.class), any(TransactionValidationParams.class),
any(OperationTracer.class), any(OperationTracer.class),
eq(1L))) eq(blockHeader)))
.thenReturn(Optional.of(mockTxSimResult)); .thenReturn(Optional.of(mockTxSimResult));
final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class);
@ -523,4 +545,10 @@ public class EthEstimateGasTest {
return new JsonRpcRequestContext( return new JsonRpcRequestContext(
new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter})); 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}));
}
} }

@ -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
}

@ -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
}

@ -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
}

@ -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
}
Loading…
Cancel
Save