Skip to content

Commit d048739

Browse files
authored
fix: improve error ergonomics and delegate to From() (#7)
* fix: improve error ergonomics and delegate to From() * fix: some PR comments * fix: refactored errors to be fully matchable * chore: clippy
1 parent a929e6b commit d048739

File tree

4 files changed

+210
-135
lines changed

4 files changed

+210
-135
lines changed

metabased-sequencer/interceptor/src/application/send_raw_transaction.rs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
use crate::presentation::json_rpc_errors::JsonRpcErrorCode;
2-
use crate::presentation::json_rpc_errors::JsonRpcErrorCode::InvalidRequest;
3-
use crate::presentation::jsonrpc::{rpc_error, JsonRpcError};
1+
use crate::presentation::json_rpc_errors::Error;
2+
use crate::presentation::json_rpc_errors::Error::InvalidInput;
43
use crate::presentation::transaction;
54
use alloy::consensus::{Transaction, TxEnvelope, TxType};
65
use alloy::primitives::private::alloy_rlp::Decodable;
76
use alloy::primitives::TxHash;
87
use alloy_primitives::U256;
98
use bytes::Bytes;
9+
use crate::presentation::json_rpc_errors::InvalidInputError::MissingGasPrice;
1010

1111
/// Sends serialized and signed transaction `tx`.
12-
pub fn send_raw_transaction(tx: Bytes) -> Result<TxHash, JsonRpcError<()>> {
12+
pub fn send_raw_transaction(tx: Bytes) -> Result<TxHash, Error> {
1313
// TODO remove or move up to function comment
1414
// 1. Decoding
1515
// 2. Validation
@@ -20,33 +20,20 @@ pub fn send_raw_transaction(tx: Bytes) -> Result<TxHash, JsonRpcError<()>> {
2020
let tx = TxEnvelope::decode(&mut slice)?;
2121

2222
// 2. Validation:
23-
tx.recover_signer().map_err(|_e| {
24-
rpc_error(
25-
"Invalid signer on transaction",
26-
JsonRpcErrorCode::InvalidParams,
27-
None,
28-
)
29-
})?;
23+
tx.recover_signer()?;
3024

3125
if tx.tx_type() == TxType::Legacy {
3226
// TODO(SEQ-179): introduce optional global tx cap config. See op-geth's checkTxFee() + RPCTxFeeCap for equivalent
3327
// skip check if unset
3428
let tx_cap_in_wei = U256::from(1_000_000_000_000_000_000u64); // 1e18wei = 1 ETH
35-
let gas_price = tx.gas_price().ok_or_else(|| {
36-
rpc_error("Legacy transaction missing gas price", InvalidRequest, None)
37-
})?;
29+
let gas_price = tx
30+
.gas_price()
31+
.ok_or(InvalidInput(MissingGasPrice))?;
3832
transaction::check_tx_fee(
3933
U256::try_from(gas_price)?,
4034
U256::try_from(tx.gas_limit())?,
4135
tx_cap_in_wei,
42-
)
43-
.map_err(|_e| {
44-
rpc_error(
45-
"Transaction fee exceeds the configured cap",
46-
JsonRpcErrorCode::InvalidInput,
47-
None,
48-
)
49-
})?;
36+
)?;
5037
}
5138

5239
Ok(tx.tx_hash().to_owned())
Lines changed: 131 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,139 @@
1+
use crate::presentation::transaction;
2+
use alloy::hex;
3+
use std::fmt;
4+
use alloy_primitives::private::alloy_rlp;
5+
use crate::presentation::json_rpc_errors::InvalidInputError::{InvalidJson, MissingGasPrice, InvalidTransactionSignature, InvalidUint, UnableToRLPDecode};
6+
use crate::presentation::json_rpc_errors::InvalidParamsError::InvalidHex;
7+
use crate::presentation::json_rpc_errors::Rejection::FeeTooHigh;
8+
19
// Source: https://github.com/MetaMask/rpc-errors/blob/main/src/errors.ts
210
#[derive(Debug, Clone, PartialEq)]
3-
pub enum JsonRpcErrorCode {
4-
InvalidRequest = -32600,
5-
MethodNotFound = -32601,
6-
InvalidParams = -32602,
7-
InternalError = -32603,
8-
ParseError = -32700,
9-
InvalidInput = -32000,
10-
ResourceNotFound = -32001,
11-
ResourceUnavailable = -32002,
12-
TransactionRejected = -32003,
13-
MethodNotSupported = -32004,
14-
LimitExceeded = -32005,
15-
ServerError = -32099, // We'll use this as the base for server errors
16-
}
17-
18-
impl JsonRpcErrorCode {
19-
// TODO - bring back if needed
20-
// pub fn message(&self) -> &'static str {
21-
// match self {
22-
// Self::InvalidRequest => "Invalid request",
23-
// Self::MethodNotFound => "Method not found",
24-
// Self::InvalidParams => "Invalid params",
25-
// Self::InternalError => "Internal error",
26-
// Self::ParseError => "Parse error",
27-
// Self::InvalidInput => "Invalid input",
28-
// Self::ResourceNotFound => "Resource not found",
29-
// Self::ResourceUnavailable => "Resource unavailable",
30-
// Self::TransactionRejected => "Transaction rejected",
31-
// Self::MethodNotSupported => "Method not supported",
32-
// Self::LimitExceeded => "Limit exceeded",
33-
// Self::ServerError => "Server error",
34-
// }
35-
// }
36-
}
37-
38-
impl From<i32> for JsonRpcErrorCode {
39-
fn from(value: i32) -> Self {
40-
match value {
41-
-32700 => Self::ParseError,
42-
-32600 => Self::InvalidRequest,
43-
-32601 => Self::MethodNotFound,
44-
-32602 => Self::InvalidParams,
45-
-32603 => Self::InternalError,
46-
-32000 => Self::InvalidInput,
47-
-32001 => Self::ResourceNotFound,
48-
-32002 => Self::ResourceUnavailable,
49-
-32003 => Self::TransactionRejected,
50-
-32004 => Self::MethodNotSupported,
51-
-32005 => Self::LimitExceeded,
52-
_ if (-32099..=-32000).contains(&value) => Self::ServerError,
53-
_ => Self::InternalError, // Default case
11+
pub enum Error {
12+
// Parent errors with a JSON-RPC error code mapping
13+
InvalidRequest,
14+
MethodNotFound(String),
15+
InvalidParams(InvalidParamsError),
16+
Internal,
17+
Parse,
18+
InvalidInput(InvalidInputError),
19+
ResourceNotFound,
20+
ResourceUnavailable,
21+
TransactionRejected(Rejection),
22+
MethodNotSupported,
23+
LimitExceeded,
24+
Server,
25+
}
26+
27+
#[derive(Debug, Clone, PartialEq)]
28+
pub enum Rejection {
29+
FeeTooHigh,
30+
}
31+
32+
#[derive(Debug, Clone, PartialEq)]
33+
pub enum InvalidParamsError {
34+
BadSignature,
35+
NonceTooLow,
36+
InvalidHex,
37+
NotAnArray,
38+
WrongParamCount(usize),
39+
MissingParam,
40+
NotHexEncoded
41+
}
42+
43+
#[derive(Debug, Clone, PartialEq)]
44+
pub enum InvalidInputError {
45+
InvalidJson,
46+
InvalidUint,
47+
InvalidTransactionSignature,
48+
MissingGasPrice,
49+
UnableToRLPDecode
50+
}
51+
52+
impl fmt::Display for InvalidParamsError {
53+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54+
match self {
55+
InvalidParamsError::BadSignature => write!(f, "bad signature"),
56+
InvalidParamsError::NonceTooLow => write!(f, "nonce too low"),
57+
InvalidHex => write!(f, "invalid hex"),
58+
InvalidParamsError::NotAnArray => write!(f, "params must be an array"),
59+
InvalidParamsError::WrongParamCount(_) => write!(f, "wrong number of params"),
60+
InvalidParamsError::MissingParam => write!(f, "missing param"),
61+
InvalidParamsError::NotHexEncoded => write!(f, "not a hex encoded string"),
62+
}
63+
}
64+
}
65+
66+
impl fmt::Display for InvalidInputError {
67+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68+
match self {
69+
InvalidJson => write!(f, "invalid JSON"),
70+
InvalidUint => write!(f, "invalid uint"),
71+
InvalidTransactionSignature => write!(f, "invalid transaction signature"),
72+
MissingGasPrice => write!(f, "transaction missing gas price"),
73+
UnableToRLPDecode => write!(f, "unable to RLP decode"),
74+
}
75+
}
76+
}
77+
78+
impl fmt::Display for Error {
79+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80+
match self {
81+
Error::InvalidRequest => write!(f, "invalid request",),
82+
Error::MethodNotFound(m) => write!(f, "method not found: {}", m),
83+
Error::InvalidParams(m) => write!(f, "invalid params: {}", m),
84+
Error::Internal => write!(f, "internal error"),
85+
Error::Parse => write!(f, "parse error"),
86+
Error::InvalidInput(m) => write!(f, "invalid input: {}", m),
87+
Error::ResourceNotFound => write!(f, "resource not found", ),
88+
Error::ResourceUnavailable => write!(f, "resource unavailable",),
89+
Error::TransactionRejected(m) => write!(f, "transaction rejected: {}", m),
90+
Error::MethodNotSupported => write!(f, "method not supported"),
91+
Error::LimitExceeded => write!(f, "limit exceeded"),
92+
Error::Server => write!(f, "server error"),
93+
}
94+
}
95+
}
96+
97+
impl fmt::Display for Rejection {
98+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99+
match self {
100+
FeeTooHigh => write!(f, "transaction fee too high"),
54101
}
55102
}
56103
}
57104

58-
impl JsonRpcErrorCode {
59-
pub fn code(&self) -> i32 {
60-
self.to_owned() as i32
105+
impl From<serde_json::Error> for Error {
106+
fn from(_: serde_json::Error) -> Self {
107+
Error::InvalidInput(InvalidJson)
61108
}
62109
}
110+
111+
impl From<hex::FromHexError> for Error {
112+
fn from(_: hex::FromHexError) -> Self {
113+
Error::InvalidParams(InvalidHex)
114+
}
115+
}
116+
117+
impl From<alloy_rlp::Error> for Error {
118+
fn from(_: alloy_rlp::Error) -> Self {
119+
Error::InvalidInput(UnableToRLPDecode)
120+
}
121+
}
122+
123+
impl From<alloy_primitives::SignatureError> for Error {
124+
fn from(_: alloy_primitives::SignatureError) -> Self {
125+
Error::InvalidInput(InvalidTransactionSignature)
126+
}
127+
}
128+
129+
impl From<transaction::TransactionFeeTooHigh> for Error {
130+
fn from(_: transaction::TransactionFeeTooHigh) -> Self {
131+
Error::TransactionRejected(FeeTooHigh)
132+
}
133+
}
134+
135+
impl<T> From<alloy_primitives::ruint::ToUintError<T>> for Error {
136+
fn from(_: alloy_primitives::ruint::ToUintError<T>) -> Self {
137+
Error::InvalidInput(InvalidUint)
138+
}
139+
}

metabased-sequencer/interceptor/src/presentation/jsonrpc.rs

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::application;
2-
use crate::presentation::json_rpc_errors::JsonRpcErrorCode;
3-
use crate::presentation::json_rpc_errors::JsonRpcErrorCode::InvalidParams;
2+
use crate::presentation::json_rpc_errors::Error;
3+
use crate::presentation::json_rpc_errors::Error::InvalidParams;
44
use alloy::hex;
55
use alloy::hex::ToHexExt;
66
use bytes::Bytes;
77
use jsonrpsee::types::{ErrorObject, Params};
88
use serde::Serialize;
99
use std::fmt::{Debug, Display, Formatter};
10+
use crate::presentation::json_rpc_errors::InvalidParamsError::{MissingParam, NotAnArray, NotHexEncoded, WrongParamCount};
1011

1112
/// An error type for JSON-RPC endpoints.
1213
///
@@ -45,6 +46,37 @@ impl<'error, S: Serialize> From<JsonRpcError<S>> for ErrorObject<'error> {
4546
}
4647
}
4748

49+
impl From<Error> for JsonRpcError<()> {
50+
fn from(value: Error) -> Self {
51+
let code = Self::valid_eth_rpc_codes(&value);
52+
JsonRpcError {
53+
code,
54+
message: value.to_string(),
55+
data: None,
56+
}
57+
}
58+
}
59+
60+
impl JsonRpcError<()> {
61+
fn valid_eth_rpc_codes(value: &Error) -> i32 {
62+
match value {
63+
Error::InvalidRequest => -32600,
64+
Error::MethodNotFound(_) => -32601,
65+
Error::InvalidParams(_) => -32602,
66+
Error::Internal => -32603,
67+
Error::Parse => -32700,
68+
Error::InvalidInput(_) => -32000,
69+
Error::ResourceNotFound => -32001,
70+
Error::ResourceUnavailable => -32002,
71+
Error::TransactionRejected(_) => -32003,
72+
Error::MethodNotSupported => -32004,
73+
Error::LimitExceeded => -32005,
74+
Error::Server => -32099,
75+
}
76+
}
77+
}
78+
79+
4880
/// The JSON-RPC endpoint for `eth_sendRawTransaction`.
4981
///
5082
/// # Parameters
@@ -57,48 +89,21 @@ pub fn send_raw_transaction(
5789
_ctx: &(),
5890
_ext: &http::Extensions,
5991
) -> Result<String, JsonRpcError<()>> {
60-
let mut json: serde_json::Value =
61-
serde_json::from_str(params.as_str().unwrap()).map_err(|e| {
62-
rpc_error(
63-
&format!("failed to unmarshal params: {}", e),
64-
InvalidParams,
65-
None,
66-
)
67-
})?;
68-
let arr = json.as_array_mut().ok_or(rpc_error(
69-
"unexpected parameter format",
70-
InvalidParams,
71-
None,
72-
))?;
92+
let mut json: serde_json::Value = serde_json::from_str(params.as_str().unwrap())?;
93+
let arr = json
94+
.as_array_mut()
95+
.ok_or(InvalidParams(NotAnArray))?;
7396
if arr.len() != 1 {
74-
return Err(rpc_error(
75-
&format!("Expected 1 parameter, got {}", arr.len()),
76-
InvalidParams,
77-
None,
78-
));
97+
return Err(InvalidParams(WrongParamCount(arr.len())).into());
7998
}
8099
let item = arr
81100
.pop()
82-
.ok_or(rpc_error("Missing parameter", InvalidParams, None))?;
83-
let str = item.as_str().ok_or(rpc_error(
84-
"Expected hex encoded string",
85-
InvalidParams,
86-
None,
87-
))?;
101+
.ok_or(InvalidParams(MissingParam))?; // should be impossible
102+
let str = item
103+
.as_str()
104+
.ok_or(InvalidParams(NotHexEncoded))?;
88105
let bytes = hex::decode(str)?;
89106
let bytes = Bytes::from(bytes);
90107

91108
Ok(application::send_raw_transaction(bytes)?.encode_hex_with_prefix())
92109
}
93-
94-
pub fn rpc_error<S: Serialize>(
95-
message: &str,
96-
error_code: JsonRpcErrorCode,
97-
data: Option<S>,
98-
) -> JsonRpcError<S> {
99-
JsonRpcError {
100-
code: error_code.code(),
101-
message: message.to_string(),
102-
data,
103-
}
104-
}

0 commit comments

Comments
 (0)