feat(rpc): more rpc scaffolding (#99)

* feat(rpc): more rpc scaffolding

* use Into<String>

* rm unused error
This commit is contained in:
Matthias Seitz
2022-10-19 22:12:53 +02:00
committed by GitHub
parent 630baf5d70
commit 4298e3aa87
10 changed files with 235 additions and 33 deletions

7
Cargo.lock generated
View File

@ -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",

View File

@ -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<ChainInfo>;

View File

@ -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<Option<H256>>;
}

View File

@ -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"]
client = ["jsonrpsee/client", "jsonrpsee/async-client"]

View File

@ -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: <https://ethereum.github.io/execution-apis/api-documentation/>
#[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<u64>;
fn protocol_version(&self) -> Result<U64>;
/// Returns an object with data about the sync status or false.
#[method(name = "eth_syncing")]
async fn syncing(&self) -> Result<SyncStatus>;
fn syncing(&self) -> Result<SyncStatus>;
/// Returns block author.
#[method(name = "eth_coinbase")]
@ -32,9 +29,9 @@ pub trait EthApi {
#[method(name = "eth_accounts")]
async fn accounts(&self) -> Result<Vec<Address>>;
/// Returns highest block number.
/// Returns the best block number.
#[method(name = "eth_blockNumber")]
async fn block_number(&self) -> Result<U256>;
fn block_number(&self) -> Result<U256>;
/// Returns the chain ID used for transaction signing at the
/// current best block. None is returned if not

View File

@ -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"

View File

@ -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<Pool> {
/// The transaction pool.
_pool: Pool,
}
#[async_trait::async_trait]
impl<Pool> EthApiServer for EthApi<Pool>
impl<Pool, Client> EthApiServer for EthApi<Pool, Client>
where
Pool: TransactionPool<Transaction = Transaction>,
Pool: TransactionPool<Transaction = Transaction> + Clone,
Client: BlockProvider + StorageProvider,
{
async fn protocol_version(&self) -> Result<u64> {
todo!()
fn protocol_version(&self) -> Result<U64> {
Ok(self.protocol_version())
}
async fn syncing(&self) -> Result<SyncStatus> {
fn syncing(&self) -> Result<SyncStatus> {
todo!()
}
@ -43,8 +38,8 @@ where
todo!()
}
async fn block_number(&self) -> Result<U256> {
todo!()
fn block_number(&self) -> Result<U256> {
self.block_number().with_message("Failed to read block number")
}
async fn chain_id(&self) -> Result<Option<U64>> {

View File

@ -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<Pool, Client> {
/// All nested fields bundled together.
inner: Arc<EthApiInner<Pool, Client>>,
}
impl<Pool, Client> EthApi<Pool, Client>
where
Pool: TransactionPool<Transaction = Transaction> + Clone,
Client: BlockProvider + StorageProvider,
{
/// Creates a new, shareable instance.
pub fn new(client: Arc<Client>, pool: Pool) -> Self {
let inner = EthApiInner { client, pool };
Self { inner: Arc::new(inner) }
}
/// Returns the inner `Client`
fn client(&self) -> &Arc<Client> {
&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<U256> {
Ok(self.client().chain_info()?.best_number.into())
}
}
/// Container type `EthApi`
#[derive(Debug)]
struct EthApiInner<Pool, Client> {
/// The transaction pool.
pool: Pool,
/// The client that can interact with the chain.
client: Arc<Client>,
// TODO needs network access to handle things like `eth_syncing`
}

View File

@ -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;

View File

@ -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<Ok, Err> {
/// Converts this type into an [`RpcResult`]
fn map_rpc_err<'a, F, M>(self, op: F) -> RpcResult<Ok>
where
F: FnOnce(Err) -> (i32, M, Option<&'a [u8]>),
M: Into<String>;
/// Converts this type into an [`RpcResult`] with the
/// [`jsonrpsee::types::error::INTERNAL_ERROR_CODE` and the given message.
fn map_internal_err<F, M>(self, op: F) -> RpcResult<Ok>
where
F: FnOnce(Err) -> M,
M: Into<String>;
/// 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<Ok>
where
F: FnOnce(Err) -> (M, &'a [u8]),
M: Into<String>;
/// 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<Ok>;
}
impl<Ok> ToRpcResult<Ok, reth_interfaces::Error> for reth_interfaces::Result<Ok> {
#[inline]
fn map_rpc_err<'a, F, M>(self, op: F) -> RpcResult<Ok>
where
F: FnOnce(reth_interfaces::Error) -> (i32, M, Option<&'a [u8]>),
M: Into<String>,
{
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<Ok>
where
F: FnOnce(reth_interfaces::Error) -> M,
M: Into<String>,
{
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<Ok>
where
F: FnOnce(reth_interfaces::Error) -> (M, &'a [u8]),
M: Into<String>,
{
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<Ok> {
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<String>) -> 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<String>,
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<String>, 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<Ok, Err, T: ToRpcResult<Ok, Err>>() {}
fn to_reth_err<Ok>(o: Ok) -> reth_interfaces::Result<Ok> {
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);
}
}