Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Internal error returned on eth_estimateGas #1478

4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
* Added support for the updated smart contract-based [node permissioning EEA interface](https://entethalliance.github.io/client-spec/spec.html#dfn-connectionallowed). [\#1435](https://github.com/hyperledger/besu/pull/1435)
* Added EvmTool binary to the distribution. EvmTool is a CLI that can execute EVM bytecode and execute ethereum state tests. [\#1465](https://github.com/hyperledger/besu/pull/1465)

### Bug Fixes

* Fix a bug on `eth_estimateGas` which returned `Internal error` instead of `Execution reverted` in case of reverted transaction [\#1478](https://github.com/hyperledger/besu/pull/1478)

## Deprecated and Scheduled for removal in _Next_ Release

### --privacy-precompiled-address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.mainnet.TransactionProcessor;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidator;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.transaction.CallParameter;
Expand Down Expand Up @@ -61,6 +62,11 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
if (blockHeader == null) {
return errorResponse(requestContext, JsonRpcError.INTERNAL_ERROR);
}
if (!blockchainQueries
.getWorldStateArchive()
.isWorldStateAvailable(blockHeader.getStateRoot())) {
return errorResponse(requestContext, JsonRpcError.WORLD_STATE_UNAVAILABLE);
}

final JsonCallParameter modifiedCallParams =
overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit());
Expand Down Expand Up @@ -96,7 +102,7 @@ private Function<TransactionSimulatorResult, JsonRpcResponse> gasEstimateRespons
? new JsonRpcSuccessResponse(
request.getRequest().getId(),
Quantity.create(processEstimateGas(result, operationTracer)))
: errorResponse(request, result.getValidationResult());
: errorResponse(request, result);
}

/**
Expand All @@ -119,21 +125,28 @@ private long processEstimateGas(
}

private JsonRpcErrorResponse errorResponse(
final JsonRpcRequestContext request,
final ValidationResult<TransactionValidator.TransactionInvalidReason> validationResult) {
JsonRpcError jsonRpcError = null;
final JsonRpcRequestContext request, final TransactionSimulatorResult result) {
final JsonRpcError jsonRpcError;

final ValidationResult<TransactionValidator.TransactionInvalidReason> validationResult =
result.getValidationResult();
if (validationResult != null && !validationResult.isValid()) {
jsonRpcError =
JsonRpcErrorConverter.convertTransactionInvalidReason(
validationResult.getInvalidReason());
} else {
final TransactionProcessor.Result resultTrx = result.getResult();
if (resultTrx != null && resultTrx.getRevertReason().isPresent()) {
jsonRpcError = JsonRpcError.REVERT_ERROR;
} else {
jsonRpcError = JsonRpcError.INTERNAL_ERROR;
}
}
return errorResponse(request, jsonRpcError);
}

private JsonRpcErrorResponse errorResponse(
final JsonRpcRequestContext request, final JsonRpcError jsonRpcError) {
return new JsonRpcErrorResponse(
request.getRequest().getId(),
jsonRpcError == null ? JsonRpcError.INTERNAL_ERROR : jsonRpcError);
return new JsonRpcErrorResponse(request.getRequest().getId(), jsonRpcError);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public enum JsonRpcError {
WRONG_CHAIN_ID(-32000, "Wrong chainId"),
REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED(-32000, "ChainId not supported"),
TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"),
REVERT_ERROR(-32000, "Execution reverted"),

// Miner failures
COINBASE_NOT_SET(-32010, "Coinbase not set. Unable to start mining without a coinbase"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@
import org.hyperledger.besu.ethereum.transaction.TransactionSimulator;
import org.hyperledger.besu.ethereum.transaction.TransactionSimulatorResult;
import org.hyperledger.besu.ethereum.vm.OperationTracer;
import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive;

import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;
import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -57,14 +59,17 @@ public class EthEstimateGasTest {
@Mock private Blockchain blockchain;
@Mock private BlockchainQueries blockchainQueries;
@Mock private TransactionSimulator transactionSimulator;
@Mock private WorldStateArchive worldStateArchive;

@Before
public void setUp() {
when(blockchainQueries.headBlockNumber()).thenReturn(1L);
when(blockchainQueries.getBlockchain()).thenReturn(blockchain);
when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive);
when(blockchain.getBlockHeader(eq(1L))).thenReturn(Optional.of(blockHeader));
when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE);
when(blockHeader.getNumber()).thenReturn(1L);
when(worldStateArchive.isWorldStateAvailable(any())).thenReturn(true);

method = new EthEstimateGas(blockchainQueries, transactionSimulator);
}
Expand All @@ -91,7 +96,7 @@ public void shouldReturnErrorWhenTransientTransactionProcessorReturnsEmpty() {
@Test
public void shouldReturnGasEstimateWhenTransientTransactionProcessorReturnsResultSuccess() {
final JsonRpcRequestContext request = ethEstimateGasRequest(callParameter());
mockTransientProcessorResultGasEstimate(1L, true);
mockTransientProcessorResultGasEstimate(1L, true, false);

final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L));

Expand All @@ -102,7 +107,7 @@ public void shouldReturnGasEstimateWhenTransientTransactionProcessorReturnsResul
@Test
public void shouldReturnGasEstimateErrorWhenTransientTransactionProcessorReturnsResultFailure() {
final JsonRpcRequestContext request = ethEstimateGasRequest(callParameter());
mockTransientProcessorResultGasEstimate(1L, false);
mockTransientProcessorResultGasEstimate(1L, false, false);

final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.INTERNAL_ERROR);
Expand All @@ -124,24 +129,52 @@ public void shouldReturnErrorWhenTransactionProcessorReturnsTxInvalidReason() {
.isEqualToComparingFieldByField(expectedResponse);
}

@Test
public void shouldReturnErrorWhenWorldStateIsNotAvailable() {
when(worldStateArchive.isWorldStateAvailable(any())).thenReturn(false);
final JsonRpcRequestContext request = ethEstimateGasRequest(callParameter());
mockTransientProcessorResultGasEstimate(1L, false, false);

final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.WORLD_STATE_UNAVAILABLE);

Assertions.assertThat(method.response(request))
.isEqualToComparingFieldByField(expectedResponse);
}

@Test
public void shouldReturnErrorWhenTransactioReverted() {
final JsonRpcRequestContext request = ethEstimateGasRequest(callParameter());
mockTransientProcessorResultGasEstimate(1L, false, true);

final JsonRpcResponse expectedResponse =
new JsonRpcErrorResponse(null, JsonRpcError.REVERT_ERROR);

Assertions.assertThat(method.response(request))
.isEqualToComparingFieldByField(expectedResponse);
}

private void mockTransientProcessorResultTxInvalidReason(final TransactionInvalidReason reason) {
final TransactionSimulatorResult mockTxSimResult = getMockTransactionSimulatorResult(false, 0);
final TransactionSimulatorResult mockTxSimResult =
getMockTransactionSimulatorResult(false, false, 0);
when(mockTxSimResult.getValidationResult()).thenReturn(ValidationResult.invalid(reason));
}

private void mockTransientProcessorResultGasEstimate(
final long estimateGas, final boolean isSuccessful) {
getMockTransactionSimulatorResult(isSuccessful, estimateGas);
final long estimateGas, final boolean isSuccessful, final boolean isReverted) {
getMockTransactionSimulatorResult(isSuccessful, isReverted, estimateGas);
}

private TransactionSimulatorResult getMockTransactionSimulatorResult(
final boolean isSuccessful, final long estimateGas) {
final boolean isSuccessful, final boolean isReverted, final long estimateGas) {
final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class);
when(transactionSimulator.process(
eq(modifiedCallParameter()), any(OperationTracer.class), eq(1L)))
.thenReturn(Optional.of(mockTxSimResult));
final TransactionProcessor.Result mockResult = mock(TransactionProcessor.Result.class);
when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas);
when(mockResult.getRevertReason())
.thenReturn(isReverted ? Optional.of(Bytes.of(0)) : Optional.empty());
when(mockTxSimResult.getResult()).thenReturn(mockResult);
when(mockTxSimResult.isSuccessful()).thenReturn(isSuccessful);
return mockTxSimResult;
Expand Down