diff --git a/Cargo.lock b/Cargo.lock index a3d1cea9d..69b75886b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2568,7 +2568,9 @@ name = "reth-rpc" version = "0.1.0" dependencies = [ "async-trait", + "hex", "jsonrpsee", + "reth-interfaces", "reth-primitives", "reth-rpc-api", "reth-rpc-types", @@ -2585,6 +2587,7 @@ dependencies = [ "jsonrpsee", "reth-primitives", "reth-rpc-types", + "serde_json", ] [[package]] @@ -3027,9 +3030,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" dependencies = [ "itoa", "ryu", diff --git a/crates/interfaces/src/provider/block.rs b/crates/interfaces/src/provider/block.rs index 3a7d439c9..fa79f2f01 100644 --- a/crates/interfaces/src/provider/block.rs +++ b/crates/interfaces/src/provider/block.rs @@ -5,7 +5,7 @@ use reth_primitives::{ }; /// Client trait for fetching `Block` related data. -pub trait BlockProvider: Send + Sync { +pub trait BlockProvider: Send + Sync + 'static { /// Returns the current info for the chain. fn chain_info(&self) -> Result; diff --git a/crates/interfaces/src/provider/storage.rs b/crates/interfaces/src/provider/storage.rs index 6216546fb..848bf5ba2 100644 --- a/crates/interfaces/src/provider/storage.rs +++ b/crates/interfaces/src/provider/storage.rs @@ -2,7 +2,7 @@ use crate::Result; use reth_primitives::{rpc::BlockId, Address, H256, U256}; /// Provides access to storage data -pub trait StorageProvider { +pub trait StorageProvider: Send + Sync + 'static { /// Returns the value from a storage position at a given address and `BlockId` fn storage_at(&self, address: Address, index: U256, at: BlockId) -> Result>; } diff --git a/crates/net/rpc-api/Cargo.toml b/crates/net/rpc-api/Cargo.toml index 300eb51ec..5e011b025 100644 --- a/crates/net/rpc-api/Cargo.toml +++ b/crates/net/rpc-api/Cargo.toml @@ -16,6 +16,7 @@ reth-rpc-types = { path = "../rpc-types" } # misc jsonrpsee = { version = "0.15", features = ["server", "macros"] } +serde_json = "1.0" [features] -client = ["jsonrpsee/client", "jsonrpsee/async-client"] \ No newline at end of file +client = ["jsonrpsee/client", "jsonrpsee/async-client"] diff --git a/crates/net/rpc-api/src/eth.rs b/crates/net/rpc-api/src/eth.rs index c4096c76f..281f71a4c 100644 --- a/crates/net/rpc-api/src/eth.rs +++ b/crates/net/rpc-api/src/eth.rs @@ -1,7 +1,4 @@ -use jsonrpsee::{ - core::{RpcResult as Result, __reexports::serde_json}, - proc_macros::rpc, -}; +use jsonrpsee::{core::RpcResult as Result, proc_macros::rpc}; use reth_primitives::{ rpc::{transaction::eip2930::AccessListWithGasUsed, BlockId}, Address, BlockNumber, Bytes, H256, H64, U256, U64, @@ -11,18 +8,18 @@ use reth_rpc_types::{ Transaction, TransactionReceipt, TransactionRequest, Work, }; -/// Eth rpc interface. +/// Eth rpc interface: #[cfg_attr(not(feature = "client"), rpc(server))] #[cfg_attr(feature = "client", rpc(server, client))] #[async_trait] pub trait EthApi { - /// Returns protocol version encoded as a string (quotes are necessary). + /// Returns protocol version encoded as a string. #[method(name = "eth_protocolVersion")] - async fn protocol_version(&self) -> Result; + fn protocol_version(&self) -> Result; /// Returns an object with data about the sync status or false. #[method(name = "eth_syncing")] - async fn syncing(&self) -> Result; + fn syncing(&self) -> Result; /// Returns block author. #[method(name = "eth_coinbase")] @@ -32,9 +29,9 @@ pub trait EthApi { #[method(name = "eth_accounts")] async fn accounts(&self) -> Result>; - /// Returns highest block number. + /// Returns the best block number. #[method(name = "eth_blockNumber")] - async fn block_number(&self) -> Result; + fn block_number(&self) -> Result; /// Returns the chain ID used for transaction signing at the /// current best block. None is returned if not diff --git a/crates/net/rpc/Cargo.toml b/crates/net/rpc/Cargo.toml index 21dd3a792..d22a01d05 100644 --- a/crates/net/rpc/Cargo.toml +++ b/crates/net/rpc/Cargo.toml @@ -10,6 +10,7 @@ Reth RPC implementation """ [dependencies] # reth +reth-interfaces = { path = "../../interfaces" } reth-primitives = { path = "../../primitives" } reth-rpc-api = { path = "../rpc-api" } reth-rpc-types = { path = "../rpc-types" } @@ -23,3 +24,4 @@ async-trait = "0.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" +hex = "0.4" diff --git a/crates/net/rpc/src/eth.rs b/crates/net/rpc/src/eth/eth_server.rs similarity index 88% rename from crates/net/rpc/src/eth.rs rename to crates/net/rpc/src/eth/eth_server.rs index 8c883914a..8bfbd5d5c 100644 --- a/crates/net/rpc/src/eth.rs +++ b/crates/net/rpc/src/eth/eth_server.rs @@ -1,6 +1,9 @@ -//! Implementation for the `eth` namespace rpc request handler. +//! Implementation of the [`jsonrpsee`] generated [`reth_rpc_api::EthApiServer`] trait +//! implementation for handling RPC requests for he `eth_` namespace. +use crate::{eth::EthApi, result::ToRpcResult}; use jsonrpsee::core::RpcResult as Result; +use reth_interfaces::provider::{BlockProvider, StorageProvider}; use reth_primitives::{ rpc::{transaction::eip2930::AccessListWithGasUsed, BlockId}, Address, BlockNumber, Bytes, Transaction, H256, H64, U256, U64, @@ -13,25 +16,17 @@ use reth_rpc_types::{ use reth_transaction_pool::TransactionPool; use serde_json::Value; -/// `Eth` API implementation. -/// -/// This type serves as the handler for RPCs for the `eth` namespace. -#[derive(Debug, Clone)] -pub struct EthApi { - /// The transaction pool. - _pool: Pool, -} - #[async_trait::async_trait] -impl EthApiServer for EthApi +impl EthApiServer for EthApi where - Pool: TransactionPool, + Pool: TransactionPool + Clone, + Client: BlockProvider + StorageProvider, { - async fn protocol_version(&self) -> Result { - todo!() + fn protocol_version(&self) -> Result { + Ok(self.protocol_version()) } - async fn syncing(&self) -> Result { + fn syncing(&self) -> Result { todo!() } @@ -43,8 +38,8 @@ where todo!() } - async fn block_number(&self) -> Result { - todo!() + fn block_number(&self) -> Result { + self.block_number().with_message("Failed to read block number") } async fn chain_id(&self) -> Result> { diff --git a/crates/net/rpc/src/eth/mod.rs b/crates/net/rpc/src/eth/mod.rs new file mode 100644 index 000000000..b77d623dd --- /dev/null +++ b/crates/net/rpc/src/eth/mod.rs @@ -0,0 +1,64 @@ +//! Provides everything related to `eth_` namespace + +use reth_interfaces::{ + provider::{BlockProvider, StorageProvider}, + Result, +}; +use reth_primitives::{Transaction, U256, U64}; +use reth_transaction_pool::TransactionPool; +use std::sync::Arc; + +mod eth_server; + +/// `Eth` API implementation. +/// +/// This type provides the functionality for handling `eth_` related requests. +/// These are implemented two-fold: Core functionality is implemented as functions directly on this +/// type. Additionally, the required server implementations (e.g. [`reth_rpc_api::EthApiServer`]) +/// are implemented separately in submodules. The rpc handler implementation can then delegate to +/// the main impls. This way [`EthApi`] is not limited to [`jsonrpsee`] and can be used standalone +/// or in other network handlers (for example ipc). +#[derive(Debug, Clone)] +pub struct EthApi { + /// All nested fields bundled together. + inner: Arc>, +} + +impl EthApi +where + Pool: TransactionPool + Clone, + Client: BlockProvider + StorageProvider, +{ + /// Creates a new, shareable instance. + pub fn new(client: Arc, pool: Pool) -> Self { + let inner = EthApiInner { client, pool }; + Self { inner: Arc::new(inner) } + } + + /// Returns the inner `Client` + fn client(&self) -> &Arc { + &self.inner.client + } + + /// Returns the current ethereum protocol version. + /// + /// Note: This returns an `U64`, since this should return as hex string. + pub fn protocol_version(&self) -> U64 { + 1u64.into() + } + + /// Returns the best block number + pub fn block_number(&self) -> Result { + Ok(self.client().chain_info()?.best_number.into()) + } +} + +/// Container type `EthApi` +#[derive(Debug)] +struct EthApiInner { + /// The transaction pool. + pool: Pool, + /// The client that can interact with the chain. + client: Arc, + // TODO needs network access to handle things like `eth_syncing` +} diff --git a/crates/net/rpc/src/lib.rs b/crates/net/rpc/src/lib.rs index 7137e1ca6..bcc3e2ce7 100644 --- a/crates/net/rpc/src/lib.rs +++ b/crates/net/rpc/src/lib.rs @@ -4,9 +4,15 @@ no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] +// TODO remove later +#![allow(dead_code)] //! Reth RPC implementation //! //! Provides the implementation of all RPC interfaces. -pub mod eth; +mod eth; + +pub use eth::EthApi; + +pub(crate) mod result; diff --git a/crates/net/rpc/src/result.rs b/crates/net/rpc/src/result.rs new file mode 100644 index 000000000..b41b6c0c2 --- /dev/null +++ b/crates/net/rpc/src/result.rs @@ -0,0 +1,134 @@ +//! Additional helpers for converting errors. + +use jsonrpsee::core::{Error as RpcError, RpcResult}; + +/// Helper trait to easily convert various `Result` types into [`RpcResult`] +pub(crate) trait ToRpcResult { + /// Converts this type into an [`RpcResult`] + fn map_rpc_err<'a, F, M>(self, op: F) -> RpcResult + where + F: FnOnce(Err) -> (i32, M, Option<&'a [u8]>), + M: Into; + + /// Converts this type into an [`RpcResult`] with the + /// [`jsonrpsee::types::error::INTERNAL_ERROR_CODE` and the given message. + fn map_internal_err(self, op: F) -> RpcResult + where + F: FnOnce(Err) -> M, + M: Into; + + /// Converts this type into an [`RpcResult`] with the + /// [`jsonrpsee::types::error::INTERNAL_ERROR_CODE`] and given message and data. + fn map_internal_err_with_data<'a, F, M>(self, op: F) -> RpcResult + where + F: FnOnce(Err) -> (M, &'a [u8]), + M: Into; + + /// Adds a message to the error variant and returns an internal Error. + /// + /// This is shorthand for `Self::map_internal_err(|err| format!("{msg}: {err}"))`. + fn with_message(self, msg: &str) -> RpcResult; +} + +impl ToRpcResult for reth_interfaces::Result { + #[inline] + fn map_rpc_err<'a, F, M>(self, op: F) -> RpcResult + where + F: FnOnce(reth_interfaces::Error) -> (i32, M, Option<&'a [u8]>), + M: Into, + { + match self { + Ok(t) => Ok(t), + Err(err) => { + let (code, msg, data) = op(err); + Err(rpc_err(code, msg, data)) + } + } + } + + #[inline] + fn map_internal_err<'a, F, M>(self, op: F) -> RpcResult + where + F: FnOnce(reth_interfaces::Error) -> M, + M: Into, + { + match self { + Ok(t) => Ok(t), + Err(err) => Err(internal_rpc_err(op(err))), + } + } + + #[inline] + fn map_internal_err_with_data<'a, F, M>(self, op: F) -> RpcResult + where + F: FnOnce(reth_interfaces::Error) -> (M, &'a [u8]), + M: Into, + { + match self { + Ok(t) => Ok(t), + Err(err) => { + let (msg, data) = op(err); + Err(internal_rpc_err_with_data(msg, data)) + } + } + } + + #[inline] + fn with_message(self, msg: &str) -> RpcResult { + match self { + Ok(t) => Ok(t), + Err(err) => { + let msg = format!("{}: {:?}", msg, err); + Err(internal_rpc_err(msg)) + } + } + } +} + +/// Constructs an internal JSON-RPC error. +pub(crate) fn internal_rpc_err(msg: impl Into) -> jsonrpsee::core::Error { + rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None) +} + +/// Constructs an internal JSON-RPC error with data +pub(crate) fn internal_rpc_err_with_data( + msg: impl Into, + data: &[u8], +) -> jsonrpsee::core::Error { + rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, Some(data)) +} + +/// Constructs a JSON-RPC error, consisting of `code`, `message` and optional `data`. +pub(crate) fn rpc_err(code: i32, msg: impl Into, data: Option<&[u8]>) -> RpcError { + RpcError::Call(jsonrpsee::types::error::CallError::Custom( + jsonrpsee::types::error::ErrorObject::owned( + code, + msg.into(), + data.map(|data| { + jsonrpsee::core::to_json_raw_value(&format!("0x{}", hex::encode(data))) + .expect("serializing String does fail") + }), + ), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_rpc_result>() {} + + fn to_reth_err(o: Ok) -> reth_interfaces::Result { + Ok(o) + } + + #[test] + fn can_convert_rpc() { + assert_rpc_result::<(), reth_interfaces::Error, reth_interfaces::Result<()>>(); + let res = to_reth_err(100); + + let rpc_res = res.map_internal_err(|_| "This is a message"); + let val = rpc_res.unwrap(); + assert_eq!(val, 100); + } +}