From a0ff24b691aee859db10a11b48df56100fd5e1e2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 14 Mar 2023 13:55:35 +0100 Subject: [PATCH] feat(rpc): add eth transactions trait (#1751) --- .../rpc/rpc-types/src/eth/transaction/mod.rs | 4 +- crates/rpc/rpc/src/eth/api/mod.rs | 25 ++-- crates/rpc/rpc/src/eth/api/server.rs | 7 +- crates/rpc/rpc/src/eth/api/transactions.rs | 110 ++++++++++++++---- crates/rpc/rpc/src/eth/error.rs | 8 +- crates/rpc/rpc/src/eth/mod.rs | 4 +- 6 files changed, 107 insertions(+), 51 deletions(-) diff --git a/crates/rpc/rpc-types/src/eth/transaction/mod.rs b/crates/rpc/rpc-types/src/eth/transaction/mod.rs index f6a2f42133..a5a169bf9b 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/mod.rs @@ -87,9 +87,9 @@ impl Transaction { tx } - /// Create a new rpc transaction result for a pending signed transaction, setting block + /// Create a new rpc transaction result for a _pending_ signed transaction, setting block /// environment related fields to `None`. - pub(crate) fn from_recovered(tx: TransactionSignedEcRecovered) -> Self { + pub fn from_recovered(tx: TransactionSignedEcRecovered) -> Self { let signer = tx.signer(); let signed_tx = tx.into_signed(); diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 97911adf5c..826ae34c90 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -3,29 +3,25 @@ //! The entire implementation of the namespace is quite large, hence it is divided across several //! files. -use crate::eth::signer::EthSigner; +use crate::eth::{cache::EthStateCache, error::EthResult, signer::EthSigner}; use async_trait::async_trait; use reth_interfaces::Result; use reth_network_api::NetworkInfo; -use reth_primitives::{ - Address, BlockId, BlockNumberOrTag, ChainInfo, TransactionSigned, H256, U64, -}; +use reth_primitives::{Address, BlockId, BlockNumberOrTag, ChainInfo, H256, U64}; use reth_provider::{ - BlockProvider, EvmEnvProvider, StateProvider as StateProviderTrait, StateProviderFactory, + providers::ChainState, BlockProvider, EvmEnvProvider, StateProvider as StateProviderTrait, + StateProviderFactory, }; -use std::{num::NonZeroUsize, ops::Deref}; - -use crate::eth::{cache::EthStateCache, error::EthResult}; -use reth_provider::providers::ChainState; use reth_rpc_types::FeeHistoryCache; use reth_transaction_pool::TransactionPool; -use std::sync::Arc; +use std::{num::NonZeroUsize, ops::Deref, sync::Arc}; mod block; mod call; mod server; mod state; mod transactions; +pub use transactions::{EthTransactions, TransactionSource}; /// Cache limit of block-level fee history for `eth_feeHistory` RPC method. const FEE_HISTORY_CACHE_LIMIT: usize = 2048; @@ -34,7 +30,7 @@ const FEE_HISTORY_CACHE_LIMIT: usize = 2048; /// /// Defines core functionality of the `eth` API implementation. #[async_trait] -pub trait EthApiSpec: Send + Sync { +pub trait EthApiSpec: EthTransactions + Send + Sync { /// Returns the current ethereum protocol version. async fn protocol_version(&self) -> Result; @@ -46,9 +42,6 @@ pub trait EthApiSpec: Send + Sync { /// Returns a list of addresses owned by client. fn accounts(&self) -> Vec
; - - /// Returns the transaction by hash - async fn transaction_by_hash(&self, hash: H256) -> Result>; } /// `Eth` API implementation. @@ -243,10 +236,6 @@ where fn accounts(&self) -> Vec
{ self.inner.signers.iter().flat_map(|s| s.accounts()).collect() } - - async fn transaction_by_hash(&self, hash: H256) -> Result> { - self.client().transaction_by_hash(hash) - } } /// Container type `EthApi` diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index f59f25f5d9..9a6f4f7d5e 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -3,7 +3,10 @@ use super::EthApiSpec; use crate::{ - eth::{api::EthApi, error::EthApiError}, + eth::{ + api::{EthApi, EthTransactions}, + error::EthApiError, + }, result::{internal_rpc_err, ToRpcResult}, }; use jsonrpsee::core::RpcResult as Result; @@ -118,7 +121,7 @@ where /// Handler for: `eth_getTransactionByHash` async fn transaction_by_hash(&self, hash: H256) -> Result> { - Ok(EthApi::transaction_by_hash(self, hash).await?) + Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into)) } /// Handler for: `eth_getTransactionByBlockHashAndIndex` diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index bece1772f1..416a97c1d6 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -3,12 +3,62 @@ use crate::{ eth::error::{EthApiError, EthResult}, EthApi, }; -use reth_primitives::{BlockId, Bytes, FromRecoveredTransaction, TransactionSigned, H256}; -use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory}; +use async_trait::async_trait; +use reth_primitives::{ + BlockId, Bytes, FromRecoveredTransaction, IntoRecoveredTransaction, TransactionSigned, + TransactionSignedEcRecovered, H256, U256, +}; +use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory, TransactionsProvider}; use reth_rlp::Decodable; use reth_rpc_types::{Index, Transaction, TransactionRequest}; use reth_transaction_pool::{TransactionOrigin, TransactionPool}; +/// Commonly used transaction related functions for the [EthApi] type in the `eth_` namespace +#[async_trait::async_trait] +pub trait EthTransactions: Send + Sync { + /// Returns the transaction by hash. + /// + /// Checks the pool and state. + /// + /// Returns `Ok(None)` if no matching transaction was found. + async fn transaction_by_hash(&self, hash: H256) -> EthResult>; +} + +#[async_trait] +impl EthTransactions for EthApi +where + Pool: TransactionPool + Clone + 'static, + Client: TransactionsProvider + 'static, + Network: Send + Sync + 'static, +{ + async fn transaction_by_hash(&self, hash: H256) -> EthResult> { + if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction()) + { + return Ok(Some(TransactionSource::Pool(tx))) + } + + match self.client().transaction_by_hash(hash)? { + None => Ok(None), + Some(tx) => { + let transaction = + tx.into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; + + let tx = TransactionSource::Database { + transaction, + // TODO: this is just stubbed out for now still need to fully implement tx => + // block + index: 0, + block_hash: Default::default(), + block_number: 0, + }; + Ok(Some(tx)) + } + } + } +} + +// === impl EthApi === + impl EthApi where Pool: TransactionPool + 'static, @@ -19,28 +69,6 @@ where unimplemented!() } - /// Finds a given [Transaction] by its hash. - /// - /// Returns `Ok(None)` if no matching transaction was found. - pub(crate) async fn transaction_by_hash(&self, hash: H256) -> EthResult> { - match self.client().transaction_by_hash(hash)? { - None => Ok(None), - Some(tx) => { - let tx = tx.into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; - - let tx = Transaction::from_recovered_with_block_context( - tx, - // TODO: this is just stubbed out for now still need to fully implement tx => - // block - H256::default(), - u64::default(), - Index::default().into(), - ); - Ok(Some(tx)) - } - } - } - /// Get Transaction by [BlockId] and the index of the transaction within that Block. /// /// Returns `Ok(None)` if the block does not exist, or the block as fewer transactions @@ -94,6 +122,40 @@ where } } +/// Represents from where a transaction was fetched. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TransactionSource { + /// Transaction exists in the pool (Pending) + Pool(TransactionSignedEcRecovered), + /// Transaction already executed + Database { + /// Transaction fetched via provider + transaction: TransactionSignedEcRecovered, + /// Index of the transaction in the block + index: usize, + /// Hash of the block. + block_hash: H256, + /// Number of the block. + block_number: u64, + }, +} + +impl From for Transaction { + fn from(value: TransactionSource) -> Self { + match value { + TransactionSource::Pool(tx) => Transaction::from_recovered(tx), + TransactionSource::Database { transaction, index, block_hash, block_number } => { + Transaction::from_recovered_with_block_context( + transaction, + block_hash, + block_number, + U256::from(index), + ) + } + } + } +} + #[cfg(test)] mod tests { use crate::eth::cache::EthStateCache; diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index 2d5466417c..3aa76da580 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -8,12 +8,12 @@ use reth_transaction_pool::error::{InvalidPoolTransactionError, PoolError}; use revm::primitives::{EVMError, Halt}; /// Result alias -pub(crate) type EthResult = Result; +pub type EthResult = Result; /// Errors that can occur when interacting with the `eth_` namespace #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] -pub(crate) enum EthApiError { +pub enum EthApiError { /// When a raw transaction is empty #[error("Empty transaction data")] EmptyRawTransactionData, @@ -161,6 +161,7 @@ pub enum InvalidTransactionError { /// Unspecific evm halt error #[error("EVM error {0:?}")] EvmHalt(Halt), + /// Invalid chain id set for the transaction. #[error("Invalid chain id")] InvalidChainId, } @@ -268,7 +269,8 @@ impl std::error::Error for RevertError {} /// A helper error type that's mainly used to mirror `geth` Txpool's error messages #[derive(Debug, thiserror::Error)] -pub(crate) enum RpcPoolError { +#[allow(missing_docs)] +pub enum RpcPoolError { #[error("already known")] AlreadyKnown, #[error("invalid sender")] diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs index da465c50a5..7dee1a1257 100644 --- a/crates/rpc/rpc/src/eth/mod.rs +++ b/crates/rpc/rpc/src/eth/mod.rs @@ -2,14 +2,14 @@ mod api; pub mod cache; -pub(crate) mod error; +pub mod error; mod filter; mod id_provider; mod pubsub; pub(crate) mod revm_utils; mod signer; -pub use api::{EthApi, EthApiSpec}; +pub use api::{EthApi, EthApiSpec, EthTransactions, TransactionSource}; pub use filter::EthFilter; pub use id_provider::EthSubscriptionIdProvider; pub use pubsub::EthPubSub;