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-provider",
|
||||
"reth-prune-types",
|
||||
"reth-rpc",
|
||||
"reth-rpc-api",
|
||||
"reth-rpc-eth-api",
|
||||
"reth-rpc-server-types",
|
||||
"reth-rpc-types",
|
||||
"reth-rpc-types-compat",
|
||||
@ -7996,33 +7996,22 @@ dependencies = [
|
||||
name = "reth-rpc"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"alloy-dyn-abi",
|
||||
"alloy-genesis",
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"alloy-sol-types",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"derive_more",
|
||||
"dyn-clone",
|
||||
"futures",
|
||||
"http 1.1.0",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"jsonrpsee",
|
||||
"jsonwebtoken",
|
||||
"metrics",
|
||||
"parking_lot 0.12.3",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"reth-chainspec",
|
||||
"reth-consensus-common",
|
||||
"reth-errors",
|
||||
"reth-evm",
|
||||
"reth-evm-ethereum",
|
||||
"reth-evm-optimism",
|
||||
"reth-execution-types",
|
||||
"reth-metrics",
|
||||
"reth-network-api",
|
||||
"reth-network-peers",
|
||||
"reth-primitives",
|
||||
@ -8030,7 +8019,7 @@ dependencies = [
|
||||
"reth-revm",
|
||||
"reth-rpc-api",
|
||||
"reth-rpc-engine-api",
|
||||
"reth-rpc-server-types",
|
||||
"reth-rpc-eth-api",
|
||||
"reth-rpc-types",
|
||||
"reth-rpc-types-compat",
|
||||
"reth-tasks",
|
||||
@ -8039,14 +8028,8 @@ dependencies = [
|
||||
"revm",
|
||||
"revm-inspectors",
|
||||
"revm-primitives",
|
||||
"schnellru",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
@ -8056,11 +8039,11 @@ dependencies = [
|
||||
name = "reth-rpc-api"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"alloy-dyn-abi",
|
||||
"jsonrpsee",
|
||||
"reth-engine-primitives",
|
||||
"reth-network-peers",
|
||||
"reth-primitives",
|
||||
"reth-rpc-eth-api",
|
||||
"reth-rpc-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -8074,6 +8057,7 @@ dependencies = [
|
||||
"jsonrpsee",
|
||||
"reth-primitives",
|
||||
"reth-rpc-api",
|
||||
"reth-rpc-eth-api",
|
||||
"reth-rpc-types",
|
||||
"serde_json",
|
||||
"similar-asserts",
|
||||
@ -8155,6 +8139,53 @@ dependencies = [
|
||||
"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]]
|
||||
name = "reth-rpc-layer"
|
||||
version = "1.0.0"
|
||||
@ -8372,6 +8403,7 @@ dependencies = [
|
||||
name = "reth-tasks"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"auto_impl",
|
||||
"dyn-clone",
|
||||
"futures-util",
|
||||
"metrics",
|
||||
|
||||
@ -80,6 +80,7 @@ members = [
|
||||
"crates/rpc/rpc-api/",
|
||||
"crates/rpc/rpc-builder/",
|
||||
"crates/rpc/rpc-engine-api/",
|
||||
"crates/rpc/rpc-eth-api/",
|
||||
"crates/rpc/rpc-layer",
|
||||
"crates/rpc/rpc-testing-util/",
|
||||
"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-builder = { path = "crates/rpc/rpc-builder" }
|
||||
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-server-types = { path = "crates/rpc/rpc-server-types" }
|
||||
reth-rpc-types = { path = "crates/rpc/rpc-types" }
|
||||
|
||||
@ -2,7 +2,7 @@ use alloy_consensus::TxEnvelope;
|
||||
use alloy_network::eip2718::Decodable2718;
|
||||
use reth::{api::FullNodeComponents, builder::rpc::RpcRegistry, rpc::api::DebugApiServer};
|
||||
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 inner: RpcRegistry<Node>,
|
||||
|
||||
@ -179,7 +179,7 @@ where
|
||||
.into())
|
||||
}
|
||||
|
||||
EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender);
|
||||
self.evm_config.fill_tx_env(evm.tx_mut(), transaction, *sender);
|
||||
|
||||
// Execute transaction.
|
||||
let ResultAndState { result, state } = evm.transact().map_err(move |err| {
|
||||
|
||||
@ -12,13 +12,7 @@
|
||||
#[cfg(not(feature = "std"))]
|
||||
extern crate alloc;
|
||||
|
||||
use reth_chainspec::ChainSpec;
|
||||
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};
|
||||
|
||||
pub mod execute;
|
||||
@ -34,34 +28,7 @@ pub mod eip6110;
|
||||
#[non_exhaustive]
|
||||
pub struct 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 ConfigureEvmEnv for EthEvmConfig {}
|
||||
|
||||
impl ConfigureEvm for EthEvmConfig {
|
||||
type DefaultExternalContext<'a> = ();
|
||||
@ -77,7 +44,12 @@ impl ConfigureEvm for EthEvmConfig {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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]
|
||||
#[ignore]
|
||||
|
||||
@ -2,7 +2,7 @@ use crate::utils::EthNode;
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::{b256, hex};
|
||||
use futures::StreamExt;
|
||||
use reth::rpc::eth::EthTransactions;
|
||||
use reth::rpc::eth::servers::EthTransactions;
|
||||
use reth_chainspec::ChainSpec;
|
||||
use reth_e2e_test_utils::setup;
|
||||
use reth_provider::CanonStateSubscriptions;
|
||||
|
||||
@ -13,9 +13,17 @@
|
||||
extern crate alloc;
|
||||
|
||||
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_primitives::{BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv};
|
||||
use revm_primitives::{
|
||||
AnalysisKind, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv,
|
||||
};
|
||||
|
||||
pub mod either;
|
||||
pub mod execute;
|
||||
@ -27,6 +35,7 @@ pub mod provider;
|
||||
pub mod test_utils;
|
||||
|
||||
/// Trait for configuring the EVM for executing full blocks.
|
||||
#[auto_impl::auto_impl(&, Arc)]
|
||||
pub trait ConfigureEvm: ConfigureEvmEnv {
|
||||
/// Associated type for the default external context that should be configured for the EVM.
|
||||
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
|
||||
/// execution.
|
||||
///
|
||||
/// Default trait method implementation is done w.r.t. L1.
|
||||
#[auto_impl::auto_impl(&, Arc)]
|
||||
pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
|
||||
/// 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
|
||||
fn fill_cfg_env(
|
||||
@ -108,8 +122,24 @@ pub trait ConfigureEvmEnv: Send + Sync + Unpin + Clone + 'static {
|
||||
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;
|
||||
}
|
||||
|
||||
/// Convenience function to call both [`fill_cfg_env`](ConfigureEvmEnv::fill_cfg_env) and
|
||||
/// [`fill_block_env`].
|
||||
fn fill_cfg_and_block_env(
|
||||
|
||||
@ -6,13 +6,13 @@ use reth_storage_errors::provider::ProviderResult;
|
||||
use revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, SpecId};
|
||||
|
||||
/// 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
|
||||
/// usually stored on disk.
|
||||
#[auto_impl::auto_impl(&, Arc)]
|
||||
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].
|
||||
fn fill_env_at<EvmConfig>(
|
||||
&self,
|
||||
@ -24,7 +24,7 @@ pub trait EvmEnvProvider: Send + Sync {
|
||||
where
|
||||
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].
|
||||
fn env_with_header<EvmConfig>(
|
||||
&self,
|
||||
@ -40,7 +40,7 @@ pub trait EvmEnvProvider: Send + Sync {
|
||||
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].
|
||||
fn fill_env_with_header<EvmConfig>(
|
||||
&self,
|
||||
@ -66,7 +66,7 @@ pub trait EvmEnvProvider: Send + Sync {
|
||||
header: &Header,
|
||||
) -> ProviderResult<()>;
|
||||
|
||||
/// Fills the [CfgEnvWithHandlerCfg] fields with values specific to the given
|
||||
/// Fills the [`CfgEnvWithHandlerCfg`] fields with values specific to the given
|
||||
/// [BlockHashOrNumber].
|
||||
fn fill_cfg_env_at<EvmConfig>(
|
||||
&self,
|
||||
@ -77,7 +77,7 @@ pub trait EvmEnvProvider: Send + Sync {
|
||||
where
|
||||
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>(
|
||||
&self,
|
||||
cfg: &mut CfgEnvWithHandlerCfg,
|
||||
|
||||
@ -21,11 +21,11 @@ reth-storage-errors.workspace = true
|
||||
reth-provider.workspace = true
|
||||
reth-network = { workspace = true, features = ["serde"] }
|
||||
reth-network-p2p.workspace = true
|
||||
reth-rpc.workspace = true
|
||||
reth-rpc-server-types.workspace = true
|
||||
reth-rpc-types.workspace = true
|
||||
reth-rpc-types-compat.workspace = true
|
||||
reth-rpc-api = { workspace = true, features = ["client"] }
|
||||
reth-rpc-eth-api = { workspace = true, features = ["client"] }
|
||||
reth-transaction-pool.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
reth-config.workspace = true
|
||||
@ -99,10 +99,10 @@ proptest.workspace = true
|
||||
[features]
|
||||
optimism = [
|
||||
"reth-primitives/optimism",
|
||||
"reth-rpc/optimism",
|
||||
"reth-provider/optimism",
|
||||
"reth-rpc-types-compat/optimism",
|
||||
"reth-beacon-consensus/optimism",
|
||||
"reth-rpc-eth-api/optimism",
|
||||
]
|
||||
|
||||
jemalloc = ["dep:tikv-jemalloc-ctl"]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::primitives::U256;
|
||||
use clap::Args;
|
||||
use reth_rpc::eth::gas_oracle::GasPriceOracleConfig;
|
||||
use reth_rpc_eth_api::GasPriceOracleConfig;
|
||||
use reth_rpc_server_types::constants::gas_oracle::{
|
||||
DEFAULT_GAS_PRICE_BLOCKS, DEFAULT_GAS_PRICE_PERCENTILE, DEFAULT_IGNORE_GAS_PRICE,
|
||||
DEFAULT_MAX_GAS_PRICE,
|
||||
|
||||
@ -10,7 +10,7 @@ use clap::{
|
||||
Arg, Args, Command,
|
||||
};
|
||||
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 std::{
|
||||
|
||||
@ -38,12 +38,12 @@ pub mod rpc {
|
||||
}
|
||||
/// Re-exported from `reth_rpc::eth`.
|
||||
pub mod eth {
|
||||
pub use reth_rpc::eth::*;
|
||||
pub use reth_rpc_eth_api::*;
|
||||
}
|
||||
|
||||
/// Re-exported from `reth_rpc::rpc`.
|
||||
pub mod result {
|
||||
pub use reth_rpc::result::*;
|
||||
pub use reth_rpc_eth_api::result::*;
|
||||
}
|
||||
|
||||
/// Re-exported from `reth_rpc::eth`.
|
||||
|
||||
@ -189,8 +189,9 @@ impl<Node: FullNodeComponents> Clone for RpcRegistry<Node> {
|
||||
/// [`AuthRpcModule`].
|
||||
///
|
||||
/// 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
|
||||
/// modules [`TransportRpcModules`] as well as configured authenticated methods [`AuthRpcModule`].
|
||||
/// [`reth_rpc::eth::EthApi`], and ultimately merge additional rpc handler into the configured
|
||||
/// transport modules [`TransportRpcModules`] as well as configured authenticated methods
|
||||
/// [`AuthRpcModule`].
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct RpcContext<'a, Node: FullNodeComponents> {
|
||||
/// The node components.
|
||||
|
||||
@ -177,7 +177,7 @@ where
|
||||
.transpose()
|
||||
.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.
|
||||
let ResultAndState { result, state } = evm.transact().map_err(move |err| {
|
||||
|
||||
@ -32,7 +32,7 @@ pub use error::OptimismBlockExecutionError;
|
||||
pub struct 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());
|
||||
transaction.encode_enveloped(&mut buf);
|
||||
fill_op_tx_env(tx_env, transaction, sender, buf.into());
|
||||
|
||||
@ -4,7 +4,7 @@ use jsonrpsee::types::ErrorObject;
|
||||
use reqwest::Client;
|
||||
use reth_rpc::eth::{
|
||||
error::{EthApiError, EthResult},
|
||||
traits::RawTransactionForwarder,
|
||||
servers::RawTransactionForwarder,
|
||||
};
|
||||
use reth_rpc_types::ToRpcError;
|
||||
use std::sync::{atomic::AtomicUsize, Arc};
|
||||
|
||||
@ -15,11 +15,11 @@ workspace = true
|
||||
# reth
|
||||
reth-primitives.workspace = true
|
||||
reth-rpc-types.workspace = true
|
||||
reth-rpc-eth-api.workspace = true
|
||||
reth-engine-primitives.workspace = true
|
||||
reth-network-peers.workspace = true
|
||||
|
||||
# misc
|
||||
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
|
||||
jsonrpsee = { workspace = true, features = ["server", "macros"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@ -27,4 +27,9 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
|
||||
[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")]
|
||||
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")]
|
||||
async fn raw_transactions(&self, block_id: BlockId) -> RpcResult<Vec<Bytes>>;
|
||||
|
||||
|
||||
@ -16,12 +16,8 @@
|
||||
|
||||
mod admin;
|
||||
mod anvil;
|
||||
mod bundle;
|
||||
mod debug;
|
||||
mod engine;
|
||||
mod eth;
|
||||
mod eth_filter;
|
||||
mod eth_pubsub;
|
||||
mod ganache;
|
||||
mod hardhat;
|
||||
mod mev;
|
||||
@ -42,12 +38,8 @@ pub use servers::*;
|
||||
pub mod servers {
|
||||
pub use crate::{
|
||||
admin::AdminApiServer,
|
||||
bundle::{EthBundleApiServer, EthCallBundleApiServer},
|
||||
debug::DebugApiServer,
|
||||
engine::{EngineApiServer, EngineEthApiServer},
|
||||
eth::EthApiServer,
|
||||
eth_filter::EthFilterApiServer,
|
||||
eth_pubsub::EthPubSubApiServer,
|
||||
mev::MevApiServer,
|
||||
net::NetApiServer,
|
||||
otterscan::OtterscanServer,
|
||||
@ -58,6 +50,10 @@ pub mod servers {
|
||||
validation::BlockSubmissionValidationApiServer,
|
||||
web3::Web3ApiServer,
|
||||
};
|
||||
pub use reth_rpc_eth_api::{
|
||||
EthApiServer, EthBundleApiServer, EthCallBundleApiServer, EthFilterApiServer,
|
||||
EthPubSubApiServer,
|
||||
};
|
||||
}
|
||||
|
||||
/// re-export of all client traits
|
||||
@ -70,11 +66,8 @@ pub mod clients {
|
||||
pub use crate::{
|
||||
admin::AdminApiClient,
|
||||
anvil::AnvilApiClient,
|
||||
bundle::{EthBundleApiClient, EthCallBundleApiClient},
|
||||
debug::DebugApiClient,
|
||||
engine::{EngineApiClient, EngineEthApiClient},
|
||||
eth::EthApiClient,
|
||||
eth_filter::EthFilterApiClient,
|
||||
ganache::GanacheApiClient,
|
||||
hardhat::HardhatApiClient,
|
||||
mev::MevApiClient,
|
||||
@ -86,4 +79,7 @@ pub mod clients {
|
||||
validation::BlockSubmissionValidationApiClient,
|
||||
web3::Web3ApiClient,
|
||||
};
|
||||
pub use reth_rpc_eth_api::{
|
||||
EthApiClient, EthBundleApiClient, EthCallBundleApiClient, EthFilterApiClient,
|
||||
};
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ use jsonrpsee::{
|
||||
Methods,
|
||||
};
|
||||
use reth_engine_primitives::EngineTypes;
|
||||
use reth_rpc::EthSubscriptionIdProvider;
|
||||
use reth_rpc::eth::EthSubscriptionIdProvider;
|
||||
use reth_rpc_api::servers::*;
|
||||
use reth_rpc_layer::{
|
||||
secret_to_bearer_header, AuthClientLayer, AuthClientService, AuthLayer, JwtAuthValidator,
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use jsonrpsee::server::ServerBuilder;
|
||||
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_server_types::RpcModuleSelection;
|
||||
use std::{net::SocketAddr, path::PathBuf};
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
use crate::RpcModuleConfig;
|
||||
use std::sync::Arc;
|
||||
|
||||
use reth_evm::ConfigureEvm;
|
||||
use reth_network_api::{NetworkInfo, Peers};
|
||||
use reth_provider::{
|
||||
AccountReader, BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader,
|
||||
EvmEnvProvider, StateProviderFactory,
|
||||
};
|
||||
use reth_rpc::{
|
||||
eth::{
|
||||
cache::{cache_new_blocks_task, EthStateCache, EthStateCacheConfig},
|
||||
fee_history_cache_new_blocks_task,
|
||||
gas_oracle::{GasPriceOracle, GasPriceOracleConfig},
|
||||
traits::RawTransactionForwarder,
|
||||
EthFilterConfig, FeeHistoryCache, FeeHistoryCacheConfig, RPC_DEFAULT_GAS_CAP,
|
||||
},
|
||||
EthApi, EthFilter, EthPubSub,
|
||||
use reth_rpc::eth::{
|
||||
cache::cache_new_blocks_task, fee_history::fee_history_cache_new_blocks_task,
|
||||
servers::RawTransactionForwarder, EthApi, EthFilter, EthFilterConfig, EthPubSub, EthStateCache,
|
||||
EthStateCacheConfig, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
|
||||
GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP,
|
||||
};
|
||||
use reth_rpc_server_types::constants::{
|
||||
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_transaction_pool::TransactionPool;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::RpcModuleConfig;
|
||||
|
||||
/// All handlers for the `eth` namespace
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
//! Pool: TransactionPool + Clone + 'static,
|
||||
//! Network: NetworkInfo + Peers + Clone + 'static,
|
||||
//! Events: CanonStateSubscriptions + Clone + 'static,
|
||||
//! EvmConfig: ConfigureEvm + 'static,
|
||||
//! EvmConfig: ConfigureEvm,
|
||||
//! {
|
||||
//! // configure the rpc module per transport
|
||||
//! let transports = TransportRpcModuleConfig::default().with_http(vec![
|
||||
@ -115,7 +115,7 @@
|
||||
//! Events: CanonStateSubscriptions + Clone + 'static,
|
||||
//! EngineApi: EngineApiServer<EngineT>,
|
||||
//! EngineT: EngineTypes + 'static,
|
||||
//! EvmConfig: ConfigureEvm + 'static,
|
||||
//! EvmConfig: ConfigureEvm,
|
||||
//! {
|
||||
//! // configure the rpc module per transport
|
||||
//! let transports = TransportRpcModuleConfig::default().with_http(vec![
|
||||
@ -178,9 +178,12 @@ use reth_provider::{
|
||||
ChangeSetReader, EvmEnvProvider, StateProviderFactory,
|
||||
};
|
||||
use reth_rpc::{
|
||||
eth::{cache::EthStateCache, traits::RawTransactionForwarder, EthBundle},
|
||||
AdminApi, DebugApi, EngineEthApi, EthApi, EthSubscriptionIdProvider, NetApi, OtterscanApi,
|
||||
RPCApi, RethApi, TraceApi, TxPoolApi, Web3Api,
|
||||
eth::{
|
||||
servers::RawTransactionForwarder, EthApi, EthBundle, EthStateCache,
|
||||
EthSubscriptionIdProvider,
|
||||
},
|
||||
AdminApi, DebugApi, EngineEthApi, NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi,
|
||||
Web3Api,
|
||||
};
|
||||
use reth_rpc_api::servers::*;
|
||||
use reth_rpc_layer::{AuthLayer, Claims, JwtAuthValidator, JwtSecret};
|
||||
@ -250,7 +253,7 @@ where
|
||||
Network: NetworkInfo + Peers + Clone + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
Events: CanonStateSubscriptions + Clone + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
let module_config = module_config.into();
|
||||
let server_config = server_config.into();
|
||||
@ -441,7 +444,7 @@ where
|
||||
Network: NetworkInfo + Peers + Clone + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
Events: CanonStateSubscriptions + Clone + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
/// Configures all [`RpcModule`]s specific to the given [`TransportRpcModuleConfig`] which can
|
||||
/// be used to start the transport server(s).
|
||||
@ -766,7 +769,7 @@ where
|
||||
Network: NetworkInfo + Peers + Clone + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
Events: CanonStateSubscriptions + Clone + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
/// 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>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
//! `eth_` RPC API for filtering.
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use reth_rpc_types::{Filter, FilterChanges, FilterId, Log, PendingTransactionFilterKind};
|
||||
|
||||
/// Rpc Interface for poll-based ethereum filter API.
|
||||
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))]
|
||||
#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))]
|
||||
@ -1,3 +1,5 @@
|
||||
//! `eth_` RPC API.
|
||||
|
||||
use alloy_dyn_abi::TypedData;
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use reth_primitives::{Address, BlockId, BlockNumberOrTag, Bytes, B256, B64, U256, U64};
|
||||
@ -8,6 +10,11 @@ use reth_rpc_types::{
|
||||
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/>
|
||||
#[cfg_attr(not(feature = "client"), rpc(server, 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 reth_rpc_types::pubsub::{Params, SubscriptionKind};
|
||||
|
||||
@ -1,19 +1,15 @@
|
||||
//! `Eth` bundle implementation and helpers.
|
||||
|
||||
use crate::eth::{
|
||||
error::{EthApiError, EthResult, RpcInvalidTransactionError},
|
||||
revm_utils::FillableTransaction,
|
||||
utils::recover_raw_transaction,
|
||||
EthTransactions,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use jsonrpsee::core::RpcResult;
|
||||
use reth_evm::ConfigureEvmEnv;
|
||||
use reth_primitives::{
|
||||
keccak256,
|
||||
revm_primitives::db::{DatabaseCommit, DatabaseRef},
|
||||
PooledTransactionsElement, U256,
|
||||
};
|
||||
use reth_revm::database::StateProviderDatabase;
|
||||
use reth_rpc_api::EthCallBundleApiServer;
|
||||
use reth_rpc_types::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult};
|
||||
use reth_tasks::pool::BlockingTaskGuard;
|
||||
use revm::{
|
||||
@ -21,7 +17,12 @@ use revm::{
|
||||
primitives::{ResultAndState, TxEnv},
|
||||
};
|
||||
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.
|
||||
pub struct EthBundle<Eth> {
|
||||
@ -38,7 +39,7 @@ impl<Eth> EthBundle<Eth> {
|
||||
|
||||
impl<Eth> EthBundle<Eth>
|
||||
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
|
||||
/// 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
|
||||
block_env.number = U256::from(block_number);
|
||||
|
||||
let eth_api = self.inner.eth_api.clone();
|
||||
|
||||
self.inner
|
||||
.eth_api
|
||||
.spawn_with_state_at_block(at, move |state| {
|
||||
@ -129,13 +132,13 @@ where
|
||||
.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());
|
||||
let gas_price = tx
|
||||
.effective_tip_per_gas(basefee)
|
||||
.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 gas_used = result.gas_used();
|
||||
@ -166,7 +169,7 @@ where
|
||||
let tx_res = EthCallBundleTransactionResult {
|
||||
coinbase_diff,
|
||||
eth_sent_to_coinbase,
|
||||
from_address: tx.signer(),
|
||||
from_address: signer,
|
||||
gas_fees,
|
||||
gas_price: U256::from(gas_price),
|
||||
gas_used,
|
||||
@ -212,7 +215,7 @@ where
|
||||
#[async_trait::async_trait]
|
||||
impl<Eth> EthCallBundleApiServer for EthBundle<Eth>
|
||||
where
|
||||
Eth: EthTransactions + 'static,
|
||||
Eth: EthTransactions + LoadPendingBlock + Call + 'static,
|
||||
{
|
||||
async fn call_bundle(&self, request: EthCallBundle) -> RpcResult<EthCallBundleResponse> {
|
||||
Ok(Self::call_bundle(self, request).await?)
|
||||
@ -1,40 +1,37 @@
|
||||
use super::cache::EthStateCache;
|
||||
use crate::{
|
||||
eth::{
|
||||
error::EthApiError,
|
||||
logs_utils::{self, append_matching_block_logs},
|
||||
},
|
||||
result::{rpc_error_with_code, ToRpcResult},
|
||||
EthSubscriptionIdProvider,
|
||||
//! `eth_` `Filter` RPC handler implementation
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
iter::StepBy,
|
||||
ops::RangeInclusive,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use core::fmt;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee::{core::RpcResult, server::IdProvider};
|
||||
use reth_chainspec::ChainInfo;
|
||||
use reth_primitives::{IntoRecoveredTransaction, TxHash};
|
||||
use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider, ProviderError};
|
||||
use reth_rpc_api::EthFilterApiServer;
|
||||
use reth_rpc_types::{
|
||||
BlockNumHash, Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log,
|
||||
PendingTransactionFilterKind,
|
||||
};
|
||||
|
||||
use reth_tasks::TaskSpawner;
|
||||
use reth_transaction_pool::{NewSubpoolTransactionStream, PoolTransaction, TransactionPool};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
iter::StepBy,
|
||||
ops::RangeInclusive,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::{
|
||||
sync::{mpsc::Receiver, Mutex},
|
||||
time::MissedTickBehavior,
|
||||
};
|
||||
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.
|
||||
const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb
|
||||
|
||||
@ -132,7 +129,7 @@ where
|
||||
<Pool as TransactionPool>::Transaction: 'static,
|
||||
{
|
||||
/// 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 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
|
||||
let (start_block, kind) = {
|
||||
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 {
|
||||
// no new blocks since the last poll
|
||||
@ -204,16 +201,16 @@ where
|
||||
/// Returns an error if no matching log filter exists.
|
||||
///
|
||||
/// 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 filters = self.inner.active_filters.inner.lock().await;
|
||||
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()
|
||||
} else {
|
||||
// Not a log filter
|
||||
return Err(FilterError::FilterNotFound(id))
|
||||
return Err(EthFilterError::FilterNotFound(id))
|
||||
}
|
||||
};
|
||||
|
||||
@ -347,7 +344,7 @@ where
|
||||
Pool: TransactionPool + 'static,
|
||||
{
|
||||
/// 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 {
|
||||
FilterBlockOption::AtBlockHash(block_hash) => {
|
||||
// for all matching logs in the block
|
||||
@ -428,16 +425,16 @@ where
|
||||
from_block: u64,
|
||||
to_block: u64,
|
||||
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");
|
||||
let best_number = chain_info.best_number;
|
||||
|
||||
if to_block < from_block {
|
||||
return Err(FilterError::InvalidBlockRangeParams)
|
||||
return Err(EthFilterError::InvalidBlockRangeParams)
|
||||
}
|
||||
|
||||
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();
|
||||
@ -505,7 +502,7 @@ where
|
||||
// logs of a single block
|
||||
let is_multi_block_range = from_block != to_block;
|
||||
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,
|
||||
))
|
||||
}
|
||||
@ -682,51 +679,6 @@ enum FilterKind {
|
||||
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
|
||||
#[derive(Debug)]
|
||||
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)]
|
||||
mod tests {
|
||||
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.
|
||||
|
||||
use crate::eth::error::SignError;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use alloy_dyn_abi::TypedData;
|
||||
use reth_primitives::{
|
||||
eip191_hash_message, sign_message, Address, Signature, TransactionSigned, B256,
|
||||
};
|
||||
use reth_rpc_types::TypedTransactionRequest;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use reth_rpc_types_compat::transaction::to_primitive_transaction;
|
||||
use secp256k1::SecretKey;
|
||||
use std::collections::HashMap;
|
||||
|
||||
type Result<T> = std::result::Result<T, SignError>;
|
||||
|
||||
/// An Ethereum Signer used via RPC.
|
||||
#[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);
|
||||
use crate::{
|
||||
servers::{helpers::traits::signer::Result, EthSigner},
|
||||
SignError,
|
||||
};
|
||||
|
||||
/// Holds developer keys
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DevSigner {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DevSigner {
|
||||
addresses: Vec<Address>,
|
||||
accounts: HashMap<Address, SecretKey>,
|
||||
}
|
||||
@ -121,9 +95,12 @@ impl EthSigner for DevSigner {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use reth_primitives::U256;
|
||||
use std::str::FromStr;
|
||||
|
||||
use reth_primitives::U256;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn build_signer() -> DevSigner {
|
||||
let addresses = vec![];
|
||||
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 reth_chainspec::ChainSpec;
|
||||
use reth_errors::ProviderError;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use futures::Future;
|
||||
use reth_evm::ConfigureEvm;
|
||||
use reth_execution_types::ExecutionOutcome;
|
||||
use reth_primitives::{
|
||||
constants::{eip4844::MAX_DATA_GAS_PER_BLOCK, BEACON_NONCE, EMPTY_ROOT_HASH},
|
||||
proofs,
|
||||
proofs::calculate_transaction_root,
|
||||
revm::env::tx_env_with_recovered,
|
||||
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,
|
||||
SealedBlockWithSenders, SealedHeader, B256, EMPTY_OMMER_ROOT_HASH, U256,
|
||||
Block, BlockNumber, Header, IntoRecoveredTransaction, Receipt, Requests,
|
||||
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::{
|
||||
database::StateProviderDatabase,
|
||||
state_change::{
|
||||
apply_beacon_root_contract_call, apply_blockhashes_update,
|
||||
post_block_withdrawals_balance_increments,
|
||||
},
|
||||
database::StateProviderDatabase, state_change::post_block_withdrawals_balance_increments,
|
||||
};
|
||||
use reth_transaction_pool::{BestTransactionsAttributes, TransactionPool};
|
||||
use revm::{db::states::bundle_state::BundleRetention, Database, DatabaseCommit, State};
|
||||
use revm_primitives::EnvWithHandlerCfg;
|
||||
use std::time::Instant;
|
||||
use revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::debug;
|
||||
|
||||
/// Configured [`BlockEnv`] and [`CfgEnvWithHandlerCfg`] for a pending block
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PendingBlockEnv {
|
||||
/// Configured [`CfgEnvWithHandlerCfg`] for the pending block.
|
||||
pub(crate) cfg: CfgEnvWithHandlerCfg,
|
||||
/// Configured [`BlockEnv`] for the pending block.
|
||||
pub(crate) block_env: BlockEnv,
|
||||
/// Origin block for the config
|
||||
pub(crate) origin: PendingBlockEnvOrigin,
|
||||
}
|
||||
use crate::{
|
||||
pending_block::{pre_block_beacon_root_contract_call, pre_block_blockhashes_update},
|
||||
servers::SpawnBlocking,
|
||||
EthApiError, EthResult, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin,
|
||||
};
|
||||
|
||||
impl PendingBlockEnv {
|
||||
/// Builds a pending block using the given client and pool.
|
||||
/// Loads a pending block from database.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
pub(crate) fn build_block<Client, Pool>(
|
||||
self,
|
||||
client: &Client,
|
||||
pool: &Pool,
|
||||
) -> EthResult<SealedBlockWithSenders>
|
||||
where
|
||||
Client: StateProviderFactory + ChainSpecProvider,
|
||||
Pool: TransactionPool,
|
||||
{
|
||||
let Self { cfg, block_env, origin } = self;
|
||||
fn build_block(&self, env: PendingBlockEnv) -> EthResult<SealedBlockWithSenders> {
|
||||
let PendingBlockEnv { cfg, block_env, origin } = env;
|
||||
|
||||
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 mut db = State::builder().with_database(state).with_bundle_update().build();
|
||||
|
||||
@ -69,7 +215,8 @@ impl PendingBlockEnv {
|
||||
|
||||
let mut executed_txs = Vec::new();
|
||||
let mut senders = Vec::new();
|
||||
let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new(
|
||||
let mut best_txs =
|
||||
self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new(
|
||||
base_fee,
|
||||
block_env.get_blob_gasprice().map(|gasprice| gasprice as u64),
|
||||
));
|
||||
@ -81,7 +228,7 @@ impl PendingBlockEnv {
|
||||
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() {
|
||||
// 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;
|
||||
|
||||
// Push transaction changeset and calculate header bloom filter for receipt.
|
||||
receipts.push(Some(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,
|
||||
}));
|
||||
receipts.push(Some(self.assemble_receipt(&tx, result, cumulative_gas_used)));
|
||||
|
||||
// append transaction to the list of executed transactions
|
||||
let (tx, sender) = tx.to_components();
|
||||
@ -229,18 +367,7 @@ impl PendingBlockEnv {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "optimism")]
|
||||
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 receipts_root = self.receipts_root(&block_env, &execution_outcome, block_number);
|
||||
|
||||
let logs_bloom =
|
||||
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())?;
|
||||
|
||||
// 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
|
||||
let blob_gas_used =
|
||||
@ -294,137 +421,3 @@ impl PendingBlockEnv {
|
||||
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
|
||||
|
||||
use crate::{
|
||||
eth::logs_utils,
|
||||
result::{internal_rpc_err, invalid_params_rpc_err},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::StreamExt;
|
||||
use jsonrpsee::{
|
||||
server::SubscriptionMessage, types::ErrorObject, PendingSubscriptionSink, SubscriptionSink,
|
||||
@ -11,7 +9,6 @@ use jsonrpsee::{
|
||||
use reth_network_api::NetworkInfo;
|
||||
use reth_primitives::{IntoRecoveredTransaction, TxHash};
|
||||
use reth_provider::{BlockReader, CanonStateSubscriptions, EvmEnvProvider};
|
||||
use reth_rpc_api::EthPubSubApiServer;
|
||||
use reth_rpc_types::{
|
||||
pubsub::{
|
||||
Params, PubSubSyncStatus, SubscriptionKind, SubscriptionResult as EthSubscriptionResult,
|
||||
@ -22,12 +19,17 @@ use reth_rpc_types::{
|
||||
use reth_tasks::{TaskSpawner, TokioTaskExecutor};
|
||||
use reth_transaction_pool::{NewTransactionEvent, TransactionPool};
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
use tokio_stream::{
|
||||
wrappers::{BroadcastStream, ReceiverStream},
|
||||
Stream,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
logs_utils,
|
||||
result::{internal_rpc_err, invalid_params_rpc_err},
|
||||
EthPubSubApiServer,
|
||||
};
|
||||
|
||||
/// `Eth` pubsub RPC implementation.
|
||||
///
|
||||
/// This handles `eth_subscribe` RPC calls.
|
||||
@ -197,10 +199,10 @@ where
|
||||
/// Helper to convert a serde error into an [`ErrorObject`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Failed to serialize subscription item: {0}")]
|
||||
pub(crate) struct SubscriptionSerializeError(#[from] serde_json::Error);
|
||||
pub struct SubscriptionSerializeError(#[from] serde_json::Error);
|
||||
|
||||
impl SubscriptionSerializeError {
|
||||
pub(crate) const fn new(err: serde_json::Error) -> Self {
|
||||
const fn new(err: serde_json::Error) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
@ -1,49 +1,37 @@
|
||||
//! Implementation of the [`jsonrpsee`] generated [`reth_rpc_api::EthApiServer`] trait
|
||||
//! Handles RPC requests for the `eth_` namespace.
|
||||
//! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for
|
||||
//! 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 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_provider::{
|
||||
BlockIdReader, BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider,
|
||||
HeaderProvider, StateProviderFactory,
|
||||
};
|
||||
use reth_rpc_api::EthApiServer;
|
||||
use reth_rpc_types::{
|
||||
serde_helpers::JsonStorageKey,
|
||||
state::{EvmOverrides, StateOverride},
|
||||
AccessListWithGasUsed, AnyTransactionReceipt, BlockOverrides, Bundle,
|
||||
EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Header, Index, RichBlock,
|
||||
StateContext, SyncStatus, TransactionRequest, Work,
|
||||
StateContext, SyncStatus, Transaction, TransactionRequest, Work,
|
||||
};
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
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]
|
||||
impl<Provider, Pool, Network, EvmConfig> EthApiServer for EthApi<Provider, Pool, Network, EvmConfig>
|
||||
impl<T> EthApiServer for T
|
||||
where
|
||||
Self: EthApiSpec + EthTransactions,
|
||||
Pool: TransactionPool + 'static,
|
||||
Provider: BlockReader
|
||||
+ BlockIdReader
|
||||
+ BlockReaderIdExt
|
||||
+ ChainSpecProvider
|
||||
+ HeaderProvider
|
||||
+ StateProviderFactory
|
||||
+ EvmEnvProvider
|
||||
+ 'static,
|
||||
Network: NetworkInfo + Send + Sync + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
Self: EthApiSpec
|
||||
+ EthTransactions
|
||||
+ EthBlocks
|
||||
+ EthState
|
||||
+ EthCall
|
||||
+ EthFees
|
||||
+ Trace
|
||||
+ LoadReceipt,
|
||||
{
|
||||
/// Handler for: `eth_protocolVersion`
|
||||
async fn protocol_version(&self) -> Result<U64> {
|
||||
@ -85,7 +73,7 @@ where
|
||||
/// Handler for: `eth_getBlockByHash`
|
||||
async fn block_by_hash(&self, hash: B256, full: bool) -> Result<Option<RichBlock>> {
|
||||
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`
|
||||
@ -95,13 +83,13 @@ where
|
||||
full: bool,
|
||||
) -> Result<Option<RichBlock>> {
|
||||
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`
|
||||
async fn block_transaction_count_by_hash(&self, hash: B256) -> Result<Option<U256>> {
|
||||
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`
|
||||
@ -110,19 +98,19 @@ where
|
||||
number: BlockNumberOrTag,
|
||||
) -> Result<Option<U256>> {
|
||||
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`
|
||||
async fn block_uncles_count_by_hash(&self, hash: B256) -> Result<Option<U256>> {
|
||||
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`
|
||||
async fn block_uncles_count_by_number(&self, number: BlockNumberOrTag) -> Result<Option<U256>> {
|
||||
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`
|
||||
@ -131,7 +119,7 @@ where
|
||||
block_id: BlockId,
|
||||
) -> Result<Option<Vec<AnyTransactionReceipt>>> {
|
||||
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`
|
||||
@ -141,7 +129,7 @@ where
|
||||
index: Index,
|
||||
) -> Result<Option<RichBlock>> {
|
||||
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`
|
||||
@ -151,7 +139,7 @@ where
|
||||
index: Index,
|
||||
) -> Result<Option<RichBlock>> {
|
||||
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`
|
||||
@ -161,7 +149,7 @@ where
|
||||
}
|
||||
|
||||
/// 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");
|
||||
Ok(EthTransactions::transaction_by_hash(self, hash).await?.map(Into::into))
|
||||
}
|
||||
@ -173,7 +161,7 @@ where
|
||||
index: Index,
|
||||
) -> Result<Option<Bytes>> {
|
||||
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`
|
||||
@ -183,7 +171,7 @@ where
|
||||
index: Index,
|
||||
) -> Result<Option<reth_rpc_types::Transaction>> {
|
||||
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`
|
||||
@ -193,7 +181,8 @@ where
|
||||
index: Index,
|
||||
) -> Result<Option<Bytes>> {
|
||||
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`
|
||||
@ -203,7 +192,7 @@ where
|
||||
index: Index,
|
||||
) -> Result<Option<reth_rpc_types::Transaction>> {
|
||||
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`
|
||||
@ -215,7 +204,7 @@ where
|
||||
/// Handler for: `eth_getBalance`
|
||||
async fn balance(&self, address: Address, block_number: Option<BlockId>) -> Result<U256> {
|
||||
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`
|
||||
@ -226,9 +215,8 @@ where
|
||||
block_number: Option<BlockId>,
|
||||
) -> Result<B256> {
|
||||
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getStorageAt");
|
||||
Ok(self
|
||||
.on_blocking_task(|this| async move { this.storage_at(address, index, block_number) })
|
||||
.await?)
|
||||
let res: B256 = EthState::storage_at(self, address, index, block_number).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Handler for: `eth_getTransactionCount`
|
||||
@ -238,31 +226,25 @@ where
|
||||
block_number: Option<BlockId>,
|
||||
) -> Result<U256> {
|
||||
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getTransactionCount");
|
||||
Ok(self
|
||||
.on_blocking_task(
|
||||
|this| async move { this.get_transaction_count(address, block_number) },
|
||||
)
|
||||
.await?)
|
||||
Ok(EthState::transaction_count(self, address, block_number).await?)
|
||||
}
|
||||
|
||||
/// Handler for: `eth_getCode`
|
||||
async fn get_code(&self, address: Address, block_number: Option<BlockId>) -> Result<Bytes> {
|
||||
trace!(target: "rpc::eth", ?address, ?block_number, "Serving eth_getCode");
|
||||
Ok(self
|
||||
.on_blocking_task(|this| async move { this.get_code(address, block_number) })
|
||||
.await?)
|
||||
Ok(EthState::get_code(self, address, block_number).await?)
|
||||
}
|
||||
|
||||
/// Handler for: `eth_getHeaderByNumber`
|
||||
async fn header_by_number(&self, block_number: BlockNumberOrTag) -> Result<Option<Header>> {
|
||||
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`
|
||||
async fn header_by_hash(&self, hash: B256) -> Result<Option<Header>> {
|
||||
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`
|
||||
@ -274,8 +256,12 @@ where
|
||||
block_overrides: Option<Box<BlockOverrides>>,
|
||||
) -> Result<Bytes> {
|
||||
trace!(target: "rpc::eth", ?request, ?block_number, ?state_overrides, ?block_overrides, "Serving eth_call");
|
||||
Ok(self
|
||||
.call(request, block_number, EvmOverrides::new(state_overrides, block_overrides))
|
||||
Ok(EthCall::call(
|
||||
self,
|
||||
request,
|
||||
block_number,
|
||||
EvmOverrides::new(state_overrides, block_overrides),
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@ -287,7 +273,7 @@ where
|
||||
state_override: Option<StateOverride>,
|
||||
) -> Result<Vec<EthCallResponse>> {
|
||||
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`
|
||||
@ -297,7 +283,8 @@ where
|
||||
block_number: Option<BlockId>,
|
||||
) -> Result<AccessListWithGasUsed> {
|
||||
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)
|
||||
}
|
||||
@ -310,25 +297,31 @@ where
|
||||
state_override: Option<StateOverride>,
|
||||
) -> Result<U256> {
|
||||
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`
|
||||
async fn gas_price(&self) -> Result<U256> {
|
||||
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`
|
||||
async fn max_priority_fee_per_gas(&self) -> Result<U256> {
|
||||
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`
|
||||
async fn blob_base_fee(&self) -> Result<U256> {
|
||||
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
|
||||
@ -347,7 +340,9 @@ where
|
||||
reward_percentiles: Option<Vec<f64>>,
|
||||
) -> Result<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`
|
||||
@ -390,7 +385,7 @@ where
|
||||
/// Handler for: `eth_sign`
|
||||
async fn sign(&self, address: Address, message: Bytes) -> Result<Bytes> {
|
||||
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`
|
||||
@ -401,7 +396,7 @@ where
|
||||
/// Handler for: `eth_signTypedData`
|
||||
async fn sign_typed_data(&self, address: Address, data: TypedData) -> Result<Bytes> {
|
||||
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`
|
||||
@ -412,7 +407,7 @@ where
|
||||
block_number: Option<BlockId>,
|
||||
) -> Result<EIP1186AccountProofResponse> {
|
||||
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 {
|
||||
EthApiError::InvalidBlockRange => {
|
||||
@ -425,13 +420,6 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
eth::{
|
||||
cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache,
|
||||
FeeHistoryCacheConfig,
|
||||
},
|
||||
EthApi,
|
||||
};
|
||||
use jsonrpsee::types::error::INVALID_PARAMS_CODE;
|
||||
use reth_chainspec::BaseFeeParams;
|
||||
use reth_evm_ethereum::EthEvmConfig;
|
||||
@ -444,12 +432,15 @@ mod tests {
|
||||
test_utils::{MockEthProvider, NoopProvider},
|
||||
BlockReader, BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderFactory,
|
||||
};
|
||||
use reth_rpc_api::EthApiServer;
|
||||
use reth_rpc_types::FeeHistory;
|
||||
use reth_tasks::pool::BlockingTaskPool;
|
||||
use reth_testing_utils::{generators, generators::Rng};
|
||||
use reth_transaction_pool::test_utils::{testing_pool, TestPool};
|
||||
|
||||
use crate::{
|
||||
EthApi, EthApiServer, EthStateCache, FeeHistoryCache, FeeHistoryCacheConfig, GasPriceOracle,
|
||||
};
|
||||
|
||||
fn build_test_eth_api<
|
||||
P: BlockReaderIdExt
|
||||
+ BlockReader
|
||||
@ -661,7 +652,8 @@ mod tests {
|
||||
let (eth_api, base_fees_per_gas, gas_used_ratios) =
|
||||
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!(
|
||||
fee_history.base_fee_per_gas,
|
||||
&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());
|
||||
|
||||
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!(
|
||||
&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 serde::{Deserialize, Serialize};
|
||||
|
||||
/// Settings for the [`EthStateCache`](crate::eth::cache::EthStateCache).
|
||||
/// Settings for the [`EthStateCache`](crate::EthStateCache).
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
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 reth_metrics::{metrics::Gauge, Metrics};
|
||||
|
||||
@ -26,13 +26,12 @@ use tokio::sync::{
|
||||
};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
mod config;
|
||||
pub use config::*;
|
||||
use crate::{EthStateCacheConfig, MultiConsumerLruCache};
|
||||
|
||||
mod metrics;
|
||||
|
||||
mod multi_consumer;
|
||||
pub use multi_consumer::MultiConsumerLruCache;
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
pub mod metrics;
|
||||
pub mod multi_consumer;
|
||||
|
||||
/// The type that can send the response to a requested [Block]
|
||||
type BlockTransactionsResponseSender =
|
||||
@ -107,7 +106,7 @@ impl EthStateCache {
|
||||
) -> Self
|
||||
where
|
||||
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
Self::spawn_with(provider, config, TokioTaskExecutor::default(), evm_config)
|
||||
}
|
||||
@ -125,7 +124,7 @@ impl EthStateCache {
|
||||
where
|
||||
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
let EthStateCacheConfig { max_blocks, max_receipts, max_envs, max_concurrent_db_requests } =
|
||||
config;
|
||||
@ -316,7 +315,7 @@ impl<Provider, Tasks, EvmConfig> EthStateCacheService<Provider, Tasks, EvmConfig
|
||||
where
|
||||
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
fn on_new_block(&mut self, block_hash: B256, res: ProviderResult<Option<BlockWithSenders>>) {
|
||||
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
|
||||
Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static,
|
||||
Tasks: TaskSpawner + Clone + 'static,
|
||||
EvmConfig: ConfigureEvm + 'static,
|
||||
EvmConfig: ConfigureEvm,
|
||||
{
|
||||
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 schnellru::{ByLength, Limiter, LruMap};
|
||||
use std::{
|
||||
@ -12,9 +15,9 @@ where
|
||||
K: Hash + Eq,
|
||||
L: Limiter<K, V>,
|
||||
{
|
||||
/// The LRU cache for the
|
||||
/// The LRU cache.
|
||||
cache: LruMap<K, V, L>,
|
||||
/// All queued consumers
|
||||
/// All queued consumers.
|
||||
queued: HashMap<K, Vec<S>>,
|
||||
/// Cache metrics
|
||||
metrics: CacheMetrics,
|
||||
@ -1,6 +1,11 @@
|
||||
//! 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::{
|
||||
future::{Fuse, FusedFuture},
|
||||
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_types::TxGasAndReward;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
fmt::Debug,
|
||||
sync::{atomic::Ordering::SeqCst, Arc},
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{EthApiError, EthStateCache};
|
||||
|
||||
/// Contains cached fee history entries for blocks.
|
||||
///
|
||||
/// 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
|
||||
//! previous blocks.
|
||||
|
||||
use crate::eth::{
|
||||
cache::EthStateCache,
|
||||
error::{EthApiError, EthResult, RpcInvalidTransactionError},
|
||||
};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, B256, U256};
|
||||
use reth_provider::BlockReaderIdExt;
|
||||
use reth_rpc_server_types::constants::gas_oracle::*;
|
||||
use schnellru::{ByLength, LruMap};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use tokio::sync::Mutex;
|
||||
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`]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -73,7 +86,7 @@ pub struct GasPriceOracle<Provider> {
|
||||
|
||||
impl<Provider> GasPriceOracle<Provider>
|
||||
where
|
||||
Provider: BlockReaderIdExt + 'static,
|
||||
Provider: BlockReaderIdExt,
|
||||
{
|
||||
/// Creates and returns the [`GasPriceOracle`].
|
||||
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)]
|
||||
mod tests {
|
||||
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 jsonrpsee::types::SubscriptionId;
|
||||
|
||||
/// 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
|
||||
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;
|
||||
use alloy_primitives::TxHash;
|
||||
//! Helper functions for [`EthFilterApiServer`](crate::EthFilterApiServer) implementation.
|
||||
//!
|
||||
//! Log parsing for building filter.
|
||||
|
||||
use reth_chainspec::ChainInfo;
|
||||
use reth_primitives::{BlockNumHash, Receipt};
|
||||
use reth_primitives::{BlockNumHash, Receipt, TxHash};
|
||||
use reth_provider::{BlockReader, ProviderError};
|
||||
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.
|
||||
pub(crate) fn matching_block_logs_with_tx_hashes<'a, I>(
|
||||
filter: &FilteredParams,
|
||||
@ -51,7 +55,7 @@ pub(crate) fn append_matching_block_logs(
|
||||
receipts: &[Receipt],
|
||||
removed: bool,
|
||||
block_timestamp: u64,
|
||||
) -> Result<(), FilterError> {
|
||||
) -> Result<(), EthFilterError> {
|
||||
// Tracks the index of a log in the entire block.
|
||||
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.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use jsonrpsee::core::RpcResult;
|
||||
use reth_rpc_types::engine::PayloadError;
|
||||
use std::fmt::Display;
|
||||
|
||||
/// Helper trait to easily convert various `Result` types into [`RpcResult`]
|
||||
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);
|
||||
|
||||
/// Constructs an invalid params JSON-RPC error.
|
||||
pub(crate) fn invalid_params_rpc_err(
|
||||
pub fn invalid_params_rpc_err(
|
||||
msg: impl Into<String>,
|
||||
) -> jsonrpsee::types::error::ErrorObject<'static> {
|
||||
rpc_err(jsonrpsee::types::error::INVALID_PARAMS_CODE, msg, None)
|
||||
}
|
||||
|
||||
/// Constructs an internal JSON-RPC error.
|
||||
pub(crate) fn internal_rpc_err(
|
||||
msg: impl Into<String>,
|
||||
) -> jsonrpsee::types::error::ErrorObject<'static> {
|
||||
pub fn internal_rpc_err(msg: impl Into<String>) -> jsonrpsee::types::error::ErrorObject<'static> {
|
||||
rpc_err(jsonrpsee::types::error::INTERNAL_ERROR_CODE, msg, None)
|
||||
}
|
||||
|
||||
/// Constructs an internal JSON-RPC error with data
|
||||
pub(crate) fn internal_rpc_err_with_data(
|
||||
pub fn internal_rpc_err_with_data(
|
||||
msg: impl Into<String>,
|
||||
data: &[u8],
|
||||
) -> 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
|
||||
pub(crate) fn rpc_error_with_code(
|
||||
pub fn rpc_error_with_code(
|
||||
code: i32,
|
||||
msg: impl Into<String>,
|
||||
) -> 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`.
|
||||
pub(crate) fn rpc_err(
|
||||
pub fn rpc_err(
|
||||
code: i32,
|
||||
msg: impl Into<String>,
|
||||
data: Option<&[u8]>,
|
||||
@ -1,14 +1,8 @@
|
||||
//! utilities for working with revm
|
||||
|
||||
use crate::eth::error::{EthApiError, EthResult, RpcInvalidTransactionError};
|
||||
#[cfg(feature = "optimism")]
|
||||
use reth_primitives::revm::env::fill_op_tx_env;
|
||||
#[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 std::cmp::min;
|
||||
|
||||
use reth_primitives::{Address, TxKind, B256, U256};
|
||||
use reth_rpc_types::{
|
||||
state::{AccountOverride, EvmOverrides, StateOverride},
|
||||
BlockOverrides, TransactionRequest,
|
||||
@ -23,58 +17,9 @@ use revm::{
|
||||
},
|
||||
Database,
|
||||
};
|
||||
use std::cmp::min;
|
||||
use tracing::trace;
|
||||
|
||||
/// Helper type to work with different transaction types when configuring the EVM env.
|
||||
///
|
||||
/// 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(())
|
||||
}
|
||||
}
|
||||
use crate::{EthApiError, EthResult, RpcInvalidTransactionError};
|
||||
|
||||
/// Returns the addresses of the precompiles corresponding to the `SpecId`.
|
||||
#[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
|
||||
|
||||
use crate::eth::error::{EthApiError, EthResult};
|
||||
use reth_primitives::{Bytes, PooledTransactionsElement, PooledTransactionsElementEcRecovered};
|
||||
|
||||
use crate::{EthApiError, EthResult};
|
||||
|
||||
/// Recovers a [`PooledTransactionsElementEcRecovered`] from an enveloped encoded byte stream.
|
||||
///
|
||||
/// See [`PooledTransactionsElement::decode_enveloped`]
|
||||
pub(crate) fn recover_raw_transaction(
|
||||
data: Bytes,
|
||||
) -> EthResult<PooledTransactionsElementEcRecovered> {
|
||||
pub fn recover_raw_transaction(data: Bytes) -> EthResult<PooledTransactionsElementEcRecovered> {
|
||||
if data.is_empty() {
|
||||
return Err(EthApiError::EmptyRawTransactionData)
|
||||
}
|
||||
@ -29,3 +29,4 @@ similar-asserts.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] }
|
||||
reth-rpc-eth-api.workspace = true
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use futures::StreamExt;
|
||||
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_eth_api::EthApiClient;
|
||||
use reth_rpc_types::trace::{
|
||||
filter::TraceFilter, parity::TraceType, tracerequest::TraceCallRequest,
|
||||
};
|
||||
|
||||
@ -16,7 +16,7 @@ workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
reth-primitives.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-errors.workspace = true
|
||||
reth-provider = { workspace = true, features = ["test-utils"] }
|
||||
@ -28,17 +28,11 @@ reth-tasks = { workspace = true, features = ["rayon"] }
|
||||
reth-consensus-common.workspace = true
|
||||
reth-rpc-types-compat.workspace = true
|
||||
revm-inspectors = { workspace = true, features = ["js-tracer"] }
|
||||
reth-evm.workspace = true
|
||||
reth-network-peers.workspace = true
|
||||
reth-execution-types.workspace = true
|
||||
|
||||
reth-evm-optimism = { workspace = true, optional = true }
|
||||
|
||||
# eth
|
||||
alloy-rlp.workspace = true
|
||||
alloy-dyn-abi = { workspace = true, features = ["eip712"] }
|
||||
alloy-primitives.workspace = true
|
||||
alloy-sol-types.workspace = true
|
||||
alloy-genesis.workspace = true
|
||||
revm = { workspace = true, features = [
|
||||
"optional_block_gas_limit",
|
||||
@ -58,30 +52,12 @@ jsonwebtoken.workspace = true
|
||||
async-trait.workspace = true
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tower.workspace = true
|
||||
tokio-stream = { workspace = true, features = ["sync"] }
|
||||
pin-project.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
# metrics
|
||||
reth-metrics.workspace = true
|
||||
metrics.workspace = true
|
||||
|
||||
# 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-futures = "0.2"
|
||||
schnellru.workspace = true
|
||||
futures.workspace = true
|
||||
derive_more.workspace = true
|
||||
dyn-clone.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
reth-evm-ethereum.workspace = true
|
||||
@ -96,7 +72,7 @@ optimism = [
|
||||
"reth-primitives/optimism",
|
||||
"reth-rpc-types-compat/optimism",
|
||||
"reth-provider/optimism",
|
||||
"dep:reth-evm-optimism",
|
||||
"reth-evm-optimism/optimism",
|
||||
"reth-rpc-api/optimism",
|
||||
"reth-rpc-eth-api/optimism",
|
||||
"reth-revm/optimism",
|
||||
]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::result::ToRpcResult;
|
||||
use std::sync::Arc;
|
||||
|
||||
use alloy_genesis::ChainConfig;
|
||||
use alloy_primitives::B256;
|
||||
use async_trait::async_trait;
|
||||
@ -11,7 +12,8 @@ use reth_rpc_types::{
|
||||
admin::{EthProtocolInfo, NodeInfo, Ports, ProtocolInfo},
|
||||
PeerEthProtocolInfo, PeerInfo, PeerNetworkInfo, PeerProtocolsInfo,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::result::ToRpcResult;
|
||||
|
||||
/// `admin` API implementation.
|
||||
///
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
use crate::{
|
||||
eth::{
|
||||
error::{EthApiError, EthResult},
|
||||
revm_utils::prepare_call_env,
|
||||
EthTransactions,
|
||||
},
|
||||
result::{internal_rpc_err, ToRpcResult},
|
||||
EthApiSpec,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use alloy_rlp::{Decodable, Encodable};
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee::core::RpcResult;
|
||||
@ -15,10 +8,17 @@ use reth_primitives::{
|
||||
TransactionSignedEcRecovered, Withdrawals, B256, U256,
|
||||
};
|
||||
use reth_provider::{
|
||||
BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderBox, TransactionVariant,
|
||||
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory,
|
||||
TransactionVariant,
|
||||
};
|
||||
use reth_revm::database::StateProviderDatabase;
|
||||
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::{
|
||||
state::EvmOverrides,
|
||||
trace::geth::{
|
||||
@ -36,7 +36,6 @@ use revm_inspectors::tracing::{
|
||||
js::{JsInspector, TransactionContext},
|
||||
FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{AcquireError, OwnedSemaphorePermit};
|
||||
|
||||
/// `debug` API implementation.
|
||||
@ -65,8 +64,13 @@ impl<Provider, Eth> DebugApi<Provider, Eth> {
|
||||
|
||||
impl<Provider, Eth> DebugApi<Provider, Eth>
|
||||
where
|
||||
Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static,
|
||||
Eth: EthTransactions + 'static,
|
||||
Provider: BlockReaderIdExt
|
||||
+ HeaderProvider
|
||||
+ ChainSpecProvider
|
||||
+ StateProviderFactory
|
||||
+ EvmEnvProvider
|
||||
+ 'static,
|
||||
Eth: TraceExt + 'static,
|
||||
{
|
||||
/// Acquires a permit to execute a tracing call.
|
||||
async fn acquire_trace_permit(&self) -> Result<OwnedSemaphorePermit, AcquireError> {
|
||||
@ -74,7 +78,7 @@ where
|
||||
}
|
||||
|
||||
/// Trace the entire block asynchronously
|
||||
async fn trace_block_with(
|
||||
async fn trace_block(
|
||||
&self,
|
||||
at: BlockId,
|
||||
transactions: Vec<TransactionSignedEcRecovered>,
|
||||
@ -165,7 +169,7 @@ where
|
||||
.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.
|
||||
@ -182,7 +186,7 @@ where
|
||||
|
||||
let ((cfg, block_env, _), block) = futures::try_join!(
|
||||
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)?;
|
||||
@ -190,7 +194,7 @@ where
|
||||
// its parent block's state
|
||||
let state_at = block.parent_hash;
|
||||
|
||||
self.trace_block_with(
|
||||
self.trace_block(
|
||||
state_at.into(),
|
||||
block.into_transactions_ecrecovered().collect(),
|
||||
cfg,
|
||||
@ -324,6 +328,10 @@ where
|
||||
self.inner
|
||||
.eth_api
|
||||
.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, _) =
|
||||
this.eth_api().inspect(&mut *db, env, &mut inspector)?;
|
||||
let frame = inspector
|
||||
@ -346,6 +354,10 @@ where
|
||||
.inner
|
||||
.eth_api
|
||||
.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, _) =
|
||||
this.eth_api().inspect(&mut *db, env, &mut inspector)?;
|
||||
let frame = inspector.try_into_mux_frame(&res, db)?;
|
||||
@ -364,6 +376,10 @@ where
|
||||
.inner
|
||||
.eth_api
|
||||
.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 (res, _) =
|
||||
this.eth_api().inspect(&mut *db, env.clone(), &mut inspector)?;
|
||||
@ -415,7 +431,7 @@ where
|
||||
let target_block = block_number.unwrap_or_default();
|
||||
let ((cfg, mut block_env, _), block) = futures::try_join!(
|
||||
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();
|
||||
@ -518,7 +534,7 @@ where
|
||||
&self,
|
||||
opts: GethDebugTracingOptions,
|
||||
env: EnvWithHandlerCfg,
|
||||
db: &mut CacheDB<StateProviderDatabase<StateProviderBox>>,
|
||||
db: &mut StateCacheDb<'_>,
|
||||
transaction_context: Option<TransactionContext>,
|
||||
) -> EthResult<(GethTrace, revm_primitives::EvmState)> {
|
||||
let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts;
|
||||
@ -614,8 +630,13 @@ where
|
||||
#[async_trait]
|
||||
impl<Provider, Eth> DebugApiServer for DebugApi<Provider, Eth>
|
||||
where
|
||||
Provider: BlockReaderIdExt + HeaderProvider + ChainSpecProvider + 'static,
|
||||
Eth: EthApiSpec + 'static,
|
||||
Provider: BlockReaderIdExt
|
||||
+ HeaderProvider
|
||||
+ ChainSpecProvider
|
||||
+ StateProviderFactory
|
||||
+ EvmEnvProvider
|
||||
+ 'static,
|
||||
Eth: EthApiSpec + EthTransactions + TraceExt + 'static,
|
||||
{
|
||||
/// Handler for `debug_getRawHeader`
|
||||
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.
|
||||
//!
|
||||
//! 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
|
||||
//! available jsonrpsee `blocking` attribute to give implementers more freedom because the
|
||||
//! `blocking` attribute and async handlers are mutually exclusive. However, as mentioned above, a
|
||||
//! lot of handlers make use of async functions, caching for example, but are also using blocking
|
||||
//! disk-io, hence these calls are spawned as futures to a blocking task manually.
|
||||
//! the [`EthApi`](reth_rpc_eth_api::EthApi) handler implementations for examples. The rpc-api
|
||||
//! traits make no use of the available jsonrpsee `blocking` attribute to give implementers more
|
||||
//! freedom because the `blocking` attribute and async handlers are mutually exclusive. However, as
|
||||
//! mentioned above, a lot of handlers make use of async functions, caching for example, but are
|
||||
//! also using blocking disk-io, hence these calls are spawned as futures to a blocking task
|
||||
//! manually.
|
||||
|
||||
#![doc(
|
||||
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
|
||||
@ -35,7 +36,6 @@ use tower as _;
|
||||
mod admin;
|
||||
mod debug;
|
||||
mod engine;
|
||||
pub mod eth;
|
||||
mod net;
|
||||
mod otterscan;
|
||||
mod reth;
|
||||
@ -46,7 +46,6 @@ mod web3;
|
||||
pub use admin::AdminApi;
|
||||
pub use debug::DebugApi;
|
||||
pub use engine::{EngineApi, EngineEthApi};
|
||||
pub use eth::{EthApi, EthApiSpec, EthFilter, EthPubSub, EthSubscriptionIdProvider};
|
||||
pub use net::NetApi;
|
||||
pub use otterscan::OtterscanApi;
|
||||
pub use reth::RethApi;
|
||||
@ -54,4 +53,6 @@ pub use rpc::RPCApi;
|
||||
pub use trace::TraceApi;
|
||||
pub use txpool::TxPoolApi;
|
||||
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 reth_network_api::PeersInfo;
|
||||
use reth_primitives::U64;
|
||||
use reth_rpc_api::NetApiServer;
|
||||
use reth_rpc_eth_api::servers::EthApiSpec;
|
||||
use reth_rpc_types::PeerCount;
|
||||
|
||||
/// `Net` API implementation.
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
use alloy_primitives::Bytes;
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee::core::RpcResult;
|
||||
use revm_inspectors::transfer::{TransferInspector, TransferKind};
|
||||
use revm_primitives::ExecutionResult;
|
||||
|
||||
use reth_primitives::{Address, BlockId, BlockNumberOrTag, TxHash, B256};
|
||||
use reth_rpc_api::{EthApiServer, OtterscanServer};
|
||||
use reth_rpc_eth_api::{result::internal_rpc_err, servers::TraceExt};
|
||||
use reth_rpc_types::{
|
||||
trace::otterscan::{
|
||||
BlockDetails, ContractCreator, InternalOperation, OperationType, OtsBlockTransactions,
|
||||
@ -13,8 +11,8 @@ use reth_rpc_types::{
|
||||
},
|
||||
BlockTransactions, Transaction,
|
||||
};
|
||||
|
||||
use crate::{eth::EthTransactions, result::internal_rpc_err};
|
||||
use revm_inspectors::transfer::{TransferInspector, TransferKind};
|
||||
use revm_primitives::ExecutionResult;
|
||||
|
||||
const API_LEVEL: u64 = 8;
|
||||
|
||||
@ -34,7 +32,7 @@ impl<Eth> OtterscanApi<Eth> {
|
||||
#[async_trait]
|
||||
impl<Eth> OtterscanServer for OtterscanApi<Eth>
|
||||
where
|
||||
Eth: EthApiServer + EthTransactions,
|
||||
Eth: EthApiServer + TraceExt + 'static,
|
||||
{
|
||||
/// Handler for `ots_hasCode`
|
||||
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 jsonrpsee::core::RpcResult;
|
||||
use reth_errors::RethResult;
|
||||
use reth_primitives::{Address, BlockId, U256};
|
||||
use reth_provider::{BlockReaderIdExt, ChangeSetReader, StateProviderFactory};
|
||||
use reth_rpc_api::RethApiServer;
|
||||
use reth_rpc_eth_api::{EthApiError, EthResult};
|
||||
use reth_tasks::TaskSpawner;
|
||||
use std::{collections::HashMap, future::Future, sync::Arc};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
/// `reth` API implementation.
|
||||
|
||||
@ -1,9 +1,5 @@
|
||||
use crate::eth::{
|
||||
error::{EthApiError, EthResult},
|
||||
revm_utils::prepare_call_env,
|
||||
utils::recover_raw_transaction,
|
||||
EthTransactions,
|
||||
};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee::core::RpcResult as Result;
|
||||
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_revm::database::StateProviderDatabase;
|
||||
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::{
|
||||
state::{EvmOverrides, StateOverride},
|
||||
trace::{
|
||||
@ -32,7 +34,6 @@ use revm_inspectors::{
|
||||
opcode::OpcodeGasInspector,
|
||||
tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig},
|
||||
};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
use tokio::sync::{AcquireError, OwnedSemaphorePermit};
|
||||
|
||||
/// `trace` API implementation.
|
||||
@ -74,7 +75,7 @@ impl<Provider, Eth> TraceApi<Provider, Eth> {
|
||||
impl<Provider, Eth> TraceApi<Provider, Eth>
|
||||
where
|
||||
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.
|
||||
pub async fn trace_call(&self, trace_request: TraceCallRequest) -> EthResult<TraceResults> {
|
||||
@ -86,6 +87,10 @@ where
|
||||
let this = self.clone();
|
||||
self.eth_api()
|
||||
.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 trace_res = inspector.into_parity_builder().into_trace_results_with_state(
|
||||
&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 mut maybe_traces =
|
||||
@ -455,7 +460,7 @@ where
|
||||
let res = self
|
||||
.inner
|
||||
.eth_api
|
||||
.trace_block_with_inspector(
|
||||
.trace_block_inspector(
|
||||
block_id,
|
||||
OpcodeGasInspector::default,
|
||||
move |tx_info, inspector, _res, _, _| {
|
||||
@ -470,7 +475,7 @@ where
|
||||
|
||||
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 {
|
||||
block_hash: block.hash(),
|
||||
@ -548,7 +553,7 @@ where
|
||||
impl<Provider, Eth> TraceApiServer for TraceApi<Provider, Eth>
|
||||
where
|
||||
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.
|
||||
///
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
use crate::result::ToRpcResult;
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee::core::RpcResult;
|
||||
use reth_network_api::NetworkInfo;
|
||||
use reth_primitives::{keccak256, Bytes, B256};
|
||||
use reth_rpc_api::Web3ApiServer;
|
||||
|
||||
use crate::result::ToRpcResult;
|
||||
|
||||
/// `web3` API implementation.
|
||||
///
|
||||
/// This type provides the functionality for handling `web3` related requests.
|
||||
|
||||
@ -71,4 +71,4 @@ arbitrary = [
|
||||
"dep:arbitrary",
|
||||
"dep:proptest",
|
||||
]
|
||||
optimism = []
|
||||
optimism = ["reth-primitives/optimism"]
|
||||
|
||||
@ -314,10 +314,9 @@ mod tests {
|
||||
//
|
||||
// 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
|
||||
#[cfg(not(feature = "optimism"))]
|
||||
#[test]
|
||||
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!(CheckpointBlockRange::bitflag_encoded_bytes(), 1);
|
||||
@ -349,7 +348,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[cfg(feature = "optimism")]
|
||||
{
|
||||
#[test]
|
||||
fn test_ensure_backwards_compatibility() {
|
||||
assert_eq!(Account::bitflag_encoded_bytes(), 2);
|
||||
assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1);
|
||||
assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1);
|
||||
@ -379,5 +379,4 @@ mod tests {
|
||||
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}")]
|
||||
UnexpectedMissingValue(u64, u64),
|
||||
#[error(transparent)]
|
||||
FilterError(#[from] cuckoofilter::CuckooError),
|
||||
EthFilterError(#[from] cuckoofilter::CuckooError),
|
||||
#[error("nippy jar initialized without filter")]
|
||||
FilterMissing,
|
||||
#[error("filter has reached max capacity")]
|
||||
|
||||
@ -2,6 +2,7 @@ use reth_chainspec::ChainSpec;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A trait for reading the current chainspec.
|
||||
#[auto_impl::auto_impl(&, Arc)]
|
||||
pub trait ChainSpecProvider: Send + Sync {
|
||||
/// Get an [`Arc`] to the 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