Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::presentation::json_rpc_errors::JsonRpcErrorCode;
use crate::presentation::json_rpc_errors::JsonRpcErrorCode::InvalidRequest;
use crate::presentation::jsonrpc::{rpc_error, JsonRpcError};
use crate::presentation::json_rpc_errors::Error;
use crate::presentation::json_rpc_errors::Error::InvalidInput;
use crate::presentation::transaction;
use alloy::consensus::{Transaction, TxEnvelope, TxType};
use alloy::primitives::private::alloy_rlp::Decodable;
use alloy::primitives::TxHash;
use alloy_primitives::U256;
use bytes::Bytes;
use crate::presentation::json_rpc_errors::InvalidInputError::MissingGasPrice;

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

// 2. Validation:
tx.recover_signer().map_err(|_e| {
rpc_error(
"Invalid signer on transaction",
JsonRpcErrorCode::InvalidParams,
None,
)
})?;
tx.recover_signer()?;

if tx.tx_type() == TxType::Legacy {
// TODO(SEQ-179): introduce optional global tx cap config. See op-geth's checkTxFee() + RPCTxFeeCap for equivalent
// skip check if unset
let tx_cap_in_wei = U256::from(1_000_000_000_000_000_000u64); // 1e18wei = 1 ETH
let gas_price = tx.gas_price().ok_or_else(|| {
rpc_error("Legacy transaction missing gas price", InvalidRequest, None)
})?;
let gas_price = tx
.gas_price()
.ok_or(InvalidInput(MissingGasPrice))?;
transaction::check_tx_fee(
U256::try_from(gas_price)?,
U256::try_from(tx.gas_limit())?,
tx_cap_in_wei,
)
.map_err(|_e| {
rpc_error(
"Transaction fee exceeds the configured cap",
JsonRpcErrorCode::InvalidInput,
None,
)
})?;
)?;
}

Ok(tx.tx_hash().to_owned())
Expand Down
185 changes: 131 additions & 54 deletions metabased-sequencer/interceptor/src/presentation/json_rpc_errors.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,139 @@
use crate::presentation::transaction;
use alloy::hex;
use std::fmt;
use alloy_primitives::private::alloy_rlp;
use crate::presentation::json_rpc_errors::InvalidInputError::{InvalidJson, MissingGasPrice, InvalidTransactionSignature, InvalidUint, UnableToRLPDecode};
use crate::presentation::json_rpc_errors::InvalidParamsError::InvalidHex;
use crate::presentation::json_rpc_errors::Rejection::FeeTooHigh;

// Source: https://github.com/MetaMask/rpc-errors/blob/main/src/errors.ts
#[derive(Debug, Clone, PartialEq)]
pub enum JsonRpcErrorCode {
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
ParseError = -32700,
InvalidInput = -32000,
ResourceNotFound = -32001,
ResourceUnavailable = -32002,
TransactionRejected = -32003,
MethodNotSupported = -32004,
LimitExceeded = -32005,
ServerError = -32099, // We'll use this as the base for server errors
}

impl JsonRpcErrorCode {
// TODO - bring back if needed
// pub fn message(&self) -> &'static str {
// match self {
// Self::InvalidRequest => "Invalid request",
// Self::MethodNotFound => "Method not found",
// Self::InvalidParams => "Invalid params",
// Self::InternalError => "Internal error",
// Self::ParseError => "Parse error",
// Self::InvalidInput => "Invalid input",
// Self::ResourceNotFound => "Resource not found",
// Self::ResourceUnavailable => "Resource unavailable",
// Self::TransactionRejected => "Transaction rejected",
// Self::MethodNotSupported => "Method not supported",
// Self::LimitExceeded => "Limit exceeded",
// Self::ServerError => "Server error",
// }
// }
}

impl From<i32> for JsonRpcErrorCode {
fn from(value: i32) -> Self {
match value {
-32700 => Self::ParseError,
-32600 => Self::InvalidRequest,
-32601 => Self::MethodNotFound,
-32602 => Self::InvalidParams,
-32603 => Self::InternalError,
-32000 => Self::InvalidInput,
-32001 => Self::ResourceNotFound,
-32002 => Self::ResourceUnavailable,
-32003 => Self::TransactionRejected,
-32004 => Self::MethodNotSupported,
-32005 => Self::LimitExceeded,
_ if (-32099..=-32000).contains(&value) => Self::ServerError,
_ => Self::InternalError, // Default case
pub enum Error {
// Parent errors with a JSON-RPC error code mapping
InvalidRequest,
MethodNotFound(String),
InvalidParams(InvalidParamsError),
Internal,
Parse,
InvalidInput(InvalidInputError),
ResourceNotFound,
ResourceUnavailable,
TransactionRejected(Rejection),
MethodNotSupported,
LimitExceeded,
Server,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Rejection {
FeeTooHigh,
}

#[derive(Debug, Clone, PartialEq)]
pub enum InvalidParamsError {
BadSignature,
NonceTooLow,
InvalidHex,
NotAnArray,
WrongParamCount(usize),
MissingParam,
NotHexEncoded
}

#[derive(Debug, Clone, PartialEq)]
pub enum InvalidInputError {
InvalidJson,
InvalidUint,
InvalidTransactionSignature,
MissingGasPrice,
UnableToRLPDecode
}

impl fmt::Display for InvalidParamsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidParamsError::BadSignature => write!(f, "bad signature"),
InvalidParamsError::NonceTooLow => write!(f, "nonce too low"),
InvalidHex => write!(f, "invalid hex"),
InvalidParamsError::NotAnArray => write!(f, "params must be an array"),
InvalidParamsError::WrongParamCount(_) => write!(f, "wrong number of params"),
InvalidParamsError::MissingParam => write!(f, "missing param"),
InvalidParamsError::NotHexEncoded => write!(f, "not a hex encoded string"),
}
}
}

impl fmt::Display for InvalidInputError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InvalidJson => write!(f, "invalid JSON"),
InvalidUint => write!(f, "invalid uint"),
InvalidTransactionSignature => write!(f, "invalid transaction signature"),
MissingGasPrice => write!(f, "transaction missing gas price"),
UnableToRLPDecode => write!(f, "unable to RLP decode"),
}
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidRequest => write!(f, "invalid request",),
Error::MethodNotFound(m) => write!(f, "method not found: {}", m),
Error::InvalidParams(m) => write!(f, "invalid params: {}", m),
Error::Internal => write!(f, "internal error"),
Error::Parse => write!(f, "parse error"),
Error::InvalidInput(m) => write!(f, "invalid input: {}", m),
Error::ResourceNotFound => write!(f, "resource not found", ),
Error::ResourceUnavailable => write!(f, "resource unavailable",),
Error::TransactionRejected(m) => write!(f, "transaction rejected: {}", m),
Error::MethodNotSupported => write!(f, "method not supported"),
Error::LimitExceeded => write!(f, "limit exceeded"),
Error::Server => write!(f, "server error"),
}
}
}

impl fmt::Display for Rejection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FeeTooHigh => write!(f, "transaction fee too high"),
}
}
}

impl JsonRpcErrorCode {
pub fn code(&self) -> i32 {
self.to_owned() as i32
impl From<serde_json::Error> for Error {
fn from(_: serde_json::Error) -> Self {
Error::InvalidInput(InvalidJson)
}
}

impl From<hex::FromHexError> for Error {
fn from(_: hex::FromHexError) -> Self {
Error::InvalidParams(InvalidHex)
}
}

impl From<alloy_rlp::Error> for Error {
fn from(_: alloy_rlp::Error) -> Self {
Error::InvalidInput(UnableToRLPDecode)
}
}

impl From<alloy_primitives::SignatureError> for Error {
fn from(_: alloy_primitives::SignatureError) -> Self {
Error::InvalidInput(InvalidTransactionSignature)
}
}

impl From<transaction::TransactionFeeTooHigh> for Error {
fn from(_: transaction::TransactionFeeTooHigh) -> Self {
Error::TransactionRejected(FeeTooHigh)
}
}

impl<T> From<alloy_primitives::ruint::ToUintError<T>> for Error {
fn from(_: alloy_primitives::ruint::ToUintError<T>) -> Self {
Error::InvalidInput(InvalidUint)
}
}
81 changes: 43 additions & 38 deletions metabased-sequencer/interceptor/src/presentation/jsonrpc.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use crate::application;
use crate::presentation::json_rpc_errors::JsonRpcErrorCode;
use crate::presentation::json_rpc_errors::JsonRpcErrorCode::InvalidParams;
use crate::presentation::json_rpc_errors::Error;
use crate::presentation::json_rpc_errors::Error::InvalidParams;
use alloy::hex;
use alloy::hex::ToHexExt;
use bytes::Bytes;
use jsonrpsee::types::{ErrorObject, Params};
use serde::Serialize;
use std::fmt::{Debug, Display, Formatter};
use crate::presentation::json_rpc_errors::InvalidParamsError::{MissingParam, NotAnArray, NotHexEncoded, WrongParamCount};

/// An error type for JSON-RPC endpoints.
///
Expand Down Expand Up @@ -45,6 +46,37 @@ impl<'error, S: Serialize> From<JsonRpcError<S>> for ErrorObject<'error> {
}
}

impl From<Error> for JsonRpcError<()> {
fn from(value: Error) -> Self {
let code = Self::valid_eth_rpc_codes(&value);
JsonRpcError {
code,
message: value.to_string(),
data: None,
}
}
}

impl JsonRpcError<()> {
fn valid_eth_rpc_codes(value: &Error) -> i32 {
match value {
Error::InvalidRequest => -32600,
Error::MethodNotFound(_) => -32601,
Error::InvalidParams(_) => -32602,
Error::Internal => -32603,
Error::Parse => -32700,
Error::InvalidInput(_) => -32000,
Error::ResourceNotFound => -32001,
Error::ResourceUnavailable => -32002,
Error::TransactionRejected(_) => -32003,
Error::MethodNotSupported => -32004,
Error::LimitExceeded => -32005,
Error::Server => -32099,
}
}
}


/// The JSON-RPC endpoint for `eth_sendRawTransaction`.
///
/// # Parameters
Expand All @@ -57,48 +89,21 @@ pub fn send_raw_transaction(
_ctx: &(),
_ext: &http::Extensions,
) -> Result<String, JsonRpcError<()>> {
let mut json: serde_json::Value =
serde_json::from_str(params.as_str().unwrap()).map_err(|e| {
rpc_error(
&format!("failed to unmarshal params: {}", e),
InvalidParams,
None,
)
})?;
let arr = json.as_array_mut().ok_or(rpc_error(
"unexpected parameter format",
InvalidParams,
None,
))?;
let mut json: serde_json::Value = serde_json::from_str(params.as_str().unwrap())?;
let arr = json
.as_array_mut()
.ok_or(InvalidParams(NotAnArray))?;
if arr.len() != 1 {
return Err(rpc_error(
&format!("Expected 1 parameter, got {}", arr.len()),
InvalidParams,
None,
));
return Err(InvalidParams(WrongParamCount(arr.len())).into());
}
let item = arr
.pop()
.ok_or(rpc_error("Missing parameter", InvalidParams, None))?;
let str = item.as_str().ok_or(rpc_error(
"Expected hex encoded string",
InvalidParams,
None,
))?;
.ok_or(InvalidParams(MissingParam))?; // should be impossible
let str = item
.as_str()
.ok_or(InvalidParams(NotHexEncoded))?;
let bytes = hex::decode(str)?;
let bytes = Bytes::from(bytes);

Ok(application::send_raw_transaction(bytes)?.encode_hex_with_prefix())
}

pub fn rpc_error<S: Serialize>(
message: &str,
error_code: JsonRpcErrorCode,
data: Option<S>,
) -> JsonRpcError<S> {
JsonRpcError {
code: error_code.code(),
message: message.to_string(),
data,
}
}
Loading