Extend error handling of plugin RPC methods (#6759)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/6451/head
Fabio Di Fabio 8 months ago committed by GitHub
parent 2bfd510785
commit 86cc6cb19e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 23
      besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java
  3. 11
      ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java
  4. 2
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java
  5. 7
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java
  6. 9
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginJsonRpcMethod.java
  7. 31
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java
  8. 28
      ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java
  9. 5
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java
  10. 173
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/PluginJsonRpcMethodTest.java
  11. 23
      ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java
  12. 5
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java
  13. 1
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java
  14. 2
      plugin-api/build.gradle
  15. 22
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java
  16. 38
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/exception/PluginRpcEndpointException.java
  17. 51
      plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcMethodError.java

@ -23,6 +23,7 @@
- Introduce `TransactionSimulationService` [#6686](https://github.com/hyperledger/besu/pull/6686)
- Transaction call object to accept both `input` and `data` field simultaneously if they are set to equal values [#6702](https://github.com/hyperledger/besu/pull/6702)
- `eth_call` for blob tx allows for empty `maxFeePerBlobGas` [#6731](https://github.com/hyperledger/besu/pull/6731)
- Extend error handling of plugin RPC methods [#6759](https://github.com/hyperledger/besu/pull/6759)
### Bug fixes
- Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665)

@ -19,7 +19,10 @@ import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.ethereum.chain.Blockchain;
import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams;
import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
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.evm.tracing.OperationTracer;
import org.hyperledger.besu.plugin.Unstable;
@ -62,14 +65,16 @@ public class TransactionSimulationServiceImpl implements TransactionSimulationSe
final CallParameter callParameter = CallParameter.fromTransaction(transaction);
final var blockHeader =
blockchain
.getBlockHeader(blockHash)
.or(() -> blockchain.getBlockHeaderSafe(blockHash))
.orElseThrow(
() ->
new IllegalStateException(
"Block header not yet present for chain head hash: " + blockHash));
final var maybeBlockHeader =
blockchain.getBlockHeader(blockHash).or(() -> blockchain.getBlockHeaderSafe(blockHash));
if (maybeBlockHeader.isEmpty()) {
return Optional.of(
new TransactionSimulationResult(
transaction,
TransactionProcessingResult.invalid(
ValidationResult.invalid(TransactionInvalidReason.BLOCK_NOT_FOUND))));
}
return transactionSimulator
.process(
@ -78,7 +83,7 @@ public class TransactionSimulationServiceImpl implements TransactionSimulationSe
? SIMULATOR_ALLOWING_EXCEEDING_BALANCE
: TransactionValidationParams.transactionSimulator(),
operationTracer,
blockHeader)
maybeBlockHeader.get())
.map(res -> new TransactionSimulationResult(transaction, res.result()));
}
}

@ -28,7 +28,8 @@ 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;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.testutil.BlockTestUtil;
import java.util.Map;
@ -171,11 +172,11 @@ public class EthEstimateGasIntegrationTest {
null,
null);
final JsonRpcRequestContext request = requestWithParams(callParameter);
final RpcErrorType rpcErrorType = RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE;
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason(
final ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 0x1cc31b3333167018 exceeds transaction sender account balance 0x140");
final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
final JsonRpcResponse response = method.response(request);

@ -83,6 +83,8 @@ public class JsonRpcErrorConverter {
return RpcErrorType.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE;
case EXECUTION_HALTED:
return RpcErrorType.EXECUTION_HALTED;
case BLOCK_NOT_FOUND:
return RpcErrorType.BLOCK_NOT_FOUND;
default:
return RpcErrorType.INTERNAL_ERROR;
}

@ -111,12 +111,7 @@ public abstract class AbstractEstimateGas implements JsonRpcMethod {
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, JsonRpcError.from(validationResult));
}
return errorResponse(
request,

@ -14,9 +14,6 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INTERNAL_ERROR;
import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.PLUGIN_INTERNAL_ERROR;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
@ -53,13 +50,9 @@ public class PluginJsonRpcMethod implements JsonRpcMethod {
final Object result = function.apply(() -> request.getRequest().getParams());
return new JsonRpcSuccessResponse(request.getRequest().getId(), result);
} catch (final PluginRpcEndpointException ex) {
final JsonRpcError error = new JsonRpcError(PLUGIN_INTERNAL_ERROR, ex.getMessage());
final JsonRpcError error = new JsonRpcError(ex.getRpcMethodError(), ex.getMessage());
LOG.error("Error calling plugin JSON-RPC endpoint", ex);
return new JsonRpcErrorResponse(request.getRequest().getId(), error);
} catch (final Exception ex) {
LOG.error("Error calling plugin JSON-RPC endpoint", ex);
return new JsonRpcErrorResponse(
request.getRequest().getId(), new JsonRpcError(INTERNAL_ERROR));
}
}
}

@ -14,6 +14,11 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter;
import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.plugin.services.rpc.RpcMethodError;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonCreator;
@ -21,7 +26,6 @@ import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.apache.tuweni.bytes.Bytes;
@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
@ -41,16 +45,11 @@ public class JsonRpcError {
this.data = data;
}
public JsonRpcError(final RpcErrorType errorType, final String data) {
public JsonRpcError(final RpcMethodError errorType, final String data) {
this(errorType.getCode(), errorType.getMessage(), data);
// For execution reverted errors decode the data (if present)
if (errorType == RpcErrorType.REVERT_ERROR && data != null) {
JsonRpcErrorResponse.decodeRevertReason(Bytes.fromHexString(data))
.ifPresent(
(decodedReason) -> {
this.reason = decodedReason;
});
if (data != null) {
errorType.decodeData(data).ifPresent(decodedData -> this.reason = decodedData);
}
}
@ -58,6 +57,16 @@ public class JsonRpcError {
this(errorType, null);
}
public static JsonRpcError from(
final ValidationResult<TransactionInvalidReason> validationResult) {
final var jsonRpcError =
new JsonRpcError(
JsonRpcErrorConverter.convertTransactionInvalidReason(
validationResult.getInvalidReason()));
jsonRpcError.reason = validationResult.getErrorMessage();
return jsonRpcError;
}
@JsonGetter("code")
public int getCode() {
return code;
@ -73,10 +82,6 @@ public class JsonRpcError {
return data;
}
public void setReason(final String reason) {
this.reason = reason;
}
@Override
public boolean equals(final Object o) {
if (this == o) {

@ -14,7 +14,14 @@
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response;
public enum RpcErrorType {
import org.hyperledger.besu.plugin.services.rpc.RpcMethodError;
import java.util.Optional;
import java.util.function.Function;
import org.apache.tuweni.bytes.Bytes;
public enum RpcErrorType implements RpcMethodError {
// Standard errors
PARSE_ERROR(-32700, "Parse error"),
INVALID_REQUEST(-32600, "Invalid Request"),
@ -67,7 +74,10 @@ public enum RpcErrorType {
REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED(-32000, "ChainId not supported"),
REPLAY_PROTECTED_SIGNATURE_REQUIRED(-32000, "ChainId is required"),
TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"),
REVERT_ERROR(-32000, "Execution reverted"),
REVERT_ERROR(
-32000,
"Execution reverted",
data -> JsonRpcErrorResponse.decodeRevertReason(Bytes.fromHexString(data))),
TRANSACTION_NOT_FOUND(-32000, "Transaction not found"),
MAX_PRIORITY_FEE_PER_GAS_EXCEEDS_MAX_FEE_PER_GAS(
-32000, "Max priority fee per gas exceeds max fee per gas"),
@ -222,17 +232,31 @@ public enum RpcErrorType {
private final int code;
private final String message;
private final Function<String, Optional<String>> dataDecoder;
RpcErrorType(final int code, final String message) {
this(code, message, null);
}
RpcErrorType(
final int code, final String message, Function<String, Optional<String>> dataDecoder) {
this.code = code;
this.message = message;
this.dataDecoder = dataDecoder;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
@Override
public Optional<String> decodeData(final String data) {
return dataDecoder == null ? Optional.empty() : dataDecoder.apply(data);
}
}

@ -152,8 +152,7 @@ public class JsonRpcHttpServiceTestBase {
baseUrl = service.url();
}
protected static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config)
throws Exception {
protected static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config) {
return new JsonRpcHttpService(
vertx,
folder,
@ -165,7 +164,7 @@ public class JsonRpcHttpServiceTestBase {
HealthService.ALWAYS_HEALTHY);
}
protected static JsonRpcHttpService createJsonRpcHttpService() throws Exception {
protected static JsonRpcHttpService createJsonRpcHttpService() {
return new JsonRpcHttpService(
vertx,
folder,

@ -0,0 +1,173 @@
/*
* Copyright Hyperledger Besu contributors
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc;
import static org.assertj.core.api.Assertions.assertThat;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginJsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType;
import org.hyperledger.besu.plugin.services.exception.PluginRpcEndpointException;
import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest;
import org.hyperledger.besu.plugin.services.rpc.RpcMethodError;
import io.vertx.core.json.JsonObject;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class PluginJsonRpcMethodTest extends JsonRpcHttpServiceTestBase {
@BeforeAll
public static void setup() throws Exception {
initServerAndClient();
}
/** Tears down the HTTP server. */
@AfterAll
public static void shutdownServer() {
service.stop().join();
}
@Test
public void happyPath() throws Exception {
final var request =
"""
{"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":["hello"]}""";
try (var unused =
addRpcMethod(
"plugin_echo",
new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) {
final RequestBody body = RequestBody.create(request, JSON);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final JsonObject json = new JsonObject(resp.body().string());
testHelper.assertValidJsonRpcResult(json, 1);
assertThat(json.getString("result")).isEqualTo("hello");
}
}
}
@Test
public void invalidJsonShouldReturnParseError() throws Exception {
final var malformedRequest =
"""
{"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":}""";
try (var unused =
addRpcMethod(
"plugin_echo",
new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) {
final RequestBody body = RequestBody.create(malformedRequest, JSON);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(400);
final JsonObject json = new JsonObject(resp.body().string());
final JsonRpcError expectedError = new JsonRpcError(RpcErrorType.PARSE_ERROR);
testHelper.assertValidJsonRpcError(
json, null, expectedError.getCode(), expectedError.getMessage());
}
}
}
@Test
public void invalidParamsShouldReturnInvalidParams() throws Exception {
final var missingRequiredParam =
"""
{"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":[]}""";
try (var unused =
addRpcMethod(
"plugin_echo",
new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) {
final RequestBody body = RequestBody.create(missingRequiredParam, JSON);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final JsonObject json = new JsonObject(resp.body().string());
final JsonRpcError expectedError = new JsonRpcError(RpcErrorType.INVALID_PARAMS);
testHelper.assertValidJsonRpcError(
json, 1, expectedError.getCode(), expectedError.getMessage());
}
}
}
@Test
public void methodErrorShouldReturnErrorResponse() throws Exception {
final var wrongParamContent =
"""
{"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":[" "]}""";
try (var unused =
addRpcMethod(
"plugin_echo",
new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) {
final RequestBody body = RequestBody.create(wrongParamContent, JSON);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final JsonObject json = new JsonObject(resp.body().string());
testHelper.assertValidJsonRpcError(json, 1, -1, "Blank input not allowed");
}
}
}
@Test
public void unhandledExceptionShouldReturnInternalErrorResponse() throws Exception {
final var nullParam =
"""
{"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":[null]}""";
try (var unused =
addRpcMethod(
"plugin_echo",
new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) {
final RequestBody body = RequestBody.create(nullParam, JSON);
try (final Response resp = client.newCall(buildPostRequest(body)).execute()) {
assertThat(resp.code()).isEqualTo(200);
final JsonObject json = new JsonObject(resp.body().string());
final JsonRpcError expectedError = new JsonRpcError(RpcErrorType.INTERNAL_ERROR);
testHelper.assertValidJsonRpcError(
json, 1, expectedError.getCode(), expectedError.getMessage());
}
}
}
private static Object echoPluginRpcMethod(final PluginRpcRequest request) {
final var params = request.getParams();
if (params.length == 0) {
throw new InvalidJsonRpcParameters("parameter is mandatory");
}
final var input = params[0];
if (input.toString().isBlank()) {
throw new PluginRpcEndpointException(
new RpcMethodError() {
@Override
public int getCode() {
return -1;
}
@Override
public String getMessage() {
return "Blank input not allowed";
}
});
}
return input;
}
}

@ -219,9 +219,11 @@ public class EthEstimateGasTest {
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 10 exceeds transaction sender account balance 5");
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 ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 10 exceeds transaction sender account balance 5");
final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
Assertions.assertThat(method.response(request))
@ -235,10 +237,11 @@ public class EthEstimateGasTest {
mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 10 exceeds transaction sender account balance 5");
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 ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid(
TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE,
"transaction up-front cost 10 exceeds transaction sender account balance 5");
final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
Assertions.assertThat(method.response(request))
@ -384,9 +387,9 @@ public class EthEstimateGasTest {
mockTransientProcessorResultTxInvalidReason(
TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION");
final RpcErrorType rpcErrorType = RpcErrorType.EXECUTION_HALTED;
final JsonRpcError rpcError = new JsonRpcError(rpcErrorType);
rpcError.setReason("INVALID_OPERATION");
final ValidationResult<TransactionInvalidReason> validationResult =
ValidationResult.invalid(TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION");
final JsonRpcError rpcError = JsonRpcError.from(validationResult);
final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError);
Assertions.assertThat(method.response(request))

@ -18,7 +18,6 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
import org.hyperledger.besu.evm.log.Log;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -56,7 +55,7 @@ public class TransactionProcessingResult
public static TransactionProcessingResult invalid(
final ValidationResult<TransactionInvalidReason> validationResult) {
return new TransactionProcessingResult(
Status.INVALID, new ArrayList<>(), -1, -1, Bytes.EMPTY, validationResult, Optional.empty());
Status.INVALID, List.of(), -1, -1, Bytes.EMPTY, validationResult, Optional.empty());
}
public static TransactionProcessingResult failed(
@ -66,7 +65,7 @@ public class TransactionProcessingResult
final Optional<Bytes> revertReason) {
return new TransactionProcessingResult(
Status.FAILED,
new ArrayList<>(),
List.of(),
gasUsedByTransaction,
gasRemaining,
Bytes.EMPTY,

@ -32,6 +32,7 @@ public enum TransactionInvalidReason {
TX_SENDER_NOT_AUTHORIZED,
CHAIN_HEAD_NOT_AVAILABLE,
CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE,
BLOCK_NOT_FOUND,
EXCEEDS_PER_TRANSACTION_GAS_LIMIT,
INVALID_TRANSACTION_FORMAT,
TRANSACTION_PRICE_TOO_LOW,

@ -69,7 +69,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = 'ytjNiSzw9IR8YHyO4ikmqRTg1GTWkCX9QiQtwq2dRSg='
knownHash = '0xiYCyr3M4oSrvqYXVkLgVDzlBg2T3fmrADub5tY5a0='
}
check.dependsOn('checkAPIChanges')

@ -16,6 +16,10 @@ package org.hyperledger.besu.plugin.data;
import org.hyperledger.besu.datatypes.Transaction;
import java.util.Optional;
import org.apache.tuweni.bytes.Bytes;
/**
* TransactionSimulationResult
*
@ -43,6 +47,24 @@ public record TransactionSimulationResult(
return result.isInvalid();
}
/**
* Return the optional revert reason
*
* @return the optional revert reason
*/
public Optional<Bytes> getRevertReason() {
return result.getRevertReason();
}
/**
* Return the optional invalid reason
*
* @return the optional invalid reason
*/
public Optional<String> getInvalidReason() {
return result.getInvalidReason();
}
/**
* Estimated gas used by the transaction
*

@ -14,28 +14,58 @@
*/
package org.hyperledger.besu.plugin.services.exception;
import org.hyperledger.besu.plugin.services.rpc.RpcMethodError;
/** Base exception class for problems encountered in the RpcEndpointService. */
public class PluginRpcEndpointException extends RuntimeException {
/** The error */
private final RpcMethodError rpcMethodError;
/**
* Constructs a new PluginRpcEndpointException exception with the specified error.
*
* @param rpcMethodError the error.
*/
public PluginRpcEndpointException(final RpcMethodError rpcMethodError) {
super();
this.rpcMethodError = rpcMethodError;
}
/**
* Constructs a new PluginRpcEndpointException exception with the specified message.
* Constructs a new PluginRpcEndpointException exception with the specified error and message.
*
* @param rpcMethodError the error.
* @param message the detail message (which is saved for later retrieval by the {@link
* #getMessage()} method).
*/
public PluginRpcEndpointException(final String message) {
public PluginRpcEndpointException(final RpcMethodError rpcMethodError, final String message) {
super(message);
this.rpcMethodError = rpcMethodError;
}
/**
* Constructs a new PluginRpcEndpointException exception with the specified message.
* Constructs a new PluginRpcEndpointException exception with the specified error, message and
* cause.
*
* @param rpcMethodError the error.
* @param message the detail message (which is saved for later retrieval by the {@link
* #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
* (A {@code null} value is permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public PluginRpcEndpointException(final String message, final Throwable cause) {
public PluginRpcEndpointException(
final RpcMethodError rpcMethodError, final String message, final Throwable cause) {
super(message, cause);
this.rpcMethodError = rpcMethodError;
}
/**
* Get the error
*
* @return the error
*/
public RpcMethodError getRpcMethodError() {
return rpcMethodError;
}
}

@ -0,0 +1,51 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.plugin.services.rpc;
import java.util.Optional;
/**
* The {@code RpcMethodError} interface defines the structure for RPC error handling within the
* context of plugins. It provides methods to retrieve error code, message, and an optional data
* decoder function.
*/
public interface RpcMethodError {
/**
* Retrieves the error code associated with the RPC error.
*
* @return An integer representing the error code.
*/
int getCode();
/**
* Retrieves the message associated with the RPC error.
*
* @return A {@code String} containing the error message.
*/
String getMessage();
/**
* Some errors have additional data associated with them, that is possible to decode to provide a
* more detailed error response.
*
* @param data the additional data to decode
* @return an optional containing the decoded data if the error has it and the decoding is
* successful, otherwise empty.
*/
default Optional<String> decodeData(final String data) {
return Optional.empty();
}
}
Loading…
Cancel
Save