From 933a1dea399d92e9aff07fd98162ba03d7749a09 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Thu, 27 Jun 2024 13:37:52 +0200 Subject: [PATCH] chore(rpc): `reth-eth-api` crate (#8887) Co-authored-by: Matthias Seitz --- Cargo.lock | 72 +- Cargo.toml | 2 + crates/e2e-test-utils/src/rpc.rs | 2 +- crates/ethereum/evm/src/execute.rs | 2 +- crates/ethereum/evm/src/lib.rs | 42 +- crates/ethereum/node/tests/e2e/dev.rs | 2 +- crates/evm/src/lib.rs | 38 +- crates/evm/src/provider.rs | 12 +- crates/node-core/Cargo.toml | 4 +- crates/node-core/src/args/gas_price_oracle.rs | 2 +- crates/node-core/src/args/rpc_server.rs | 2 +- crates/node-core/src/lib.rs | 4 +- crates/node/builder/src/rpc.rs | 5 +- crates/optimism/evm/src/execute.rs | 2 +- crates/optimism/evm/src/lib.rs | 2 +- crates/optimism/node/src/rpc.rs | 2 +- crates/rpc/rpc-api/Cargo.toml | 9 +- crates/rpc/rpc-api/src/debug.rs | 2 +- crates/rpc/rpc-api/src/lib.rs | 18 +- crates/rpc/rpc-builder/src/auth.rs | 2 +- crates/rpc/rpc-builder/src/config.rs | 2 +- crates/rpc/rpc-builder/src/eth.rs | 20 +- crates/rpc/rpc-builder/src/lib.rs | 19 +- crates/rpc/rpc-eth-api/Cargo.toml | 78 + .../src => rpc-eth-api/src/api}/bundle.rs | 2 +- .../src/api/filter.rs} | 3 + .../src/eth.rs => rpc-eth-api/src/api/mod.rs} | 7 + .../src/api/pubsub.rs} | 2 + .../src/api/servers}/bundle.rs | 29 +- .../src/api/servers}/filter.rs | 132 +- .../src/api/servers/helpers/block.rs | 35 + .../src/api/servers/helpers/call.rs | 29 + .../src/api/servers/helpers/fees.rs | 39 + .../src/api/servers/helpers/mod.rs | 19 + .../src/api/servers/helpers/optimism.rs | 231 ++ .../src/api/servers/helpers/pending_block.rs | 41 + .../src/api/servers/helpers/receipt.rs | 13 + .../src/api/servers/helpers}/signer.rs | 49 +- .../src/api/servers/helpers/spec.rs | 64 + .../src/api/servers/helpers/state.rs | 104 + .../src/api/servers/helpers/trace.rs | 19 + .../src/api/servers/helpers/traits/block.rs | 244 +++ .../servers/helpers/traits/blocking_task.rs | 55 + .../src/api/servers/helpers/traits/call.rs | 775 +++++++ .../src/api/servers/helpers/traits/fee.rs | 346 +++ .../src/api/servers/helpers/traits/mod.rs | 45 + .../servers/helpers/traits}/pending_block.rs | 399 ++-- .../src/api/servers/helpers/traits/receipt.rs | 37 + .../src/api/servers/helpers/traits/signer.rs | 40 + .../src/api/servers/helpers/traits/spec.rs | 31 + .../src/api/servers/helpers/traits/state.rs | 247 +++ .../src/api/servers/helpers/traits/trace.rs | 412 ++++ .../api/servers/helpers/traits/transaction.rs | 651 ++++++ .../src/api/servers/helpers/transaction.rs | 126 ++ crates/rpc/rpc-eth-api/src/api/servers/mod.rs | 323 +++ .../src/api/servers}/pubsub.rs | 18 +- .../src/api/servers}/server.rs | 158 +- .../eth => rpc-eth-api/src}/cache/config.rs | 4 +- crates/rpc/rpc-eth-api/src/cache/db.rs | 169 ++ .../eth => rpc-eth-api/src}/cache/metrics.rs | 2 + .../src/eth => rpc-eth-api/src}/cache/mod.rs | 19 +- .../src}/cache/multi_consumer.rs | 7 +- .../{rpc/src/eth => rpc-eth-api/src}/error.rs | 0 .../api => rpc-eth-api/src}/fee_history.rs | 14 +- .../src/eth => rpc-eth-api/src}/gas_oracle.rs | 47 +- .../eth => rpc-eth-api/src}/id_provider.rs | 7 +- crates/rpc/rpc-eth-api/src/lib.rs | 62 + .../src/eth => rpc-eth-api/src}/logs_utils.rs | 12 +- crates/rpc/rpc-eth-api/src/pending_block.rs | 162 ++ crates/rpc/rpc-eth-api/src/receipt.rs | 126 ++ crates/rpc/{rpc => rpc-eth-api}/src/result.rs | 15 +- .../src/eth => rpc-eth-api/src}/revm_utils.rs | 63 +- crates/rpc/rpc-eth-api/src/transaction.rs | 96 + .../{rpc/src/eth => rpc-eth-api/src}/utils.rs | 7 +- crates/rpc/rpc-testing-util/Cargo.toml | 1 + crates/rpc/rpc-testing-util/tests/it/trace.rs | 2 +- crates/rpc/rpc/Cargo.toml | 30 +- crates/rpc/rpc/src/admin.rs | 6 +- crates/rpc/rpc/src/debug.rs | 63 +- crates/rpc/rpc/src/eth/api/block.rs | 217 -- crates/rpc/rpc/src/eth/api/call.rs | 530 ----- crates/rpc/rpc/src/eth/api/fees.rs | 228 -- crates/rpc/rpc/src/eth/api/mod.rs | 503 ----- crates/rpc/rpc/src/eth/api/optimism.rs | 31 - crates/rpc/rpc/src/eth/api/sign.rs | 41 - crates/rpc/rpc/src/eth/api/state.rs | 178 -- crates/rpc/rpc/src/eth/api/transactions.rs | 1861 ----------------- crates/rpc/rpc/src/eth/mod.rs | 28 - crates/rpc/rpc/src/eth/optimism.rs | 32 - crates/rpc/rpc/src/eth/traits.rs | 13 - crates/rpc/rpc/src/lib.rs | 17 +- crates/rpc/rpc/src/net.rs | 2 +- crates/rpc/rpc/src/otterscan.rs | 10 +- crates/rpc/rpc/src/reth.rs | 5 +- crates/rpc/rpc/src/trace.rs | 29 +- crates/rpc/rpc/src/web3.rs | 3 +- crates/storage/db-api/Cargo.toml | 2 +- crates/storage/db-api/src/models/mod.rs | 125 +- crates/storage/nippy-jar/src/error.rs | 2 +- crates/storage/provider/src/traits/spec.rs | 1 + crates/storage/storage-api/src/receipts.rs | 1 + crates/tasks/Cargo.toml | 1 + crates/tasks/src/lib.rs | 2 + crates/tasks/src/pool.rs | 3 +- crates/transaction-pool/src/traits.rs | 4 +- examples/custom-dev-node/src/main.rs | 2 +- examples/custom-evm/src/main.rs | 24 +- examples/custom-inspector/src/main.rs | 2 +- examples/stateful-precompile/src/main.rs | 4 +- 109 files changed, 5427 insertions(+), 4458 deletions(-) create mode 100644 crates/rpc/rpc-eth-api/Cargo.toml rename crates/rpc/{rpc-api/src => rpc-eth-api/src/api}/bundle.rs (98%) rename crates/rpc/{rpc-api/src/eth_filter.rs => rpc-eth-api/src/api/filter.rs} (97%) rename crates/rpc/{rpc-api/src/eth.rs => rpc-eth-api/src/api/mod.rs} (99%) rename crates/rpc/{rpc-api/src/eth_pubsub.rs => rpc-eth-api/src/api/pubsub.rs} (92%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers}/bundle.rs (94%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers}/filter.rs (94%) create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers/helpers}/signer.rs (85%) create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs rename crates/rpc/{rpc/src/eth/api => rpc-eth-api/src/api/servers/helpers/traits}/pending_block.rs (53%) create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs create mode 100644 crates/rpc/rpc-eth-api/src/api/servers/mod.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src/api/servers}/pubsub.rs (98%) rename crates/rpc/{rpc/src/eth/api => rpc-eth-api/src/api/servers}/server.rs (85%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/config.rs (91%) create mode 100644 crates/rpc/rpc-eth-api/src/cache/db.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/metrics.rs (93%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/mod.rs (99%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/cache/multi_consumer.rs (94%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/error.rs (100%) rename crates/rpc/{rpc/src/eth/api => rpc-eth-api/src}/fee_history.rs (99%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/gas_oracle.rs (89%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/id_provider.rs (93%) create mode 100644 crates/rpc/rpc-eth-api/src/lib.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/logs_utils.rs (96%) create mode 100644 crates/rpc/rpc-eth-api/src/pending_block.rs create mode 100644 crates/rpc/rpc-eth-api/src/receipt.rs rename crates/rpc/{rpc => rpc-eth-api}/src/result.rs (95%) rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/revm_utils.rs (90%) create mode 100644 crates/rpc/rpc-eth-api/src/transaction.rs rename crates/rpc/{rpc/src/eth => rpc-eth-api/src}/utils.rs (79%) delete mode 100644 crates/rpc/rpc/src/eth/api/block.rs delete mode 100644 crates/rpc/rpc/src/eth/api/call.rs delete mode 100644 crates/rpc/rpc/src/eth/api/fees.rs delete mode 100644 crates/rpc/rpc/src/eth/api/mod.rs delete mode 100644 crates/rpc/rpc/src/eth/api/optimism.rs delete mode 100644 crates/rpc/rpc/src/eth/api/sign.rs delete mode 100644 crates/rpc/rpc/src/eth/api/state.rs delete mode 100644 crates/rpc/rpc/src/eth/api/transactions.rs delete mode 100644 crates/rpc/rpc/src/eth/mod.rs delete mode 100644 crates/rpc/rpc/src/eth/optimism.rs delete mode 100644 crates/rpc/rpc/src/eth/traits.rs diff --git a/Cargo.lock b/Cargo.lock index e8a8039233bb..4c2408d2ac58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 7cc284de43f8..441b69b2aa8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/crates/e2e-test-utils/src/rpc.rs b/crates/e2e-test-utils/src/rpc.rs index 09f161a91dc7..0c55e9c23c0d 100644 --- a/crates/e2e-test-utils/src/rpc.rs +++ b/crates/e2e-test-utils/src/rpc.rs @@ -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 { pub inner: RpcRegistry, diff --git a/crates/ethereum/evm/src/execute.rs b/crates/ethereum/evm/src/execute.rs index e4c89dd778dd..d4cc8be77d20 100644 --- a/crates/ethereum/evm/src/execute.rs +++ b/crates/ethereum/evm/src/execute.rs @@ -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| { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 4134849ea8f9..73d8a8111b81 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -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] diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index 990c6f0bf2b0..4a95231d4fb4 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -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; diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs index a3e643e88b4c..dd16aa0d046d 100644 --- a/crates/evm/src/lib.rs +++ b/crates/evm/src/lib.rs @@ -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,7 +122,23 @@ 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`]. diff --git a/crates/evm/src/provider.rs b/crates/evm/src/provider.rs index abf04be8938c..b976351c66b0 100644 --- a/crates/evm/src/provider.rs +++ b/crates/evm/src/provider.rs @@ -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( &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( &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( &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( &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( &self, cfg: &mut CfgEnvWithHandlerCfg, diff --git a/crates/node-core/Cargo.toml b/crates/node-core/Cargo.toml index 2390cbfb1a8e..259cfcdb5b49 100644 --- a/crates/node-core/Cargo.toml +++ b/crates/node-core/Cargo.toml @@ -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"] diff --git a/crates/node-core/src/args/gas_price_oracle.rs b/crates/node-core/src/args/gas_price_oracle.rs index 5148fdca34b2..c719e9c8a5fb 100644 --- a/crates/node-core/src/args/gas_price_oracle.rs +++ b/crates/node-core/src/args/gas_price_oracle.rs @@ -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, diff --git a/crates/node-core/src/args/rpc_server.rs b/crates/node-core/src/args/rpc_server.rs index 7ab2dd268fa1..62c675d50777 100644 --- a/crates/node-core/src/args/rpc_server.rs +++ b/crates/node-core/src/args/rpc_server.rs @@ -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::{ diff --git a/crates/node-core/src/lib.rs b/crates/node-core/src/lib.rs index 5894da8ee9bb..8fbc0b2f9d8b 100644 --- a/crates/node-core/src/lib.rs +++ b/crates/node-core/src/lib.rs @@ -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`. diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 9a6ada8f916d..9a7a86b91b5a 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -189,8 +189,9 @@ impl Clone for RpcRegistry { /// [`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. diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 12edb225fe43..3580c9108dd4 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -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| { diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index d13168bfe0c6..68aabf452ca5 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -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()); diff --git a/crates/optimism/node/src/rpc.rs b/crates/optimism/node/src/rpc.rs index 5ae1ba7b2538..01cb08c268d6 100644 --- a/crates/optimism/node/src/rpc.rs +++ b/crates/optimism/node/src/rpc.rs @@ -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}; diff --git a/crates/rpc/rpc-api/Cargo.toml b/crates/rpc/rpc-api/Cargo.toml index 5374c46e4898..59ae5d4cfcdc 100644 --- a/crates/rpc/rpc-api/Cargo.toml +++ b/crates/rpc/rpc-api/Cargo.toml @@ -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"] \ No newline at end of file diff --git a/crates/rpc/rpc-api/src/debug.rs b/crates/rpc/rpc-api/src/debug.rs index ccee09cc2b44..580245b1014c 100644 --- a/crates/rpc/rpc-api/src/debug.rs +++ b/crates/rpc/rpc-api/src/debug.rs @@ -26,7 +26,7 @@ pub trait DebugApi { #[method(name = "getRawTransaction")] async fn raw_transaction(&self, hash: B256) -> RpcResult>; - /// 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>; diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index 82af34a86d73..661b7780e18c 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -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, + }; } diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 1e8ef8f56a18..30d5f3389d04 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -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, diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index 45cad81cd7f0..89ce742e38e4 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -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}; diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 224301966b23..62e857e0267e 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -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)] diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 7257b3be35ab..883409331601 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -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: 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 /// diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml new file mode 100644 index 000000000000..88d3efd5fff6 --- /dev/null +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -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", +] \ No newline at end of file diff --git a/crates/rpc/rpc-api/src/bundle.rs b/crates/rpc/rpc-eth-api/src/api/bundle.rs similarity index 98% rename from crates/rpc/rpc-api/src/bundle.rs rename to crates/rpc/rpc-eth-api/src/api/bundle.rs index 429f6948f8ab..f657e30c430c 100644 --- a/crates/rpc/rpc-api/src/bundle.rs +++ b/crates/rpc/rpc-eth-api/src/api/bundle.rs @@ -1,4 +1,4 @@ -//! Additional `eth_` functions for bundles +//! Additional `eth_` RPC API for bundles. //! //! See also diff --git a/crates/rpc/rpc-api/src/eth_filter.rs b/crates/rpc/rpc-eth-api/src/api/filter.rs similarity index 97% rename from crates/rpc/rpc-api/src/eth_filter.rs rename to crates/rpc/rpc-eth-api/src/api/filter.rs index 2e395d5bad76..da53b577eec5 100644 --- a/crates/rpc/rpc-api/src/eth_filter.rs +++ b/crates/rpc/rpc-eth-api/src/api/filter.rs @@ -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"))] diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-eth-api/src/api/mod.rs similarity index 99% rename from crates/rpc/rpc-api/src/eth.rs rename to crates/rpc/rpc-eth-api/src/api/mod.rs index eb11fde824cc..63e61254afc8 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-eth-api/src/api/mod.rs @@ -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: #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] diff --git a/crates/rpc/rpc-api/src/eth_pubsub.rs b/crates/rpc/rpc-eth-api/src/api/pubsub.rs similarity index 92% rename from crates/rpc/rpc-api/src/eth_pubsub.rs rename to crates/rpc/rpc-eth-api/src/api/pubsub.rs index eaa1ef2d817e..8de125152823 100644 --- a/crates/rpc/rpc-api/src/eth_pubsub.rs +++ b/crates/rpc/rpc-eth-api/src/api/pubsub.rs @@ -1,3 +1,5 @@ +//! `eth_` RPC API for pubsub subscription. + use jsonrpsee::proc_macros::rpc; use reth_rpc_types::pubsub::{Params, SubscriptionKind}; diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc-eth-api/src/api/servers/bundle.rs similarity index 94% rename from crates/rpc/rpc/src/eth/bundle.rs rename to crates/rpc/rpc-eth-api/src/api/servers/bundle.rs index 97ea4bb4c0fe..b272d72a9548 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/bundle.rs @@ -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 { @@ -38,7 +39,7 @@ impl EthBundle { impl EthBundle 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 EthCallBundleApiServer for EthBundle where - Eth: EthTransactions + 'static, + Eth: EthTransactions + LoadPendingBlock + Call + 'static, { async fn call_bundle(&self, request: EthCallBundle) -> RpcResult { Ok(Self::call_bundle(self, request).await?) diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc-eth-api/src/api/servers/filter.rs similarity index 94% rename from crates/rpc/rpc/src/eth/filter.rs rename to crates/rpc/rpc-eth-api/src/api/servers/filter.rs index 1fea2df4a4b4..8f2ff382c544 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/filter.rs @@ -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 ::Transaction: 'static, { /// Returns all the filter changes for the given id, if any - pub async fn filter_changes(&self, id: FilterId) -> Result { + pub async fn filter_changes(&self, id: FilterId) -> Result { 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, FilterError> { + pub async fn filter_logs(&self, id: FilterId) -> Result, 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, FilterError> { + async fn logs_for_filter(&self, filter: Filter) -> Result, 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, FilterError> { + ) -> Result, 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,17 +679,49 @@ enum FilterKind { PendingTransaction(PendingTransactionKind), } +/// An iterator that yields _inclusive_ block ranges of a given step size +#[derive(Debug)] +struct BlockRangeInclusiveIter { + iter: StepBy>, + step: u64, + end: u64, +} + +impl BlockRangeInclusiveIter { + fn new(range: RangeInclusive, step: u64) -> Self { + Self { end: *range.end(), iter: range.step_by(step as usize + 1), step } + } +} + +impl Iterator for BlockRangeInclusiveIter { + type Item = (u64, u64); + + fn next(&mut self) -> Option { + let start = self.iter.next()?; + let end = (start + self.step).min(self.end); + if start > end { + return None + } + Some((start, end)) + } +} + /// Errors that can occur in the handler implementation #[derive(Debug, thiserror::Error)] -pub enum FilterError { +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. @@ -701,59 +730,32 @@ pub enum FilterError { } // convert the error -impl From for jsonrpsee::types::error::ErrorObject<'static> { - fn from(err: FilterError) -> Self { +impl From for jsonrpsee::types::error::ErrorObject<'static> { + fn from(err: EthFilterError) -> Self { match err { - FilterError::FilterNotFound(_) => rpc_error_with_code( + EthFilterError::FilterNotFound(_) => rpc_error_with_code( jsonrpsee::types::error::INVALID_PARAMS_CODE, "filter not found", ), - err @ FilterError::InternalError => { + err @ EthFilterError::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(_) => { + 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 for FilterError { +impl From for EthFilterError { 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 { - iter: StepBy>, - step: u64, - end: u64, -} - -impl BlockRangeInclusiveIter { - fn new(range: RangeInclusive, step: u64) -> Self { - Self { end: *range.end(), iter: range.step_by(step as usize + 1), step } - } -} - -impl Iterator for BlockRangeInclusiveIter { - type Item = (u64, u64); - - fn next(&mut self) -> Option { - let start = self.iter.next()?; - let end = (start + self.step).min(self.end); - if start > end { - return None - } - Some((start, end)) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs new file mode 100644 index 000000000000..2d583161f921 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/block.rs @@ -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 EthBlocks for EthApi +where + Self: LoadBlock, + Provider: HeaderProvider, +{ + #[inline] + fn provider(&self) -> impl reth_provider::HeaderProvider { + self.inner.provider() + } +} + +impl LoadBlock for EthApi +where + Self: LoadPendingBlock + SpawnBlocking, + Provider: BlockReaderIdExt, +{ + #[inline] + fn provider(&self) -> impl BlockReaderIdExt { + self.inner.provider() + } + + #[inline] + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs new file mode 100644 index 000000000000..151cfc91f44b --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/call.rs @@ -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 EthCall for EthApi where + Self: Call + LoadPendingBlock +{ +} + +impl Call for EthApi +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() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs new file mode 100644 index 000000000000..351ce9c18a31 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/fees.rs @@ -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 EthFees for EthApi where + Self: LoadFee +{ +} + +impl LoadFee for EthApi +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 { + self.inner.gas_oracle() + } + + #[inline] + fn fee_history_cache(&self) -> &FeeHistoryCache { + self.inner.fee_history_cache() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs new file mode 100644 index 000000000000..e360df71394f --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/mod.rs @@ -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; diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs new file mode 100644 index 000000000000..4726a00e848e --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/optimism.rs @@ -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, + /// The L1 fee for the block. + pub l1_fee: Option, + /// The L1 data gas for the block. + pub l1_data_gas: Option, +} + +impl OptimismTxMeta { + /// Creates a new [`OptimismTxMeta`]. + pub const fn new( + l1_block_info: Option, + l1_fee: Option, + l1_data_gas: Option, + ) -> Self { + Self { l1_block_info, l1_fee, l1_data_gas } + } +} + +impl EthApi +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, + block_timestamp: u64, + ) -> EthResult { + 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::()), + Some(inner_l1_data_gas.saturating_to::()), + ) + } else { + (None, None) + }; + + Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas)) + } +} + +impl LoadReceipt for EthApi +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 { + 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::() + }); + 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 LoadPendingBlock + for EthApi +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> { + 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::(), + ) + .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 for EthApiError { + fn from(err: OptimismEthApiError) -> Self { + Self::other(err) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs new file mode 100644 index 000000000000..33db098da6d0 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/pending_block.rs @@ -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 LoadPendingBlock + for EthApi +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> { + self.inner.pending_block() + } + + #[inline] + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs new file mode 100644 index 000000000000..404b526e4a9a --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/receipt.rs @@ -0,0 +1,13 @@ +//! Builds an RPC receipt response w.r.t. data layout of network. + +use crate::{servers::LoadReceipt, EthApi, EthStateCache}; + +impl LoadReceipt for EthApi +where + Self: Send + Sync, +{ + #[inline] + fn cache(&self) -> &EthStateCache { + &self.inner.eth_cache + } +} diff --git a/crates/rpc/rpc/src/eth/signer.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs similarity index 85% rename from crates/rpc/rpc/src/eth/signer.rs rename to crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs index 50c06159c4f0..bd2cdc244ac6 100644 --- a/crates/rpc/rpc/src/eth/signer.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/signer.rs @@ -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 = std::result::Result; - -/// 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
; - - /// 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; - - /// signs a transaction request using the given account in request - fn sign_transaction( - &self, - request: TypedTransactionRequest, - address: &Address, - ) -> Result; - /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. - fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result; -} - -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
, accounts: HashMap, } @@ -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 = diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs new file mode 100644 index 000000000000..f1ec323743f2 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/spec.rs @@ -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 EthApiSpec for EthApi +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 { + 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 { + Ok(self.provider().chain_info()?) + } + + fn accounts(&self) -> Vec
{ + 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 { + 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) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs new file mode 100644 index 000000000000..c55c695c346c --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/state.rs @@ -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 EthState for EthApi where + Self: LoadState + SpawnBlocking +{ +} + +impl LoadState for EthApi +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()); + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs new file mode 100644 index 000000000000..14b853adbc49 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/trace.rs @@ -0,0 +1,19 @@ +//! Contains RPC handler implementations specific to tracing. + +use reth_evm::ConfigureEvm; + +use crate::{ + servers::{LoadState, Trace}, + EthApi, +}; + +impl Trace for EthApi +where + Self: LoadState, + EvmConfig: ConfigureEvm, +{ + #[inline] + fn evm_config(&self) -> &impl ConfigureEvm { + self.inner.evm_config() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs new file mode 100644 index 000000000000..804069f6e617 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/block.rs @@ -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>> + 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>> + 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, + ) -> impl Future>> + 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>>> + 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::>>(); + 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>)>>> + 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, + ) -> EthResult>> { + 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, + index: Index, + ) -> impl Future>> + 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>> + 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>> + 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?) + } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs new file mode 100644 index 000000000000..1dae8dc6bb1e --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/blocking_task.rs @@ -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(&self, f: F) -> impl Future> + Send + where + F: FnOnce(Self) -> EthResult + 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 + /// . + fn spawn_tracing(&self, f: F) -> impl Future> + Send + where + F: FnOnce(Self) -> EthResult + 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)? } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs new file mode 100644 index 000000000000..ee531b7f9e3f --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/call.rs @@ -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, + ) -> impl Future> + 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, + overrides: EvmOverrides, + ) -> impl Future> + 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, + mut state_override: Option, + ) -> impl Future>> + 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, + ) -> impl Future> + 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 + 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 + env.cfg.disable_block_gas_limit = true; + + // The basefee should be ignored for eth_createAccessList + // See: + // + 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(&self, at: BlockId, f: F) -> EthResult + where + F: FnOnce(StateProviderTraitObjWrapper<'_>) -> EthResult, + { + let state = self.state_at_block_id(at)?; + f(StateProviderTraitObjWrapper(&state)) + } + + /// Executes the [`EnvWithHandlerCfg`] against the given [Database] without committing state + /// changes. + fn transact( + &self, + db: DB, + env: EnvWithHandlerCfg, + ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> + where + DB: Database, + ::Error: Into, + { + 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> + 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( + &self, + at: BlockId, + f: F, + ) -> impl Future> + Send + where + F: FnOnce(StateProviderTraitObjWrapper<'_>) -> EthResult + 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( + &self, + request: TransactionRequest, + at: BlockId, + overrides: EvmOverrides, + f: F, + ) -> impl Future> + Send + where + Self: LoadPendingBlock, + F: FnOnce(StateCacheDbRefMutWrapper<'_, '_>, EnvWithHandlerCfg) -> EthResult + + 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( + &self, + hash: B256, + f: F, + ) -> impl Future>> + Send + where + Self: LoadBlock + LoadPendingBlock + LoadTransaction, + F: FnOnce(TransactionInfo, ResultAndState, StateCacheDb<'_>) -> EthResult + + 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( + &self, + db: &mut CacheDB, + cfg: CfgEnvWithHandlerCfg, + block_env: BlockEnv, + transactions: impl IntoIterator, + target_tx_hash: B256, + ) -> Result + where + DB: DatabaseRef, + EthApiError: From<::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, + ) -> impl Future> + 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( + &self, + mut cfg: CfgEnvWithHandlerCfg, + block: BlockEnv, + request: TransactionRequest, + state: S, + state_override: Option, + ) -> EthResult + where + S: StateProvider, + { + // Disabled because eth_estimateGas is sometimes used with eoa senders + // See + cfg.disable_eip3607 = true; + + // The basefee should be ignored for eth_createAccessList + // See: + // + 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 + // 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) + // { + // 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: + // + + // 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( + &self, + env_gas_limit: U256, + mut env: EnvWithHandlerCfg, + db: &mut CacheDB>, + ) -> 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() + } + } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs new file mode 100644 index 000000000000..9217b7d5f653 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/fee.rs @@ -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: + fn gas_price(&self) -> impl Future> + 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> + 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> + 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>, + ) -> impl Future> + 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 = Vec::new(); + let mut gas_used_ratio: Vec = Vec::new(); + + let mut base_fee_per_blob_gas: Vec = Vec::new(); + let mut blob_gas_used_ratio: Vec = Vec::new(); + + let mut rewards: Vec> = 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; + + /// 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, + ) -> impl Future> + 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, + max_priority_fee_per_gas: Option, + ) -> impl Future> + 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, + ) -> impl Future> + 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: + fn gas_price(&self) -> impl Future> + 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> + 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> + Send + where + Self: 'static, + { + self.gas_oracle().suggest_tip_cap() + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs new file mode 100644 index 000000000000..c714a166ddc0 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/mod.rs @@ -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 TraceExt for T where T: LoadTransaction + LoadBlock + LoadPendingBlock + Trace + Call {} diff --git a/crates/rpc/rpc/src/eth/api/pending_block.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs similarity index 53% rename from crates/rpc/rpc/src/eth/api/pending_block.rs rename to crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs index 4f9a616dece8..f167a82fef20 100644 --- a/crates/rpc/rpc/src/eth/api/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/pending_block.rs @@ -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; - -/// 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 revm::{db::states::bundle_state::BundleRetention, DatabaseCommit, State}; +use tokio::sync::Mutex; +use tracing::debug; + +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>; + + /// 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 { + 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>> + 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::() == 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( - self, - client: &Client, - pool: &Pool, - ) -> EthResult - where - Client: StateProviderFactory + ChainSpecProvider, - Pool: TransactionPool, - { - let Self { cfg, block_env, origin } = self; + fn build_block(&self, env: PendingBlockEnv) -> EthResult { + 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,10 +215,11 @@ impl PendingBlockEnv { let mut executed_txs = Vec::new(); let mut senders = Vec::new(); - let mut best_txs = pool.best_transactions_with_attributes(BestTransactionsAttributes::new( - base_fee, - block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), - )); + let mut best_txs = + self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new( + base_fee, + block_env.get_blob_gasprice().map(|gasprice| gasprice as u64), + )); let (withdrawals, withdrawals_root) = match origin { PendingBlockEnvOrigin::ActualPending(ref block) => { @@ -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::(), - ) - .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: &mut DB, - chain_spec: &ChainSpec, - block_number: u64, - initialized_cfg: &CfgEnvWithHandlerCfg, - initialized_block_env: &BlockEnv, - parent_beacon_block_root: Option, -) -> 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::(), - 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 + 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::(), - 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 { - 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, -} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs new file mode 100644 index 000000000000..83c4b9d03d8a --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/receipt.rs @@ -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> + 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()) + } + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs new file mode 100644 index 000000000000..637708eb7c44 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/signer.rs @@ -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 = result::Result; + +/// 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
; + + /// 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; + + /// signs a transaction request using the given account in request + fn sign_transaction( + &self, + request: TypedTransactionRequest, + address: &Address, + ) -> Result; + + /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. + fn sign_typed_data(&self, address: Address, payload: &TypedData) -> Result; +} + +dyn_clone::clone_trait_object!(EthSigner); diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs new file mode 100644 index 000000000000..63722e376e64 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/spec.rs @@ -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> + Send; + + /// Returns the chain id + fn chain_id(&self) -> U64; + + /// Returns provider chain info + fn chain_info(&self) -> RethResult; + + /// Returns a list of addresses owned by provider. + fn accounts(&self) -> Vec
; + + /// Returns `true` if the network is undergoing sync. + fn is_syncing(&self) -> bool; + + /// Returns the [`SyncStatus`] of the network + fn sync_status(&self) -> RethResult; +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs new file mode 100644 index 000000000000..220a33e91683 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/state.rs @@ -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, + ) -> impl Future> + 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, + ) -> impl Future> + 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, + ) -> impl Future> + 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, + ) -> impl Future> + 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, + block_id: Option, + ) -> EthResult> + 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::>(); + 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 { + 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 + fn state_at_block_id(&self, at: BlockId) -> EthResult { + Ok(self.provider().state_by_block_id(at)?) + } + + /// Returns the _latest_ state + fn latest_state(&self) -> EthResult { + 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, + ) -> EthResult { + 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> + 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> + 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, + ) -> impl Future> + 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())) + }) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs new file mode 100644 index 000000000000..a1eedf5dc1bf --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/trace.rs @@ -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( + &self, + db: DB, + env: EnvWithHandlerCfg, + inspector: I, + ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> + where + DB: Database, + ::Error: Into, + I: GetInspector, + { + 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( + &self, + db: DB, + env: EnvWithHandlerCfg, + inspector: I, + ) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)> + where + DB: Database, + ::Error: Into, + I: GetInspector, + { + 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( + &self, + env: EnvWithHandlerCfg, + config: TracingInspectorConfig, + at: BlockId, + f: F, + ) -> EthResult + where + Self: Call, + F: FnOnce(TracingInspector, ResultAndState) -> EthResult, + { + 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( + &self, + env: EnvWithHandlerCfg, + config: TracingInspectorConfig, + at: BlockId, + f: F, + ) -> impl Future> + Send + where + Self: LoadPendingBlock + Call, + F: FnOnce(TracingInspector, ResultAndState, StateCacheDb<'_>) -> EthResult + + 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( + &self, + hash: B256, + config: TracingInspectorConfig, + f: F, + ) -> impl Future>> + Send + where + Self: LoadPendingBlock + LoadTransaction + Call, + F: FnOnce( + TransactionInfo, + TracingInspector, + ResultAndState, + StateCacheDb<'_>, + ) -> EthResult + + 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( + &self, + hash: B256, + mut inspector: Insp, + f: F, + ) -> impl Future>> + Send + where + Self: LoadPendingBlock + LoadTransaction + Call, + F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDb<'_>) -> EthResult + + Send + + 'static, + Insp: for<'a, 'b> Inspector> + 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( + &self, + block_id: BlockId, + highest_index: Option, + config: TracingInspectorConfig, + f: F, + ) -> impl Future>>> + Send + where + Self: LoadBlock, + F: Fn( + TransactionInfo, + TracingInspector, + ExecutionResult, + &EvmState, + &StateCacheDb<'_>, + ) -> EthResult + + 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( + &self, + block_id: BlockId, + highest_index: Option, + mut inspector_setup: Setup, + f: F, + ) -> impl Future>>> + Send + where + Self: LoadBlock, + F: Fn(TransactionInfo, Insp, ExecutionResult, &EvmState, &StateCacheDb<'_>) -> EthResult + + Send + + 'static, + Setup: FnMut() -> Insp + Send + 'static, + Insp: for<'a, 'b> Inspector> + 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::(); + let base_fee = block_env.basefee.saturating_to::(); + + // 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( + &self, + block_id: BlockId, + config: TracingInspectorConfig, + f: F, + ) -> impl Future>>> + 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 + + 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( + &self, + block_id: BlockId, + insp_setup: Setup, + f: F, + ) -> impl Future>>> + 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 + + Send + + 'static, + Setup: FnMut() -> Insp + Send + 'static, + Insp: for<'a, 'b> Inspector> + Send + 'static, + R: Send + 'static, + { + self.trace_block_until_with_inspector(block_id, None, insp_setup, f) + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs new file mode 100644 index 000000000000..5c87de1d5ed6 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/traits/transaction.rs @@ -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: +/// Erigon does not: +/// +/// See also +/// +/// 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>; + + /// Returns a handle for signing data. + /// + /// Singer access in default (L1) trait method implementations. + fn signers(&self) -> &parking_lot::RwLock>>; + + /// 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>> + 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>>> + 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>> + 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>> + 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>> + 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>> + 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>> + 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>> + 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> + 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 = + ::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> + 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::()); + } + + 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) => <::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 { + 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> + 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 { + 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, 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>> + 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>> + 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>> + 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<()>; +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs new file mode 100644 index 000000000000..1f4afa7a9b0e --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/helpers/transaction.rs @@ -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 EthTransactions + for EthApi +where + Self: LoadTransaction, + Pool: TransactionPool + 'static, + Provider: BlockReaderIdExt, +{ + #[inline] + fn provider(&self) -> impl BlockReaderIdExt { + self.inner.provider() + } + + #[inline] + fn raw_tx_forwarder(&self) -> Option> { + self.inner.raw_tx_forwarder() + } + + #[inline] + fn signers(&self) -> &parking_lot::RwLock>> { + self.inner.signers() + } +} + +impl LoadTransaction + for EthApi +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"); + } +} diff --git a/crates/rpc/rpc-eth-api/src/api/servers/mod.rs b/crates/rpc/rpc-eth-api/src/api/servers/mod.rs new file mode 100644 index 000000000000..5be35304a2e4 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/api/servers/mod.rs @@ -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 { + /// All nested fields bundled together. + inner: Arc>, +} + +impl EthApi { + /// 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) { + self.inner.raw_transaction_forwarder.write().replace(forwarder); + } +} + +impl EthApi +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, + gas_cap: impl Into, + blocking_task_pool: BlockingTaskPool, + fee_history_cache: FeeHistoryCache, + evm_config: EvmConfig, + raw_transaction_forwarder: Option>, + ) -> Self { + Self::with_spawner( + provider, + pool, + network, + eth_cache, + gas_oracle, + gas_cap.into().into(), + Box::::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, + gas_cap: u64, + task_spawner: Box, + blocking_task_pool: BlockingTaskPool, + fee_history_cache: FeeHistoryCache, + evm_config: EvmConfig, + raw_transaction_forwarder: Option>, + ) -> 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 { + &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 std::fmt::Debug + for EthApi +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EthApi").finish_non_exhaustive() + } +} + +impl Clone for EthApi { + 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 $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); + +impl EthApi { + /// 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 { + /// 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>>, + /// The async cache frontend for eth related data + eth_cache: EthStateCache, + /// The async gas oracle frontend for gas price suggestions + gas_oracle: GasPriceOracle, + /// 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, + /// Cached pending block if any + pending_block: Mutex>, + /// 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>>, +} + +impl EthApiInner { + /// 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> { + &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> { + 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 { + &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>> { + &self.signers + } +} diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs similarity index 98% rename from crates/rpc/rpc/src/eth/pubsub.rs rename to crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs index fdfa836b91ff..a0e886f6d947 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/pubsub.rs @@ -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) } } diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc-eth-api/src/api/servers/server.rs similarity index 85% rename from crates/rpc/rpc/src/eth/api/server.rs rename to crates/rpc/rpc-eth-api/src/api/servers/server.rs index f238b4da079a..0701ff70db48 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc-eth-api/src/api/servers/server.rs @@ -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 EthApiServer for EthApi +impl 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 { @@ -85,7 +73,7 @@ where /// Handler for: `eth_getBlockByHash` async fn block_by_hash(&self, hash: B256, full: bool) -> Result> { 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> { 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> { 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> { 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> { 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> { 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>> { 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> { 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> { 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> { + async fn transaction_by_hash(&self, hash: B256) -> Result> { 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> { 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> { 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> { 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> { 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) -> Result { 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, ) -> Result { 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, ) -> Result { 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) -> Result { 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> { 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> { 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,9 +256,13 @@ where block_overrides: Option>, ) -> Result { 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)) - .await?) + Ok(EthCall::call( + self, + request, + block_number, + EvmOverrides::new(state_overrides, block_overrides), + ) + .await?) } /// Handler for: `eth_callMany` @@ -287,7 +273,7 @@ where state_override: Option, ) -> Result> { 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, ) -> Result { 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, ) -> Result { 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 { 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 { 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 { 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>, ) -> Result { 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 { 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 { 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, ) -> Result { 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, diff --git a/crates/rpc/rpc/src/eth/cache/config.rs b/crates/rpc/rpc-eth-api/src/cache/config.rs similarity index 91% rename from crates/rpc/rpc/src/eth/cache/config.rs rename to crates/rpc/rpc-eth-api/src/cache/config.rs index 5dc989e8e637..93207c930f5c 100644 --- a/crates/rpc/rpc/src/eth/cache/config.rs +++ b/crates/rpc/rpc-eth-api/src/cache/config.rs @@ -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 { diff --git a/crates/rpc/rpc-eth-api/src/cache/db.rs b/crates/rpc/rpc-eth-api/src/cache/db.rs new file mode 100644 index 000000000000..74b20defbc60 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/cache/db.rs @@ -0,0 +1,169 @@ +//! Helper types to workaround 'higher-ranked lifetime error' +//! 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>>; + +/// Hack to get around 'higher-ranked lifetime error', see +/// +#[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 { + 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> { + 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> { + self.0.block_hash(block_number) + } + + fn canonical_hashes_range( + &self, + start: reth_primitives::BlockNumber, + end: reth_primitives::BlockNumber, + ) -> reth_errors::ProviderResult> { + self.0.canonical_hashes_range(start, end) + } + + fn convert_block_hash( + &self, + hash_or_number: reth_rpc_types::BlockHashOrNumber, + ) -> reth_errors::ProviderResult> { + 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> { + self.0.account_balance(addr) + } + + fn account_code( + &self, + addr: revm_primitives::Address, + ) -> reth_errors::ProviderResult> { + self.0.account_code(addr) + } + + fn account_nonce( + &self, + addr: revm_primitives::Address, + ) -> reth_errors::ProviderResult> { + self.0.account_nonce(addr) + } + + fn bytecode_by_hash( + &self, + code_hash: B256, + ) -> reth_errors::ProviderResult> { + self.0.bytecode_by_hash(code_hash) + } + + fn proof( + &self, + address: revm_primitives::Address, + keys: &[B256], + ) -> reth_errors::ProviderResult { + self.0.proof(address, keys) + } + + fn storage( + &self, + account: revm_primitives::Address, + storage_key: reth_primitives::StorageKey, + ) -> reth_errors::ProviderResult> { + self.0.storage(account, storage_key) + } +} + +/// Hack to get around 'higher-ranked lifetime error', see +/// +#[allow(missing_debug_implementations)] +pub struct StateCacheDbRefMutWrapper<'a, 'b>(pub &'b mut StateCacheDb<'a>); + +impl<'a, 'b> Database for StateCacheDbRefMutWrapper<'a, 'b> { + type Error = as Database>::Error; + fn basic( + &mut self, + address: revm_primitives::Address, + ) -> Result, Self::Error> { + self.0.basic(address) + } + + fn block_hash(&mut self, number: U256) -> Result { + self.0.block_hash(number) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + self.0.code_by_hash(code_hash) + } + + fn storage( + &mut self, + address: revm_primitives::Address, + index: U256, + ) -> Result { + self.0.storage(address, index) + } +} + +impl<'a, 'b> DatabaseRef for StateCacheDbRefMutWrapper<'a, 'b> { + type Error = as Database>::Error; + + fn basic_ref( + &self, + address: revm_primitives::Address, + ) -> Result, Self::Error> { + self.0.basic_ref(address) + } + + fn block_hash_ref(&self, number: U256) -> Result { + self.0.block_hash_ref(number) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.0.code_by_hash_ref(code_hash) + } + + fn storage_ref( + &self, + address: revm_primitives::Address, + index: U256, + ) -> Result { + self.0.storage_ref(address, index) + } +} diff --git a/crates/rpc/rpc/src/eth/cache/metrics.rs b/crates/rpc/rpc-eth-api/src/cache/metrics.rs similarity index 93% rename from crates/rpc/rpc/src/eth/cache/metrics.rs rename to crates/rpc/rpc-eth-api/src/cache/metrics.rs index c9b18a299da3..d87a35e03170 100644 --- a/crates/rpc/rpc/src/eth/cache/metrics.rs +++ b/crates/rpc/rpc-eth-api/src/cache/metrics.rs @@ -1,3 +1,5 @@ +//! Tracks state of RPC cache. + use metrics::Counter; use reth_metrics::{metrics::Gauge, Metrics}; diff --git a/crates/rpc/rpc/src/eth/cache/mod.rs b/crates/rpc/rpc-eth-api/src/cache/mod.rs similarity index 99% rename from crates/rpc/rpc/src/eth/cache/mod.rs rename to crates/rpc/rpc-eth-api/src/cache/mod.rs index cfbe68311ee3..92e96806bc9b 100644 --- a/crates/rpc/rpc/src/eth/cache/mod.rs +++ b/crates/rpc/rpc-eth-api/src/cache/mod.rs @@ -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 EthStateCacheService>) { if let Some(queued) = self.full_block_cache.remove(&block_hash) { @@ -403,7 +402,7 @@ impl Future for EthStateCacheService, { - /// The LRU cache for the + /// The LRU cache. cache: LruMap, - /// All queued consumers + /// All queued consumers. queued: HashMap>, /// Cache metrics metrics: CacheMetrics, diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc-eth-api/src/error.rs similarity index 100% rename from crates/rpc/rpc/src/eth/error.rs rename to crates/rpc/rpc-eth-api/src/error.rs diff --git a/crates/rpc/rpc/src/eth/api/fee_history.rs b/crates/rpc/rpc-eth-api/src/fee_history.rs similarity index 99% rename from crates/rpc/rpc/src/eth/api/fee_history.rs rename to crates/rpc/rpc-eth-api/src/fee_history.rs index 626c670376c8..10c06eab06c6 100644 --- a/crates/rpc/rpc/src/eth/api/fee_history.rs +++ b/crates/rpc/rpc-eth-api/src/fee_history.rs @@ -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`. diff --git a/crates/rpc/rpc/src/eth/gas_oracle.rs b/crates/rpc/rpc-eth-api/src/gas_oracle.rs similarity index 89% rename from crates/rpc/rpc/src/eth/gas_oracle.rs rename to crates/rpc/rpc-eth-api/src/gas_oracle.rs index bb44af67b5f7..3cc55eb2b341 100644 --- a/crates/rpc/rpc/src/eth/gas_oracle.rs +++ b/crates/rpc/rpc-eth-api/src/gas_oracle.rs @@ -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 +/// +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 { impl GasPriceOracle 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 for GasCap { + fn from(gas_cap: u64) -> Self { + Self(gas_cap) + } +} + +impl From for u64 { + fn from(gas_cap: GasCap) -> Self { + gas_cap.0 + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc/src/eth/id_provider.rs b/crates/rpc/rpc-eth-api/src/id_provider.rs similarity index 93% rename from crates/rpc/rpc/src/eth/id_provider.rs rename to crates/rpc/rpc-eth-api/src/id_provider.rs index 6691e13a9f4b..0b63da39f425 100644 --- a/crates/rpc/rpc/src/eth/id_provider.rs +++ b/crates/rpc/rpc-eth-api/src/id_provider.rs @@ -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 diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs new file mode 100644 index 000000000000..51cf5dafc07b --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -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; diff --git a/crates/rpc/rpc/src/eth/logs_utils.rs b/crates/rpc/rpc-eth-api/src/logs_utils.rs similarity index 96% rename from crates/rpc/rpc/src/eth/logs_utils.rs rename to crates/rpc/rpc-eth-api/src/logs_utils.rs index c57ce5fcb986..f98567c4ed9b 100644 --- a/crates/rpc/rpc/src/eth/logs_utils.rs +++ b/crates/rpc/rpc-eth-api/src/logs_utils.rs @@ -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; diff --git a/crates/rpc/rpc-eth-api/src/pending_block.rs b/crates/rpc/rpc-eth-api/src/pending_block.rs new file mode 100644 index 000000000000..a1671b2833ac --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/pending_block.rs @@ -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: &mut DB, + chain_spec: &ChainSpec, + block_number: u64, + initialized_cfg: &CfgEnvWithHandlerCfg, + initialized_block_env: &BlockEnv, + parent_beacon_block_root: Option, +) -> 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::(), + 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 + 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::(), + 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 { + 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, +} diff --git a/crates/rpc/rpc-eth-api/src/receipt.rs b/crates/rpc/rpc-eth-api/src/receipt.rs new file mode 100644 index 000000000000..ac9944ab8825 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/receipt.rs @@ -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>, + /// 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 { + // 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 + } +} diff --git a/crates/rpc/rpc/src/result.rs b/crates/rpc/rpc-eth-api/src/result.rs similarity index 95% rename from crates/rpc/rpc/src/result.rs rename to crates/rpc/rpc-eth-api/src/result.rs index f00c9e279939..ac55bb3fefea 100644 --- a/crates/rpc/rpc/src/result.rs +++ b/crates/rpc/rpc-eth-api/src/result.rs @@ -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: 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, ) -> 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, -) -> jsonrpsee::types::error::ErrorObject<'static> { +pub fn internal_rpc_err(msg: impl Into) -> 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, 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, ) -> 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, data: Option<&[u8]>, diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc-eth-api/src/revm_utils.rs similarity index 90% rename from crates/rpc/rpc/src/eth/revm_utils.rs rename to crates/rpc/rpc-eth-api/src/revm_utils.rs index 4f4b9d7f02e4..b0d0e6a882d3 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc-eth-api/src/revm_utils.rs @@ -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] diff --git a/crates/rpc/rpc-eth-api/src/transaction.rs b/crates/rpc/rpc-eth-api/src/transaction.rs new file mode 100644 index 000000000000..685f37b93d79 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/transaction.rs @@ -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, + }, +} + +// === 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 for TransactionSignedEcRecovered { + fn from(value: TransactionSource) -> Self { + match value { + TransactionSource::Pool(tx) => tx, + TransactionSource::Block { transaction, .. } => transaction, + } + } +} + +impl From 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, + ) + } + } + } +} diff --git a/crates/rpc/rpc/src/eth/utils.rs b/crates/rpc/rpc-eth-api/src/utils.rs similarity index 79% rename from crates/rpc/rpc/src/eth/utils.rs rename to crates/rpc/rpc-eth-api/src/utils.rs index a4291c4b933d..6573fb6b77cc 100644 --- a/crates/rpc/rpc/src/eth/utils.rs +++ b/crates/rpc/rpc-eth-api/src/utils.rs @@ -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 { +pub fn recover_raw_transaction(data: Bytes) -> EthResult { if data.is_empty() { return Err(EthApiError::EmptyRawTransactionData) } diff --git a/crates/rpc/rpc-testing-util/Cargo.toml b/crates/rpc/rpc-testing-util/Cargo.toml index 898fec038f70..8ab37d1b18d0 100644 --- a/crates/rpc/rpc-testing-util/Cargo.toml +++ b/crates/rpc/rpc-testing-util/Cargo.toml @@ -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 diff --git a/crates/rpc/rpc-testing-util/tests/it/trace.rs b/crates/rpc/rpc-testing-util/tests/it/trace.rs index a6439f0744c4..029e9fbbc8b5 100644 --- a/crates/rpc/rpc-testing-util/tests/it/trace.rs +++ b/crates/rpc/rpc-testing-util/tests/it/trace.rs @@ -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, }; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 87539eab4ae5..7082a75fcc8e 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -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", ] diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 71f95fedec9a..0e1e13a0c013 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -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. /// diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index b2d524a6325b..8fd7c582a115 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -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 DebugApi { impl DebugApi 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 { @@ -74,7 +78,7 @@ where } /// Trace the entire block asynchronously - async fn trace_block_with( + async fn trace_block( &self, at: BlockId, transactions: Vec, @@ -165,7 +169,7 @@ where .collect::>>()? }; - 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 + 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 + // + 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 + // + 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>, + db: &mut StateCacheDb<'_>, transaction_context: Option, ) -> EthResult<(GethTrace, revm_primitives::EvmState)> { let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; @@ -614,8 +630,13 @@ where #[async_trait] impl DebugApiServer for DebugApi 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 { diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs deleted file mode 100644 index 18a547faf9e6..000000000000 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ /dev/null @@ -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 EthApi -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, - ) -> EthResult>> { - 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, - index: Index, - ) -> EthResult> { - 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>> { - // 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::>>() - .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, - ) -> EthResult> { - 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, - ) -> EthResult> { - 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, - ) -> EthResult> { - 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, - full: bool, - ) -> EthResult> { - 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, - ) -> EthResult> { - let header = self.rpc_block(block_id, false).await?.map(|block| block.inner.header); - Ok(header) - } -} diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs deleted file mode 100644 index 907065e476d4..000000000000 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ /dev/null @@ -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 -/// -const ESTIMATE_GAS_ERROR_RATIO: f64 = 0.015; - -impl EthApi -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, - ) -> EthResult { - 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, - overrides: EvmOverrides, - ) -> EthResult { - 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, - mut state_override: Option, - ) -> EthResult> { - 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( - &self, - mut cfg: CfgEnvWithHandlerCfg, - block: BlockEnv, - request: TransactionRequest, - state: S, - state_override: Option, - ) -> EthResult - where - S: StateProvider, - { - // Disabled because eth_estimateGas is sometimes used with eoa senders - // See - cfg.disable_eip3607 = true; - - // The basefee should be ignored for eth_createAccessList - // See: - // - 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 - // 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) - // { - // 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, - ) -> EthResult { - 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, - ) -> EthResult { - 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 - env.cfg.disable_block_gas_limit = true; - - // The basefee should be ignored for eth_createAccessList - // See: - // - 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( - &self, - env_gas_limit: U256, - mut env: EnvWithHandlerCfg, - db: &mut CacheDB>, - ) -> 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: - // - - // 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(()) -} diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs deleted file mode 100644 index 2493d6055778..000000000000 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ /dev/null @@ -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 EthApi -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: - pub(crate) async fn gas_price(&self) -> EthResult { - 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 { - 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 { - 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>, - ) -> EthResult { - 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 = Vec::new(); - let mut gas_used_ratio: Vec = Vec::new(); - - let mut base_fee_per_blob_gas: Vec = Vec::new(); - let mut blob_gas_used_ratio: Vec = Vec::new(); - - let mut rewards: Vec> = 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() - } -} diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs deleted file mode 100644 index 364a55842d3c..000000000000 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ /dev/null @@ -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; - - /// Returns the chain id - fn chain_id(&self) -> U64; - - /// Returns provider chain info - fn chain_info(&self) -> RethResult; - - /// Returns a list of addresses owned by provider. - fn accounts(&self) -> Vec
; - - /// Returns `true` if the network is undergoing sync. - fn is_syncing(&self) -> bool; - - /// Returns the [SyncStatus] of the network - fn sync_status(&self) -> RethResult; -} - -/// `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 { - /// All nested fields bundled together. - inner: Arc>, -} - -impl EthApi { - /// 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) { - self.inner.raw_transaction_forwarder.write().replace(forwarder); - } -} - -impl EthApi -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, - gas_cap: impl Into, - blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, - evm_config: EvmConfig, - raw_transaction_forwarder: Option>, - ) -> Self { - Self::with_spawner( - provider, - pool, - network, - eth_cache, - gas_oracle, - gas_cap.into().into(), - Box::::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, - gas_cap: u64, - task_spawner: Box, - blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, - evm_config: EvmConfig, - raw_transaction_forwarder: Option>, - ) -> 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(&self, c: C) -> EthResult - where - C: FnOnce(Self) -> F, - F: Future> + 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 { - &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 EthApi -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 - pub fn state_at_block_id(&self, at: BlockId) -> EthResult { - 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, - ) -> EthResult { - 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 { - Ok(self.provider().history_by_block_hash(block_hash)?) - } - - /// Returns the _latest_ state - pub fn latest_state(&self) -> RethResult { - Ok(self.provider().latest()?) - } -} - -impl EthApi -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 { - 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> { - 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::() == 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 std::fmt::Debug - for EthApi -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("EthApi").finish_non_exhaustive() - } -} - -impl Clone for EthApi { - fn clone(&self) -> Self { - Self { inner: Arc::clone(&self.inner) } - } -} - -#[async_trait] -impl EthApiSpec for EthApi -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 { - 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 { - Ok(self.provider().chain_info()?) - } - - fn accounts(&self) -> Vec
{ - 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 { - 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 for GasCap { - fn from(gas_cap: u64) -> Self { - Self(gas_cap) - } -} - -impl From for u64 { - fn from(gas_cap: GasCap) -> Self { - gas_cap.0 - } -} - -/// Container type `EthApi` -struct EthApiInner { - /// 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>>, - /// The async cache frontend for eth related data - eth_cache: EthStateCache, - /// The async gas oracle frontend for gas price suggestions - gas_oracle: GasPriceOracle, - /// 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, - /// Cached pending block if any - pending_block: Mutex>, - /// 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>>, -} diff --git a/crates/rpc/rpc/src/eth/api/optimism.rs b/crates/rpc/rpc/src/eth/api/optimism.rs deleted file mode 100644 index af58450145b8..000000000000 --- a/crates/rpc/rpc/src/eth/api/optimism.rs +++ /dev/null @@ -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, - /// The L1 fee for the block. - pub(crate) l1_fee: Option, - /// The L1 data gas for the block. - pub(crate) l1_data_gas: Option, -} - -impl OptimismTxMeta { - /// Creates a new [`OptimismTxMeta`]. - pub(crate) const fn new( - l1_block_info: Option, - l1_fee: Option, - l1_data_gas: Option, - ) -> Self { - Self { l1_block_info, l1_fee, l1_data_gas } - } -} diff --git a/crates/rpc/rpc/src/eth/api/sign.rs b/crates/rpc/rpc/src/eth/api/sign.rs deleted file mode 100644 index 5256de4a4dae..000000000000 --- a/crates/rpc/rpc/src/eth/api/sign.rs +++ /dev/null @@ -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 EthApi { - pub(crate) async fn sign(&self, account: Address, message: &[u8]) -> EthResult { - Ok(self.find_signer(&account)?.sign(account, message).await?.to_hex_bytes()) - } - - pub(crate) fn sign_typed_data(&self, data: &TypedData, account: Address) -> EthResult { - Ok(self.find_signer(&account)?.sign_typed_data(account, data)?.to_hex_bytes()) - } - - pub(crate) fn find_signer( - &self, - account: &Address, - ) -> Result, 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); - } -} diff --git a/crates/rpc/rpc/src/eth/api/state.rs b/crates/rpc/rpc/src/eth/api/state.rs deleted file mode 100644 index d7c1bafacf9f..000000000000 --- a/crates/rpc/rpc/src/eth/api/state.rs +++ /dev/null @@ -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 EthApi -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) -> EthResult { - 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) -> EthResult { - 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, - ) -> EthResult { - 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, - ) -> EthResult { - 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, - block_id: Option, - ) -> EthResult { - 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::>(); - 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()); - } -} diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs deleted file mode 100644 index 8829a0434e49..000000000000 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ /dev/null @@ -1,1861 +0,0 @@ -//! Contains RPC handler implementations specific to transactions -use crate::{ - eth::{ - api::pending_block::PendingBlockEnv, - error::{EthApiError, EthResult, RpcInvalidTransactionError, SignError}, - revm_utils::prepare_call_env, - utils::recover_raw_transaction, - }, - EthApi, EthApiSpec, -}; -use alloy_primitives::TxKind as RpcTransactionKind; -use async_trait::async_trait; -use reth_evm::ConfigureEvm; -use reth_network_api::NetworkInfo; -use reth_primitives::{ - eip4844::calc_blob_gasprice, - revm::env::{fill_block_env_with_coinbase, tx_env_with_recovered}, - Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredPooledTransaction, Header, - IntoRecoveredTransaction, Receipt, SealedBlock, SealedBlockWithSenders, TransactionMeta, - TransactionSigned, TransactionSignedEcRecovered, - TxKind::{Call, Create}, - B256, U256, -}; -use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory, -}; -use reth_revm::database::StateProviderDatabase; -use reth_rpc_types::{ - state::EvmOverrides, - transaction::{ - EIP1559TransactionRequest, EIP2930TransactionRequest, EIP4844TransactionRequest, - LegacyTransactionRequest, - }, - AnyReceiptEnvelope, AnyTransactionReceipt, Index, Log, ReceiptWithBloom, Transaction, - TransactionInfo, TransactionReceipt, TransactionRequest, TypedTransactionRequest, - WithOtherFields, -}; -use reth_rpc_types_compat::transaction::from_recovered_with_block_context; -use reth_transaction_pool::{TransactionOrigin, TransactionPool}; -use revm::{ - db::CacheDB, - primitives::{ - db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, EvmState, - ExecutionResult, ResultAndState, SpecId, - }, - GetInspector, Inspector, -}; -use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; -use std::future::Future; - -use crate::eth::revm_utils::FillableTransaction; -#[cfg(feature = "optimism")] -use reth_rpc_types::OptimismTransactionReceiptFields; -use revm_primitives::db::{Database, DatabaseRef}; - -/// Helper alias type for the state's [`CacheDB`] -pub(crate) type StateCacheDB = CacheDB>; - -/// Commonly used transaction related functions for the [EthApi] type 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] -/// [Cfg](revm_primitives::CfgEnvWithHandlerCfg). -/// -/// The behaviour for tracing endpoints is not consistent across clients. -/// Geth also disables the basefee check for tracing: -/// Erigon does not: -/// -/// See also -/// -/// This implementation follows the behaviour of Geth and disables the basefee check for tracing. -#[async_trait::async_trait] -pub trait EthTransactions: Send + Sync { - /// Executes the [EnvWithHandlerCfg] against the given [Database] without committing state - /// changes. - fn transact( - &self, - db: DB, - env: EnvWithHandlerCfg, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into; - - /// Executes the [EnvWithHandlerCfg] against the given [Database] without committing state - /// changes. - fn inspect( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into, - I: GetInspector; - - /// Same as [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( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)> - where - DB: Database, - ::Error: Into, - I: GetInspector; - - /// 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( - &self, - db: &mut CacheDB, - cfg: CfgEnvWithHandlerCfg, - block_env: BlockEnv, - transactions: I, - target_tx_hash: B256, - ) -> Result - where - DB: DatabaseRef, - EthApiError: From<::Error>, - I: IntoIterator, - Tx: FillableTransaction; - - /// Returns default gas limit to use for `eth_call` and tracing RPC methods. - fn call_gas_limit(&self) -> u64; - - /// 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 [Self::spawn_blocking]. - async fn spawn_blocking_future(&self, c: F) -> EthResult - where - F: Future> + Send + 'static, - R: Send + 'static; - - /// Executes a blocking on the tracing pol. - /// - /// Note: This is expected for futures that are predominantly CPU bound, for blocking IO futures - /// use [Self::spawn_blocking_future]. - async fn spawn_blocking(&self, c: F) -> EthResult - where - F: FnOnce() -> EthResult + Send + 'static, - R: Send + 'static; - - /// Returns the state at the given [BlockId] - fn state_at(&self, at: BlockId) -> EthResult; - - /// Executes the closure with the state that corresponds to the given [BlockId]. - fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult; - - /// Executes the closure with the state that corresponds to the given [BlockId] on a new task - async fn spawn_with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult + Send + 'static, - T: Send + 'static; - - /// 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. - async fn evm_env_at(&self, at: BlockId) - -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv, BlockId)>; - - /// Returns the revm evm env for the raw block header - /// - /// This is used for tracing raw blocks - async fn evm_env_for_raw_block( - &self, - at: &Header, - ) -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv)>; - - /// Get all transactions in the block with the given hash. - /// - /// Returns `None` if block does not exist. - async fn transactions_by_block(&self, block: B256) - -> EthResult>>; - - /// Get the entire block for the given id. - /// - /// Returns `None` if block does not exist. - async fn block_by_id(&self, id: BlockId) -> EthResult>; - - /// Get the entire block for the given id. - /// - /// Returns `None` if block does not exist. - async fn block_by_id_with_senders( - &self, - id: BlockId, - ) -> EthResult>; - - /// Get all transactions in the block with the given hash. - /// - /// Returns `None` if block does not exist. - async fn transactions_by_block_id( - &self, - block: BlockId, - ) -> EthResult>>; - - /// 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. - async fn raw_transaction_by_hash(&self, hash: B256) -> EthResult>; - - /// Returns the transaction by hash. - /// - /// Checks the pool and state. - /// - /// Returns `Ok(None)` if no matching transaction was found. - async fn transaction_by_hash(&self, hash: B256) -> EthResult>; - - /// Returns the transaction by including its corresponding [BlockId] - /// - /// Note: this supports pending transactions - async fn transaction_by_hash_at( - &self, - hash: B256, - ) -> EthResult>; - - /// Returns the _historical_ transaction and the block it was mined in - async fn historical_transaction_by_hash_at( - &self, - hash: B256, - ) -> EthResult>; - - /// 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. - async fn transaction_receipt(&self, hash: B256) -> EthResult>; - - /// Decodes and recovers the transaction and submits it to the pool. - /// - /// Returns the hash of the transaction. - async fn send_raw_transaction(&self, tx: Bytes) -> EthResult; - - /// Signs transaction with a matching signer, if any and submits the transaction to the pool. - /// Returns the hash of the signed transaction. - async fn send_transaction(&self, request: TransactionRequest) -> EthResult; - - /// 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`. - async fn spawn_with_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - f: F, - ) -> EthResult - where - F: FnOnce(&mut StateCacheDB, EnvWithHandlerCfg) -> EthResult + Send + 'static, - R: Send + 'static; - - /// Executes the call request at the given [BlockId]. - async fn transact_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)>; - - /// Executes the call request at the given [BlockId] on a new task and returns the result of the - /// inspect call. - async fn spawn_inspect_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - I: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static; - - /// 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( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState) -> EthResult; - - /// Same as [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. - async fn spawn_trace_at_with_state( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static; - - /// Fetches the transaction and the transaction's block - async fn transaction_and_block( - &self, - hash: B256, - ) -> EthResult>; - - /// 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). - async fn spawn_trace_transaction_in_block( - &self, - hash: B256, - config: TracingInspectorConfig, - f: F, - ) -> EthResult> - where - F: FnOnce(TransactionInfo, TracingInspector, ResultAndState, StateCacheDB) -> EthResult - + Send - + 'static, - R: Send + 'static, - { - self.spawn_trace_transaction_in_block_with_inspector(hash, TracingInspector::new(config), f) - .await - } - - /// 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). - async fn spawn_replay_transaction(&self, hash: B256, f: F) -> EthResult> - where - F: FnOnce(TransactionInfo, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static; - - /// 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). - async fn spawn_trace_transaction_in_block_with_inspector( - &self, - hash: B256, - inspector: Insp, - f: F, - ) -> EthResult> - where - F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDB) -> EthResult - + Send - + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static; - - /// 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. - async fn trace_block_with( - &self, - block_id: BlockId, - config: TracingInspectorConfig, - f: F, - ) -> EthResult>> - where - // This is the callback that's invoked for each transaction with the inspector, the result, - // state and db - F: for<'a> Fn( - TransactionInfo, - TracingInspector, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - R: Send + 'static, - { - self.trace_block_until(block_id, None, config, f).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 [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. - async fn trace_block_with_inspector( - &self, - block_id: BlockId, - insp_setup: Setup, - f: F, - ) -> EthResult>> - where - // This is the callback that's invoked for each transaction with the inspector, the result, - // state and db - F: for<'a> Fn( - TransactionInfo, - Insp, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static, - { - self.trace_block_until_with_inspector(block_id, None, insp_setup, f).await - } - - /// 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. - async fn trace_block_until( - &self, - block_id: BlockId, - highest_index: Option, - config: TracingInspectorConfig, - f: F, - ) -> EthResult>> - where - F: for<'a> Fn( - TransactionInfo, - TracingInspector, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - R: Send + 'static, - { - self.trace_block_until_with_inspector( - block_id, - highest_index, - move || TracingInspector::new(config), - f, - ) - .await - } - - /// 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. - async fn trace_block_until_with_inspector( - &self, - block_id: BlockId, - highest_index: Option, - inspector_setup: Setup, - f: F, - ) -> EthResult>> - where - F: for<'a> Fn( - TransactionInfo, - Insp, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static; -} - -#[async_trait] -impl EthTransactions - for EthApi -where - Pool: TransactionPool + Clone + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + Send + Sync + 'static, - EvmConfig: ConfigureEvm + 'static, -{ - fn transact( - &self, - db: DB, - env: EnvWithHandlerCfg, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into, - { - let mut evm = self.inner.evm_config.evm_with_env(db, env); - let res = evm.transact()?; - let (_, env) = evm.into_db_and_env_with_handler_cfg(); - Ok((res, env)) - } - - fn inspect( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - DB: Database, - ::Error: Into, - I: GetInspector, - { - self.inspect_and_return_db(db, env, inspector).map(|(res, env, _)| (res, env)) - } - - fn inspect_and_return_db( - &self, - db: DB, - env: EnvWithHandlerCfg, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg, DB)> - where - DB: Database, - ::Error: Into, - I: GetInspector, - { - let mut evm = self.inner.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)) - } - - fn replay_transactions_until( - &self, - db: &mut CacheDB, - cfg: CfgEnvWithHandlerCfg, - block_env: BlockEnv, - transactions: I, - target_tx_hash: B256, - ) -> Result - where - DB: DatabaseRef, - EthApiError: From<::Error>, - I: IntoIterator, - Tx: FillableTransaction, - { - let env = EnvWithHandlerCfg::new_with_cfg_env(cfg, block_env, Default::default()); - - let mut evm = self.inner.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 - } - - tx.try_fill_tx_env(evm.tx_mut())?; - evm.transact_commit()?; - index += 1; - } - Ok(index) - } - - fn call_gas_limit(&self) -> u64 { - self.inner.gas_cap - } - - async fn spawn_blocking_future(&self, c: F) -> EthResult - where - F: Future> + Send + 'static, - R: Send + 'static, - { - self.on_blocking_task(|_| c).await - } - - async fn spawn_blocking(&self, c: F) -> EthResult - where - F: FnOnce() -> EthResult + Send + 'static, - R: Send + 'static, - { - self.spawn_tracing_task_with(move |_| c()).await - } - - fn state_at(&self, at: BlockId) -> EthResult { - self.state_at_block_id(at) - } - - fn with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult, - { - let state = self.state_at(at)?; - f(state) - } - - async fn spawn_with_state_at_block(&self, at: BlockId, f: F) -> EthResult - where - F: FnOnce(StateProviderBox) -> EthResult + Send + 'static, - T: Send + 'static, - { - self.spawn_tracing_task_with(move |this| { - let state = this.state_at(at)?; - f(state) - }) - .await - } - - async fn evm_env_at( - &self, - at: BlockId, - ) -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv, BlockId)> { - 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 = self - .provider() - .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())) - } - } - - async fn evm_env_for_raw_block( - &self, - header: &Header, - ) -> EthResult<(CfgEnvWithHandlerCfg, BlockEnv)> { - // 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)) - } - - async fn transactions_by_block( - &self, - block: B256, - ) -> EthResult>> { - Ok(self.cache().get_block_transactions(block).await?) - } - - async fn block_by_id(&self, id: BlockId) -> EthResult> { - self.block(id).await - } - - async fn block_by_id_with_senders( - &self, - id: BlockId, - ) -> EthResult> { - self.block_with_senders(id).await - } - - async fn transactions_by_block_id( - &self, - block: BlockId, - ) -> EthResult>> { - self.block_by_id(block).await.map(|block| block.map(|block| block.body)) - } - - async fn raw_transaction_by_hash(&self, hash: B256) -> EthResult> { - // 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.on_blocking_task(|this| async move { - Ok(this.provider().transaction_by_hash(hash)?.map(|tx| tx.envelope_encoded())) - }) - .await - } - - async fn transaction_by_hash(&self, hash: B256) -> EthResult> { - // Try to find the transaction on disk - let mut resp = self - .on_blocking_task(|this| async move { - 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) - } - - async fn transaction_by_hash_at( - &self, - transaction_hash: B256, - ) -> EthResult> { - match self.transaction_by_hash(transaction_hash).await? { - None => return 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)) - } - } - } - - async fn historical_transaction_by_hash_at( - &self, - hash: B256, - ) -> EthResult> { - match self.transaction_by_hash_at(hash).await? { - None => Ok(None), - Some((tx, at)) => Ok(at.as_block_hash().map(|hash| (tx, hash))), - } - } - - async fn transaction_receipt(&self, hash: B256) -> EthResult> { - let result = self - .on_blocking_task(|this| async move { - let (tx, meta) = match this.provider().transaction_by_hash_with_meta(hash)? { - Some((tx, meta)) => (tx, meta), - None => return Ok(None), - }; - - let receipt = match this.provider().receipt_by_hash(hash)? { - Some(recpt) => recpt, - None => return Ok(None), - }; - - Ok(Some((tx, meta, receipt))) - }) - .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) - } - - async fn send_raw_transaction(&self, tx: Bytes) -> EthResult { - // On optimism, transactions are forwarded directly to the sequencer to be included in - // blocks that it builds. - let maybe_forwarder = self.inner.raw_transaction_forwarder.read().clone(); - if let Some(client) = maybe_forwarder { - tracing::debug!( target: "rpc::eth", "forwarding raw transaction to"); - client.forward_raw_transaction(&tx).await?; - } - - let recovered = recover_raw_transaction(tx)?; - let pool_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) - } - - async fn send_transaction(&self, mut request: TransactionRequest) -> EthResult { - 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.get_transaction_count(from, Some(BlockId::pending()))?; - // note: `.to()` can't panic because the nonce is constructed from a `u64` - request.nonce = Some(nonce.to::()); - } - - 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(RpcTransactionKind::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(RpcTransactionKind::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(RpcTransactionKind::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(RpcTransactionKind::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) => ::from_recovered_pooled_transaction(converted), - Err(_) => return Err(EthApiError::TransactionConversionError), - }; - - // submit the transaction to the pool with a `Local` origin - let hash = self.pool().add_transaction(TransactionOrigin::Local, pool_transaction).await?; - - Ok(hash) - } - - async fn spawn_with_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - f: F, - ) -> EthResult - where - F: FnOnce(&mut StateCacheDB, EnvWithHandlerCfg) -> EthResult + Send + 'static, - R: Send + 'static, - { - let (cfg, block_env, at) = self.evm_env_at(at).await?; - let this = self.clone(); - self.inner - .blocking_task_pool - .spawn(move || { - let state = this.state_at(at)?; - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - - let env = prepare_call_env( - cfg, - block_env, - request, - this.call_gas_limit(), - &mut db, - overrides, - )?; - f(&mut db, env) - }) - .await - .map_err(|_| EthApiError::InternalBlockingTaskError)? - } - - async fn transact_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> { - let this = self.clone(); - self.spawn_with_call_at(request, at, overrides, move |db, env| this.transact(db, env)).await - } - - async fn spawn_inspect_call_at( - &self, - request: TransactionRequest, - at: BlockId, - overrides: EvmOverrides, - inspector: I, - ) -> EthResult<(ResultAndState, EnvWithHandlerCfg)> - where - I: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - { - let this = self.clone(); - self.spawn_with_call_at(request, at, overrides, move |db, env| { - this.inspect(db, env, inspector) - }) - .await - } - - fn trace_at( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState) -> EthResult, - { - let this = self.clone(); - self.with_state_at_block(at, |state| { - let mut db = CacheDB::new(StateProviderDatabase::new(state)); - let mut inspector = TracingInspector::new(config); - let (res, _) = this.inspect(&mut db, env, &mut inspector)?; - f(inspector, res) - }) - } - - async fn spawn_trace_at_with_state( - &self, - env: EnvWithHandlerCfg, - config: TracingInspectorConfig, - at: BlockId, - f: F, - ) -> EthResult - where - F: FnOnce(TracingInspector, ResultAndState, StateCacheDB) -> EthResult + 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(&mut db, env, &mut inspector)?; - f(inspector, res, db) - }) - .await - } - - async fn transaction_and_block( - &self, - hash: B256, - ) -> EthResult> { - 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)))) - } - - async fn spawn_replay_transaction(&self, hash: B256, f: F) -> EthResult> - where - F: FnOnce(TransactionInfo, ResultAndState, StateCacheDB) -> EthResult + Send + 'static, - R: Send + 'static, - { - 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) - } - - async fn spawn_trace_transaction_in_block_with_inspector( - &self, - hash: B256, - mut inspector: Insp, - f: F, - ) -> EthResult> - where - F: FnOnce(TransactionInfo, Insp, ResultAndState, StateCacheDB) -> EthResult - + Send - + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static, - { - 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(&mut db, env, &mut inspector)?; - f(tx_info, inspector, res, db) - }) - .await - .map(Some) - } - - async fn trace_block_until_with_inspector( - &self, - block_id: BlockId, - highest_index: Option, - mut inspector_setup: Setup, - f: F, - ) -> EthResult>> - where - F: for<'a> Fn( - TransactionInfo, - Insp, - ExecutionResult, - &'a EvmState, - &'a StateCacheDB, - ) -> EthResult - + Send - + 'static, - Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a> Inspector<&'a mut StateCacheDB> + Send + 'static, - R: Send + 'static, - { - 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_task_with(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::(); - let base_fee = block_env.basefee.saturating_to::(); - - // 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(state_at.into())?; - let mut db = CacheDB::new(StateProviderDatabase::new(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(&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(results) - }) - .await - .map(Some) - } -} - -// === impl EthApi === - -impl EthApi -where - Self: Send + Sync + 'static, -{ - /// Spawns the given closure on a new blocking tracing task - async fn spawn_tracing_task_with(&self, f: F) -> EthResult - where - F: FnOnce(Self) -> EthResult + Send + 'static, - T: Send + 'static, - { - let this = self.clone(); - self.inner - .blocking_task_pool - .spawn(move || f(this)) - .await - .map_err(|_| EthApiError::InternalBlockingTaskError)? - } -} - -impl EthApi -where - Pool: TransactionPool + 'static, - Provider: - BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static, - Network: NetworkInfo + 'static, - EvmConfig: ConfigureEvm, -{ - /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy - /// transactions. - pub(crate) async fn legacy_gas_price(&self, gas_price: Option) -> EthResult { - 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`) - pub(crate) async fn eip1559_fees( - &self, - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - ) -> EthResult<(U256, U256)> { - 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) - .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. - pub(crate) async fn eip4844_blob_fee(&self, blob_fee: Option) -> EthResult { - match blob_fee { - Some(blob_fee) => Ok(blob_fee), - None => self.blob_base_fee().await, - } - } - - pub(crate) fn sign_request( - &self, - from: &Address, - request: TypedTransactionRequest, - ) -> EthResult { - for signer in self.inner.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) - } - - /// Get Transaction by [`BlockId`] and the index of the transaction within that Block. - /// - /// Returns `Ok(None)` if the block does not exist, or the block as fewer transactions - pub(crate) async fn transaction_by_block_and_tx_index( - &self, - block_id: impl Into, - index: Index, - ) -> EthResult> { - if let Some(block) = self.block_with_senders(block_id.into()).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) - } - - pub(crate) async fn raw_transaction_by_block_and_tx_index( - &self, - block_id: impl Into, - index: Index, - ) -> EthResult> { - if let Some(block) = self.block_with_senders(block_id.into()).await? { - if let Some(tx) = block.transactions().nth(index.into()) { - return Ok(Some(tx.envelope_encoded())) - } - } - - Ok(None) - } -} - -impl EthApi -where - Provider: BlockReaderIdExt + ChainSpecProvider, -{ - /// Helper function for `eth_getTransactionReceipt` - /// - /// Returns the receipt - #[cfg(not(feature = "optimism"))] - pub(crate) async fn build_transaction_receipt( - &self, - tx: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - ) -> EthResult { - // 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), - }; - build_transaction_receipt_with_block_receipts(tx, meta, receipt, &all_receipts) - } - - /// Helper function for `eth_getTransactionReceipt` (optimism) - /// - /// Returns the receipt - #[cfg(feature = "optimism")] - pub(crate) async fn build_transaction_receipt( - &self, - tx: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - ) -> EthResult { - 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)?; - - build_transaction_receipt_with_block_receipts( - tx, - meta, - receipt, - &receipts, - optimism_tx_meta, - ) - } - - /// Builds op metadata 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. - #[cfg(feature = "optimism")] - pub(crate) fn build_op_tx_meta( - &self, - tx: &TransactionSigned, - l1_block_info: Option, - block_timestamp: u64, - ) -> EthResult { - use crate::eth::{api::optimism::OptimismTxMeta, optimism::OptimismEthApiError}; - use reth_evm_optimism::RethL1BlockInfo; - - 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::()), - Some(inner_l1_data_gas.saturating_to::()), - ) - } else { - (None, None) - }; - - Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas)) - } -} - -/// 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, - }, -} - -// === 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 for TransactionSignedEcRecovered { - fn from(value: TransactionSource) -> Self { - match value { - TransactionSource::Pool(tx) => tx, - TransactionSource::Block { transaction, .. } => transaction, - } - } -} - -impl From 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, - ) - } - } - } -} - -/// Helper function to construct a transaction receipt -/// -/// Note: This requires _all_ block receipts because we need to calculate the gas used by the -/// transaction. -pub(crate) fn build_transaction_receipt_with_block_receipts( - transaction: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - all_receipts: &[Receipt], - #[cfg(feature = "optimism")] optimism_tx_meta: crate::eth::api::optimism::OptimismTxMeta, -) -> EthResult { - // 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.into_iter().enumerate() { - let rpclog = Log { - inner: log, - 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, - }; - - #[allow(clippy::needless_update)] - let res_receipt = 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: None, - gas_used: gas_used as u128, - contract_address: None, - 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), - }; - let mut res_receipt = WithOtherFields::new(res_receipt); - - #[cfg(feature = "optimism")] - { - let mut op_fields = OptimismTransactionReceiptFields::default(); - - if transaction.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::() - }); - 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()); - } - - res_receipt.other = op_fields.into(); - } - - match transaction.transaction.kind() { - Create => { - res_receipt.contract_address = Some(from.create(transaction.transaction.nonce())); - } - Call(addr) => { - res_receipt.to = Some(Address(*addr)); - } - } - - Ok(res_receipt) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::eth::{ - cache::EthStateCache, gas_oracle::GasPriceOracle, FeeHistoryCache, FeeHistoryCacheConfig, - }; - use reth_evm_ethereum::EthEvmConfig; - use reth_network_api::noop::NoopNetwork; - use reth_primitives::{constants::ETHEREUM_BLOCK_GAS_LIMIT, hex_literal::hex}; - use reth_provider::test_utils::NoopProvider; - use reth_tasks::pool::BlockingTaskPool; - use reth_transaction_pool::test_utils::testing_pool; - - #[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"); - } -} diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs deleted file mode 100644 index 8d8e982c2c79..000000000000 --- a/crates/rpc/rpc/src/eth/mod.rs +++ /dev/null @@ -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; diff --git a/crates/rpc/rpc/src/eth/optimism.rs b/crates/rpc/rpc/src/eth/optimism.rs deleted file mode 100644 index fb1665b95785..000000000000 --- a/crates/rpc/rpc/src/eth/optimism.rs +++ /dev/null @@ -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 for EthApiError { - fn from(err: OptimismEthApiError) -> Self { - Self::other(err) - } -} diff --git a/crates/rpc/rpc/src/eth/traits.rs b/crates/rpc/rpc/src/eth/traits.rs deleted file mode 100644 index 0f73ded3c82f..000000000000 --- a/crates/rpc/rpc/src/eth/traits.rs +++ /dev/null @@ -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<()>; -} diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index 17dc8fcb809f..fbd9244734b2 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -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; diff --git a/crates/rpc/rpc/src/net.rs b/crates/rpc/rpc/src/net.rs index 8e6615a281bf..c3a1229a5c39 100644 --- a/crates/rpc/rpc/src/net.rs +++ b/crates/rpc/rpc/src/net.rs @@ -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. diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index e658402f3a94..9405ffd3e1c1 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -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 OtterscanApi { #[async_trait] impl OtterscanServer for OtterscanApi where - Eth: EthApiServer + EthTransactions, + Eth: EthApiServer + TraceExt + 'static, { /// Handler for `ots_hasCode` async fn has_code(&self, address: Address, block_number: Option) -> RpcResult { diff --git a/crates/rpc/rpc/src/reth.rs b/crates/rpc/rpc/src/reth.rs index 17925b5ab8bf..11c437e696a6 100644 --- a/crates/rpc/rpc/src/reth.rs +++ b/crates/rpc/rpc/src/reth.rs @@ -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. diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index e959a367e3f3..2c3e9334a43f 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -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 TraceApi { impl TraceApi 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 { @@ -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 + // + 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 TraceApiServer for TraceApi 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. /// diff --git a/crates/rpc/rpc/src/web3.rs b/crates/rpc/rpc/src/web3.rs index 4ed94ac85523..78464327aa2c 100644 --- a/crates/rpc/rpc/src/web3.rs +++ b/crates/rpc/rpc/src/web3.rs @@ -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. diff --git a/crates/storage/db-api/Cargo.toml b/crates/storage/db-api/Cargo.toml index 07d402d4afa8..7286e03f2da4 100644 --- a/crates/storage/db-api/Cargo.toml +++ b/crates/storage/db-api/Cargo.toml @@ -71,4 +71,4 @@ arbitrary = [ "dep:arbitrary", "dep:proptest", ] -optimism = [] +optimism = ["reth-primitives/optimism"] diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index f5ef4ea5fcce..df6467336507 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -314,70 +314,69 @@ 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); - assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); - assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); - assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); - assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(Header::bitflag_encoded_bytes(), 4); - assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); - assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); - assert_eq!(Receipt::bitflag_encoded_bytes(), 1); - assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); - assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); - assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); - assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); - assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); - assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); - assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); - assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); - assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); - } - - #[cfg(feature = "optimism")] - { - assert_eq!(Account::bitflag_encoded_bytes(), 2); - assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); - assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); - assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); - assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); - assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(Header::bitflag_encoded_bytes(), 4); - assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); - assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); - assert_eq!(Receipt::bitflag_encoded_bytes(), 2); - assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); - assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); - assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); - assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); - assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); - assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); - assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); - assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); - assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); - } + assert_eq!(Account::bitflag_encoded_bytes(), 2); + assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); + assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); + assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); + assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); + assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(Header::bitflag_encoded_bytes(), 4); + assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); + assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); + assert_eq!(Receipt::bitflag_encoded_bytes(), 1); + assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); + assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); + assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); + assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); + assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); + assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); + assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); + assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); + assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); + } + + #[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); + assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); + assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); + assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); + assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(Header::bitflag_encoded_bytes(), 4); + assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); + assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); + assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); + assert_eq!(Receipt::bitflag_encoded_bytes(), 2); + assert_eq!(ReceiptWithBloom::bitflag_encoded_bytes(), 0); + assert_eq!(SealedHeader::bitflag_encoded_bytes(), 0); + assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); + assert_eq!(StoredBlockOmmers::bitflag_encoded_bytes(), 0); + assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); + assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); + assert_eq!(TxEip1559::bitflag_encoded_bytes(), 4); + assert_eq!(TxEip2930::bitflag_encoded_bytes(), 3); + assert_eq!(TxEip4844::bitflag_encoded_bytes(), 5); + assert_eq!(TxLegacy::bitflag_encoded_bytes(), 3); + assert_eq!(Withdrawals::bitflag_encoded_bytes(), 0); } } diff --git a/crates/storage/nippy-jar/src/error.rs b/crates/storage/nippy-jar/src/error.rs index a440a9cb3e30..225d4fba30fb 100644 --- a/crates/storage/nippy-jar/src/error.rs +++ b/crates/storage/nippy-jar/src/error.rs @@ -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")] diff --git a/crates/storage/provider/src/traits/spec.rs b/crates/storage/provider/src/traits/spec.rs index 917051d97aca..798bfeae16fd 100644 --- a/crates/storage/provider/src/traits/spec.rs +++ b/crates/storage/provider/src/traits/spec.rs @@ -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; diff --git a/crates/storage/storage-api/src/receipts.rs b/crates/storage/storage-api/src/receipts.rs index b050ca3e248c..04eb81aad02d 100644 --- a/crates/storage/storage-api/src/receipts.rs +++ b/crates/storage/storage-api/src/receipts.rs @@ -38,6 +38,7 @@ pub trait ReceiptProvider: Send + Sync { /// so this trait can only be implemented for types that implement `BlockIdReader`. The /// `BlockIdReader` methods should be used to resolve `BlockId`s to block numbers or hashes, and /// retrieving the receipts should be done using the type's `ReceiptProvider` methods. +#[auto_impl::auto_impl(&, Arc)] pub trait ReceiptProviderIdExt: ReceiptProvider + BlockIdReader { /// Get receipt by block id fn receipts_by_block_id(&self, block: BlockId) -> ProviderResult>> { diff --git a/crates/tasks/Cargo.toml b/crates/tasks/Cargo.toml index 63eb870fc15e..82c80c0932b8 100644 --- a/crates/tasks/Cargo.toml +++ b/crates/tasks/Cargo.toml @@ -23,6 +23,7 @@ reth-metrics.workspace = true metrics.workspace = true # misc +auto_impl.workspace = true tracing.workspace = true thiserror.workspace = true dyn-clone.workspace = true diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index 2d3f5f41b2b6..a0070698fcff 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -84,6 +84,7 @@ pub mod pool; /// ``` /// /// The [`TaskSpawner`] trait is [`DynClone`] so `Box` are also `Clone`. +#[auto_impl::auto_impl(&, Arc)] pub trait TaskSpawner: Send + Sync + Unpin + std::fmt::Debug + DynClone { /// Spawns the task onto the runtime. /// See also [`Handle::spawn`]. @@ -580,6 +581,7 @@ impl TaskSpawner for TaskExecutor { } /// `TaskSpawner` with extended behaviour +#[auto_impl::auto_impl(&, Arc)] pub trait TaskSpawnerExt: Send + Sync + Unpin + std::fmt::Debug + DynClone { /// This spawns a critical task onto the runtime. /// diff --git a/crates/tasks/src/pool.rs b/crates/tasks/src/pool.rs index dbb4e19de98d..10fedccedd1f 100644 --- a/crates/tasks/src/pool.rs +++ b/crates/tasks/src/pool.rs @@ -43,7 +43,8 @@ impl BlockingTaskGuard { /// /// This is a dedicated threadpool for blocking tasks which are CPU bound. /// RPC calls that perform blocking IO (disk lookups) are not executed on this pool but on the tokio -/// runtime's blocking pool, which performs poorly with CPU bound tasks. Once the tokio blocking +/// runtime's blocking pool, which performs poorly with CPU bound tasks (see +/// ). Once the tokio blocking /// pool is saturated it is converted into a queue, blocking tasks could then interfere with the /// queue and block other RPC calls. /// diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index 41d57e94165e..e43c45e17e82 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -39,7 +39,7 @@ pub type PeerId = reth_primitives::B512; /// /// Note: This requires `Clone` for convenience, since it is assumed that this will be implemented /// for a wrapped `Arc` type, see also [`Pool`](crate::Pool). -#[auto_impl::auto_impl(Arc)] +#[auto_impl::auto_impl(&, Arc)] pub trait TransactionPool: Send + Sync + Clone { /// The transaction type of the pool type Transaction: PoolTransaction; @@ -388,7 +388,7 @@ pub trait TransactionPool: Send + Sync + Clone { } /// Extension for [TransactionPool] trait that allows to set the current block info. -#[auto_impl::auto_impl(Arc)] +#[auto_impl::auto_impl(&, Arc)] pub trait TransactionPoolExt: TransactionPool { /// Sets the current block info for the pool. fn set_block_info(&self, info: BlockInfo); diff --git a/examples/custom-dev-node/src/main.rs b/examples/custom-dev-node/src/main.rs index 498971dbd1b6..5f346e7a2932 100644 --- a/examples/custom-dev-node/src/main.rs +++ b/examples/custom-dev-node/src/main.rs @@ -7,7 +7,7 @@ use futures_util::StreamExt; use reth::{ builder::{NodeBuilder, NodeHandle}, providers::CanonStateSubscriptions, - rpc::eth::EthTransactions, + rpc::eth::servers::EthTransactions, tasks::TaskManager, }; use reth_chainspec::ChainSpec; diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 9d394126fde2..638a2dc172cb 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -2,13 +2,12 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] -use alloy_genesis::Genesis; use reth::{ builder::{components::ExecutorBuilder, BuilderContext, NodeBuilder}, primitives::{ address, - revm_primitives::{CfgEnvWithHandlerCfg, Env, PrecompileResult, TxEnv}, - Address, Bytes, U256, + revm_primitives::{Env, PrecompileResult}, + Bytes, }, revm::{ handler::register::EvmHandler, @@ -21,8 +20,8 @@ use reth::{ use reth_chainspec::{Chain, ChainSpec}; use reth_node_api::{ConfigureEvm, ConfigureEvmEnv, FullNodeTypes}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; -use reth_node_ethereum::{EthEvmConfig, EthExecutorProvider, EthereumNode}; -use reth_primitives::{Header, TransactionSigned}; +use reth_node_ethereum::{EthExecutorProvider, EthereumNode}; +use reth_primitives::Genesis; use reth_tracing::{RethTracer, Tracer}; use std::sync::Arc; @@ -62,20 +61,7 @@ impl MyEvmConfig { } } -impl ConfigureEvmEnv for MyEvmConfig { - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - EthEvmConfig::fill_tx_env(tx_env, transaction, sender) - } - - fn fill_cfg_env( - cfg_env: &mut CfgEnvWithHandlerCfg, - chain_spec: &ChainSpec, - header: &Header, - total_difficulty: U256, - ) { - EthEvmConfig::fill_cfg_env(cfg_env, chain_spec, header, total_difficulty) - } -} +impl ConfigureEvmEnv for MyEvmConfig {} impl ConfigureEvm for MyEvmConfig { type DefaultExternalContext<'a> = (); diff --git a/examples/custom-inspector/src/main.rs b/examples/custom-inspector/src/main.rs index c1aae0227fc0..b3f33af07437 100644 --- a/examples/custom-inspector/src/main.rs +++ b/examples/custom-inspector/src/main.rs @@ -21,7 +21,7 @@ use reth::{ interpreter::{Interpreter, OpCode}, Database, Evm, EvmContext, Inspector, }, - rpc::{compat::transaction::transaction_to_call_request, eth::EthTransactions}, + rpc::{compat::transaction::transaction_to_call_request, eth::servers::Call}, transaction_pool::TransactionPool, }; use reth_node_ethereum::node::EthereumNode; diff --git a/examples/stateful-precompile/src/main.rs b/examples/stateful-precompile/src/main.rs index 8eaecb29ac73..adca439d764a 100644 --- a/examples/stateful-precompile/src/main.rs +++ b/examples/stateful-precompile/src/main.rs @@ -133,8 +133,8 @@ impl StatefulPrecompileMut for WrappedPrecompile { } impl ConfigureEvmEnv for MyEvmConfig { - fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { - EthEvmConfig::fill_tx_env(tx_env, transaction, sender) + fn fill_tx_env(&self, tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { + EthEvmConfig::default().fill_tx_env(tx_env, transaction, sender) } fn fill_cfg_env(