chore(rpc): reth-eth-api crate (#8887)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Emilia Hane
2024-06-27 13:37:52 +02:00
committed by GitHub
parent acc07537bc
commit 933a1dea39
109 changed files with 5433 additions and 4464 deletions

72
Cargo.lock generated
View File

@ -7602,8 +7602,8 @@ dependencies = [
"reth-primitives", "reth-primitives",
"reth-provider", "reth-provider",
"reth-prune-types", "reth-prune-types",
"reth-rpc",
"reth-rpc-api", "reth-rpc-api",
"reth-rpc-eth-api",
"reth-rpc-server-types", "reth-rpc-server-types",
"reth-rpc-types", "reth-rpc-types",
"reth-rpc-types-compat", "reth-rpc-types-compat",
@ -7996,33 +7996,22 @@ dependencies = [
name = "reth-rpc" name = "reth-rpc"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"alloy-dyn-abi",
"alloy-genesis", "alloy-genesis",
"alloy-primitives", "alloy-primitives",
"alloy-rlp", "alloy-rlp",
"alloy-sol-types",
"assert_matches", "assert_matches",
"async-trait", "async-trait",
"derive_more",
"dyn-clone",
"futures", "futures",
"http 1.1.0", "http 1.1.0",
"http-body", "http-body",
"hyper", "hyper",
"jsonrpsee", "jsonrpsee",
"jsonwebtoken", "jsonwebtoken",
"metrics",
"parking_lot 0.12.3",
"pin-project", "pin-project",
"rand 0.8.5",
"reth-chainspec", "reth-chainspec",
"reth-consensus-common", "reth-consensus-common",
"reth-errors", "reth-errors",
"reth-evm",
"reth-evm-ethereum", "reth-evm-ethereum",
"reth-evm-optimism",
"reth-execution-types",
"reth-metrics",
"reth-network-api", "reth-network-api",
"reth-network-peers", "reth-network-peers",
"reth-primitives", "reth-primitives",
@ -8030,7 +8019,7 @@ dependencies = [
"reth-revm", "reth-revm",
"reth-rpc-api", "reth-rpc-api",
"reth-rpc-engine-api", "reth-rpc-engine-api",
"reth-rpc-server-types", "reth-rpc-eth-api",
"reth-rpc-types", "reth-rpc-types",
"reth-rpc-types-compat", "reth-rpc-types-compat",
"reth-tasks", "reth-tasks",
@ -8039,14 +8028,8 @@ dependencies = [
"revm", "revm",
"revm-inspectors", "revm-inspectors",
"revm-primitives", "revm-primitives",
"schnellru",
"secp256k1",
"serde",
"serde_json",
"tempfile", "tempfile",
"thiserror",
"tokio", "tokio",
"tokio-stream",
"tower", "tower",
"tracing", "tracing",
"tracing-futures", "tracing-futures",
@ -8056,11 +8039,11 @@ dependencies = [
name = "reth-rpc-api" name = "reth-rpc-api"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"alloy-dyn-abi",
"jsonrpsee", "jsonrpsee",
"reth-engine-primitives", "reth-engine-primitives",
"reth-network-peers", "reth-network-peers",
"reth-primitives", "reth-primitives",
"reth-rpc-eth-api",
"reth-rpc-types", "reth-rpc-types",
"serde", "serde",
"serde_json", "serde_json",
@ -8074,6 +8057,7 @@ dependencies = [
"jsonrpsee", "jsonrpsee",
"reth-primitives", "reth-primitives",
"reth-rpc-api", "reth-rpc-api",
"reth-rpc-eth-api",
"reth-rpc-types", "reth-rpc-types",
"serde_json", "serde_json",
"similar-asserts", "similar-asserts",
@ -8155,6 +8139,53 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "reth-rpc-eth-api"
version = "1.0.0"
dependencies = [
"alloy-dyn-abi",
"alloy-sol-types",
"async-trait",
"auto_impl",
"derive_more",
"dyn-clone",
"futures",
"jsonrpsee",
"jsonrpsee-types",
"metrics",
"parking_lot 0.12.3",
"rand 0.8.5",
"reth-chainspec",
"reth-errors",
"reth-evm",
"reth-evm-ethereum",
"reth-evm-optimism",
"reth-execution-types",
"reth-metrics",
"reth-network-api",
"reth-primitives",
"reth-provider",
"reth-revm",
"reth-rpc-server-types",
"reth-rpc-types",
"reth-rpc-types-compat",
"reth-tasks",
"reth-testing-utils",
"reth-transaction-pool",
"reth-trie",
"revm",
"revm-inspectors",
"revm-primitives",
"schnellru",
"secp256k1",
"serde",
"serde_json",
"thiserror",
"tokio",
"tokio-stream",
"tracing",
]
[[package]] [[package]]
name = "reth-rpc-layer" name = "reth-rpc-layer"
version = "1.0.0" version = "1.0.0"
@ -8372,6 +8403,7 @@ dependencies = [
name = "reth-tasks" name = "reth-tasks"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"auto_impl",
"dyn-clone", "dyn-clone",
"futures-util", "futures-util",
"metrics", "metrics",

View File

@ -80,6 +80,7 @@ members = [
"crates/rpc/rpc-api/", "crates/rpc/rpc-api/",
"crates/rpc/rpc-builder/", "crates/rpc/rpc-builder/",
"crates/rpc/rpc-engine-api/", "crates/rpc/rpc-engine-api/",
"crates/rpc/rpc-eth-api/",
"crates/rpc/rpc-layer", "crates/rpc/rpc-layer",
"crates/rpc/rpc-testing-util/", "crates/rpc/rpc-testing-util/",
"crates/rpc/rpc-types-compat/", "crates/rpc/rpc-types-compat/",
@ -339,6 +340,7 @@ reth-rpc-api = { path = "crates/rpc/rpc-api" }
reth-rpc-api-testing-util = { path = "crates/rpc/rpc-testing-util" } reth-rpc-api-testing-util = { path = "crates/rpc/rpc-testing-util" }
reth-rpc-builder = { path = "crates/rpc/rpc-builder" } reth-rpc-builder = { path = "crates/rpc/rpc-builder" }
reth-rpc-engine-api = { path = "crates/rpc/rpc-engine-api" } reth-rpc-engine-api = { path = "crates/rpc/rpc-engine-api" }
reth-rpc-eth-api = { path = "crates/rpc/rpc-eth-api" }
reth-rpc-layer = { path = "crates/rpc/rpc-layer" } reth-rpc-layer = { path = "crates/rpc/rpc-layer" }
reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" }
reth-rpc-types = { path = "crates/rpc/rpc-types" } reth-rpc-types = { path = "crates/rpc/rpc-types" }

View File

@ -2,7 +2,7 @@ use alloy_consensus::TxEnvelope;
use alloy_network::eip2718::Decodable2718; use alloy_network::eip2718::Decodable2718;
use reth::{api::FullNodeComponents, builder::rpc::RpcRegistry, rpc::api::DebugApiServer}; use reth::{api::FullNodeComponents, builder::rpc::RpcRegistry, rpc::api::DebugApiServer};
use reth_primitives::{Bytes, B256}; use reth_primitives::{Bytes, B256};
use reth_rpc::eth::{error::EthResult, EthTransactions}; use reth_rpc::eth::{servers::EthTransactions, EthResult};
pub struct RpcTestContext<Node: FullNodeComponents> { pub struct RpcTestContext<Node: FullNodeComponents> {
pub inner: RpcRegistry<Node>, pub inner: RpcRegistry<Node>,

View File

@ -179,7 +179,7 @@ where
.into()) .into())
} }
EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender); self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender);
// Execute transaction. // Execute transaction.
let ResultAndState { result, state } = evm.transact().map_err(move |err| { let ResultAndState { result, state } = evm.transact().map_err(move |err| {

View File

@ -12,13 +12,7 @@
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
extern crate alloc; extern crate alloc;
use reth_chainspec::ChainSpec;
use reth_evm::{ConfigureEvm, ConfigureEvmEnv}; use reth_evm::{ConfigureEvm, ConfigureEvmEnv};
use reth_primitives::{
revm::{config::revm_spec, env::fill_tx_env},
revm_primitives::{AnalysisKind, CfgEnvWithHandlerCfg, TxEnv},
Address, Head, Header, TransactionSigned, U256,
};
use reth_revm::{Database, EvmBuilder}; use reth_revm::{Database, EvmBuilder};
pub mod execute; pub mod execute;
@ -34,34 +28,7 @@ pub mod eip6110;
#[non_exhaustive] #[non_exhaustive]
pub struct EthEvmConfig; pub struct EthEvmConfig;
impl ConfigureEvmEnv for EthEvmConfig { impl ConfigureEvmEnv for EthEvmConfig {}
fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
fill_tx_env(tx_env, transaction, sender)
}
fn fill_cfg_env(
cfg_env: &mut CfgEnvWithHandlerCfg,
chain_spec: &ChainSpec,
header: &Header,
total_difficulty: U256,
) {
let spec_id = revm_spec(
chain_spec,
Head {
number: header.number,
timestamp: header.timestamp,
difficulty: header.difficulty,
total_difficulty,
hash: Default::default(),
},
);
cfg_env.chain_id = chain_spec.chain().id();
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
cfg_env.handler_cfg.spec_id = spec_id;
}
}
impl ConfigureEvm for EthEvmConfig { impl ConfigureEvm for EthEvmConfig {
type DefaultExternalContext<'a> = (); type DefaultExternalContext<'a> = ();
@ -77,7 +44,12 @@ impl ConfigureEvm for EthEvmConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use reth_primitives::revm_primitives::{BlockEnv, CfgEnv, SpecId}; use reth_chainspec::ChainSpec;
use reth_primitives::{
revm_primitives::{BlockEnv, CfgEnv, SpecId},
Header, U256,
};
use revm_primitives::CfgEnvWithHandlerCfg;
#[test] #[test]
#[ignore] #[ignore]

View File

@ -2,7 +2,7 @@ use crate::utils::EthNode;
use alloy_genesis::Genesis; use alloy_genesis::Genesis;
use alloy_primitives::{b256, hex}; use alloy_primitives::{b256, hex};
use futures::StreamExt; use futures::StreamExt;
use reth::rpc::eth::EthTransactions; use reth::rpc::eth::servers::EthTransactions;
use reth_chainspec::ChainSpec; use reth_chainspec::ChainSpec;
use reth_e2e_test_utils::setup; use reth_e2e_test_utils::setup;
use reth_provider::CanonStateSubscriptions; use reth_provider::CanonStateSubscriptions;

View File

@ -13,9 +13,17 @@
extern crate alloc; extern crate alloc;
use reth_chainspec::ChainSpec; use reth_chainspec::ChainSpec;
use reth_primitives::{revm::env::fill_block_env, Address, Header, TransactionSigned, U256}; use reth_primitives::{
revm::{
config::revm_spec,
env::{fill_block_env, fill_tx_env},
},
Address, Head, Header, TransactionSigned, U256,
};
use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector}; use revm::{inspector_handle_register, Database, Evm, EvmBuilder, GetInspector};
use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}; use revm_primitives::{
AnalysisKind, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv,
};
pub mod either; pub mod either;
pub mod execute; pub mod execute;
@ -27,6 +35,7 @@ pub mod provider;
pub mod test_utils; pub mod test_utils;
/// Trait for configuring the EVM for executing full blocks. /// Trait for configuring the EVM for executing full blocks.
#[auto_impl::auto_impl(&, Arc)]
pub trait ConfigureEvm: ConfigureEvmEnv { pub trait ConfigureEvm: ConfigureEvmEnv {
/// Associated type for the default external context that should be configured for the EVM. /// Associated type for the default external context that should be configured for the EVM.
type DefaultExternalContext<'a>; type DefaultExternalContext<'a>;
@ -98,9 +107,14 @@ pub trait ConfigureEvm: ConfigureEvmEnv {
/// This represents the set of methods used to configure the EVM's environment before block /// This represents the set of methods used to configure the EVM's environment before block
/// execution. /// execution.
///
/// Default trait method implementation is done w.r.t. L1.
#[auto_impl::auto_impl(&, Arc)]
pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static { pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
/// Fill transaction environment from a [`TransactionSigned`] and the given sender address. /// Fill transaction environment from a [`TransactionSigned`] and the given sender address.
fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address); fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
fill_tx_env(tx_env, transaction, sender)
}
/// Fill [`CfgEnvWithHandlerCfg`] fields according to the chain spec and given header /// Fill [`CfgEnvWithHandlerCfg`] fields according to the chain spec and given header
fn fill_cfg_env( fn fill_cfg_env(
@ -108,7 +122,23 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
chain_spec: &ChainSpec, chain_spec: &ChainSpec,
header: &Header, header: &Header,
total_difficulty: U256, total_difficulty: U256,
); ) {
let spec_id = revm_spec(
chain_spec,
Head {
number: header.number,
timestamp: header.timestamp,
difficulty: header.difficulty,
total_difficulty,
hash: Default::default(),
},
);
cfg_env.chain_id = chain_spec.chain().id();
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
cfg_env.handler_cfg.spec_id = spec_id;
}
/// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and /// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and
/// [`fill_block_env`]. /// [`fill_block_env`].

View File

@ -6,13 +6,13 @@ use reth_storage_errors::provider::ProviderResult;
use revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId}; use revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId};
/// A provider type that knows chain specific information required to configure a /// A provider type that knows chain specific information required to configure a
/// [CfgEnvWithHandlerCfg]. /// [`CfgEnvWithHandlerCfg`].
/// ///
/// This type is mainly used to provide required data to configure the EVM environment that is /// This type is mainly used to provide required data to configure the EVM environment that is
/// usually stored on disk. /// usually stored on disk.
#[auto_impl::auto_impl(&, Arc)] #[auto_impl::auto_impl(&, Arc)]
pub trait EvmEnvProvider: Send + Sync { pub trait EvmEnvProvider: Send + Sync {
/// Fills the [CfgEnvWithHandlerCfg] and [BlockEnv] fields with values specific to the given /// Fills the [`CfgEnvWithHandlerCfg`] and [BlockEnv] fields with values specific to the given
/// [BlockHashOrNumber]. /// [BlockHashOrNumber].
fn fill_env_at<EvmConfig>( fn fill_env_at<EvmConfig>(
&self, &self,
@ -24,7 +24,7 @@ pub trait EvmEnvProvider: Send + Sync {
where where
EvmConfig: ConfigureEvmEnv; EvmConfig: ConfigureEvmEnv;
/// Fills the default [CfgEnvWithHandlerCfg] and [BlockEnv] fields with values specific to the /// Fills the default [`CfgEnvWithHandlerCfg`] and [BlockEnv] fields with values specific to the
/// given [Header]. /// given [Header].
fn env_with_header<EvmConfig>( fn env_with_header<EvmConfig>(
&self, &self,
@ -40,7 +40,7 @@ pub trait EvmEnvProvider: Send + Sync {
Ok((cfg, block_env)) Ok((cfg, block_env))
} }
/// Fills the [CfgEnvWithHandlerCfg] and [BlockEnv] fields with values specific to the given /// Fills the [`CfgEnvWithHandlerCfg`] and [BlockEnv] fields with values specific to the given
/// [Header]. /// [Header].
fn fill_env_with_header<EvmConfig>( fn fill_env_with_header<EvmConfig>(
&self, &self,
@ -66,7 +66,7 @@ pub trait EvmEnvProvider: Send + Sync {
header: &Header, header: &Header,
) -> ProviderResult<()>; ) -> ProviderResult<()>;
/// Fills the [CfgEnvWithHandlerCfg] fields with values specific to the given /// Fills the [`CfgEnvWithHandlerCfg`] fields with values specific to the given
/// [BlockHashOrNumber]. /// [BlockHashOrNumber].
fn fill_cfg_env_at<EvmConfig>( fn fill_cfg_env_at<EvmConfig>(
&self, &self,
@ -77,7 +77,7 @@ pub trait EvmEnvProvider: Send + Sync {
where where
EvmConfig: ConfigureEvmEnv; EvmConfig: ConfigureEvmEnv;
/// Fills the [CfgEnvWithHandlerCfg] fields with values specific to the given [Header]. /// Fills the [`CfgEnvWithHandlerCfg`] fields with values specific to the given [Header].
fn fill_cfg_env_with_header<EvmConfig>( fn fill_cfg_env_with_header<EvmConfig>(
&self, &self,
cfg: &mut CfgEnvWithHandlerCfg, cfg: &mut CfgEnvWithHandlerCfg,

View File

@ -21,11 +21,11 @@ reth-storage-errors.workspace = true
reth-provider.workspace = true reth-provider.workspace = true
reth-network = { workspace = true, features = ["serde"] } reth-network = { workspace = true, features = ["serde"] }
reth-network-p2p.workspace = true reth-network-p2p.workspace = true
reth-rpc.workspace = true
reth-rpc-server-types.workspace = true reth-rpc-server-types.workspace = true
reth-rpc-types.workspace = true reth-rpc-types.workspace = true
reth-rpc-types-compat.workspace = true reth-rpc-types-compat.workspace = true
reth-rpc-api = { workspace = true, features = ["client"] } reth-rpc-api = { workspace = true, features = ["client"] }
reth-rpc-eth-api = { workspace = true, features = ["client"] }
reth-transaction-pool.workspace = true reth-transaction-pool.workspace = true
reth-tracing.workspace = true reth-tracing.workspace = true
reth-config.workspace = true reth-config.workspace = true
@ -99,10 +99,10 @@ proptest.workspace = true
[features] [features]
optimism = [ optimism = [
"reth-primitives/optimism", "reth-primitives/optimism",
"reth-rpc/optimism",
"reth-provider/optimism", "reth-provider/optimism",
"reth-rpc-types-compat/optimism", "reth-rpc-types-compat/optimism",
"reth-beacon-consensus/optimism", "reth-beacon-consensus/optimism",
"reth-rpc-eth-api/optimism",
] ]
jemalloc = ["dep:tikv-jemalloc-ctl"] jemalloc = ["dep:tikv-jemalloc-ctl"]

View File

@ -1,6 +1,6 @@
use crate::primitives::U256; use crate::primitives::U256;
use clap::Args; use clap::Args;
use reth_rpc::eth::gas_oracle::GasPriceOracleConfig; use reth_rpc_eth_api::GasPriceOracleConfig;
use reth_rpc_server_types::constants::gas_oracle::{ use reth_rpc_server_types::constants::gas_oracle::{
DEFAULT_GAS_PRICE_BLOCKS, DEFAULT_GAS_PRICE_PERCENTILE, DEFAULT_IGNORE_GAS_PRICE, DEFAULT_GAS_PRICE_BLOCKS, DEFAULT_GAS_PRICE_PERCENTILE, DEFAULT_IGNORE_GAS_PRICE,
DEFAULT_MAX_GAS_PRICE, DEFAULT_MAX_GAS_PRICE,

View File

@ -10,7 +10,7 @@ use clap::{
Arg, Args, Command, Arg, Args, Command,
}; };
use rand::Rng; use rand::Rng;
use reth_rpc::eth::RPC_DEFAULT_GAS_CAP; use reth_rpc_eth_api::RPC_DEFAULT_GAS_CAP;
use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection};
use std::{ use std::{

View File

@ -38,12 +38,12 @@ pub mod rpc {
} }
/// Re-exported from `reth_rpc::eth`. /// Re-exported from `reth_rpc::eth`.
pub mod eth { pub mod eth {
pub use reth_rpc::eth::*; pub use reth_rpc_eth_api::*;
} }
/// Re-exported from `reth_rpc::rpc`. /// Re-exported from `reth_rpc::rpc`.
pub mod result { pub mod result {
pub use reth_rpc::result::*; pub use reth_rpc_eth_api::result::*;
} }
/// Re-exported from `reth_rpc::eth`. /// Re-exported from `reth_rpc::eth`.

View File

@ -189,8 +189,9 @@ impl<Node: FullNodeComponents> Clone for RpcRegistry<Node> {
/// [`AuthRpcModule`]. /// [`AuthRpcModule`].
/// ///
/// This can be used to access installed modules, or create commonly used handlers like /// This can be used to access installed modules, or create commonly used handlers like
/// [`reth_rpc::EthApi`], and ultimately merge additional rpc handler into the configured transport /// [`reth_rpc::eth::EthApi`], and ultimately merge additional rpc handler into the configured
/// modules [`TransportRpcModules`] as well as configured authenticated methods [`AuthRpcModule`]. /// transport modules [`TransportRpcModules`] as well as configured authenticated methods
/// [`AuthRpcModule`].
#[allow(missing_debug_implementations)] #[allow(missing_debug_implementations)]
pub struct RpcContext<'a, Node: FullNodeComponents> { pub struct RpcContext<'a, Node: FullNodeComponents> {
/// The node components. /// The node components.

View File

@ -177,7 +177,7 @@ where
.transpose() .transpose()
.map_err(|_| OptimismBlockExecutionError::AccountLoadFailed(*sender))?; .map_err(|_| OptimismBlockExecutionError::AccountLoadFailed(*sender))?;
EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender); self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender);
// Execute transaction. // Execute transaction.
let ResultAndState { result, state } = evm.transact().map_err(move |err| { let ResultAndState { result, state } = evm.transact().map_err(move |err| {

View File

@ -32,7 +32,7 @@ pub use error::OptimismBlockExecutionError;
pub struct OptimismEvmConfig; pub struct OptimismEvmConfig;
impl ConfigureEvmEnv for OptimismEvmConfig { impl ConfigureEvmEnv for OptimismEvmConfig {
fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) {
let mut buf = Vec::with_capacity(transaction.length_without_header()); let mut buf = Vec::with_capacity(transaction.length_without_header());
transaction.encode_enveloped(&mut buf); transaction.encode_enveloped(&mut buf);
fill_op_tx_env(tx_env, transaction, sender, buf.into()); fill_op_tx_env(tx_env, transaction, sender, buf.into());

View File

@ -4,7 +4,7 @@ use jsonrpsee::types::ErrorObject;
use reqwest::Client; use reqwest::Client;
use reth_rpc::eth::{ use reth_rpc::eth::{
error::{EthApiError, EthResult}, error::{EthApiError, EthResult},
traits::RawTransactionForwarder, servers::RawTransactionForwarder,
}; };
use reth_rpc_types::ToRpcError; use reth_rpc_types::ToRpcError;
use std::sync::{atomic::AtomicUsize, Arc}; use std::sync::{atomic::AtomicUsize, Arc};

View File

@ -15,11 +15,11 @@ workspace = true
# reth # reth
reth-primitives.workspace = true reth-primitives.workspace = true
reth-rpc-types.workspace = true reth-rpc-types.workspace = true
reth-rpc-eth-api.workspace = true
reth-engine-primitives.workspace = true reth-engine-primitives.workspace = true
reth-network-peers.workspace = true reth-network-peers.workspace = true
# misc # misc
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
jsonrpsee = { workspace = true, features = ["server", "macros"] } jsonrpsee = { workspace = true, features = ["server", "macros"] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
@ -27,4 +27,9 @@ serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true serde_json.workspace = true
[features] [features]
client = ["jsonrpsee/client", "jsonrpsee/async-client"] client = [
"jsonrpsee/client",
"jsonrpsee/async-client",
"reth-rpc-eth-api/client"
]
optimism = ["reth-rpc-eth-api/optimism"]

View File

@ -26,7 +26,7 @@ pub trait DebugApi {
#[method(name = "getRawTransaction")] #[method(name = "getRawTransaction")]
async fn raw_transaction(&self, hash: B256) -> RpcResult<Option<Bytes>>; async fn raw_transaction(&self, hash: B256) -> RpcResult<Option<Bytes>>;
/// Returns an array of EIP-2718 binary-encoded transactions for the given [BlockId]. /// Returns an array of EIP-2718 binary-encoded transactions for the given [`BlockId`].
#[method(name = "getRawTransactions")] #[method(name = "getRawTransactions")]
async fn raw_transactions(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>>; async fn raw_transactions(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>>;

View File

@ -16,12 +16,8 @@
mod admin; mod admin;
mod anvil; mod anvil;
mod bundle;
mod debug; mod debug;
mod engine; mod engine;
mod eth;
mod eth_filter;
mod eth_pubsub;
mod ganache; mod ganache;
mod hardhat; mod hardhat;
mod mev; mod mev;
@ -42,12 +38,8 @@ pub use servers::*;
pub mod servers { pub mod servers {
pub use crate::{ pub use crate::{
admin::AdminApiServer, admin::AdminApiServer,
bundle::{EthBundleApiServer, EthCallBundleApiServer},
debug::DebugApiServer, debug::DebugApiServer,
engine::{EngineApiServer, EngineEthApiServer}, engine::{EngineApiServer, EngineEthApiServer},
eth::EthApiServer,
eth_filter::EthFilterApiServer,
eth_pubsub::EthPubSubApiServer,
mev::MevApiServer, mev::MevApiServer,
net::NetApiServer, net::NetApiServer,
otterscan::OtterscanServer, otterscan::OtterscanServer,
@ -58,6 +50,10 @@ pub mod servers {
validation::BlockSubmissionValidationApiServer, validation::BlockSubmissionValidationApiServer,
web3::Web3ApiServer, web3::Web3ApiServer,
}; };
pub use reth_rpc_eth_api::{
EthApiServer, EthBundleApiServer, EthCallBundleApiServer, EthFilterApiServer,
EthPubSubApiServer,
};
} }
/// re-export of all client traits /// re-export of all client traits
@ -70,11 +66,8 @@ pub mod clients {
pub use crate::{ pub use crate::{
admin::AdminApiClient, admin::AdminApiClient,
anvil::AnvilApiClient, anvil::AnvilApiClient,
bundle::{EthBundleApiClient, EthCallBundleApiClient},
debug::DebugApiClient, debug::DebugApiClient,
engine::{EngineApiClient, EngineEthApiClient}, engine::{EngineApiClient, EngineEthApiClient},
eth::EthApiClient,
eth_filter::EthFilterApiClient,
ganache::GanacheApiClient, ganache::GanacheApiClient,
hardhat::HardhatApiClient, hardhat::HardhatApiClient,
mev::MevApiClient, mev::MevApiClient,
@ -86,4 +79,7 @@ pub mod clients {
validation::BlockSubmissionValidationApiClient, validation::BlockSubmissionValidationApiClient,
web3::Web3ApiClient, web3::Web3ApiClient,
}; };
pub use reth_rpc_eth_api::{
EthApiClient, EthBundleApiClient, EthCallBundleApiClient, EthFilterApiClient,
};
} }

View File

@ -7,7 +7,7 @@ use jsonrpsee::{
Methods, Methods,
}; };
use reth_engine_primitives::EngineTypes; use reth_engine_primitives::EngineTypes;
use reth_rpc::EthSubscriptionIdProvider; use reth_rpc::eth::EthSubscriptionIdProvider;
use reth_rpc_api::servers::*; use reth_rpc_api::servers::*;
use reth_rpc_layer::{ use reth_rpc_layer::{
secret_to_bearer_header, AuthClientLayer, AuthClientService, AuthLayer, JwtAuthValidator, secret_to_bearer_header, AuthClientLayer, AuthClientService, AuthLayer, JwtAuthValidator,

View File

@ -4,7 +4,7 @@ use crate::{
}; };
use jsonrpsee::server::ServerBuilder; use jsonrpsee::server::ServerBuilder;
use reth_node_core::{args::RpcServerArgs, utils::get_or_create_jwt_secret_from_path}; use reth_node_core::{args::RpcServerArgs, utils::get_or_create_jwt_secret_from_path};
use reth_rpc::eth::{cache::EthStateCacheConfig, gas_oracle::GasPriceOracleConfig}; use reth_rpc::eth::{EthStateCacheConfig, GasPriceOracleConfig};
use reth_rpc_layer::{JwtError, JwtSecret}; use reth_rpc_layer::{JwtError, JwtSecret};
use reth_rpc_server_types::RpcModuleSelection; use reth_rpc_server_types::RpcModuleSelection;
use std::{net::SocketAddr, path::PathBuf}; use std::{net::SocketAddr, path::PathBuf};

View File

@ -1,19 +1,16 @@
use crate::RpcModuleConfig; use std::sync::Arc;
use reth_evm::ConfigureEvm; use reth_evm::ConfigureEvm;
use reth_network_api::{NetworkInfo, Peers}; use reth_network_api::{NetworkInfo, Peers};
use reth_provider::{ use reth_provider::{
AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader,
EvmEnvProvider, StateProviderFactory, EvmEnvProvider, StateProviderFactory,
}; };
use reth_rpc::{ use reth_rpc::eth::{
eth::{ cache::cache_new_blocks_task, fee_history::fee_history_cache_new_blocks_task,
cache::{cache_new_blocks_task, EthStateCache, EthStateCacheConfig}, servers::RawTransactionForwarder, EthApi, EthFilter, EthFilterConfig, EthPubSub, EthStateCache,
fee_history_cache_new_blocks_task, EthStateCacheConfig, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
gas_oracle::{GasPriceOracle, GasPriceOracleConfig}, GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP,
traits::RawTransactionForwarder,
EthFilterConfig, FeeHistoryCache, FeeHistoryCacheConfig, RPC_DEFAULT_GAS_CAP,
},
EthApi, EthFilter, EthPubSub,
}; };
use reth_rpc_server_types::constants::{ use reth_rpc_server_types::constants::{
default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, default_max_tracing_requests, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE,
@ -21,7 +18,8 @@ use reth_rpc_server_types::constants::{
use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner};
use reth_transaction_pool::TransactionPool; use reth_transaction_pool::TransactionPool;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::RpcModuleConfig;
/// All handlers for the `eth` namespace /// All handlers for the `eth` namespace
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -47,7 +47,7 @@
//! Pool: TransactionPool + Clone + 'static, //! Pool: TransactionPool + Clone + 'static,
//! Network: NetworkInfo + Peers + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static,
//! Events: CanonStateSubscriptions + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static,
//! EvmConfig: ConfigureEvm + 'static, //! EvmConfig: ConfigureEvm,
//! { //! {
//! // configure the rpc module per transport //! // configure the rpc module per transport
//! let transports = TransportRpcModuleConfig::default().with_http(vec![ //! let transports = TransportRpcModuleConfig::default().with_http(vec![
@ -115,7 +115,7 @@
//! Events: CanonStateSubscriptions + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static,
//! EngineApi: EngineApiServer<EngineT>, //! EngineApi: EngineApiServer<EngineT>,
//! EngineT: EngineTypes + 'static, //! EngineT: EngineTypes + 'static,
//! EvmConfig: ConfigureEvm + 'static, //! EvmConfig: ConfigureEvm,
//! { //! {
//! // configure the rpc module per transport //! // configure the rpc module per transport
//! let transports = TransportRpcModuleConfig::default().with_http(vec![ //! let transports = TransportRpcModuleConfig::default().with_http(vec![
@ -178,9 +178,12 @@ use reth_provider::{
ChangeSetReader, EvmEnvProvider, StateProviderFactory, ChangeSetReader, EvmEnvProvider, StateProviderFactory,
}; };
use reth_rpc::{ use reth_rpc::{
eth::{cache::EthStateCache, traits::RawTransactionForwarder, EthBundle}, eth::{
AdminApi, DebugApi, EngineEthApi, EthApi, EthSubscriptionIdProvider, NetApi, OtterscanApi, servers::RawTransactionForwarder, EthApi, EthBundle, EthStateCache,
RPCApi, RethApi, TraceApi, TxPoolApi, Web3Api, EthSubscriptionIdProvider,
},
AdminApi, DebugApi, EngineEthApi, NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi,
Web3Api,
}; };
use reth_rpc_api::servers::*; use reth_rpc_api::servers::*;
use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret}; use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret};
@ -250,7 +253,7 @@ where
Network: NetworkInfo + Peers + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static,
Tasks: TaskSpawner + Clone + 'static, Tasks: TaskSpawner + Clone + 'static,
Events: CanonStateSubscriptions + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
let module_config = module_config.into(); let module_config = module_config.into();
let server_config = server_config.into(); let server_config = server_config.into();
@ -441,7 +444,7 @@ where
Network: NetworkInfo + Peers + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static,
Tasks: TaskSpawner + Clone + 'static, Tasks: TaskSpawner + Clone + 'static,
Events: CanonStateSubscriptions + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
/// Configures all [`RpcModule`]s specific to the given [`TransportRpcModuleConfig`] which can /// Configures all [`RpcModule`]s specific to the given [`TransportRpcModuleConfig`] which can
/// be used to start the transport server(s). /// be used to start the transport server(s).
@ -766,7 +769,7 @@ where
Network: NetworkInfo + Peers + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static,
Tasks: TaskSpawner + Clone + 'static, Tasks: TaskSpawner + Clone + 'static,
Events: CanonStateSubscriptions + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
/// Register Eth Namespace /// Register Eth Namespace
/// ///

View File

@ -0,0 +1,78 @@
[package]
name = "reth-rpc-eth-api"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Reth RPC `eth_` API implementation"
[lints]
workspace = true
[dependencies]
# reth
revm.workspace = true
revm-inspectors = { workspace = true, features = ["js-tracer"] }
revm-primitives = { workspace = true, features = ["dev"] }
reth-errors.workspace = true
reth-evm.workspace = true
reth-metrics.workspace = true
reth-network-api.workspace = true
reth-primitives.workspace = true
reth-provider.workspace = true
reth-revm.workspace = true
reth-rpc-server-types.workspace = true
reth-rpc-types.workspace = true
reth-rpc-types-compat.workspace = true
reth-tasks = { workspace = true, features = ["rayon"] }
reth-trie.workspace = true
reth-transaction-pool.workspace = true
reth-evm-optimism = { workspace = true, optional = true }
reth-chainspec.workspace = true
reth-execution-types.workspace = true
# ethereum
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
alloy-sol-types.workspace = true
secp256k1.workspace = true
# rpc
jsonrpsee = { workspace = true, features = ["server", "macros"] }
jsonrpsee-types = { workspace = true, optional = true }
serde_json.workspace = true
# async
async-trait.workspace = true
futures.workspace = true
parking_lot.workspace = true
tokio.workspace = true
tokio-stream.workspace = true
# misc
auto_impl.workspace = true
derive_more.workspace = true
dyn-clone.workspace = true
metrics.workspace = true
rand.workspace = true
schnellru.workspace = true
serde.workspace = true
thiserror.workspace = true
tracing.workspace = true
[dev-dependencies]
reth-evm-ethereum.workspace = true
reth-testing-utils.workspace = true
reth-transaction-pool = { workspace = true, features = ["test-utils"] }
reth-provider = { workspace = true, features = ["test-utils"] }
[features]
client = ["jsonrpsee/client", "jsonrpsee/async-client"]
optimism = [
"reth-primitives/optimism",
"reth-evm-optimism",
"revm/optimism",
"reth-provider/optimism",
"jsonrpsee-types",
]

View File

@ -1,4 +1,4 @@
//! Additional `eth_` functions for bundles //! Additional `eth_` RPC API for bundles.
//! //!
//! See also <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint> //! See also <https://docs.flashbots.net/flashbots-auction/searchers/advanced/rpc-endpoint>

View File

@ -1,5 +1,8 @@
//! `eth_` RPC API for filtering.
use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_rpc_types::{Filter, FilterChanges, FilterId, Log, PendingTransactionFilterKind}; use reth_rpc_types::{Filter, FilterChanges, FilterId, Log, PendingTransactionFilterKind};
/// Rpc Interface for poll-based ethereum filter API. /// Rpc Interface for poll-based ethereum filter API.
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))]
#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))]

View File

@ -1,3 +1,5 @@
//! `eth_` RPC API.
use alloy_dyn_abi::TypedData; use alloy_dyn_abi::TypedData;
use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64};
@ -8,6 +10,11 @@ use reth_rpc_types::{
TransactionRequest, Work, TransactionRequest, Work,
}; };
pub mod bundle;
pub mod filter;
pub mod pubsub;
pub mod servers;
/// Eth rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/> /// Eth rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/>
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))]
#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))]

View File

@ -1,3 +1,5 @@
//! `eth_` RPC API for pubsub subscription.
use jsonrpsee::proc_macros::rpc; use jsonrpsee::proc_macros::rpc;
use reth_rpc_types::pubsub::{Params, SubscriptionKind}; use reth_rpc_types::pubsub::{Params, SubscriptionKind};

View File

@ -1,19 +1,15 @@
//! `Eth` bundle implementation and helpers. //! `Eth` bundle implementation and helpers.
use crate::eth::{ use std::sync::Arc;
error::{EthApiError, EthResult, RpcInvalidTransactionError},
revm_utils::FillableTransaction,
utils::recover_raw_transaction,
EthTransactions,
};
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
use reth_evm::ConfigureEvmEnv;
use reth_primitives::{ use reth_primitives::{
keccak256, keccak256,
revm_primitives::db::{DatabaseCommit, DatabaseRef}, revm_primitives::db::{DatabaseCommit, DatabaseRef},
PooledTransactionsElement, U256, PooledTransactionsElement, U256,
}; };
use reth_revm::database::StateProviderDatabase; use reth_revm::database::StateProviderDatabase;
use reth_rpc_api::EthCallBundleApiServer;
use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
use reth_tasks::pool::BlockingTaskGuard; use reth_tasks::pool::BlockingTaskGuard;
use revm::{ use revm::{
@ -21,7 +17,12 @@ use revm::{
primitives::{ResultAndState, TxEnv}, primitives::{ResultAndState, TxEnv},
}; };
use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK}; use revm_primitives::{EnvKzgSettings, EnvWithHandlerCfg, MAX_BLOB_GAS_PER_BLOCK};
use std::sync::Arc;
use crate::{
servers::{Call, EthTransactions, LoadPendingBlock},
utils::recover_raw_transaction,
EthApiError, EthCallBundleApiServer, EthResult, RpcInvalidTransactionError,
};
/// `Eth` bundle implementation. /// `Eth` bundle implementation.
pub struct EthBundle<Eth> { pub struct EthBundle<Eth> {
@ -38,7 +39,7 @@ impl<Eth> EthBundle<Eth> {
impl<Eth> EthBundle<Eth> impl<Eth> EthBundle<Eth>
where where
Eth: EthTransactions + 'static, Eth: EthTransactions + LoadPendingBlock + Call + 'static,
{ {
/// Simulates a bundle of transactions at the top of a given block number with the state of /// Simulates a bundle of transactions at the top of a given block number with the state of
/// another (or the same) block. This can be used to simulate future blocks with the current /// another (or the same) block. This can be used to simulate future blocks with the current
@ -98,6 +99,8 @@ where
// use the block number of the request // use the block number of the request
block_env.number = U256::from(block_number); block_env.number = U256::from(block_number);
let eth_api = self.inner.eth_api.clone();
self.inner self.inner
.eth_api .eth_api
.spawn_with_state_at_block(at, move |state| { .spawn_with_state_at_block(at, move |state| {
@ -129,13 +132,13 @@ where
.map_err(|e| EthApiError::InvalidParams(e.to_string()))?; .map_err(|e| EthApiError::InvalidParams(e.to_string()))?;
} }
let tx = tx.into_ecrecovered_transaction(signer); let tx = tx.into_transaction();
hash_bytes.extend_from_slice(tx.hash().as_slice()); hash_bytes.extend_from_slice(tx.hash().as_slice());
let gas_price = tx let gas_price = tx
.effective_tip_per_gas(basefee) .effective_tip_per_gas(basefee)
.ok_or_else(|| RpcInvalidTransactionError::FeeCapTooLow)?; .ok_or_else(|| RpcInvalidTransactionError::FeeCapTooLow)?;
tx.try_fill_tx_env(evm.tx_mut())?; Call::evm_config(&eth_api).fill_tx_env(evm.tx_mut(), &tx.clone(), signer);
let ResultAndState { result, state } = evm.transact()?; let ResultAndState { result, state } = evm.transact()?;
let gas_used = result.gas_used(); let gas_used = result.gas_used();
@ -166,7 +169,7 @@ where
let tx_res = EthCallBundleTransactionResult { let tx_res = EthCallBundleTransactionResult {
coinbase_diff, coinbase_diff,
eth_sent_to_coinbase, eth_sent_to_coinbase,
from_address: tx.signer(), from_address: signer,
gas_fees, gas_fees,
gas_price: U256::from(gas_price), gas_price: U256::from(gas_price),
gas_used, gas_used,
@ -212,7 +215,7 @@ where
#[async_trait::async_trait] #[async_trait::async_trait]
impl<Eth> EthCallBundleApiServer for EthBundle<Eth> impl<Eth> EthCallBundleApiServer for EthBundle<Eth>
where where
Eth: EthTransactions + 'static, Eth: EthTransactions + LoadPendingBlock + Call + 'static,
{ {
async fn call_bundle(&self, request: EthCallBundle) -> RpcResult<EthCallBundleResponse> { async fn call_bundle(&self, request: EthCallBundle) -> RpcResult<EthCallBundleResponse> {
Ok(Self::call_bundle(self, request).await?) Ok(Self::call_bundle(self, request).await?)

View File

@ -1,40 +1,37 @@
use super::cache::EthStateCache; //! `eth_` `Filter` RPC handler implementation
use crate::{
eth::{ use std::{
error::EthApiError, collections::HashMap,
logs_utils::{self, append_matching_block_logs}, fmt,
}, iter::StepBy,
result::{rpc_error_with_code, ToRpcResult}, ops::RangeInclusive,
EthSubscriptionIdProvider, sync::Arc,
time::{Duration, Instant},
}; };
use core::fmt;
use async_trait::async_trait; use async_trait::async_trait;
use jsonrpsee::{core::RpcResult, server::IdProvider}; use jsonrpsee::{core::RpcResult, server::IdProvider};
use reth_chainspec::ChainInfo; use reth_chainspec::ChainInfo;
use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_primitives::{IntoRecoveredTransaction, TxHash};
use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider, ProviderError}; use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider, ProviderError};
use reth_rpc_api::EthFilterApiServer;
use reth_rpc_types::{ use reth_rpc_types::{
BlockNumHash, Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log, BlockNumHash, Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log,
PendingTransactionFilterKind, PendingTransactionFilterKind,
}; };
use reth_tasks::TaskSpawner; use reth_tasks::TaskSpawner;
use reth_transaction_pool::{NewSubpoolTransactionStream, PoolTransaction, TransactionPool}; use reth_transaction_pool::{NewSubpoolTransactionStream, PoolTransaction, TransactionPool};
use std::{
collections::HashMap,
iter::StepBy,
ops::RangeInclusive,
sync::Arc,
time::{Duration, Instant},
};
use tokio::{ use tokio::{
sync::{mpsc::Receiver, Mutex}, sync::{mpsc::Receiver, Mutex},
time::MissedTickBehavior, time::MissedTickBehavior,
}; };
use tracing::trace; use tracing::trace;
use crate::{
logs_utils::{self, append_matching_block_logs},
result::rpc_error_with_code,
EthApiError, EthFilterApiServer, EthStateCache, EthSubscriptionIdProvider, ToRpcResult,
};
/// The maximum number of headers we read at once when handling a range filter. /// The maximum number of headers we read at once when handling a range filter.
const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb
@ -132,7 +129,7 @@ where
<Pool as TransactionPool>::Transaction: 'static, <Pool as TransactionPool>::Transaction: 'static,
{ {
/// Returns all the filter changes for the given id, if any /// Returns all the filter changes for the given id, if any
pub async fn filter_changes(&self, id: FilterId) -> Result<FilterChanges, FilterError> { pub async fn filter_changes(&self, id: FilterId) -> Result<FilterChanges, EthFilterError> {
let info = self.inner.provider.chain_info()?; let info = self.inner.provider.chain_info()?;
let best_number = info.best_number; let best_number = info.best_number;
@ -140,7 +137,7 @@ where
// the last time changes were polled, in other words the best block at last poll + 1 // the last time changes were polled, in other words the best block at last poll + 1
let (start_block, kind) = { let (start_block, kind) = {
let mut filters = self.inner.active_filters.inner.lock().await; let mut filters = self.inner.active_filters.inner.lock().await;
let filter = filters.get_mut(&id).ok_or(FilterError::FilterNotFound(id))?; let filter = filters.get_mut(&id).ok_or(EthFilterError::FilterNotFound(id))?;
if filter.block > best_number { if filter.block > best_number {
// no new blocks since the last poll // no new blocks since the last poll
@ -204,16 +201,16 @@ where
/// Returns an error if no matching log filter exists. /// Returns an error if no matching log filter exists.
/// ///
/// Handler for `eth_getFilterLogs` /// Handler for `eth_getFilterLogs`
pub async fn filter_logs(&self, id: FilterId) -> Result<Vec<Log>, FilterError> { pub async fn filter_logs(&self, id: FilterId) -> Result<Vec<Log>, EthFilterError> {
let filter = { let filter = {
let filters = self.inner.active_filters.inner.lock().await; let filters = self.inner.active_filters.inner.lock().await;
if let FilterKind::Log(ref filter) = if let FilterKind::Log(ref filter) =
filters.get(&id).ok_or_else(|| FilterError::FilterNotFound(id.clone()))?.kind filters.get(&id).ok_or_else(|| EthFilterError::FilterNotFound(id.clone()))?.kind
{ {
*filter.clone() *filter.clone()
} else { } else {
// Not a log filter // Not a log filter
return Err(FilterError::FilterNotFound(id)) return Err(EthFilterError::FilterNotFound(id))
} }
}; };
@ -347,7 +344,7 @@ where
Pool: TransactionPool + 'static, Pool: TransactionPool + 'static,
{ {
/// Returns logs matching given filter object. /// Returns logs matching given filter object.
async fn logs_for_filter(&self, filter: Filter) -> Result<Vec<Log>, FilterError> { async fn logs_for_filter(&self, filter: Filter) -> Result<Vec<Log>, EthFilterError> {
match filter.block_option { match filter.block_option {
FilterBlockOption::AtBlockHash(block_hash) => { FilterBlockOption::AtBlockHash(block_hash) => {
// for all matching logs in the block // for all matching logs in the block
@ -428,16 +425,16 @@ where
from_block: u64, from_block: u64,
to_block: u64, to_block: u64,
chain_info: ChainInfo, chain_info: ChainInfo,
) -> Result<Vec<Log>, FilterError> { ) -> Result<Vec<Log>, EthFilterError> {
trace!(target: "rpc::eth::filter", from=from_block, to=to_block, ?filter, "finding logs in range"); trace!(target: "rpc::eth::filter", from=from_block, to=to_block, ?filter, "finding logs in range");
let best_number = chain_info.best_number; let best_number = chain_info.best_number;
if to_block < from_block { if to_block < from_block {
return Err(FilterError::InvalidBlockRangeParams) return Err(EthFilterError::InvalidBlockRangeParams)
} }
if to_block - from_block > self.max_blocks_per_filter { if to_block - from_block > self.max_blocks_per_filter {
return Err(FilterError::QueryExceedsMaxBlocks(self.max_blocks_per_filter)) return Err(EthFilterError::QueryExceedsMaxBlocks(self.max_blocks_per_filter))
} }
let mut all_logs = Vec::new(); let mut all_logs = Vec::new();
@ -505,7 +502,7 @@ where
// logs of a single block // logs of a single block
let is_multi_block_range = from_block != to_block; let is_multi_block_range = from_block != to_block;
if is_multi_block_range && all_logs.len() > self.max_logs_per_response { if is_multi_block_range && all_logs.len() > self.max_logs_per_response {
return Err(FilterError::QueryExceedsMaxResults( return Err(EthFilterError::QueryExceedsMaxResults(
self.max_logs_per_response, self.max_logs_per_response,
)) ))
} }
@ -682,51 +679,6 @@ enum FilterKind {
PendingTransaction(PendingTransactionKind), PendingTransaction(PendingTransactionKind),
} }
/// Errors that can occur in the handler implementation
#[derive(Debug, thiserror::Error)]
pub enum FilterError {
#[error("filter not found")]
FilterNotFound(FilterId),
#[error("invalid block range params")]
InvalidBlockRangeParams,
#[error("query exceeds max block range {0}")]
QueryExceedsMaxBlocks(u64),
#[error("query exceeds max results {0}")]
QueryExceedsMaxResults(usize),
#[error(transparent)]
EthAPIError(#[from] EthApiError),
/// Error thrown when a spawned task failed to deliver a response.
#[error("internal filter error")]
InternalError,
}
// convert the error
impl From<FilterError> for jsonrpsee::types::error::ErrorObject<'static> {
fn from(err: FilterError) -> Self {
match err {
FilterError::FilterNotFound(_) => rpc_error_with_code(
jsonrpsee::types::error::INVALID_PARAMS_CODE,
"filter not found",
),
err @ FilterError::InternalError => {
rpc_error_with_code(jsonrpsee::types::error::INTERNAL_ERROR_CODE, err.to_string())
}
FilterError::EthAPIError(err) => err.into(),
err @ FilterError::InvalidBlockRangeParams |
err @ FilterError::QueryExceedsMaxBlocks(_) |
err @ FilterError::QueryExceedsMaxResults(_) => {
rpc_error_with_code(jsonrpsee::types::error::INVALID_PARAMS_CODE, err.to_string())
}
}
}
}
impl From<ProviderError> for FilterError {
fn from(err: ProviderError) -> Self {
Self::EthAPIError(err.into())
}
}
/// An iterator that yields _inclusive_ block ranges of a given step size /// An iterator that yields _inclusive_ block ranges of a given step size
#[derive(Debug)] #[derive(Debug)]
struct BlockRangeInclusiveIter { struct BlockRangeInclusiveIter {
@ -754,6 +706,56 @@ impl Iterator for BlockRangeInclusiveIter {
} }
} }
/// Errors that can occur in the handler implementation
#[derive(Debug, thiserror::Error)]
pub enum EthFilterError {
/// Filter not found.
#[error("filter not found")]
FilterNotFound(FilterId),
/// Invalid block range.
#[error("invalid block range params")]
InvalidBlockRangeParams,
/// Query scope is too broad.
#[error("query exceeds max block range {0}")]
QueryExceedsMaxBlocks(u64),
/// Query result is too large.
#[error("query exceeds max results {0}")]
QueryExceedsMaxResults(usize),
/// Error serving request in `eth_` namespace.
#[error(transparent)]
EthAPIError(#[from] EthApiError),
/// Error thrown when a spawned task failed to deliver a response.
#[error("internal filter error")]
InternalError,
}
// convert the error
impl From<EthFilterError> for jsonrpsee::types::error::ErrorObject<'static> {
fn from(err: EthFilterError) -> Self {
match err {
EthFilterError::FilterNotFound(_) => rpc_error_with_code(
jsonrpsee::types::error::INVALID_PARAMS_CODE,
"filter not found",
),
err @ EthFilterError::InternalError => {
rpc_error_with_code(jsonrpsee::types::error::INTERNAL_ERROR_CODE, err.to_string())
}
EthFilterError::EthAPIError(err) => err.into(),
err @ EthFilterError::InvalidBlockRangeParams |
err @ EthFilterError::QueryExceedsMaxBlocks(_) |
err @ EthFilterError::QueryExceedsMaxResults(_) => {
rpc_error_with_code(jsonrpsee::types::error::INVALID_PARAMS_CODE, err.to_string())
}
}
}
}
impl From<ProviderError> for EthFilterError {
fn from(err: ProviderError) -> Self {
Self::EthAPIError(err.into())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -0,0 +1,35 @@
//! Contains RPC handler implementations specific to blocks.
use reth_provider::{BlockReaderIdExt, HeaderProvider};
use crate::{
servers::{EthBlocks, LoadBlock, LoadPendingBlock, SpawnBlocking},
EthApi, EthStateCache,
};
impl<Provider, Pool, Network, EvmConfig> EthBlocks for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: LoadBlock,
Provider: HeaderProvider,
{
#[inline]
fn provider(&self) -> impl reth_provider::HeaderProvider {
self.inner.provider()
}
}
impl<Provider, Pool, Network, EvmConfig> LoadBlock for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: LoadPendingBlock + SpawnBlocking,
Provider: BlockReaderIdExt,
{
#[inline]
fn provider(&self) -> impl BlockReaderIdExt {
self.inner.provider()
}
#[inline]
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
}

View File

@ -0,0 +1,29 @@
//! Contains RPC handler implementations specific to endpoints that call/execute within evm.
use reth_evm::ConfigureEvm;
use crate::{
servers::{Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking},
EthApi,
};
impl<Provider, Pool, Network, EvmConfig> EthCall for EthApi<Provider, Pool, Network, EvmConfig> where
Self: Call + LoadPendingBlock
{
}
impl<Provider, Pool, Network, EvmConfig> Call for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: LoadState + SpawnBlocking,
EvmConfig: ConfigureEvm,
{
#[inline]
fn call_gas_limit(&self) -> u64 {
self.inner.gas_cap()
}
#[inline]
fn evm_config(&self) -> &impl ConfigureEvm {
self.inner.evm_config()
}
}

View File

@ -0,0 +1,39 @@
//! Contains RPC handler implementations for fee history.
use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider};
use crate::{
servers::{EthFees, LoadBlock, LoadFee},
EthApi, EthStateCache, FeeHistoryCache, GasPriceOracle,
};
impl<Provider, Pool, Network, EvmConfig> EthFees for EthApi<Provider, Pool, Network, EvmConfig> where
Self: LoadFee
{
}
impl<Provider, Pool, Network, EvmConfig> LoadFee for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: LoadBlock,
Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider,
{
#[inline]
fn provider(&self) -> impl BlockIdReader + HeaderProvider + ChainSpecProvider {
self.inner.provider()
}
#[inline]
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
#[inline]
fn gas_oracle(&self) -> &GasPriceOracle<impl BlockReaderIdExt> {
self.inner.gas_oracle()
}
#[inline]
fn fee_history_cache(&self) -> &FeeHistoryCache {
self.inner.fee_history_cache()
}
}

View File

@ -0,0 +1,19 @@
//! The entire implementation of the namespace is quite large, hence it is divided across several
//! files.
pub mod signer;
pub mod traits;
mod block;
mod call;
mod fees;
#[cfg(feature = "optimism")]
pub mod optimism;
#[cfg(not(feature = "optimism"))]
mod pending_block;
#[cfg(not(feature = "optimism"))]
mod receipt;
mod spec;
mod state;
mod trace;
mod transaction;

View File

@ -0,0 +1,231 @@
//! Loads and formats OP transaction RPC response.
use jsonrpsee_types::error::ErrorObject;
use reth_evm::ConfigureEvm;
use reth_evm_optimism::RethL1BlockInfo;
use reth_primitives::{
BlockNumber, Receipt, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, B256,
};
use reth_provider::{
BlockIdReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, ExecutionOutcome,
StateProviderFactory,
};
use reth_rpc_types::{AnyTransactionReceipt, OptimismTransactionReceiptFields, ToRpcError};
use reth_transaction_pool::TransactionPool;
use revm::L1BlockInfo;
use revm_primitives::{BlockEnv, ExecutionResult};
use crate::{
result::internal_rpc_err,
servers::{LoadPendingBlock, LoadReceipt, SpawnBlocking},
EthApi, EthApiError, EthResult, EthStateCache, PendingBlock, ReceiptBuilder,
};
/// L1 fee and data gas for a transaction, along with the L1 block info.
#[derive(Debug, Default, Clone)]
pub struct OptimismTxMeta {
/// The L1 block info.
pub l1_block_info: Option<L1BlockInfo>,
/// The L1 fee for the block.
pub l1_fee: Option<u128>,
/// The L1 data gas for the block.
pub l1_data_gas: Option<u128>,
}
impl OptimismTxMeta {
/// Creates a new [`OptimismTxMeta`].
pub const fn new(
l1_block_info: Option<L1BlockInfo>,
l1_fee: Option<u128>,
l1_data_gas: Option<u128>,
) -> Self {
Self { l1_block_info, l1_fee, l1_data_gas }
}
}
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider: BlockIdReader + ChainSpecProvider,
{
/// Builds [`OptimismTxMeta`] object using the provided [`TransactionSigned`], L1 block
/// info and block timestamp. The [`L1BlockInfo`] is used to calculate the l1 fee and l1 data
/// gas for the transaction. If the [`L1BlockInfo`] is not provided, the meta info will be
/// empty.
pub fn build_op_tx_meta(
&self,
tx: &TransactionSigned,
l1_block_info: Option<L1BlockInfo>,
block_timestamp: u64,
) -> EthResult<OptimismTxMeta> {
let Some(l1_block_info) = l1_block_info else { return Ok(OptimismTxMeta::default()) };
let (l1_fee, l1_data_gas) = if !tx.is_deposit() {
let envelope_buf = tx.envelope_encoded();
let inner_l1_fee = l1_block_info
.l1_tx_data_fee(
&self.inner.provider().chain_spec(),
block_timestamp,
&envelope_buf,
tx.is_deposit(),
)
.map_err(|_| OptimismEthApiError::L1BlockFeeError)?;
let inner_l1_data_gas = l1_block_info
.l1_data_gas(&self.inner.provider().chain_spec(), block_timestamp, &envelope_buf)
.map_err(|_| OptimismEthApiError::L1BlockGasError)?;
(
Some(inner_l1_fee.saturating_to::<u128>()),
Some(inner_l1_data_gas.saturating_to::<u128>()),
)
} else {
(None, None)
};
Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas))
}
}
impl<Provider, Pool, Network, EvmConfig> LoadReceipt for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: Send + Sync,
Provider: BlockIdReader + ChainSpecProvider,
{
#[inline]
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
async fn build_transaction_receipt(
&self,
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
) -> EthResult<AnyTransactionReceipt> {
let (block, receipts) = self
.cache()
.get_block_and_receipts(meta.block_hash)
.await?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block = block.unseal();
let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok();
let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?;
let resp_builder = ReceiptBuilder::new(&tx, meta, &receipt, &receipts)?;
let resp_builder = op_receipt_fields(resp_builder, &tx, &receipt, optimism_tx_meta);
Ok(resp_builder.build())
}
}
/// Applies OP specific fields to a receipt.
fn op_receipt_fields(
resp_builder: ReceiptBuilder,
tx: &TransactionSigned,
receipt: &Receipt,
optimism_tx_meta: OptimismTxMeta,
) -> ReceiptBuilder {
let mut op_fields = OptimismTransactionReceiptFields::default();
if tx.is_deposit() {
op_fields.deposit_nonce = receipt.deposit_nonce.map(reth_primitives::U64::from);
op_fields.deposit_receipt_version =
receipt.deposit_receipt_version.map(reth_primitives::U64::from);
} else if let Some(l1_block_info) = optimism_tx_meta.l1_block_info {
op_fields.l1_fee = optimism_tx_meta.l1_fee;
op_fields.l1_gas_used = optimism_tx_meta.l1_data_gas.map(|dg| {
dg + l1_block_info.l1_fee_overhead.unwrap_or_default().saturating_to::<u128>()
});
op_fields.l1_fee_scalar = Some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0);
op_fields.l1_gas_price = Some(l1_block_info.l1_base_fee.saturating_to());
}
resp_builder.add_other_fields(op_fields.into())
}
impl<Provider, Pool, Network, EvmConfig> LoadPendingBlock
for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: SpawnBlocking,
Provider: BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory,
Pool: TransactionPool,
EvmConfig: ConfigureEvm,
{
#[inline]
fn provider(
&self,
) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory {
self.inner.provider()
}
#[inline]
fn pool(&self) -> impl reth_transaction_pool::TransactionPool {
self.inner.pool()
}
#[inline]
fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock>> {
self.inner.pending_block()
}
#[inline]
fn evm_config(&self) -> &impl reth_evm::ConfigureEvm {
self.inner.evm_config()
}
fn assemble_receipt(
&self,
tx: &TransactionSignedEcRecovered,
result: ExecutionResult,
cumulative_gas_used: u64,
) -> Receipt {
Receipt {
tx_type: tx.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.into_logs().into_iter().map(Into::into).collect(),
deposit_nonce: None,
deposit_receipt_version: None,
}
}
fn receipts_root(
&self,
_block_env: &BlockEnv,
execution_outcome: &ExecutionOutcome,
block_number: BlockNumber,
) -> B256 {
execution_outcome
.optimism_receipts_root_slow(
block_number,
self.provider().chain_spec().as_ref(),
_block_env.timestamp.to::<u64>(),
)
.expect("Block is present")
}
}
/// Optimism specific errors, that extend [`EthApiError`].
#[derive(Debug, thiserror::Error)]
pub enum OptimismEthApiError {
/// Thrown when calculating L1 gas fee.
#[error("failed to calculate l1 gas fee")]
L1BlockFeeError,
/// Thrown when calculating L1 gas used
#[error("failed to calculate l1 gas used")]
L1BlockGasError,
}
impl ToRpcError for OptimismEthApiError {
fn to_rpc_error(&self) -> ErrorObject<'static> {
match self {
Self::L1BlockFeeError | Self::L1BlockGasError => internal_rpc_err(self.to_string()),
}
}
}
impl From<OptimismEthApiError> for EthApiError {
fn from(err: OptimismEthApiError) -> Self {
Self::other(err)
}
}

View File

@ -0,0 +1,41 @@
//! Support for building a pending block with transactions from local view of mempool.
use reth_evm::ConfigureEvm;
use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory};
use reth_transaction_pool::TransactionPool;
use crate::{
servers::{LoadPendingBlock, SpawnBlocking},
EthApi, PendingBlock,
};
impl<Provider, Pool, Network, EvmConfig> LoadPendingBlock
for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: SpawnBlocking,
Provider: BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory,
Pool: TransactionPool,
EvmConfig: reth_evm::ConfigureEvm,
{
#[inline]
fn provider(
&self,
) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory {
self.inner.provider()
}
#[inline]
fn pool(&self) -> impl TransactionPool {
self.inner.pool()
}
#[inline]
fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock>> {
self.inner.pending_block()
}
#[inline]
fn evm_config(&self) -> &impl ConfigureEvm {
self.inner.evm_config()
}
}

View File

@ -0,0 +1,13 @@
//! Builds an RPC receipt response w.r.t. data layout of network.
use crate::{servers::LoadReceipt, EthApi, EthStateCache};
impl<Provider, Pool, Network, EvmConfig> LoadReceipt for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: Send + Sync,
{
#[inline]
fn cache(&self) -> &EthStateCache {
&self.inner.eth_cache
}
}

View File

@ -1,49 +1,23 @@
//! An abstraction over ethereum signers. //! An abstraction over ethereum signers.
use crate::eth::error::SignError; use std::collections::HashMap;
use alloy_dyn_abi::TypedData; use alloy_dyn_abi::TypedData;
use reth_primitives::{ use reth_primitives::{
eip191_hash_message, sign_message, Address, Signature, TransactionSigned, B256, eip191_hash_message, sign_message, Address, Signature, TransactionSigned, B256,
}; };
use reth_rpc_types::TypedTransactionRequest; use reth_rpc_types::TypedTransactionRequest;
use dyn_clone::DynClone;
use reth_rpc_types_compat::transaction::to_primitive_transaction; use reth_rpc_types_compat::transaction::to_primitive_transaction;
use secp256k1::SecretKey; use secp256k1::SecretKey;
use std::collections::HashMap;
type Result<T> = std::result::Result<T, SignError>; use crate::{
servers::{helpers::traits::signer::Result, EthSigner},
/// An Ethereum Signer used via RPC. SignError,
#[async_trait::async_trait] };
pub(crate) trait EthSigner: Send + Sync + DynClone {
/// Returns the available accounts for this signer.
fn accounts(&self) -> Vec<Address>;
/// Returns `true` whether this signer can sign for this address
fn is_signer_for(&self, addr: &Address) -> bool {
self.accounts().contains(addr)
}
/// Returns the signature
async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature>;
/// signs a transaction request using the given account in request
fn sign_transaction(
&self,
request: TypedTransactionRequest,
address: &Address,
) -> Result<TransactionSigned>;
/// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait.
fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result<Signature>;
}
dyn_clone::clone_trait_object!(EthSigner);
/// Holds developer keys /// Holds developer keys
#[derive(Clone)] #[derive(Debug, Clone)]
pub(crate) struct DevSigner { pub struct DevSigner {
addresses: Vec<Address>, addresses: Vec<Address>,
accounts: HashMap<Address, SecretKey>, accounts: HashMap<Address, SecretKey>,
} }
@ -121,9 +95,12 @@ impl EthSigner for DevSigner {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use reth_primitives::U256;
use std::str::FromStr; use std::str::FromStr;
use reth_primitives::U256;
use super::*;
fn build_signer() -> DevSigner { fn build_signer() -> DevSigner {
let addresses = vec![]; let addresses = vec![];
let secret = let secret =

View File

@ -0,0 +1,64 @@
use reth_chainspec::ChainInfo;
use reth_errors::{RethError, RethResult};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
use reth_primitives::{Address, U256, U64};
use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory};
use reth_rpc_types::{SyncInfo, SyncStatus};
use reth_transaction_pool::TransactionPool;
use crate::{servers::EthApiSpec, EthApi};
impl<Provider, Pool, Network, EvmConfig> EthApiSpec for EthApi<Provider, Pool, Network, EvmConfig>
where
Pool: TransactionPool + 'static,
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: NetworkInfo + 'static,
EvmConfig: ConfigureEvm,
{
/// Returns the current ethereum protocol version.
///
/// Note: This returns an [`U64`], since this should return as hex string.
async fn protocol_version(&self) -> RethResult<U64> {
let status = self.network().network_status().await.map_err(RethError::other)?;
Ok(U64::from(status.protocol_version))
}
/// Returns the chain id
fn chain_id(&self) -> U64 {
U64::from(self.network().chain_id())
}
/// Returns the current info for the chain
fn chain_info(&self) -> RethResult<ChainInfo> {
Ok(self.provider().chain_info()?)
}
fn accounts(&self) -> Vec<Address> {
self.inner.signers.read().iter().flat_map(|s| s.accounts()).collect()
}
fn is_syncing(&self) -> bool {
self.network().is_syncing()
}
/// Returns the [`SyncStatus`] of the network
fn sync_status(&self) -> RethResult<SyncStatus> {
let status = if self.is_syncing() {
let current_block = U256::from(
self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(),
);
SyncStatus::Info(SyncInfo {
starting_block: self.inner.starting_block,
current_block,
highest_block: current_block,
warp_chunks_amount: None,
warp_chunks_processed: None,
})
} else {
SyncStatus::None
};
Ok(status)
}
}

View File

@ -0,0 +1,104 @@
//! Contains RPC handler implementations specific to state.
use reth_provider::StateProviderFactory;
use reth_transaction_pool::TransactionPool;
use crate::{
servers::{EthState, LoadState, SpawnBlocking},
EthApi, EthStateCache,
};
impl<Provider, Pool, Network, EvmConfig> EthState for EthApi<Provider, Pool, Network, EvmConfig> where
Self: LoadState + SpawnBlocking
{
}
impl<Provider, Pool, Network, EvmConfig> LoadState for EthApi<Provider, Pool, Network, EvmConfig>
where
Provider: StateProviderFactory,
Pool: TransactionPool,
{
#[inline]
fn provider(&self) -> impl StateProviderFactory {
self.inner.provider()
}
#[inline]
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
#[inline]
fn pool(&self) -> impl TransactionPool {
self.inner.pool()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use reth_evm_ethereum::EthEvmConfig;
use reth_primitives::{
constants::ETHEREUM_BLOCK_GAS_LIMIT, Address, StorageKey, StorageValue, U256,
};
use reth_provider::test_utils::{ExtendedAccount, MockEthProvider, NoopProvider};
use reth_tasks::pool::BlockingTaskPool;
use reth_transaction_pool::test_utils::testing_pool;
use crate::{
servers::EthState, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
};
use super::*;
#[tokio::test]
async fn test_storage() {
// === Noop ===
let pool = testing_pool();
let evm_config = EthEvmConfig::default();
let cache = EthStateCache::spawn(NoopProvider::default(), Default::default(), evm_config);
let eth_api = EthApi::new(
NoopProvider::default(),
pool.clone(),
(),
cache.clone(),
GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
BlockingTaskPool::build().expect("failed to build tracing pool"),
FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()),
evm_config,
None,
);
let address = Address::random();
let storage = eth_api.storage_at(address, U256::ZERO.into(), None).await.unwrap();
assert_eq!(storage, U256::ZERO.to_be_bytes());
// === Mock ===
let mock_provider = MockEthProvider::default();
let storage_value = StorageValue::from(1337);
let storage_key = StorageKey::random();
let storage = HashMap::from([(storage_key, storage_value)]);
let account = ExtendedAccount::new(0, U256::ZERO).extend_storage(storage);
mock_provider.add_account(address, account);
let cache = EthStateCache::spawn(mock_provider.clone(), Default::default(), evm_config);
let eth_api = EthApi::new(
mock_provider.clone(),
pool,
(),
cache.clone(),
GasPriceOracle::new(mock_provider, Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
BlockingTaskPool::build().expect("failed to build tracing pool"),
FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()),
evm_config,
None,
);
let storage_key: U256 = storage_key.into();
let storage = eth_api.storage_at(address, storage_key.into(), None).await.unwrap();
assert_eq!(storage, storage_value.to_be_bytes());
}
}

View File

@ -0,0 +1,19 @@
//! Contains RPC handler implementations specific to tracing.
use reth_evm::ConfigureEvm;
use crate::{
servers::{LoadState, Trace},
EthApi,
};
impl<Provider, Pool, Network, EvmConfig> Trace for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: LoadState,
EvmConfig: ConfigureEvm,
{
#[inline]
fn evm_config(&self) -> &impl ConfigureEvm {
self.inner.evm_config()
}
}

View File

@ -0,0 +1,244 @@
//! Database access for `eth_` block RPC methods. Loads block and receipt data w.r.t. network.
use std::sync::Arc;
use futures::Future;
use reth_primitives::{BlockId, Receipt, SealedBlock, SealedBlockWithSenders, TransactionMeta};
use reth_provider::{BlockIdReader, BlockReader, BlockReaderIdExt, HeaderProvider};
use reth_rpc_types::{AnyTransactionReceipt, Header, Index, RichBlock};
use reth_rpc_types_compat::block::{from_block, uncle_block_from_header};
use crate::{
servers::{LoadPendingBlock, LoadReceipt, SpawnBlocking},
EthApiError, EthResult, EthStateCache, ReceiptBuilder,
};
/// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the
/// `eth_` namespace.
pub trait EthBlocks: LoadBlock {
/// Returns a handle for reading data from disk.
///
/// Data access in default (L1) trait method implementations.
fn provider(&self) -> impl HeaderProvider;
/// Returns the block header for the given block id.
fn rpc_block_header(
&self,
block_id: BlockId,
) -> impl Future<Output = EthResult<Option<Header>>> + Send
where
Self: LoadPendingBlock + SpawnBlocking,
{
async move { Ok(self.rpc_block(block_id, false).await?.map(|block| block.inner.header)) }
}
/// Returns the populated rpc block object for the given block id.
///
/// If `full` is true, the block object will contain all transaction objects, otherwise it will
/// only contain the transaction hashes.
fn rpc_block(
&self,
block_id: BlockId,
full: bool,
) -> impl Future<Output = EthResult<Option<RichBlock>>> + Send
where
Self: LoadPendingBlock + SpawnBlocking,
{
async move {
let block = match self.block_with_senders(block_id).await? {
Some(block) => block,
None => return Ok(None),
};
let block_hash = block.hash();
let total_difficulty = EthBlocks::provider(self)
.header_td_by_number(block.number)?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block =
from_block(block.unseal(), total_difficulty, full.into(), Some(block_hash))?;
Ok(Some(block.into()))
}
}
/// Returns the number transactions in the given block.
///
/// Returns `None` if the block does not exist
fn block_transaction_count(
&self,
block_id: impl Into<BlockId>,
) -> impl Future<Output = EthResult<Option<usize>>> + Send {
let block_id = block_id.into();
async move {
if block_id.is_pending() {
// Pending block can be fetched directly without need for caching
return Ok(LoadBlock::provider(self).pending_block()?.map(|block| block.body.len()))
}
let block_hash = match LoadBlock::provider(self).block_hash_for_id(block_id)? {
Some(block_hash) => block_hash,
None => return Ok(None),
};
Ok(self.cache().get_block_transactions(block_hash).await?.map(|txs| txs.len()))
}
}
/// Helper function for `eth_getBlockReceipts`.
///
/// Returns all transaction receipts in block, or `None` if block wasn't found.
fn block_receipts(
&self,
block_id: BlockId,
) -> impl Future<Output = EthResult<Option<Vec<AnyTransactionReceipt>>>> + Send
where
Self: LoadReceipt,
{
async move {
if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
let block_number = block.number;
let base_fee = block.base_fee_per_gas;
let block_hash = block.hash();
let excess_blob_gas = block.excess_blob_gas;
let timestamp = block.timestamp;
let block = block.unseal();
let receipts = block
.body
.into_iter()
.zip(receipts.iter())
.enumerate()
.map(|(idx, (tx, receipt))| {
let meta = TransactionMeta {
tx_hash: tx.hash,
index: idx as u64,
block_hash,
block_number,
base_fee,
excess_blob_gas,
timestamp,
};
ReceiptBuilder::new(&tx, meta, receipt, &receipts)
.map(|builder| builder.build())
})
.collect::<EthResult<Vec<_>>>();
return receipts.map(Some)
}
Ok(None)
}
}
/// Helper method that loads a bock and all its receipts.
fn load_block_and_receipts(
&self,
block_id: BlockId,
) -> impl Future<Output = EthResult<Option<(SealedBlock, Arc<Vec<Receipt>>)>>> + Send
where
Self: LoadReceipt,
{
async move {
if block_id.is_pending() {
return Ok(LoadBlock::provider(self)
.pending_block_and_receipts()?
.map(|(sb, receipts)| (sb, Arc::new(receipts))))
}
if let Some(block_hash) = LoadBlock::provider(self).block_hash_for_id(block_id)? {
return Ok(LoadReceipt::cache(self).get_block_and_receipts(block_hash).await?)
}
Ok(None)
}
}
/// Returns uncle headers of given block.
///
/// Returns an empty vec if there are none.
fn ommers(
&self,
block_id: impl Into<BlockId>,
) -> EthResult<Option<Vec<reth_primitives::Header>>> {
let block_id = block_id.into();
Ok(LoadBlock::provider(self).ommers_by_id(block_id)?)
}
/// Returns uncle block at given index in given block.
///
/// Returns `None` if index out of range.
fn ommer_by_block_and_index(
&self,
block_id: impl Into<BlockId>,
index: Index,
) -> impl Future<Output = EthResult<Option<RichBlock>>> + Send {
let block_id = block_id.into();
async move {
let uncles = if block_id.is_pending() {
// Pending block can be fetched directly without need for caching
LoadBlock::provider(self).pending_block()?.map(|block| block.ommers)
} else {
LoadBlock::provider(self).ommers_by_id(block_id)?
}
.unwrap_or_default();
let index = usize::from(index);
let uncle =
uncles.into_iter().nth(index).map(|header| uncle_block_from_header(header).into());
Ok(uncle)
}
}
}
/// Loads a block from database.
///
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
pub trait LoadBlock: LoadPendingBlock + SpawnBlocking {
// Returns a handle for reading data from disk.
///
/// Data access in default (L1) trait method implementations.
fn provider(&self) -> impl BlockReaderIdExt;
/// Returns a handle for reading data from memory.
///
/// Data access in default (L1) trait method implementations.
fn cache(&self) -> &EthStateCache;
/// Returns the block object for the given block id.
fn block(
&self,
block_id: BlockId,
) -> impl Future<Output = EthResult<Option<SealedBlock>>> + Send {
async move {
self.block_with_senders(block_id)
.await
.map(|maybe_block| maybe_block.map(|block| block.block))
}
}
/// Returns the block object for the given block id.
fn block_with_senders(
&self,
block_id: BlockId,
) -> impl Future<Output = EthResult<Option<SealedBlockWithSenders>>> + Send {
async move {
if block_id.is_pending() {
// Pending block can be fetched directly without need for caching
let maybe_pending =
LoadPendingBlock::provider(self).pending_block_with_senders()?;
return if maybe_pending.is_some() {
Ok(maybe_pending)
} else {
self.local_pending_block().await
}
}
let block_hash = match LoadPendingBlock::provider(self).block_hash_for_id(block_id)? {
Some(block_hash) => block_hash,
None => return Ok(None),
};
Ok(self.cache().get_sealed_block_with_senders(block_hash).await?)
}
}
}

View File

@ -0,0 +1,55 @@
//! Spawns a blocking task. CPU heavy tasks are executed with the `rayon` library. IO heavy tasks
//! are executed on the `tokio` runtime.
use futures::Future;
use reth_tasks::{pool::BlockingTaskPool, TaskSpawner};
use tokio::sync::oneshot;
use crate::{EthApiError, EthResult};
/// Executes code on a blocking thread.
pub trait SpawnBlocking: Clone + Send + Sync + 'static {
/// Returns a handle for spawning IO heavy blocking tasks.
///
/// Runtime access in default trait method implementations.
fn io_task_spawner(&self) -> impl TaskSpawner;
/// Returns a handle for spawning CPU heavy blocking tasks.
///
/// Thread pool access in default trait method implementations.
fn tracing_task_pool(&self) -> &BlockingTaskPool;
/// Executes the future on a new blocking task.
///
/// Note: This is expected for futures that are dominated by blocking IO operations, for tracing
/// or CPU bound operations in general use [`spawn_tracing`](Self::spawn_tracing).
fn spawn_blocking_io<F, R>(&self, f: F) -> impl Future<Output = EthResult<R>> + Send
where
F: FnOnce(Self) -> EthResult<R> + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = oneshot::channel();
let this = self.clone();
self.io_task_spawner().spawn_blocking(Box::pin(async move {
let res = async move { f(this) }.await;
let _ = tx.send(res);
}));
async move { rx.await.map_err(|_| EthApiError::InternalEthError)? }
}
/// Executes a blocking task on the tracing pool.
///
/// Note: This is expected for futures that are predominantly CPU bound, as it uses `rayon`
/// under the hood, for blocking IO futures use [`spawn_blocking`](Self::spawn_blocking_io). See
/// <https://ryhl.io/blog/async-what-is-blocking/>.
fn spawn_tracing<F, R>(&self, f: F) -> impl Future<Output = EthResult<R>> + Send
where
F: FnOnce(Self) -> EthResult<R> + Send + 'static,
R: Send + 'static,
{
let this = self.clone();
let fut = self.tracing_task_pool().spawn(move || f(this));
async move { fut.await.map_err(|_| EthApiError::InternalBlockingTaskError)? }
}
}

View File

@ -0,0 +1,775 @@
//! Loads a pending block from database. Helper trait for `eth_` transaction, call and trace RPC
//! methods.
use futures::Future;
use reth_evm::{ConfigureEvm, ConfigureEvmEnv};
use reth_primitives::{
revm::env::tx_env_with_recovered,
revm_primitives::{
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason,
ResultAndState, TransactTo,
},
Bytes, TransactionSignedEcRecovered, TxKind, B256, U256,
};
use reth_provider::StateProvider;
use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef};
use reth_rpc_types::{
state::{EvmOverrides, StateOverride},
AccessListWithGasUsed, BlockId, Bundle, EthCallResponse, StateContext, TransactionInfo,
TransactionRequest,
};
use revm::{Database, DatabaseCommit};
use revm_inspectors::access_list::AccessListInspector;
use tracing::trace;
use crate::{
cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
error::ensure_success,
revm_utils::{
apply_state_overrides, build_call_evm_env, caller_gas_allowance,
cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env,
},
servers::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocking, Trace},
EthApiError, EthResult, RevertError, RpcInvalidTransactionError, StateCacheDb,
ESTIMATE_GAS_ERROR_RATIO, MIN_TRANSACTION_GAS,
};
/// Execution related functions for the [`EthApiServer`](crate::EthApiServer) trait in
/// the `eth_` namespace.
pub trait EthCall: Call + LoadPendingBlock {
/// Estimate gas needed for execution of the `request` at the [`BlockId`].
fn estimate_gas_at(
&self,
request: TransactionRequest,
at: BlockId,
state_override: Option<StateOverride>,
) -> impl Future<Output = EthResult<U256>> + Send {
Call::estimate_gas_at(self, request, at, state_override)
}
/// Executes the call request (`eth_call`) and returns the output
fn call(
&self,
request: TransactionRequest,
block_number: Option<BlockId>,
overrides: EvmOverrides,
) -> impl Future<Output = EthResult<Bytes>> + Send {
async move {
let (res, _env) =
self.transact_call_at(request, block_number.unwrap_or_default(), overrides).await?;
ensure_success(res.result)
}
}
/// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the
/// optionality of state overrides
fn call_many(
&self,
bundle: Bundle,
state_context: Option<StateContext>,
mut state_override: Option<StateOverride>,
) -> impl Future<Output = EthResult<Vec<EthCallResponse>>> + Send
where
Self: LoadBlock,
{
async move {
let Bundle { transactions, block_override } = bundle;
if transactions.is_empty() {
return Err(EthApiError::InvalidParams(String::from("transactions are empty.")))
}
let StateContext { transaction_index, block_number } =
state_context.unwrap_or_default();
let transaction_index = transaction_index.unwrap_or_default();
let target_block = block_number.unwrap_or_default();
let is_block_target_pending = target_block.is_pending();
let ((cfg, block_env, _), block) = futures::try_join!(
self.evm_env_at(target_block),
self.block_with_senders(target_block)
)?;
let Some(block) = block else { return Err(EthApiError::UnknownBlockNumber) };
let gas_limit = self.call_gas_limit();
// we're essentially replaying the transactions in the block here, hence we need the
// state that points to the beginning of the block, which is the state at
// the parent block
let mut at = block.parent_hash;
let mut replay_block_txs = true;
let num_txs = transaction_index.index().unwrap_or(block.body.len());
// but if all transactions are to be replayed, we can use the state at the block itself,
// however only if we're not targeting the pending block, because for pending we can't
// rely on the block's state being available
if !is_block_target_pending && num_txs == block.body.len() {
at = block.hash();
replay_block_txs = false;
}
let this = self.clone();
self.spawn_with_state_at_block(at.into(), move |state| {
let mut results = Vec::with_capacity(transactions.len());
let mut db = CacheDB::new(StateProviderDatabase::new(state));
if replay_block_txs {
// only need to replay the transactions in the block if not all transactions are
// to be replayed
let transactions = block.into_transactions_ecrecovered().take(num_txs);
for tx in transactions {
let tx = tx_env_with_recovered(&tx);
let env =
EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx);
let (res, _) = this.transact(&mut db, env)?;
db.commit(res.state);
}
}
let block_overrides = block_override.map(Box::new);
let mut transactions = transactions.into_iter().peekable();
while let Some(tx) = transactions.next() {
// apply state overrides only once, before the first transaction
let state_overrides = state_override.take();
let overrides = EvmOverrides::new(state_overrides, block_overrides.clone());
let env = prepare_call_env(
cfg.clone(),
block_env.clone(),
tx,
gas_limit,
&mut db,
overrides,
)?;
let (res, _) = this.transact(&mut db, env)?;
match ensure_success(res.result) {
Ok(output) => {
results.push(EthCallResponse { value: Some(output), error: None });
}
Err(err) => {
results.push(EthCallResponse {
value: None,
error: Some(err.to_string()),
});
}
}
if transactions.peek().is_some() {
// need to apply the state changes of this call before executing the next
// call
db.commit(res.state);
}
}
Ok(results)
})
.await
}
}
/// Creates [`AccessListWithGasUsed`] for the [`TransactionRequest`] at the given
/// [`BlockId`], or latest block.
fn create_access_list_at(
&self,
request: TransactionRequest,
block_number: Option<BlockId>,
) -> impl Future<Output = EthResult<AccessListWithGasUsed>> + Send
where
Self: Trace,
{
async move {
let block_id = block_number.unwrap_or_default();
let (cfg, block, at) = self.evm_env_at(block_id).await?;
self.spawn_blocking_io(move |this| {
this.create_access_list_with(cfg, block, at, request)
})
.await
}
}
/// Creates [`AccessListWithGasUsed`] for the [`TransactionRequest`] at the given
/// [`BlockId`].
fn create_access_list_with(
&self,
cfg: CfgEnvWithHandlerCfg,
block: BlockEnv,
at: BlockId,
mut request: TransactionRequest,
) -> EthResult<AccessListWithGasUsed>
where
Self: Trace,
{
let state = self.state_at_block_id(at)?;
let mut env = build_call_evm_env(cfg, block, request.clone())?;
// we want to disable this in eth_createAccessList, since this is common practice used by
// other node impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
env.cfg.disable_block_gas_limit = true;
// The basefee should be ignored for eth_createAccessList
// See:
// <https://github.com/ethereum/go-ethereum/blob/8990c92aea01ca07801597b00c0d83d4e2d9b811/internal/ethapi/api.go#L1476-L1476>
env.cfg.disable_base_fee = true;
let mut db = CacheDB::new(StateProviderDatabase::new(state));
if request.gas.is_none() && env.tx.gas_price > U256::ZERO {
// no gas limit was provided in the request, so we need to cap the request's gas limit
cap_tx_gas_limit_with_caller_allowance(&mut db, &mut env.tx)?;
}
let from = request.from.unwrap_or_default();
let to = if let Some(TxKind::Call(to)) = request.to {
to
} else {
let nonce = db.basic_ref(from)?.unwrap_or_default().nonce;
from.create(nonce)
};
// can consume the list since we're not using the request anymore
let initial = request.access_list.take().unwrap_or_default();
let precompiles = get_precompiles(env.handler_cfg.spec_id);
let mut inspector = AccessListInspector::new(initial, from, to, precompiles);
let (result, env) = self.inspect(&mut db, env, &mut inspector)?;
match result.result {
ExecutionResult::Halt { reason, .. } => Err(match reason {
HaltReason::NonceOverflow => RpcInvalidTransactionError::NonceMaxValue,
halt => RpcInvalidTransactionError::EvmHalt(halt),
}),
ExecutionResult::Revert { output, .. } => {
Err(RpcInvalidTransactionError::Revert(RevertError::new(output)))
}
ExecutionResult::Success { .. } => Ok(()),
}?;
let access_list = inspector.into_access_list();
let cfg_with_spec_id =
CfgEnvWithHandlerCfg { cfg_env: env.cfg.clone(), handler_cfg: env.handler_cfg };
// calculate the gas used using the access list
request.access_list = Some(access_list.clone());
let gas_used =
self.estimate_gas_with(cfg_with_spec_id, env.block.clone(), request, &*db.db, None)?;
Ok(AccessListWithGasUsed { access_list, gas_used })
}
}
/// Executes code on state.
pub trait Call: LoadState + SpawnBlocking {
/// Returns default gas limit to use for `eth_call` and tracing RPC methods.
///
/// Data access in default trait method implementations.
fn call_gas_limit(&self) -> u64;
/// Returns a handle for reading evm config.
///
/// Data access in default (L1) trait method implementations.
fn evm_config(&self) -> &impl ConfigureEvm;
/// Executes the closure with the state that corresponds to the given [`BlockId`].
fn with_state_at_block<F, T>(&self, at: BlockId, f: F) -> EthResult<T>
where
F: FnOnce(StateProviderTraitObjWrapper<'_>) -> EthResult<T>,
{
let state = self.state_at_block_id(at)?;
f(StateProviderTraitObjWrapper(&state))
}
/// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state
/// changes.
fn transact<DB>(
&self,
db: DB,
env: EnvWithHandlerCfg,
) -> EthResult<(ResultAndState, EnvWithHandlerCfg)>
where
DB: Database,
<DB as Database>::Error: Into<EthApiError>,
{
let mut evm = self.evm_config().evm_with_env(db, env);
let res = evm.transact()?;
let (_, env) = evm.into_db_and_env_with_handler_cfg();
Ok((res, env))
}
/// Executes the call request at the given [`BlockId`].
fn transact_call_at(
&self,
request: TransactionRequest,
at: BlockId,
overrides: EvmOverrides,
) -> impl Future<Output = EthResult<(ResultAndState, EnvWithHandlerCfg)>> + Send
where
Self: LoadPendingBlock,
{
let this = self.clone();
self.spawn_with_call_at(request, at, overrides, move |db, env| this.transact(db, env))
}
/// Executes the closure with the state that corresponds to the given [`BlockId`] on a new task
fn spawn_with_state_at_block<F, T>(
&self,
at: BlockId,
f: F,
) -> impl Future<Output = EthResult<T>> + Send
where
F: FnOnce(StateProviderTraitObjWrapper<'_>) -> EthResult<T> + Send + 'static,
T: Send + 'static,
{
self.spawn_tracing(move |this| {
let state = this.state_at_block_id(at)?;
f(StateProviderTraitObjWrapper(&state))
})
}
/// Prepares the state and env for the given [`TransactionRequest`] at the given [`BlockId`] and
/// executes the closure on a new task returning the result of the closure.
///
/// This returns the configured [`EnvWithHandlerCfg`] for the given [`TransactionRequest`] at
/// the given [`BlockId`] and with configured call settings: `prepare_call_env`.
fn spawn_with_call_at<F, R>(
&self,
request: TransactionRequest,
at: BlockId,
overrides: EvmOverrides,
f: F,
) -> impl Future<Output = EthResult<R>> + Send
where
Self: LoadPendingBlock,
F: FnOnce(StateCacheDbRefMutWrapper<'_, '_>, EnvWithHandlerCfg) -> EthResult<R>
+ Send
+ 'static,
R: Send + 'static,
{
async move {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
let this = self.clone();
self.spawn_tracing(move |_| {
let state = this.state_at_block_id(at)?;
let mut db =
CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state)));
let env = prepare_call_env(
cfg,
block_env,
request,
this.call_gas_limit(),
&mut db,
overrides,
)?;
f(StateCacheDbRefMutWrapper(&mut db), env)
})
.await
.map_err(|_| EthApiError::InternalBlockingTaskError)
}
}
/// Retrieves the transaction if it exists and executes it.
///
/// Before the transaction is executed, all previous transaction in the block are applied to the
/// state by executing them first.
/// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
/// and the database that points to the beginning of the transaction.
///
/// Note: Implementers should use a threadpool where blocking is allowed, such as
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
fn spawn_replay_transaction<F, R>(
&self,
hash: B256,
f: F,
) -> impl Future<Output = EthResult<Option<R>>> + Send
where
Self: LoadBlock + LoadPendingBlock + LoadTransaction,
F: FnOnce(TransactionInfo, ResultAndState, StateCacheDb<'_>) -> EthResult<R>
+ Send
+ 'static,
R: Send + 'static,
{
async move {
let (transaction, block) = match self.transaction_and_block(hash).await? {
None => return Ok(None),
Some(res) => res,
};
let (tx, tx_info) = transaction.split();
let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?;
// we need to get the state of the parent block because we're essentially replaying the
// block the transaction is included in
let parent_block = block.parent_hash;
let block_txs = block.into_transactions_ecrecovered();
let this = self.clone();
self.spawn_with_state_at_block(parent_block.into(), move |state| {
let mut db = CacheDB::new(StateProviderDatabase::new(state));
// replay all transactions prior to the targeted transaction
this.replay_transactions_until(
&mut db,
cfg.clone(),
block_env.clone(),
block_txs,
tx.hash,
)?;
let env =
EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx));
let (res, _) = this.transact(&mut db, env)?;
f(tx_info, res, db)
})
.await
.map(Some)
}
}
/// Replays all the transactions until the target transaction is found.
///
/// All transactions before the target transaction are executed and their changes are written to
/// the _runtime_ db ([`CacheDB`]).
///
/// Note: This assumes the target transaction is in the given iterator.
/// Returns the index of the target transaction in the given iterator.
fn replay_transactions_until<DB>(
&self,
db: &mut CacheDB<DB>,
cfg: CfgEnvWithHandlerCfg,
block_env: BlockEnv,
transactions: impl IntoIterator<Item = TransactionSignedEcRecovered>,
target_tx_hash: B256,
) -> Result<usize, EthApiError>
where
DB: DatabaseRef,
EthApiError: From<<DB as DatabaseRef>::Error>,
{
let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default());
let mut evm = self.evm_config().evm_with_env(db, env);
let mut index = 0;
for tx in transactions {
if tx.hash() == target_tx_hash {
// reached the target transaction
break
}
let sender = tx.signer();
self.evm_config().fill_tx_env(evm.tx_mut(), &tx.into_signed(), sender);
evm.transact_commit()?;
index += 1;
}
Ok(index)
}
/// Estimate gas needed for execution of the `request` at the [`BlockId`].
fn estimate_gas_at(
&self,
request: TransactionRequest,
at: BlockId,
state_override: Option<StateOverride>,
) -> impl Future<Output = EthResult<U256>> + Send
where
Self: LoadPendingBlock,
{
async move {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
self.spawn_blocking_io(move |this| {
let state = this.state_at_block_id(at)?;
this.estimate_gas_with(cfg, block_env, request, state, state_override)
})
.await
}
}
/// Estimates the gas usage of the `request` with the state.
///
/// This will execute the [`TransactionRequest`] and find the best gas limit via binary search
fn estimate_gas_with<S>(
&self,
mut cfg: CfgEnvWithHandlerCfg,
block: BlockEnv,
request: TransactionRequest,
state: S,
state_override: Option<StateOverride>,
) -> EthResult<U256>
where
S: StateProvider,
{
// Disabled because eth_estimateGas is sometimes used with eoa senders
// See <https://github.com/paradigmxyz/reth/issues/1959>
cfg.disable_eip3607 = true;
// The basefee should be ignored for eth_createAccessList
// See:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
cfg.disable_base_fee = true;
// Keep a copy of gas related request values
let tx_request_gas_limit = request.gas;
let tx_request_gas_price = request.gas_price;
let block_env_gas_limit = block.gas_limit;
// Determine the highest possible gas limit, considering both the request's specified limit
// and the block's limit.
let mut highest_gas_limit = tx_request_gas_limit
.map(|tx_gas_limit| U256::from(tx_gas_limit).max(block_env_gas_limit))
.unwrap_or(block_env_gas_limit);
// Configure the evm env
let mut env = build_call_evm_env(cfg, block, request)?;
let mut db = CacheDB::new(StateProviderDatabase::new(state));
// Apply any state overrides if specified.
if let Some(state_override) = state_override {
apply_state_overrides(state_override, &mut db)?;
}
// Optimize for simple transfer transactions, potentially reducing the gas estimate.
if env.tx.data.is_empty() {
if let TransactTo::Call(to) = env.tx.transact_to {
if let Ok(code) = db.db.account_code(to) {
let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true);
if no_code_callee {
// If the tx is a simple transfer (call to an account with no code) we can
// shortcircuit. But simply returning
// `MIN_TRANSACTION_GAS` is dangerous because there might be additional
// field combos that bump the price up, so we try executing the function
// with the minimum gas limit to make sure.
let mut env = env.clone();
env.tx.gas_limit = MIN_TRANSACTION_GAS;
if let Ok((res, _)) = self.transact(&mut db, env) {
if res.result.is_success() {
return Ok(U256::from(MIN_TRANSACTION_GAS))
}
}
}
}
}
}
// Check funds of the sender (only useful to check if transaction gas price is more than 0).
//
// The caller allowance is check by doing `(account.balance - tx.value) / tx.gas_price`
if env.tx.gas_price > U256::ZERO {
// cap the highest gas limit by max gas caller can afford with given gas price
highest_gas_limit = highest_gas_limit.min(caller_gas_allowance(&mut db, &env.tx)?);
}
// We can now normalize the highest gas limit to a u64
let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX);
// If the provided gas limit is less than computed cap, use that
env.tx.gas_limit = env.tx.gas_limit.min(highest_gas_limit);
trace!(target: "rpc::eth::estimate", ?env, "Starting gas estimation");
// Execute the transaction with the highest possible gas limit.
let (mut res, mut env) = match self.transact(&mut db, env.clone()) {
// Handle the exceptional case where the transaction initialization uses too much gas.
// If the gas price or gas limit was specified in the request, retry the transaction
// with the block's gas limit to determine if the failure was due to
// insufficient gas.
Err(EthApiError::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh))
if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() =>
{
return Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db))
}
// Propagate other results (successful or other errors).
ethres => ethres?,
};
let gas_refund = match res.result {
ExecutionResult::Success { gas_refunded, .. } => gas_refunded,
ExecutionResult::Halt { reason, gas_used } => {
// here we don't check for invalid opcode because already executed with highest gas
// limit
return Err(RpcInvalidTransactionError::halt(reason, gas_used).into())
}
ExecutionResult::Revert { output, .. } => {
// if price or limit was included in the request then we can execute the request
// again with the block's gas limit to check if revert is gas related or not
return if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() {
Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db))
} else {
// the transaction did revert
Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into())
}
}
};
// At this point we know the call succeeded but want to find the _best_ (lowest) gas the
// transaction succeeds with. We find this by doing a binary search over the possible range.
//
// NOTE: this is the gas the transaction used, which is less than the
// transaction requires to succeed.
let mut gas_used = res.result.gas_used();
// the lowest value is capped by the gas used by the unconstrained transaction
let mut lowest_gas_limit = gas_used.saturating_sub(1);
// As stated in Geth, there is a good chance that the transaction will pass if we set the
// gas limit to the execution gas used plus the gas refund, so we check this first
// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L135
//
// Calculate the optimistic gas limit by adding gas used and gas refund,
// then applying a 64/63 multiplier to account for gas forwarding rules.
let optimistic_gas_limit = (gas_used + gas_refund) * 64 / 63;
if optimistic_gas_limit < highest_gas_limit {
// Set the transaction's gas limit to the calculated optimistic gas limit.
env.tx.gas_limit = optimistic_gas_limit;
// Re-execute the transaction with the new gas limit and update the result and
// environment.
(res, env) = self.transact(&mut db, env)?;
// Update the gas used based on the new result.
gas_used = res.result.gas_used();
// Update the gas limit estimates (highest and lowest) based on the execution result.
self.update_estimated_gas_range(
res.result,
optimistic_gas_limit,
&mut highest_gas_limit,
&mut lowest_gas_limit,
)?;
};
// Pick a point that's close to the estimated gas
let mut mid_gas_limit = std::cmp::min(
gas_used * 3,
((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64,
);
trace!(target: "rpc::eth::estimate", ?env, ?highest_gas_limit, ?lowest_gas_limit, ?mid_gas_limit, "Starting binary search for gas");
// Binary search narrows the range to find the minimum gas limit needed for the transaction
// to succeed.
while (highest_gas_limit - lowest_gas_limit) > 1 {
// An estimation error is allowed once the current gas limit range used in the binary
// search is small enough (less than 1.5% of the highest gas limit)
// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L152
if (highest_gas_limit - lowest_gas_limit) as f64 / (highest_gas_limit as f64) <
ESTIMATE_GAS_ERROR_RATIO
{
break
};
env.tx.gas_limit = mid_gas_limit;
// Execute transaction and handle potential gas errors, adjusting limits accordingly.
match self.transact(&mut db, env.clone()) {
// Check if the error is due to gas being too high.
Err(EthApiError::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh)) => {
// Increase the lowest gas limit if gas is too high
lowest_gas_limit = mid_gas_limit;
}
// Handle other cases, including successful transactions.
ethres => {
// Unpack the result and environment if the transaction was successful.
(res, env) = ethres?;
// Update the estimated gas range based on the transaction result.
self.update_estimated_gas_range(
res.result,
mid_gas_limit,
&mut highest_gas_limit,
&mut lowest_gas_limit,
)?;
}
}
// New midpoint
mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64;
}
Ok(U256::from(highest_gas_limit))
}
/// Updates the highest and lowest gas limits for binary search based on the execution result.
///
/// This function refines the gas limit estimates used in a binary search to find the optimal
/// gas limit for a transaction. It adjusts the highest or lowest gas limits depending on
/// whether the execution succeeded, reverted, or halted due to specific reasons.
#[inline]
fn update_estimated_gas_range(
&self,
result: ExecutionResult,
tx_gas_limit: u64,
highest_gas_limit: &mut u64,
lowest_gas_limit: &mut u64,
) -> EthResult<()> {
match result {
ExecutionResult::Success { .. } => {
// Cap the highest gas limit with the succeeding gas limit.
*highest_gas_limit = tx_gas_limit;
}
ExecutionResult::Revert { .. } => {
// Increase the lowest gas limit.
*lowest_gas_limit = tx_gas_limit;
}
ExecutionResult::Halt { reason, .. } => {
match reason {
HaltReason::OutOfGas(_) | HaltReason::InvalidEFOpcode => {
// Both `OutOfGas` and `InvalidEFOpcode` can occur dynamically if the gas
// left is too low. Treat this as an out of gas
// condition, knowing that the call succeeds with a
// higher gas limit.
//
// Common usage of invalid opcode in OpenZeppelin:
// <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/metatx/ERC2771Forwarder.sol#L360-L367>
// Increase the lowest gas limit.
*lowest_gas_limit = tx_gas_limit;
}
err => {
// These cases should be unreachable because we know the transaction
// succeeds, but if they occur, treat them as an
// error.
return Err(RpcInvalidTransactionError::EvmHalt(err).into())
}
}
}
};
Ok(())
}
/// Executes the requests again after an out of gas error to check if the error is gas related
/// or not
#[inline]
fn map_out_of_gas_err<S>(
&self,
env_gas_limit: U256,
mut env: EnvWithHandlerCfg,
db: &mut CacheDB<StateProviderDatabase<S>>,
) -> EthApiError
where
S: StateProvider,
{
let req_gas_limit = env.tx.gas_limit;
env.tx.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX);
let (res, _) = match self.transact(db, env) {
Ok(res) => res,
Err(err) => return err,
};
match res.result {
ExecutionResult::Success { .. } => {
// transaction succeeded by manually increasing the gas limit to
// highest, which means the caller lacks funds to pay for the tx
RpcInvalidTransactionError::BasicOutOfGas(req_gas_limit).into()
}
ExecutionResult::Revert { output, .. } => {
// reverted again after bumping the limit
RpcInvalidTransactionError::Revert(RevertError::new(output)).into()
}
ExecutionResult::Halt { reason, .. } => {
RpcInvalidTransactionError::EvmHalt(reason).into()
}
}
}
}

View File

@ -0,0 +1,346 @@
//! Loads fee history from database. Helper trait for `eth_` fee and transaction RPC methods.
use futures::Future;
use reth_primitives::U256;
use reth_provider::{BlockIdReader, BlockReaderIdExt, ChainSpecProvider, HeaderProvider};
use reth_rpc_types::{BlockNumberOrTag, FeeHistory};
use tracing::debug;
use crate::{
fee_history::calculate_reward_percentiles_for_block, servers::LoadBlock, EthApiError,
EthResult, EthStateCache, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle,
RpcInvalidTransactionError,
};
/// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the
/// `eth_` namespace.
pub trait EthFees: LoadFee {
/// Returns a suggestion for a gas price for legacy transactions.
///
/// See also: <https://github.com/ethereum/pm/issues/328#issuecomment-853234014>
fn gas_price(&self) -> impl Future<Output = EthResult<U256>> + Send
where
Self: LoadBlock,
{
LoadFee::gas_price(self)
}
/// Returns a suggestion for a base fee for blob transactions.
fn blob_base_fee(&self) -> impl Future<Output = EthResult<U256>> + Send
where
Self: LoadBlock,
{
LoadFee::blob_base_fee(self)
}
/// Returns a suggestion for the priority fee (the tip)
fn suggested_priority_fee(&self) -> impl Future<Output = EthResult<U256>> + Send
where
Self: 'static,
{
LoadFee::suggested_priority_fee(self)
}
/// Reports the fee history, for the given amount of blocks, up until the given newest block.
///
/// If `reward_percentiles` are provided the [`FeeHistory`] will include the _approximated_
/// rewards for the requested range.
fn fee_history(
&self,
mut block_count: u64,
newest_block: BlockNumberOrTag,
reward_percentiles: Option<Vec<f64>>,
) -> impl Future<Output = EthResult<FeeHistory>> + Send {
async move {
if block_count == 0 {
return Ok(FeeHistory::default())
}
// See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225
let max_fee_history = if reward_percentiles.is_none() {
self.gas_oracle().config().max_header_history
} else {
self.gas_oracle().config().max_block_history
};
if block_count > max_fee_history {
debug!(
requested = block_count,
truncated = max_fee_history,
"Sanitizing fee history block count"
);
block_count = max_fee_history
}
let Some(end_block) =
LoadFee::provider(self).block_number_for_id(newest_block.into())?
else {
return Err(EthApiError::UnknownBlockNumber)
};
// need to add 1 to the end block to get the correct (inclusive) range
let end_block_plus = end_block + 1;
// Ensure that we would not be querying outside of genesis
if end_block_plus < block_count {
block_count = end_block_plus;
}
// If reward percentiles were specified, we
// need to validate that they are monotonically
// increasing and 0 <= p <= 100
// Note: The types used ensure that the percentiles are never < 0
if let Some(percentiles) = &reward_percentiles {
if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) {
return Err(EthApiError::InvalidRewardPercentiles)
}
}
// Fetch the headers and ensure we got all of them
//
// Treat a request for 1 block as a request for `newest_block..=newest_block`,
// otherwise `newest_block - 2
// NOTE: We ensured that block count is capped
let start_block = end_block_plus - block_count;
// Collect base fees, gas usage ratios and (optionally) reward percentile data
let mut base_fee_per_gas: Vec<u128> = Vec::new();
let mut gas_used_ratio: Vec<f64> = Vec::new();
let mut base_fee_per_blob_gas: Vec<u128> = Vec::new();
let mut blob_gas_used_ratio: Vec<f64> = Vec::new();
let mut rewards: Vec<Vec<u128>> = Vec::new();
// Check if the requested range is within the cache bounds
let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await;
if let Some(fee_entries) = fee_entries {
if fee_entries.len() != block_count as usize {
return Err(EthApiError::InvalidBlockRange)
}
for entry in &fee_entries {
base_fee_per_gas.push(entry.base_fee_per_gas as u128);
gas_used_ratio.push(entry.gas_used_ratio);
base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default());
blob_gas_used_ratio.push(entry.blob_gas_used_ratio);
if let Some(percentiles) = &reward_percentiles {
let mut block_rewards = Vec::with_capacity(percentiles.len());
for &percentile in percentiles {
block_rewards.push(self.approximate_percentile(entry, percentile));
}
rewards.push(block_rewards);
}
}
let last_entry = fee_entries.last().expect("is not empty");
// Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the
// next block
base_fee_per_gas
.push(last_entry.next_block_base_fee(&LoadFee::provider(self).chain_spec())
as u128);
base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default());
} else {
// read the requested header range
let headers = LoadFee::provider(self).sealed_headers_range(start_block..=end_block)?;
if headers.len() != block_count as usize {
return Err(EthApiError::InvalidBlockRange)
}
for header in &headers {
base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128);
gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64);
base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default());
blob_gas_used_ratio.push(
header.blob_gas_used.unwrap_or_default() as f64 /
reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64,
);
// Percentiles were specified, so we need to collect reward percentile ino
if let Some(percentiles) = &reward_percentiles {
let (transactions, receipts) = LoadFee::cache(self)
.get_transactions_and_receipts(header.hash())
.await?
.ok_or(EthApiError::InvalidBlockRange)?;
rewards.push(
calculate_reward_percentiles_for_block(
percentiles,
header.gas_used,
header.base_fee_per_gas.unwrap_or_default(),
&transactions,
&receipts,
)
.unwrap_or_default(),
);
}
}
// The spec states that `base_fee_per_gas` "[..] includes the next block after the
// newest of the returned range, because this value can be derived from the
// newest block"
//
// The unwrap is safe since we checked earlier that we got at least 1 header.
let last_header = headers.last().expect("is present");
base_fee_per_gas.push(
LoadFee::provider(self).chain_spec().base_fee_params_at_timestamp(last_header.timestamp).next_block_base_fee(
last_header.gas_used as u128,
last_header.gas_limit as u128,
last_header.base_fee_per_gas.unwrap_or_default() as u128,
));
// Same goes for the `base_fee_per_blob_gas`:
// > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block.
base_fee_per_blob_gas
.push(last_header.next_block_blob_fee().unwrap_or_default());
};
Ok(FeeHistory {
base_fee_per_gas,
gas_used_ratio,
base_fee_per_blob_gas,
blob_gas_used_ratio,
oldest_block: start_block,
reward: reward_percentiles.map(|_| rewards),
})
}
}
/// Approximates reward at a given percentile for a specific block
/// Based on the configured resolution
fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 {
let resolution = self.fee_history_cache().resolution();
let rounded_percentile =
(requested_percentile * resolution as f64).round() / resolution as f64;
let clamped_percentile = rounded_percentile.clamp(0.0, 100.0);
// Calculate the index in the precomputed rewards array
let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize;
// Fetch the reward from the FeeHistoryEntry
entry.rewards.get(index).cloned().unwrap_or_default()
}
}
/// Loads fee from database.
///
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` fees RPC methods.
pub trait LoadFee: LoadBlock {
// Returns a handle for reading data from disk.
///
/// Data access in default (L1) trait method implementations.
fn provider(&self) -> impl BlockIdReader + HeaderProvider + ChainSpecProvider;
/// Returns a handle for reading data from memory.
///
/// Data access in default (L1) trait method implementations.
fn cache(&self) -> &EthStateCache;
/// Returns a handle for reading gas price.
///
/// Data access in default (L1) trait method implementations.
fn gas_oracle(&self) -> &GasPriceOracle<impl BlockReaderIdExt>;
/// Returns a handle for reading fee history data from memory.
///
/// Data access in default (L1) trait method implementations.
fn fee_history_cache(&self) -> &FeeHistoryCache;
/// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy
/// transactions.
fn legacy_gas_price(
&self,
gas_price: Option<U256>,
) -> impl Future<Output = EthResult<U256>> + Send {
async move {
match gas_price {
Some(gas_price) => Ok(gas_price),
None => {
// fetch a suggested gas price
self.gas_price().await
}
}
}
}
/// Returns the EIP-1559 fees if they are set, otherwise fetches a suggested gas price for
/// EIP-1559 transactions.
///
/// Returns (`max_fee`, `priority_fee`)
fn eip1559_fees(
&self,
max_fee_per_gas: Option<U256>,
max_priority_fee_per_gas: Option<U256>,
) -> impl Future<Output = EthResult<(U256, U256)>> + Send {
async move {
let max_fee_per_gas = match max_fee_per_gas {
Some(max_fee_per_gas) => max_fee_per_gas,
None => {
// fetch pending base fee
let base_fee = self
.block(BlockNumberOrTag::Pending.into())
.await?
.ok_or(EthApiError::UnknownBlockNumber)?
.base_fee_per_gas
.ok_or_else(|| {
EthApiError::InvalidTransaction(
RpcInvalidTransactionError::TxTypeNotSupported,
)
})?;
U256::from(base_fee)
}
};
let max_priority_fee_per_gas = match max_priority_fee_per_gas {
Some(max_priority_fee_per_gas) => max_priority_fee_per_gas,
None => self.suggested_priority_fee().await?,
};
Ok((max_fee_per_gas, max_priority_fee_per_gas))
}
}
/// Returns the EIP-4844 blob fee if it is set, otherwise fetches a blob fee.
fn eip4844_blob_fee(
&self,
blob_fee: Option<U256>,
) -> impl Future<Output = EthResult<U256>> + Send {
async move {
match blob_fee {
Some(blob_fee) => Ok(blob_fee),
None => self.blob_base_fee().await,
}
}
}
/// Returns a suggestion for a gas price for legacy transactions.
///
/// See also: <https://github.com/ethereum/pm/issues/328#issuecomment-853234014>
fn gas_price(&self) -> impl Future<Output = EthResult<U256>> + Send {
let header = self.block(BlockNumberOrTag::Latest.into());
let suggested_tip = self.suggested_priority_fee();
async move {
let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?;
let base_fee = header.and_then(|h| h.base_fee_per_gas).unwrap_or_default();
Ok(suggested_tip + U256::from(base_fee))
}
}
/// Returns a suggestion for a base fee for blob transactions.
fn blob_base_fee(&self) -> impl Future<Output = EthResult<U256>> + Send {
async move {
self.block(BlockNumberOrTag::Latest.into())
.await?
.and_then(|h: reth_primitives::SealedBlock| h.next_block_blob_fee())
.ok_or(EthApiError::ExcessBlobGasNotSet)
.map(U256::from)
}
}
/// Returns a suggestion for the priority fee (the tip)
fn suggested_priority_fee(&self) -> impl Future<Output = EthResult<U256>> + Send
where
Self: 'static,
{
self.gas_oracle().suggest_tip_cap()
}
}

View File

@ -0,0 +1,45 @@
//! Behaviour needed to serve `eth_` RPC requests, divided into general database reads and
//! specific database access.
//!
//! Traits with `Load` prefix, read atomic data from database, e.g. a block or transaction. Any
//! database read done in more than one default `Eth` trait implementation, is defined in a `Load`
//! trait.
//!
//! Traits with `Eth` prefix, compose specific data needed to serve RPC requests in the `eth`
//! namespace. They use `Load` traits as building blocks.
//! [`EthTransactions`](crate::servers::EthTransactions) also writes data (submits transactions).
//! Based on the `eth_` request method semantics, request methods are divided into:
//! [`EthTransactions`](crate::servers::EthTransactions), [`EthBlocks`](crate::servers::EthBlocks),
//! [`EthFees`](crate::servers::EthFees), [`EthState`](crate::servers::EthState) and
//! [`EthCall`](crate::servers::EthCall). Default implementation of the `Eth` traits, is done w.r.t.
//! L1.
//!
//! [`EthApiServer`](crate::EthApiServer), is implemented for any type that implements
//! all the `Eth` traits, e.g. [`EthApi`](crate::EthApi).
pub mod block;
pub mod blocking_task;
pub mod call;
pub mod fee;
pub mod pending_block;
pub mod receipt;
pub mod signer;
pub mod spec;
pub mod state;
pub mod trace;
pub mod transaction;
use block::LoadBlock;
use blocking_task::SpawnBlocking;
use call::Call;
use pending_block::LoadPendingBlock;
use trace::Trace;
use transaction::LoadTransaction;
/// Extension trait that bundles traits needed for tracing transactions.
pub trait TraceExt:
LoadTransaction + LoadBlock + LoadPendingBlock + SpawnBlocking + Trace + Call
{
}
impl<T> TraceExt for T where T: LoadTransaction + LoadBlock + LoadPendingBlock + Trace + Call {}

View File

@ -1,63 +1,209 @@
//! Support for building a pending block via local txpool. //! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace
//! RPC methods.
use crate::eth::error::{EthApiError, EthResult}; use std::time::{Duration, Instant};
use reth_chainspec::ChainSpec;
use reth_errors::ProviderError; use futures::Future;
use reth_evm::ConfigureEvm;
use reth_execution_types::ExecutionOutcome; use reth_execution_types::ExecutionOutcome;
use reth_primitives::{ use reth_primitives::{
constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH}, constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH},
proofs, proofs::calculate_transaction_root,
revm::env::tx_env_with_recovered, revm::env::tx_env_with_recovered,
revm_primitives::{ revm_primitives::{
BlockEnv, CfgEnvWithHandlerCfg, EVMError, Env, InvalidTransaction, ResultAndState, SpecId, BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EVMError, Env, ExecutionResult, InvalidTransaction,
ResultAndState, SpecId,
}, },
Block, BlockId, BlockNumberOrTag, Header, IntoRecoveredTransaction, Receipt, Requests, Block, BlockNumber, Header, IntoRecoveredTransaction, Receipt, Requests,
SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256, SealedBlockWithSenders, SealedHeader, TransactionSignedEcRecovered, B256,
EMPTY_OMMER_ROOT_HASH, U256,
};
use reth_provider::{
BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory,
}; };
use reth_provider::{ChainSpecProvider, StateProviderFactory};
use reth_revm::{ use reth_revm::{
database::StateProviderDatabase, database::StateProviderDatabase, state_change::post_block_withdrawals_balance_increments,
state_change::{
apply_beacon_root_contract_call, apply_blockhashes_update,
post_block_withdrawals_balance_increments,
},
}; };
use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool}; use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool};
use revm::{db::states::bundle_state::BundleRetention, Database, DatabaseCommit, State}; use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State};
use revm_primitives::EnvWithHandlerCfg; use tokio::sync::Mutex;
use std::time::Instant; use tracing::debug;
/// Configured [`BlockEnv`] and [`CfgEnvWithHandlerCfg`] for a pending block use crate::{
#[derive(Debug, Clone)] pending_block::{pre_block_beacon_root_contract_call, pre_block_blockhashes_update},
pub(crate) struct PendingBlockEnv { servers::SpawnBlocking,
/// Configured [`CfgEnvWithHandlerCfg`] for the pending block. EthApiError, EthResult, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin,
pub(crate) cfg: CfgEnvWithHandlerCfg, };
/// Configured [`BlockEnv`] for the pending block.
pub(crate) block_env: BlockEnv,
/// Origin block for the config
pub(crate) origin: PendingBlockEnvOrigin,
}
impl PendingBlockEnv { /// Loads a pending block from database.
/// Builds a pending block using the given client and pool. ///
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
#[auto_impl::auto_impl(&, Arc)]
pub trait LoadPendingBlock {
/// Returns a handle for reading data from disk.
///
/// Data access in default (L1) trait method implementations.
fn provider(
&self,
) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory;
/// Returns a handle for reading data from transaction pool.
///
/// Data access in default (L1) trait method implementations.
fn pool(&self) -> impl TransactionPool;
/// Returns a handle to the pending block.
///
/// Data access in default (L1) trait method implementations.
fn pending_block(&self) -> &Mutex<Option<PendingBlock>>;
/// Returns a handle for reading evm config.
///
/// Data access in default (L1) trait method implementations.
fn evm_config(&self) -> &impl ConfigureEvm;
/// Configures the [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the pending block
///
/// If no pending block is available, this will derive it from the `latest` block
fn pending_block_env_and_cfg(&self) -> EthResult<PendingBlockEnv> {
let origin: PendingBlockEnvOrigin = if let Some(pending) =
self.provider().pending_block_with_senders()?
{
PendingBlockEnvOrigin::ActualPending(pending)
} else {
// no pending block from the CL yet, so we use the latest block and modify the env
// values that we can
let latest =
self.provider().latest_header()?.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
let (mut latest_header, block_hash) = latest.split();
// child block
latest_header.number += 1;
// assumed child block is in the next slot: 12s
latest_header.timestamp += 12;
// base fee of the child block
let chain_spec = self.provider().chain_spec();
latest_header.base_fee_per_gas = latest_header.next_block_base_fee(
chain_spec.base_fee_params_at_timestamp(latest_header.timestamp),
);
// update excess blob gas consumed above target
latest_header.excess_blob_gas = latest_header.next_block_excess_blob_gas();
// we're reusing the same block hash because we need this to lookup the block's state
let latest = SealedHeader::new(latest_header, block_hash);
PendingBlockEnvOrigin::DerivedFromLatest(latest)
};
let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::LATEST);
let mut block_env = BlockEnv::default();
// Note: for the PENDING block we assume it is past the known merge block and thus this will
// not fail when looking up the total difficulty value for the blockenv.
self.provider().fill_env_with_header(
&mut cfg,
&mut block_env,
origin.header(),
self.evm_config().clone(),
)?;
Ok(PendingBlockEnv::new(cfg, block_env, origin))
}
/// Returns the locally built pending block
fn local_pending_block(
&self,
) -> impl Future<Output = EthResult<Option<SealedBlockWithSenders>>> + Send
where
Self: SpawnBlocking,
{
async move {
let pending = self.pending_block_env_and_cfg()?;
if pending.origin.is_actual_pending() {
return Ok(pending.origin.into_actual_pending())
}
let mut lock = self.pending_block().lock().await;
let now = Instant::now();
// check if the block is still good
if let Some(pending_block) = lock.as_ref() {
// this is guaranteed to be the `latest` header
if pending.block_env.number.to::<u64>() == pending_block.block.number &&
pending.origin.header().hash() == pending_block.block.parent_hash &&
now <= pending_block.expires_at
{
return Ok(Some(pending_block.block.clone()))
}
}
// no pending block from the CL yet, so we need to build it ourselves via txpool
let pending_block = match self
.spawn_blocking_io(move |this| {
// we rebuild the block
this.build_block(pending)
})
.await
{
Ok(block) => block,
Err(err) => {
debug!(target: "rpc", "Failed to build pending block: {:?}", err);
return Ok(None)
}
};
let now = Instant::now();
*lock = Some(PendingBlock::new(pending_block.clone(), now + Duration::from_secs(1)));
Ok(Some(pending_block))
}
}
/// Assembles a [`Receipt`] for a transaction, based on its [`ExecutionResult`].
fn assemble_receipt(
&self,
tx: &TransactionSignedEcRecovered,
result: ExecutionResult,
cumulative_gas_used: u64,
) -> Receipt {
Receipt {
tx_type: tx.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.into_logs().into_iter().map(Into::into).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: None,
#[cfg(feature = "optimism")]
deposit_receipt_version: None,
}
}
/// Calculates receipts root in block building.
///
/// Panics if block is not in the [`ExecutionOutcome`]'s block range.
fn receipts_root(
&self,
_block_env: &BlockEnv,
execution_outcome: &ExecutionOutcome,
block_number: BlockNumber,
) -> B256 {
execution_outcome.receipts_root_slow(block_number).expect("Block is present")
}
/// Builds a pending block using the configured provider and pool.
/// ///
/// If the origin is the actual pending block, the block is built with withdrawals. /// If the origin is the actual pending block, the block is built with withdrawals.
/// ///
/// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre /// After Cancun, if the origin is the actual pending block, the block includes the EIP-4788 pre
/// block contract call using the parent beacon block root received from the CL. /// block contract call using the parent beacon block root received from the CL.
pub(crate) fn build_block<Client, Pool>( fn build_block(&self, env: PendingBlockEnv) -> EthResult<SealedBlockWithSenders> {
self, let PendingBlockEnv { cfg, block_env, origin } = env;
client: &Client,
pool: &Pool,
) -> EthResult<SealedBlockWithSenders>
where
Client: StateProviderFactory + ChainSpecProvider,
Pool: TransactionPool,
{
let Self { cfg, block_env, origin } = self;
let parent_hash = origin.build_target_hash(); let parent_hash = origin.build_target_hash();
let state_provider = client.history_by_block_hash(parent_hash)?; let state_provider = self.provider().history_by_block_hash(parent_hash)?;
let state = StateProviderDatabase::new(state_provider); let state = StateProviderDatabase::new(state_provider);
let mut db = State::builder().with_database(state).with_bundle_update().build(); let mut db = State::builder().with_database(state).with_bundle_update().build();
@ -69,10 +215,11 @@ impl PendingBlockEnv {
let mut executed_txs = Vec::new(); let mut executed_txs = Vec::new();
let mut senders = Vec::new(); let mut senders = Vec::new();
let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( let mut best_txs =
base_fee, self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new(
block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), base_fee,
)); block_env.get_blob_gasprice().map(|gasprice| gasprice as u64),
));
let (withdrawals, withdrawals_root) = match origin { let (withdrawals, withdrawals_root) = match origin {
PendingBlockEnvOrigin::ActualPending(ref block) => { PendingBlockEnvOrigin::ActualPending(ref block) => {
@ -81,7 +228,7 @@ impl PendingBlockEnv {
PendingBlockEnvOrigin::DerivedFromLatest(_) => (None, None), PendingBlockEnvOrigin::DerivedFromLatest(_) => (None, None),
}; };
let chain_spec = client.chain_spec(); let chain_spec = self.provider().chain_spec();
let parent_beacon_block_root = if origin.is_actual_pending() { let parent_beacon_block_root = if origin.is_actual_pending() {
// apply eip-4788 pre block contract call if we got the block from the CL with the real // apply eip-4788 pre block contract call if we got the block from the CL with the real
@ -192,16 +339,7 @@ impl PendingBlockEnv {
cumulative_gas_used += gas_used; cumulative_gas_used += gas_used;
// Push transaction changeset and calculate header bloom filter for receipt. // Push transaction changeset and calculate header bloom filter for receipt.
receipts.push(Some(Receipt { receipts.push(Some(self.assemble_receipt(&tx, result, cumulative_gas_used)));
tx_type: tx.tx_type(),
success: result.is_success(),
cumulative_gas_used,
logs: result.into_logs().into_iter().map(Into::into).collect(),
#[cfg(feature = "optimism")]
deposit_nonce: None,
#[cfg(feature = "optimism")]
deposit_receipt_version: None,
}));
// append transaction to the list of executed transactions // append transaction to the list of executed transactions
let (tx, sender) = tx.to_components(); let (tx, sender) = tx.to_components();
@ -229,18 +367,7 @@ impl PendingBlockEnv {
Vec::new(), Vec::new(),
); );
#[cfg(feature = "optimism")] let receipts_root = self.receipts_root(&block_env, &execution_outcome, block_number);
let receipts_root = execution_outcome
.optimism_receipts_root_slow(
block_number,
chain_spec.as_ref(),
block_env.timestamp.to::<u64>(),
)
.expect("Block is present");
#[cfg(not(feature = "optimism"))]
let receipts_root =
execution_outcome.receipts_root_slow(block_number).expect("Block is present");
let logs_bloom = let logs_bloom =
execution_outcome.block_logs_bloom(block_number).expect("Block is present"); execution_outcome.block_logs_bloom(block_number).expect("Block is present");
@ -250,7 +377,7 @@ impl PendingBlockEnv {
let state_root = state_provider.state_root(execution_outcome.state())?; let state_root = state_provider.state_root(execution_outcome.state())?;
// create the block header // create the block header
let transactions_root = proofs::calculate_transaction_root(&executed_txs); let transactions_root = calculate_transaction_root(&executed_txs);
// check if cancun is activated to set eip4844 header fields correctly // check if cancun is activated to set eip4844 header fields correctly
let blob_gas_used = let blob_gas_used =
@ -294,137 +421,3 @@ impl PendingBlockEnv {
Ok(SealedBlockWithSenders { block: block.seal_slow(), senders }) Ok(SealedBlockWithSenders { block: block.seal_slow(), senders })
} }
} }
/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call.
///
/// This constructs a new [Evm](revm::Evm) with the given DB, and environment
/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] to execute the pre block contract call.
///
/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state
/// change.
fn pre_block_beacon_root_contract_call<DB: Database + DatabaseCommit>(
db: &mut DB,
chain_spec: &ChainSpec,
block_number: u64,
initialized_cfg: &CfgEnvWithHandlerCfg,
initialized_block_env: &BlockEnv,
parent_beacon_block_root: Option<B256>,
) -> EthResult<()>
where
DB::Error: std::fmt::Display,
{
// apply pre-block EIP-4788 contract call
let mut evm_pre_block = revm::Evm::builder()
.with_db(db)
.with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
initialized_cfg.clone(),
initialized_block_env.clone(),
Default::default(),
))
.build();
// initialize a block from the env, because the pre block call needs the block itself
apply_beacon_root_contract_call(
chain_spec,
initialized_block_env.timestamp.to::<u64>(),
block_number,
parent_beacon_block_root,
&mut evm_pre_block,
)
.map_err(|err| EthApiError::Internal(err.into()))
}
/// Apply the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) pre block state transitions.
///
/// This constructs a new [Evm](revm::Evm) with the given DB, and environment
/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`].
///
/// This uses [`apply_blockhashes_update`].
fn pre_block_blockhashes_update<DB: Database<Error = ProviderError> + DatabaseCommit>(
db: &mut DB,
chain_spec: &ChainSpec,
initialized_block_env: &BlockEnv,
block_number: u64,
parent_block_hash: B256,
) -> EthResult<()>
where
DB::Error: std::fmt::Display,
{
apply_blockhashes_update(
db,
chain_spec,
initialized_block_env.timestamp.to::<u64>(),
block_number,
parent_block_hash,
)
.map_err(|err| EthApiError::Internal(err.into()))
}
/// The origin for a configured [`PendingBlockEnv`]
#[derive(Clone, Debug)]
pub(crate) enum PendingBlockEnvOrigin {
/// The pending block as received from the CL.
ActualPending(SealedBlockWithSenders),
/// The _modified_ header of the latest block.
///
/// This derives the pending state based on the latest header by modifying:
/// - the timestamp
/// - the block number
/// - fees
DerivedFromLatest(SealedHeader),
}
impl PendingBlockEnvOrigin {
/// Returns true if the origin is the actual pending block as received from the CL.
pub(crate) const fn is_actual_pending(&self) -> bool {
matches!(self, Self::ActualPending(_))
}
/// Consumes the type and returns the actual pending block.
pub(crate) fn into_actual_pending(self) -> Option<SealedBlockWithSenders> {
match self {
Self::ActualPending(block) => Some(block),
_ => None,
}
}
/// Returns the [`BlockId`] that represents the state of the block.
///
/// If this is the actual pending block, the state is the "Pending" tag, otherwise we can safely
/// identify the block by its hash (latest block).
pub(crate) fn state_block_id(&self) -> BlockId {
match self {
Self::ActualPending(_) => BlockNumberOrTag::Pending.into(),
Self::DerivedFromLatest(header) => BlockId::Hash(header.hash().into()),
}
}
/// Returns the hash of the block the pending block should be built on.
///
/// For the [`PendingBlockEnvOrigin::ActualPending`] this is the parent hash of the block.
/// For the [`PendingBlockEnvOrigin::DerivedFromLatest`] this is the hash of the _latest_
/// header.
fn build_target_hash(&self) -> B256 {
match self {
Self::ActualPending(block) => block.parent_hash,
Self::DerivedFromLatest(header) => header.hash(),
}
}
/// Returns the header this pending block is based on.
pub(crate) fn header(&self) -> &SealedHeader {
match self {
Self::ActualPending(block) => &block.header,
Self::DerivedFromLatest(header) => header,
}
}
}
/// In memory pending block for `pending` tag
#[derive(Debug)]
pub(crate) struct PendingBlock {
/// The cached pending block
pub(crate) block: SealedBlockWithSenders,
/// Timestamp when the pending block is considered outdated
pub(crate) expires_at: Instant,
}

View File

@ -0,0 +1,37 @@
//! Loads a receipt from database. Helper trait for `eth_` block and transaction RPC methods, that
//! loads receipt data w.r.t. network.
use futures::Future;
use reth_primitives::{Receipt, TransactionMeta, TransactionSigned};
use reth_rpc_types::AnyTransactionReceipt;
use crate::{EthApiError, EthResult, EthStateCache, ReceiptBuilder};
/// Assembles transaction receipt data w.r.t to network.
///
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` receipts RPC methods.
#[auto_impl::auto_impl(&, Arc)]
pub trait LoadReceipt: Send + Sync {
/// Returns a handle for reading data from memory.
///
/// Data access in default (L1) trait method implementations.
fn cache(&self) -> &EthStateCache;
/// Helper method for `eth_getBlockReceipts` and `eth_getTransactionReceipt`.
fn build_transaction_receipt(
&self,
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
) -> impl Future<Output = EthResult<AnyTransactionReceipt>> + Send {
async move {
// get all receipts for the block
let all_receipts = match self.cache().get_receipts(meta.block_hash).await? {
Some(recpts) => recpts,
None => return Err(EthApiError::UnknownBlockNumber),
};
Ok(ReceiptBuilder::new(&tx, meta, &receipt, &all_receipts)?.build())
}
}
}

View File

@ -0,0 +1,40 @@
//! An abstraction over ethereum signers.
use std::result;
use alloy_dyn_abi::TypedData;
use dyn_clone::DynClone;
use reth_primitives::{Address, Signature, TransactionSigned};
use reth_rpc_types::TypedTransactionRequest;
use crate::SignError;
/// Result returned by [`EthSigner`] methods.
pub type Result<T> = result::Result<T, SignError>;
/// An Ethereum Signer used via RPC.
#[async_trait::async_trait]
pub trait EthSigner: Send + Sync + DynClone {
/// Returns the available accounts for this signer.
fn accounts(&self) -> Vec<Address>;
/// Returns `true` whether this signer can sign for this address
fn is_signer_for(&self, addr: &Address) -> bool {
self.accounts().contains(addr)
}
/// Returns the signature
async fn sign(&self, address: Address, message: &[u8]) -> Result<Signature>;
/// signs a transaction request using the given account in request
fn sign_transaction(
&self,
request: TypedTransactionRequest,
address: &Address,
) -> Result<TransactionSigned>;
/// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait.
fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result<Signature>;
}
dyn_clone::clone_trait_object!(EthSigner);

View File

@ -0,0 +1,31 @@
//! Loads chain metadata.
use futures::Future;
use reth_chainspec::ChainInfo;
use reth_errors::RethResult;
use reth_primitives::{Address, U64};
use reth_rpc_types::SyncStatus;
/// `Eth` API trait.
///
/// Defines core functionality of the `eth` API implementation.
#[auto_impl::auto_impl(&, Arc)]
pub trait EthApiSpec: Send + Sync {
/// Returns the current ethereum protocol version.
fn protocol_version(&self) -> impl Future<Output = RethResult<U64>> + Send;
/// Returns the chain id
fn chain_id(&self) -> U64;
/// Returns provider chain info
fn chain_info(&self) -> RethResult<ChainInfo>;
/// Returns a list of addresses owned by provider.
fn accounts(&self) -> Vec<Address>;
/// Returns `true` if the network is undergoing sync.
fn is_syncing(&self) -> bool;
/// Returns the [`SyncStatus`] of the network
fn sync_status(&self) -> RethResult<SyncStatus>;
}

View File

@ -0,0 +1,247 @@
//! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace
//! RPC methods.
use futures::Future;
use reth_primitives::{
revm::env::fill_block_env_with_coinbase, Address, BlockId, BlockNumberOrTag, Bytes, Header,
B256, U256,
};
use reth_provider::{BlockIdReader, StateProvider, StateProviderBox, StateProviderFactory};
use reth_rpc_types::{serde_helpers::JsonStorageKey, EIP1186AccountProofResponse};
use reth_rpc_types_compat::proof::from_primitive_account_proof;
use reth_transaction_pool::{PoolTransaction, TransactionPool};
use revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg, SpecId};
use crate::{
servers::{EthApiSpec, LoadPendingBlock, SpawnBlocking},
EthApiError, EthResult, EthStateCache, PendingBlockEnv, RpcInvalidTransactionError,
};
/// Helper methods for `eth_` methods relating to state (accounts).
pub trait EthState: LoadState + SpawnBlocking {
/// Returns the number of transactions sent from an address at the given block identifier.
///
/// If this is [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this will
/// look up the highest transaction in pool and return the next nonce (highest + 1).
fn transaction_count(
&self,
address: Address,
block_id: Option<BlockId>,
) -> impl Future<Output = EthResult<U256>> + Send {
LoadState::transaction_count(self, address, block_id)
}
/// Returns code of given account, at given blocknumber.
fn get_code(
&self,
address: Address,
block_id: Option<BlockId>,
) -> impl Future<Output = EthResult<Bytes>> + Send {
self.spawn_blocking_io(move |this| {
Ok(this
.state_at_block_id_or_latest(block_id)?
.account_code(address)?
.unwrap_or_default()
.original_bytes())
})
}
/// Returns balance of given account, at given blocknumber.
fn balance(
&self,
address: Address,
block_id: Option<BlockId>,
) -> impl Future<Output = EthResult<U256>> + Send {
self.spawn_blocking_io(move |this| {
Ok(this
.state_at_block_id_or_latest(block_id)?
.account_balance(address)?
.unwrap_or_default())
})
}
/// Returns values stored of given account, at given blocknumber.
fn storage_at(
&self,
address: Address,
index: JsonStorageKey,
block_id: Option<BlockId>,
) -> impl Future<Output = EthResult<B256>> + Send {
self.spawn_blocking_io(move |this| {
Ok(B256::new(
this.state_at_block_id_or_latest(block_id)?
.storage(address, index.0)?
.unwrap_or_default()
.to_be_bytes(),
))
})
}
/// Returns values stored of given account, with Merkle-proof, at given blocknumber.
fn get_proof(
&self,
address: Address,
keys: Vec<JsonStorageKey>,
block_id: Option<BlockId>,
) -> EthResult<impl Future<Output = EthResult<EIP1186AccountProofResponse>> + Send>
where
Self: EthApiSpec,
{
let chain_info = self.chain_info()?;
let block_id = block_id.unwrap_or_default();
// if we are trying to create a proof for the latest block, but have a BlockId as input
// that is not BlockNumberOrTag::Latest, then we need to figure out whether or not the
// BlockId corresponds to the latest block
let is_latest_block = match block_id {
BlockId::Number(BlockNumberOrTag::Number(num)) => num == chain_info.best_number,
BlockId::Hash(hash) => hash == chain_info.best_hash.into(),
BlockId::Number(BlockNumberOrTag::Latest) => true,
_ => false,
};
// TODO: remove when HistoricalStateProviderRef::proof is implemented
if !is_latest_block {
return Err(EthApiError::InvalidBlockRange)
}
Ok(self.spawn_tracing(move |this| {
let state = this.state_at_block_id(block_id)?;
let storage_keys = keys.iter().map(|key| key.0).collect::<Vec<_>>();
let proof = state.proof(address, &storage_keys)?;
Ok(from_primitive_account_proof(proof))
}))
}
}
/// Loads state from database.
///
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` state RPC methods.
pub trait LoadState {
/// Returns a handle for reading state from database.
///
/// Data access in default trait method implementations.
fn provider(&self) -> impl StateProviderFactory;
/// Returns a handle for reading data from memory.
///
/// Data access in default (L1) trait method implementations.
fn cache(&self) -> &EthStateCache;
/// Returns a handle for reading data from transaction pool.
///
/// Data access in default trait method implementations.
fn pool(&self) -> impl TransactionPool;
/// Returns the state at the given block number
fn state_at_hash(&self, block_hash: B256) -> EthResult<StateProviderBox> {
Ok(self.provider().history_by_block_hash(block_hash)?)
}
/// Returns the state at the given [`BlockId`] enum.
///
/// Note: if not [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this
/// will only return canonical state. See also <https://github.com/paradigmxyz/reth/issues/4515>
fn state_at_block_id(&self, at: BlockId) -> EthResult<StateProviderBox> {
Ok(self.provider().state_by_block_id(at)?)
}
/// Returns the _latest_ state
fn latest_state(&self) -> EthResult<StateProviderBox> {
Ok(self.provider().latest()?)
}
/// Returns the state at the given [`BlockId`] enum or the latest.
///
/// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)`
fn state_at_block_id_or_latest(
&self,
block_id: Option<BlockId>,
) -> EthResult<StateProviderBox> {
if let Some(block_id) = block_id {
self.state_at_block_id(block_id)
} else {
Ok(self.latest_state()?)
}
}
/// Returns the revm evm env for the requested [`BlockId`]
///
/// If the [`BlockId`] this will return the [`BlockId`] of the block the env was configured
/// for.
/// If the [`BlockId`] is pending, this will return the "Pending" tag, otherwise this returns
/// the hash of the exact block.
fn evm_env_at(
&self,
at: BlockId,
) -> impl Future<Output = EthResult<(CfgEnvWithHandlerCfg, BlockEnv, BlockId)>> + Send
where
Self: LoadPendingBlock + SpawnBlocking,
{
async move {
if at.is_pending() {
let PendingBlockEnv { cfg, block_env, origin } =
self.pending_block_env_and_cfg()?;
Ok((cfg, block_env, origin.state_block_id()))
} else {
// Use cached values if there is no pending block
let block_hash = LoadPendingBlock::provider(self)
.block_hash_for_id(at)?
.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
let (cfg, env) = self.cache().get_evm_env(block_hash).await?;
Ok((cfg, env, block_hash.into()))
}
}
}
/// Returns the revm evm env for the raw block header
///
/// This is used for tracing raw blocks
fn evm_env_for_raw_block(
&self,
header: &Header,
) -> impl Future<Output = EthResult<(CfgEnvWithHandlerCfg, BlockEnv)>> + Send
where
Self: LoadPendingBlock + SpawnBlocking,
{
async move {
// get the parent config first
let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?;
let after_merge = cfg.handler_cfg.spec_id >= SpecId::MERGE;
fill_block_env_with_coinbase(&mut block_env, header, after_merge, header.beneficiary);
Ok((cfg, block_env))
}
}
/// Returns the number of transactions sent from an address at the given block identifier.
///
/// If this is [`BlockNumberOrTag::Pending`](reth_primitives::BlockNumberOrTag) then this will
/// look up the highest transaction in pool and return the next nonce (highest + 1).
fn transaction_count(
&self,
address: Address,
block_id: Option<BlockId>,
) -> impl Future<Output = EthResult<U256>> + Send
where
Self: SpawnBlocking,
{
self.spawn_blocking_io(move |this| {
if block_id == Some(BlockId::pending()) {
let address_txs = this.pool().get_transactions_by_sender(address);
if let Some(highest_nonce) =
address_txs.iter().map(|item| item.transaction.nonce()).max()
{
let tx_count = highest_nonce
.checked_add(1)
.ok_or(RpcInvalidTransactionError::NonceMaxValue)?;
return Ok(U256::from(tx_count))
}
}
let state = this.state_at_block_id_or_latest(block_id)?;
Ok(U256::from(state.account_nonce(address)?.unwrap_or_default()))
})
}
}

View File

@ -0,0 +1,412 @@
//! Loads a pending block from database. Helper trait for `eth_` call and trace RPC methods.
use futures::Future;
use reth_evm::ConfigureEvm;
use reth_primitives::{revm::env::tx_env_with_recovered, B256};
use reth_revm::database::StateProviderDatabase;
use reth_rpc_types::{BlockId, TransactionInfo};
use revm::{db::CacheDB, Database, DatabaseCommit, GetInspector, Inspector};
use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig};
use revm_primitives::{EnvWithHandlerCfg, EvmState, ExecutionResult, ResultAndState};
use crate::{
cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper},
servers::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction},
EthApiError, EthResult, StateCacheDb,
};
/// Executes CPU heavy tasks.
pub trait Trace: LoadState {
/// Returns a handle for reading evm config.
///
/// Data access in default (L1) trait method implementations.
fn evm_config(&self) -> &impl ConfigureEvm;
/// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state
/// changes.
fn inspect<DB, I>(
&self,
db: DB,
env: EnvWithHandlerCfg,
inspector: I,
) -> EthResult<(ResultAndState, EnvWithHandlerCfg)>
where
DB: Database,
<DB as Database>::Error: Into<EthApiError>,
I: GetInspector<DB>,
{
self.inspect_and_return_db(db, env, inspector).map(|(res, env, _)| (res, env))
}
/// Same as [`inspect`](Self::inspect) but also returns the database again.
///
/// Even though [Database] is also implemented on `&mut`
/// this is still useful if there are certain trait bounds on the Inspector's database generic
/// type
fn inspect_and_return_db<DB, I>(
&self,
db: DB,
env: EnvWithHandlerCfg,
inspector: I,
) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)>
where
DB: Database,
<DB as Database>::Error: Into<EthApiError>,
I: GetInspector<DB>,
{
let mut evm = self.evm_config().evm_with_env_and_inspector(db, env, inspector);
let res = evm.transact()?;
let (db, env) = evm.into_db_and_env_with_handler_cfg();
Ok((res, env, db))
}
/// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the
/// config.
///
/// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after
/// the configured [`EnvWithHandlerCfg`] was inspected.
///
/// Caution: this is blocking
fn trace_at<F, R>(
&self,
env: EnvWithHandlerCfg,
config: TracingInspectorConfig,
at: BlockId,
f: F,
) -> EthResult<R>
where
Self: Call,
F: FnOnce(TracingInspector, ResultAndState) -> EthResult<R>,
{
self.with_state_at_block(at, |state| {
let mut db = CacheDB::new(StateProviderDatabase::new(state));
let mut inspector = TracingInspector::new(config);
let (res, _) = self.inspect(&mut db, env, &mut inspector)?;
f(inspector, res)
})
}
/// Same as [`trace_at`](Self::trace_at) but also provides the used database to the callback.
///
/// Executes the transaction on top of the given [`BlockId`] with a tracer configured by the
/// config.
///
/// The callback is then called with the [`TracingInspector`] and the [`ResultAndState`] after
/// the configured [`EnvWithHandlerCfg`] was inspected.
fn spawn_trace_at_with_state<F, R>(
&self,
env: EnvWithHandlerCfg,
config: TracingInspectorConfig,
at: BlockId,
f: F,
) -> impl Future<Output = EthResult<R>> + Send
where
Self: LoadPendingBlock + Call,
F: FnOnce(TracingInspector, ResultAndState, StateCacheDb<'_>) -> EthResult<R>
+ Send
+ 'static,
R: Send + 'static,
{
let this = self.clone();
self.spawn_with_state_at_block(at, move |state| {
let mut db = CacheDB::new(StateProviderDatabase::new(state));
let mut inspector = TracingInspector::new(config);
let (res, _) = this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?;
f(inspector, res, db)
})
}
/// Retrieves the transaction if it exists and returns its trace.
///
/// Before the transaction is traced, all previous transaction in the block are applied to the
/// state by executing them first.
/// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
/// and the database that points to the beginning of the transaction.
///
/// Note: Implementers should use a threadpool where blocking is allowed, such as
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
fn spawn_trace_transaction_in_block<F, R>(
&self,
hash: B256,
config: TracingInspectorConfig,
f: F,
) -> impl Future<Output = EthResult<Option<R>>> + Send
where
Self: LoadPendingBlock + LoadTransaction + Call,
F: FnOnce(
TransactionInfo,
TracingInspector,
ResultAndState,
StateCacheDb<'_>,
) -> EthResult<R>
+ Send
+ 'static,
R: Send + 'static,
{
self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f)
}
/// Retrieves the transaction if it exists and returns its trace.
///
/// Before the transaction is traced, all previous transaction in the block are applied to the
/// state by executing them first.
/// The callback `f` is invoked with the [`ResultAndState`] after the transaction was executed
/// and the database that points to the beginning of the transaction.
///
/// Note: Implementers should use a threadpool where blocking is allowed, such as
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool).
fn spawn_trace_transaction_in_block_with_inspector<Insp, F, R>(
&self,
hash: B256,
mut inspector: Insp,
f: F,
) -> impl Future<Output = EthResult<Option<R>>> + Send
where
Self: LoadPendingBlock + LoadTransaction + Call,
F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDb<'_>) -> EthResult<R>
+ Send
+ 'static,
Insp: for<'a, 'b> Inspector<StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
R: Send + 'static,
{
async move {
let (transaction, block) = match self.transaction_and_block(hash).await? {
None => return Ok(None),
Some(res) => res,
};
let (tx, tx_info) = transaction.split();
let (cfg, block_env, _) = self.evm_env_at(block.hash().into()).await?;
// we need to get the state of the parent block because we're essentially replaying the
// block the transaction is included in
let parent_block = block.parent_hash;
let block_txs = block.into_transactions_ecrecovered();
let this = self.clone();
self.spawn_with_state_at_block(parent_block.into(), move |state| {
let mut db = CacheDB::new(StateProviderDatabase::new(state));
// replay all transactions prior to the targeted transaction
this.replay_transactions_until(
&mut db,
cfg.clone(),
block_env.clone(),
block_txs,
tx.hash,
)?;
let env =
EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, tx_env_with_recovered(&tx));
let (res, _) =
this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?;
f(tx_info, inspector, res, db)
})
.await
.map(Some)
}
}
/// Executes all transactions of a block up to a given index.
///
/// If a `highest_index` is given, this will only execute the first `highest_index`
/// transactions, in other words, it will stop executing transactions after the
/// `highest_index`th transaction. If `highest_index` is `None`, all transactions
/// are executed.
fn trace_block_until<F, R>(
&self,
block_id: BlockId,
highest_index: Option<u64>,
config: TracingInspectorConfig,
f: F,
) -> impl Future<Output = EthResult<Option<Vec<R>>>> + Send
where
Self: LoadBlock,
F: Fn(
TransactionInfo,
TracingInspector,
ExecutionResult,
&EvmState,
&StateCacheDb<'_>,
) -> EthResult<R>
+ Send
+ 'static,
R: Send + 'static,
{
self.trace_block_until_with_inspector(
block_id,
highest_index,
move || TracingInspector::new(config),
f,
)
}
/// Executes all transactions of a block.
///
/// If a `highest_index` is given, this will only execute the first `highest_index`
/// transactions, in other words, it will stop executing transactions after the
/// `highest_index`th transaction.
///
/// Note: This expect tx index to be 0-indexed, so the first transaction is at index 0.
///
/// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
/// the transactions.
fn trace_block_until_with_inspector<Setup, Insp, F, R>(
&self,
block_id: BlockId,
highest_index: Option<u64>,
mut inspector_setup: Setup,
f: F,
) -> impl Future<Output = EthResult<Option<Vec<R>>>> + Send
where
Self: LoadBlock,
F: Fn(TransactionInfo, Insp, ExecutionResult, &EvmState, &StateCacheDb<'_>) -> EthResult<R>
+ Send
+ 'static,
Setup: FnMut() -> Insp + Send + 'static,
Insp: for<'a, 'b> Inspector<StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
R: Send + 'static,
{
async move {
let ((cfg, block_env, _), block) =
futures::try_join!(self.evm_env_at(block_id), self.block_with_senders(block_id))?;
let Some(block) = block else { return Ok(None) };
if block.body.is_empty() {
// nothing to trace
return Ok(Some(Vec::new()))
}
// replay all transactions of the block
self.spawn_tracing(move |this| {
// we need to get the state of the parent block because we're replaying this block
// on top of its parent block's state
let state_at = block.parent_hash;
let block_hash = block.hash();
let block_number = block_env.number.saturating_to::<u64>();
let base_fee = block_env.basefee.saturating_to::<u128>();
// prepare transactions, we do everything upfront to reduce time spent with open
// state
let max_transactions = highest_index.map_or(block.body.len(), |highest| {
// we need + 1 because the index is 0-based
highest as usize + 1
});
let mut results = Vec::with_capacity(max_transactions);
let mut transactions = block
.into_transactions_ecrecovered()
.take(max_transactions)
.enumerate()
.map(|(idx, tx)| {
let tx_info = TransactionInfo {
hash: Some(tx.hash()),
index: Some(idx as u64),
block_hash: Some(block_hash),
block_number: Some(block_number),
base_fee: Some(base_fee),
};
let tx_env = tx_env_with_recovered(&tx);
(tx_info, tx_env)
})
.peekable();
// now get the state
let state = this.state_at_block_id(state_at.into())?;
let mut db =
CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state)));
while let Some((tx_info, tx)) = transactions.next() {
let env =
EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx);
let mut inspector = inspector_setup();
let (res, _) =
this.inspect(StateCacheDbRefMutWrapper(&mut db), env, &mut inspector)?;
let ResultAndState { result, state } = res;
results.push(f(tx_info, inspector, result, &state, &db)?);
// need to apply the state changes of this transaction before executing the
// next transaction, but only if there's a next transaction
if transactions.peek().is_some() {
// commit the state changes to the DB
db.commit(state)
}
}
Ok(Some(results))
})
.await
}
}
/// Executes all transactions of a block and returns a list of callback results invoked for each
/// transaction in the block.
///
/// This
/// 1. fetches all transactions of the block
/// 2. configures the EVM evn
/// 3. loops over all transactions and executes them
/// 4. calls the callback with the transaction info, the execution result, the changed state
/// _after_ the transaction [`StateProviderDatabase`] and the database that points to the
/// state right _before_ the transaction.
fn trace_block_with<F, R>(
&self,
block_id: BlockId,
config: TracingInspectorConfig,
f: F,
) -> impl Future<Output = EthResult<Option<Vec<R>>>> + Send
where
Self: LoadBlock,
// This is the callback that's invoked for each transaction with the inspector, the result,
// state and db
F: Fn(
TransactionInfo,
TracingInspector,
ExecutionResult,
&EvmState,
&StateCacheDb<'_>,
) -> EthResult<R>
+ Send
+ 'static,
R: Send + 'static,
{
self.trace_block_until(block_id, None, config, f)
}
/// Executes all transactions of a block and returns a list of callback results invoked for each
/// transaction in the block.
///
/// This
/// 1. fetches all transactions of the block
/// 2. configures the EVM evn
/// 3. loops over all transactions and executes them
/// 4. calls the callback with the transaction info, the execution result, the changed state
/// _after_ the transaction [`EvmState`] and the database that points to the state right
/// _before_ the transaction, in other words the state the transaction was executed on:
/// `changed_state = tx(cached_state)`
///
/// This accepts a `inspector_setup` closure that returns the inspector to be used for tracing
/// a transaction. This is invoked for each transaction.
fn trace_block_inspector<Setup, Insp, F, R>(
&self,
block_id: BlockId,
insp_setup: Setup,
f: F,
) -> impl Future<Output = EthResult<Option<Vec<R>>>> + Send
where
Self: LoadBlock,
// This is the callback that's invoked for each transaction with the inspector, the result,
// state and db
F: Fn(TransactionInfo, Insp, ExecutionResult, &EvmState, &StateCacheDb<'_>) -> EthResult<R>
+ Send
+ 'static,
Setup: FnMut() -> Insp + Send + 'static,
Insp: for<'a, 'b> Inspector<StateCacheDbRefMutWrapper<'a, 'b>> + Send + 'static,
R: Send + 'static,
{
self.trace_block_until_with_inspector(block_id, None, insp_setup, f)
}
}

View File

@ -0,0 +1,651 @@
//! Database access for `eth_` transaction RPC methods. Loads transaction and receipt data w.r.t.
//! network.
use std::{fmt, sync::Arc};
use alloy_dyn_abi::TypedData;
use futures::Future;
use reth_primitives::{
Address, BlockId, Bytes, FromRecoveredPooledTransaction, IntoRecoveredTransaction, Receipt,
SealedBlockWithSenders, TransactionMeta, TransactionSigned, TxHash, TxKind, B256, U256,
};
use reth_provider::{BlockReaderIdExt, ReceiptProvider, TransactionsProvider};
use reth_rpc_types::{
transaction::{
EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest,
LegacyTransactionRequest,
},
AnyTransactionReceipt, Index, Transaction, TransactionRequest, TypedTransactionRequest,
};
use reth_rpc_types_compat::transaction::from_recovered_with_block_context;
use reth_transaction_pool::{TransactionOrigin, TransactionPool};
use crate::{
servers::{
Call, EthApiSpec, EthSigner, LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt,
SpawnBlocking,
},
utils::recover_raw_transaction,
EthApiError, EthResult, EthStateCache, SignError, TransactionSource,
};
/// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in
/// the `eth_` namespace.
///
/// This includes utilities for transaction tracing, transacting and inspection.
///
/// Async functions that are spawned onto the
/// [`BlockingTaskPool`](reth_tasks::pool::BlockingTaskPool) begin with `spawn_`
///
/// ## Calls
///
/// There are subtle differences between when transacting [`TransactionRequest`]:
///
/// The endpoints `eth_call` and `eth_estimateGas` and `eth_createAccessList` should always
/// __disable__ the base fee check in the
/// [`EnvWithHandlerCfg`](revm_primitives::CfgEnvWithHandlerCfg).
///
/// The behaviour for tracing endpoints is not consistent across clients.
/// Geth also disables the basefee check for tracing: <https://github.com/ethereum/go-ethereum/blob/bc0b87ca196f92e5af49bd33cc190ef0ec32b197/eth/tracers/api.go#L955-L955>
/// Erigon does not: <https://github.com/ledgerwatch/erigon/blob/aefb97b07d1c4fd32a66097a24eddd8f6ccacae0/turbo/transactions/tracing.go#L209-L209>
///
/// See also <https://github.com/paradigmxyz/reth/issues/6240>
///
/// This implementation follows the behaviour of Geth and disables the basefee check for tracing.
pub trait EthTransactions: LoadTransaction {
/// Returns a handle for reading data from disk.
///
/// Data access in default (L1) trait method implementations.
fn provider(&self) -> impl BlockReaderIdExt;
/// Returns a handle for forwarding received raw transactions.
///
/// Access to transaction forwarder in default (L1) trait method implementations.
fn raw_tx_forwarder(&self) -> Option<Arc<dyn RawTransactionForwarder>>;
/// Returns a handle for signing data.
///
/// Singer access in default (L1) trait method implementations.
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner>>>;
/// Returns the transaction by hash.
///
/// Checks the pool and state.
///
/// Returns `Ok(None)` if no matching transaction was found.
fn transaction_by_hash(
&self,
hash: B256,
) -> impl Future<Output = EthResult<Option<TransactionSource>>> + Send {
LoadTransaction::transaction_by_hash(self, hash)
}
/// Get all transactions in the block with the given hash.
///
/// Returns `None` if block does not exist.
fn transactions_by_block(
&self,
block: B256,
) -> impl Future<Output = EthResult<Option<Vec<TransactionSigned>>>> + Send {
async move { Ok(self.cache().get_block_transactions(block).await?) }
}
/// Returns the EIP-2718 encoded transaction by hash.
///
/// If this is a pooled EIP-4844 transaction, the blob sidecar is included.
///
/// Checks the pool and state.
///
/// Returns `Ok(None)` if no matching transaction was found.
fn raw_transaction_by_hash(
&self,
hash: B256,
) -> impl Future<Output = EthResult<Option<Bytes>>> + Send {
async move {
// Note: this is mostly used to fetch pooled transactions so we check the pool first
if let Some(tx) =
self.pool().get_pooled_transaction_element(hash).map(|tx| tx.envelope_encoded())
{
return Ok(Some(tx))
}
self.spawn_blocking_io(move |ref this| {
Ok(LoadTransaction::provider(this)
.transaction_by_hash(hash)?
.map(|tx| tx.envelope_encoded()))
})
.await
}
}
/// Returns the _historical_ transaction and the block it was mined in
fn historical_transaction_by_hash_at(
&self,
hash: B256,
) -> impl Future<Output = EthResult<Option<(TransactionSource, B256)>>> + Send {
async move {
match self.transaction_by_hash_at(hash).await? {
None => Ok(None),
Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))),
}
}
}
/// Returns the transaction receipt for the given hash.
///
/// Returns None if the transaction does not exist or is pending
/// Note: The tx receipt is not available for pending transactions.
fn transaction_receipt(
&self,
hash: B256,
) -> impl Future<Output = EthResult<Option<AnyTransactionReceipt>>> + Send
where
Self: LoadReceipt + 'static,
{
async move {
let result = self.load_transaction_and_receipt(hash).await?;
let (tx, meta, receipt) = match result {
Some((tx, meta, receipt)) => (tx, meta, receipt),
None => return Ok(None),
};
self.build_transaction_receipt(tx, meta, receipt).await.map(Some)
}
}
/// Helper method that loads a transaction and its receipt.
fn load_transaction_and_receipt(
&self,
hash: TxHash,
) -> impl Future<Output = EthResult<Option<(TransactionSigned, TransactionMeta, Receipt)>>> + Send
where
Self: 'static,
{
let this = self.clone();
self.spawn_blocking_io(move |_| {
let (tx, meta) =
match LoadTransaction::provider(&this).transaction_by_hash_with_meta(hash)? {
Some((tx, meta)) => (tx, meta),
None => return Ok(None),
};
let receipt = match EthTransactions::provider(&this).receipt_by_hash(hash)? {
Some(recpt) => recpt,
None => return Ok(None),
};
Ok(Some((tx, meta, receipt)))
})
}
/// Get [`Transaction`] by [`BlockId`] and index of transaction within that block.
///
/// Returns `Ok(None)` if the block does not exist, or index is out of range.
fn transaction_by_block_and_tx_index(
&self,
block_id: BlockId,
index: Index,
) -> impl Future<Output = EthResult<Option<Transaction>>> + Send
where
Self: LoadBlock,
{
async move {
if let Some(block) = self.block_with_senders(block_id).await? {
let block_hash = block.hash();
let block_number = block.number;
let base_fee_per_gas = block.base_fee_per_gas;
if let Some(tx) = block.into_transactions_ecrecovered().nth(index.into()) {
return Ok(Some(from_recovered_with_block_context(
tx,
block_hash,
block_number,
base_fee_per_gas,
index.into(),
)))
}
}
Ok(None)
}
}
/// Get transaction, as raw bytes, by [`BlockId`] and index of transaction within that block.
///
/// Returns `Ok(None)` if the block does not exist, or index is out of range.
fn raw_transaction_by_block_and_tx_index(
&self,
block_id: BlockId,
index: Index,
) -> impl Future<Output = EthResult<Option<Bytes>>> + Send
where
Self: LoadBlock,
{
async move {
if let Some(block) = self.block_with_senders(block_id).await? {
if let Some(tx) = block.transactions().nth(index.into()) {
return Ok(Some(tx.envelope_encoded()))
}
}
Ok(None)
}
}
/// Decodes and recovers the transaction and submits it to the pool.
///
/// Returns the hash of the transaction.
fn send_raw_transaction(&self, tx: Bytes) -> impl Future<Output = EthResult<B256>> + Send {
async move {
// On optimism, transactions are forwarded directly to the sequencer to be included in
// blocks that it builds.
if let Some(client) = self.raw_tx_forwarder().as_ref() {
tracing::debug!( target: "rpc::eth", "forwarding raw transaction to");
client.forward_raw_transaction(&tx).await?;
}
let recovered = recover_raw_transaction(tx)?;
let pool_transaction =
<Self::Pool as TransactionPool>::Transaction::from_recovered_pooled_transaction(
recovered,
);
// submit the transaction to the pool with a `Local` origin
let hash =
self.pool().add_transaction(TransactionOrigin::Local, pool_transaction).await?;
Ok(hash)
}
}
/// Signs transaction with a matching signer, if any and submits the transaction to the pool.
/// Returns the hash of the signed transaction.
fn send_transaction(
&self,
mut request: TransactionRequest,
) -> impl Future<Output = EthResult<B256>> + Send
where
Self: EthApiSpec + LoadBlock + LoadPendingBlock + LoadFee + Call,
{
async move {
let from = match request.from {
Some(from) => from,
None => return Err(SignError::NoAccount.into()),
};
// set nonce if not already set before
if request.nonce.is_none() {
let nonce = self.transaction_count(from, Some(BlockId::pending())).await?;
// note: `.to()` can't panic because the nonce is constructed from a `u64`
request.nonce = Some(nonce.to::<u64>());
}
let chain_id = self.chain_id();
let estimated_gas =
self.estimate_gas_at(request.clone(), BlockId::pending(), None).await?;
let gas_limit = estimated_gas;
let TransactionRequest {
to,
gas_price,
max_fee_per_gas,
max_priority_fee_per_gas,
gas,
value,
input: data,
nonce,
mut access_list,
max_fee_per_blob_gas,
blob_versioned_hashes,
sidecar,
..
} = request;
// todo: remove this inlining after https://github.com/alloy-rs/alloy/pull/183#issuecomment-1928161285
let transaction = match (
gas_price,
max_fee_per_gas,
access_list.take(),
max_fee_per_blob_gas,
blob_versioned_hashes,
sidecar,
) {
// legacy transaction
// gas price required
(Some(_), None, None, None, None, None) => {
Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest {
nonce: nonce.unwrap_or_default(),
gas_price: U256::from(gas_price.unwrap_or_default()),
gas_limit: U256::from(gas.unwrap_or_default()),
value: value.unwrap_or_default(),
input: data.into_input().unwrap_or_default(),
kind: to.unwrap_or(TxKind::Create),
chain_id: None,
}))
}
// EIP2930
// if only accesslist is set, and no eip1599 fees
(_, None, Some(access_list), None, None, None) => {
Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest {
nonce: nonce.unwrap_or_default(),
gas_price: U256::from(gas_price.unwrap_or_default()),
gas_limit: U256::from(gas.unwrap_or_default()),
value: value.unwrap_or_default(),
input: data.into_input().unwrap_or_default(),
kind: to.unwrap_or(TxKind::Create),
chain_id: 0,
access_list,
}))
}
// EIP1559
// if 4844 fields missing
// gas_price, max_fee_per_gas, access_list, max_fee_per_blob_gas,
// blob_versioned_hashes, sidecar,
(None, _, _, None, None, None) => {
// Empty fields fall back to the canonical transaction schema.
Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest {
nonce: nonce.unwrap_or_default(),
max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()),
max_priority_fee_per_gas: U256::from(
max_priority_fee_per_gas.unwrap_or_default(),
),
gas_limit: U256::from(gas.unwrap_or_default()),
value: value.unwrap_or_default(),
input: data.into_input().unwrap_or_default(),
kind: to.unwrap_or(TxKind::Create),
chain_id: 0,
access_list: access_list.unwrap_or_default(),
}))
}
// EIP4884
// all blob fields required
(
None,
_,
_,
Some(max_fee_per_blob_gas),
Some(blob_versioned_hashes),
Some(sidecar),
) => {
// As per the EIP, we follow the same semantics as EIP-1559.
Some(TypedTransactionRequest::EIP4844(EIP4844TransactionRequest {
chain_id: 0,
nonce: nonce.unwrap_or_default(),
max_priority_fee_per_gas: U256::from(
max_priority_fee_per_gas.unwrap_or_default(),
),
max_fee_per_gas: U256::from(max_fee_per_gas.unwrap_or_default()),
gas_limit: U256::from(gas.unwrap_or_default()),
value: value.unwrap_or_default(),
input: data.into_input().unwrap_or_default(),
#[allow(clippy::manual_unwrap_or_default)] // clippy is suggesting here unwrap_or_default
to: match to {
Some(TxKind::Call(to)) => to,
_ => Address::default(),
},
access_list: access_list.unwrap_or_default(),
// eip-4844 specific.
max_fee_per_blob_gas: U256::from(max_fee_per_blob_gas),
blob_versioned_hashes,
sidecar,
}))
}
_ => None,
};
let transaction = match transaction {
Some(TypedTransactionRequest::Legacy(mut req)) => {
req.chain_id = Some(chain_id.to());
req.gas_limit = gas_limit.saturating_to();
req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?;
TypedTransactionRequest::Legacy(req)
}
Some(TypedTransactionRequest::EIP2930(mut req)) => {
req.chain_id = chain_id.to();
req.gas_limit = gas_limit.saturating_to();
req.gas_price = self.legacy_gas_price(gas_price.map(U256::from)).await?;
TypedTransactionRequest::EIP2930(req)
}
Some(TypedTransactionRequest::EIP1559(mut req)) => {
let (max_fee_per_gas, max_priority_fee_per_gas) = self
.eip1559_fees(
max_fee_per_gas.map(U256::from),
max_priority_fee_per_gas.map(U256::from),
)
.await?;
req.chain_id = chain_id.to();
req.gas_limit = gas_limit.saturating_to();
req.max_fee_per_gas = max_fee_per_gas.saturating_to();
req.max_priority_fee_per_gas = max_priority_fee_per_gas.saturating_to();
TypedTransactionRequest::EIP1559(req)
}
Some(TypedTransactionRequest::EIP4844(mut req)) => {
let (max_fee_per_gas, max_priority_fee_per_gas) = self
.eip1559_fees(
max_fee_per_gas.map(U256::from),
max_priority_fee_per_gas.map(U256::from),
)
.await?;
req.max_fee_per_gas = max_fee_per_gas;
req.max_priority_fee_per_gas = max_priority_fee_per_gas;
req.max_fee_per_blob_gas =
self.eip4844_blob_fee(max_fee_per_blob_gas.map(U256::from)).await?;
req.chain_id = chain_id.to();
req.gas_limit = gas_limit;
TypedTransactionRequest::EIP4844(req)
}
None => return Err(EthApiError::ConflictingFeeFieldsInRequest),
};
let signed_tx = self.sign_request(&from, transaction)?;
let recovered =
signed_tx.into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?;
let pool_transaction = match recovered.try_into() {
Ok(converted) => <<Self as LoadTransaction>::Pool as TransactionPool>::Transaction::from_recovered_pooled_transaction(converted),
Err(_) => return Err(EthApiError::TransactionConversionError),
};
// submit the transaction to the pool with a `Local` origin
let hash = LoadTransaction::pool(self)
.add_transaction(TransactionOrigin::Local, pool_transaction)
.await?;
Ok(hash)
}
}
/// Signs a transaction, with configured signers.
fn sign_request(
&self,
from: &Address,
request: TypedTransactionRequest,
) -> EthResult<TransactionSigned> {
for signer in self.signers().read().iter() {
if signer.is_signer_for(from) {
return match signer.sign_transaction(request, from) {
Ok(tx) => Ok(tx),
Err(e) => Err(e.into()),
}
}
}
Err(EthApiError::InvalidTransactionSignature)
}
/// Signs given message. Returns the signature.
fn sign(
&self,
account: Address,
message: Bytes,
) -> impl Future<Output = EthResult<Bytes>> + Send {
async move { Ok(self.find_signer(&account)?.sign(account, &message).await?.to_hex_bytes()) }
}
/// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait.
fn sign_typed_data(&self, data: &TypedData, account: Address) -> EthResult<Bytes> {
Ok(self.find_signer(&account)?.sign_typed_data(account, data)?.to_hex_bytes())
}
/// Returns the signer for the given account, if found in configured signers.
fn find_signer(&self, account: &Address) -> Result<Box<(dyn EthSigner + 'static)>, SignError> {
self.signers()
.read()
.iter()
.find(|signer| signer.is_signer_for(account))
.map(|signer| dyn_clone::clone_box(&**signer))
.ok_or(SignError::NoAccount)
}
}
/// Loads a transaction from database.
///
/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` transactions RPC
/// methods.
pub trait LoadTransaction: SpawnBlocking {
/// Transaction pool with pending transactions. [`TransactionPool::Transaction`] is the
/// supported transaction type.
type Pool: TransactionPool;
/// Returns a handle for reading data from disk.
///
/// Data access in default (L1) trait method implementations.
fn provider(&self) -> impl TransactionsProvider;
/// Returns a handle for reading data from memory.
///
/// Data access in default (L1) trait method implementations.
fn cache(&self) -> &EthStateCache;
/// Returns a handle for reading data from pool.
///
/// Data access in default (L1) trait method implementations.
fn pool(&self) -> &Self::Pool;
/// Returns the transaction by hash.
///
/// Checks the pool and state.
///
/// Returns `Ok(None)` if no matching transaction was found.
fn transaction_by_hash(
&self,
hash: B256,
) -> impl Future<Output = EthResult<Option<TransactionSource>>> + Send {
async move {
// Try to find the transaction on disk
let mut resp = self
.spawn_blocking_io(move |this| {
match this.provider().transaction_by_hash_with_meta(hash)? {
None => Ok(None),
Some((tx, meta)) => {
// Note: we assume this transaction is valid, because it's mined (or
// part of pending block) and already. We don't need to
// check for pre EIP-2 because this transaction could be pre-EIP-2.
let transaction = tx
.into_ecrecovered_unchecked()
.ok_or(EthApiError::InvalidTransactionSignature)?;
let tx = TransactionSource::Block {
transaction,
index: meta.index,
block_hash: meta.block_hash,
block_number: meta.block_number,
base_fee: meta.base_fee,
};
Ok(Some(tx))
}
}
})
.await?;
if resp.is_none() {
// tx not found on disk, check pool
if let Some(tx) =
self.pool().get(&hash).map(|tx| tx.transaction.to_recovered_transaction())
{
resp = Some(TransactionSource::Pool(tx));
}
}
Ok(resp)
}
}
/// Returns the transaction by including its corresponding [`BlockId`].
///
/// Note: this supports pending transactions
fn transaction_by_hash_at(
&self,
transaction_hash: B256,
) -> impl Future<Output = EthResult<Option<(TransactionSource, BlockId)>>> + Send {
async move {
match self.transaction_by_hash(transaction_hash).await? {
None => Ok(None),
Some(tx) => {
let res = match tx {
tx @ TransactionSource::Pool(_) => (tx, BlockId::pending()),
TransactionSource::Block {
transaction,
index,
block_hash,
block_number,
base_fee,
} => {
let at = BlockId::Hash(block_hash.into());
let tx = TransactionSource::Block {
transaction,
index,
block_hash,
block_number,
base_fee,
};
(tx, at)
}
};
Ok(Some(res))
}
}
}
}
/// Fetches the transaction and the transaction's block
fn transaction_and_block(
&self,
hash: B256,
) -> impl Future<Output = EthResult<Option<(TransactionSource, SealedBlockWithSenders)>>> + Send
{
async move {
let (transaction, at) = match self.transaction_by_hash_at(hash).await? {
None => return Ok(None),
Some(res) => res,
};
// Note: this is always either hash or pending
let block_hash = match at {
BlockId::Hash(hash) => hash.block_hash,
_ => return Ok(None),
};
let block = self.cache().get_block_with_senders(block_hash).await?;
Ok(block.map(|block| (transaction, block.seal(block_hash))))
}
}
}
/// A trait that allows for forwarding raw transactions.
///
/// For example to a sequencer.
#[async_trait::async_trait]
pub trait RawTransactionForwarder: fmt::Debug + Send + Sync + 'static {
/// Forwards raw transaction bytes for `eth_sendRawTransaction`
async fn forward_raw_transaction(&self, raw: &[u8]) -> EthResult<()>;
}

View File

@ -0,0 +1,126 @@
//! Contains RPC handler implementations specific to transactions
use reth_provider::{BlockReaderIdExt, TransactionsProvider};
use reth_transaction_pool::TransactionPool;
use crate::{
servers::{
EthSigner, EthTransactions, LoadTransaction, RawTransactionForwarder, SpawnBlocking,
},
EthApi, EthStateCache,
};
impl<Provider, Pool, Network, EvmConfig> EthTransactions
for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: LoadTransaction,
Pool: TransactionPool + 'static,
Provider: BlockReaderIdExt,
{
#[inline]
fn provider(&self) -> impl BlockReaderIdExt {
self.inner.provider()
}
#[inline]
fn raw_tx_forwarder(&self) -> Option<std::sync::Arc<dyn RawTransactionForwarder>> {
self.inner.raw_tx_forwarder()
}
#[inline]
fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner>>> {
self.inner.signers()
}
}
impl<Provider, Pool, Network, EvmConfig> LoadTransaction
for EthApi<Provider, Pool, Network, EvmConfig>
where
Self: SpawnBlocking,
Provider: TransactionsProvider,
Pool: TransactionPool,
{
type Pool = Pool;
#[inline]
fn provider(&self) -> impl reth_provider::TransactionsProvider {
self.inner.provider()
}
#[inline]
fn cache(&self) -> &EthStateCache {
self.inner.cache()
}
#[inline]
fn pool(&self) -> &Self::Pool {
self.inner.pool()
}
}
#[cfg(test)]
mod tests {
use reth_evm_ethereum::EthEvmConfig;
use reth_network_api::noop::NoopNetwork;
use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, hex_literal::hex, Bytes};
use reth_provider::test_utils::NoopProvider;
use reth_tasks::pool::BlockingTaskPool;
use reth_transaction_pool::{test_utils::testing_pool, TransactionPool};
use crate::{
servers::EthTransactions, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig,
GasPriceOracle,
};
use super::*;
#[tokio::test]
async fn send_raw_transaction() {
let noop_provider = NoopProvider::default();
let noop_network_provider = NoopNetwork::default();
let pool = testing_pool();
let evm_config = EthEvmConfig::default();
let cache = EthStateCache::spawn(noop_provider, Default::default(), evm_config);
let fee_history_cache =
FeeHistoryCache::new(cache.clone(), FeeHistoryCacheConfig::default());
let eth_api = EthApi::new(
noop_provider,
pool.clone(),
noop_network_provider,
cache.clone(),
GasPriceOracle::new(noop_provider, Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
BlockingTaskPool::build().expect("failed to build tracing pool"),
fee_history_cache,
evm_config,
None,
);
// https://etherscan.io/tx/0xa694b71e6c128a2ed8e2e0f6770bddbe52e3bb8f10e8472f9a79ab81497a8b5d
let tx_1 = Bytes::from(hex!("02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3"));
let tx_1_result = eth_api.send_raw_transaction(tx_1).await.unwrap();
assert_eq!(
pool.len(),
1,
"expect 1 transactions in the pool, but pool size is {}",
pool.len()
);
// https://etherscan.io/tx/0x48816c2f32c29d152b0d86ff706f39869e6c1f01dc2fe59a3c1f9ecf39384694
let tx_2 = Bytes::from(hex!("02f9043c018202b7843b9aca00850c807d37a08304d21d94ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b881bc16d674ec80000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000063e2d99f00000000000000000000000000000000000000000000000000000000000000030b000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000065717fe021ea67801d1088cc80099004b05b64600000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e95fd5965fd1f1a6f0d4600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000428dca9537116148616a5a3e44035af17238fe9dc080a0c6ec1e41f5c0b9511c49b171ad4e04c6bb419c74d99fe9891d74126ec6e4e879a032069a753d7a2cfa158df95421724d24c0e9501593c09905abf3699b4a4405ce"));
let tx_2_result = eth_api.send_raw_transaction(tx_2).await.unwrap();
assert_eq!(
pool.len(),
2,
"expect 2 transactions in the pool, but pool size is {}",
pool.len()
);
assert!(pool.get(&tx_1_result).is_some(), "tx1 not found in the pool");
assert!(pool.get(&tx_2_result).is_some(), "tx2 not found in the pool");
}
}

View File

@ -0,0 +1,323 @@
//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait
//! Handles RPC requests for the `eth_` namespace.
use std::sync::Arc;
use reth_primitives::{BlockNumberOrTag, U256};
use reth_provider::{BlockReaderIdExt, ChainSpecProvider};
pub mod bundle;
pub mod filter;
pub mod helpers;
pub mod pubsub;
mod server;
pub use helpers::{
signer::DevSigner,
traits::{
block::{EthBlocks, LoadBlock},
blocking_task::SpawnBlocking,
call::{Call, EthCall},
fee::{EthFees, LoadFee},
pending_block::LoadPendingBlock,
receipt::LoadReceipt,
signer::EthSigner,
spec::EthApiSpec,
state::{EthState, LoadState},
trace::Trace,
transaction::{EthTransactions, LoadTransaction, RawTransactionForwarder},
TraceExt,
},
};
use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor};
use tokio::sync::Mutex;
use crate::{EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock};
/// `Eth` API implementation.
///
/// This type provides the functionality for handling `eth_` related requests.
/// These are implemented two-fold: Core functionality is implemented as [`EthApiSpec`]
/// trait. Additionally, the required server implementations (e.g.
/// [`EthApiServer`](crate::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).
pub struct EthApi<Provider, Pool, Network, EvmConfig> {
/// All nested fields bundled together.
inner: Arc<EthApiInner<Provider, Pool, Network, EvmConfig>>,
}
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig> {
/// Sets a forwarder for `eth_sendRawTransaction`
///
/// Note: this might be removed in the future in favor of a more generic approach.
pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc<dyn RawTransactionForwarder>) {
self.inner.raw_transaction_forwarder.write().replace(forwarder);
}
}
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider: BlockReaderIdExt + ChainSpecProvider,
{
/// Creates a new, shareable instance using the default tokio task spawner.
#[allow(clippy::too_many_arguments)]
pub fn new(
provider: Provider,
pool: Pool,
network: Network,
eth_cache: EthStateCache,
gas_oracle: GasPriceOracle<Provider>,
gas_cap: impl Into<GasCap>,
blocking_task_pool: BlockingTaskPool,
fee_history_cache: FeeHistoryCache,
evm_config: EvmConfig,
raw_transaction_forwarder: Option<Arc<dyn RawTransactionForwarder>>,
) -> Self {
Self::with_spawner(
provider,
pool,
network,
eth_cache,
gas_oracle,
gas_cap.into().into(),
Box::<TokioTaskExecutor>::default(),
blocking_task_pool,
fee_history_cache,
evm_config,
raw_transaction_forwarder,
)
}
/// Creates a new, shareable instance.
#[allow(clippy::too_many_arguments)]
pub fn with_spawner(
provider: Provider,
pool: Pool,
network: Network,
eth_cache: EthStateCache,
gas_oracle: GasPriceOracle<Provider>,
gas_cap: u64,
task_spawner: Box<dyn TaskSpawner>,
blocking_task_pool: BlockingTaskPool,
fee_history_cache: FeeHistoryCache,
evm_config: EvmConfig,
raw_transaction_forwarder: Option<Arc<dyn RawTransactionForwarder>>,
) -> Self {
// get the block number of the latest block
let latest_block = provider
.header_by_number_or_tag(BlockNumberOrTag::Latest)
.ok()
.flatten()
.map(|header| header.number)
.unwrap_or_default();
let inner = EthApiInner {
provider,
pool,
network,
signers: parking_lot::RwLock::new(Default::default()),
eth_cache,
gas_oracle,
gas_cap,
starting_block: U256::from(latest_block),
task_spawner,
pending_block: Default::default(),
blocking_task_pool,
fee_history_cache,
evm_config,
raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder),
};
Self { inner: Arc::new(inner) }
}
/// Returns the state cache frontend
pub fn cache(&self) -> &EthStateCache {
&self.inner.eth_cache
}
/// Returns the gas oracle frontend
pub fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
&self.inner.gas_oracle
}
/// Returns the configured gas limit cap for `eth_call` and tracing related calls
pub fn gas_cap(&self) -> u64 {
self.inner.gas_cap
}
/// Returns the inner `Provider`
pub fn provider(&self) -> &Provider {
&self.inner.provider
}
/// Returns the inner `Network`
pub fn network(&self) -> &Network {
&self.inner.network
}
/// Returns the inner `Pool`
pub fn pool(&self) -> &Pool {
&self.inner.pool
}
/// Returns fee history cache
pub fn fee_history_cache(&self) -> &FeeHistoryCache {
&self.inner.fee_history_cache
}
}
impl<Provider, Pool, Network, EvmConfig> std::fmt::Debug
for EthApi<Provider, Pool, Network, EvmConfig>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EthApi").finish_non_exhaustive()
}
}
impl<Provider, Pool, Network, EvmConfig> Clone for EthApi<Provider, Pool, Network, EvmConfig> {
fn clone(&self) -> Self {
Self { inner: Arc::clone(&self.inner) }
}
}
/// Implements [`SpawnBlocking`] for a type, that has similar data layout to [`EthApi`].
#[macro_export]
macro_rules! spawn_blocking_impl {
($network_api:ty) => {
impl<Provider, Pool, Network, EvmConfig> $crate::servers::SpawnBlocking for $network_api
where
Self: Clone + Send + Sync + 'static,
{
#[inline]
fn io_task_spawner(&self) -> impl reth_tasks::TaskSpawner {
self.inner.task_spawner()
}
#[inline]
fn tracing_task_pool(&self) -> &reth_tasks::pool::BlockingTaskPool {
self.inner.blocking_task_pool()
}
}
};
}
spawn_blocking_impl!(EthApi<Provider, Pool, Network, EvmConfig>);
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig> {
/// Generates 20 random developer accounts.
/// Used in DEV mode.
pub fn with_dev_accounts(&self) {
let mut signers = self.inner.signers.write();
*signers = DevSigner::random_signers(20);
}
}
/// Container type `EthApi`
#[allow(missing_debug_implementations)]
pub struct EthApiInner<Provider, Pool, Network, EvmConfig> {
/// The transaction pool.
pool: Pool,
/// The provider that can interact with the chain.
provider: Provider,
/// An interface to interact with the network
network: Network,
/// All configured Signers
signers: parking_lot::RwLock<Vec<Box<dyn EthSigner>>>,
/// The async cache frontend for eth related data
eth_cache: EthStateCache,
/// The async gas oracle frontend for gas price suggestions
gas_oracle: GasPriceOracle<Provider>,
/// Maximum gas limit for `eth_call` and call tracing RPC methods.
gas_cap: u64,
/// The block number at which the node started
starting_block: U256,
/// The type that can spawn tasks which would otherwise block.
task_spawner: Box<dyn TaskSpawner>,
/// Cached pending block if any
pending_block: Mutex<Option<PendingBlock>>,
/// A pool dedicated to CPU heavy blocking tasks.
blocking_task_pool: BlockingTaskPool,
/// Cache for block fees history
fee_history_cache: FeeHistoryCache,
/// The type that defines how to configure the EVM
evm_config: EvmConfig,
/// Allows forwarding received raw transactions
raw_transaction_forwarder: parking_lot::RwLock<Option<Arc<dyn RawTransactionForwarder>>>,
}
impl<Provider, Pool, Network, EvmConfig> EthApiInner<Provider, Pool, Network, EvmConfig> {
/// Returns a handle to data on disk.
#[inline]
pub const fn provider(&self) -> &Provider {
&self.provider
}
/// Returns a handle to data in memory.
#[inline]
pub const fn cache(&self) -> &EthStateCache {
&self.eth_cache
}
/// Returns a handle to the pending block.
#[inline]
pub const fn pending_block(&self) -> &Mutex<Option<PendingBlock>> {
&self.pending_block
}
/// Returns a handle to the task spawner.
#[inline]
pub const fn task_spawner(&self) -> &dyn TaskSpawner {
&*self.task_spawner
}
/// Returns a handle to the blocking thread pool.
#[inline]
pub const fn blocking_task_pool(&self) -> &BlockingTaskPool {
&self.blocking_task_pool
}
/// Returns a handle to the EVM config.
#[inline]
pub const fn evm_config(&self) -> &EvmConfig {
&self.evm_config
}
/// Returns a handle to the transaction pool.
#[inline]
pub const fn pool(&self) -> &Pool {
&self.pool
}
/// Returns a handle to the transaction forwarder.
#[inline]
pub fn raw_tx_forwarder(&self) -> Option<Arc<dyn RawTransactionForwarder>> {
self.raw_transaction_forwarder.read().clone()
}
/// Returns the gas cap.
#[inline]
pub const fn gas_cap(&self) -> u64 {
self.gas_cap
}
/// Returns a handle to the gas oracle.
#[inline]
pub const fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
&self.gas_oracle
}
/// Returns a handle to the fee history cache.
#[inline]
pub const fn fee_history_cache(&self) -> &FeeHistoryCache {
&self.fee_history_cache
}
/// Returns a handle to the signers.
#[inline]
pub const fn signers(&self) -> &parking_lot::RwLock<Vec<Box<dyn EthSigner>>> {
&self.signers
}
}

View File

@ -1,9 +1,7 @@
//! `eth_` `PubSub` RPC handler implementation //! `eth_` `PubSub` RPC handler implementation
use crate::{ use std::sync::Arc;
eth::logs_utils,
result::{internal_rpc_err, invalid_params_rpc_err},
};
use futures::StreamExt; use futures::StreamExt;
use jsonrpsee::{ use jsonrpsee::{
server::SubscriptionMessage, types::ErrorObject, PendingSubscriptionSink, SubscriptionSink, server::SubscriptionMessage, types::ErrorObject, PendingSubscriptionSink, SubscriptionSink,
@ -11,7 +9,6 @@ use jsonrpsee::{
use reth_network_api::NetworkInfo; use reth_network_api::NetworkInfo;
use reth_primitives::{IntoRecoveredTransaction, TxHash}; use reth_primitives::{IntoRecoveredTransaction, TxHash};
use reth_provider::{BlockReader, CanonStateSubscriptions, EvmEnvProvider}; use reth_provider::{BlockReader, CanonStateSubscriptions, EvmEnvProvider};
use reth_rpc_api::EthPubSubApiServer;
use reth_rpc_types::{ use reth_rpc_types::{
pubsub::{ pubsub::{
Params, PubSubSyncStatus, SubscriptionKind, SubscriptionResult as EthSubscriptionResult, Params, PubSubSyncStatus, SubscriptionKind, SubscriptionResult as EthSubscriptionResult,
@ -22,12 +19,17 @@ use reth_rpc_types::{
use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use reth_tasks::{TaskSpawner, TokioTaskExecutor};
use reth_transaction_pool::{NewTransactionEvent, TransactionPool}; use reth_transaction_pool::{NewTransactionEvent, TransactionPool};
use serde::Serialize; use serde::Serialize;
use std::sync::Arc;
use tokio_stream::{ use tokio_stream::{
wrappers::{BroadcastStream, ReceiverStream}, wrappers::{BroadcastStream, ReceiverStream},
Stream, Stream,
}; };
use crate::{
logs_utils,
result::{internal_rpc_err, invalid_params_rpc_err},
EthPubSubApiServer,
};
/// `Eth` pubsub RPC implementation. /// `Eth` pubsub RPC implementation.
/// ///
/// This handles `eth_subscribe` RPC calls. /// This handles `eth_subscribe` RPC calls.
@ -197,10 +199,10 @@ where
/// Helper to convert a serde error into an [`ErrorObject`] /// Helper to convert a serde error into an [`ErrorObject`]
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
#[error("Failed to serialize subscription item: {0}")] #[error("Failed to serialize subscription item: {0}")]
pub(crate) struct SubscriptionSerializeError(#[from] serde_json::Error); pub struct SubscriptionSerializeError(#[from] serde_json::Error);
impl SubscriptionSerializeError { impl SubscriptionSerializeError {
pub(crate) const fn new(err: serde_json::Error) -> Self { const fn new(err: serde_json::Error) -> Self {
Self(err) Self(err)
} }
} }

View File

@ -1,49 +1,37 @@
//! Implementation of the [`jsonrpsee`] generated [`reth_rpc_api::EthApiServer`] trait //! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for
//! Handles RPC requests for the `eth_` namespace. //! the `eth_` namespace.
use super::EthApiSpec;
use crate::{
eth::{
api::{EthApi, EthTransactions},
error::EthApiError,
},
result::{internal_rpc_err, ToRpcResult},
};
use alloy_dyn_abi::TypedData; use alloy_dyn_abi::TypedData;
use jsonrpsee::core::RpcResult as Result; use jsonrpsee::core::RpcResult as Result;
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64};
use reth_provider::{
BlockIdReader, BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider,
HeaderProvider, StateProviderFactory,
};
use reth_rpc_api::EthApiServer;
use reth_rpc_types::{ use reth_rpc_types::{
serde_helpers::JsonStorageKey, serde_helpers::JsonStorageKey,
state::{EvmOverrides, StateOverride}, state::{EvmOverrides, StateOverride},
AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle, AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle,
EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock, EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock,
StateContext, SyncStatus, TransactionRequest, Work, StateContext, SyncStatus, Transaction, TransactionRequest, Work,
}; };
use reth_transaction_pool::TransactionPool;
use tracing::trace; use tracing::trace;
use crate::{
result::internal_rpc_err,
servers::{
EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadReceipt, Trace,
},
EthApiError, EthApiServer, ToRpcResult,
};
#[async_trait::async_trait] #[async_trait::async_trait]
impl<Provider, Pool, Network, EvmConfig> EthApiServer for EthApi<Provider, Pool, Network, EvmConfig> impl<T> EthApiServer for T
where where
Self: EthApiSpec + EthTransactions, Self: EthApiSpec
Pool: TransactionPool + 'static, + EthTransactions
Provider: BlockReader + EthBlocks
+ BlockIdReader + EthState
+ BlockReaderIdExt + EthCall
+ ChainSpecProvider + EthFees
+ HeaderProvider + Trace
+ StateProviderFactory + LoadReceipt,
+ EvmEnvProvider
+ 'static,
Network: NetworkInfo + Send + Sync + 'static,
EvmConfig: ConfigureEvm + 'static,
{ {
/// Handler for: `eth_protocolVersion` /// Handler for: `eth_protocolVersion`
async fn protocol_version(&self) -> Result<U64> { async fn protocol_version(&self) -> Result<U64> {
@ -85,7 +73,7 @@ where
/// Handler for: `eth_getBlockByHash` /// Handler for: `eth_getBlockByHash`
async fn block_by_hash(&self, hash: B256, full: bool) -> Result<Option<RichBlock>> { async fn block_by_hash(&self, hash: B256, full: bool) -> Result<Option<RichBlock>> {
trace!(target: "rpc::eth", ?hash, ?full, "Serving eth_getBlockByHash"); trace!(target: "rpc::eth", ?hash, ?full, "Serving eth_getBlockByHash");
Ok(Self::rpc_block(self, hash, full).await?) Ok(EthBlocks::rpc_block(self, hash.into(), full).await?)
} }
/// Handler for: `eth_getBlockByNumber` /// Handler for: `eth_getBlockByNumber`
@ -95,13 +83,13 @@ where
full: bool, full: bool,
) -> Result<Option<RichBlock>> { ) -> Result<Option<RichBlock>> {
trace!(target: "rpc::eth", ?number, ?full, "Serving eth_getBlockByNumber"); trace!(target: "rpc::eth", ?number, ?full, "Serving eth_getBlockByNumber");
Ok(Self::rpc_block(self, number, full).await?) Ok(EthBlocks::rpc_block(self, number.into(), full).await?)
} }
/// Handler for: `eth_getBlockTransactionCountByHash` /// Handler for: `eth_getBlockTransactionCountByHash`
async fn block_transaction_count_by_hash(&self, hash: B256) -> Result<Option<U256>> { async fn block_transaction_count_by_hash(&self, hash: B256) -> Result<Option<U256>> {
trace!(target: "rpc::eth", ?hash, "Serving eth_getBlockTransactionCountByHash"); trace!(target: "rpc::eth", ?hash, "Serving eth_getBlockTransactionCountByHash");
Ok(Self::block_transaction_count(self, hash).await?.map(U256::from)) Ok(EthBlocks::block_transaction_count(self, hash).await?.map(U256::from))
} }
/// Handler for: `eth_getBlockTransactionCountByNumber` /// Handler for: `eth_getBlockTransactionCountByNumber`
@ -110,19 +98,19 @@ where
number: BlockNumberOrTag, number: BlockNumberOrTag,
) -> Result<Option<U256>> { ) -> Result<Option<U256>> {
trace!(target: "rpc::eth", ?number, "Serving eth_getBlockTransactionCountByNumber"); trace!(target: "rpc::eth", ?number, "Serving eth_getBlockTransactionCountByNumber");
Ok(Self::block_transaction_count(self, number).await?.map(U256::from)) Ok(EthBlocks::block_transaction_count(self, number).await?.map(U256::from))
} }
/// Handler for: `eth_getUncleCountByBlockHash` /// Handler for: `eth_getUncleCountByBlockHash`
async fn block_uncles_count_by_hash(&self, hash: B256) -> Result<Option<U256>> { async fn block_uncles_count_by_hash(&self, hash: B256) -> Result<Option<U256>> {
trace!(target: "rpc::eth", ?hash, "Serving eth_getUncleCountByBlockHash"); trace!(target: "rpc::eth", ?hash, "Serving eth_getUncleCountByBlockHash");
Ok(Self::ommers(self, hash)?.map(|ommers| U256::from(ommers.len()))) Ok(EthBlocks::ommers(self, hash)?.map(|ommers| U256::from(ommers.len())))
} }
/// Handler for: `eth_getUncleCountByBlockNumber` /// Handler for: `eth_getUncleCountByBlockNumber`
async fn block_uncles_count_by_number(&self, number: BlockNumberOrTag) -> Result<Option<U256>> { async fn block_uncles_count_by_number(&self, number: BlockNumberOrTag) -> Result<Option<U256>> {
trace!(target: "rpc::eth", ?number, "Serving eth_getUncleCountByBlockNumber"); trace!(target: "rpc::eth", ?number, "Serving eth_getUncleCountByBlockNumber");
Ok(Self::ommers(self, number)?.map(|ommers| U256::from(ommers.len()))) Ok(EthBlocks::ommers(self, number)?.map(|ommers| U256::from(ommers.len())))
} }
/// Handler for: `eth_getBlockReceipts` /// Handler for: `eth_getBlockReceipts`
@ -131,7 +119,7 @@ where
block_id: BlockId, block_id: BlockId,
) -> Result<Option<Vec<AnyTransactionReceipt>>> { ) -> Result<Option<Vec<AnyTransactionReceipt>>> {
trace!(target: "rpc::eth", ?block_id, "Serving eth_getBlockReceipts"); trace!(target: "rpc::eth", ?block_id, "Serving eth_getBlockReceipts");
Ok(Self::block_receipts(self, block_id).await?) Ok(EthBlocks::block_receipts(self, block_id).await?)
} }
/// Handler for: `eth_getUncleByBlockHashAndIndex` /// Handler for: `eth_getUncleByBlockHashAndIndex`
@ -141,7 +129,7 @@ where
index: Index, index: Index,
) -> Result<Option<RichBlock>> { ) -> Result<Option<RichBlock>> {
trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getUncleByBlockHashAndIndex"); trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getUncleByBlockHashAndIndex");
Ok(Self::ommer_by_block_and_index(self, hash, index).await?) Ok(EthBlocks::ommer_by_block_and_index(self, hash, index).await?)
} }
/// Handler for: `eth_getUncleByBlockNumberAndIndex` /// Handler for: `eth_getUncleByBlockNumberAndIndex`
@ -151,7 +139,7 @@ where
index: Index, index: Index,
) -> Result<Option<RichBlock>> { ) -> Result<Option<RichBlock>> {
trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getUncleByBlockNumberAndIndex"); trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getUncleByBlockNumberAndIndex");
Ok(Self::ommer_by_block_and_index(self, number, index).await?) Ok(EthBlocks::ommer_by_block_and_index(self, number, index).await?)
} }
/// Handler for: `eth_getRawTransactionByHash` /// Handler for: `eth_getRawTransactionByHash`
@ -161,7 +149,7 @@ where
} }
/// Handler for: `eth_getTransactionByHash` /// Handler for: `eth_getTransactionByHash`
async fn transaction_by_hash(&self, hash: B256) -> Result<Option<reth_rpc_types::Transaction>> { async fn transaction_by_hash(&self, hash: B256) -> Result<Option<Transaction>> {
trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionByHash"); trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionByHash");
Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into)) Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into))
} }
@ -173,7 +161,7 @@ where
index: Index, index: Index,
) -> Result<Option<Bytes>> { ) -> Result<Option<Bytes>> {
trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getRawTransactionByBlockHashAndIndex"); trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getRawTransactionByBlockHashAndIndex");
Ok(Self::raw_transaction_by_block_and_tx_index(self, hash, index).await?) Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, hash.into(), index).await?)
} }
/// Handler for: `eth_getTransactionByBlockHashAndIndex` /// Handler for: `eth_getTransactionByBlockHashAndIndex`
@ -183,7 +171,7 @@ where
index: Index, index: Index,
) -> Result<Option<reth_rpc_types::Transaction>> { ) -> Result<Option<reth_rpc_types::Transaction>> {
trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getTransactionByBlockHashAndIndex"); trace!(target: "rpc::eth", ?hash, ?index, "Serving eth_getTransactionByBlockHashAndIndex");
Ok(Self::transaction_by_block_and_tx_index(self, hash, index).await?) Ok(EthTransactions::transaction_by_block_and_tx_index(self, hash.into(), index).await?)
} }
/// Handler for: `eth_getRawTransactionByBlockNumberAndIndex` /// Handler for: `eth_getRawTransactionByBlockNumberAndIndex`
@ -193,7 +181,8 @@ where
index: Index, index: Index,
) -> Result<Option<Bytes>> { ) -> Result<Option<Bytes>> {
trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getRawTransactionByBlockNumberAndIndex"); trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getRawTransactionByBlockNumberAndIndex");
Ok(Self::raw_transaction_by_block_and_tx_index(self, number, index).await?) Ok(EthTransactions::raw_transaction_by_block_and_tx_index(self, number.into(), index)
.await?)
} }
/// Handler for: `eth_getTransactionByBlockNumberAndIndex` /// Handler for: `eth_getTransactionByBlockNumberAndIndex`
@ -203,7 +192,7 @@ where
index: Index, index: Index,
) -> Result<Option<reth_rpc_types::Transaction>> { ) -> Result<Option<reth_rpc_types::Transaction>> {
trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getTransactionByBlockNumberAndIndex"); trace!(target: "rpc::eth", ?number, ?index, "Serving eth_getTransactionByBlockNumberAndIndex");
Ok(Self::transaction_by_block_and_tx_index(self, number, index).await?) Ok(EthTransactions::transaction_by_block_and_tx_index(self, number.into(), index).await?)
} }
/// Handler for: `eth_getTransactionReceipt` /// Handler for: `eth_getTransactionReceipt`
@ -215,7 +204,7 @@ where
/// Handler for: `eth_getBalance` /// Handler for: `eth_getBalance`
async fn balance(&self, address: Address, block_number: Option<BlockId>) -> Result<U256> { async fn balance(&self, address: Address, block_number: Option<BlockId>) -> Result<U256> {
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getBalance"); trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getBalance");
Ok(self.on_blocking_task(|this| async move { this.balance(address, block_number) }).await?) Ok(EthState::balance(self, address, block_number).await?)
} }
/// Handler for: `eth_getStorageAt` /// Handler for: `eth_getStorageAt`
@ -226,9 +215,8 @@ where
block_number: Option<BlockId>, block_number: Option<BlockId>,
) -> Result<B256> { ) -> Result<B256> {
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getStorageAt"); trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getStorageAt");
Ok(self let res: B256 = EthState::storage_at(self, address, index, block_number).await?;
.on_blocking_task(|this| async move { this.storage_at(address, index, block_number) }) Ok(res)
.await?)
} }
/// Handler for: `eth_getTransactionCount` /// Handler for: `eth_getTransactionCount`
@ -238,31 +226,25 @@ where
block_number: Option<BlockId>, block_number: Option<BlockId>,
) -> Result<U256> { ) -> Result<U256> {
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getTransactionCount"); trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getTransactionCount");
Ok(self Ok(EthState::transaction_count(self, address, block_number).await?)
.on_blocking_task(
|this| async move { this.get_transaction_count(address, block_number) },
)
.await?)
} }
/// Handler for: `eth_getCode` /// Handler for: `eth_getCode`
async fn get_code(&self, address: Address, block_number: Option<BlockId>) -> Result<Bytes> { async fn get_code(&self, address: Address, block_number: Option<BlockId>) -> Result<Bytes> {
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getCode"); trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getCode");
Ok(self Ok(EthState::get_code(self, address, block_number).await?)
.on_blocking_task(|this| async move { this.get_code(address, block_number) })
.await?)
} }
/// Handler for: `eth_getHeaderByNumber` /// Handler for: `eth_getHeaderByNumber`
async fn header_by_number(&self, block_number: BlockNumberOrTag) -> Result<Option<Header>> { async fn header_by_number(&self, block_number: BlockNumberOrTag) -> Result<Option<Header>> {
trace!(target: "rpc::eth", ?block_number, "Serving eth_getHeaderByNumber"); trace!(target: "rpc::eth", ?block_number, "Serving eth_getHeaderByNumber");
Ok(Self::rpc_block_header(self, block_number).await?) Ok(EthBlocks::rpc_block_header(self, block_number.into()).await?)
} }
/// Handler for: `eth_getHeaderByHash` /// Handler for: `eth_getHeaderByHash`
async fn header_by_hash(&self, hash: B256) -> Result<Option<Header>> { async fn header_by_hash(&self, hash: B256) -> Result<Option<Header>> {
trace!(target: "rpc::eth", ?hash, "Serving eth_getHeaderByHash"); trace!(target: "rpc::eth", ?hash, "Serving eth_getHeaderByHash");
Ok(Self::rpc_block_header(self, hash).await?) Ok(EthBlocks::rpc_block_header(self, hash.into()).await?)
} }
/// Handler for: `eth_call` /// Handler for: `eth_call`
@ -274,9 +256,13 @@ where
block_overrides: Option<Box<BlockOverrides>>, block_overrides: Option<Box<BlockOverrides>>,
) -> Result<Bytes> { ) -> Result<Bytes> {
trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving eth_call"); trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving eth_call");
Ok(self Ok(EthCall::call(
.call(request, block_number, EvmOverrides::new(state_overrides, block_overrides)) self,
.await?) request,
block_number,
EvmOverrides::new(state_overrides, block_overrides),
)
.await?)
} }
/// Handler for: `eth_callMany` /// Handler for: `eth_callMany`
@ -287,7 +273,7 @@ where
state_override: Option<StateOverride>, state_override: Option<StateOverride>,
) -> Result<Vec<EthCallResponse>> { ) -> Result<Vec<EthCallResponse>> {
trace!(target: "rpc::eth", ?bundle, ?state_context, ?state_override, "Serving eth_callMany"); trace!(target: "rpc::eth", ?bundle, ?state_context, ?state_override, "Serving eth_callMany");
Ok(Self::call_many(self, bundle, state_context, state_override).await?) Ok(EthCall::call_many(self, bundle, state_context, state_override).await?)
} }
/// Handler for: `eth_createAccessList` /// Handler for: `eth_createAccessList`
@ -297,7 +283,8 @@ where
block_number: Option<BlockId>, block_number: Option<BlockId>,
) -> Result<AccessListWithGasUsed> { ) -> Result<AccessListWithGasUsed> {
trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_createAccessList"); trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_createAccessList");
let access_list_with_gas_used = self.create_access_list_at(request, block_number).await?; let access_list_with_gas_used =
EthCall::create_access_list_at(self, request, block_number).await?;
Ok(access_list_with_gas_used) Ok(access_list_with_gas_used)
} }
@ -310,25 +297,31 @@ where
state_override: Option<StateOverride>, state_override: Option<StateOverride>,
) -> Result<U256> { ) -> Result<U256> {
trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_estimateGas"); trace!(target: "rpc::eth", ?request, ?block_number, "Serving eth_estimateGas");
Ok(self.estimate_gas_at(request, block_number.unwrap_or_default(), state_override).await?) Ok(EthCall::estimate_gas_at(
self,
request,
block_number.unwrap_or_default(),
state_override,
)
.await?)
} }
/// Handler for: `eth_gasPrice` /// Handler for: `eth_gasPrice`
async fn gas_price(&self) -> Result<U256> { async fn gas_price(&self) -> Result<U256> {
trace!(target: "rpc::eth", "Serving eth_gasPrice"); trace!(target: "rpc::eth", "Serving eth_gasPrice");
return Ok(Self::gas_price(self).await?) return Ok(EthFees::gas_price(self).await?)
} }
/// Handler for: `eth_maxPriorityFeePerGas` /// Handler for: `eth_maxPriorityFeePerGas`
async fn max_priority_fee_per_gas(&self) -> Result<U256> { async fn max_priority_fee_per_gas(&self) -> Result<U256> {
trace!(target: "rpc::eth", "Serving eth_maxPriorityFeePerGas"); trace!(target: "rpc::eth", "Serving eth_maxPriorityFeePerGas");
return Ok(Self::suggested_priority_fee(self).await?) return Ok(EthFees::suggested_priority_fee(self).await?)
} }
/// Handler for: `eth_blobBaseFee` /// Handler for: `eth_blobBaseFee`
async fn blob_base_fee(&self) -> Result<U256> { async fn blob_base_fee(&self) -> Result<U256> {
trace!(target: "rpc::eth", "Serving eth_blobBaseFee"); trace!(target: "rpc::eth", "Serving eth_blobBaseFee");
return Ok(Self::blob_base_fee(self).await?) return Ok(EthFees::blob_base_fee(self).await?)
} }
// FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further // FeeHistory is calculated based on lazy evaluation of fees for historical blocks, and further
@ -347,7 +340,9 @@ where
reward_percentiles: Option<Vec<f64>>, reward_percentiles: Option<Vec<f64>>,
) -> Result<FeeHistory> { ) -> Result<FeeHistory> {
trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory"); trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory");
return Ok(Self::fee_history(self, block_count.to(), newest_block, reward_percentiles).await?) return Ok(
EthFees::fee_history(self, block_count.to(), newest_block, reward_percentiles).await?
)
} }
/// Handler for: `eth_mining` /// Handler for: `eth_mining`
@ -390,7 +385,7 @@ where
/// Handler for: `eth_sign` /// Handler for: `eth_sign`
async fn sign(&self, address: Address, message: Bytes) -> Result<Bytes> { async fn sign(&self, address: Address, message: Bytes) -> Result<Bytes> {
trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign"); trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign");
Ok(Self::sign(self, address, &message).await?) Ok(EthTransactions::sign(self, address, message).await?)
} }
/// Handler for: `eth_signTransaction` /// Handler for: `eth_signTransaction`
@ -401,7 +396,7 @@ where
/// Handler for: `eth_signTypedData` /// Handler for: `eth_signTypedData`
async fn sign_typed_data(&self, address: Address, data: TypedData) -> Result<Bytes> { async fn sign_typed_data(&self, address: Address, data: TypedData) -> Result<Bytes> {
trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData"); trace!(target: "rpc::eth", ?address, ?data, "Serving eth_signTypedData");
Ok(Self::sign_typed_data(self, &data, address)?) Ok(EthTransactions::sign_typed_data(self, &data, address)?)
} }
/// Handler for: `eth_getProof` /// Handler for: `eth_getProof`
@ -412,7 +407,7 @@ where
block_number: Option<BlockId>, block_number: Option<BlockId>,
) -> Result<EIP1186AccountProofResponse> { ) -> Result<EIP1186AccountProofResponse> {
trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof"); trace!(target: "rpc::eth", ?address, ?keys, ?block_number, "Serving eth_getProof");
let res = Self::get_proof(self, address, keys, block_number).await; let res = EthState::get_proof(self, address, keys, block_number)?.await;
Ok(res.map_err(|e| match e { Ok(res.map_err(|e| match e {
EthApiError::InvalidBlockRange => { EthApiError::InvalidBlockRange => {
@ -425,13 +420,6 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{
eth::{
cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache,
FeeHistoryCacheConfig,
},
EthApi,
};
use jsonrpsee::types::error::INVALID_PARAMS_CODE; use jsonrpsee::types::error::INVALID_PARAMS_CODE;
use reth_chainspec::BaseFeeParams; use reth_chainspec::BaseFeeParams;
use reth_evm_ethereum::EthEvmConfig; use reth_evm_ethereum::EthEvmConfig;
@ -444,12 +432,15 @@ mod tests {
test_utils::{MockEthProvider, NoopProvider}, test_utils::{MockEthProvider, NoopProvider},
BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory, BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory,
}; };
use reth_rpc_api::EthApiServer;
use reth_rpc_types::FeeHistory; use reth_rpc_types::FeeHistory;
use reth_tasks::pool::BlockingTaskPool; use reth_tasks::pool::BlockingTaskPool;
use reth_testing_utils::{generators, generators::Rng}; use reth_testing_utils::{generators, generators::Rng};
use reth_transaction_pool::test_utils::{testing_pool, TestPool}; use reth_transaction_pool::test_utils::{testing_pool, TestPool};
use crate::{
EthApi, EthApiServer, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
};
fn build_test_eth_api< fn build_test_eth_api<
P: BlockReaderIdExt P: BlockReaderIdExt
+ BlockReader + BlockReader
@ -661,7 +652,8 @@ mod tests {
let (eth_api, base_fees_per_gas, gas_used_ratios) = let (eth_api, base_fees_per_gas, gas_used_ratios) =
prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
let fee_history = eth_api.fee_history(1, newest_block.into(), None).await.unwrap(); let fee_history =
eth_api.fee_history(U64::from(1), newest_block.into(), None).await.unwrap();
assert_eq!( assert_eq!(
fee_history.base_fee_per_gas, fee_history.base_fee_per_gas,
&base_fees_per_gas[base_fees_per_gas.len() - 2..], &base_fees_per_gas[base_fees_per_gas.len() - 2..],
@ -695,7 +687,7 @@ mod tests {
prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default());
let fee_history = let fee_history =
eth_api.fee_history(block_count, newest_block.into(), None).await.unwrap(); eth_api.fee_history(U64::from(block_count), newest_block.into(), None).await.unwrap();
assert_eq!( assert_eq!(
&fee_history.base_fee_per_gas, &base_fees_per_gas, &fee_history.base_fee_per_gas, &base_fees_per_gas,

View File

@ -1,7 +1,9 @@
//! Configuration for RPC cache.
use reth_rpc_server_types::constants::cache::*; use reth_rpc_server_types::constants::cache::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Settings for the [`EthStateCache`](crate::eth::cache::EthStateCache). /// Settings for the [`EthStateCache`](crate::EthStateCache).
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct EthStateCacheConfig { pub struct EthStateCacheConfig {

169
crates/rpc/rpc-eth-api/src/cache/db.rs vendored Normal file
View File

@ -0,0 +1,169 @@
//! Helper types to workaround 'higher-ranked lifetime error'
//! <https://github.com/rust-lang/rust/issues/100013> in default implementation of
//! [`Call`](crate::servers::Call).
use reth_primitives::{B256, U256};
use reth_provider::StateProvider;
use reth_revm::{database::StateProviderDatabase, db::CacheDB, DatabaseRef};
use revm::Database;
/// Helper alias type for the state's [`CacheDB`]
pub type StateCacheDb<'a> = CacheDB<StateProviderDatabase<StateProviderTraitObjWrapper<'a>>>;
/// Hack to get around 'higher-ranked lifetime error', see
/// <https://github.com/rust-lang/rust/issues/100013>
#[allow(missing_debug_implementations)]
pub struct StateProviderTraitObjWrapper<'a>(pub &'a dyn StateProvider);
impl<'a> reth_provider::StateRootProvider for StateProviderTraitObjWrapper<'a> {
fn state_root(
&self,
bundle_state: &revm::db::BundleState,
) -> reth_errors::ProviderResult<B256> {
self.0.state_root(bundle_state)
}
fn state_root_with_updates(
&self,
bundle_state: &revm::db::BundleState,
) -> reth_errors::ProviderResult<(B256, reth_trie::updates::TrieUpdates)> {
self.0.state_root_with_updates(bundle_state)
}
}
impl<'a> reth_provider::AccountReader for StateProviderTraitObjWrapper<'a> {
fn basic_account(
&self,
address: revm_primitives::Address,
) -> reth_errors::ProviderResult<Option<reth_primitives::Account>> {
self.0.basic_account(address)
}
}
impl<'a> reth_provider::BlockHashReader for StateProviderTraitObjWrapper<'a> {
fn block_hash(
&self,
block_number: reth_primitives::BlockNumber,
) -> reth_errors::ProviderResult<Option<B256>> {
self.0.block_hash(block_number)
}
fn canonical_hashes_range(
&self,
start: reth_primitives::BlockNumber,
end: reth_primitives::BlockNumber,
) -> reth_errors::ProviderResult<Vec<B256>> {
self.0.canonical_hashes_range(start, end)
}
fn convert_block_hash(
&self,
hash_or_number: reth_rpc_types::BlockHashOrNumber,
) -> reth_errors::ProviderResult<Option<B256>> {
self.0.convert_block_hash(hash_or_number)
}
}
impl<'a> StateProvider for StateProviderTraitObjWrapper<'a> {
fn account_balance(
&self,
addr: revm_primitives::Address,
) -> reth_errors::ProviderResult<Option<U256>> {
self.0.account_balance(addr)
}
fn account_code(
&self,
addr: revm_primitives::Address,
) -> reth_errors::ProviderResult<Option<reth_primitives::Bytecode>> {
self.0.account_code(addr)
}
fn account_nonce(
&self,
addr: revm_primitives::Address,
) -> reth_errors::ProviderResult<Option<u64>> {
self.0.account_nonce(addr)
}
fn bytecode_by_hash(
&self,
code_hash: B256,
) -> reth_errors::ProviderResult<Option<reth_primitives::Bytecode>> {
self.0.bytecode_by_hash(code_hash)
}
fn proof(
&self,
address: revm_primitives::Address,
keys: &[B256],
) -> reth_errors::ProviderResult<reth_trie::AccountProof> {
self.0.proof(address, keys)
}
fn storage(
&self,
account: revm_primitives::Address,
storage_key: reth_primitives::StorageKey,
) -> reth_errors::ProviderResult<Option<reth_primitives::StorageValue>> {
self.0.storage(account, storage_key)
}
}
/// Hack to get around 'higher-ranked lifetime error', see
/// <https://github.com/rust-lang/rust/issues/100013>
#[allow(missing_debug_implementations)]
pub struct StateCacheDbRefMutWrapper<'a, 'b>(pub &'b mut StateCacheDb<'a>);
impl<'a, 'b> Database for StateCacheDbRefMutWrapper<'a, 'b> {
type Error = <StateCacheDb<'a> as Database>::Error;
fn basic(
&mut self,
address: revm_primitives::Address,
) -> Result<Option<revm_primitives::AccountInfo>, Self::Error> {
self.0.basic(address)
}
fn block_hash(&mut self, number: U256) -> Result<B256, Self::Error> {
self.0.block_hash(number)
}
fn code_by_hash(&mut self, code_hash: B256) -> Result<revm_primitives::Bytecode, Self::Error> {
self.0.code_by_hash(code_hash)
}
fn storage(
&mut self,
address: revm_primitives::Address,
index: U256,
) -> Result<U256, Self::Error> {
self.0.storage(address, index)
}
}
impl<'a, 'b> DatabaseRef for StateCacheDbRefMutWrapper<'a, 'b> {
type Error = <StateCacheDb<'a> as Database>::Error;
fn basic_ref(
&self,
address: revm_primitives::Address,
) -> Result<Option<revm_primitives::AccountInfo>, Self::Error> {
self.0.basic_ref(address)
}
fn block_hash_ref(&self, number: U256) -> Result<B256, Self::Error> {
self.0.block_hash_ref(number)
}
fn code_by_hash_ref(&self, code_hash: B256) -> Result<revm_primitives::Bytecode, Self::Error> {
self.0.code_by_hash_ref(code_hash)
}
fn storage_ref(
&self,
address: revm_primitives::Address,
index: U256,
) -> Result<U256, Self::Error> {
self.0.storage_ref(address, index)
}
}

View File

@ -1,3 +1,5 @@
//! Tracks state of RPC cache.
use metrics::Counter; use metrics::Counter;
use reth_metrics::{metrics::Gauge, Metrics}; use reth_metrics::{metrics::Gauge, Metrics};

View File

@ -26,13 +26,12 @@ use tokio::sync::{
}; };
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
mod config; use crate::{EthStateCacheConfig, MultiConsumerLruCache};
pub use config::*;
mod metrics; pub mod config;
pub mod db;
mod multi_consumer; pub mod metrics;
pub use multi_consumer::MultiConsumerLruCache; pub mod multi_consumer;
/// The type that can send the response to a requested [Block] /// The type that can send the response to a requested [Block]
type BlockTransactionsResponseSender = type BlockTransactionsResponseSender =
@ -107,7 +106,7 @@ impl EthStateCache {
) -> Self ) -> Self
where where
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
Self::spawn_with(provider, config, TokioTaskExecutor::default(), evm_config) Self::spawn_with(provider, config, TokioTaskExecutor::default(), evm_config)
} }
@ -125,7 +124,7 @@ impl EthStateCache {
where where
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
Tasks: TaskSpawner + Clone + 'static, Tasks: TaskSpawner + Clone + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
let EthStateCacheConfig { max_blocks, max_receipts, max_envs, max_concurrent_db_requests } = let EthStateCacheConfig { max_blocks, max_receipts, max_envs, max_concurrent_db_requests } =
config; config;
@ -316,7 +315,7 @@ impl<Provider, Tasks, EvmConfig> EthStateCacheService<Provider, Tasks, EvmConfig
where where
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
Tasks: TaskSpawner + Clone + 'static, Tasks: TaskSpawner + Clone + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
fn on_new_block(&mut self, block_hash: B256, res: ProviderResult<Option<BlockWithSenders>>) { fn on_new_block(&mut self, block_hash: B256, res: ProviderResult<Option<BlockWithSenders>>) {
if let Some(queued) = self.full_block_cache.remove(&block_hash) { if let Some(queued) = self.full_block_cache.remove(&block_hash) {
@ -403,7 +402,7 @@ impl<Provider, Tasks, EvmConfig> Future for EthStateCacheService<Provider, Tasks
where where
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
Tasks: TaskSpawner + Clone + 'static, Tasks: TaskSpawner + Clone + 'static,
EvmConfig: ConfigureEvm + 'static, EvmConfig: ConfigureEvm,
{ {
type Output = (); type Output = ();

View File

@ -1,3 +1,6 @@
//! Metered cache, which also provides storage for senders in order to queue queries that result in
//! a cache miss.
use super::metrics::CacheMetrics; use super::metrics::CacheMetrics;
use schnellru::{ByLength, Limiter, LruMap}; use schnellru::{ByLength, Limiter, LruMap};
use std::{ use std::{
@ -12,9 +15,9 @@ where
K: Hash + Eq, K: Hash + Eq,
L: Limiter<K, V>, L: Limiter<K, V>,
{ {
/// The LRU cache for the /// The LRU cache.
cache: LruMap<K, V, L>, cache: LruMap<K, V, L>,
/// All queued consumers /// All queued consumers.
queued: HashMap<K, Vec<S>>, queued: HashMap<K, Vec<S>>,
/// Cache metrics /// Cache metrics
metrics: CacheMetrics, metrics: CacheMetrics,

View File

@ -1,6 +1,11 @@
//! Consist of types adjacent to the fee history cache and its configs //! Consist of types adjacent to the fee history cache and its configs
use crate::eth::{cache::EthStateCache, error::EthApiError}; use std::{
collections::{BTreeMap, VecDeque},
fmt::Debug,
sync::{atomic::Ordering::SeqCst, Arc},
};
use futures::{ use futures::{
future::{Fuse, FusedFuture}, future::{Fuse, FusedFuture},
FutureExt, Stream, StreamExt, FutureExt, Stream, StreamExt,
@ -16,13 +21,10 @@ use reth_provider::{BlockReaderIdExt, CanonStateNotification, ChainSpecProvider}
use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY; use reth_rpc_server_types::constants::gas_oracle::MAX_HEADER_HISTORY;
use reth_rpc_types::TxGasAndReward; use reth_rpc_types::TxGasAndReward;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, VecDeque},
fmt::Debug,
sync::{atomic::Ordering::SeqCst, Arc},
};
use tracing::trace; use tracing::trace;
use crate::{EthApiError, EthStateCache};
/// Contains cached fee history entries for blocks. /// Contains cached fee history entries for blocks.
/// ///
/// Purpose for this is to provide cached data for `eth_feeHistory`. /// Purpose for this is to provide cached data for `eth_feeHistory`.

View File

@ -1,20 +1,33 @@
//! An implementation of the eth gas price oracle, used for providing gas price estimates based on //! An implementation of the eth gas price oracle, used for providing gas price estimates based on
//! previous blocks. //! previous blocks.
use crate::eth::{ use std::fmt::{self, Debug, Formatter};
cache::EthStateCache,
error::{EthApiError, EthResult, RpcInvalidTransactionError},
};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, B256, U256}; use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, B256, U256};
use reth_provider::BlockReaderIdExt; use reth_provider::BlockReaderIdExt;
use reth_rpc_server_types::constants::gas_oracle::*; use reth_rpc_server_types::constants::gas_oracle::*;
use schnellru::{ByLength, LruMap}; use schnellru::{ByLength, LruMap};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::{self, Debug, Formatter};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::warn; use tracing::warn;
use crate::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError};
/// The default gas limit for `eth_call` and adjacent calls.
///
/// This is different from the default to regular 30M block gas limit
/// [`ETHEREUM_BLOCK_GAS_LIMIT`](reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT) to allow for
/// more complex calls.
pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(50_000_000);
/// Gas per transaction not creating a contract.
pub const MIN_TRANSACTION_GAS: u64 = 21_000u64;
/// Allowed error ratio for gas estimation
/// Taken from Geth's implementation in order to pass the hive tests
/// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/internal/ethapi/api.go#L56>
pub const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015;
/// Settings for the [`GasPriceOracle`] /// Settings for the [`GasPriceOracle`]
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -73,7 +86,7 @@ pub struct GasPriceOracle<Provider> {
impl<Provider> GasPriceOracle<Provider> impl<Provider> GasPriceOracle<Provider>
where where
Provider: BlockReaderIdExt + 'static, Provider: BlockReaderIdExt,
{ {
/// Creates and returns the [`GasPriceOracle`]. /// Creates and returns the [`GasPriceOracle`].
pub fn new( pub fn new(
@ -286,6 +299,28 @@ impl Default for GasPriceOracleResult {
} }
} }
/// The wrapper type for gas limit
#[derive(Debug, Clone, Copy)]
pub struct GasCap(u64);
impl Default for GasCap {
fn default() -> Self {
RPC_DEFAULT_GAS_CAP
}
}
impl From<u64> for GasCap {
fn from(gas_cap: u64) -> Self {
Self(gas_cap)
}
}
impl From<GasCap> for u64 {
fn from(gas_cap: GasCap) -> Self {
gas_cap.0
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,6 +1,11 @@
use jsonrpsee::types::SubscriptionId; //! Helper type for [`EthPubSubApiServer`](crate::EthPubSubApiServer) implementation.
//!
//! Generates IDs for tracking subscriptions.
use std::fmt::Write; use std::fmt::Write;
use jsonrpsee::types::SubscriptionId;
/// An [`IdProvider`](jsonrpsee::core::traits::IdProvider) for ethereum subscription ids. /// An [`IdProvider`](jsonrpsee::core::traits::IdProvider) for ethereum subscription ids.
/// ///
/// Returns new hex-string [QUANTITY](https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding) ids /// Returns new hex-string [QUANTITY](https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding) ids

View File

@ -0,0 +1,62 @@
//! Reth RPC `eth_` API implementation
//!
//! ## Feature Flags
//!
//! - `client`: Enables JSON-RPC client support.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
pub mod api;
pub mod cache;
pub mod error;
pub mod fee_history;
pub mod gas_oracle;
pub mod id_provider;
pub mod logs_utils;
pub mod pending_block;
pub mod receipt;
pub mod result;
pub mod revm_utils;
pub mod transaction;
pub mod utils;
#[cfg(feature = "client")]
pub use api::{
bundle::{EthBundleApiClient, EthCallBundleApiClient},
filter::EthFilterApiClient,
EthApiClient,
};
pub use api::{
bundle::{EthBundleApiServer, EthCallBundleApiServer},
filter::EthFilterApiServer,
pubsub::EthPubSubApiServer,
servers::{
self,
bundle::EthBundle,
filter::{EthFilter, EthFilterConfig},
pubsub::EthPubSub,
EthApi,
},
EthApiServer,
};
pub use cache::{
config::EthStateCacheConfig, db::StateCacheDb, multi_consumer::MultiConsumerLruCache,
EthStateCache,
};
pub use error::{EthApiError, EthResult, RevertError, RpcInvalidTransactionError, SignError};
pub use fee_history::{FeeHistoryCache, FeeHistoryCacheConfig, FeeHistoryEntry};
pub use gas_oracle::{
GasCap, GasPriceOracle, GasPriceOracleConfig, GasPriceOracleResult, ESTIMATE_GAS_ERROR_RATIO,
MIN_TRANSACTION_GAS, RPC_DEFAULT_GAS_CAP,
};
pub use id_provider::EthSubscriptionIdProvider;
pub use pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin};
pub use receipt::ReceiptBuilder;
pub use result::ToRpcResult;
pub use transaction::TransactionSource;

View File

@ -1,10 +1,14 @@
use super::filter::FilterError; //! Helper functions for [`EthFilterApiServer`](crate::EthFilterApiServer) implementation.
use alloy_primitives::TxHash; //!
//! Log parsing for building filter.
use reth_chainspec::ChainInfo; use reth_chainspec::ChainInfo;
use reth_primitives::{BlockNumHash, Receipt}; use reth_primitives::{BlockNumHash, Receipt, TxHash};
use reth_provider::{BlockReader, ProviderError}; use reth_provider::{BlockReader, ProviderError};
use reth_rpc_types::{FilteredParams, Log}; use reth_rpc_types::{FilteredParams, Log};
use crate::servers::filter::EthFilterError;
/// Returns all matching of a block's receipts when the transaction hashes are known. /// Returns all matching of a block's receipts when the transaction hashes are known.
pub(crate) fn matching_block_logs_with_tx_hashes<'a, I>( pub(crate) fn matching_block_logs_with_tx_hashes<'a, I>(
filter: &FilteredParams, filter: &FilteredParams,
@ -51,7 +55,7 @@ pub(crate) fn append_matching_block_logs(
receipts: &[Receipt], receipts: &[Receipt],
removed: bool, removed: bool,
block_timestamp: u64, block_timestamp: u64,
) -> Result<(), FilterError> { ) -> Result<(), EthFilterError> {
// Tracks the index of a log in the entire block. // Tracks the index of a log in the entire block.
let mut log_index: u64 = 0; let mut log_index: u64 = 0;

View File

@ -0,0 +1,162 @@
//! Helper types for [`EthApiServer`](crate::EthApiServer) implementation.
//!
//! Types used in block building.
use std::{fmt, time::Instant};
use derive_more::Constructor;
use reth_chainspec::ChainSpec;
use reth_primitives::{BlockId, BlockNumberOrTag, SealedBlockWithSenders, SealedHeader, B256};
use reth_provider::ProviderError;
use reth_revm::state_change::{apply_beacon_root_contract_call, apply_blockhashes_update};
use revm_primitives::{
db::{Database, DatabaseCommit},
BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg,
};
use crate::{EthApiError, EthResult};
/// Configured [`BlockEnv`] and [`CfgEnvWithHandlerCfg`] for a pending block
#[derive(Debug, Clone, Constructor)]
pub struct PendingBlockEnv {
/// Configured [`CfgEnvWithHandlerCfg`] for the pending block.
pub cfg: CfgEnvWithHandlerCfg,
/// Configured [`BlockEnv`] for the pending block.
pub block_env: BlockEnv,
/// Origin block for the config
pub origin: PendingBlockEnvOrigin,
}
/// Apply the [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) pre block contract call.
///
/// This constructs a new [Evm](revm::Evm) with the given DB, and environment
/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] to execute the pre block contract call.
///
/// This uses [`apply_beacon_root_contract_call`] to ultimately apply the beacon root contract state
/// change.
pub fn pre_block_beacon_root_contract_call<DB: Database + DatabaseCommit>(
db: &mut DB,
chain_spec: &ChainSpec,
block_number: u64,
initialized_cfg: &CfgEnvWithHandlerCfg,
initialized_block_env: &BlockEnv,
parent_beacon_block_root: Option<B256>,
) -> EthResult<()>
where
DB::Error: fmt::Display,
{
// apply pre-block EIP-4788 contract call
let mut evm_pre_block = revm::Evm::builder()
.with_db(db)
.with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
initialized_cfg.clone(),
initialized_block_env.clone(),
Default::default(),
))
.build();
// initialize a block from the env, because the pre block call needs the block itself
apply_beacon_root_contract_call(
chain_spec,
initialized_block_env.timestamp.to::<u64>(),
block_number,
parent_beacon_block_root,
&mut evm_pre_block,
)
.map_err(|err| EthApiError::Internal(err.into()))
}
/// Apply the [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) pre block state transitions.
///
/// This constructs a new [Evm](revm::Evm) with the given DB, and environment
/// [`CfgEnvWithHandlerCfg`] and [`BlockEnv`].
///
/// This uses [`apply_blockhashes_update`].
pub fn pre_block_blockhashes_update<DB: Database<Error = ProviderError> + DatabaseCommit>(
db: &mut DB,
chain_spec: &ChainSpec,
initialized_block_env: &BlockEnv,
block_number: u64,
parent_block_hash: B256,
) -> EthResult<()>
where
DB::Error: fmt::Display,
{
apply_blockhashes_update(
db,
chain_spec,
initialized_block_env.timestamp.to::<u64>(),
block_number,
parent_block_hash,
)
.map_err(|err| EthApiError::Internal(err.into()))
}
/// The origin for a configured [`PendingBlockEnv`]
#[derive(Clone, Debug)]
pub enum PendingBlockEnvOrigin {
/// The pending block as received from the CL.
ActualPending(SealedBlockWithSenders),
/// The _modified_ header of the latest block.
///
/// This derives the pending state based on the latest header by modifying:
/// - the timestamp
/// - the block number
/// - fees
DerivedFromLatest(SealedHeader),
}
impl PendingBlockEnvOrigin {
/// Returns true if the origin is the actual pending block as received from the CL.
pub const fn is_actual_pending(&self) -> bool {
matches!(self, Self::ActualPending(_))
}
/// Consumes the type and returns the actual pending block.
pub fn into_actual_pending(self) -> Option<SealedBlockWithSenders> {
match self {
Self::ActualPending(block) => Some(block),
_ => None,
}
}
/// Returns the [`BlockId`] that represents the state of the block.
///
/// If this is the actual pending block, the state is the "Pending" tag, otherwise we can safely
/// identify the block by its hash (latest block).
pub fn state_block_id(&self) -> BlockId {
match self {
Self::ActualPending(_) => BlockNumberOrTag::Pending.into(),
Self::DerivedFromLatest(header) => BlockId::Hash(header.hash().into()),
}
}
/// Returns the hash of the block the pending block should be built on.
///
/// For the [`PendingBlockEnvOrigin::ActualPending`] this is the parent hash of the block.
/// For the [`PendingBlockEnvOrigin::DerivedFromLatest`] this is the hash of the _latest_
/// header.
pub fn build_target_hash(&self) -> B256 {
match self {
Self::ActualPending(block) => block.parent_hash,
Self::DerivedFromLatest(header) => header.hash(),
}
}
/// Returns the header this pending block is based on.
pub fn header(&self) -> &SealedHeader {
match self {
Self::ActualPending(block) => &block.header,
Self::DerivedFromLatest(header) => header,
}
}
}
/// In memory pending block for `pending` tag
#[derive(Debug, Constructor)]
pub struct PendingBlock {
/// The cached pending block
pub block: SealedBlockWithSenders,
/// Timestamp when the pending block is considered outdated
pub expires_at: Instant,
}

View File

@ -0,0 +1,126 @@
//! RPC receipt response builder, extends a layer one receipt with layer two data.
use reth_primitives::{Address, Receipt, TransactionMeta, TransactionSigned, TxKind};
use reth_rpc_types::{
AnyReceiptEnvelope, AnyTransactionReceipt, Log, OtherFields, ReceiptWithBloom,
TransactionReceipt, WithOtherFields,
};
use revm_primitives::calc_blob_gasprice;
use crate::{EthApiError, EthResult};
/// Receipt response builder.
#[derive(Debug)]
pub struct ReceiptBuilder {
/// The base response body, contains L1 fields.
base: TransactionReceipt<AnyReceiptEnvelope<Log>>,
/// Additional L2 fields.
other: OtherFields,
}
impl ReceiptBuilder {
/// Returns a new builder with the base response body (L1 fields) set.
///
/// Note: This requires _all_ block receipts because we need to calculate the gas used by the
/// transaction.
pub fn new(
transaction: &TransactionSigned,
meta: TransactionMeta,
receipt: &Receipt,
all_receipts: &[Receipt],
) -> EthResult<Self> {
// Note: we assume this transaction is valid, because it's mined (or part of pending block)
// and we don't need to check for pre EIP-2
let from = transaction
.recover_signer_unchecked()
.ok_or(EthApiError::InvalidTransactionSignature)?;
// get the previous transaction cumulative gas used
let gas_used = if meta.index == 0 {
receipt.cumulative_gas_used
} else {
let prev_tx_idx = (meta.index - 1) as usize;
all_receipts
.get(prev_tx_idx)
.map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used)
.unwrap_or_default()
};
let blob_gas_used = transaction.transaction.blob_gas_used();
// Blob gas price should only be present if the transaction is a blob transaction
let blob_gas_price =
blob_gas_used.and_then(|_| meta.excess_blob_gas.map(calc_blob_gasprice));
let logs_bloom = receipt.bloom_slow();
// get number of logs in the block
let mut num_logs = 0;
for prev_receipt in all_receipts.iter().take(meta.index as usize) {
num_logs += prev_receipt.logs.len();
}
let mut logs = Vec::with_capacity(receipt.logs.len());
for (tx_log_idx, log) in receipt.logs.iter().enumerate() {
let rpclog = Log {
inner: log.clone(),
block_hash: Some(meta.block_hash),
block_number: Some(meta.block_number),
block_timestamp: Some(meta.timestamp),
transaction_hash: Some(meta.tx_hash),
transaction_index: Some(meta.index),
log_index: Some((num_logs + tx_log_idx) as u64),
removed: false,
};
logs.push(rpclog);
}
let rpc_receipt = reth_rpc_types::Receipt {
status: receipt.success.into(),
cumulative_gas_used: receipt.cumulative_gas_used as u128,
logs,
};
let (contract_address, to) = match transaction.transaction.kind() {
TxKind::Create => (Some(from.create(transaction.transaction.nonce())), None),
TxKind::Call(addr) => (None, Some(Address(*addr))),
};
#[allow(clippy::needless_update)]
let base = TransactionReceipt {
inner: AnyReceiptEnvelope {
inner: ReceiptWithBloom { receipt: rpc_receipt, logs_bloom },
r#type: transaction.transaction.tx_type().into(),
},
transaction_hash: meta.tx_hash,
transaction_index: Some(meta.index),
block_hash: Some(meta.block_hash),
block_number: Some(meta.block_number),
from,
to,
gas_used: gas_used as u128,
contract_address,
effective_gas_price: transaction.effective_gas_price(meta.base_fee),
// TODO pre-byzantium receipts have a post-transaction state root
state_root: None,
// EIP-4844 fields
blob_gas_price,
blob_gas_used: blob_gas_used.map(u128::from),
};
Ok(Self { base, other: Default::default() })
}
/// Adds fields to response body.
pub fn add_other_fields(mut self, mut fields: OtherFields) -> Self {
self.other.append(&mut fields);
self
}
/// Builds a receipt response from the base response body, and any set additional fields.
pub fn build(self) -> AnyTransactionReceipt {
let Self { base, other } = self;
let mut res = WithOtherFields::new(base);
res.other = other;
res
}
}

View File

@ -1,8 +1,9 @@
//! Additional helpers for converting errors. //! Additional helpers for converting errors.
use std::fmt::Display;
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
use reth_rpc_types::engine::PayloadError; use reth_rpc_types::engine::PayloadError;
use std::fmt::Display;
/// Helper trait to easily convert various `Result` types into [`RpcResult`] /// Helper trait to easily convert various `Result` types into [`RpcResult`]
pub trait ToRpcResult<Ok, Err>: Sized { pub trait ToRpcResult<Ok, Err>: Sized {
@ -104,21 +105,19 @@ impl_to_rpc_result!(reth_errors::ProviderError);
impl_to_rpc_result!(reth_network_api::NetworkError); impl_to_rpc_result!(reth_network_api::NetworkError);
/// Constructs an invalid params JSON-RPC error. /// Constructs an invalid params JSON-RPC error.
pub(crate) fn invalid_params_rpc_err( pub fn invalid_params_rpc_err(
msg: impl Into<String>, msg: impl Into<String>,
) -> jsonrpsee::types::error::ErrorObject<'static> { ) -> jsonrpsee::types::error::ErrorObject<'static> {
rpc_err(jsonrpsee::types::error::INVALID_PARAMS_CODE, msg, None) rpc_err(jsonrpsee::types::error::INVALID_PARAMS_CODE, msg, None)
} }
/// Constructs an internal JSON-RPC error. /// Constructs an internal JSON-RPC error.
pub(crate) fn internal_rpc_err( pub fn internal_rpc_err(msg: impl Into<String>) -> jsonrpsee::types::error::ErrorObject<'static> {
msg: impl Into<String>,
) -> jsonrpsee::types::error::ErrorObject<'static> {
rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None) rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None)
} }
/// Constructs an internal JSON-RPC error with data /// Constructs an internal JSON-RPC error with data
pub(crate) fn internal_rpc_err_with_data( pub fn internal_rpc_err_with_data(
msg: impl Into<String>, msg: impl Into<String>,
data: &[u8], data: &[u8],
) -> jsonrpsee::types::error::ErrorObject<'static> { ) -> jsonrpsee::types::error::ErrorObject<'static> {
@ -126,7 +125,7 @@ pub(crate) fn internal_rpc_err_with_data(
} }
/// Constructs an internal JSON-RPC error with code and message /// Constructs an internal JSON-RPC error with code and message
pub(crate) fn rpc_error_with_code( pub fn rpc_error_with_code(
code: i32, code: i32,
msg: impl Into<String>, msg: impl Into<String>,
) -> jsonrpsee::types::error::ErrorObject<'static> { ) -> jsonrpsee::types::error::ErrorObject<'static> {
@ -134,7 +133,7 @@ pub(crate) fn rpc_error_with_code(
} }
/// Constructs a JSON-RPC error, consisting of `code`, `message` and optional `data`. /// Constructs a JSON-RPC error, consisting of `code`, `message` and optional `data`.
pub(crate) fn rpc_err( pub fn rpc_err(
code: i32, code: i32,
msg: impl Into<String>, msg: impl Into<String>,
data: Option<&[u8]>, data: Option<&[u8]>,

View File

@ -1,14 +1,8 @@
//! utilities for working with revm //! utilities for working with revm
use crate::eth::error::{EthApiError, EthResult, RpcInvalidTransactionError}; use std::cmp::min;
#[cfg(feature = "optimism")]
use reth_primitives::revm::env::fill_op_tx_env; use reth_primitives::{Address, TxKind, B256, U256};
#[cfg(not(feature = "optimism"))]
use reth_primitives::revm::env::fill_tx_env;
use reth_primitives::{
revm::env::fill_tx_env_with_recovered, Address, TransactionSigned,
TransactionSignedEcRecovered, TxHash, TxKind, B256, U256,
};
use reth_rpc_types::{ use reth_rpc_types::{
state::{AccountOverride, EvmOverrides, StateOverride}, state::{AccountOverride, EvmOverrides, StateOverride},
BlockOverrides, TransactionRequest, BlockOverrides, TransactionRequest,
@ -23,58 +17,9 @@ use revm::{
}, },
Database, Database,
}; };
use std::cmp::min;
use tracing::trace; use tracing::trace;
/// Helper type to work with different transaction types when configuring the EVM env. use crate::{EthApiError, EthResult, RpcInvalidTransactionError};
///
/// This makes it easier to handle errors.
pub trait FillableTransaction {
/// Returns the hash of the transaction.
fn hash(&self) -> TxHash;
/// Fill the transaction environment with the given transaction.
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()>;
}
impl FillableTransaction for TransactionSignedEcRecovered {
fn hash(&self) -> TxHash {
self.hash
}
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> {
#[cfg(not(feature = "optimism"))]
fill_tx_env_with_recovered(tx_env, self);
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(self.length_without_header());
self.encode_enveloped(&mut envelope_buf);
fill_tx_env_with_recovered(tx_env, self, envelope_buf.into());
}
Ok(())
}
}
impl FillableTransaction for TransactionSigned {
fn hash(&self) -> TxHash {
self.hash
}
fn try_fill_tx_env(&self, tx_env: &mut TxEnv) -> EthResult<()> {
let signer =
self.recover_signer().ok_or_else(|| EthApiError::InvalidTransactionSignature)?;
#[cfg(not(feature = "optimism"))]
fill_tx_env(tx_env, self, signer);
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(self.length_without_header());
self.encode_enveloped(&mut envelope_buf);
fill_op_tx_env(tx_env, self, signer, envelope_buf.into());
}
Ok(())
}
}
/// Returns the addresses of the precompiles corresponding to the `SpecId`. /// Returns the addresses of the precompiles corresponding to the `SpecId`.
#[inline] #[inline]

View File

@ -0,0 +1,96 @@
//! Helper types for [`EthApiServer`](crate::EthApiServer) implementation.
//!
//! Transaction wrapper that labels transaction with its origin.
use reth_primitives::{TransactionSignedEcRecovered, B256};
use reth_rpc_types::{Transaction, TransactionInfo};
use reth_rpc_types_compat::transaction::from_recovered_with_block_context;
/// 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 included in a block
///
/// This can be a historical block or a pending block (received from the CL)
Block {
/// Transaction fetched via provider
transaction: TransactionSignedEcRecovered,
/// Index of the transaction in the block
index: u64,
/// Hash of the block.
block_hash: B256,
/// Number of the block.
block_number: u64,
/// base fee of the block.
base_fee: Option<u64>,
},
}
// === impl TransactionSource ===
impl TransactionSource {
/// Consumes the type and returns the wrapped transaction.
pub fn into_recovered(self) -> TransactionSignedEcRecovered {
self.into()
}
/// Returns the transaction and block related info, if not pending
pub fn split(self) -> (TransactionSignedEcRecovered, TransactionInfo) {
match self {
Self::Pool(tx) => {
let hash = tx.hash();
(
tx,
TransactionInfo {
hash: Some(hash),
index: None,
block_hash: None,
block_number: None,
base_fee: None,
},
)
}
Self::Block { transaction, index, block_hash, block_number, base_fee } => {
let hash = transaction.hash();
(
transaction,
TransactionInfo {
hash: Some(hash),
index: Some(index),
block_hash: Some(block_hash),
block_number: Some(block_number),
base_fee: base_fee.map(u128::from),
},
)
}
}
}
}
impl From<TransactionSource> for TransactionSignedEcRecovered {
fn from(value: TransactionSource) -> Self {
match value {
TransactionSource::Pool(tx) => tx,
TransactionSource::Block { transaction, .. } => transaction,
}
}
}
impl From<TransactionSource> for Transaction {
fn from(value: TransactionSource) -> Self {
match value {
TransactionSource::Pool(tx) => reth_rpc_types_compat::transaction::from_recovered(tx),
TransactionSource::Block { transaction, index, block_hash, block_number, base_fee } => {
from_recovered_with_block_context(
transaction,
block_hash,
block_number,
base_fee,
index as usize,
)
}
}
}
}

View File

@ -1,14 +1,13 @@
//! Commonly used code snippets //! Commonly used code snippets
use crate::eth::error::{EthApiError, EthResult};
use reth_primitives::{Bytes, PooledTransactionsElement, PooledTransactionsElementEcRecovered}; use reth_primitives::{Bytes, PooledTransactionsElement, PooledTransactionsElementEcRecovered};
use crate::{EthApiError, EthResult};
/// Recovers a [`PooledTransactionsElementEcRecovered`] from an enveloped encoded byte stream. /// Recovers a [`PooledTransactionsElementEcRecovered`] from an enveloped encoded byte stream.
/// ///
/// See [`PooledTransactionsElement::decode_enveloped`] /// See [`PooledTransactionsElement::decode_enveloped`]
pub(crate) fn recover_raw_transaction( pub fn recover_raw_transaction(data: Bytes) -> EthResult<PooledTransactionsElementEcRecovered> {
data: Bytes,
) -> EthResult<PooledTransactionsElementEcRecovered> {
if data.is_empty() { if data.is_empty() {
return Err(EthApiError::EmptyRawTransactionData) return Err(EthApiError::EmptyRawTransactionData)
} }

View File

@ -29,3 +29,4 @@ similar-asserts.workspace = true
[dev-dependencies] [dev-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] }
reth-rpc-eth-api.workspace = true

View File

@ -1,7 +1,7 @@
use futures::StreamExt; use futures::StreamExt;
use jsonrpsee::http_client::HttpClientBuilder; use jsonrpsee::http_client::HttpClientBuilder;
use reth_rpc_api::EthApiClient;
use reth_rpc_api_testing_util::{debug::DebugApiExt, trace::TraceApiExt, utils::parse_env_url}; use reth_rpc_api_testing_util::{debug::DebugApiExt, trace::TraceApiExt, utils::parse_env_url};
use reth_rpc_eth_api::EthApiClient;
use reth_rpc_types::trace::{ use reth_rpc_types::trace::{
filter::TraceFilter, parity::TraceType, tracerequest::TraceCallRequest, filter::TraceFilter, parity::TraceType, tracerequest::TraceCallRequest,
}; };

View File

@ -16,7 +16,7 @@ workspace = true
reth-chainspec.workspace = true reth-chainspec.workspace = true
reth-primitives.workspace = true reth-primitives.workspace = true
reth-rpc-api.workspace = true reth-rpc-api.workspace = true
reth-rpc-server-types.workspace = true reth-rpc-eth-api.workspace = true
reth-rpc-types.workspace = true reth-rpc-types.workspace = true
reth-errors.workspace = true reth-errors.workspace = true
reth-provider = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] }
@ -28,17 +28,11 @@ reth-tasks = { workspace = true, features = ["rayon"] }
reth-consensus-common.workspace = true reth-consensus-common.workspace = true
reth-rpc-types-compat.workspace = true reth-rpc-types-compat.workspace = true
revm-inspectors = { workspace = true, features = ["js-tracer"] } revm-inspectors = { workspace = true, features = ["js-tracer"] }
reth-evm.workspace = true
reth-network-peers.workspace = true reth-network-peers.workspace = true
reth-execution-types.workspace = true
reth-evm-optimism = { workspace = true, optional = true }
# eth # eth
alloy-rlp.workspace = true alloy-rlp.workspace = true
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
alloy-primitives.workspace = true alloy-primitives.workspace = true
alloy-sol-types.workspace = true
alloy-genesis.workspace = true alloy-genesis.workspace = true
revm = { workspace = true, features = [ revm = { workspace = true, features = [
"optional_block_gas_limit", "optional_block_gas_limit",
@ -58,30 +52,12 @@ jsonwebtoken.workspace = true
async-trait.workspace = true async-trait.workspace = true
tokio = { workspace = true, features = ["sync"] } tokio = { workspace = true, features = ["sync"] }
tower.workspace = true tower.workspace = true
tokio-stream = { workspace = true, features = ["sync"] }
pin-project.workspace = true pin-project.workspace = true
parking_lot.workspace = true
# metrics
reth-metrics.workspace = true
metrics.workspace = true
# misc # misc
secp256k1 = { workspace = true, features = [
"global-context",
"rand-std",
"recovery",
] }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
thiserror.workspace = true
rand.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-futures = "0.2" tracing-futures = "0.2"
schnellru.workspace = true
futures.workspace = true futures.workspace = true
derive_more.workspace = true
dyn-clone.workspace = true
[dev-dependencies] [dev-dependencies]
reth-evm-ethereum.workspace = true reth-evm-ethereum.workspace = true
@ -96,7 +72,7 @@ optimism = [
"reth-primitives/optimism", "reth-primitives/optimism",
"reth-rpc-types-compat/optimism", "reth-rpc-types-compat/optimism",
"reth-provider/optimism", "reth-provider/optimism",
"dep:reth-evm-optimism", "reth-rpc-api/optimism",
"reth-evm-optimism/optimism", "reth-rpc-eth-api/optimism",
"reth-revm/optimism", "reth-revm/optimism",
] ]

View File

@ -1,4 +1,5 @@
use crate::result::ToRpcResult; use std::sync::Arc;
use alloy_genesis::ChainConfig; use alloy_genesis::ChainConfig;
use alloy_primitives::B256; use alloy_primitives::B256;
use async_trait::async_trait; use async_trait::async_trait;
@ -11,7 +12,8 @@ use reth_rpc_types::{
admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo}, admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo},
PeerEthProtocolInfo, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo, PeerEthProtocolInfo, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo,
}; };
use std::sync::Arc;
use crate::result::ToRpcResult;
/// `admin` API implementation. /// `admin` API implementation.
/// ///

View File

@ -1,12 +1,5 @@
use crate::{ use std::sync::Arc;
eth::{
error::{EthApiError, EthResult},
revm_utils::prepare_call_env,
EthTransactions,
},
result::{internal_rpc_err, ToRpcResult},
EthApiSpec,
};
use alloy_rlp::{Decodable, Encodable}; use alloy_rlp::{Decodable, Encodable};
use async_trait::async_trait; use async_trait::async_trait;
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
@ -15,10 +8,17 @@ use reth_primitives::{
TransactionSignedEcRecovered, Withdrawals, B256, U256, TransactionSignedEcRecovered, Withdrawals, B256, U256,
}; };
use reth_provider::{ use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderBox, TransactionVariant, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory,
TransactionVariant,
}; };
use reth_revm::database::StateProviderDatabase; use reth_revm::database::StateProviderDatabase;
use reth_rpc_api::DebugApiServer; use reth_rpc_api::DebugApiServer;
use reth_rpc_eth_api::{
result::internal_rpc_err,
revm_utils::prepare_call_env,
servers::{EthApiSpec, EthTransactions, TraceExt},
EthApiError, EthResult, StateCacheDb, ToRpcResult,
};
use reth_rpc_types::{ use reth_rpc_types::{
state::EvmOverrides, state::EvmOverrides,
trace::geth::{ trace::geth::{
@ -36,7 +36,6 @@ use revm_inspectors::tracing::{
js::{JsInspector, TransactionContext}, js::{JsInspector, TransactionContext},
FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig, FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig,
}; };
use std::sync::Arc;
use tokio::sync::{AcquireError, OwnedSemaphorePermit}; use tokio::sync::{AcquireError, OwnedSemaphorePermit};
/// `debug` API implementation. /// `debug` API implementation.
@ -65,8 +64,13 @@ impl<Provider, Eth> DebugApi<Provider, Eth> {
impl<Provider, Eth> DebugApi<Provider, Eth> impl<Provider, Eth> DebugApi<Provider, Eth>
where where
Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static, Provider: BlockReaderIdExt
Eth: EthTransactions + 'static, + HeaderProvider
+ ChainSpecProvider
+ StateProviderFactory
+ EvmEnvProvider
+ 'static,
Eth: TraceExt + 'static,
{ {
/// Acquires a permit to execute a tracing call. /// Acquires a permit to execute a tracing call.
async fn acquire_trace_permit(&self) -> Result<OwnedSemaphorePermit, AcquireError> { async fn acquire_trace_permit(&self) -> Result<OwnedSemaphorePermit, AcquireError> {
@ -74,7 +78,7 @@ where
} }
/// Trace the entire block asynchronously /// Trace the entire block asynchronously
async fn trace_block_with( async fn trace_block(
&self, &self,
at: BlockId, at: BlockId,
transactions: Vec<TransactionSignedEcRecovered>, transactions: Vec<TransactionSignedEcRecovered>,
@ -165,7 +169,7 @@ where
.collect::<EthResult<Vec<_>>>()? .collect::<EthResult<Vec<_>>>()?
}; };
self.trace_block_with(parent.into(), transactions, cfg, block_env, opts).await self.trace_block(parent.into(), transactions, cfg, block_env, opts).await
} }
/// Replays a block and returns the trace of each transaction. /// Replays a block and returns the trace of each transaction.
@ -182,7 +186,7 @@ where
let ((cfg, block_env, _), block) = futures::try_join!( let ((cfg, block_env, _), block) = futures::try_join!(
self.inner.eth_api.evm_env_at(block_hash.into()), self.inner.eth_api.evm_env_at(block_hash.into()),
self.inner.eth_api.block_by_id_with_senders(block_id), self.inner.eth_api.block_with_senders(block_id),
)?; )?;
let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?; let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
@ -190,7 +194,7 @@ where
// its parent block's state // its parent block's state
let state_at = block.parent_hash; let state_at = block.parent_hash;
self.trace_block_with( self.trace_block(
state_at.into(), state_at.into(),
block.into_transactions_ecrecovered().collect(), block.into_transactions_ecrecovered().collect(),
cfg, cfg,
@ -324,6 +328,10 @@ where
self.inner self.inner
.eth_api .eth_api
.spawn_with_call_at(call, at, overrides, move |db, env| { .spawn_with_call_at(call, at, overrides, move |db, env| {
// wrapper is hack to get around 'higher-ranked lifetime error',
// see <https://github.com/rust-lang/rust/issues/100013>
let db = db.0;
let (res, _) = let (res, _) =
this.eth_api().inspect(&mut *db, env, &mut inspector)?; this.eth_api().inspect(&mut *db, env, &mut inspector)?;
let frame = inspector let frame = inspector
@ -346,6 +354,10 @@ where
.inner .inner
.eth_api .eth_api
.spawn_with_call_at(call, at, overrides, move |db, env| { .spawn_with_call_at(call, at, overrides, move |db, env| {
// wrapper is hack to get around 'higher-ranked lifetime error', see
// <https://github.com/rust-lang/rust/issues/100013>
let db = db.0;
let (res, _) = let (res, _) =
this.eth_api().inspect(&mut *db, env, &mut inspector)?; this.eth_api().inspect(&mut *db, env, &mut inspector)?;
let frame = inspector.try_into_mux_frame(&res, db)?; let frame = inspector.try_into_mux_frame(&res, db)?;
@ -364,6 +376,10 @@ where
.inner .inner
.eth_api .eth_api
.spawn_with_call_at(call, at, overrides, move |db, env| { .spawn_with_call_at(call, at, overrides, move |db, env| {
// wrapper is hack to get around 'higher-ranked lifetime error', see
// <https://github.com/rust-lang/rust/issues/100013>
let db = db.0;
let mut inspector = JsInspector::new(code, config)?; let mut inspector = JsInspector::new(code, config)?;
let (res, _) = let (res, _) =
this.eth_api().inspect(&mut *db, env.clone(), &mut inspector)?; this.eth_api().inspect(&mut *db, env.clone(), &mut inspector)?;
@ -415,7 +431,7 @@ where
let target_block = block_number.unwrap_or_default(); let target_block = block_number.unwrap_or_default();
let ((cfg, mut block_env, _), block) = futures::try_join!( let ((cfg, mut block_env, _), block) = futures::try_join!(
self.inner.eth_api.evm_env_at(target_block), self.inner.eth_api.evm_env_at(target_block),
self.inner.eth_api.block_by_id_with_senders(target_block), self.inner.eth_api.block_with_senders(target_block),
)?; )?;
let opts = opts.unwrap_or_default(); let opts = opts.unwrap_or_default();
@ -518,7 +534,7 @@ where
&self, &self,
opts: GethDebugTracingOptions, opts: GethDebugTracingOptions,
env: EnvWithHandlerCfg, env: EnvWithHandlerCfg,
db: &mut CacheDB<StateProviderDatabase<StateProviderBox>>, db: &mut StateCacheDb<'_>,
transaction_context: Option<TransactionContext>, transaction_context: Option<TransactionContext>,
) -> EthResult<(GethTrace, revm_primitives::EvmState)> { ) -> EthResult<(GethTrace, revm_primitives::EvmState)> {
let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
@ -614,8 +630,13 @@ where
#[async_trait] #[async_trait]
impl<Provider, Eth> DebugApiServer for DebugApi<Provider, Eth> impl<Provider, Eth> DebugApiServer for DebugApi<Provider, Eth>
where where
Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static, Provider: BlockReaderIdExt
Eth: EthApiSpec + 'static, + HeaderProvider
+ ChainSpecProvider
+ StateProviderFactory
+ EvmEnvProvider
+ 'static,
Eth: EthApiSpec + EthTransactions + TraceExt + 'static,
{ {
/// Handler for `debug_getRawHeader` /// Handler for `debug_getRawHeader`
async fn raw_header(&self, block_id: BlockId) -> RpcResult<Bytes> { async fn raw_header(&self, block_id: BlockId) -> RpcResult<Bytes> {

View File

@ -1,217 +0,0 @@
//! Contains RPC handler implementations specific to blocks.
use crate::{
eth::{
api::transactions::build_transaction_receipt_with_block_receipts,
error::{EthApiError, EthResult},
},
EthApi,
};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
use reth_primitives::{BlockId, TransactionMeta};
use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory};
use reth_rpc_types::{AnyTransactionReceipt, Header, Index, RichBlock};
use reth_rpc_types_compat::block::{from_block, uncle_block_from_header};
use reth_transaction_pool::TransactionPool;
use std::sync::Arc;
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Pool: TransactionPool + Clone + 'static,
Network: NetworkInfo + Send + Sync + 'static,
EvmConfig: ConfigureEvm + 'static,
{
/// Returns the uncle headers of the given block
///
/// Returns an empty vec if there are none.
pub(crate) fn ommers(
&self,
block_id: impl Into<BlockId>,
) -> EthResult<Option<Vec<reth_primitives::Header>>> {
let block_id = block_id.into();
Ok(self.provider().ommers_by_id(block_id)?)
}
pub(crate) async fn ommer_by_block_and_index(
&self,
block_id: impl Into<BlockId>,
index: Index,
) -> EthResult<Option<RichBlock>> {
let block_id = block_id.into();
let uncles = if block_id.is_pending() {
// Pending block can be fetched directly without need for caching
self.provider().pending_block()?.map(|block| block.ommers)
} else {
self.provider().ommers_by_id(block_id)?
}
.unwrap_or_default();
let index = usize::from(index);
let uncle =
uncles.into_iter().nth(index).map(|header| uncle_block_from_header(header).into());
Ok(uncle)
}
/// Returns all transaction receipts in the block.
///
/// Returns `None` if the block wasn't found.
pub(crate) async fn block_receipts(
&self,
block_id: BlockId,
) -> EthResult<Option<Vec<AnyTransactionReceipt>>> {
// Fetch block and receipts based on block_id
let block_and_receipts = if block_id.is_pending() {
self.provider()
.pending_block_and_receipts()?
.map(|(sb, receipts)| (sb, Arc::new(receipts)))
} else if let Some(block_hash) = self.provider().block_hash_for_id(block_id)? {
self.cache().get_block_and_receipts(block_hash).await?
} else {
None
};
// If no block and receipts found, return None
let Some((block, receipts)) = block_and_receipts else {
return Ok(None);
};
// Extract block details
let block_number = block.number;
let base_fee = block.base_fee_per_gas;
let block_hash = block.hash();
let excess_blob_gas = block.excess_blob_gas;
let timestamp = block.timestamp;
let block = block.unseal();
#[cfg(feature = "optimism")]
let (block_timestamp, l1_block_info) = {
let body = reth_evm_optimism::extract_l1_info(&block);
(block.timestamp, body.ok())
};
// Build transaction receipts
block
.body
.into_iter()
.zip(receipts.iter())
.enumerate()
.map(|(idx, (tx, receipt))| {
let meta = TransactionMeta {
tx_hash: tx.hash,
index: idx as u64,
block_hash,
block_number,
base_fee,
excess_blob_gas,
timestamp,
};
#[cfg(feature = "optimism")]
let op_tx_meta =
self.build_op_tx_meta(&tx, l1_block_info.clone(), block_timestamp)?;
build_transaction_receipt_with_block_receipts(
tx,
meta,
receipt.clone(),
&receipts,
#[cfg(feature = "optimism")]
op_tx_meta,
)
})
.collect::<EthResult<Vec<_>>>()
.map(Some)
}
/// Returns the number transactions in the given block.
///
/// Returns `None` if the block does not exist
pub(crate) async fn block_transaction_count(
&self,
block_id: impl Into<BlockId>,
) -> EthResult<Option<usize>> {
let block_id = block_id.into();
if block_id.is_pending() {
// Pending block can be fetched directly without need for caching
return Ok(self.provider().pending_block()?.map(|block| block.body.len()))
}
let block_hash = match self.provider().block_hash_for_id(block_id)? {
Some(block_hash) => block_hash,
None => return Ok(None),
};
Ok(self.cache().get_block_transactions(block_hash).await?.map(|txs| txs.len()))
}
/// Returns the block object for the given block id.
pub(crate) async fn block(
&self,
block_id: impl Into<BlockId>,
) -> EthResult<Option<reth_primitives::SealedBlock>> {
self.block_with_senders(block_id)
.await
.map(|maybe_block| maybe_block.map(|block| block.block))
}
/// Returns the block object for the given block id.
pub(crate) async fn block_with_senders(
&self,
block_id: impl Into<BlockId>,
) -> EthResult<Option<reth_primitives::SealedBlockWithSenders>> {
let block_id = block_id.into();
if block_id.is_pending() {
// Pending block can be fetched directly without need for caching
let maybe_pending = self.provider().pending_block_with_senders()?;
return if maybe_pending.is_some() {
Ok(maybe_pending)
} else {
self.local_pending_block().await
}
}
let block_hash = match self.provider().block_hash_for_id(block_id)? {
Some(block_hash) => block_hash,
None => return Ok(None),
};
Ok(self.cache().get_sealed_block_with_senders(block_hash).await?)
}
/// Returns the populated rpc block object for the given block id.
///
/// If `full` is true, the block object will contain all transaction objects, otherwise it will
/// only contain the transaction hashes.
pub(crate) async fn rpc_block(
&self,
block_id: impl Into<BlockId>,
full: bool,
) -> EthResult<Option<RichBlock>> {
let block = match self.block_with_senders(block_id).await? {
Some(block) => block,
None => return Ok(None),
};
let block_hash = block.hash();
let total_difficulty = self
.provider()
.header_td_by_number(block.number)?
.ok_or(EthApiError::UnknownBlockNumber)?;
let block = from_block(block.unseal(), total_difficulty, full.into(), Some(block_hash))?;
Ok(Some(block.into()))
}
/// Returns the block header for the given block id.
pub(crate) async fn rpc_block_header(
&self,
block_id: impl Into<BlockId>,
) -> EthResult<Option<Header>> {
let header = self.rpc_block(block_id, false).await?.map(|block| block.inner.header);
Ok(header)
}
}

View File

@ -1,530 +0,0 @@
//! Contains RPC handler implementations specific to endpoints that call/execute within evm.
use crate::{
eth::{
error::{ensure_success, EthApiError, EthResult, RevertError, RpcInvalidTransactionError},
revm_utils::{
apply_state_overrides, build_call_evm_env, caller_gas_allowance,
cap_tx_gas_limit_with_caller_allowance, get_precompiles, prepare_call_env,
},
EthTransactions,
},
EthApi,
};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, TxKind, U256};
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProvider, StateProviderFactory,
};
use reth_revm::database::StateProviderDatabase;
use reth_rpc_types::{
state::{EvmOverrides, StateOverride},
AccessListWithGasUsed, Bundle, EthCallResponse, StateContext, TransactionRequest,
};
use reth_transaction_pool::TransactionPool;
use revm::{
db::{CacheDB, DatabaseRef},
primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, HaltReason},
DatabaseCommit,
};
use revm_inspectors::access_list::AccessListInspector;
use tracing::trace;
// Gas per transaction not creating a contract.
const MIN_TRANSACTION_GAS: u64 = 21_000u64;
/// Allowed error ratio for gas estimation
/// Taken from Geth's implementation in order to pass the hive tests
/// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/internal/ethapi/api.go#L56>
const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015;
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Pool: TransactionPool + Clone + 'static,
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: NetworkInfo + Send + Sync + 'static,
EvmConfig: ConfigureEvm + 'static,
{
/// Estimate gas needed for execution of the `request` at the [`BlockId`].
pub async fn estimate_gas_at(
&self,
request: TransactionRequest,
at: BlockId,
state_override: Option<StateOverride>,
) -> EthResult<U256> {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
self.on_blocking_task(|this| async move {
let state = this.state_at(at)?;
this.estimate_gas_with(cfg, block_env, request, state, state_override)
})
.await
}
/// Executes the call request (`eth_call`) and returns the output
pub async fn call(
&self,
request: TransactionRequest,
block_number: Option<BlockId>,
overrides: EvmOverrides,
) -> EthResult<Bytes> {
let (res, _env) =
self.transact_call_at(request, block_number.unwrap_or_default(), overrides).await?;
ensure_success(res.result)
}
/// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the
/// optionality of state overrides
pub async fn call_many(
&self,
bundle: Bundle,
state_context: Option<StateContext>,
mut state_override: Option<StateOverride>,
) -> EthResult<Vec<EthCallResponse>> {
let Bundle { transactions, block_override } = bundle;
if transactions.is_empty() {
return Err(EthApiError::InvalidParams(String::from("transactions are empty.")))
}
let StateContext { transaction_index, block_number } = state_context.unwrap_or_default();
let transaction_index = transaction_index.unwrap_or_default();
let target_block = block_number.unwrap_or_default();
let is_block_target_pending = target_block.is_pending();
let ((cfg, block_env, _), block) = futures::try_join!(
self.evm_env_at(target_block),
self.block_with_senders(target_block)
)?;
let Some(block) = block else { return Err(EthApiError::UnknownBlockNumber) };
let gas_limit = self.inner.gas_cap;
// we're essentially replaying the transactions in the block here, hence we need the state
// that points to the beginning of the block, which is the state at the parent block
let mut at = block.parent_hash;
let mut replay_block_txs = true;
let num_txs = transaction_index.index().unwrap_or(block.body.len());
// but if all transactions are to be replayed, we can use the state at the block itself,
// however only if we're not targeting the pending block, because for pending we can't rely
// on the block's state being available
if !is_block_target_pending && num_txs == block.body.len() {
at = block.hash();
replay_block_txs = false;
}
let this = self.clone();
self.spawn_with_state_at_block(at.into(), move |state| {
let mut results = Vec::with_capacity(transactions.len());
let mut db = CacheDB::new(StateProviderDatabase::new(state));
if replay_block_txs {
// only need to replay the transactions in the block if not all transactions are
// to be replayed
let transactions = block.into_transactions_ecrecovered().take(num_txs);
for tx in transactions {
let tx = tx_env_with_recovered(&tx);
let env =
EnvWithHandlerCfg::new_with_cfg_env(cfg.clone(), block_env.clone(), tx);
let (res, _) = this.transact(&mut db, env)?;
db.commit(res.state);
}
}
let block_overrides = block_override.map(Box::new);
let mut transactions = transactions.into_iter().peekable();
while let Some(tx) = transactions.next() {
// apply state overrides only once, before the first transaction
let state_overrides = state_override.take();
let overrides = EvmOverrides::new(state_overrides, block_overrides.clone());
let env = prepare_call_env(
cfg.clone(),
block_env.clone(),
tx,
gas_limit,
&mut db,
overrides,
)?;
let (res, _) = this.transact(&mut db, env)?;
match ensure_success(res.result) {
Ok(output) => {
results.push(EthCallResponse { value: Some(output), error: None });
}
Err(err) => {
results.push(EthCallResponse { value: None, error: Some(err.to_string()) });
}
}
if transactions.peek().is_some() {
// need to apply the state changes of this call before executing the next call
db.commit(res.state);
}
}
Ok(results)
})
.await
}
/// Estimates the gas usage of the `request` with the state.
///
/// This will execute the [`TransactionRequest`] and find the best gas limit via binary search
pub fn estimate_gas_with<S>(
&self,
mut cfg: CfgEnvWithHandlerCfg,
block: BlockEnv,
request: TransactionRequest,
state: S,
state_override: Option<StateOverride>,
) -> EthResult<U256>
where
S: StateProvider,
{
// Disabled because eth_estimateGas is sometimes used with eoa senders
// See <https://github.com/paradigmxyz/reth/issues/1959>
cfg.disable_eip3607 = true;
// The basefee should be ignored for eth_createAccessList
// See:
// <https://github.com/ethereum/go-ethereum/blob/ee8e83fa5f6cb261dad2ed0a7bbcde4930c41e6c/internal/ethapi/api.go#L985>
cfg.disable_base_fee = true;
// Keep a copy of gas related request values
let tx_request_gas_limit = request.gas;
let tx_request_gas_price = request.gas_price;
let block_env_gas_limit = block.gas_limit;
// Determine the highest possible gas limit, considering both the request's specified limit
// and the block's limit.
let mut highest_gas_limit = tx_request_gas_limit
.map(|tx_gas_limit| U256::from(tx_gas_limit).max(block_env_gas_limit))
.unwrap_or(block_env_gas_limit);
// Configure the evm env
let mut env = build_call_evm_env(cfg, block, request)?;
let mut db = CacheDB::new(StateProviderDatabase::new(state));
// Apply any state overrides if specified.
if let Some(state_override) = state_override {
apply_state_overrides(state_override, &mut db)?;
}
// Optimize for simple transfer transactions, potentially reducing the gas estimate.
if env.tx.data.is_empty() {
if let TxKind::Call(to) = env.tx.transact_to {
if let Ok(code) = db.db.account_code(to) {
let no_code_callee = code.map(|code| code.is_empty()).unwrap_or(true);
if no_code_callee {
// If the tx is a simple transfer (call to an account with no code) we can
// shortcircuit. But simply returning
// `MIN_TRANSACTION_GAS` is dangerous because there might be additional
// field combos that bump the price up, so we try executing the function
// with the minimum gas limit to make sure.
let mut env = env.clone();
env.tx.gas_limit = MIN_TRANSACTION_GAS;
if let Ok((res, _)) = self.transact(&mut db, env) {
if res.result.is_success() {
return Ok(U256::from(MIN_TRANSACTION_GAS))
}
}
}
}
}
}
// Check funds of the sender (only useful to check if transaction gas price is more than 0).
//
// The caller allowance is check by doing `(account.balance - tx.value) / tx.gas_price`
if env.tx.gas_price > U256::ZERO {
// cap the highest gas limit by max gas caller can afford with given gas price
highest_gas_limit = highest_gas_limit.min(caller_gas_allowance(&mut db, &env.tx)?);
}
// We can now normalize the highest gas limit to a u64
let mut highest_gas_limit: u64 = highest_gas_limit.try_into().unwrap_or(u64::MAX);
// If the provided gas limit is less than computed cap, use that
env.tx.gas_limit = env.tx.gas_limit.min(highest_gas_limit);
trace!(target: "rpc::eth::estimate", ?env, "Starting gas estimation");
// Execute the transaction with the highest possible gas limit.
let (mut res, mut env) = match self.transact(&mut db, env.clone()) {
// Handle the exceptional case where the transaction initialization uses too much gas.
// If the gas price or gas limit was specified in the request, retry the transaction
// with the block's gas limit to determine if the failure was due to
// insufficient gas.
Err(EthApiError::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh))
if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() =>
{
return Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db))
}
// Propagate other results (successful or other errors).
ethres => ethres?,
};
let gas_refund = match res.result {
ExecutionResult::Success { gas_refunded, .. } => gas_refunded,
ExecutionResult::Halt { reason, gas_used } => {
// here we don't check for invalid opcode because already executed with highest gas
// limit
return Err(RpcInvalidTransactionError::halt(reason, gas_used).into())
}
ExecutionResult::Revert { output, .. } => {
// if price or limit was included in the request then we can execute the request
// again with the block's gas limit to check if revert is gas related or not
return if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() {
Err(self.map_out_of_gas_err(block_env_gas_limit, env, &mut db))
} else {
// the transaction did revert
Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into())
}
}
};
// At this point we know the call succeeded but want to find the _best_ (lowest) gas the
// transaction succeeds with. We find this by doing a binary search over the possible range.
//
// NOTE: this is the gas the transaction used, which is less than the
// transaction requires to succeed.
let mut gas_used = res.result.gas_used();
// the lowest value is capped by the gas used by the unconstrained transaction
let mut lowest_gas_limit = gas_used.saturating_sub(1);
// As stated in Geth, there is a good chance that the transaction will pass if we set the
// gas limit to the execution gas used plus the gas refund, so we check this first
// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L135
//
// Calculate the optimistic gas limit by adding gas used and gas refund,
// then applying a 64/63 multiplier to account for gas forwarding rules.
let optimistic_gas_limit = (gas_used + gas_refund) * 64 / 63;
if optimistic_gas_limit < highest_gas_limit {
// Set the transaction's gas limit to the calculated optimistic gas limit.
env.tx.gas_limit = optimistic_gas_limit;
// Re-execute the transaction with the new gas limit and update the result and
// environment.
(res, env) = self.transact(&mut db, env)?;
// Update the gas used based on the new result.
gas_used = res.result.gas_used();
// Update the gas limit estimates (highest and lowest) based on the execution result.
update_estimated_gas_range(
res.result,
optimistic_gas_limit,
&mut highest_gas_limit,
&mut lowest_gas_limit,
)?;
};
// Pick a point that's close to the estimated gas
let mut mid_gas_limit = std::cmp::min(
gas_used * 3,
((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64,
);
trace!(target: "rpc::eth::estimate", ?env, ?highest_gas_limit, ?lowest_gas_limit, ?mid_gas_limit, "Starting binary search for gas");
// Binary search narrows the range to find the minimum gas limit needed for the transaction
// to succeed.
while (highest_gas_limit - lowest_gas_limit) > 1 {
// An estimation error is allowed once the current gas limit range used in the binary
// search is small enough (less than 1.5% of the highest gas limit)
// <https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L152
if (highest_gas_limit - lowest_gas_limit) as f64 / (highest_gas_limit as f64) <
ESTIMATE_GAS_ERROR_RATIO
{
break
};
env.tx.gas_limit = mid_gas_limit;
// Execute transaction and handle potential gas errors, adjusting limits accordingly.
match self.transact(&mut db, env.clone()) {
// Check if the error is due to gas being too high.
Err(EthApiError::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh)) => {
// Increase the lowest gas limit if gas is too high
lowest_gas_limit = mid_gas_limit;
}
// Handle other cases, including successful transactions.
ethres => {
// Unpack the result and environment if the transaction was successful.
(res, env) = ethres?;
// Update the estimated gas range based on the transaction result.
update_estimated_gas_range(
res.result,
mid_gas_limit,
&mut highest_gas_limit,
&mut lowest_gas_limit,
)?;
}
}
// New midpoint
mid_gas_limit = ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64;
}
Ok(U256::from(highest_gas_limit))
}
/// Creates the `AccessList` for the `request` at the [`BlockId`] or latest.
pub(crate) async fn create_access_list_at(
&self,
request: TransactionRequest,
block_number: Option<BlockId>,
) -> EthResult<AccessListWithGasUsed> {
self.on_blocking_task(|this| async move {
this.create_access_list_with(request, block_number).await
})
.await
}
async fn create_access_list_with(
&self,
mut request: TransactionRequest,
at: Option<BlockId>,
) -> EthResult<AccessListWithGasUsed> {
let block_id = at.unwrap_or_default();
let (cfg, block, at) = self.evm_env_at(block_id).await?;
let state = self.state_at(at)?;
let mut env = build_call_evm_env(cfg, block, request.clone())?;
// we want to disable this in eth_createAccessList, since this is common practice used by
// other node impls and providers <https://github.com/foundry-rs/foundry/issues/4388>
env.cfg.disable_block_gas_limit = true;
// The basefee should be ignored for eth_createAccessList
// See:
// <https://github.com/ethereum/go-ethereum/blob/8990c92aea01ca07801597b00c0d83d4e2d9b811/internal/ethapi/api.go#L1476-L1476>
env.cfg.disable_base_fee = true;
let mut db = CacheDB::new(StateProviderDatabase::new(state));
if request.gas.is_none() && env.tx.gas_price > U256::ZERO {
// no gas limit was provided in the request, so we need to cap the request's gas limit
cap_tx_gas_limit_with_caller_allowance(&mut db, &mut env.tx)?;
}
let from = request.from.unwrap_or_default();
let to = if let Some(TxKind::Call(to)) = request.to {
to
} else {
let nonce = db.basic_ref(from)?.unwrap_or_default().nonce;
from.create(nonce)
};
// can consume the list since we're not using the request anymore
let initial = request.access_list.take().unwrap_or_default();
let precompiles = get_precompiles(env.handler_cfg.spec_id);
let mut inspector = AccessListInspector::new(initial, from, to, precompiles);
let (result, env) = self.inspect(&mut db, env, &mut inspector)?;
match result.result {
ExecutionResult::Halt { reason, .. } => Err(match reason {
HaltReason::NonceOverflow => RpcInvalidTransactionError::NonceMaxValue,
halt => RpcInvalidTransactionError::EvmHalt(halt),
}),
ExecutionResult::Revert { output, .. } => {
Err(RpcInvalidTransactionError::Revert(RevertError::new(output)))
}
ExecutionResult::Success { .. } => Ok(()),
}?;
let access_list = inspector.into_access_list();
let cfg_with_spec_id =
CfgEnvWithHandlerCfg { cfg_env: env.cfg.clone(), handler_cfg: env.handler_cfg };
// calculate the gas used using the access list
request.access_list = Some(access_list.clone());
let gas_used =
self.estimate_gas_with(cfg_with_spec_id, env.block.clone(), request, &*db.db, None)?;
Ok(AccessListWithGasUsed { access_list, gas_used })
}
/// Executes the requests again after an out of gas error to check if the error is gas related
/// or not
#[inline]
fn map_out_of_gas_err<S>(
&self,
env_gas_limit: U256,
mut env: EnvWithHandlerCfg,
db: &mut CacheDB<StateProviderDatabase<S>>,
) -> EthApiError
where
S: StateProvider,
{
let req_gas_limit = env.tx.gas_limit;
env.tx.gas_limit = env_gas_limit.try_into().unwrap_or(u64::MAX);
let (res, _) = match self.transact(db, env) {
Ok(res) => res,
Err(err) => return err,
};
match res.result {
ExecutionResult::Success { .. } => {
// transaction succeeded by manually increasing the gas limit to
// highest, which means the caller lacks funds to pay for the tx
RpcInvalidTransactionError::BasicOutOfGas(req_gas_limit).into()
}
ExecutionResult::Revert { output, .. } => {
// reverted again after bumping the limit
RpcInvalidTransactionError::Revert(RevertError::new(output)).into()
}
ExecutionResult::Halt { reason, .. } => {
RpcInvalidTransactionError::EvmHalt(reason).into()
}
}
}
}
/// Updates the highest and lowest gas limits for binary search based on the execution result.
///
/// This function refines the gas limit estimates used in a binary search to find the optimal gas
/// limit for a transaction. It adjusts the highest or lowest gas limits depending on whether the
/// execution succeeded, reverted, or halted due to specific reasons.
#[inline]
fn update_estimated_gas_range(
result: ExecutionResult,
tx_gas_limit: u64,
highest_gas_limit: &mut u64,
lowest_gas_limit: &mut u64,
) -> EthResult<()> {
match result {
ExecutionResult::Success { .. } => {
// Cap the highest gas limit with the succeeding gas limit.
*highest_gas_limit = tx_gas_limit;
}
ExecutionResult::Revert { .. } => {
// Increase the lowest gas limit.
*lowest_gas_limit = tx_gas_limit;
}
ExecutionResult::Halt { reason, .. } => {
match reason {
HaltReason::OutOfGas(_) | HaltReason::InvalidEFOpcode => {
// Both `OutOfGas` and `InvalidFEOpcode` can occur dynamically if the gas left
// is too low. Treat this as an out of gas condition,
// knowing that the call succeeds with a higher gas limit.
//
// Common usage of invalid opcode in OpenZeppelin:
// <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/metatx/ERC2771Forwarder.sol#L360-L367>
// Increase the lowest gas limit.
*lowest_gas_limit = tx_gas_limit;
}
err => {
// These cases should be unreachable because we know the transaction succeeds,
// but if they occur, treat them as an error.
return Err(RpcInvalidTransactionError::EvmHalt(err).into())
}
}
}
};
Ok(())
}

View File

@ -1,228 +0,0 @@
//! Contains RPC handler implementations for fee history.
use crate::{
eth::{
api::fee_history::{calculate_reward_percentiles_for_block, FeeHistoryEntry},
error::{EthApiError, EthResult},
},
EthApi,
};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
use reth_primitives::{BlockNumberOrTag, U256};
use reth_provider::{BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory};
use reth_rpc_types::FeeHistory;
use reth_transaction_pool::TransactionPool;
use tracing::debug;
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Pool: TransactionPool + Clone + 'static,
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: NetworkInfo + Send + Sync + 'static,
EvmConfig: ConfigureEvm + 'static,
{
/// Returns a suggestion for a gas price for legacy transactions.
///
/// See also: <https://github.com/ethereum/pm/issues/328#issuecomment-853234014>
pub(crate) async fn gas_price(&self) -> EthResult<U256> {
let header = self.block(BlockNumberOrTag::Latest);
let suggested_tip = self.suggested_priority_fee();
let (header, suggested_tip) = futures::try_join!(header, suggested_tip)?;
let base_fee = header.and_then(|h| h.base_fee_per_gas).unwrap_or_default();
Ok(suggested_tip + U256::from(base_fee))
}
/// Returns a suggestion for a base fee for blob transactions.
pub(crate) async fn blob_base_fee(&self) -> EthResult<U256> {
self.block(BlockNumberOrTag::Latest)
.await?
.and_then(|h: reth_primitives::SealedBlock| h.next_block_blob_fee())
.ok_or(EthApiError::ExcessBlobGasNotSet)
.map(U256::from)
}
/// Returns a suggestion for the priority fee (the tip)
pub(crate) async fn suggested_priority_fee(&self) -> EthResult<U256> {
self.gas_oracle().suggest_tip_cap().await
}
/// Reports the fee history, for the given amount of blocks, up until the given newest block.
///
/// If `reward_percentiles` are provided the [`FeeHistory`] will include the _approximated_
/// rewards for the requested range.
pub(crate) async fn fee_history(
&self,
mut block_count: u64,
newest_block: BlockNumberOrTag,
reward_percentiles: Option<Vec<f64>>,
) -> EthResult<FeeHistory> {
if block_count == 0 {
return Ok(FeeHistory::default())
}
// See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225
let max_fee_history = if reward_percentiles.is_none() {
self.gas_oracle().config().max_header_history
} else {
self.gas_oracle().config().max_block_history
};
if block_count > max_fee_history {
debug!(
requested = block_count,
truncated = max_fee_history,
"Sanitizing fee history block count"
);
block_count = max_fee_history
}
let Some(end_block) = self.provider().block_number_for_id(newest_block.into())? else {
return Err(EthApiError::UnknownBlockNumber)
};
// need to add 1 to the end block to get the correct (inclusive) range
let end_block_plus = end_block + 1;
// Ensure that we would not be querying outside of genesis
if end_block_plus < block_count {
block_count = end_block_plus;
}
// If reward percentiles were specified, we
// need to validate that they are monotonically
// increasing and 0 <= p <= 100
// Note: The types used ensure that the percentiles are never < 0
if let Some(percentiles) = &reward_percentiles {
if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) {
return Err(EthApiError::InvalidRewardPercentiles)
}
}
// Fetch the headers and ensure we got all of them
//
// Treat a request for 1 block as a request for `newest_block..=newest_block`,
// otherwise `newest_block - 2
// NOTE: We ensured that block count is capped
let start_block = end_block_plus - block_count;
// Collect base fees, gas usage ratios and (optionally) reward percentile data
let mut base_fee_per_gas: Vec<u128> = Vec::new();
let mut gas_used_ratio: Vec<f64> = Vec::new();
let mut base_fee_per_blob_gas: Vec<u128> = Vec::new();
let mut blob_gas_used_ratio: Vec<f64> = Vec::new();
let mut rewards: Vec<Vec<u128>> = Vec::new();
// Check if the requested range is within the cache bounds
let fee_entries = self.fee_history_cache().get_history(start_block, end_block).await;
if let Some(fee_entries) = fee_entries {
if fee_entries.len() != block_count as usize {
return Err(EthApiError::InvalidBlockRange)
}
for entry in &fee_entries {
base_fee_per_gas.push(entry.base_fee_per_gas as u128);
gas_used_ratio.push(entry.gas_used_ratio);
base_fee_per_blob_gas.push(entry.base_fee_per_blob_gas.unwrap_or_default());
blob_gas_used_ratio.push(entry.blob_gas_used_ratio);
if let Some(percentiles) = &reward_percentiles {
let mut block_rewards = Vec::with_capacity(percentiles.len());
for &percentile in percentiles {
block_rewards.push(self.approximate_percentile(entry, percentile));
}
rewards.push(block_rewards);
}
}
let last_entry = fee_entries.last().expect("is not empty");
// Also need to include the `base_fee_per_gas` and `base_fee_per_blob_gas` for the next
// block
base_fee_per_gas
.push(last_entry.next_block_base_fee(&self.provider().chain_spec()) as u128);
base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default());
} else {
// read the requested header range
let headers = self.provider().sealed_headers_range(start_block..=end_block)?;
if headers.len() != block_count as usize {
return Err(EthApiError::InvalidBlockRange)
}
for header in &headers {
let ratio = if header.gas_limit > 0 {header.gas_used as f64 / header.gas_limit as f64} else {1.0};
base_fee_per_gas.push(header.base_fee_per_gas.unwrap_or_default() as u128);
gas_used_ratio.push(ratio);
base_fee_per_blob_gas.push(header.blob_fee().unwrap_or_default());
blob_gas_used_ratio.push(
header.blob_gas_used.unwrap_or_default() as f64 /
reth_primitives::constants::eip4844::MAX_DATA_GAS_PER_BLOCK as f64,
);
// Percentiles were specified, so we need to collect reward percentile ino
if let Some(percentiles) = &reward_percentiles {
let (transactions, receipts) = self
.cache()
.get_transactions_and_receipts(header.hash())
.await?
.ok_or(EthApiError::InvalidBlockRange)?;
rewards.push(
calculate_reward_percentiles_for_block(
percentiles,
header.gas_used,
header.base_fee_per_gas.unwrap_or_default(),
&transactions,
&receipts,
)
.unwrap_or_default(),
);
}
}
// The spec states that `base_fee_per_gas` "[..] includes the next block after the
// newest of the returned range, because this value can be derived from the
// newest block"
//
// The unwrap is safe since we checked earlier that we got at least 1 header.
let last_header = headers.last().expect("is present");
base_fee_per_gas.push(
self.provider().chain_spec().base_fee_params_at_timestamp(last_header.timestamp).next_block_base_fee(
last_header.gas_used as u128,
last_header.gas_limit as u128,
last_header.base_fee_per_gas.unwrap_or_default() as u128,
));
// Same goes for the `base_fee_per_blob_gas`:
// > "[..] includes the next block after the newest of the returned range, because this value can be derived from the newest block.
base_fee_per_blob_gas
.push(last_header.next_block_blob_fee().unwrap_or_default());
};
Ok(FeeHistory {
base_fee_per_gas,
gas_used_ratio,
base_fee_per_blob_gas,
blob_gas_used_ratio,
oldest_block: start_block,
reward: reward_percentiles.map(|_| rewards),
})
}
/// Approximates reward at a given percentile for a specific block
/// Based on the configured resolution
fn approximate_percentile(&self, entry: &FeeHistoryEntry, requested_percentile: f64) -> u128 {
let resolution = self.fee_history_cache().resolution();
let rounded_percentile =
(requested_percentile * resolution as f64).round() / resolution as f64;
let clamped_percentile = rounded_percentile.clamp(0.0, 100.0);
// Calculate the index in the precomputed rewards array
let index = (clamped_percentile / (1.0 / resolution as f64)).round() as usize;
// Fetch the reward from the FeeHistoryEntry
entry.rewards.get(index).cloned().unwrap_or_default()
}
}

View File

@ -1,503 +0,0 @@
//! The entire implementation of the namespace is quite large, hence it is divided across several
//! files.
use crate::eth::{
api::{
fee_history::FeeHistoryCache,
pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin},
},
cache::EthStateCache,
error::{EthApiError, EthResult},
gas_oracle::GasPriceOracle,
signer::EthSigner,
traits::RawTransactionForwarder,
};
use async_trait::async_trait;
use reth_chainspec::ChainInfo;
use reth_errors::{RethError, RethResult};
use reth_evm::ConfigureEvm;
use reth_network_api::NetworkInfo;
use reth_primitives::{
revm_primitives::{BlockEnv, CfgEnvWithHandlerCfg},
Address, BlockId, BlockNumberOrTag, SealedBlockWithSenders, SealedHeader, B256, U256, U64,
};
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory,
};
use reth_rpc_types::{SyncInfo, SyncStatus};
use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor};
use reth_transaction_pool::TransactionPool;
use revm_primitives::{CfgEnv, SpecId};
use std::{
fmt::Debug,
future::Future,
sync::Arc,
time::{Duration, Instant},
};
use tokio::sync::{oneshot, Mutex};
mod block;
mod call;
pub(crate) mod fee_history;
mod fees;
#[cfg(feature = "optimism")]
mod optimism;
mod pending_block;
mod server;
mod sign;
mod state;
mod transactions;
pub use transactions::{EthTransactions, TransactionSource};
/// `Eth` API trait.
///
/// Defines core functionality of the `eth` API implementation.
#[async_trait]
pub trait EthApiSpec: EthTransactions + Send + Sync {
/// Returns the current ethereum protocol version.
async fn protocol_version(&self) -> RethResult<U64>;
/// Returns the chain id
fn chain_id(&self) -> U64;
/// Returns provider chain info
fn chain_info(&self) -> RethResult<ChainInfo>;
/// Returns a list of addresses owned by provider.
fn accounts(&self) -> Vec<Address>;
/// Returns `true` if the network is undergoing sync.
fn is_syncing(&self) -> bool;
/// Returns the [SyncStatus] of the network
fn sync_status(&self) -> RethResult<SyncStatus>;
}
/// `Eth` API implementation.
///
/// This type provides the functionality for handling `eth_` related requests.
/// These are implemented two-fold: Core functionality is implemented as [`EthApiSpec`]
/// trait. 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).
pub struct EthApi<Provider, Pool, Network, EvmConfig> {
/// All nested fields bundled together.
inner: Arc<EthApiInner<Provider, Pool, Network, EvmConfig>>,
}
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig> {
/// Sets a forwarder for `eth_sendRawTransaction`
///
/// Note: this might be removed in the future in favor of a more generic approach.
pub fn set_eth_raw_transaction_forwarder(&self, forwarder: Arc<dyn RawTransactionForwarder>) {
self.inner.raw_transaction_forwarder.write().replace(forwarder);
}
}
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider: BlockReaderIdExt + ChainSpecProvider,
{
/// Creates a new, shareable instance using the default tokio task spawner.
#[allow(clippy::too_many_arguments)]
pub fn new(
provider: Provider,
pool: Pool,
network: Network,
eth_cache: EthStateCache,
gas_oracle: GasPriceOracle<Provider>,
gas_cap: impl Into<GasCap>,
blocking_task_pool: BlockingTaskPool,
fee_history_cache: FeeHistoryCache,
evm_config: EvmConfig,
raw_transaction_forwarder: Option<Arc<dyn RawTransactionForwarder>>,
) -> Self {
Self::with_spawner(
provider,
pool,
network,
eth_cache,
gas_oracle,
gas_cap.into().into(),
Box::<TokioTaskExecutor>::default(),
blocking_task_pool,
fee_history_cache,
evm_config,
raw_transaction_forwarder,
)
}
/// Creates a new, shareable instance.
#[allow(clippy::too_many_arguments)]
pub fn with_spawner(
provider: Provider,
pool: Pool,
network: Network,
eth_cache: EthStateCache,
gas_oracle: GasPriceOracle<Provider>,
gas_cap: u64,
task_spawner: Box<dyn TaskSpawner>,
blocking_task_pool: BlockingTaskPool,
fee_history_cache: FeeHistoryCache,
evm_config: EvmConfig,
raw_transaction_forwarder: Option<Arc<dyn RawTransactionForwarder>>,
) -> Self {
// get the block number of the latest block
let latest_block = provider
.header_by_number_or_tag(BlockNumberOrTag::Latest)
.ok()
.flatten()
.map(|header| header.number)
.unwrap_or_default();
let inner = EthApiInner {
provider,
pool,
network,
signers: parking_lot::RwLock::new(Default::default()),
eth_cache,
gas_oracle,
gas_cap,
starting_block: U256::from(latest_block),
task_spawner,
pending_block: Default::default(),
blocking_task_pool,
fee_history_cache,
evm_config,
raw_transaction_forwarder: parking_lot::RwLock::new(raw_transaction_forwarder),
};
Self { inner: Arc::new(inner) }
}
/// Executes the future on a new blocking task.
///
/// This accepts a closure that creates a new future using a clone of this type and spawns the
/// future onto a new task that is allowed to block.
///
/// Note: This is expected for futures that are dominated by blocking IO operations.
pub(crate) async fn on_blocking_task<C, F, R>(&self, c: C) -> EthResult<R>
where
C: FnOnce(Self) -> F,
F: Future<Output = EthResult<R>> + Send + 'static,
R: Send + 'static,
{
let (tx, rx) = oneshot::channel();
let this = self.clone();
let f = c(this);
self.inner.task_spawner.spawn_blocking(Box::pin(async move {
let res = f.await;
let _ = tx.send(res);
}));
rx.await.map_err(|_| EthApiError::InternalEthError)?
}
/// Returns the state cache frontend
pub(crate) fn cache(&self) -> &EthStateCache {
&self.inner.eth_cache
}
/// Returns the gas oracle frontend
pub(crate) fn gas_oracle(&self) -> &GasPriceOracle<Provider> {
&self.inner.gas_oracle
}
/// Returns the configured gas limit cap for `eth_call` and tracing related calls
pub fn gas_cap(&self) -> u64 {
self.inner.gas_cap
}
/// Returns the inner `Provider`
pub fn provider(&self) -> &Provider {
&self.inner.provider
}
/// Returns the inner `Network`
pub fn network(&self) -> &Network {
&self.inner.network
}
/// Returns the inner `Pool`
pub fn pool(&self) -> &Pool {
&self.inner.pool
}
/// Returns fee history cache
pub fn fee_history_cache(&self) -> &FeeHistoryCache {
&self.inner.fee_history_cache
}
}
// === State access helpers ===
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
{
/// Returns the state at the given [`BlockId`] enum.
///
/// Note: if not [`BlockNumberOrTag::Pending`] then this will only return canonical state. See also <https://github.com/paradigmxyz/reth/issues/4515>
pub fn state_at_block_id(&self, at: BlockId) -> EthResult<StateProviderBox> {
Ok(self.provider().state_by_block_id(at)?)
}
/// Returns the state at the given [`BlockId`] enum or the latest.
///
/// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)`
pub fn state_at_block_id_or_latest(
&self,
block_id: Option<BlockId>,
) -> EthResult<StateProviderBox> {
if let Some(block_id) = block_id {
self.state_at_block_id(block_id)
} else {
Ok(self.latest_state()?)
}
}
/// Returns the state at the given block number
pub fn state_at_hash(&self, block_hash: B256) -> RethResult<StateProviderBox> {
Ok(self.provider().history_by_block_hash(block_hash)?)
}
/// Returns the _latest_ state
pub fn latest_state(&self) -> RethResult<StateProviderBox> {
Ok(self.provider().latest()?)
}
}
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Pool: TransactionPool + Clone + 'static,
Network: NetworkInfo + Send + Sync + 'static,
EvmConfig: ConfigureEvm + Clone + 'static,
{
/// Configures the [`CfgEnvWithHandlerCfg`] and [`BlockEnv`] for the pending block
///
/// If no pending block is available, this will derive it from the `latest` block
pub(crate) fn pending_block_env_and_cfg(&self) -> EthResult<PendingBlockEnv> {
let origin: PendingBlockEnvOrigin = if let Some(pending) =
self.provider().pending_block_with_senders()?
{
PendingBlockEnvOrigin::ActualPending(pending)
} else {
// no pending block from the CL yet, so we use the latest block and modify the env
// values that we can
let latest =
self.provider().latest_header()?.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
let (mut latest_header, block_hash) = latest.split();
// child block
latest_header.number += 1;
// assumed child block is in the next slot: 12s
latest_header.timestamp += 12;
// base fee of the child block
let chain_spec = self.provider().chain_spec();
latest_header.base_fee_per_gas = latest_header.next_block_base_fee(
chain_spec.base_fee_params_at_timestamp(latest_header.timestamp),
);
// update excess blob gas consumed above target
latest_header.excess_blob_gas = latest_header.next_block_excess_blob_gas();
// we're reusing the same block hash because we need this to lookup the block's state
let latest = SealedHeader::new(latest_header, block_hash);
PendingBlockEnvOrigin::DerivedFromLatest(latest)
};
let mut cfg = CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), SpecId::LATEST);
let mut block_env = BlockEnv::default();
// Note: for the PENDING block we assume it is past the known merge block and thus this will
// not fail when looking up the total difficulty value for the blockenv.
self.provider().fill_env_with_header(
&mut cfg,
&mut block_env,
origin.header(),
self.inner.evm_config.clone(),
)?;
Ok(PendingBlockEnv { cfg, block_env, origin })
}
/// Returns the locally built pending block
pub(crate) async fn local_pending_block(&self) -> EthResult<Option<SealedBlockWithSenders>> {
let pending = self.pending_block_env_and_cfg()?;
if pending.origin.is_actual_pending() {
return Ok(pending.origin.into_actual_pending())
}
// no pending block from the CL yet, so we need to build it ourselves via txpool
self.on_blocking_task(|this| async move {
let mut lock = this.inner.pending_block.lock().await;
let now = Instant::now();
// check if the block is still good
if let Some(pending_block) = lock.as_ref() {
// this is guaranteed to be the `latest` header
if pending.block_env.number.to::<u64>() == pending_block.block.number &&
pending.origin.header().hash() == pending_block.block.parent_hash &&
now <= pending_block.expires_at
{
return Ok(Some(pending_block.block.clone()))
}
}
// we rebuild the block
let pending_block = match pending.build_block(this.provider(), this.pool()) {
Ok(block) => block,
Err(err) => {
tracing::debug!(target: "rpc", "Failed to build pending block: {:?}", err);
return Ok(None)
}
};
let now = Instant::now();
*lock = Some(PendingBlock {
block: pending_block.clone(),
expires_at: now + Duration::from_secs(1),
});
Ok(Some(pending_block))
})
.await
}
}
impl<Provider, Pool, Events, EvmConfig> std::fmt::Debug
for EthApi<Provider, Pool, Events, EvmConfig>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EthApi").finish_non_exhaustive()
}
}
impl<Provider, Pool, Events, EvmConfig> Clone for EthApi<Provider, Pool, Events, EvmConfig> {
fn clone(&self) -> Self {
Self { inner: Arc::clone(&self.inner) }
}
}
#[async_trait]
impl<Provider, Pool, Network, EvmConfig> EthApiSpec for EthApi<Provider, Pool, Network, EvmConfig>
where
Pool: TransactionPool + Clone + 'static,
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: NetworkInfo + 'static,
EvmConfig: ConfigureEvm + 'static,
{
/// Returns the current ethereum protocol version.
///
/// Note: This returns an `U64`, since this should return as hex string.
async fn protocol_version(&self) -> RethResult<U64> {
let status = self.network().network_status().await.map_err(RethError::other)?;
Ok(U64::from(status.protocol_version))
}
/// Returns the chain id
fn chain_id(&self) -> U64 {
U64::from(self.network().chain_id())
}
/// Returns the current info for the chain
fn chain_info(&self) -> RethResult<ChainInfo> {
Ok(self.provider().chain_info()?)
}
fn accounts(&self) -> Vec<Address> {
self.inner.signers.read().iter().flat_map(|s| s.accounts()).collect()
}
fn is_syncing(&self) -> bool {
self.network().is_syncing()
}
/// Returns the [SyncStatus] of the network
fn sync_status(&self) -> RethResult<SyncStatus> {
let status = if self.is_syncing() {
let current_block = U256::from(
self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(),
);
SyncStatus::Info(SyncInfo {
starting_block: self.inner.starting_block,
current_block,
highest_block: current_block,
warp_chunks_amount: None,
warp_chunks_processed: None,
})
} else {
SyncStatus::None
};
Ok(status)
}
}
/// The default gas limit for `eth_call` and adjacent calls.
///
/// This is different from the default to regular 30M block gas limit
/// [`ETHEREUM_BLOCK_GAS_LIMIT`](reth_primitives::constants::ETHEREUM_BLOCK_GAS_LIMIT) to allow for
/// more complex calls.
pub const RPC_DEFAULT_GAS_CAP: GasCap = GasCap(50_000_000);
/// The wrapper type for gas limit
#[derive(Debug, Clone, Copy)]
pub struct GasCap(u64);
impl Default for GasCap {
fn default() -> Self {
RPC_DEFAULT_GAS_CAP
}
}
impl From<u64> for GasCap {
fn from(gas_cap: u64) -> Self {
Self(gas_cap)
}
}
impl From<GasCap> for u64 {
fn from(gas_cap: GasCap) -> Self {
gas_cap.0
}
}
/// Container type `EthApi`
struct EthApiInner<Provider, Pool, Network, EvmConfig> {
/// The transaction pool.
pool: Pool,
/// The provider that can interact with the chain.
provider: Provider,
/// An interface to interact with the network
network: Network,
/// All configured Signers
signers: parking_lot::RwLock<Vec<Box<dyn EthSigner>>>,
/// The async cache frontend for eth related data
eth_cache: EthStateCache,
/// The async gas oracle frontend for gas price suggestions
gas_oracle: GasPriceOracle<Provider>,
/// Maximum gas limit for `eth_call` and call tracing RPC methods.
gas_cap: u64,
/// The block number at which the node started
starting_block: U256,
/// The type that can spawn tasks which would otherwise block.
task_spawner: Box<dyn TaskSpawner>,
/// Cached pending block if any
pending_block: Mutex<Option<PendingBlock>>,
/// A pool dedicated to blocking tasks.
blocking_task_pool: BlockingTaskPool,
/// Cache for block fees history
fee_history_cache: FeeHistoryCache,
/// The type that defines how to configure the EVM
evm_config: EvmConfig,
/// Allows forwarding received raw transactions
raw_transaction_forwarder: parking_lot::RwLock<Option<Arc<dyn RawTransactionForwarder>>>,
}

View File

@ -1,31 +0,0 @@
//! Optimism helpers.
use revm::L1BlockInfo;
/// Optimism Transaction Metadata
///
/// Includes the L1 fee and data gas for the tx along with the L1
/// block info. In order to pass the [`OptimismTxMeta`] into the
/// async colored `build_transaction_receipt_with_block_receipts`
/// function, a reference counter for the L1 block info is
/// used so the L1 block info can be shared between receipts.
#[derive(Debug, Default, Clone)]
pub(crate) struct OptimismTxMeta {
/// The L1 block info.
pub(crate) l1_block_info: Option<L1BlockInfo>,
/// The L1 fee for the block.
pub(crate) l1_fee: Option<u128>,
/// The L1 data gas for the block.
pub(crate) l1_data_gas: Option<u128>,
}
impl OptimismTxMeta {
/// Creates a new [`OptimismTxMeta`].
pub(crate) const fn new(
l1_block_info: Option<L1BlockInfo>,
l1_fee: Option<u128>,
l1_data_gas: Option<u128>,
) -> Self {
Self { l1_block_info, l1_fee, l1_data_gas }
}
}

View File

@ -1,41 +0,0 @@
//! Contains RPC handler implementations specific to sign endpoints
use crate::{
eth::{
error::{EthResult, SignError},
signer::{DevSigner, EthSigner},
},
EthApi,
};
use alloy_dyn_abi::TypedData;
use reth_primitives::{Address, Bytes};
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig> {
pub(crate) async fn sign(&self, account: Address, message: &[u8]) -> EthResult<Bytes> {
Ok(self.find_signer(&account)?.sign(account, message).await?.to_hex_bytes())
}
pub(crate) fn sign_typed_data(&self, data: &TypedData, account: Address) -> EthResult<Bytes> {
Ok(self.find_signer(&account)?.sign_typed_data(account, data)?.to_hex_bytes())
}
pub(crate) fn find_signer(
&self,
account: &Address,
) -> Result<Box<(dyn EthSigner + 'static)>, SignError> {
self.inner
.signers
.read()
.iter()
.find(|signer| signer.is_signer_for(account))
.map(|signer| dyn_clone::clone_box(&**signer))
.ok_or(SignError::NoAccount)
}
/// Generates 20 random developer accounts.
/// Used in DEV mode.
pub fn with_dev_accounts(&self) {
let mut signers = self.inner.signers.write();
*signers = DevSigner::random_signers(20);
}
}

View File

@ -1,178 +0,0 @@
//! Contains RPC handler implementations specific to state.
use crate::{
eth::error::{EthApiError, EthResult, RpcInvalidTransactionError},
EthApi,
};
use reth_evm::ConfigureEvm;
use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, U256};
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProvider, StateProviderFactory,
};
use reth_rpc_types::{serde_helpers::JsonStorageKey, EIP1186AccountProofResponse};
use reth_rpc_types_compat::proof::from_primitive_account_proof;
use reth_transaction_pool::{PoolTransaction, TransactionPool};
impl<Provider, Pool, Network, EvmConfig> EthApi<Provider, Pool, Network, EvmConfig>
where
Provider:
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Pool: TransactionPool + Clone + 'static,
Network: Send + Sync + 'static,
EvmConfig: ConfigureEvm + 'static,
{
pub(crate) fn get_code(&self, address: Address, block_id: Option<BlockId>) -> EthResult<Bytes> {
Ok(self
.state_at_block_id_or_latest(block_id)?
.account_code(address)?
.unwrap_or_default()
.original_bytes())
}
pub(crate) fn balance(&self, address: Address, block_id: Option<BlockId>) -> EthResult<U256> {
Ok(self
.state_at_block_id_or_latest(block_id)?
.account_balance(address)?
.unwrap_or_default())
}
/// Returns the number of transactions sent from an address at the given block identifier.
///
/// If this is [`BlockNumberOrTag::Pending`] then this will look up the highest transaction in
/// pool and return the next nonce (highest + 1).
pub(crate) fn get_transaction_count(
&self,
address: Address,
block_id: Option<BlockId>,
) -> EthResult<U256> {
if block_id == Some(BlockId::pending()) {
let address_txs = self.pool().get_transactions_by_sender(address);
if let Some(highest_nonce) =
address_txs.iter().map(|item| item.transaction.nonce()).max()
{
let tx_count = highest_nonce
.checked_add(1)
.ok_or(RpcInvalidTransactionError::NonceMaxValue)?;
return Ok(U256::from(tx_count))
}
}
let state = self.state_at_block_id_or_latest(block_id)?;
Ok(U256::from(state.account_nonce(address)?.unwrap_or_default()))
}
pub(crate) fn storage_at(
&self,
address: Address,
index: JsonStorageKey,
block_id: Option<BlockId>,
) -> EthResult<B256> {
Ok(B256::new(
self.state_at_block_id_or_latest(block_id)?
.storage(address, index.0)?
.unwrap_or_default()
.to_be_bytes(),
))
}
pub(crate) async fn get_proof(
&self,
address: Address,
keys: Vec<JsonStorageKey>,
block_id: Option<BlockId>,
) -> EthResult<EIP1186AccountProofResponse> {
let chain_info = self.provider().chain_info()?;
let block_id = block_id.unwrap_or_default();
// if we are trying to create a proof for the latest block, but have a BlockId as input
// that is not BlockNumberOrTag::Latest, then we need to figure out whether or not the
// BlockId corresponds to the latest block
let is_latest_block = match block_id {
BlockId::Number(BlockNumberOrTag::Number(num)) => num == chain_info.best_number,
BlockId::Hash(hash) => hash == chain_info.best_hash.into(),
BlockId::Number(BlockNumberOrTag::Latest) => true,
_ => false,
};
// TODO: remove when HistoricalStateProviderRef::proof is implemented
if !is_latest_block {
return Err(EthApiError::InvalidBlockRange)
}
let this = self.clone();
self.inner
.blocking_task_pool
.spawn(move || {
let state = this.state_at_block_id(block_id)?;
let storage_keys = keys.iter().map(|key| key.0).collect::<Vec<_>>();
let proof = state.proof(address, &storage_keys)?;
Ok(from_primitive_account_proof(proof))
})
.await
.map_err(|_| EthApiError::InternalBlockingTaskError)?
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eth::{
cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache, FeeHistoryCacheConfig,
};
use reth_evm_ethereum::EthEvmConfig;
use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, StorageKey, StorageValue};
use reth_provider::test_utils::{ExtendedAccount, MockEthProvider, NoopProvider};
use reth_tasks::pool::BlockingTaskPool;
use reth_transaction_pool::test_utils::testing_pool;
use std::collections::HashMap;
#[tokio::test]
async fn test_storage() {
// === Noop ===
let pool = testing_pool();
let evm_config = EthEvmConfig::default();
let cache = EthStateCache::spawn(NoopProvider::default(), Default::default(), evm_config);
let eth_api = EthApi::new(
NoopProvider::default(),
pool.clone(),
(),
cache.clone(),
GasPriceOracle::new(NoopProvider::default(), Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
BlockingTaskPool::build().expect("failed to build tracing pool"),
FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()),
evm_config,
None,
);
let address = Address::random();
let storage = eth_api.storage_at(address, U256::ZERO.into(), None).unwrap();
assert_eq!(storage, U256::ZERO.to_be_bytes());
// === Mock ===
let mock_provider = MockEthProvider::default();
let storage_value = StorageValue::from(1337);
let storage_key = StorageKey::random();
let storage = HashMap::from([(storage_key, storage_value)]);
let account = ExtendedAccount::new(0, U256::ZERO).extend_storage(storage);
mock_provider.add_account(address, account);
let cache = EthStateCache::spawn(mock_provider.clone(), Default::default(), evm_config);
let eth_api = EthApi::new(
mock_provider.clone(),
pool,
(),
cache.clone(),
GasPriceOracle::new(mock_provider, Default::default(), cache.clone()),
ETHEREUM_BLOCK_GAS_LIMIT,
BlockingTaskPool::build().expect("failed to build tracing pool"),
FeeHistoryCache::new(cache, FeeHistoryCacheConfig::default()),
evm_config,
None,
);
let storage_key: U256 = storage_key.into();
let storage = eth_api.storage_at(address, storage_key.into(), None).unwrap();
assert_eq!(storage, storage_value.to_be_bytes());
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
//! `eth` namespace handler implementation.
mod api;
pub mod bundle;
pub mod cache;
pub mod error;
mod filter;
pub mod gas_oracle;
mod id_provider;
mod logs_utils;
mod pubsub;
pub mod revm_utils;
mod signer;
pub mod traits;
pub(crate) mod utils;
#[cfg(feature = "optimism")]
pub mod optimism;
pub use api::{
fee_history::{fee_history_cache_new_blocks_task, FeeHistoryCache, FeeHistoryCacheConfig},
EthApi, EthApiSpec, EthTransactions, TransactionSource, RPC_DEFAULT_GAS_CAP,
};
pub use bundle::EthBundle;
pub use filter::{EthFilter, EthFilterConfig};
pub use id_provider::EthSubscriptionIdProvider;
pub use pubsub::EthPubSub;

View File

@ -1,32 +0,0 @@
//! Optimism specific types.
use jsonrpsee::types::ErrorObject;
use reth_rpc_types::ToRpcError;
use crate::{eth::error::EthApiError, result::internal_rpc_err};
/// Eth Optimism Api Error
#[cfg(feature = "optimism")]
#[derive(Debug, thiserror::Error)]
pub enum OptimismEthApiError {
/// Thrown when calculating L1 gas fee
#[error("failed to calculate l1 gas fee")]
L1BlockFeeError,
/// Thrown when calculating L1 gas used
#[error("failed to calculate l1 gas used")]
L1BlockGasError,
}
impl ToRpcError for OptimismEthApiError {
fn to_rpc_error(&self) -> ErrorObject<'static> {
match self {
Self::L1BlockFeeError | Self::L1BlockGasError => internal_rpc_err(self.to_string()),
}
}
}
impl From<OptimismEthApiError> for EthApiError {
fn from(err: OptimismEthApiError) -> Self {
Self::other(err)
}
}

View File

@ -1,13 +0,0 @@
//! Additional helper traits that allow for more customization.
use crate::eth::error::EthResult;
use std::fmt;
/// A trait that allows for forwarding raw transactions.
///
/// For example to a sequencer.
#[async_trait::async_trait]
pub trait RawTransactionForwarder: fmt::Debug + Send + Sync + 'static {
/// Forwards raw transaction bytes for `eth_sendRawTransaction`
async fn forward_raw_transaction(&self, raw: &[u8]) -> EthResult<()>;
}

View File

@ -11,11 +11,12 @@
//! and can reduce overall performance of all concurrent requests handled via the jsonrpsee server. //! and can reduce overall performance of all concurrent requests handled via the jsonrpsee server.
//! //!
//! To avoid this, all blocking or CPU intensive handlers must be spawned to a separate task. See //! To avoid this, all blocking or CPU intensive handlers must be spawned to a separate task. See
//! the [`EthApi`] handler implementations for examples. The rpc-api traits make no use of the //! the [`EthApi`](reth_rpc_eth_api::EthApi) handler implementations for examples. The rpc-api
//! available jsonrpsee `blocking` attribute to give implementers more freedom because the //! traits make no use of the available jsonrpsee `blocking` attribute to give implementers more
//! `blocking` attribute and async handlers are mutually exclusive. However, as mentioned above, a //! freedom because the `blocking` attribute and async handlers are mutually exclusive. However, as
//! lot of handlers make use of async functions, caching for example, but are also using blocking //! mentioned above, a lot of handlers make use of async functions, caching for example, but are
//! disk-io, hence these calls are spawned as futures to a blocking task manually. //! also using blocking disk-io, hence these calls are spawned as futures to a blocking task
//! manually.
#![doc( #![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
@ -35,7 +36,6 @@ use tower as _;
mod admin; mod admin;
mod debug; mod debug;
mod engine; mod engine;
pub mod eth;
mod net; mod net;
mod otterscan; mod otterscan;
mod reth; mod reth;
@ -46,7 +46,6 @@ mod web3;
pub use admin::AdminApi; pub use admin::AdminApi;
pub use debug::DebugApi; pub use debug::DebugApi;
pub use engine::{EngineApi, EngineEthApi}; pub use engine::{EngineApi, EngineEthApi};
pub use eth::{EthApi, EthApiSpec, EthFilter, EthPubSub, EthSubscriptionIdProvider};
pub use net::NetApi; pub use net::NetApi;
pub use otterscan::OtterscanApi; pub use otterscan::OtterscanApi;
pub use reth::RethApi; pub use reth::RethApi;
@ -54,4 +53,6 @@ pub use rpc::RPCApi;
pub use trace::TraceApi; pub use trace::TraceApi;
pub use txpool::TxPoolApi; pub use txpool::TxPoolApi;
pub use web3::Web3Api; pub use web3::Web3Api;
pub mod result;
pub use reth_rpc_eth_api as eth;
pub use reth_rpc_eth_api::result;

View File

@ -1,8 +1,8 @@
use crate::eth::EthApiSpec;
use jsonrpsee::core::RpcResult as Result; use jsonrpsee::core::RpcResult as Result;
use reth_network_api::PeersInfo; use reth_network_api::PeersInfo;
use reth_primitives::U64; use reth_primitives::U64;
use reth_rpc_api::NetApiServer; use reth_rpc_api::NetApiServer;
use reth_rpc_eth_api::servers::EthApiSpec;
use reth_rpc_types::PeerCount; use reth_rpc_types::PeerCount;
/// `Net` API implementation. /// `Net` API implementation.

View File

@ -1,11 +1,9 @@
use alloy_primitives::Bytes; use alloy_primitives::Bytes;
use async_trait::async_trait; use async_trait::async_trait;
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
use revm_inspectors::transfer::{TransferInspector, TransferKind};
use revm_primitives::ExecutionResult;
use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256}; use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256};
use reth_rpc_api::{EthApiServer, OtterscanServer}; use reth_rpc_api::{EthApiServer, OtterscanServer};
use reth_rpc_eth_api::{result::internal_rpc_err, servers::TraceExt};
use reth_rpc_types::{ use reth_rpc_types::{
trace::otterscan::{ trace::otterscan::{
BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions, BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions,
@ -13,8 +11,8 @@ use reth_rpc_types::{
}, },
BlockTransactions, Transaction, BlockTransactions, Transaction,
}; };
use revm_inspectors::transfer::{TransferInspector, TransferKind};
use crate::{eth::EthTransactions, result::internal_rpc_err}; use revm_primitives::ExecutionResult;
const API_LEVEL: u64 = 8; const API_LEVEL: u64 = 8;
@ -34,7 +32,7 @@ impl<Eth> OtterscanApi<Eth> {
#[async_trait] #[async_trait]
impl<Eth> OtterscanServer for OtterscanApi<Eth> impl<Eth> OtterscanServer for OtterscanApi<Eth>
where where
Eth: EthApiServer + EthTransactions, Eth: EthApiServer + TraceExt + 'static,
{ {
/// Handler for `ots_hasCode` /// Handler for `ots_hasCode`
async fn has_code(&self, address: Address, block_number: Option<BlockId>) -> RpcResult<bool> { async fn has_code(&self, address: Address, block_number: Option<BlockId>) -> RpcResult<bool> {

View File

@ -1,12 +1,13 @@
use crate::eth::error::{EthApiError, EthResult}; use std::{collections::HashMap, future::Future, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
use reth_errors::RethResult; use reth_errors::RethResult;
use reth_primitives::{Address, BlockId, U256}; use reth_primitives::{Address, BlockId, U256};
use reth_provider::{BlockReaderIdExt, ChangeSetReader, StateProviderFactory}; use reth_provider::{BlockReaderIdExt, ChangeSetReader, StateProviderFactory};
use reth_rpc_api::RethApiServer; use reth_rpc_api::RethApiServer;
use reth_rpc_eth_api::{EthApiError, EthResult};
use reth_tasks::TaskSpawner; use reth_tasks::TaskSpawner;
use std::{collections::HashMap, future::Future, sync::Arc};
use tokio::sync::oneshot; use tokio::sync::oneshot;
/// `reth` API implementation. /// `reth` API implementation.

View File

@ -1,9 +1,5 @@
use crate::eth::{ use std::{collections::HashSet, sync::Arc};
error::{EthApiError, EthResult},
revm_utils::prepare_call_env,
utils::recover_raw_transaction,
EthTransactions,
};
use async_trait::async_trait; use async_trait::async_trait;
use jsonrpsee::core::RpcResult as Result; use jsonrpsee::core::RpcResult as Result;
use reth_consensus_common::calc::{ use reth_consensus_common::calc::{
@ -13,6 +9,12 @@ use reth_primitives::{revm::env::tx_env_with_recovered, BlockId, Bytes, Header,
use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory}; use reth_provider::{BlockReader, ChainSpecProvider, EvmEnvProvider, StateProviderFactory};
use reth_revm::database::StateProviderDatabase; use reth_revm::database::StateProviderDatabase;
use reth_rpc_api::TraceApiServer; use reth_rpc_api::TraceApiServer;
use reth_rpc_eth_api::{
error::{EthApiError, EthResult},
revm_utils::prepare_call_env,
servers::TraceExt,
utils::recover_raw_transaction,
};
use reth_rpc_types::{ use reth_rpc_types::{
state::{EvmOverrides, StateOverride}, state::{EvmOverrides, StateOverride},
trace::{ trace::{
@ -32,7 +34,6 @@ use revm_inspectors::{
opcode::OpcodeGasInspector, opcode::OpcodeGasInspector,
tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig}, tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
}; };
use std::{collections::HashSet, sync::Arc};
use tokio::sync::{AcquireError, OwnedSemaphorePermit}; use tokio::sync::{AcquireError, OwnedSemaphorePermit};
/// `trace` API implementation. /// `trace` API implementation.
@ -74,7 +75,7 @@ impl<Provider, Eth> TraceApi<Provider, Eth> {
impl<Provider, Eth> TraceApi<Provider, Eth> impl<Provider, Eth> TraceApi<Provider, Eth>
where where
Provider: BlockReader + StateProviderFactory + EvmEnvProvider + ChainSpecProvider + 'static, Provider: BlockReader + StateProviderFactory + EvmEnvProvider + ChainSpecProvider + 'static,
Eth: EthTransactions + 'static, Eth: TraceExt + 'static,
{ {
/// Executes the given call and returns a number of possible traces for it. /// Executes the given call and returns a number of possible traces for it.
pub async fn trace_call(&self, trace_request: TraceCallRequest) -> EthResult<TraceResults> { pub async fn trace_call(&self, trace_request: TraceCallRequest) -> EthResult<TraceResults> {
@ -86,6 +87,10 @@ where
let this = self.clone(); let this = self.clone();
self.eth_api() self.eth_api()
.spawn_with_call_at(trace_request.call, at, overrides, move |db, env| { .spawn_with_call_at(trace_request.call, at, overrides, move |db, env| {
// wrapper is hack to get around 'higher-ranked lifetime error', see
// <https://github.com/rust-lang/rust/issues/100013>
let db = db.0;
let (res, _) = this.eth_api().inspect(&mut *db, env, &mut inspector)?; let (res, _) = this.eth_api().inspect(&mut *db, env, &mut inspector)?;
let trace_res = inspector.into_parity_builder().into_trace_results_with_state( let trace_res = inspector.into_parity_builder().into_trace_results_with_state(
&res, &res,
@ -372,7 +377,7 @@ where
}, },
); );
let block = self.inner.eth_api.block_by_id(block_id); let block = self.inner.eth_api.block(block_id);
let (maybe_traces, maybe_block) = futures::try_join!(traces, block)?; let (maybe_traces, maybe_block) = futures::try_join!(traces, block)?;
let mut maybe_traces = let mut maybe_traces =
@ -455,7 +460,7 @@ where
let res = self let res = self
.inner .inner
.eth_api .eth_api
.trace_block_with_inspector( .trace_block_inspector(
block_id, block_id,
OpcodeGasInspector::default, OpcodeGasInspector::default,
move |tx_info, inspector, _res, _, _| { move |tx_info, inspector, _res, _, _| {
@ -470,7 +475,7 @@ where
let Some(transactions) = res else { return Ok(None) }; let Some(transactions) = res else { return Ok(None) };
let Some(block) = self.inner.eth_api.block_by_id(block_id).await? else { return Ok(None) }; let Some(block) = self.inner.eth_api.block(block_id).await? else { return Ok(None) };
Ok(Some(BlockOpcodeGas { Ok(Some(BlockOpcodeGas {
block_hash: block.hash(), block_hash: block.hash(),
@ -548,7 +553,7 @@ where
impl<Provider, Eth> TraceApiServer for TraceApi<Provider, Eth> impl<Provider, Eth> TraceApiServer for TraceApi<Provider, Eth>
where where
Provider: BlockReader + StateProviderFactory + EvmEnvProvider + ChainSpecProvider + 'static, Provider: BlockReader + StateProviderFactory + EvmEnvProvider + ChainSpecProvider + 'static,
Eth: EthTransactions + 'static, Eth: TraceExt + 'static,
{ {
/// Executes the given call and returns a number of possible traces for it. /// Executes the given call and returns a number of possible traces for it.
/// ///

View File

@ -1,10 +1,11 @@
use crate::result::ToRpcResult;
use async_trait::async_trait; use async_trait::async_trait;
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
use reth_network_api::NetworkInfo; use reth_network_api::NetworkInfo;
use reth_primitives::{keccak256, Bytes, B256}; use reth_primitives::{keccak256, Bytes, B256};
use reth_rpc_api::Web3ApiServer; use reth_rpc_api::Web3ApiServer;
use crate::result::ToRpcResult;
/// `web3` API implementation. /// `web3` API implementation.
/// ///
/// This type provides the functionality for handling `web3` related requests. /// This type provides the functionality for handling `web3` related requests.

View File

@ -71,4 +71,4 @@ arbitrary = [
"dep:arbitrary", "dep:arbitrary",
"dep:proptest", "dep:proptest",
] ]
optimism = [] optimism = ["reth-primitives/optimism"]

View File

@ -314,70 +314,69 @@ mod tests {
// //
// this check is to ensure we do not inadvertently add too many fields to a struct which would // this check is to ensure we do not inadvertently add too many fields to a struct which would
// expand the flags field and break backwards compatibility // expand the flags field and break backwards compatibility
#[cfg(not(feature = "optimism"))]
#[test] #[test]
fn test_ensure_backwards_compatibility() { fn test_ensure_backwards_compatibility() {
#[cfg(not(feature = "optimism"))] assert_eq!(Account::bitflag_encoded_bytes(), 2);
{ assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(Account::bitflag_encoded_bytes(), 2); assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1);
assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0);
assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); assert_eq!(CompactU256::bitflag_encoded_bytes(), 1);
assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); assert_eq!(CompactU64::bitflag_encoded_bytes(), 1);
assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(Header::bitflag_encoded_bytes(), 4);
assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(Header::bitflag_encoded_bytes(), 4); assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); assert_eq!(PruneMode::bitflag_encoded_bytes(), 1);
assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1);
assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); assert_eq!(Receipt::bitflag_encoded_bytes(), 1);
assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0);
assert_eq!(Receipt::bitflag_encoded_bytes(), 1); assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0);
assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1);
assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0);
assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0);
assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4);
assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3);
assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5);
assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3);
assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0);
assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); }
assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0);
}
#[cfg(feature = "optimism")] #[cfg(feature = "optimism")]
{ #[test]
assert_eq!(Account::bitflag_encoded_bytes(), 2); fn test_ensure_backwards_compatibility() {
assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(Account::bitflag_encoded_bytes(), 2);
assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1);
assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0);
assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); assert_eq!(CompactU256::bitflag_encoded_bytes(), 1);
assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(CompactU64::bitflag_encoded_bytes(), 1);
assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(Header::bitflag_encoded_bytes(), 4); assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); assert_eq!(Header::bitflag_encoded_bytes(), 4);
assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0);
assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); assert_eq!(PruneMode::bitflag_encoded_bytes(), 1);
assert_eq!(Receipt::bitflag_encoded_bytes(), 2); assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1);
assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); assert_eq!(Receipt::bitflag_encoded_bytes(), 2);
assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0);
assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0);
assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1);
assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0);
assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0);
assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1);
assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4);
assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3);
assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5);
assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3);
} assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0);
} }
} }

View File

@ -24,7 +24,7 @@ pub enum NippyJarError {
#[error("unexpected missing value: row:col {0}:{1}")] #[error("unexpected missing value: row:col {0}:{1}")]
UnexpectedMissingValue(u64, u64), UnexpectedMissingValue(u64, u64),
#[error(transparent)] #[error(transparent)]
FilterError(#[from] cuckoofilter::CuckooError), EthFilterError(#[from] cuckoofilter::CuckooError),
#[error("nippy jar initialized without filter")] #[error("nippy jar initialized without filter")]
FilterMissing, FilterMissing,
#[error("filter has reached max capacity")] #[error("filter has reached max capacity")]

View File

@ -2,6 +2,7 @@ use reth_chainspec::ChainSpec;
use std::sync::Arc; use std::sync::Arc;
/// A trait for reading the current chainspec. /// A trait for reading the current chainspec.
#[auto_impl::auto_impl(&, Arc)]
pub trait ChainSpecProvider: Send + Sync { pub trait ChainSpecProvider: Send + Sync {
/// Get an [`Arc`] to the chainspec. /// Get an [`Arc`] to the chainspec.
fn chain_spec(&self) -> Arc<ChainSpec>; fn chain_spec(&self) -> Arc<ChainSpec>;

Some files were not shown because too many files have changed in this diff Show More