Skip to content

Commit

Permalink
Extend error handlig of plugin RPC methods
Browse files Browse the repository at this point in the history
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
  • Loading branch information
fab-10 committed Mar 19, 2024
1 parent 42b32d2 commit 67ceb03
Show file tree
Hide file tree
Showing 14 changed files with 193 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
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;
Expand Down Expand Up @@ -62,14 +65,16 @@ public Optional<TransactionSimulationResult> simulate(

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(
Expand All @@ -78,7 +83,7 @@ public Optional<TransactionSimulationResult> simulate(
? SIMULATOR_ALLOWING_EXCEEDING_BALANCE
: TransactionValidationParams.transactionSimulator(),
operationTracer,
blockHeader)
maybeBlockHeader.get())
.map(res -> new TransactionSimulationResult(transaction, res.result()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
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;
Expand Down Expand Up @@ -171,11 +172,11 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowErr
null,
null);
final JsonRpcRequestContext request = requestWithParams(callParameter);

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 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public static RpcErrorType convertTransactionInvalidReason(
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,7 @@ protected JsonRpcErrorResponse errorResponse(
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,13 +50,9 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) {
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.getRpcError(), 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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@
*/
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;
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)
Expand All @@ -41,23 +45,28 @@ public 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);
}
}

public JsonRpcError(final RpcErrorType errorType) {
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;
Expand All @@ -73,10 +82,6 @@ public String getData() {
return data;
}

public void setReason(final String reason) {
this.reason = reason;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,11 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso
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))
Expand All @@ -235,10 +237,11 @@ public void shouldReturnErrorWhenEip1559TransactionProcessorReturnsTxInvalidReas
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))
Expand Down Expand Up @@ -384,9 +387,9 @@ public void shouldIncludeHaltReasonWhenExecutionHalts() {
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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
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;

Expand Down Expand Up @@ -56,7 +55,7 @@ public enum Status {
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(
Expand All @@ -66,7 +65,7 @@ public static TransactionProcessingResult failed(
final Optional<Bytes> revertReason) {
return new TransactionProcessingResult(
Status.FAILED,
new ArrayList<>(),
List.of(),
gasUsedByTransaction,
gasRemaining,
Bytes.EMPTY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion plugin-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 'g2DFNN/OMEwLVcoRUZW7vA4PMQMi5vxkztOmbgkB+dU='
}
check.dependsOn('checkAPIChanges')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

import org.hyperledger.besu.datatypes.Transaction;

import java.util.Optional;

import org.apache.tuweni.bytes.Bytes;

/**
* TransactionSimulationResult
*
Expand Down Expand Up @@ -43,6 +47,24 @@ public boolean isInvalid() {
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
*
Expand Down
Loading

0 comments on commit 67ceb03

Please sign in to comment.