Fix Internal error returned on eth_estimateGas (#1478)

Signed-off-by: Karim TAAM <karim.t2am@gmail.com>
pull/1525/head
matkt 4 years ago committed by GitHub
parent ddbd12a591
commit 83e03706b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      CHANGELOG.md
  2. 27
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java
  3. 1
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java
  4. 45
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java

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

@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSucces
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;
@ -61,6 +62,11 @@ public class EthEstimateGas implements JsonRpcMethod {
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());
@ -96,7 +102,7 @@ public class EthEstimateGas implements JsonRpcMethod {
? new JsonRpcSuccessResponse(
request.getRequest().getId(),
Quantity.create(processEstimateGas(result, operationTracer)))
: errorResponse(request, result.getValidationResult());
: errorResponse(request, result);
}
/**
@ -119,21 +125,28 @@ public class EthEstimateGas implements JsonRpcMethod {
}
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);
}
}

@ -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"),

@ -38,9 +38,11 @@ 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.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;
@ -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);
}
@ -91,7 +96,7 @@ public class EthEstimateGasTest {
@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));
@ -102,7 +107,7 @@ public class EthEstimateGasTest {
@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);
@ -124,24 +129,52 @@ public class EthEstimateGasTest {
.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;

Loading…
Cancel
Save