mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
chore(rpc): reth-eth-api crate (#8887)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -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",
|
||||||
|
|||||||
@ -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" }
|
||||||
|
|||||||
@ -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>,
|
||||||
|
|||||||
@ -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| {
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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`].
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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"]
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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::{
|
||||||
|
|||||||
@ -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`.
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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| {
|
||||||
|
|||||||
@ -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());
|
||||||
|
|||||||
@ -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};
|
||||||
|
|||||||
@ -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"]
|
||||||
@ -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>>;
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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};
|
||||||
|
|||||||
@ -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)]
|
||||||
|
|||||||
@ -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
|
||||||
///
|
///
|
||||||
|
|||||||
78
crates/rpc/rpc-eth-api/Cargo.toml
Normal file
78
crates/rpc/rpc-eth-api/Cargo.toml
Normal 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",
|
||||||
|
]
|
||||||
@ -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>
|
||||||
|
|
||||||
@ -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"))]
|
||||||
@ -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"))]
|
||||||
@ -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};
|
||||||
|
|
||||||
@ -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(ð_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?)
|
||||||
@ -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::*;
|
||||||
35
crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs
Normal file
35
crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
29
crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs
Normal file
29
crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
39
crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs
Normal file
39
crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
19
crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs
Normal file
19
crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs
Normal 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;
|
||||||
231
crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs
Normal file
231
crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
13
crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs
Normal file
13
crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 =
|
||||||
64
crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs
Normal file
64
crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
104
crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs
Normal file
104
crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
19
crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs
Normal file
19
crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
244
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs
Normal file
244
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs
Normal 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?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)? }
|
||||||
|
}
|
||||||
|
}
|
||||||
775
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs
Normal file
775
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
346
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs
Normal file
346
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
45
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs
Normal file
45
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs
Normal 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 {}
|
||||||
@ -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,
|
|
||||||
}
|
|
||||||
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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);
|
||||||
@ -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>;
|
||||||
|
}
|
||||||
247
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs
Normal file
247
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs
Normal 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()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
412
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs
Normal file
412
crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<()>;
|
||||||
|
}
|
||||||
126
crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs
Normal file
126
crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
323
crates/rpc/rpc-eth-api/src/api/servers/mod.rs
Normal file
323
crates/rpc/rpc-eth-api/src/api/servers/mod.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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,
|
||||||
@ -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
169
crates/rpc/rpc-eth-api/src/cache/db.rs
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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};
|
||||||
|
|
||||||
@ -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 = ();
|
||||||
|
|
||||||
@ -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,
|
||||||
@ -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`.
|
||||||
@ -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::*;
|
||||||
@ -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
|
||||||
62
crates/rpc/rpc-eth-api/src/lib.rs
Normal file
62
crates/rpc/rpc-eth-api/src/lib.rs
Normal 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;
|
||||||
@ -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;
|
||||||
|
|
||||||
162
crates/rpc/rpc-eth-api/src/pending_block.rs
Normal file
162
crates/rpc/rpc-eth-api/src/pending_block.rs
Normal 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,
|
||||||
|
}
|
||||||
126
crates/rpc/rpc-eth-api/src/receipt.rs
Normal file
126
crates/rpc/rpc-eth-api/src/receipt.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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]>,
|
||||||
@ -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]
|
||||||
96
crates/rpc/rpc-eth-api/src/transaction.rs
Normal file
96
crates/rpc/rpc-eth-api/src/transaction.rs
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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(())
|
|
||||||
}
|
|
||||||
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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>>>,
|
|
||||||
}
|
|
||||||
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
@ -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;
|
|
||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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<()>;
|
|
||||||
}
|
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -71,4 +71,4 @@ arbitrary = [
|
|||||||
"dep:arbitrary",
|
"dep:arbitrary",
|
||||||
"dep:proptest",
|
"dep:proptest",
|
||||||
]
|
]
|
||||||
optimism = []
|
optimism = ["reth-primitives/optimism"]
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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")]
|
||||||
|
|||||||
@ -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
Reference in New Issue
Block a user