diff --git a/CHANGELOG.md b/CHANGELOG.md index 28d0a87442..d9d69f5ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,8 @@ - Update Java dependencies [#7786](https://github.com/hyperledger/besu/pull/7786) - Add a method to get all the transaction in the pool, to the `TransactionPoolService`, to easily access the transaction pool content from plugins [#7813](https://github.com/hyperledger/besu/pull/7813) - Add a method to check if a metric category is enabled to the plugin API [#7832](https://github.com/hyperledger/besu/pull/7832) -- Add account and state overrides to `eth_call` and `eth_estimateGas` [#7801](https://github.com/hyperledger/besu/pull/7801) - Add a new metric collector for counters which get their value from suppliers [#7894](https://github.com/hyperledger/besu/pull/7894) +- Add account and state overrides to `eth_call` [#7801](https://github.com/hyperledger/besu/pull/7801) and `eth_estimateGas` [#7890](https://github.com/hyperledger/besu/pull/7890) ### Bug fixes - Fix registering new metric categories from plugins [#7825](https://github.com/hyperledger/besu/pull/7825) 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 c713ea20a7..ab758dadec 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 @@ -16,7 +16,9 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; @@ -27,10 +29,12 @@ import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; +import org.hyperledger.besu.ethereum.util.AccountOverrideMap; import org.hyperledger.besu.evm.tracing.EstimateGasOperationTracer; import java.util.Optional; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,6 +59,8 @@ public class EthEstimateGas extends AbstractEstimateGas { final CallParameter modifiedCallParams = overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit()); + Optional maybeStateOverrides = getAddressAccountOverrideMap(requestContext); + // TODO implement for block overrides final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); @@ -68,7 +74,11 @@ public class EthEstimateGas extends AbstractEstimateGas { LOG.debug("Processing transaction with params: {}", modifiedCallParams); final var maybeResult = transactionSimulator.process( - modifiedCallParams, transactionValidationParams, operationTracer, blockHeader); + modifiedCallParams, + maybeStateOverrides, + transactionValidationParams, + operationTracer, + blockHeader); final Optional maybeErrorResponse = validateSimulationResult(requestContext, maybeResult); @@ -81,6 +91,7 @@ public class EthEstimateGas extends AbstractEstimateGas { final var lowResult = transactionSimulator.process( overrideGasLimitAndPrice(callParams, low), + maybeStateOverrides, transactionValidationParams, operationTracer, blockHeader); @@ -97,6 +108,7 @@ public class EthEstimateGas extends AbstractEstimateGas { var binarySearchResult = transactionSimulator.process( overrideGasLimitAndPrice(callParams, mid), + maybeStateOverrides, transactionValidationParams, operationTracer, blockHeader); @@ -127,4 +139,15 @@ public class EthEstimateGas extends AbstractEstimateGas { } return Optional.empty(); } + + @VisibleForTesting + protected Optional getAddressAccountOverrideMap( + final JsonRpcRequestContext request) { + try { + return request.getOptionalParameter(2, AccountOverrideMap.class); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcRequestException( + "Invalid account overrides parameter (index 2)", RpcErrorType.INVALID_CALL_PARAMS, e); + } + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java index 1ba9839b66..d16de7d994 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java @@ -342,7 +342,8 @@ public class EthCallTest { public void shouldUseCorrectBlockNumberWhenEarliest() { final JsonRpcRequestContext request = ethCallRequest(callParameter(), "earliest"); when(blockchainQueries.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(Hash.ZERO)); - when(transactionSimulator.process(any(), any(), any(), any(), any())) + when(transactionSimulator.process( + any(), any(), any(TransactionValidationParams.class), any(), any(BlockHeader.class))) .thenReturn(Optional.empty()); when(blockchainQueries.getBlockHeaderByHash(Hash.ZERO)) .thenReturn(Optional.of(mock(BlockHeader.class))); 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 f85a0813ef..9f321e0050 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 @@ -44,6 +44,8 @@ import org.hyperledger.besu.ethereum.transaction.CallParameter; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult; +import org.hyperledger.besu.ethereum.util.AccountOverride; +import org.hyperledger.besu.ethereum.util.AccountOverrideMap; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -99,12 +101,44 @@ public class EthEstimateGasTest { assertThat(method.getName()).isEqualTo("eth_estimateGas"); } + @Test + public void noAccountOverrides() { + final Wei gasPrice = Wei.of(1000); + final JsonRpcRequestContext request = + ethEstimateGasRequest(defaultLegacyTransactionCallParameter(gasPrice), "latest"); + Optional overrideMap = method.getAddressAccountOverrideMap(request); + assertThat(overrideMap.isPresent()).isFalse(); + } + + @Test + public void someAccountOverrides() { + AccountOverrideMap expectedOverrides = new AccountOverrideMap(); + AccountOverride override = new AccountOverride.Builder().withNonce(88L).build(); + final Address address = Address.fromHexString("0xd9c9cd5f6779558b6e0ed4e6acf6b1947e7fa1f3"); + expectedOverrides.put(address, override); + + final Wei gasPrice = Wei.of(1000); + final JsonRpcRequestContext request = + ethEstimateGasRequestWithStateOverrides( + defaultLegacyTransactionCallParameter(gasPrice), "latest", expectedOverrides); + + Optional maybeOverrideMap = method.getAddressAccountOverrideMap(request); + assertThat(maybeOverrideMap.isPresent()).isTrue(); + AccountOverrideMap overrideMap = maybeOverrideMap.get(); + assertThat(overrideMap.keySet()).hasSize(1); + assertThat(overrideMap.values()).hasSize(1); + + assertThat(overrideMap).containsKey(address); + assertThat(overrideMap).containsValue(override); + } + @Test public void shouldReturnErrorWhenTransientLegacyTransactionProcessorReturnsEmpty() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); when(transactionSimulator.process( eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), + eq(Optional.empty()), // no account overrides any(TransactionValidationParams.class), any(OperationTracer.class), eq(latestBlockHeader))) @@ -341,6 +375,7 @@ public class EthEstimateGasTest { verify(transactionSimulator) .process( eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), + eq(Optional.empty()), // no account overrides eq( ImmutableTransactionValidationParams.builder() .from(TransactionValidationParams.transactionSimulator()) @@ -361,6 +396,7 @@ public class EthEstimateGasTest { verify(transactionSimulator) .process( eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), + eq(Optional.empty()), // no account overrides eq( ImmutableTransactionValidationParams.builder() .from(TransactionValidationParams.transactionSimulator()) @@ -461,12 +497,14 @@ public class EthEstimateGasTest { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); when(transactionSimulator.process( eq(modifiedLegacyTransactionCallParameter(gasPrice)), + eq(Optional.empty()), // no account overrides any(TransactionValidationParams.class), any(OperationTracer.class), eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); when(transactionSimulator.process( eq(modifiedEip1559TransactionCallParameter()), + eq(Optional.empty()), // no account overrides any(TransactionValidationParams.class), any(OperationTracer.class), eq(blockHeader))) @@ -551,4 +589,13 @@ public class EthEstimateGasTest { return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter, blockParam})); } + + private JsonRpcRequestContext ethEstimateGasRequestWithStateOverrides( + final CallParameter callParameter, + final String blockParam, + final AccountOverrideMap overrides) { + return new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", "eth_estimateGas", new Object[] {callParameter, blockParam, overrides})); + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index b0a7fa4325..3f35492b2b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -124,6 +124,21 @@ public class TransactionSimulator { blockHeader); } + public Optional process( + final CallParameter callParams, + final Optional maybeStateOverrides, + final TransactionValidationParams transactionValidationParams, + final OperationTracer operationTracer, + final BlockHeader blockHeader) { + return process( + callParams, + maybeStateOverrides, + transactionValidationParams, + operationTracer, + (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult, + blockHeader); + } + public Optional processAtHead(final CallParameter callParams) { final var chainHeadHash = blockchain.getChainHeadHash(); return process(