From e04eed4300b97a7da972d51670dca06059259bbf Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Wed, 3 Jan 2024 14:06:23 +0000 Subject: [PATCH 1/5] Make RPC reason settable, pass execution failure reason in RPC error message Signed-off-by: Matthew Whitehead --- .../besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java | 2 ++ .../api/jsonrpc/internal/methods/AbstractEstimateGas.java | 8 ++++++++ .../api/jsonrpc/internal/response/JsonRpcError.java | 4 ++++ .../api/jsonrpc/internal/response/RpcErrorType.java | 1 + .../ethereum/mainnet/MainnetTransactionProcessor.java | 7 +++++++ .../ethereum/transaction/TransactionInvalidReason.java | 1 + 6 files changed, 23 insertions(+) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java index 89107497d51..202759c18e7 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java @@ -79,6 +79,8 @@ public static RpcErrorType convertTransactionInvalidReason( return RpcErrorType.PLUGIN_TX_VALIDATOR; case INVALID_BLOBS: return RpcErrorType.INVALID_BLOBS; + case EXECUTION_HALTED: + return RpcErrorType.EXECUTION_HALTED; default: return RpcErrorType.INTERNAL_ERROR; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index ddfd522c8f7..1a45d194efe 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -101,6 +101,14 @@ protected JsonRpcErrorResponse errorResponse( final ValidationResult validationResult = result.getValidationResult(); if (validationResult != null && !validationResult.isValid()) { + if (validationResult.getErrorMessage().length() > 0) { + final RpcErrorType rpcErrorType = + JsonRpcErrorConverter.convertTransactionInvalidReason( + validationResult.getInvalidReason()); + final JsonRpcError rpcError = new JsonRpcError(rpcErrorType); + rpcError.setReason(validationResult.getErrorMessage()); + return errorResponse(request, rpcError); + } return errorResponse( request, JsonRpcErrorConverter.convertTransactionInvalidReason( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java index 73c8a20519f..a3a3427de2b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java @@ -73,6 +73,10 @@ public String getData() { return data; } + public void setReason(final String reason) { + this.reason = reason; + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java index f025b4177a1..7e49ed9387e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java @@ -75,6 +75,7 @@ public enum RpcErrorType { -32000, "An invalid transaction with a lower nonce exists"), TOTAL_BLOB_GAS_TOO_HIGH(-32000, "Total blob gas too high"), PLUGIN_TX_VALIDATOR(-32000, "Plugin has marked the transaction as invalid"), + EXECUTION_HALTED(-32000, "Transaction processing could not be completed due to an exception"), // Execution engine failures UNKNOWN_PAYLOAD(-32001, "Payload does not exist / is not available"), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index 876fa65d298..931388b3878 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -426,6 +426,13 @@ public TransactionProcessingResult processTransaction( if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { worldUpdater.commit(); + } else { + if (initialFrame.getExceptionalHaltReason().isPresent()) { + validationResult = + ValidationResult.invalid( + TransactionInvalidReason.EXECUTION_HALTED, + initialFrame.getExceptionalHaltReason().get().toString()); + } } if (LOG.isTraceEnabled()) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index aef98171b42..55d7e431c8b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -49,6 +49,7 @@ public enum TransactionInvalidReason { TX_POOL_DISABLED, INVALID_BLOBS, PLUGIN_TX_VALIDATOR, + EXECUTION_HALTED, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_INVALID, PRIVATE_TRANSACTION_FAILED, From b4dbc9e4f28812c03bf9e895bb7702d94b5ddfad Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 4 Jan 2024 10:31:41 +0000 Subject: [PATCH 2/5] Update unit tests Signed-off-by: Matthew Whitehead --- .../internal/methods/EthEstimateGasTest.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) 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 6f1d748030f..9ac62cf8c28 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 @@ -209,10 +209,13 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); mockTransientProcessorResultTxInvalidReason( - TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE); + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 10 exceeds transaction sender account balance 5"); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); + final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; + final JsonRpcError rpcError = new JsonRpcError(rpcErrorType); + rpcError.setReason("transaction up-front cost 10 exceeds transaction sender account balance 5"); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); Assertions.assertThat(method.response(request)) .usingRecursiveComparison() @@ -223,10 +226,13 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso public void shouldReturnErrorWhenEip1559TransactionProcessorReturnsTxInvalidReason() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); mockTransientProcessorResultTxInvalidReason( - TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE); + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 10 exceeds transaction sender account balance 5"); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); + final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; + final JsonRpcError rpcError = new JsonRpcError(rpcErrorType); + rpcError.setReason("transaction up-front cost 10 exceeds transaction sender account balance 5"); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); Assertions.assertThat(method.response(request)) .usingRecursiveComparison() @@ -243,9 +249,9 @@ public void shouldReturnErrorWhenWorldStateIsNotAvailable() { final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + JsonRpcResponse theResponse = method.response(request); + + Assertions.assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -364,10 +370,32 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabled() { eq(1L)); } - private void mockTransientProcessorResultTxInvalidReason(final TransactionInvalidReason reason) { + @Test + public void shouldIncludeHaltReasonWhenExecutionHalts() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); + mockTransientProcessorResultTxInvalidReason( + TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); + + final RpcErrorType rpcErrorType = RpcErrorType.EXECUTION_HALTED; + final JsonRpcError rpcError = new JsonRpcError(rpcErrorType); + rpcError.setReason("INVALID_OPERATION"); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); + + Assertions.assertThat(method.response(request)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + } + + private void mockTransientProcessorResultTxInvalidReason( + final TransactionInvalidReason reason, final String validationFailedErrorMessage) { final TransactionSimulatorResult mockTxSimResult = getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty()); - when(mockTxSimResult.getValidationResult()).thenReturn(ValidationResult.invalid(reason)); + when(mockTxSimResult.getValidationResult()) + .thenReturn( + validationFailedErrorMessage == null + ? ValidationResult.invalid(reason) + : ValidationResult.invalid(reason, validationFailedErrorMessage)); } private void mockTransientProcessorTxReverted( From ac145c436723fe31cf915c6c78ccde591330f7a0 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 4 Jan 2024 17:13:47 +0000 Subject: [PATCH 3/5] Update tests Signed-off-by: Matthew Whitehead --- .../ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json index 21f5ffb3bcb..7c78f47000f 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json @@ -14,8 +14,8 @@ "jsonrpc": "2.0", "id": 3, "error": { - "code": -32603, - "message": "Internal error" + "code": -32000, + "message": "Transaction processing could not be completed due to an exception: INVALID_OPERATION" } }, "statusCode": 200 From 8826dc9bf561946158662d7344cb98ad7c6595c5 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 4 Jan 2024 17:20:08 +0000 Subject: [PATCH 4/5] Update change log Signed-off-by: Matthew Whitehead --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb73a619bd6..82485d0e425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 24.1.0-SNAPSHOT ### Breaking Changes +- New `EXECUTION_HALTED` error returned if there is an error executing or simulating a transaction, with the reason for execution being halted. Replaces the generic `INTERNAL_ERROR` return code in certain cases which some applications may be checking for [#6343](https://github.com/hyperledger/besu/pull/6343) ### Deprecations - Forest pruning (`pruning-enabled` options) is deprecated and will be removed soon. To save disk space consider switching to Bonsai data storage format [#6230](https://github.com/hyperledger/besu/pull/6230) From ee6ee97c9e1eb7a8fba39131d40b8d643c1ecba7 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Thu, 4 Jan 2024 17:48:06 +0000 Subject: [PATCH 5/5] Update integration tests Signed-off-by: Matthew Whitehead --- .../fork/frontier/EthEstimateGasIntegrationTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java index 5bc57774c2b..8717fe05600 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +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.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -161,8 +162,12 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowErr true, null); final JsonRpcRequestContext request = requestWithParams(callParameter); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); + + final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; + final JsonRpcError rpcError = new JsonRpcError(rpcErrorType); + rpcError.setReason( + "transaction up-front cost 0x1cc31b3333167018 exceeds transaction sender account balance 0x140"); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); final JsonRpcResponse response = method.response(request); assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse);