From 895b0e9f828e1868af627506c8cbd3966519cc80 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 10 Jun 2025 12:42:12 +0200 Subject: [PATCH 001/274] feat: reintroduce generic executors (#16741) --- crates/ethereum/evm/tests/execute.rs | 23 +++++++++++++---------- crates/evm/evm/src/lib.rs | 13 ++++++++++--- crates/optimism/evm/src/execute.rs | 6 +++--- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/crates/ethereum/evm/tests/execute.rs b/crates/ethereum/evm/tests/execute.rs index c7f408f3f16..61e0c1c4b66 100644 --- a/crates/ethereum/evm/tests/execute.rs +++ b/crates/ethereum/evm/tests/execute.rs @@ -12,7 +12,10 @@ use alloy_evm::block::BlockValidationError; use alloy_primitives::{b256, fixed_bytes, keccak256, Bytes, TxKind, B256, U256}; use reth_chainspec::{ChainSpecBuilder, EthereumHardfork, ForkCondition, MAINNET}; use reth_ethereum_primitives::{Block, BlockBody, Transaction}; -use reth_evm::{execute::Executor, ConfigureEvm}; +use reth_evm::{ + execute::{BasicBlockExecutor, Executor}, + ConfigureEvm, +}; use reth_evm_ethereum::EthEvmConfig; use reth_execution_types::BlockExecutionResult; use reth_primitives_traits::{ @@ -76,7 +79,7 @@ fn eip_4788_non_genesis_call() { let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // attempt to execute a block without parent beacon block root, expect err let err = executor @@ -197,7 +200,7 @@ fn eip_4788_empty_account_call() { ..Header::default() }; - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // attempt to execute an empty block with parent beacon block root, this should not fail executor @@ -230,7 +233,7 @@ fn eip_4788_genesis_call() { let mut header = chain_spec.genesis_header().clone(); let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // attempt to execute the genesis block with non-zero parent beacon block root, expect err header.parent_beacon_block_root = Some(B256::with_last_byte(0x69)); @@ -291,7 +294,7 @@ fn eip_4788_high_base_fee() { let provider = EthEvmConfig::new(chain_spec); // execute header - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // Now execute a block with the fixed header, ensure that it does not fail executor @@ -354,7 +357,7 @@ fn eip_2935_pre_fork() { ); let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // construct the header for block one let header = Header { timestamp: 1, number: 1, ..Header::default() }; @@ -392,7 +395,7 @@ fn eip_2935_fork_activation_genesis() { let header = chain_spec.genesis_header().clone(); let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // attempt to execute genesis block, this should not fail executor @@ -436,7 +439,7 @@ fn eip_2935_fork_activation_within_window_bounds() { ..Header::default() }; let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // attempt to execute the fork activation block, this should not fail executor @@ -478,7 +481,7 @@ fn eip_2935_fork_activation_outside_window_bounds() { ); let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); let header = Header { parent_hash: B256::random(), @@ -520,7 +523,7 @@ fn eip_2935_state_transition_inside_fork() { let header_hash = header.hash_slow(); let provider = EthEvmConfig::new(chain_spec); - let mut executor = provider.batch_executor(db); + let mut executor = BasicBlockExecutor::new(provider, db); // attempt to execute the genesis block, this should not fail executor diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 4555149df43..e7735572f9e 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -17,7 +17,7 @@ extern crate alloc; -use crate::execute::BasicBlockBuilder; +use crate::execute::{BasicBlockBuilder, Executor}; use alloc::vec::Vec; use alloy_eips::{ eip2718::{EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID}, @@ -31,6 +31,7 @@ use alloy_evm::{ use alloy_primitives::{Address, B256}; use core::{error::Error, fmt::Debug}; use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder}; +use reth_execution_errors::BlockExecutionError; use reth_primitives_traits::{ BlockTy, HeaderTy, NodePrimitives, ReceiptTy, SealedBlock, SealedHeader, TxTy, }; @@ -281,13 +282,19 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// Returns a new [`BasicBlockExecutor`]. #[auto_impl(keep_default_for(&, Arc))] - fn executor(&self, db: DB) -> BasicBlockExecutor<&Self, DB> { + fn executor( + &self, + db: DB, + ) -> impl Executor { BasicBlockExecutor::new(self, db) } /// Returns a new [`BasicBlockExecutor`]. #[auto_impl(keep_default_for(&, Arc))] - fn batch_executor(&self, db: DB) -> BasicBlockExecutor<&Self, DB> { + fn batch_executor( + &self, + db: DB, + ) -> impl Executor { BasicBlockExecutor::new(self, db) } } diff --git a/crates/optimism/evm/src/execute.rs b/crates/optimism/evm/src/execute.rs index 841c5e4603d..ff8a72dc82a 100644 --- a/crates/optimism/evm/src/execute.rs +++ b/crates/optimism/evm/src/execute.rs @@ -12,7 +12,7 @@ mod tests { use op_alloy_consensus::TxDeposit; use op_revm::constants::L1_BLOCK_CONTRACT; use reth_chainspec::MIN_TRANSACTION_GAS; - use reth_evm::{execute::Executor, ConfigureEvm}; + use reth_evm::execute::{BasicBlockExecutor, Executor}; use reth_optimism_chainspec::{OpChainSpec, OpChainSpecBuilder}; use reth_optimism_primitives::{OpReceipt, OpTransactionSigned}; use reth_primitives_traits::{Account, RecoveredBlock}; @@ -90,7 +90,7 @@ mod tests { .into(); let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); + let mut executor = BasicBlockExecutor::new(provider, StateProviderDatabase::new(&db)); // make sure the L1 block contract state is preloaded. executor.with_state_mut(|state| { @@ -163,7 +163,7 @@ mod tests { .into(); let provider = evm_config(chain_spec); - let mut executor = provider.batch_executor(StateProviderDatabase::new(&db)); + let mut executor = BasicBlockExecutor::new(provider, StateProviderDatabase::new(&db)); // make sure the L1 block contract state is preloaded. executor.with_state_mut(|state| { From 1bef0092eedb6a1cd87fd181263fd47bcdcdf4de Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 10 Jun 2025 14:32:18 +0200 Subject: [PATCH 002/274] fix: small networking fixes (#16742) --- crates/net/network-types/src/peers/mod.rs | 8 ++++++-- crates/net/network/src/peers.rs | 2 +- crates/net/network/src/session/active.rs | 4 ++++ crates/node/builder/src/launch/engine.rs | 11 +++++++---- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/net/network-types/src/peers/mod.rs b/crates/net/network-types/src/peers/mod.rs index 2f0bd6141b8..f3529875018 100644 --- a/crates/net/network-types/src/peers/mod.rs +++ b/crates/net/network-types/src/peers/mod.rs @@ -83,12 +83,16 @@ impl Peer { } /// Applies a reputation change to the peer and returns what action should be taken. - pub fn apply_reputation(&mut self, reputation: i32) -> ReputationChangeOutcome { + pub fn apply_reputation( + &mut self, + reputation: i32, + kind: ReputationChangeKind, + ) -> ReputationChangeOutcome { let previous = self.reputation; // we add reputation since negative reputation change decrease total reputation self.reputation = previous.saturating_add(reputation); - trace!(target: "net::peers", reputation=%self.reputation, banned=%self.is_banned(), "applied reputation change"); + trace!(target: "net::peers", reputation=%self.reputation, banned=%self.is_banned(), ?kind, "applied reputation change"); if self.state.is_connected() && self.is_banned() { self.state.disconnect(); diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index bb69d1adc76..c0694023ceb 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -480,7 +480,7 @@ impl PeersManager { reputation_change = MAX_TRUSTED_PEER_REPUTATION_CHANGE; } } - peer.apply_reputation(reputation_change) + peer.apply_reputation(reputation_change, rep) } } else { return diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 19f57f0f249..cfbe9ef744e 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -174,6 +174,7 @@ impl ActiveSession { if let Some(req) = self.inflight_requests.remove(&request_id) { match req.request { RequestState::Waiting(PeerRequest::$item { response, .. }) => { + trace!(peer_id=?self.remote_peer_id, ?request_id, "received response from peer"); let _ = response.send(Ok(message)); self.update_request_timeout(req.timestamp, Instant::now()); } @@ -186,6 +187,7 @@ impl ActiveSession { } } } else { + trace!(peer_id=?self.remote_peer_id, ?request_id, "received response to unknown request"); // we received a response to a request we never sent self.on_bad_message(); } @@ -277,6 +279,8 @@ impl ActiveSession { /// Handle an internal peer request that will be sent to the remote. fn on_internal_peer_request(&mut self, request: PeerRequest, deadline: Instant) { let request_id = self.next_id(); + + trace!(?request, peer_id=?self.remote_peer_id, ?request_id, "sending request to peer"); let msg = request.create_request_message(request_id); self.queued_outgoing.push_back(msg.into()); let req = InflightRequest { diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 295b4fa1916..29e8bccc5ab 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -138,11 +138,15 @@ where .await?; // create pipeline - let network_client = ctx.components().network().fetch_client().await?; + let network_handle = ctx.components().network().clone(); + let network_client = network_handle.fetch_client().await?; let (consensus_engine_tx, consensus_engine_rx) = unbounded_channel(); let node_config = ctx.node_config(); + // We always assume that node is syncing after a restart + network_handle.update_sync_state(SyncState::Syncing); + let max_block = ctx.max_block(network_client.clone()).await?; let static_file_producer = ctx.static_file_producer(); @@ -289,7 +293,6 @@ where // Run consensus engine to completion let initial_target = ctx.initial_backfill_target()?; - let network_handle = ctx.components().network().clone(); let mut built_payloads = ctx .components() .payload_builder_handle() @@ -329,8 +332,6 @@ where debug!(target: "reth::cli", "Terminating after initial backfill"); break } - - network_handle.update_sync_state(SyncState::Idle); } ChainEvent::BackfillSyncStarted => { network_handle.update_sync_state(SyncState::Syncing); @@ -342,6 +343,8 @@ where } ChainEvent::Handler(ev) => { if let Some(head) = ev.canonical_header() { + // Once we're progressing via live sync, we can consider the node is not syncing anymore + network_handle.update_sync_state(SyncState::Idle); let head_block = Head { number: head.number(), hash: head.hash(), From 7e1b80b3b885c61f31e620f9497b582d39f5618a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 10 Jun 2025 23:16:44 +0200 Subject: [PATCH 003/274] ci: Add `sync-era` workflow that syncs with ERA stage enabled (#16751) --- .github/workflows/sync-era.yml | 67 ++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/sync-era.yml diff --git a/.github/workflows/sync-era.yml b/.github/workflows/sync-era.yml new file mode 100644 index 00000000000..973dc5ec036 --- /dev/null +++ b/.github/workflows/sync-era.yml @@ -0,0 +1,67 @@ +# Runs sync tests with ERA stage enabled. + +name: sync-era test + +on: + workflow_dispatch: + schedule: + - cron: "0 */6 * * *" + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + sync: + name: sync (${{ matrix.chain.bin }}) + runs-on: + group: Reth + env: + RUST_LOG: info,sync=error + RUST_BACKTRACE: 1 + timeout-minutes: 60 + strategy: + matrix: + chain: + - build: install + bin: reth + chain: mainnet + tip: "0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4" + block: 100000 + unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a" + - build: install-op + bin: op-reth + chain: base + tip: "0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7" + block: 10000 + unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de" + steps: + - uses: actions/checkout@v4 + - uses: rui314/setup-mold@v1 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Build ${{ matrix.chain.bin }} + run: make ${{ matrix.chain.build }} + - name: Run sync with ERA enabled + run: | + ${{ matrix.chain.bin }} node \ + --chain ${{ matrix.chain.chain }} \ + --debug.tip ${{ matrix.chain.tip }} \ + --debug.max-block ${{ matrix.chain.block }} \ + --debug.terminate + --era.enable + - name: Verify the target block hash + run: | + ${{ matrix.chain.bin }} db --chain ${{ matrix.chain.chain }} get static-file headers ${{ matrix.chain.block }} \ + | grep ${{ matrix.chain.tip }} + - name: Run stage unwind for 100 blocks + run: | + ${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }} + - name: Run stage unwind to block hash + run: | + ${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }} From a410b599f15f1834003d478cc9a5247abe9304c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Wed, 11 Jun 2025 08:26:10 +0200 Subject: [PATCH 004/274] ci(sync): Change schedule to run once every 6 hours (#16754) --- .github/workflows/sync.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 952cab361ab..c19ec73ea9b 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -3,7 +3,9 @@ name: sync test on: - merge_group: + workflow_dispatch: + schedule: + - cron: "0 */6 * * *" env: CARGO_TERM_COLOR: always From 628f212debd89cac9d236f119cb9790649316394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Wed, 11 Jun 2025 10:27:04 +0200 Subject: [PATCH 005/274] feat(rpc): Add `TxEnv` conversion function into `RpcConverter` (#16750) --- Cargo.lock | 3 + crates/optimism/rpc/src/error.rs | 8 +- crates/optimism/rpc/src/eth/call.rs | 110 +--------- crates/optimism/rpc/src/eth/mod.rs | 8 +- crates/rpc/rpc-eth-api/src/helpers/call.rs | 23 ++- crates/rpc/rpc-eth-api/src/lib.rs | 4 +- crates/rpc/rpc-eth-types/src/error/mod.rs | 27 ++- crates/rpc/rpc-types-compat/Cargo.toml | 5 + crates/rpc/rpc-types-compat/src/fees.rs | 165 +++++++++++++++ crates/rpc/rpc-types-compat/src/lib.rs | 7 +- .../rpc/rpc-types-compat/src/transaction.rs | 194 ++++++++++++++++-- crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/eth/helpers/call.rs | 110 +--------- crates/rpc/rpc/src/eth/helpers/types.rs | 3 +- 14 files changed, 439 insertions(+), 229 deletions(-) create mode 100644 crates/rpc/rpc-types-compat/src/fees.rs diff --git a/Cargo.lock b/Cargo.lock index 7ec5d723d6b..ddd0d94efc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10017,9 +10017,12 @@ dependencies = [ "jsonrpsee-types", "op-alloy-consensus", "op-alloy-rpc-types", + "op-revm", + "reth-evm", "reth-optimism-primitives", "reth-primitives-traits", "reth-storage-api", + "revm-context", "serde", "thiserror 2.0.12", ] diff --git a/crates/optimism/rpc/src/error.rs b/crates/optimism/rpc/src/error.rs index 134de276f92..f5445863497 100644 --- a/crates/optimism/rpc/src/error.rs +++ b/crates/optimism/rpc/src/error.rs @@ -7,7 +7,7 @@ use jsonrpsee_types::error::{INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE}; use op_revm::{OpHaltReason, OpTransactionError}; use reth_evm::execute::ProviderError; use reth_optimism_evm::OpBlockExecutionError; -use reth_rpc_eth_api::{AsEthApiError, TransactionConversionError}; +use reth_rpc_eth_api::{AsEthApiError, EthTxEnvError, TransactionConversionError}; use reth_rpc_eth_types::{error::api::FromEvmHalt, EthApiError}; use reth_rpc_server_types::result::{internal_rpc_err, rpc_err}; use revm::context_interface::result::{EVMError, InvalidTransaction}; @@ -195,6 +195,12 @@ impl From for OpEthApiError { } } +impl From for OpEthApiError { + fn from(value: EthTxEnvError) -> Self { + Self::Eth(EthApiError::from(value)) + } +} + impl From for OpEthApiError { fn from(value: ProviderError) -> Self { Self::Eth(EthApiError::from(value)) diff --git a/crates/optimism/rpc/src/eth/call.rs b/crates/optimism/rpc/src/eth/call.rs index 87e31ace9be..0459a1e2aa0 100644 --- a/crates/optimism/rpc/src/eth/call.rs +++ b/crates/optimism/rpc/src/eth/call.rs @@ -1,18 +1,14 @@ use super::OpNodeCore; use crate::{OpEthApi, OpEthApiError}; -use alloy_consensus::transaction::Either; -use alloy_primitives::{Bytes, TxKind, U256}; -use alloy_rpc_types_eth::transaction::TransactionRequest; use op_revm::OpTransaction; -use reth_evm::{execute::BlockExecutorFactory, ConfigureEvm, EvmEnv, EvmFactory, SpecFor}; +use reth_evm::{execute::BlockExecutorFactory, ConfigureEvm, EvmFactory, TxEnvFor}; use reth_node_api::NodePrimitives; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadBlock, LoadState, SpawnBlocking}, - FromEthApiError, FromEvmError, FullEthApiTypes, IntoEthApiError, + FromEvmError, FullEthApiTypes, TransactionCompat, }; -use reth_rpc_eth_types::{revm_utils::CallFees, EthApiError, RpcInvalidTransactionError}; -use reth_storage_api::{ProviderHeader, ProviderTx}; -use revm::{context::TxEnv, context_interface::Block, Database}; +use reth_storage_api::{errors::ProviderError, ProviderHeader, ProviderTx}; +use revm::context::TxEnv; impl EthCall for OpEthApi where @@ -41,7 +37,10 @@ where EvmFactory: EvmFactory>, >, >, - Error: FromEvmError, + TransactionCompat: TransactionCompat>, + Error: FromEvmError + + From<::Error> + + From, > + SpawnBlocking, Self::Error: From, N: OpNodeCore, @@ -55,97 +54,4 @@ where fn max_simulate_blocks(&self) -> u64 { self.inner.eth_api.max_simulate_blocks() } - - fn create_txn_env( - &self, - evm_env: &EvmEnv>, - request: TransactionRequest, - mut db: impl Database>, - ) -> Result, Self::Error> { - // Ensure that if versioned hashes are set, they're not empty - if request.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) { - return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into_eth_err()) - } - - let tx_type = request.minimal_tx_type() as u8; - - let TransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - input, - nonce, - access_list, - chain_id, - blob_versioned_hashes, - max_fee_per_blob_gas, - authorization_list, - transaction_type: _, - sidecar: _, - } = request; - - let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } = - CallFees::ensure_fees( - gas_price.map(U256::from), - max_fee_per_gas.map(U256::from), - max_priority_fee_per_gas.map(U256::from), - U256::from(evm_env.block_env.basefee), - blob_versioned_hashes.as_deref(), - max_fee_per_blob_gas.map(U256::from), - evm_env.block_env.blob_gasprice().map(U256::from), - )?; - - let gas_limit = gas.unwrap_or( - // Use maximum allowed gas limit. The reason for this - // is that both Erigon and Geth use pre-configured gas cap even if - // it's possible to derive the gas limit from the block: - // - evm_env.block_env.gas_limit, - ); - - let chain_id = chain_id.unwrap_or(evm_env.cfg_env.chain_id); - - let caller = from.unwrap_or_default(); - - let nonce = if let Some(nonce) = nonce { - nonce - } else { - db.basic(caller).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default() - }; - - let base = TxEnv { - tx_type, - gas_limit, - nonce, - caller, - gas_price: gas_price.saturating_to(), - gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()), - kind: to.unwrap_or(TxKind::Create), - value: value.unwrap_or_default(), - data: input - .try_into_unique_input() - .map_err(Self::Error::from_eth_err)? - .unwrap_or_default(), - chain_id: Some(chain_id), - access_list: access_list.unwrap_or_default(), - // EIP-4844 fields - blob_hashes: blob_versioned_hashes.unwrap_or_default(), - max_fee_per_blob_gas: max_fee_per_blob_gas - .map(|v| v.saturating_to()) - .unwrap_or_default(), - // EIP-7702 fields - authorization_list: authorization_list - .unwrap_or_default() - .into_iter() - .map(Either::Left) - .collect(), - }; - - Ok(OpTransaction { base, enveloped_tx: Some(Bytes::new()), deposit: Default::default() }) - } } diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index e9d2efe04f1..df709baedc9 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -67,7 +67,8 @@ pub struct OpEthApi { inner: Arc>, /// Marker for the network types. _nt: PhantomData, - tx_resp_builder: RpcConverter>, + tx_resp_builder: + RpcConverter>, } impl OpEthApi { @@ -114,12 +115,13 @@ where Self: Send + Sync + fmt::Debug, N: OpNodeCore, NetworkT: op_alloy_network::Network + Clone + fmt::Debug, + ::Evm: fmt::Debug, ::Primitives: fmt::Debug, { type Error = OpEthApiError; type NetworkTypes = NetworkT; type TransactionCompat = - RpcConverter>; + RpcConverter>; fn tx_resp_builder(&self) -> &Self::TransactionCompat { &self.tx_resp_builder @@ -203,6 +205,7 @@ where Self: Send + Sync + Clone + 'static, N: OpNodeCore, NetworkT: op_alloy_network::Network, + ::Evm: fmt::Debug, ::Primitives: fmt::Debug, { #[inline] @@ -254,6 +257,7 @@ where Pool: TransactionPool, >, NetworkT: op_alloy_network::Network, + ::Evm: fmt::Debug, ::Primitives: fmt::Debug, { } diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index dda235ffaf3..e41c8262a03 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -34,6 +34,7 @@ use reth_rpc_eth_types::{ simulate::{self, EthSimulateError}, EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb, }; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{BlockIdReader, ProviderHeader, ProviderTx}; use revm::{ context_interface::{ @@ -455,7 +456,10 @@ pub trait Call: SignedTx = ProviderTx, >, >, - Error: FromEvmError, + TransactionCompat: TransactionCompat>, + Error: FromEvmError + + From<::Error> + + From, > + SpawnBlocking { /// Returns default gas limit to use for `eth_call` and tracing RPC methods. @@ -689,9 +693,20 @@ pub trait Call: fn create_txn_env( &self, evm_env: &EvmEnv>, - request: TransactionRequest, - db: impl Database>, - ) -> Result, Self::Error>; + mut request: TransactionRequest, + mut db: impl Database>, + ) -> Result, Self::Error> { + if request.nonce.is_none() { + request.nonce.replace( + db.basic(request.from.unwrap_or_default()) + .map_err(Into::into)? + .map(|acc| acc.nonce) + .unwrap_or_default(), + ); + } + + Ok(self.tx_resp_builder().tx_env(request, &evm_env.cfg_env, &evm_env.block_env)?) + } /// Prepares the [`EvmEnv`] for execution of calls. /// diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs index 3f1cb449cc5..1a24a2cd4e2 100644 --- a/crates/rpc/rpc-eth-api/src/lib.rs +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -31,8 +31,8 @@ pub use reth_rpc_eth_types::error::{ AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError, }; pub use reth_rpc_types_compat::{ - try_into_op_tx_info, IntoRpcTx, RpcConverter, TransactionCompat, TransactionConversionError, - TryIntoSimTx, TxInfoMapper, + try_into_op_tx_info, CallFeesError, EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, + TransactionConversionError, TryIntoSimTx, TxInfoMapper, }; pub use types::{EthApiTypes, FullEthApiTypes, RpcBlock, RpcHeader, RpcReceipt, RpcTransaction}; diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index eae015060a3..4029cb2dc5a 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -13,7 +13,7 @@ use reth_primitives_traits::transaction::{error::InvalidTransactionError, signed use reth_rpc_server_types::result::{ block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code, }; -use reth_rpc_types_compat::TransactionConversionError; +use reth_rpc_types_compat::{CallFeesError, EthTxEnvError, TransactionConversionError}; use reth_transaction_pool::error::{ Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError, PoolError, PoolErrorKind, PoolTransactionError, @@ -237,6 +237,31 @@ impl From for EthApiError { } } +impl From for EthApiError { + fn from(value: EthTxEnvError) -> Self { + match value { + EthTxEnvError::CallFees(CallFeesError::BlobTransactionMissingBlobHashes) => { + Self::InvalidTransaction( + RpcInvalidTransactionError::BlobTransactionMissingBlobHashes, + ) + } + EthTxEnvError::CallFees(CallFeesError::FeeCapTooLow) => { + Self::InvalidTransaction(RpcInvalidTransactionError::FeeCapTooLow) + } + EthTxEnvError::CallFees(CallFeesError::ConflictingFeeFieldsInRequest) => { + Self::ConflictingFeeFieldsInRequest + } + EthTxEnvError::CallFees(CallFeesError::TipAboveFeeCap) => { + Self::InvalidTransaction(RpcInvalidTransactionError::TipAboveFeeCap) + } + EthTxEnvError::CallFees(CallFeesError::TipVeryHigh) => { + Self::InvalidTransaction(RpcInvalidTransactionError::TipVeryHigh) + } + EthTxEnvError::Input(err) => Self::TransactionInputError(err), + } + } +} + #[cfg(feature = "js-tracer")] impl From for EthApiError { fn from(error: revm_inspectors::tracing::js::JsInspectorError) -> Self { diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index 51f2299eaa9..d56aa569bb2 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -15,6 +15,7 @@ workspace = true # reth reth-primitives-traits.workspace = true reth-storage-api = { workspace = true, features = ["serde", "serde-bincode-compat"] } +reth-evm.workspace = true # ethereum alloy-primitives.workspace = true @@ -26,6 +27,10 @@ alloy-network.workspace = true op-alloy-consensus.workspace = true op-alloy-rpc-types.workspace = true reth-optimism-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] } +op-revm.workspace = true + +# revm +revm-context.workspace = true # io serde.workspace = true diff --git a/crates/rpc/rpc-types-compat/src/fees.rs b/crates/rpc/rpc-types-compat/src/fees.rs new file mode 100644 index 00000000000..45ee7628fc5 --- /dev/null +++ b/crates/rpc/rpc-types-compat/src/fees.rs @@ -0,0 +1,165 @@ +use alloy_primitives::{B256, U256}; +use std::cmp::min; +use thiserror::Error; + +/// Helper type for representing the fees of a `TransactionRequest` +#[derive(Debug)] +pub struct CallFees { + /// EIP-1559 priority fee + pub max_priority_fee_per_gas: Option, + /// Unified gas price setting + /// + /// Will be the configured `basefee` if unset in the request + /// + /// `gasPrice` for legacy, + /// `maxFeePerGas` for EIP-1559 + pub gas_price: U256, + /// Max Fee per Blob gas for EIP-4844 transactions + pub max_fee_per_blob_gas: Option, +} + +impl CallFees { + /// Ensures the fields of a `TransactionRequest` are not conflicting. + /// + /// # EIP-4844 transactions + /// + /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. + /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 + /// transaction. + /// + /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` + /// is always `Some` + /// + /// ## Notable design decisions + /// + /// For compatibility reasons, this contains several exceptions when fee values are validated: + /// - If both `maxFeePerGas` and `maxPriorityFeePerGas` are set to `0` they are treated as + /// missing values, bypassing fee checks wrt. `baseFeePerGas`. + /// + /// This mirrors geth's behaviour when transaction requests are executed: + /// + /// [`BlockEnv`]: revm_context::BlockEnv + pub fn ensure_fees( + call_gas_price: Option, + call_max_fee: Option, + call_priority_fee: Option, + block_base_fee: U256, + blob_versioned_hashes: Option<&[B256]>, + max_fee_per_blob_gas: Option, + block_blob_fee: Option, + ) -> Result { + /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant + /// checks. + fn get_effective_gas_price( + max_fee_per_gas: Option, + max_priority_fee_per_gas: Option, + block_base_fee: U256, + ) -> Result { + match max_fee_per_gas { + Some(max_fee) => { + let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(U256::ZERO); + + // only enforce the fee cap if provided input is not zero + if !(max_fee.is_zero() && max_priority_fee_per_gas.is_zero()) && + max_fee < block_base_fee + { + // `base_fee_per_gas` is greater than the `max_fee_per_gas` + return Err(CallFeesError::FeeCapTooLow) + } + if max_fee < max_priority_fee_per_gas { + return Err( + // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` + CallFeesError::TipAboveFeeCap, + ) + } + // ref + Ok(min( + max_fee, + block_base_fee + .checked_add(max_priority_fee_per_gas) + .ok_or(CallFeesError::TipVeryHigh)?, + )) + } + None => Ok(block_base_fee + .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) + .ok_or(CallFeesError::TipVeryHigh)?), + } + } + + let has_blob_hashes = + blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); + + match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { + (gas_price, None, None, None) => { + // either legacy transaction or no fee fields are specified + // when no fields are specified, set gas price to zero + let gas_price = gas_price.unwrap_or(U256::ZERO); + Ok(Self { + gas_price, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), + }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { + // request for eip-1559 transaction + let effective_gas_price = get_effective_gas_price( + max_fee_per_gas, + max_priority_fee_per_gas, + block_base_fee, + )?; + let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); + + Ok(Self { + gas_price: effective_gas_price, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + }) + } + (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { + // request for eip-4844 transaction + let effective_gas_price = get_effective_gas_price( + max_fee_per_gas, + max_priority_fee_per_gas, + block_base_fee, + )?; + // Ensure blob_hashes are present + if !has_blob_hashes { + // Blob transaction but no blob hashes + return Err(CallFeesError::BlobTransactionMissingBlobHashes) + } + + Ok(Self { + gas_price: effective_gas_price, + max_priority_fee_per_gas, + max_fee_per_blob_gas: Some(max_fee_per_blob_gas), + }) + } + _ => { + // this fallback covers incompatible combinations of fields + Err(CallFeesError::ConflictingFeeFieldsInRequest) + } + } + } +} + +/// Error coming from decoding and validating transaction request fees. +#[derive(Debug, Error)] +pub enum CallFeesError { + /// Thrown when a call or transaction request (`eth_call`, `eth_estimateGas`, + /// `eth_sendTransaction`) contains conflicting fields (legacy, EIP-1559) + #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")] + ConflictingFeeFieldsInRequest, + /// Thrown post London if the transaction's fee is less than the base fee of the block + #[error("max fee per gas less than block base fee")] + FeeCapTooLow, + /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total + /// fee cap. + #[error("max priority fee per gas higher than max fee per gas")] + TipAboveFeeCap, + /// A sanity error to avoid huge numbers specified in the tip field. + #[error("max priority fee per gas higher than 2^256-1")] + TipVeryHigh, + /// Blob transaction has no versioned hashes + #[error("blob transaction missing blob hashes")] + BlobTransactionMissingBlobHashes, +} diff --git a/crates/rpc/rpc-types-compat/src/lib.rs b/crates/rpc/rpc-types-compat/src/lib.rs index b203691263a..86ed5bd255c 100644 --- a/crates/rpc/rpc-types-compat/src/lib.rs +++ b/crates/rpc/rpc-types-compat/src/lib.rs @@ -11,8 +11,11 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] pub mod block; +mod fees; pub mod transaction; + +pub use fees::{CallFees, CallFeesError}; pub use transaction::{ - try_into_op_tx_info, IntoRpcTx, RpcConverter, TransactionCompat, TransactionConversionError, - TryIntoSimTx, TxInfoMapper, + try_into_op_tx_info, EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, + TransactionConversionError, TryIntoSimTx, TxInfoMapper, }; diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index ca531ed9e00..4dca40ce272 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -1,20 +1,30 @@ //! Compatibility functions for rpc `Transaction` type. +use crate::fees::{CallFees, CallFeesError}; use alloy_consensus::{ error::ValueError, transaction::Recovered, EthereumTxEnvelope, SignableTransaction, TxEip4844, }; use alloy_network::Network; -use alloy_primitives::{Address, Signature}; -use alloy_rpc_types_eth::{request::TransactionRequest, Transaction, TransactionInfo}; +use alloy_primitives::{Address, Bytes, Signature, TxKind, U256}; +use alloy_rpc_types_eth::{ + request::{TransactionInputError, TransactionRequest}, + Transaction, TransactionInfo, +}; use core::error; use op_alloy_consensus::{ transaction::{OpDepositInfo, OpTransactionInfo}, OpTxEnvelope, }; use op_alloy_rpc_types::OpTransactionRequest; +use op_revm::OpTransaction; +use reth_evm::{ + revm::context_interface::{either::Either, Block}, + ConfigureEvm, TxEnvFor, +}; use reth_optimism_primitives::DepositReceipt; use reth_primitives_traits::{NodePrimitives, SignedTransaction, TxTy}; use reth_storage_api::{errors::ProviderError, ReceiptProvider}; +use revm_context::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Serialize}; use std::{convert::Infallible, error::Error, fmt::Debug, marker::PhantomData}; use thiserror::Error; @@ -27,6 +37,9 @@ pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { /// RPC transaction response type. type Transaction: Serialize + for<'de> Deserialize<'de> + Send + Sync + Unpin + Clone + Debug; + /// A set of variables for executing a transaction. + type TxEnv; + /// RPC transaction error type. type Error: error::Error + Into>; @@ -57,6 +70,15 @@ pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { &self, request: TransactionRequest, ) -> Result, Self::Error>; + + /// Creates a transaction environment for execution based on `request` with corresponding + /// `cfg_env` and `block_env`. + fn tx_env( + &self, + request: TransactionRequest, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result; } /// Converts `self` into `T`. @@ -171,6 +193,137 @@ impl TryIntoSimTx for TransactionRequest { } } +/// Converts `self` into `T`. +/// +/// Should create an executable transaction environment using [`TransactionRequest`]. +pub trait TryIntoTxEnv { + /// An associated error that can occur during the conversion. + type Err; + + /// Performs the conversion. + fn try_into_tx_env( + self, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result; +} + +/// An Ethereum specific transaction environment error than can occur during conversion from +/// [`TransactionRequest`]. +#[derive(Debug, Error)] +pub enum EthTxEnvError { + /// Error while decoding or validating transaction request fees. + #[error(transparent)] + CallFees(#[from] CallFeesError), + /// Both data and input fields are set and not equal. + #[error(transparent)] + Input(#[from] TransactionInputError), +} + +impl TryIntoTxEnv> for TransactionRequest { + type Err = EthTxEnvError; + + fn try_into_tx_env( + self, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result, Self::Err> { + Ok(OpTransaction { + base: self.try_into_tx_env(cfg_env, block_env)?, + enveloped_tx: Some(Bytes::new()), + deposit: Default::default(), + }) + } +} +impl TryIntoTxEnv for TransactionRequest { + type Err = EthTxEnvError; + + fn try_into_tx_env( + self, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result { + // Ensure that if versioned hashes are set, they're not empty + if self.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) { + return Err(CallFeesError::BlobTransactionMissingBlobHashes.into()) + } + + let tx_type = self.minimal_tx_type() as u8; + + let Self { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + gas, + value, + input, + nonce, + access_list, + chain_id, + blob_versioned_hashes, + max_fee_per_blob_gas, + authorization_list, + transaction_type: _, + sidecar: _, + } = self; + + let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } = + CallFees::ensure_fees( + gas_price.map(U256::from), + max_fee_per_gas.map(U256::from), + max_priority_fee_per_gas.map(U256::from), + U256::from(block_env.basefee), + blob_versioned_hashes.as_deref(), + max_fee_per_blob_gas.map(U256::from), + block_env.blob_gasprice().map(U256::from), + )?; + + let gas_limit = gas.unwrap_or( + // Use maximum allowed gas limit. The reason for this + // is that both Erigon and Geth use pre-configured gas cap even if + // it's possible to derive the gas limit from the block: + // + block_env.gas_limit, + ); + + let chain_id = chain_id.unwrap_or(cfg_env.chain_id); + + let caller = from.unwrap_or_default(); + + let nonce = nonce.unwrap_or_default(); + + let env = TxEnv { + tx_type, + gas_limit, + nonce, + caller, + gas_price: gas_price.saturating_to(), + gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()), + kind: to.unwrap_or(TxKind::Create), + value: value.unwrap_or_default(), + data: input.try_into_unique_input().map_err(EthTxEnvError::from)?.unwrap_or_default(), + chain_id: Some(chain_id), + access_list: access_list.unwrap_or_default(), + // EIP-4844 fields + blob_hashes: blob_versioned_hashes.unwrap_or_default(), + max_fee_per_blob_gas: max_fee_per_blob_gas + .map(|v| v.saturating_to()) + .unwrap_or_default(), + // EIP-7702 fields + authorization_list: authorization_list + .unwrap_or_default() + .into_iter() + .map(Either::Left) + .collect(), + }; + + Ok(env) + } +} + /// Conversion into transaction RPC response failed. #[derive(Debug, Clone, Error)] #[error("Failed to convert transaction into RPC response: {0}")] @@ -178,59 +331,64 @@ pub struct TransactionConversionError(String); /// Generic RPC response object converter for primitives `N` and network `E`. #[derive(Debug)] -pub struct RpcConverter { - phantom: PhantomData<(N, E, Err)>, +pub struct RpcConverter { + phantom: PhantomData<(N, E, Evm, Err)>, mapper: Map, } -impl RpcConverter { +impl RpcConverter { /// Creates a new [`RpcConverter`] with the default mapper. pub const fn new() -> Self { Self::with_mapper(()) } } -impl RpcConverter { +impl RpcConverter { /// Creates a new [`RpcConverter`] with `mapper`. pub const fn with_mapper(mapper: Map) -> Self { Self { phantom: PhantomData, mapper } } /// Converts the generic types. - pub fn convert(self) -> RpcConverter { + pub fn convert(self) -> RpcConverter { RpcConverter::with_mapper(self.mapper) } /// Swaps the inner `mapper`. - pub fn map(self, mapper: Map2) -> RpcConverter { + pub fn map(self, mapper: Map2) -> RpcConverter { RpcConverter::with_mapper(mapper) } /// Converts the generic types and swaps the inner `mapper`. - pub fn convert_map(self, mapper: Map2) -> RpcConverter { + pub fn convert_map( + self, + mapper: Map2, + ) -> RpcConverter { self.convert().map(mapper) } } -impl Clone for RpcConverter { +impl Clone for RpcConverter { fn clone(&self) -> Self { Self::with_mapper(self.mapper.clone()) } } -impl Default for RpcConverter { +impl Default for RpcConverter { fn default() -> Self { Self::new() } } -impl TransactionCompat for RpcConverter +impl TransactionCompat for RpcConverter where N: NodePrimitives, E: Network + Unpin, + Evm: ConfigureEvm, TxTy: IntoRpcTx<::TransactionResponse> + Clone + Debug, - TransactionRequest: TryIntoSimTx>, + TransactionRequest: TryIntoSimTx> + TryIntoTxEnv>, Err: From + + From<>>::Err> + for<'a> From<>>::Err> + Error + Unpin @@ -248,6 +406,7 @@ where { type Primitives = N; type Transaction = ::TransactionResponse; + type TxEnv = TxEnvFor; type Error = Err; fn fill( @@ -267,4 +426,13 @@ where ) -> Result, Self::Error> { Ok(request.try_into_sim_tx().map_err(|e| TransactionConversionError(e.to_string()))?) } + + fn tx_env( + &self, + request: TransactionRequest, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result { + Ok(request.try_into_tx_env(cfg_env, block_env)?) + } } diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index e289c60a459..f8b264fb2b1 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -33,6 +33,7 @@ reth-rpc-types-compat.workspace = true revm-inspectors.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } reth-evm.workspace = true +reth-evm-ethereum.workspace = true reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-network-types.workspace = true diff --git a/crates/rpc/rpc/src/eth/helpers/call.rs b/crates/rpc/rpc/src/eth/helpers/call.rs index ab6adb53f39..479aa28b399 100644 --- a/crates/rpc/rpc/src/eth/helpers/call.rs +++ b/crates/rpc/rpc/src/eth/helpers/call.rs @@ -2,19 +2,17 @@ use crate::EthApi; use alloy_evm::block::BlockExecutorFactory; -use alloy_primitives::{TxKind, U256}; -use alloy_rpc_types::TransactionRequest; -use alloy_signer::Either; -use reth_evm::{ConfigureEvm, EvmEnv, EvmFactory, SpecFor}; +use reth_errors::ProviderError; +use reth_evm::{ConfigureEvm, EvmFactory, TxEnvFor}; use reth_node_api::NodePrimitives; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}, - FromEthApiError, FromEvmError, FullEthApiTypes, IntoEthApiError, RpcNodeCore, RpcNodeCoreExt, + FromEvmError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, }; -use reth_rpc_eth_types::{revm_utils::CallFees, EthApiError, RpcInvalidTransactionError}; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{BlockReader, ProviderHeader, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use revm::{context::TxEnv, context_interface::Block, Database}; +use revm::context::TxEnv; impl EthCall for EthApi where @@ -43,7 +41,10 @@ where SignedTx = ProviderTx, >, >, - Error: FromEvmError, + TransactionCompat: TransactionCompat>, + Error: FromEvmError + + From<::Error> + + From, > + SpawnBlocking, Provider: BlockReader, { @@ -56,99 +57,6 @@ where fn max_simulate_blocks(&self) -> u64 { self.inner.max_simulate_blocks() } - - fn create_txn_env( - &self, - evm_env: &EvmEnv>, - request: TransactionRequest, - mut db: impl Database>, - ) -> Result { - // Ensure that if versioned hashes are set, they're not empty - if request.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) { - return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into_eth_err()) - } - - let tx_type = request.minimal_tx_type() as u8; - - let TransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - input, - nonce, - access_list, - chain_id, - blob_versioned_hashes, - max_fee_per_blob_gas, - authorization_list, - transaction_type: _, - sidecar: _, - } = request; - - let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } = - CallFees::ensure_fees( - gas_price.map(U256::from), - max_fee_per_gas.map(U256::from), - max_priority_fee_per_gas.map(U256::from), - U256::from(evm_env.block_env.basefee), - blob_versioned_hashes.as_deref(), - max_fee_per_blob_gas.map(U256::from), - evm_env.block_env.blob_gasprice().map(U256::from), - )?; - - let gas_limit = gas.unwrap_or( - // Use maximum allowed gas limit. The reason for this - // is that both Erigon and Geth use pre-configured gas cap even if - // it's possible to derive the gas limit from the block: - // - evm_env.block_env.gas_limit, - ); - - let chain_id = chain_id.unwrap_or(evm_env.cfg_env.chain_id); - - let caller = from.unwrap_or_default(); - - let nonce = if let Some(nonce) = nonce { - nonce - } else { - db.basic(caller).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default() - }; - - let env = TxEnv { - tx_type, - gas_limit, - nonce, - caller, - gas_price: gas_price.saturating_to(), - gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()), - kind: to.unwrap_or(TxKind::Create), - value: value.unwrap_or_default(), - data: input - .try_into_unique_input() - .map_err(Self::Error::from_eth_err)? - .unwrap_or_default(), - chain_id: Some(chain_id), - access_list: access_list.unwrap_or_default(), - // EIP-4844 fields - blob_hashes: blob_versioned_hashes.unwrap_or_default(), - max_fee_per_blob_gas: max_fee_per_blob_gas - .map(|v| v.saturating_to()) - .unwrap_or_default(), - // EIP-7702 fields - authorization_list: authorization_list - .unwrap_or_default() - .into_iter() - .map(Either::Left) - .collect(), - }; - - Ok(env) - } } impl EstimateCall for EthApi diff --git a/crates/rpc/rpc/src/eth/helpers/types.rs b/crates/rpc/rpc/src/eth/helpers/types.rs index 90b6e6c9283..22300b37be5 100644 --- a/crates/rpc/rpc/src/eth/helpers/types.rs +++ b/crates/rpc/rpc/src/eth/helpers/types.rs @@ -2,11 +2,12 @@ use alloy_network::Ethereum; use reth_ethereum_primitives::EthPrimitives; +use reth_evm_ethereum::EthEvmConfig; use reth_rpc_eth_types::EthApiError; use reth_rpc_types_compat::RpcConverter; /// An [`RpcConverter`] with its generics set to Ethereum specific. -pub type EthRpcConverter = RpcConverter; +pub type EthRpcConverter = RpcConverter; //tests for simulate #[cfg(test)] From d66bc9a50057993767e2e519c30f4fc9f069d77f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 11 Jun 2025 11:01:53 +0200 Subject: [PATCH 006/274] feat: add shared local block range info between SessionManager and ActiveSession (#16763) Co-authored-by: Claude --- crates/net/network/src/session/active.rs | 8 ++++++ crates/net/network/src/session/mod.rs | 31 +++++++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index cfbe9ef744e..b89946056e8 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -116,6 +116,9 @@ pub(crate) struct ActiveSession { Option<(PollSender>, ActiveSessionMessage)>, /// The eth69 range info for the remote peer. pub(crate) range_info: Option, + /// The eth69 range info for the local node (this node). + /// This represents the range of blocks that this node can serve to other peers. + pub(crate) local_range_info: BlockRangeInfo, } impl ActiveSession { @@ -998,6 +1001,11 @@ mod tests { protocol_breach_request_timeout: PROTOCOL_BREACH_REQUEST_TIMEOUT, terminate_message: None, range_info: None, + local_range_info: BlockRangeInfo::new( + 0, + 1000, + alloy_primitives::B256::ZERO, + ), } } ev => { diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index 1b73b87f8fd..f7487fa1c77 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -116,6 +116,9 @@ pub struct SessionManager { metrics: SessionManagerMetrics, /// The [`EthRlpxHandshake`] is used to perform the initial handshake with the peer. handshake: Arc, + /// Shared local range information that gets propagated to active sessions. + /// This represents the range of blocks that this node can serve to other peers. + local_range_info: BlockRangeInfo, } // === impl SessionManager === @@ -137,6 +140,13 @@ impl SessionManager { let (active_session_tx, active_session_rx) = mpsc::channel(config.session_event_buffer); let active_session_tx = PollSender::new(active_session_tx); + // Initialize local range info from the status + let local_range_info = BlockRangeInfo::new( + status.earliest_block.unwrap_or_default(), + status.latest_block.unwrap_or_default(), + status.blockhash, + ); + Self { next_id: 0, counter: SessionCounter::new(config.limits), @@ -159,6 +169,7 @@ impl SessionManager { disconnections_counter: Default::default(), metrics: Default::default(), handshake, + local_range_info, } } @@ -544,6 +555,7 @@ impl SessionManager { protocol_breach_request_timeout: self.protocol_breach_request_timeout, terminate_message: None, range_info: None, + local_range_info: self.local_range_info.clone(), }; self.spawn(session); @@ -653,13 +665,24 @@ impl SessionManager { } } - pub(crate) const fn update_advertised_block_range( - &mut self, - block_range_update: BlockRangeUpdate, - ) { + /// Updates the advertised block range that this node can serve to other peers starting with + /// Eth69. + /// + /// This method updates both the local status message that gets sent to peers during handshake + /// and the shared local range information that gets propagated to active sessions (Eth69). + /// The range information is used in ETH69 protocol where peers announce the range of blocks + /// they can serve to optimize data synchronization. + pub(crate) fn update_advertised_block_range(&mut self, block_range_update: BlockRangeUpdate) { self.status.earliest_block = Some(block_range_update.earliest); self.status.latest_block = Some(block_range_update.latest); self.status.blockhash = block_range_update.latest_hash; + + // Update the shared local range info that gets propagated to active sessions + self.local_range_info.update( + block_range_update.earliest, + block_range_update.latest, + block_range_update.latest_hash, + ); } } From 663b44a35dd1ddb62ba98022f12c102192f73572 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Wed, 11 Jun 2025 11:34:36 +0200 Subject: [PATCH 007/274] chore: update hive expected failures (#16764) --- .github/assets/hive/expected_failures.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/assets/hive/expected_failures.yaml b/.github/assets/hive/expected_failures.yaml index c6444017ba2..4221a0a62b0 100644 --- a/.github/assets/hive/expected_failures.yaml +++ b/.github/assets/hive/expected_failures.yaml @@ -16,6 +16,10 @@ rpc-compat: - eth_getTransactionReceipt/get-legacy-input (reth) - eth_getTransactionReceipt/get-legacy-receipt (reth) + # after https://github.com/paradigmxyz/reth/pull/16742 we start the node in + # syncing mode, the test expects syncing to be false on start + - eth_syncing/check-syncing + # no fix due to https://github.com/paradigmxyz/reth/issues/8732 engine-withdrawals: - Withdrawals Fork On Genesis (Paris) (reth) From b433561cb7d0bdfdfda6f68702b22d09fb3a489e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 11 Jun 2025 11:53:11 +0200 Subject: [PATCH 008/274] test: improve ETH69 protocol test coverage (#16759) --- crates/net/network/tests/it/session.rs | 362 +++++++++++++++++++++++++ 1 file changed, 362 insertions(+) diff --git a/crates/net/network/tests/it/session.rs b/crates/net/network/tests/it/session.rs index 5ab305e5746..a83a1e652e3 100644 --- a/crates/net/network/tests/it/session.rs +++ b/crates/net/network/tests/it/session.rs @@ -123,3 +123,365 @@ async fn test_capability_version_mismatch() { handle.terminate().await; } + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_peers_can_connect() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Create two peers that only support ETH69 + let p0 = PeerConfig::with_protocols(NoopProvider::default(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p0).await.unwrap(); + + let p1 = PeerConfig::with_protocols(NoopProvider::default(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p1).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); + let handle1 = handles.next().unwrap(); + drop(handles); + + let handle = net.spawn(); + + let mut events = handle0.event_listener().take(2); + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + while let Some(event) = events.next().await { + match event { + NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id)) => { + assert_eq!(handle1.peer_id(), &peer_id); + } + NetworkEvent::ActivePeerSession { info, .. } => { + let SessionInfo { peer_id, status, .. } = info; + assert_eq!(handle1.peer_id(), &peer_id); + // Both peers support only ETH69, so they should connect with ETH69 + assert_eq!(status.version, EthVersion::Eth69); + } + ev => { + panic!("unexpected event: {ev:?}") + } + } + } + + handle.terminate().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_peers_negotiate_highest_version_eth69() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Create one peer with multiple ETH versions including ETH69 + let p0 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![ + EthVersion::Eth69.into(), + EthVersion::Eth68.into(), + EthVersion::Eth67.into(), + EthVersion::Eth66.into(), + ], + ); + net.add_peer_with_config(p0).await.unwrap(); + + // Create another peer with multiple ETH versions including ETH69 + let p1 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth69.into(), EthVersion::Eth68.into(), EthVersion::Eth67.into()], + ); + net.add_peer_with_config(p1).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); + let handle1 = handles.next().unwrap(); + drop(handles); + + let handle = net.spawn(); + + let mut events = handle0.event_listener().take(2); + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + while let Some(event) = events.next().await { + match event { + NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id)) => { + assert_eq!(handle1.peer_id(), &peer_id); + } + NetworkEvent::ActivePeerSession { info, .. } => { + let SessionInfo { peer_id, status, .. } = info; + assert_eq!(handle1.peer_id(), &peer_id); + // Both peers support ETH69, so they should negotiate to the highest version: ETH69 + assert_eq!(status.version, EthVersion::Eth69); + } + ev => { + panic!("unexpected event: {ev:?}") + } + } + } + + handle.terminate().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_vs_eth68_incompatible() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Create one peer that only supports ETH69 + let p0 = PeerConfig::with_protocols(NoopProvider::default(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p0).await.unwrap(); + + // Create another peer that only supports ETH68 + let p1 = PeerConfig::with_protocols(NoopProvider::default(), Some(EthVersion::Eth68.into())); + net.add_peer_with_config(p1).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); + let handle1 = handles.next().unwrap(); + drop(handles); + + let handle = net.spawn(); + + let events = handle0.event_listener(); + let mut event_stream = NetworkEventStream::new(events); + + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + let added_peer_id = event_stream.peer_added().await.unwrap(); + assert_eq!(added_peer_id, *handle1.peer_id()); + + // Peers with no shared ETH version should fail to connect and be removed. + let removed_peer_id = event_stream.peer_removed().await.unwrap(); + assert_eq!(removed_peer_id, *handle1.peer_id()); + + handle.terminate().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_mixed_version_negotiation() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Create one peer that supports ETH69 + ETH68 + let p0 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth69.into(), EthVersion::Eth68.into()], + ); + net.add_peer_with_config(p0).await.unwrap(); + + // Create another peer that only supports ETH68 + let p1 = PeerConfig::with_protocols(NoopProvider::default(), Some(EthVersion::Eth68.into())); + net.add_peer_with_config(p1).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); + let handle1 = handles.next().unwrap(); + drop(handles); + + let handle = net.spawn(); + + let mut events = handle0.event_listener().take(2); + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + while let Some(event) = events.next().await { + match event { + NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id)) => { + assert_eq!(handle1.peer_id(), &peer_id); + } + NetworkEvent::ActivePeerSession { info, .. } => { + let SessionInfo { peer_id, status, .. } = info; + assert_eq!(handle1.peer_id(), &peer_id); + // Should negotiate to ETH68 (highest common version) + assert_eq!(status.version, EthVersion::Eth68); + } + ev => { + panic!("unexpected event: {ev:?}") + } + } + } + + handle.terminate().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_multiple_peers_different_eth_versions() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Create a peer that supports all versions (ETH66-ETH69) + let p0 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![ + EthVersion::Eth69.into(), + EthVersion::Eth68.into(), + EthVersion::Eth67.into(), + EthVersion::Eth66.into(), + ], + ); + net.add_peer_with_config(p0).await.unwrap(); + + // Create a peer that only supports newer versions (ETH68-ETH69) + let p1 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth69.into(), EthVersion::Eth68.into()], + ); + net.add_peer_with_config(p1).await.unwrap(); + + // Create a peer that only supports older versions (ETH66-ETH67) + let p2 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth67.into(), EthVersion::Eth66.into()], + ); + net.add_peer_with_config(p2).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); // All versions peer + let handle1 = handles.next().unwrap(); // Newer versions peer + let handle2 = handles.next().unwrap(); // Older versions peer + drop(handles); + + let handle = net.spawn(); + + let events = handle0.event_listener(); + let mut event_stream = NetworkEventStream::new(events); + + // Connect peer0 (all versions) to peer1 (newer versions) - should negotiate ETH69 + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + let added_peer_id = event_stream.peer_added().await.unwrap(); + assert_eq!(added_peer_id, *handle1.peer_id()); + + let established_peer_id = event_stream.next_session_established().await.unwrap(); + assert_eq!(established_peer_id, *handle1.peer_id()); + + // Connect peer0 (all versions) to peer2 (older versions) - should negotiate ETH67 + handle0.add_peer(*handle2.peer_id(), handle2.local_addr()); + + let added_peer_id = event_stream.peer_added().await.unwrap(); + assert_eq!(added_peer_id, *handle2.peer_id()); + + let established_peer_id = event_stream.next_session_established().await.unwrap(); + assert_eq!(established_peer_id, *handle2.peer_id()); + + // Both connections should be established successfully + + handle.terminate().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_capability_negotiation_fallback() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Create a peer that prefers ETH69 but supports fallback to ETH67 + let p0 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth69.into(), EthVersion::Eth67.into()], + ); + net.add_peer_with_config(p0).await.unwrap(); + + // Create a peer that skips ETH68 and only supports ETH67/ETH66 + let p1 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth67.into(), EthVersion::Eth66.into()], + ); + net.add_peer_with_config(p1).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); + let handle1 = handles.next().unwrap(); + drop(handles); + + let handle = net.spawn(); + + let mut events = handle0.event_listener().take(2); + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + while let Some(event) = events.next().await { + match event { + NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id)) => { + assert_eq!(handle1.peer_id(), &peer_id); + } + NetworkEvent::ActivePeerSession { info, .. } => { + let SessionInfo { peer_id, status, .. } = info; + assert_eq!(handle1.peer_id(), &peer_id); + // Should fallback to ETH67 (skipping ETH68 which neither supports) + assert_eq!(status.version, EthVersion::Eth67); + } + ev => { + panic!("unexpected event: {ev:?}") + } + } + } + + handle.terminate().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_overlapping_version_sets_negotiation() { + reth_tracing::init_test_tracing(); + + let mut net = Testnet::create(0).await; + + // Peer 0: supports ETH69, ETH67, ETH66 (skips ETH68) + let p0 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth69.into(), EthVersion::Eth67.into(), EthVersion::Eth66.into()], + ); + net.add_peer_with_config(p0).await.unwrap(); + + // Peer 1: supports ETH68, ETH67, ETH66 (skips ETH69) + let p1 = PeerConfig::with_protocols( + NoopProvider::default(), + vec![EthVersion::Eth68.into(), EthVersion::Eth67.into(), EthVersion::Eth66.into()], + ); + net.add_peer_with_config(p1).await.unwrap(); + + net.for_each(|peer| assert_eq!(0, peer.num_peers())); + + let mut handles = net.handles(); + let handle0 = handles.next().unwrap(); + let handle1 = handles.next().unwrap(); + drop(handles); + + let handle = net.spawn(); + + let mut events = handle0.event_listener().take(2); + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + while let Some(event) = events.next().await { + match event { + NetworkEvent::Peer(PeerEvent::PeerAdded(peer_id)) => { + assert_eq!(handle1.peer_id(), &peer_id); + } + NetworkEvent::ActivePeerSession { info, .. } => { + let SessionInfo { peer_id, status, .. } = info; + assert_eq!(handle1.peer_id(), &peer_id); + // Should negotiate to ETH67 (highest common version between ETH69,67,66 and + // ETH68,67,66) + assert_eq!(status.version, EthVersion::Eth67); + } + ev => { + panic!("unexpected event: {ev:?}") + } + } + } + + handle.terminate().await; +} From bdd0d4384ec436fd39974963b840d897b65380f9 Mon Sep 17 00:00:00 2001 From: Femi Bankole Date: Wed, 11 Jun 2025 12:50:36 +0100 Subject: [PATCH 009/274] fix: set parent beacon block to zero hash if parent's beacon block is Some (#16767) --- crates/rpc/rpc/src/eth/helpers/pending_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index dac1ace7d82..46fb7e7b0ef 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -71,7 +71,7 @@ where suggested_fee_recipient: parent.beneficiary(), prev_randao: B256::random(), gas_limit: parent.gas_limit(), - parent_beacon_block_root: parent.parent_beacon_block_root(), + parent_beacon_block_root: parent.parent_beacon_block_root().map(|_| B256::ZERO), withdrawals: None, }) } From 57e4b919a3054ba6e6bb14bb752e93e6ebacdb9c Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:36:55 +0200 Subject: [PATCH 010/274] test(trie): fix stored nibbles tests (#16769) --- crates/trie/common/src/nibbles.rs | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/trie/common/src/nibbles.rs b/crates/trie/common/src/nibbles.rs index a7db55b854b..e73fdf0bca5 100644 --- a/crates/trie/common/src/nibbles.rs +++ b/crates/trie/common/src/nibbles.rs @@ -120,79 +120,79 @@ mod tests { #[test] fn test_stored_nibbles_from_nibbles() { - let nibbles = Nibbles::from_nibbles_unchecked(vec![0x12, 0x34, 0x56]); + let nibbles = Nibbles::from_nibbles_unchecked(vec![0x02, 0x04, 0x06]); let stored = StoredNibbles::from(nibbles.clone()); assert_eq!(stored.0, nibbles); } #[test] fn test_stored_nibbles_from_vec() { - let bytes = vec![0x12, 0x34, 0x56]; + let bytes = vec![0x02, 0x04, 0x06]; let stored = StoredNibbles::from(bytes.clone()); assert_eq!(stored.0.as_slice(), bytes.as_slice()); } #[test] fn test_stored_nibbles_equality() { - let bytes = vec![0x12, 0x34]; + let bytes = vec![0x02, 0x04]; let stored = StoredNibbles::from(bytes.clone()); assert_eq!(stored, *bytes.as_slice()); } #[test] fn test_stored_nibbles_partial_cmp() { - let stored = StoredNibbles::from(vec![0x12, 0x34]); - let other = vec![0x12, 0x35]; + let stored = StoredNibbles::from(vec![0x02, 0x04]); + let other = vec![0x02, 0x05]; assert!(stored < *other.as_slice()); } #[test] fn test_stored_nibbles_to_compact() { - let stored = StoredNibbles::from(vec![0x12, 0x34]); + let stored = StoredNibbles::from(vec![0x02, 0x04]); let mut buf = BytesMut::with_capacity(10); let len = stored.to_compact(&mut buf); assert_eq!(len, 2); - assert_eq!(buf, &vec![0x12, 0x34][..]); + assert_eq!(buf, &vec![0x02, 0x04][..]); } #[test] fn test_stored_nibbles_from_compact() { - let buf = vec![0x12, 0x34, 0x56]; + let buf = vec![0x02, 0x04, 0x06]; let (stored, remaining) = StoredNibbles::from_compact(&buf, 2); - assert_eq!(stored.0.as_slice(), &[0x12, 0x34]); - assert_eq!(remaining, &[0x56]); + assert_eq!(stored.0.as_slice(), &[0x02, 0x04]); + assert_eq!(remaining, &[0x06]); } #[test] fn test_stored_nibbles_subkey_from_nibbles() { - let nibbles = Nibbles::from_nibbles_unchecked(vec![0x12, 0x34]); + let nibbles = Nibbles::from_nibbles_unchecked(vec![0x02, 0x04]); let subkey = StoredNibblesSubKey::from(nibbles.clone()); assert_eq!(subkey.0, nibbles); } #[test] fn test_stored_nibbles_subkey_to_compact() { - let subkey = StoredNibblesSubKey::from(vec![0x12, 0x34]); + let subkey = StoredNibblesSubKey::from(vec![0x02, 0x04]); let mut buf = BytesMut::with_capacity(65); let len = subkey.to_compact(&mut buf); assert_eq!(len, 65); - assert_eq!(buf[..2], [0x12, 0x34]); + assert_eq!(buf[..2], [0x02, 0x04]); assert_eq!(buf[64], 2); // Length byte } #[test] fn test_stored_nibbles_subkey_from_compact() { - let mut buf = vec![0x12, 0x34]; + let mut buf = vec![0x02, 0x04]; buf.resize(65, 0); buf[64] = 2; let (subkey, remaining) = StoredNibblesSubKey::from_compact(&buf, 65); - assert_eq!(subkey.0.as_slice(), &[0x12, 0x34]); + assert_eq!(subkey.0.as_slice(), &[0x02, 0x04]); assert_eq!(remaining, &[] as &[u8]); } #[test] fn test_serialization_stored_nibbles() { - let stored = StoredNibbles::from(vec![0x12, 0x34]); + let stored = StoredNibbles::from(vec![0x02, 0x04]); let serialized = serde_json::to_string(&stored).unwrap(); let deserialized: StoredNibbles = serde_json::from_str(&serialized).unwrap(); assert_eq!(stored, deserialized); @@ -200,7 +200,7 @@ mod tests { #[test] fn test_serialization_stored_nibbles_subkey() { - let subkey = StoredNibblesSubKey::from(vec![0x12, 0x34]); + let subkey = StoredNibblesSubKey::from(vec![0x02, 0x04]); let serialized = serde_json::to_string(&subkey).unwrap(); let deserialized: StoredNibblesSubKey = serde_json::from_str(&serialized).unwrap(); assert_eq!(subkey, deserialized); From af912c41f35954fc5ff99e7757f03b33d43a62a3 Mon Sep 17 00:00:00 2001 From: Udoagwa Franklin <54338168+frankudoags@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:26:21 +0100 Subject: [PATCH 011/274] feat: ensure ETL data directory is cleared on launch (#16770) Co-authored-by: aolamide --- crates/node/builder/src/launch/common.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index fb289886e36..a123d6722c0 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -273,8 +273,12 @@ impl LaunchContextWith Self { if self.toml_config_mut().stages.etl.dir.is_none() { - self.toml_config_mut().stages.etl.dir = - Some(EtlConfig::from_datadir(self.data_dir().data_dir())) + let etl_path = EtlConfig::from_datadir(self.data_dir().data_dir()); + // Remove etl-path files on launch + if let Err(err) = fs::remove_dir_all(&etl_path) { + warn!(target: "reth::cli", ?etl_path, %err, "Failed to remove ETL path on launch"); + } + self.toml_config_mut().stages.etl.dir = Some(etl_path); } self From 1e40b36afc7e72e08bdc8d8768d3180e5439bf71 Mon Sep 17 00:00:00 2001 From: Luis_ <73004377+Another-DevX@users.noreply.github.com> Date: Wed, 11 Jun 2025 19:07:02 -0500 Subject: [PATCH 012/274] feat: make EthEvmConfig generic over chainSpec (#16758) Co-authored-by: Arsenii Kulikov --- crates/ethereum/evm/src/config.rs | 46 +++++++++++++++------------ crates/ethereum/evm/src/lib.rs | 14 ++++---- examples/custom-evm/src/main.rs | 2 +- examples/precompile-cache/src/main.rs | 2 +- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/crates/ethereum/evm/src/config.rs b/crates/ethereum/evm/src/config.rs index e94ca17fb37..676b790edb7 100644 --- a/crates/ethereum/evm/src/config.rs +++ b/crates/ethereum/evm/src/config.rs @@ -1,19 +1,25 @@ use alloy_consensus::Header; -use reth_chainspec::{ChainSpec, EthereumHardforks}; -use reth_ethereum_forks::EthereumHardfork; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; +use reth_ethereum_forks::{EthereumHardfork, Hardforks}; use revm::primitives::hardfork::SpecId; /// Map the latest active hardfork at the given header to a revm [`SpecId`]. -pub fn revm_spec(chain_spec: &ChainSpec, header: &Header) -> SpecId { +pub fn revm_spec(chain_spec: &C, header: &Header) -> SpecId +where + C: EthereumHardforks + EthChainSpec + Hardforks, +{ revm_spec_by_timestamp_and_block_number(chain_spec, header.timestamp, header.number) } /// Map the latest active hardfork at the given timestamp or block number to a revm [`SpecId`]. -pub fn revm_spec_by_timestamp_and_block_number( - chain_spec: &ChainSpec, +pub fn revm_spec_by_timestamp_and_block_number( + chain_spec: &C, timestamp: u64, block_number: u64, -) -> SpecId { +) -> SpecId +where + C: EthereumHardforks + EthChainSpec + Hardforks, +{ if chain_spec .fork(EthereumHardfork::Osaka) .active_at_timestamp_or_number(timestamp, block_number) @@ -83,8 +89,8 @@ pub fn revm_spec_by_timestamp_and_block_number( SpecId::FRONTIER } else { panic!( - "invalid hardfork chainspec: expected at least one hardfork, got {:?}", - chain_spec.hardforks + "invalid hardfork chainspec: expected at least one hardfork, got {}", + chain_spec.display_hardforks() ) } } @@ -199,55 +205,55 @@ mod tests { #[test] fn test_eth_spec() { assert_eq!( - revm_spec(&MAINNET, &Header { timestamp: 1710338135, ..Default::default() }), + revm_spec(&*MAINNET, &Header { timestamp: 1710338135, ..Default::default() }), SpecId::CANCUN ); assert_eq!( - revm_spec(&MAINNET, &Header { timestamp: 1681338455, ..Default::default() }), + revm_spec(&*MAINNET, &Header { timestamp: 1681338455, ..Default::default() }), SpecId::SHANGHAI ); assert_eq!( revm_spec( - &MAINNET, + &*MAINNET, &Header { difficulty: U256::from(10_u128), number: 15537394, ..Default::default() } ), SpecId::MERGE ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 15537394 - 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 15537394 - 10, ..Default::default() }), SpecId::LONDON ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 12244000 + 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 12244000 + 10, ..Default::default() }), SpecId::BERLIN ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 12244000 - 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 12244000 - 10, ..Default::default() }), SpecId::ISTANBUL ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 7280000 + 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 7280000 + 10, ..Default::default() }), SpecId::PETERSBURG ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 7280000 - 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 7280000 - 10, ..Default::default() }), SpecId::BYZANTIUM ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 2675000 + 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 2675000 + 10, ..Default::default() }), SpecId::SPURIOUS_DRAGON ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 2675000 - 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 2675000 - 10, ..Default::default() }), SpecId::TANGERINE ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 1150000 + 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 1150000 + 10, ..Default::default() }), SpecId::HOMESTEAD ); assert_eq!( - revm_spec(&MAINNET, &Header { number: 1150000 - 10, ..Default::default() }), + revm_spec(&*MAINNET, &Header { number: 1150000 - 10, ..Default::default() }), SpecId::FRONTIER ); } diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index ad77ae74ea4..e2acad027b2 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -41,8 +41,9 @@ use revm::{ mod config; use alloy_eips::{eip1559::INITIAL_BASE_FEE, eip7840::BlobParams}; +use alloy_evm::eth::spec::EthExecutorSpec; pub use config::{revm_spec, revm_spec_by_timestamp_and_block_number}; -use reth_ethereum_forks::EthereumHardfork; +use reth_ethereum_forks::{EthereumHardfork, Hardforks}; /// Helper type with backwards compatible methods to obtain Ethereum executor /// providers. @@ -67,11 +68,11 @@ pub use test_utils::*; /// Ethereum-related EVM configuration. #[derive(Debug, Clone)] -pub struct EthEvmConfig { +pub struct EthEvmConfig { /// Inner [`EthBlockExecutorFactory`]. - pub executor_factory: EthBlockExecutorFactory, EvmFactory>, + pub executor_factory: EthBlockExecutorFactory, EvmFactory>, /// Ethereum block assembler. - pub block_assembler: EthBlockAssembler, + pub block_assembler: EthBlockAssembler, } impl EthEvmConfig { @@ -91,7 +92,7 @@ impl EthEvmConfig { } } -impl EthEvmConfig { +impl EthEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec and EVM factory. pub fn new_with_evm_factory(chain_spec: Arc, evm_factory: EvmFactory) -> Self { Self { @@ -116,8 +117,9 @@ impl EthEvmConfig { } } -impl ConfigureEvm for EthEvmConfig +impl ConfigureEvm for EthEvmConfig where + ChainSpec: EthExecutorSpec + EthChainSpec + Hardforks + 'static, EvmF: EvmFactory< Tx: TransactionEnv + FromRecoveredTx diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 7e41b17aad7..93127bbc91b 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -84,7 +84,7 @@ impl ExecutorBuilder for MyExecutorBuilder where Node: FullNodeTypes>, { - type EVM = EthEvmConfig; + type EVM = EthEvmConfig; async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { let evm_config = diff --git a/examples/precompile-cache/src/main.rs b/examples/precompile-cache/src/main.rs index ed8143b36bf..9f672c66623 100644 --- a/examples/precompile-cache/src/main.rs +++ b/examples/precompile-cache/src/main.rs @@ -166,7 +166,7 @@ impl ExecutorBuilder for MyExecutorBuilder where Node: FullNodeTypes>, { - type EVM = EthEvmConfig; + type EVM = EthEvmConfig; async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { let evm_config = EthEvmConfig::new_with_evm_factory( From 4ade65a57db9996df88e134df487d9c022ab332c Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Thu, 12 Jun 2025 10:44:48 +0200 Subject: [PATCH 013/274] chore: fix hive unexpected test filter (#16782) --- .github/assets/hive/expected_failures.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/assets/hive/expected_failures.yaml b/.github/assets/hive/expected_failures.yaml index 4221a0a62b0..ae1cc77086b 100644 --- a/.github/assets/hive/expected_failures.yaml +++ b/.github/assets/hive/expected_failures.yaml @@ -18,7 +18,7 @@ rpc-compat: # after https://github.com/paradigmxyz/reth/pull/16742 we start the node in # syncing mode, the test expects syncing to be false on start - - eth_syncing/check-syncing + - eth_syncing/check-syncing (reth) # no fix due to https://github.com/paradigmxyz/reth/issues/8732 engine-withdrawals: From 64fc747bf414b7b9a1d6062ce0e2a8c994dcff4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Thu, 12 Jun 2025 10:45:40 +0200 Subject: [PATCH 014/274] fix(era): Rollback state of `StartingStream` if fetching file list fails (#16775) --- crates/era-downloader/src/stream.rs | 6 +++++- crates/era-downloader/tests/it/stream.rs | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/era-downloader/src/stream.rs b/crates/era-downloader/src/stream.rs index 336085a2682..fad02ab6efb 100644 --- a/crates/era-downloader/src/stream.rs +++ b/crates/era-downloader/src/stream.rs @@ -264,7 +264,11 @@ impl Stream for Starti if let Poll::Ready(result) = self.fetch_file_list.poll_unpin(cx) { match result { Ok(_) => self.recover_index(), - Err(e) => return Poll::Ready(Some(Box::pin(async move { Err(e) }))), + Err(e) => { + self.fetch_file_list(); + + return Poll::Ready(Some(Box::pin(async move { Err(e) }))) + } } } } diff --git a/crates/era-downloader/tests/it/stream.rs b/crates/era-downloader/tests/it/stream.rs index 5c7b812b9d7..e9a97079313 100644 --- a/crates/era-downloader/tests/it/stream.rs +++ b/crates/era-downloader/tests/it/stream.rs @@ -32,3 +32,20 @@ async fn test_streaming_files_after_fetching_file_list(url: &str) { assert_eq!(actual_file.as_ref(), expected_file.as_ref()); } + +#[tokio::test] +async fn test_streaming_files_after_fetching_file_list_into_missing_folder_fails() { + let base_url = Url::from_str("https://era.ithaca.xyz/era1/index.html").unwrap(); + let folder = tempdir().unwrap().path().to_owned().into_boxed_path(); + let client = EraClient::new(StubClient, base_url, folder.clone()); + + let mut stream = EraStream::new( + client, + EraStreamConfig::default().with_max_files(2).with_max_concurrent_downloads(1), + ); + + let actual_error = stream.next().await.unwrap().unwrap_err().to_string(); + let expected_error = "No such file or directory (os error 2)".to_owned(); + + assert_eq!(actual_error, expected_error); +} From 6ddc756489be4d1703f01f42b6aacc3a08c958ff Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 12 Jun 2025 11:36:48 +0200 Subject: [PATCH 015/274] feat: introduce RPC error for pruned history (#16780) --- crates/rpc/rpc-eth-types/src/error/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 4029cb2dc5a..24771717617 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -61,6 +61,14 @@ pub enum EthApiError { /// Header range not found for start block hash/number/tag to end block hash/number/tag #[error("header range not found, start block {0:?}, end block {1:?}")] HeaderRangeNotFound(BlockId, BlockId), + /// Thrown when historical data is not available because it has been pruned + /// + /// This error is intended for use as a standard response when historical data is + /// requested that has been pruned according to the node's data retention policy. + /// + /// See also + #[error("pruned history unavailable")] + PrunedHistoryUnavailable, /// Receipts not found for block hash/number/tag #[error("receipts not found")] ReceiptsNotFound(BlockId), @@ -225,6 +233,7 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { internal_rpc_err(err.to_string()) } err @ EthApiError::TransactionInputError(_) => invalid_params_rpc_err(err.to_string()), + EthApiError::PrunedHistoryUnavailable => rpc_error_with_code(4444, error.to_string()), EthApiError::Other(err) => err.to_rpc_error(), EthApiError::MuxTracerError(msg) => internal_rpc_err(msg.to_string()), } From a9bbc9be6551f2c6359ee8a36607aae6591a266e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 12 Jun 2025 11:41:48 +0200 Subject: [PATCH 016/274] fix: resolve external ip on launch (#16768) --- crates/net/discv4/src/lib.rs | 16 +++++++++++++++- crates/net/nat/src/lib.rs | 5 +++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 2379f71461e..976ade1728f 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -252,7 +252,12 @@ impl Discv4 { local_node_record.udp_port = local_addr.port(); trace!(target: "discv4", ?local_addr,"opened UDP socket"); - let service = Discv4Service::new(socket, local_addr, local_node_record, secret_key, config); + let mut service = + Discv4Service::new(socket, local_addr, local_node_record, secret_key, config); + + // resolve the external address immediately + service.resolve_external_ip(); + let discv4 = service.handle(); Ok((discv4, service)) } @@ -620,6 +625,15 @@ impl Discv4Service { self.lookup_interval = tokio::time::interval(duration); } + /// Sets the external Ip to the configured external IP if [`NatResolver::ExternalIp`]. + fn resolve_external_ip(&mut self) { + if let Some(r) = &self.resolve_external_ip_interval { + if let Some(external_ip) = r.resolver().as_external_ip() { + self.set_external_ip_addr(external_ip); + } + } + } + /// Sets the given ip address as the node's external IP in the node record announced in /// discovery pub fn set_external_ip_addr(&mut self, external_ip: IpAddr) { diff --git a/crates/net/nat/src/lib.rs b/crates/net/nat/src/lib.rs index 3bdb3afc902..e4ff4413051 100644 --- a/crates/net/nat/src/lib.rs +++ b/crates/net/nat/src/lib.rs @@ -161,6 +161,11 @@ impl ResolveNatInterval { Self::with_interval(resolver, interval) } + /// Returns the resolver used by this interval + pub const fn resolver(&self) -> &NatResolver { + &self.resolver + } + /// Completes when the next [`IpAddr`] in the interval has been reached. pub async fn tick(&mut self) -> Option { poll_fn(|cx| self.poll_tick(cx)).await From e7cbecb0df6b7f45b95628e0ddcf97c9edebbfae Mon Sep 17 00:00:00 2001 From: Z <12710516+zitup@users.noreply.github.com> Date: Thu, 12 Jun 2025 18:22:44 +0800 Subject: [PATCH 017/274] chore(deps): Upgrade proptest to 1.7 (#16786) --- Cargo.lock | 14 +++++++------- Cargo.toml | 2 +- crates/engine/tree/benches/channel_perf.rs | 4 ++-- crates/engine/tree/benches/state_root_task.rs | 18 +++++++++--------- crates/trie/sparse/src/trie.rs | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddd0d94efc7..47d0aefd623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6576,17 +6576,17 @@ dependencies = [ [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", "bitflags 2.9.1", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.1", + "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax 0.8.5", "rusty-fork", @@ -6860,11 +6860,11 @@ dependencies = [ [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.6.4", + "rand_core 0.9.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1f733f392be..19d070fd363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -628,7 +628,7 @@ toml = "0.8" arbitrary = "1.3" assert_matches = "1.5.0" criterion = { package = "codspeed-criterion-compat", version = "2.7" } -proptest = "1.4" +proptest = "1.7" proptest-derive = "0.5" similar-asserts = { version = "1.5.0", features = ["serde"] } tempfile = "3.20" diff --git a/crates/engine/tree/benches/channel_perf.rs b/crates/engine/tree/benches/channel_perf.rs index 74067d4de70..c809b36284a 100644 --- a/crates/engine/tree/benches/channel_perf.rs +++ b/crates/engine/tree/benches/channel_perf.rs @@ -5,7 +5,7 @@ use alloy_primitives::{B256, U256}; use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use proptest::test_runner::TestRunner; -use rand_08::Rng; +use rand::Rng; use revm_primitives::{Address, HashMap}; use revm_state::{Account, AccountInfo, AccountStatus, EvmState, EvmStorage, EvmStorageSlot}; use std::{hint::black_box, thread}; @@ -24,7 +24,7 @@ fn create_bench_state(num_accounts: usize) -> EvmState { info: AccountInfo { balance: U256::from(100), nonce: 10, - code_hash: B256::from_slice(&rng.r#gen::<[u8; 32]>()), + code_hash: B256::from_slice(&rng.random::<[u8; 32]>()), code: Default::default(), }, storage, diff --git a/crates/engine/tree/benches/state_root_task.rs b/crates/engine/tree/benches/state_root_task.rs index c049763e8d1..d705bfecd8f 100644 --- a/crates/engine/tree/benches/state_root_task.rs +++ b/crates/engine/tree/benches/state_root_task.rs @@ -8,7 +8,7 @@ use alloy_evm::block::StateChangeSource; use alloy_primitives::{Address, B256}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use proptest::test_runner::TestRunner; -use rand_08::Rng; +use rand::Rng; use reth_chain_state::EthPrimitives; use reth_chainspec::ChainSpec; use reth_db_common::init::init_genesis; @@ -52,14 +52,14 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec { for _ in 0..params.updates_per_account { let mut state_update = EvmState::default(); - let num_accounts_in_update = rng.gen_range(1..=params.num_accounts); + let num_accounts_in_update = rng.random_range(1..=params.num_accounts); // regular updates for randomly selected accounts for &address in &all_addresses[0..num_accounts_in_update] { // randomly choose to self-destruct with probability // (selfdestructs/accounts) - let is_selfdestruct = - rng.gen_bool(params.selfdestructs_per_update as f64 / params.num_accounts as f64); + let is_selfdestruct = rng + .random_bool(params.selfdestructs_per_update as f64 / params.num_accounts as f64); let account = if is_selfdestruct { RevmAccount { @@ -70,18 +70,18 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec { } else { RevmAccount { info: AccountInfo { - balance: U256::from(rng.r#gen::()), - nonce: rng.r#gen::(), + balance: U256::from(rng.random::()), + nonce: rng.random::(), code_hash: KECCAK_EMPTY, code: Some(Default::default()), }, - storage: (0..rng.gen_range(0..=params.storage_slots_per_account)) + storage: (0..rng.random_range(0..=params.storage_slots_per_account)) .map(|_| { ( - U256::from(rng.r#gen::()), + U256::from(rng.random::()), EvmStorageSlot::new_changed( U256::ZERO, - U256::from(rng.r#gen::()), + U256::from(rng.random::()), ), ) }) diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index e8f8c9f87a7..5759b5d4b89 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -3157,7 +3157,7 @@ mod tests { fn transform_updates( updates: Vec>, - mut rng: impl rand_08::Rng, + mut rng: impl rand::Rng, ) -> Vec<(BTreeMap, BTreeSet)> { let mut keys = BTreeSet::new(); updates @@ -3168,7 +3168,7 @@ mod tests { let keys_to_delete_len = update.len() / 2; let keys_to_delete = (0..keys_to_delete_len) .map(|_| { - let key = rand_08::seq::IteratorRandom::choose(keys.iter(), &mut rng) + let key = rand::seq::IteratorRandom::choose(keys.iter(), &mut rng) .unwrap() .clone(); keys.take(&key).unwrap() From 9f98728deb6787f46ef99028ab42f5bd346743ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Narzis?= <78718413+lean-apple@users.noreply.github.com> Date: Thu, 12 Jun 2025 13:48:33 +0200 Subject: [PATCH 018/274] feat(net): make bloom filter optional on receipts request (#16718) --- Cargo.lock | 186 +++++++++++------------ crates/exex/exex/src/wal/mod.rs | 4 +- crates/net/network-api/src/events.rs | 14 +- crates/net/network/src/eth_requests.rs | 56 ++++++- crates/net/network/src/manager.rs | 7 + crates/net/network/src/message.rs | 8 + crates/net/network/src/session/active.rs | 8 +- crates/rpc/ipc/src/server/mod.rs | 2 +- docs/crates/network.md | 1 + examples/network-proxy/src/main.rs | 1 + 10 files changed, 179 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47d0aefd623..23619f24cae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,9 +97,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517e5acbd38b6d4c59da380e8bbadc6d365bf001903ce46cf5521c53c647e07b" +checksum = "d6967ca1ed656766e471bc323da42fb0db320ca5e1418b408650e98e4757b3d2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -173,9 +173,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18cc14d832bc3331ca22a1c7819de1ede99f58f61a7d123952af7dde8de124a6" +checksum = "f9135eb501feccf7f4cb8a183afd406a65483fdad7bbd7332d0470e5d725c92f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -289,9 +289,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbff8445282ec080c2673692062bd4930d7a0d6bda257caf138cfc650c503000" +checksum = "977d2492ce210e34baf7b36afaacea272c96fbe6774c47e23f97d14033c0e94f" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccaa79753d7bf15f06399ea76922afbfaf8d18bebed9e8fc452984b4a90dcc9" +checksum = "8b26fdd571915bafe857fccba4ee1a4f352965800e46a53e4a5f50187b7776fa" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -385,9 +385,9 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ddfbb5cc9f614efa5d56e0d7226214bb67b29271d44b6ddfcbbe25eb0ff898b" +checksum = "08b147547aff595aa3d4c2fc2c8146263e18d3372909def423619ed631ecbcfa" dependencies = [ "alloy-hardforks", "auto_impl", @@ -396,9 +396,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "a326d47106039f38b811057215a92139f46eef7983a4b77b10930a0ea5685b1e" dependencies = [ "alloy-rlp", "arbitrary", @@ -409,7 +409,7 @@ dependencies = [ "derive_more", "foldhash", "getrandom 0.3.3", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "indexmap 2.9.0", "itoa", "k256", @@ -745,9 +745,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8612e0658964d616344f199ab251a49d48113992d81b92dab93ed855faa66383" +checksum = "d4be1ce1274ddd7fdfac86e5ece1b225e9bba1f2327e20fbb30ee6b9cc1423fe" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -759,9 +759,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a384edac7283bc4c010a355fb648082860c04b826bb7a814c45263c8f304c74" +checksum = "01e92f3708ea4e0d9139001c86c051c538af0146944a2a9c7181753bd944bf57" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -777,9 +777,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd588c2d516da7deb421b8c166dc60b7ae31bca5beea29ab6621fcfa53d6ca5" +checksum = "9afe1bd348a41f8c9b4b54dfb314886786d6201235b0b3f47198b9d910c86bb2" dependencies = [ "const-hex", "dunce", @@ -793,9 +793,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86ddeb70792c7ceaad23e57d52250107ebbb86733e52f4a25d8dc1abc931837" +checksum = "d6195df2acd42df92a380a8db6205a5c7b41282d0ce3f4c665ecf7911ac292f1" dependencies = [ "serde", "winnow", @@ -803,9 +803,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584cb97bfc5746cb9dcc4def77da11694b5d6d7339be91b7480a6a68dc129387" +checksum = "6185e98a79cf19010722f48a74b5a65d153631d2f038cabd250f4b9e9813b8ad" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -932,9 +932,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -947,33 +947,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.8" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", "once_cell_polyfill", @@ -1046,7 +1046,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "itertools 0.13.0", "num-bigint", "num-integer", @@ -1192,7 +1192,7 @@ dependencies = [ "ark-std 0.5.0", "educe", "fnv", - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -1501,9 +1501,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "bech32" @@ -1666,9 +1666,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -1710,7 +1710,7 @@ dependencies = [ "cfg-if", "dashmap 6.1.0", "fast-float2", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "icu_normalizer 1.5.0", "indexmap 2.9.0", "intrusive-collections", @@ -1745,7 +1745,7 @@ dependencies = [ "boa_macros", "boa_profiler", "boa_string", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "thin-vec", ] @@ -1757,7 +1757,7 @@ checksum = "42407a3b724cfaecde8f7d4af566df4b56af32a2f11f0956f5570bb974e7f749" dependencies = [ "boa_gc", "boa_macros", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "indexmap 2.9.0", "once_cell", "phf", @@ -1868,9 +1868,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" [[package]] name = "byte-slice-cast" @@ -1880,9 +1880,9 @@ checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" @@ -1937,9 +1937,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -2259,9 +2259,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -3713,9 +3713,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -4091,9 +4091,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -4333,9 +4333,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.6" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a01595e11bdcec50946522c32dde3fc6914743000a68b93000965f2f02406d" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http", "hyper", @@ -4352,9 +4352,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ "base64 0.22.1", "bytes", @@ -4703,7 +4703,7 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "serde", ] @@ -5371,7 +5371,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -5380,7 +5380,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", ] [[package]] @@ -5527,7 +5527,7 @@ checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" dependencies = [ "crossbeam-epoch", "crossbeam-utils", - "hashbrown 0.15.3", + "hashbrown 0.15.4", "indexmap 2.9.0", "metrics", "ordered-float", @@ -6441,9 +6441,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" @@ -7013,7 +7013,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ef7fa9ed0256d64a688a3747d0fef7a88851c18a5e1d57f115f38ec2e09366" dependencies = [ - "hashbrown 0.15.3", + "hashbrown 0.15.4", "memchr", ] @@ -7025,9 +7025,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.18" +version = "0.12.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e98ff6b0dbbe4d5a37318f433d4fc82babd21631f194d370409ceb2e40b2f0b5" +checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" dependencies = [ "base64 0.22.1", "bytes", @@ -10526,9 +10526,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "4.0.1" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91f9b90b3bab18942252de2d970ee8559794c49ca7452b2cc1774456040f8fb" +checksum = "942fe4724cf552fd28db6b0a2ca5b79e884d40dd8288a4027ed1e9090e0c6f49" dependencies = [ "bitvec", "once_cell", @@ -10632,9 +10632,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b50ef375dbacefecfdacf8f02afc31df98acc5d8859a6f2b24d121ff2a740a8" +checksum = "354e963abdea6d5b80b978614e0016a098a764063f92b2316c624faacd5301cb" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -10689,9 +10689,9 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "19.1.0" +version = "19.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18ea2ea0134568ee1e14281ce52f60e2710d42be316888d464c53e37ff184fd8" +checksum = "1c1588093530ec4442461163be49c433c07a3235d1ca6f6799fef338dacc50d3" dependencies = [ "alloy-primitives", "num_enum", @@ -11249,9 +11249,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -11498,9 +11498,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "arbitrary", "serde", @@ -11665,9 +11665,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.1.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5d879005cc1b5ba4e18665be9e9501d9da3a9b95f625497c4cb7ee082b532e" +checksum = "14c8c8f496c33dc6343dac05b4be8d9e0bca180a4caa81d7b8416b10cc2273cd" dependencies = [ "paste", "proc-macro2", @@ -12109,9 +12109,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -12121,18 +12121,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap 2.9.0", "serde", @@ -12144,9 +12144,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tonic" @@ -12191,9 +12191,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", "base64 0.22.1", @@ -12258,9 +12258,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" dependencies = [ "proc-macro2", "quote", @@ -12269,9 +12269,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", diff --git a/crates/exex/exex/src/wal/mod.rs b/crates/exex/exex/src/wal/mod.rs index 66c528c14fa..b5537aa88fc 100644 --- a/crates/exex/exex/src/wal/mod.rs +++ b/crates/exex/exex/src/wal/mod.rs @@ -96,7 +96,7 @@ where N: NodePrimitives, { fn new(directory: impl AsRef) -> WalResult { - let mut wal = Self { + let wal = Self { next_file_id: AtomicU32::new(0), storage: Storage::new(directory)?, block_cache: RwLock::new(BlockCache::default()), @@ -112,7 +112,7 @@ where /// Fills the block cache with the notifications from the storage. #[instrument(skip(self))] - fn fill_block_cache(&mut self) -> WalResult<()> { + fn fill_block_cache(&self) -> WalResult<()> { let Some(files_range) = self.storage.files_range()? else { return Ok(()) }; self.next_file_id.store(files_range.end() + 1, Ordering::Relaxed); diff --git a/crates/net/network-api/src/events.rs b/crates/net/network-api/src/events.rs index 642dd50f814..8a5c7541490 100644 --- a/crates/net/network-api/src/events.rs +++ b/crates/net/network-api/src/events.rs @@ -4,7 +4,7 @@ use reth_eth_wire_types::{ message::RequestPair, BlockBodies, BlockHeaders, Capabilities, DisconnectReason, EthMessage, EthNetworkPrimitives, EthVersion, GetBlockBodies, GetBlockHeaders, GetNodeData, GetPooledTransactions, GetReceipts, NetworkPrimitives, NodeData, PooledTransactions, Receipts, - UnifiedStatus, + Receipts69, UnifiedStatus, }; use reth_ethereum_forks::ForkId; use reth_network_p2p::error::{RequestError, RequestResult}; @@ -229,6 +229,15 @@ pub enum PeerRequest { /// The channel to send the response for receipts. response: oneshot::Sender>>, }, + /// Requests receipts from the peer without bloom filter. + /// + /// The response should be sent through the channel. + GetReceipts69 { + /// The request for receipts. + request: GetReceipts, + /// The channel to send the response for receipts. + response: oneshot::Sender>>, + }, } // === impl PeerRequest === @@ -247,6 +256,7 @@ impl PeerRequest { Self::GetPooledTransactions { response, .. } => response.send(Err(err)).ok(), Self::GetNodeData { response, .. } => response.send(Err(err)).ok(), Self::GetReceipts { response, .. } => response.send(Err(err)).ok(), + Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(), }; } @@ -268,7 +278,7 @@ impl PeerRequest { Self::GetNodeData { request, .. } => { EthMessage::GetNodeData(RequestPair { request_id, message: request.clone() }) } - Self::GetReceipts { request, .. } => { + Self::GetReceipts { request, .. } | Self::GetReceipts69 { request, .. } => { EthMessage::GetReceipts(RequestPair { request_id, message: request.clone() }) } } diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 408937e4533..39e485318bb 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -10,7 +10,7 @@ use alloy_rlp::Encodable; use futures::StreamExt; use reth_eth_wire::{ BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockBodies, GetBlockHeaders, GetNodeData, - GetReceipts, HeadersDirection, NetworkPrimitives, NodeData, Receipts, + GetReceipts, HeadersDirection, NetworkPrimitives, NodeData, Receipts, Receipts69, }; use reth_network_api::test_utils::PeersHandle; use reth_network_p2p::error::RequestResult; @@ -190,19 +190,45 @@ where ) { self.metrics.eth_receipts_requests_received_total.increment(1); - let mut receipts = Vec::new(); + let receipts = self.get_receipts_response(request, |receipts_by_block| { + receipts_by_block.into_iter().map(ReceiptWithBloom::from).collect::>() + }); + + let _ = response.send(Ok(Receipts(receipts))); + } + + fn on_receipts69_request( + &self, + _peer_id: PeerId, + request: GetReceipts, + response: oneshot::Sender>>, + ) { + self.metrics.eth_receipts_requests_received_total.increment(1); + + let receipts = self.get_receipts_response(request, |receipts_by_block| { + // skip bloom filter for eth69 + receipts_by_block + }); + + let _ = response.send(Ok(Receipts69(receipts))); + } + #[inline] + fn get_receipts_response(&self, request: GetReceipts, transform_fn: F) -> Vec> + where + F: Fn(Vec) -> Vec, + T: Encodable, + { + let mut receipts = Vec::new(); let mut total_bytes = 0; for hash in request.0 { if let Some(receipts_by_block) = self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default() { - let receipt = - receipts_by_block.into_iter().map(ReceiptWithBloom::from).collect::>(); - - total_bytes += receipt.length(); - receipts.push(receipt); + let transformed_receipts = transform_fn(receipts_by_block); + total_bytes += transformed_receipts.length(); + receipts.push(transformed_receipts); if receipts.len() >= MAX_RECEIPTS_SERVE || total_bytes > SOFT_RESPONSE_LIMIT { break @@ -212,7 +238,7 @@ where } } - let _ = response.send(Ok(Receipts(receipts))); + receipts } } @@ -252,6 +278,9 @@ where IncomingEthRequest::GetReceipts { peer_id, request, response } => { this.on_receipts_request(peer_id, request, response) } + IncomingEthRequest::GetReceipts69 { peer_id, request, response } => { + this.on_receipts69_request(peer_id, request, response) + } } }, ); @@ -315,4 +344,15 @@ pub enum IncomingEthRequest { /// The channel sender for the response containing receipts. response: oneshot::Sender>>, }, + /// Request Receipts from the peer without bloom filter. + /// + /// The response should be sent through the channel. + GetReceipts69 { + /// The ID of the peer to request receipts from. + peer_id: PeerId, + /// The specific receipts requested. + request: GetReceipts, + /// The channel sender for the response containing Receipts69. + response: oneshot::Sender>>, + }, } diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 4bc279b335a..dcb77e30937 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -512,6 +512,13 @@ impl NetworkManager { response, }) } + PeerRequest::GetReceipts69 { request, response } => { + self.delegate_eth_request(IncomingEthRequest::GetReceipts69 { + peer_id, + request, + response, + }) + } PeerRequest::GetPooledTransactions { request, response } => { self.notify_tx_manager(NetworkTransactionEvent::GetPooledTransactions { peer_id, diff --git a/crates/net/network/src/message.rs b/crates/net/network/src/message.rs index f1dd603fd22..7b489d2ffac 100644 --- a/crates/net/network/src/message.rs +++ b/crates/net/network/src/message.rs @@ -107,6 +107,11 @@ pub enum PeerResponse { /// The receiver channel for the response to a receipts request. response: oneshot::Receiver>>, }, + /// Represents a response to a request for receipts. + Receipts69 { + /// The receiver channel for the response to a receipts request. + response: oneshot::Receiver>>, + }, } // === impl PeerResponse === @@ -139,6 +144,9 @@ impl PeerResponse { Self::Receipts { response } => { poll_request!(response, Receipts, cx) } + Self::Receipts69 { response } => { + poll_request!(response, Receipts69, cx) + } }; Poll::Ready(res) } diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index b89946056e8..45a46081788 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -16,7 +16,7 @@ use crate::{ session::{ conn::EthRlpxConnection, handle::{ActiveSessionMessage, SessionCommand}, - BlockRangeInfo, SessionId, + BlockRangeInfo, EthVersion, SessionId, }, }; use alloy_primitives::Sealable; @@ -258,7 +258,11 @@ impl ActiveSession { on_response!(resp, GetNodeData) } EthMessage::GetReceipts(req) => { - on_request!(req, Receipts, GetReceipts) + if self.conn.version() >= EthVersion::Eth69 { + on_request!(req, Receipts69, GetReceipts69) + } else { + on_request!(req, Receipts, GetReceipts) + } } EthMessage::Receipts(resp) => { on_response!(resp, GetReceipts) diff --git a/crates/rpc/ipc/src/server/mod.rs b/crates/rpc/ipc/src/server/mod.rs index 818761f4475..e9e00a7f6c0 100644 --- a/crates/rpc/ipc/src/server/mod.rs +++ b/crates/rpc/ipc/src/server/mod.rs @@ -431,7 +431,7 @@ struct ProcessConnection<'a, HttpMiddleware, RpcMiddleware> { /// Spawns the IPC connection onto a new task #[instrument(name = "connection", skip_all, fields(conn_id = %params.conn_id), level = "INFO")] -fn process_connection<'b, RpcMiddleware, HttpMiddleware>( +fn process_connection( params: ProcessConnection<'_, HttpMiddleware, RpcMiddleware>, ) where RpcMiddleware: Layer + Clone + Send + 'static, diff --git a/docs/crates/network.md b/docs/crates/network.md index a35b0c9de90..15c9c2494f5 100644 --- a/docs/crates/network.md +++ b/docs/crates/network.md @@ -494,6 +494,7 @@ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { } IncomingEthRequest::GetNodeData { .. } => {} IncomingEthRequest::GetReceipts { .. } => {} + IncomingEthRequest::GetReceipts69 { .. } => {} }, } } diff --git a/examples/network-proxy/src/main.rs b/examples/network-proxy/src/main.rs index 461fe348360..51ba8e2b4a4 100644 --- a/examples/network-proxy/src/main.rs +++ b/examples/network-proxy/src/main.rs @@ -81,6 +81,7 @@ async fn main() -> eyre::Result<()> { IncomingEthRequest::GetBlockBodies { .. } => {} IncomingEthRequest::GetNodeData { .. } => {} IncomingEthRequest::GetReceipts { .. } => {} + IncomingEthRequest::GetReceipts69 { .. } => {} } } transaction_message = transactions_rx.recv() => { From 91977c9d3ae098b2dce9893cef5376470e66c24d Mon Sep 17 00:00:00 2001 From: Femi Bankole Date: Thu, 12 Jun 2025 14:26:53 +0100 Subject: [PATCH 019/274] feat: introduce 10s timeout when resolving external ips (#16787) --- crates/net/nat/src/lib.rs | 3 ++- crates/net/network/src/transactions/config.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/net/nat/src/lib.rs b/crates/net/nat/src/lib.rs index e4ff4413051..c7466b44012 100644 --- a/crates/net/nat/src/lib.rs +++ b/crates/net/nat/src/lib.rs @@ -235,7 +235,8 @@ async fn resolve_external_ip_url_res(url: &str) -> Result { } async fn resolve_external_ip_url(url: &str) -> Option { - let response = reqwest::get(url).await.ok()?; + let client = reqwest::Client::builder().timeout(Duration::from_secs(10)).build().ok()?; + let response = client.get(url).send().await.ok()?; let response = response.error_for_status().ok()?; let text = response.text().await.ok()?; text.trim().parse().ok() diff --git a/crates/net/network/src/transactions/config.rs b/crates/net/network/src/transactions/config.rs index 85ea9e23589..f23900aaffe 100644 --- a/crates/net/network/src/transactions/config.rs +++ b/crates/net/network/src/transactions/config.rs @@ -168,8 +168,8 @@ pub enum AnnouncementAcceptance { }, } -/// A policy that defines how to handle incoming transaction annoucements, -/// particularly concerning transaction types and other annoucement metadata. +/// A policy that defines how to handle incoming transaction announcements, +/// particularly concerning transaction types and other announcement metadata. pub trait AnnouncementFilteringPolicy: Send + Sync + Unpin + 'static { /// Decides how to handle a transaction announcement based on its type, hash, and size. fn decide_on_announcement(&self, ty: u8, hash: &B256, size: usize) -> AnnouncementAcceptance; From 65b824aef070256d503323c9dc9277ff529537b0 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Thu, 12 Jun 2025 18:40:39 +0200 Subject: [PATCH 020/274] chore: pin hive and add test to expected failures (#16790) --- .github/assets/hive/expected_failures.yaml | 3 +++ .github/workflows/hive.yml | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/assets/hive/expected_failures.yaml b/.github/assets/hive/expected_failures.yaml index ae1cc77086b..f155a3478c6 100644 --- a/.github/assets/hive/expected_failures.yaml +++ b/.github/assets/hive/expected_failures.yaml @@ -40,6 +40,9 @@ engine-api: [] # no fix due to https://github.com/paradigmxyz/reth/issues/8732 engine-cancun: - Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth) + # the test fails with older verions of the code for which it passed before, probably related to changes + # in hive or its dependencies + - Blob Transaction Ordering, Multiple Clients (Cancun) (reth) sync: [] diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index afba4c126dd..70516f0361f 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -32,7 +32,8 @@ jobs: uses: actions/checkout@v4 with: repository: ethereum/hive - ref: master + # TODO: unpin when https://github.com/ethereum/hive/issues/1306 is fixed + ref: edd9969338dd1798ba2e61f049c7e3a15cef53e6 path: hivetests - uses: actions/setup-go@v5 From 217289af6f58647af9011be01bd0592abdc5b844 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 13 Jun 2025 07:54:25 +0200 Subject: [PATCH 021/274] chore: re-export more op types (#16788) --- crates/optimism/node/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/optimism/node/src/lib.rs b/crates/optimism/node/src/lib.rs index ac9cfe98d83..4ef8a706785 100644 --- a/crates/optimism/node/src/lib.rs +++ b/crates/optimism/node/src/lib.rs @@ -18,7 +18,6 @@ pub mod args; /// trait. pub mod engine; pub use engine::OpEngineTypes; -pub use reth_optimism_payload_builder::{OpPayloadPrimitives, OpPayloadTypes}; pub mod node; pub use node::*; @@ -36,7 +35,8 @@ pub use reth_optimism_txpool as txpool; pub mod utils; pub use reth_optimism_payload_builder::{ - OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilder, OpPayloadBuilderAttributes, + self as payload, config::OpDAConfig, OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilder, + OpPayloadBuilderAttributes, OpPayloadPrimitives, OpPayloadTypes, }; pub use reth_optimism_evm::*; From f01f31a40e1e1c85d3dd3031537534ee58cd2f6a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 13 Jun 2025 10:30:29 +0200 Subject: [PATCH 022/274] chore: re-export network types (#16789) --- crates/net/network/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index 1072d526fbb..a6de95512f8 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -172,8 +172,11 @@ pub use swarm::NetworkConnectionState; /// re-export p2p interfaces pub use reth_network_p2p as p2p; -/// re-export types crate -pub use reth_eth_wire_types as types; +/// re-export types crates +pub mod types { + pub use reth_eth_wire_types::*; + pub use reth_network_types::*; +} use aquamarine as _; From 93e2e5876ff6f39cc71c55a4fc5a4bea0fb20d6c Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 13 Jun 2025 18:44:18 +1000 Subject: [PATCH 023/274] feat: make EthereumConsensusBuilder generic over chainSpec (#16793) --- crates/ethereum/node/src/node.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index e8c9d002eb6..ac16b5f4dee 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -398,7 +398,9 @@ pub struct EthereumConsensusBuilder { impl ConsensusBuilder for EthereumConsensusBuilder where - Node: FullNodeTypes>, + Node: FullNodeTypes< + Types: NodeTypes, + >, { type Consensus = Arc>; From 6f1a32bd04238901c92628237bab061e7a2805d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 13 Jun 2025 13:05:51 +0200 Subject: [PATCH 024/274] feat(cli): Create `folder` in chain specific data directory for `import-era` command (#16799) --- crates/cli/commands/src/import_era.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/cli/commands/src/import_era.rs b/crates/cli/commands/src/import_era.rs index fbd8d23bd56..d585fe23788 100644 --- a/crates/cli/commands/src/import_era.rs +++ b/crates/cli/commands/src/import_era.rs @@ -2,14 +2,14 @@ use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs}; use alloy_chains::{ChainKind, NamedChain}; use clap::{Args, Parser}; -use eyre::{eyre, OptionExt}; +use eyre::eyre; use reqwest::{Client, Url}; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_cli::chainspec::ChainSpecParser; use reth_era_downloader::{read_dir, EraClient, EraStream, EraStreamConfig}; use reth_era_utils as era; use reth_etl::Collector; -use reth_node_core::{dirs::data_dir, version::SHORT_VERSION}; +use reth_node_core::version::SHORT_VERSION; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -81,7 +81,9 @@ impl> ImportEraC Some(url) => url, None => self.env.chain.chain().kind().try_to_url()?, }; - let folder = data_dir().ok_or_eyre("Missing data directory")?.join("era"); + let folder = + self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era"); + let folder = folder.into_boxed_path(); let client = EraClient::new(Client::new(), url, folder); let stream = EraStream::new(client, EraStreamConfig::default()); From 71d8420426b968fb2977d42a55de981c04ec06df Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 13 Jun 2025 13:07:45 +0200 Subject: [PATCH 025/274] chore: bump inspectors 0.24 (#16797) --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23619f24cae..b392ace43cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10632,9 +10632,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "354e963abdea6d5b80b978614e0016a098a764063f92b2316c624faacd5301cb" +checksum = "48db62c066383dfc2e9636c31999265d48c19fc47652a85f9b3071c2e7512158" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", diff --git a/Cargo.toml b/Cargo.toml index 19d070fd363..03283001c27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -457,7 +457,7 @@ revm-context = { version = "5.0.0", default-features = false } revm-context-interface = { version = "5.0.0", default-features = false } revm-database-interface = { version = "4.0.0", default-features = false } op-revm = { version = "5.0.0", default-features = false } -revm-inspectors = "0.23.0" +revm-inspectors = "0.24.0" # eth alloy-chains = { version = "0.2.0", default-features = false } From 4bc77c729f05acaa88ee5418d3d9995002967cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 13 Jun 2025 13:45:45 +0200 Subject: [PATCH 026/274] feat(cli): Create `folder` and all its parents before `import` in `import-era` command (#16800) --- crates/cli/commands/src/import_era.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/cli/commands/src/import_era.rs b/crates/cli/commands/src/import_era.rs index d585fe23788..849b925a975 100644 --- a/crates/cli/commands/src/import_era.rs +++ b/crates/cli/commands/src/import_era.rs @@ -9,6 +9,7 @@ use reth_cli::chainspec::ChainSpecParser; use reth_era_downloader::{read_dir, EraClient, EraStream, EraStreamConfig}; use reth_era_utils as era; use reth_etl::Collector; +use reth_fs_util as fs; use reth_node_core::version::SHORT_VERSION; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -85,6 +86,9 @@ impl> ImportEraC self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era"); let folder = folder.into_boxed_path(); + + fs::create_dir_all(&folder)?; + let client = EraClient::new(Client::new(), url, folder); let stream = EraStream::new(client, EraStreamConfig::default()); From 8d691ab2c2255b79fdfb03da497f246b20edc329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 13 Jun 2025 14:06:53 +0200 Subject: [PATCH 027/274] feat(examples): Add `CustomEngineValidator` and its builder to the `custom_node` example (#16774) --- Cargo.lock | 1 + examples/custom-node/Cargo.toml | 4 +- examples/custom-node/src/engine.rs | 206 ++++++++++++++++++++++++----- 3 files changed, 175 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b392ace43cf..377f2d19076 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3382,6 +3382,7 @@ dependencies = [ "revm-primitives", "serde", "test-fuzz", + "thiserror 2.0.12", ] [[package]] diff --git a/examples/custom-node/Cargo.toml b/examples/custom-node/Cargo.toml index 4821ef54f40..f43f2eb1c43 100644 --- a/examples/custom-node/Cargo.toml +++ b/examples/custom-node/Cargo.toml @@ -17,7 +17,7 @@ reth-op = { workspace = true, features = ["node", "pool"] } reth-payload-builder.workspace = true reth-rpc-api.workspace = true reth-rpc-engine-api.workspace = true -reth-ethereum = { workspace = true, features = ["node-api", "network", "evm", "pool"] } +reth-ethereum = { workspace = true, features = ["node-api", "network", "evm", "pool", "trie", "storage-api"] } # revm revm.workspace = true @@ -43,7 +43,7 @@ derive_more.workspace = true eyre.workspace = true jsonrpsee.workspace = true serde.workspace = true - +thiserror.workspace = true modular-bitfield.workspace = true [dev-dependencies] diff --git a/examples/custom-node/src/engine.rs b/examples/custom-node/src/engine.rs index ab938be82d4..e3bc6019d7b 100644 --- a/examples/custom-node/src/engine.rs +++ b/examples/custom-node/src/engine.rs @@ -1,19 +1,32 @@ -use crate::primitives::CustomNodePrimitives; +use crate::{ + chainspec::CustomChainSpec, + primitives::{CustomHeader, CustomNodePrimitives, CustomTransaction}, +}; use op_alloy_rpc_types_engine::{OpExecutionData, OpExecutionPayload}; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_ethereum::{ node::api::{ - BuiltPayload, ExecutionPayload, NodePrimitives, PayloadAttributes, - PayloadBuilderAttributes, PayloadTypes, + validate_version_specific_fields, AddOnsContext, BuiltPayload, EngineApiMessageVersion, + EngineObjectValidationError, EngineValidator, ExecutionPayload, FullNodeComponents, + InvalidPayloadAttributesError, NewPayloadError, NodePrimitives, NodeTypes, + PayloadAttributes, PayloadBuilderAttributes, PayloadOrAttributes, PayloadTypes, + PayloadValidator, }, - primitives::SealedBlock, + primitives::{RecoveredBlock, SealedBlock}, + storage::StateProviderFactory, + trie::{KeccakKeyHasher, KeyHasher}, }; +use reth_node_builder::rpc::EngineValidatorBuilder; use reth_op::{ - node::{OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilderAttributes}, + node::{ + engine::OpEngineValidator, OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilderAttributes, + }, OpTransactionSigned, }; use revm_primitives::U256; use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use thiserror::Error; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub struct CustomPayloadTypes; @@ -25,6 +38,10 @@ pub struct CustomExecutionData { } impl ExecutionPayload for CustomExecutionData { + fn parent_hash(&self) -> revm_primitives::B256 { + self.inner.parent_hash() + } + fn block_hash(&self) -> revm_primitives::B256 { self.inner.block_hash() } @@ -33,24 +50,20 @@ impl ExecutionPayload for CustomExecutionData { self.inner.block_number() } - fn parent_hash(&self) -> revm_primitives::B256 { - self.inner.parent_hash() + fn withdrawals(&self) -> Option<&Vec> { + None } - fn gas_used(&self) -> u64 { - self.inner.gas_used() + fn parent_beacon_block_root(&self) -> Option { + self.inner.parent_beacon_block_root() } fn timestamp(&self) -> u64 { self.inner.timestamp() } - fn parent_beacon_block_root(&self) -> Option { - self.inner.parent_beacon_block_root() - } - - fn withdrawals(&self) -> Option<&Vec> { - None + fn gas_used(&self) -> u64 { + self.inner.gas_used() } } @@ -62,10 +75,6 @@ pub struct CustomPayloadAttributes { } impl PayloadAttributes for CustomPayloadAttributes { - fn parent_beacon_block_root(&self) -> Option { - self.inner.parent_beacon_block_root() - } - fn timestamp(&self) -> u64 { self.inner.timestamp() } @@ -73,6 +82,10 @@ impl PayloadAttributes for CustomPayloadAttributes { fn withdrawals(&self) -> Option<&Vec> { self.inner.withdrawals() } + + fn parent_beacon_block_root(&self) -> Option { + self.inner.parent_beacon_block_root() + } } #[derive(Debug, Clone)] @@ -101,28 +114,28 @@ impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes { }) } - fn parent(&self) -> revm_primitives::B256 { - self.inner.parent() + fn payload_id(&self) -> alloy_rpc_types_engine::PayloadId { + self.inner.payload_id() } - fn parent_beacon_block_root(&self) -> Option { - self.inner.parent_beacon_block_root() + fn parent(&self) -> revm_primitives::B256 { + self.inner.parent() } - fn payload_id(&self) -> alloy_rpc_types_engine::PayloadId { - self.inner.payload_id() + fn timestamp(&self) -> u64 { + self.inner.timestamp() } - fn prev_randao(&self) -> revm_primitives::B256 { - self.inner.prev_randao() + fn parent_beacon_block_root(&self) -> Option { + self.inner.parent_beacon_block_root() } fn suggested_fee_recipient(&self) -> revm_primitives::Address { self.inner.suggested_fee_recipient() } - fn timestamp(&self) -> u64 { - self.inner.timestamp() + fn prev_randao(&self) -> revm_primitives::B256 { + self.inner.prev_randao() } fn withdrawals(&self) -> &alloy_eips::eip4895::Withdrawals { @@ -140,14 +153,14 @@ impl BuiltPayload for CustomBuiltPayload { self.0.block() } - fn executed_block(&self) -> Option> { - self.0.executed_block() - } - fn fees(&self) -> U256 { self.0.fees() } + fn executed_block(&self) -> Option> { + self.0.executed_block() + } + fn requests(&self) -> Option { self.0.requests() } @@ -162,10 +175,10 @@ impl From } impl PayloadTypes for CustomPayloadTypes { + type ExecutionData = CustomExecutionData; type BuiltPayload = CustomBuiltPayload; type PayloadAttributes = CustomPayloadAttributes; type PayloadBuilderAttributes = CustomPayloadBuilderAttributes; - type ExecutionData = CustomExecutionData; fn block_to_payload( block: SealedBlock< @@ -179,3 +192,128 @@ impl PayloadTypes for CustomPayloadTypes { CustomExecutionData { inner: OpExecutionData { payload, sidecar }, extension } } } + +/// Custom engine validator +#[derive(Debug, Clone)] +pub struct CustomEngineValidator

{ + inner: OpEngineValidator, +} + +impl

CustomEngineValidator

+where + P: Send + Sync + Unpin + 'static, +{ + /// Instantiates a new validator. + pub fn new(chain_spec: Arc, provider: P) -> Self { + Self { inner: OpEngineValidator::new::(chain_spec, provider) } + } + + /// Returns the chain spec used by the validator. + #[inline] + fn chain_spec(&self) -> &CustomChainSpec { + self.inner.chain_spec() + } +} + +impl

PayloadValidator for CustomEngineValidator

+where + P: StateProviderFactory + Send + Sync + Unpin + 'static, +{ + type Block = crate::primitives::block::Block; + type ExecutionData = CustomExecutionData; + + fn ensure_well_formed_payload( + &self, + payload: CustomExecutionData, + ) -> Result, NewPayloadError> { + let sealed_block = self.inner.ensure_well_formed_payload(payload.inner)?; + let (block, senders) = sealed_block.split_sealed(); + let (header, body) = block.split_sealed_header_body(); + let header = CustomHeader { inner: header.into_header(), extension: payload.extension }; + let body = body.map_ommers(|_| CustomHeader::default()); + let block = SealedBlock::::from_parts_unhashed(header, body); + + Ok(block.with_senders(senders)) + } +} + +impl EngineValidator for CustomEngineValidator

+where + P: StateProviderFactory + Send + Sync + Unpin + 'static, + T: PayloadTypes< + PayloadAttributes = CustomPayloadAttributes, + ExecutionData = CustomExecutionData, + >, +{ + fn validate_version_specific_fields( + &self, + version: EngineApiMessageVersion, + payload_or_attrs: PayloadOrAttributes<'_, Self::ExecutionData, T::PayloadAttributes>, + ) -> Result<(), EngineObjectValidationError> { + validate_version_specific_fields(self.chain_spec(), version, payload_or_attrs) + } + + fn ensure_well_formed_attributes( + &self, + version: EngineApiMessageVersion, + attributes: &T::PayloadAttributes, + ) -> Result<(), EngineObjectValidationError> { + validate_version_specific_fields( + self.chain_spec(), + version, + PayloadOrAttributes::::PayloadAttributes( + attributes, + ), + )?; + + // custom validation logic - ensure that the custom field is not zero + if attributes.extension == 0 { + return Err(EngineObjectValidationError::invalid_params( + CustomError::CustomFieldIsNotZero, + )) + } + + Ok(()) + } + + fn validate_payload_attributes_against_header( + &self, + _attr: &::PayloadAttributes, + _header: &::Header, + ) -> Result<(), InvalidPayloadAttributesError> { + // skip default timestamp validation + Ok(()) + } +} + +/// Custom error type used in payload attributes validation +#[derive(Debug, Error)] +pub enum CustomError { + #[error("Custom field is not zero")] + CustomFieldIsNotZero, +} + +/// Custom engine validator builder +#[derive(Debug, Default, Clone, Copy)] +#[non_exhaustive] +pub struct CustomEngineValidatorBuilder; + +impl EngineValidatorBuilder for CustomEngineValidatorBuilder +where + N: FullNodeComponents< + Types: NodeTypes< + Payload = CustomPayloadTypes, + ChainSpec = CustomChainSpec, + Primitives = CustomNodePrimitives, + >, + >, +{ + type Validator = CustomEngineValidator; + + async fn build(self, ctx: &AddOnsContext<'_, N>) -> eyre::Result { + Ok(CustomEngineValidator::new::( + ctx.config.chain.clone(), + ctx.node.provider().clone(), + )) + } +} From 7272b217abc22f889ed88bbc3bcff8a8e13ce4bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 13 Jun 2025 14:49:10 +0200 Subject: [PATCH 028/274] feat(rpc): Implement `IntoRpcTx` with Ethereum RPC transaction response for `Extended` (#16783) --- .../rpc/rpc-types-compat/src/transaction.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 4dca40ce272..06f50aebf8b 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -2,7 +2,8 @@ use crate::fees::{CallFees, CallFeesError}; use alloy_consensus::{ - error::ValueError, transaction::Recovered, EthereumTxEnvelope, SignableTransaction, TxEip4844, + error::ValueError, transaction::Recovered, EthereumTxEnvelope, Extended, SignableTransaction, + Transaction as ConsensusTransaction, TxEip4844, }; use alloy_network::Network; use alloy_primitives::{Address, Bytes, Signature, TxKind, U256}; @@ -94,6 +95,33 @@ pub trait IntoRpcTx { fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> T; } +impl IntoRpcTx> for Extended +where + BuiltIn: ConsensusTransaction, + Other: ConsensusTransaction, +{ + type TxInfo = TransactionInfo; + + fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> Transaction { + let TransactionInfo { + block_hash, block_number, index: transaction_index, base_fee, .. + } = tx_info; + let effective_gas_price = base_fee + .map(|base_fee| { + self.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 + }) + .unwrap_or_else(|| self.max_fee_per_gas()); + + Transaction { + inner: Recovered::new_unchecked(self, signer), + block_hash, + block_number, + transaction_index, + effective_gas_price: Some(effective_gas_price), + } + } +} + /// Converts `self` into `T`. /// /// Should create a fake transaction for simulation using [`TransactionRequest`]. From 1f37bddd83486bfe1370c575b772f861d10d8b57 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 13 Jun 2025 14:54:16 +0200 Subject: [PATCH 029/274] test: add eth69 request/response tests (#16806) --- crates/net/network/tests/it/requests.rs | 258 +++++++++++++++++++++++- 1 file changed, 253 insertions(+), 5 deletions(-) diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 3a9dcf6308a..aa6c1d9c107 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -1,14 +1,12 @@ #![allow(unreachable_pub)] //! Tests for eth related requests -use std::sync::Arc; - use alloy_consensus::Header; use rand::Rng; -use reth_eth_wire::HeadersDirection; +use reth_eth_wire::{EthVersion, HeadersDirection}; use reth_ethereum_primitives::Block; use reth_network::{ - test_utils::{NetworkEventStream, Testnet}, + test_utils::{NetworkEventStream, PeerConfig, Testnet}, BlockDownloaderProvider, NetworkEventListenerProvider, }; use reth_network_api::{NetworkInfo, Peers}; @@ -17,7 +15,9 @@ use reth_network_p2p::{ headers::client::{HeadersClient, HeadersRequest}, }; use reth_provider::test_utils::MockEthProvider; -use reth_transaction_pool::test_utils::TransactionGenerator; +use reth_transaction_pool::test_utils::{TestPool, TransactionGenerator}; +use std::sync::Arc; +use tokio::sync::oneshot; #[tokio::test(flavor = "multi_thread")] async fn test_get_body() { @@ -107,3 +107,251 @@ async fn test_get_header() { assert_eq!(headers[0], header); } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth68_get_receipts() { + reth_tracing::init_test_tracing(); + let mut rng = rand::rng(); + let mock_provider = Arc::new(MockEthProvider::default()); + + let mut net: Testnet, TestPool> = Testnet::default(); + + // Create peers with ETH68 protocol explicitly + let p0 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth68.into())); + net.add_peer_with_config(p0).await.unwrap(); + + let p1 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth68.into())); + net.add_peer_with_config(p1).await.unwrap(); + + // install request handlers + net.for_each_mut(|peer| peer.install_request_handler()); + + let handle0 = net.peers()[0].handle(); + let mut events0 = NetworkEventStream::new(handle0.event_listener()); + + let handle1 = net.peers()[1].handle(); + + let _handle = net.spawn(); + + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + let connected = events0.next_session_established().await.unwrap(); + assert_eq!(connected, *handle1.peer_id()); + + // Create test receipts and add them to the mock provider + for block_num in 1..=10 { + let block_hash = rng.random(); + let header = Header { number: block_num, ..Default::default() }; + + // Create some test receipts + let receipts = vec![ + reth_ethereum_primitives::Receipt { + cumulative_gas_used: 21000, + success: true, + ..Default::default() + }, + reth_ethereum_primitives::Receipt { + cumulative_gas_used: 42000, + success: false, + ..Default::default() + }, + ]; + + mock_provider.add_header(block_hash, header.clone()); + mock_provider.add_receipts(header.number, receipts); + + // Test receipt request via low-level peer request + let (tx, rx) = oneshot::channel(); + handle0.send_request( + *handle1.peer_id(), + reth_network::PeerRequest::GetReceipts { + request: reth_eth_wire::GetReceipts(vec![block_hash]), + response: tx, + }, + ); + + let result = rx.await.unwrap(); + let receipts_response = result.unwrap(); + assert_eq!(receipts_response.0.len(), 1); + assert_eq!(receipts_response.0[0].len(), 2); + // Eth68 receipts should have bloom filters - verify the structure + assert_eq!(receipts_response.0[0][0].receipt.cumulative_gas_used, 21000); + assert_eq!(receipts_response.0[0][1].receipt.cumulative_gas_used, 42000); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_get_headers() { + reth_tracing::init_test_tracing(); + let mut rng = rand::rng(); + let mock_provider = Arc::new(MockEthProvider::default()); + + let mut net: Testnet, TestPool> = Testnet::default(); + + // Create peers with ETH69 protocol + let p0 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p0).await.unwrap(); + + let p1 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p1).await.unwrap(); + + // install request handlers + net.for_each_mut(|peer| peer.install_request_handler()); + + let handle0 = net.peers()[0].handle(); + let mut events0 = NetworkEventStream::new(handle0.event_listener()); + + let handle1 = net.peers()[1].handle(); + + let _handle = net.spawn(); + + let fetch0 = handle0.fetch_client().await.unwrap(); + + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + let connected = events0.next_session_established().await.unwrap(); + assert_eq!(connected, *handle1.peer_id()); + + let start: u64 = rng.random(); + let mut hash = rng.random(); + // request some headers via eth69 connection + for idx in 0..50 { + let header = Header { number: start + idx, parent_hash: hash, ..Default::default() }; + hash = rng.random(); + + mock_provider.add_header(hash, header.clone()); + + let req = + HeadersRequest { start: hash.into(), limit: 1, direction: HeadersDirection::Falling }; + + let res = fetch0.get_headers(req).await; + assert!(res.is_ok(), "{res:?}"); + + let headers = res.unwrap().1; + assert_eq!(headers.len(), 1); + assert_eq!(headers[0], header); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_get_bodies() { + reth_tracing::init_test_tracing(); + let mut rng = rand::rng(); + let mock_provider = Arc::new(MockEthProvider::default()); + let mut tx_gen = TransactionGenerator::new(rand::rng()); + + let mut net: Testnet, TestPool> = Testnet::default(); + + // Create peers with ETH69 protocol + let p0 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p0).await.unwrap(); + + let p1 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p1).await.unwrap(); + + // install request handlers + net.for_each_mut(|peer| peer.install_request_handler()); + + let handle0 = net.peers()[0].handle(); + let mut events0 = NetworkEventStream::new(handle0.event_listener()); + + let handle1 = net.peers()[1].handle(); + + let _handle = net.spawn(); + + let fetch0 = handle0.fetch_client().await.unwrap(); + + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + let connected = events0.next_session_established().await.unwrap(); + assert_eq!(connected, *handle1.peer_id()); + + // request some blocks via eth69 connection + for _ in 0..50 { + let block_hash = rng.random(); + let mut block: Block = Block::default(); + block.body.transactions.push(tx_gen.gen_eip4844()); + + mock_provider.add_block(block_hash, block.clone()); + + let res = fetch0.get_block_bodies(vec![block_hash]).await; + assert!(res.is_ok(), "{res:?}"); + + let blocks = res.unwrap().1; + assert_eq!(blocks.len(), 1); + assert_eq!(blocks[0], block.body); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_get_receipts() { + reth_tracing::init_test_tracing(); + let mut rng = rand::rng(); + let mock_provider = Arc::new(MockEthProvider::default()); + + let mut net: Testnet, TestPool> = Testnet::default(); + + // Create peers with ETH69 protocol + let p0 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p0).await.unwrap(); + + let p1 = PeerConfig::with_protocols(mock_provider.clone(), Some(EthVersion::Eth69.into())); + net.add_peer_with_config(p1).await.unwrap(); + + // install request handlers + net.for_each_mut(|peer| peer.install_request_handler()); + + let handle0 = net.peers()[0].handle(); + let mut events0 = NetworkEventStream::new(handle0.event_listener()); + + let handle1 = net.peers()[1].handle(); + + let _handle = net.spawn(); + + handle0.add_peer(*handle1.peer_id(), handle1.local_addr()); + + // Wait for the session to be established + let connected = events0.next_session_established().await.unwrap(); + assert_eq!(connected, *handle1.peer_id()); + + // Create test receipts and add them to the mock provider + for block_num in 1..=10 { + let block_hash = rng.random(); + let header = Header { number: block_num, ..Default::default() }; + + // Create some test receipts + let receipts = vec![ + reth_ethereum_primitives::Receipt { + cumulative_gas_used: 21000, + success: true, + ..Default::default() + }, + reth_ethereum_primitives::Receipt { + cumulative_gas_used: 42000, + success: false, + ..Default::default() + }, + ]; + + mock_provider.add_header(block_hash, header.clone()); + mock_provider.add_receipts(header.number, receipts); + + let (tx, rx) = oneshot::channel(); + handle0.send_request( + *handle1.peer_id(), + reth_network::PeerRequest::GetReceipts { + request: reth_eth_wire::GetReceipts(vec![block_hash]), + response: tx, + }, + ); + + let result = rx.await.unwrap(); + let receipts_response = match result { + Ok(resp) => resp, + Err(e) => panic!("Failed to get receipts response: {e:?}"), + }; + assert_eq!(receipts_response.0.len(), 1); + assert_eq!(receipts_response.0[0].len(), 2); + // When using GetReceipts request with ETH69 peers, the response should still include bloom + // filters The protocol version handling is done at a lower level + assert_eq!(receipts_response.0[0][0].receipt.cumulative_gas_used, 21000); + assert_eq!(receipts_response.0[0][1].receipt.cumulative_gas_used, 42000); + } +} From 381811406e2b4ce9240eb54114d06db5e78b3b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 13 Jun 2025 22:49:18 +0200 Subject: [PATCH 030/274] feat(era): Accept anything that converts into `Box` as `folder` of `EraClient` (#16802) --- crates/cli/commands/src/import_era.rs | 2 -- crates/era-downloader/src/client.rs | 10 +++------- crates/era-downloader/src/lib.rs | 2 +- crates/era-downloader/tests/it/checksums.rs | 4 ++-- crates/era-downloader/tests/it/download.rs | 4 ++-- crates/era-downloader/tests/it/list.rs | 2 +- crates/era-downloader/tests/it/stream.rs | 8 ++++---- crates/era-utils/tests/it/history.rs | 2 +- crates/era/tests/it/main.rs | 2 +- 9 files changed, 15 insertions(+), 21 deletions(-) diff --git a/crates/cli/commands/src/import_era.rs b/crates/cli/commands/src/import_era.rs index 849b925a975..4b22799b7e4 100644 --- a/crates/cli/commands/src/import_era.rs +++ b/crates/cli/commands/src/import_era.rs @@ -85,8 +85,6 @@ impl> ImportEraC let folder = self.env.datadir.resolve_datadir(self.env.chain.chain()).data_dir().join("era"); - let folder = folder.into_boxed_path(); - fs::create_dir_all(&folder)?; let client = EraClient::new(Client::new(), url, folder); diff --git a/crates/era-downloader/src/client.rs b/crates/era-downloader/src/client.rs index 752523c262f..90ac03ef3a0 100644 --- a/crates/era-downloader/src/client.rs +++ b/crates/era-downloader/src/client.rs @@ -49,8 +49,8 @@ impl EraClient { const CHECKSUMS: &'static str = "checksums.txt"; /// Constructs [`EraClient`] using `client` to download from `url` into `folder`. - pub const fn new(client: Http, url: Url, folder: Box) -> Self { - Self { client, url, folder, start_from: None } + pub fn new(client: Http, url: Url, folder: impl Into>) -> Self { + Self { client, url, folder: folder.into(), start_from: None } } /// Overrides the starting ERA file based on `block_number`. @@ -254,11 +254,7 @@ mod tests { impl EraClient { fn empty() -> Self { - Self::new( - Client::new(), - Url::from_str("file:///").unwrap(), - PathBuf::new().into_boxed_path(), - ) + Self::new(Client::new(), Url::from_str("file:///").unwrap(), PathBuf::new()) } } diff --git a/crates/era-downloader/src/lib.rs b/crates/era-downloader/src/lib.rs index 01147e41e1c..b8722b02270 100644 --- a/crates/era-downloader/src/lib.rs +++ b/crates/era-downloader/src/lib.rs @@ -12,7 +12,7 @@ //! let url = Url::from_str("file:///")?; //! //! // Directory where the ERA1 files will be downloaded to -//! let folder = PathBuf::new().into_boxed_path(); +//! let folder = PathBuf::new(); //! //! let client = EraClient::new(Client::new(), url, folder); //! diff --git a/crates/era-downloader/tests/it/checksums.rs b/crates/era-downloader/tests/it/checksums.rs index 70a78345dbd..a98b4ae630f 100644 --- a/crates/era-downloader/tests/it/checksums.rs +++ b/crates/era-downloader/tests/it/checksums.rs @@ -14,8 +14,8 @@ use test_case::test_case; async fn test_invalid_checksum_returns_error(url: &str) { let base_url = Url::from_str(url).unwrap(); let folder = tempdir().unwrap(); - let folder = folder.path().to_owned().into_boxed_path(); - let client = EraClient::new(FailingClient, base_url, folder.clone()); + let folder = folder.path(); + let client = EraClient::new(FailingClient, base_url, folder); let mut stream = EraStream::new( client, diff --git a/crates/era-downloader/tests/it/download.rs b/crates/era-downloader/tests/it/download.rs index 5502874fc1f..e7756bfede9 100644 --- a/crates/era-downloader/tests/it/download.rs +++ b/crates/era-downloader/tests/it/download.rs @@ -13,7 +13,7 @@ use test_case::test_case; async fn test_getting_file_url_after_fetching_file_list(url: &str) { let base_url = Url::from_str(url).unwrap(); let folder = tempdir().unwrap(); - let folder = folder.path().to_owned().into_boxed_path(); + let folder = folder.path(); let client = EraClient::new(StubClient, base_url.clone(), folder); client.fetch_file_list().await.unwrap(); @@ -31,7 +31,7 @@ async fn test_getting_file_url_after_fetching_file_list(url: &str) { async fn test_getting_file_after_fetching_file_list(url: &str) { let base_url = Url::from_str(url).unwrap(); let folder = tempdir().unwrap(); - let folder = folder.path().to_owned().into_boxed_path(); + let folder = folder.path(); let mut client = EraClient::new(StubClient, base_url, folder); client.fetch_file_list().await.unwrap(); diff --git a/crates/era-downloader/tests/it/list.rs b/crates/era-downloader/tests/it/list.rs index adc0df7e1cb..3940fa5d8be 100644 --- a/crates/era-downloader/tests/it/list.rs +++ b/crates/era-downloader/tests/it/list.rs @@ -13,7 +13,7 @@ use test_case::test_case; async fn test_getting_file_name_after_fetching_file_list(url: &str) { let url = Url::from_str(url).unwrap(); let folder = tempdir().unwrap(); - let folder = folder.path().to_owned().into_boxed_path(); + let folder = folder.path(); let client = EraClient::new(StubClient, url, folder); client.fetch_file_list().await.unwrap(); diff --git a/crates/era-downloader/tests/it/stream.rs b/crates/era-downloader/tests/it/stream.rs index e9a97079313..eb7dc2da727 100644 --- a/crates/era-downloader/tests/it/stream.rs +++ b/crates/era-downloader/tests/it/stream.rs @@ -14,8 +14,8 @@ use test_case::test_case; async fn test_streaming_files_after_fetching_file_list(url: &str) { let base_url = Url::from_str(url).unwrap(); let folder = tempdir().unwrap(); - let folder = folder.path().to_owned().into_boxed_path(); - let client = EraClient::new(StubClient, base_url, folder.clone()); + let folder = folder.path(); + let client = EraClient::new(StubClient, base_url, folder); let mut stream = EraStream::new( client, @@ -36,8 +36,8 @@ async fn test_streaming_files_after_fetching_file_list(url: &str) { #[tokio::test] async fn test_streaming_files_after_fetching_file_list_into_missing_folder_fails() { let base_url = Url::from_str("https://era.ithaca.xyz/era1/index.html").unwrap(); - let folder = tempdir().unwrap().path().to_owned().into_boxed_path(); - let client = EraClient::new(StubClient, base_url, folder.clone()); + let folder = tempdir().unwrap().path().to_owned(); + let client = EraClient::new(StubClient, base_url, folder); let mut stream = EraStream::new( client, diff --git a/crates/era-utils/tests/it/history.rs b/crates/era-utils/tests/it/history.rs index d3d447615b9..e392c5d8ade 100644 --- a/crates/era-utils/tests/it/history.rs +++ b/crates/era-utils/tests/it/history.rs @@ -15,7 +15,7 @@ async fn test_history_imports_from_fresh_state_successfully() { // Directory where the ERA1 files will be downloaded to let folder = tempdir().unwrap(); - let folder = folder.path().to_owned().into_boxed_path(); + let folder = folder.path(); let client = EraClient::new(ClientWithFakeIndex(Client::new()), url, folder); diff --git a/crates/era/tests/it/main.rs b/crates/era/tests/it/main.rs index e27e25e1658..fa939819189 100644 --- a/crates/era/tests/it/main.rs +++ b/crates/era/tests/it/main.rs @@ -115,7 +115,7 @@ impl Era1TestDownloader { let final_url = Url::from_str(url).map_err(|e| eyre!("Failed to parse URL: {}", e))?; - let folder = self.temp_dir.path().to_owned().into_boxed_path(); + let folder = self.temp_dir.path(); // set up the client let client = EraClient::new(Client::new(), final_url, folder); From 4a401e18026c3d6307091693dbc8035948a3bf45 Mon Sep 17 00:00:00 2001 From: "fuder.eth" <139509124+vtjl10@users.noreply.github.com> Date: Sat, 14 Jun 2025 17:50:30 +0300 Subject: [PATCH 031/274] fix: typo in test comment (#16811) --- crates/stages/stages/src/stages/sender_recovery.rs | 2 +- crates/stages/stages/src/stages/tx_lookup.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/stages/stages/src/stages/sender_recovery.rs b/crates/stages/stages/src/stages/sender_recovery.rs index e55682a9c0e..e6bdb92cf20 100644 --- a/crates/stages/stages/src/stages/sender_recovery.rs +++ b/crates/stages/stages/src/stages/sender_recovery.rs @@ -599,7 +599,7 @@ mod tests { /// /// 1. If there are any entries in the [`tables::TransactionSenders`] table above a given /// block number. - /// 2. If the is no requested block entry in the bodies table, but + /// 2. If there is no requested block entry in the bodies table, but /// [`tables::TransactionSenders`] is not empty. fn ensure_no_senders_by_block(&self, block: BlockNumber) -> Result<(), TestRunnerError> { let body_result = self diff --git a/crates/stages/stages/src/stages/tx_lookup.rs b/crates/stages/stages/src/stages/tx_lookup.rs index 71a790ccb14..2010e5e3555 100644 --- a/crates/stages/stages/src/stages/tx_lookup.rs +++ b/crates/stages/stages/src/stages/tx_lookup.rs @@ -460,7 +460,7 @@ mod tests { /// /// 1. If there are any entries in the [`tables::TransactionHashNumbers`] table above a /// given block number. - /// 2. If the is no requested block entry in the bodies table, but + /// 2. If there is no requested block entry in the bodies table, but /// [`tables::TransactionHashNumbers`] is not empty. fn ensure_no_hash_by_block(&self, number: BlockNumber) -> Result<(), TestRunnerError> { let body_result = self From f057ad5c135f994266ca35768f9e07388d53486e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 14 Jun 2025 16:53:08 +0200 Subject: [PATCH 032/274] feat: add to_message convenience method to BlockRangeInfo (#16778) --- crates/net/network/src/session/types.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/net/network/src/session/types.rs b/crates/net/network/src/session/types.rs index c8cd98c3cbc..b73bfe3b992 100644 --- a/crates/net/network/src/session/types.rs +++ b/crates/net/network/src/session/types.rs @@ -2,6 +2,7 @@ use alloy_primitives::B256; use parking_lot::RwLock; +use reth_eth_wire::BlockRangeUpdate; use std::{ ops::RangeInclusive, sync::{ @@ -13,7 +14,7 @@ use std::{ /// Information about the range of blocks available from a peer. /// /// This represents the announced `eth69` -/// [`BlockRangeUpdate`](reth_eth_wire_types::BlockRangeUpdate) of a peer. +/// [`BlockRangeUpdate`] of a peer. #[derive(Debug, Clone)] pub struct BlockRangeInfo { /// The inner range information. @@ -65,6 +66,15 @@ impl BlockRangeInfo { self.inner.latest.store(latest, Ordering::Relaxed); *self.inner.latest_hash.write() = latest_hash; } + + /// Converts the current range information to an Eth69 [`BlockRangeUpdate`] message. + pub fn to_message(&self) -> BlockRangeUpdate { + BlockRangeUpdate { + earliest: self.earliest(), + latest: self.latest(), + latest_hash: self.latest_hash(), + } + } } /// Inner structure containing the range information with atomic and thread-safe fields. From 4e97f48182db093533133b63c7b07eba42f572fb Mon Sep 17 00:00:00 2001 From: Rez Date: Sun, 15 Jun 2025 00:50:07 +1000 Subject: [PATCH 033/274] feat: make `EthereumEngineValidator` generic over `ChainSpec` (#16812) --- crates/ethereum/node/src/engine.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/ethereum/node/src/engine.rs b/crates/ethereum/node/src/engine.rs index f6c26dbfb25..14e1f4eff2a 100644 --- a/crates/ethereum/node/src/engine.rs +++ b/crates/ethereum/node/src/engine.rs @@ -5,7 +5,7 @@ pub use alloy_rpc_types_engine::{ ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4, ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes, }; -use reth_chainspec::ChainSpec; +use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_engine_primitives::{EngineValidator, PayloadValidator}; use reth_ethereum_payload_builder::EthereumExecutionPayloadValidator; use reth_ethereum_primitives::Block; @@ -19,11 +19,11 @@ use std::sync::Arc; /// Validator for the ethereum engine API. #[derive(Debug, Clone)] -pub struct EthereumEngineValidator { +pub struct EthereumEngineValidator { inner: EthereumExecutionPayloadValidator, } -impl EthereumEngineValidator { +impl EthereumEngineValidator { /// Instantiates a new validator. pub const fn new(chain_spec: Arc) -> Self { Self { inner: EthereumExecutionPayloadValidator::new(chain_spec) } @@ -36,7 +36,10 @@ impl EthereumEngineValidator { } } -impl PayloadValidator for EthereumEngineValidator { +impl PayloadValidator for EthereumEngineValidator +where + ChainSpec: EthChainSpec + EthereumHardforks + 'static, +{ type Block = Block; type ExecutionData = ExecutionData; @@ -49,8 +52,9 @@ impl PayloadValidator for EthereumEngineValidator { } } -impl EngineValidator for EthereumEngineValidator +impl EngineValidator for EthereumEngineValidator where + ChainSpec: EthChainSpec + EthereumHardforks + 'static, Types: PayloadTypes, { fn validate_version_specific_fields( From 82e99880491b8b7a6847995bd9769cc0b12ea138 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 14 Jun 2025 17:44:57 +0200 Subject: [PATCH 034/274] docs: document transaction flow into the pool (#16777) --- crates/net/network/src/transactions/mod.rs | 34 ++++++ crates/transaction-pool/src/lib.rs | 127 ++++++++++++++++++++- 2 files changed, 156 insertions(+), 5 deletions(-) diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index 0fdee4a915f..78e55c344ff 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -242,6 +242,40 @@ impl TransactionsHandle { /// /// It can be configured with different policies for transaction propagation and announcement /// filtering. See [`NetworkPolicies`] and [`TransactionPolicies`] for more details. +/// +/// ## Network Transaction Processing +/// +/// ### Message Types +/// +/// - **`Transactions`**: Full transaction broadcasts (rejects blob transactions) +/// - **`NewPooledTransactionHashes`**: Hash announcements +/// +/// ### Peer Tracking +/// +/// - Maintains per-peer transaction cache (default: 10,240 entries) +/// - Prevents duplicate imports and enables efficient propagation +/// +/// ### Bad Transaction Handling +/// +/// Caches and rejects transactions with consensus violations (gas, signature, chain ID). +/// Penalizes peers sending invalid transactions. +/// +/// ### Import Management +/// +/// Limits concurrent pool imports and backs off when approaching capacity. +/// +/// ### Transaction Fetching +/// +/// For announced transactions: filters known → queues unknown → fetches → imports +/// +/// ### Propagation Rules +/// +/// Based on: origin (Local/External/Private), peer capabilities, and network state. +/// Disabled during initial sync. +/// +/// ### Security +/// +/// Rate limiting via reputation, bad transaction isolation, peer scoring. #[derive(Debug)] #[must_use = "Manager does nothing unless polled."] pub struct TransactionsManager< diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 8250a8613c4..102f0140a0c 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -12,6 +12,127 @@ //! - monitoring memory footprint and enforce pool size limits //! - storing blob data for transactions in a separate blobstore on insertion //! +//! ## Transaction Flow: From Network/RPC to Pool +//! +//! Transactions enter the pool through two main paths: +//! +//! ### 1. Network Path (P2P) +//! +//! ```text +//! Network Peer +//! ↓ +//! Transactions or NewPooledTransactionHashes message +//! ↓ +//! TransactionsManager (crates/net/network/src/transactions/mod.rs) +//! │ +//! ├─→ For Transactions message: +//! │ ├─→ Validates message format +//! │ ├─→ Checks if transaction already known +//! │ ├─→ Marks peer as having seen the transaction +//! │ └─→ Queues for import +//! │ +//! └─→ For NewPooledTransactionHashes message: +//! ├─→ Filters out already known transactions +//! ├─→ Queues unknown hashes for fetching +//! ├─→ Sends GetPooledTransactions request +//! ├─→ Receives PooledTransactions response +//! └─→ Queues fetched transactions for import +//! ↓ +//! pool.add_external_transactions() [Origin: External] +//! ↓ +//! Transaction Validation & Pool Addition +//! ``` +//! +//! ### 2. RPC Path (Local submission) +//! +//! ```text +//! eth_sendRawTransaction RPC call +//! ├─→ Decodes raw bytes +//! └─→ Recovers sender +//! ↓ +//! pool.add_transaction() [Origin: Local] +//! ↓ +//! Transaction Validation & Pool Addition +//! ``` +//! +//! ### Transaction Origins +//! +//! - **Local**: Transactions submitted via RPC (trusted, may have different fee requirements) +//! - **External**: Transactions from network peers (untrusted, subject to stricter validation) +//! - **Private**: Local transactions that should not be propagated to the network +//! +//! ## Validation Process +//! +//! ### Stateless Checks +//! +//! Ethereum transactions undergo several stateless checks: +//! +//! - **Transaction Type**: Fork-dependent support (Legacy always, EIP-2930/1559/4844/7702 need +//! activation) +//! - **Size**: Input data ≤ 128KB (default) +//! - **Gas**: Limit ≤ block gas limit +//! - **Fees**: Priority fee ≤ max fee; local tx fee cap; external minimum priority fee +//! - **Chain ID**: Must match current chain +//! - **Intrinsic Gas**: Sufficient for data and access lists +//! - **Blobs** (EIP-4844): Valid count, KZG proofs +//! +//! ### Stateful Checks +//! +//! 1. **Sender**: No bytecode (unless EIP-7702 delegated in Prague) +//! 2. **Nonce**: ≥ account nonce +//! 3. **Balance**: Covers value + (`gas_limit` × `max_fee_per_gas`) +//! +//! ### Common Errors +//! +//! - [`NonceNotConsistent`](reth_primitives_traits::transaction::error::InvalidTransactionError::NonceNotConsistent): Nonce too low +//! - [`InsufficientFunds`](reth_primitives_traits::transaction::error::InvalidTransactionError::InsufficientFunds): Insufficient balance +//! - [`ExceedsGasLimit`](crate::error::InvalidPoolTransactionError::ExceedsGasLimit): Gas limit too +//! high +//! - [`SignerAccountHasBytecode`](reth_primitives_traits::transaction::error::InvalidTransactionError::SignerAccountHasBytecode): EOA has code +//! - [`Underpriced`](crate::error::InvalidPoolTransactionError::Underpriced): Fee too low +//! - [`ReplacementUnderpriced`](crate::error::PoolErrorKind::ReplacementUnderpriced): Replacement +//! transaction fee too low +//! - Blob errors: +//! - [`MissingEip4844BlobSidecar`](crate::error::Eip4844PoolTransactionError::MissingEip4844BlobSidecar): Missing sidecar +//! - [`InvalidEip4844Blob`](crate::error::Eip4844PoolTransactionError::InvalidEip4844Blob): +//! Invalid blob proofs +//! - [`NoEip4844Blobs`](crate::error::Eip4844PoolTransactionError::NoEip4844Blobs): EIP-4844 +//! transaction without blobs +//! - [`TooManyEip4844Blobs`](crate::error::Eip4844PoolTransactionError::TooManyEip4844Blobs): Too +//! many blobs +//! +//! ## Subpool Design +//! +//! The pool maintains four distinct subpools, each serving a specific purpose +//! +//! ### Subpools +//! +//! 1. **Pending**: Ready for inclusion (no gaps, sufficient balance/fees) +//! 2. **Queued**: Future transactions (nonce gaps or insufficient balance) +//! 3. **`BaseFee`**: Valid but below current base fee +//! 4. **Blob**: EIP-4844 transactions not pending due to insufficient base fee or blob fee +//! +//! ### State Transitions +//! +//! Transactions move between subpools based on state changes: +//! +//! ```text +//! Queued ─────────→ BaseFee/Blob ────────→ Pending +//! ↑ ↑ │ +//! │ │ │ +//! └────────────────────┴─────────────────────┘ +//! (demotions due to state changes) +//! ``` +//! +//! **Promotions**: Nonce gaps filled, balance/fee improvements +//! **Demotions**: Nonce gaps created, balance/fee degradation +//! +//! ## Pool Maintenance +//! +//! 1. **Block Updates**: Removes mined txs, updates accounts/fees, triggers movements +//! 2. **Size Enforcement**: Discards worst transactions when limits exceeded +//! 3. **Propagation**: External (always), Local (configurable), Private (never) +//! //! ## Assumptions //! //! ### Transaction type @@ -41,11 +162,7 @@ //! //! ### State Changes //! -//! Once a new block is mined, the pool needs to be updated with a changeset in order to: -//! -//! - remove mined transactions -//! - update using account changes: balance changes -//! - base fee updates +//! New blocks trigger pool updates via changesets (see Pool Maintenance). //! //! ## Implementation details //! From bb4bf298ec2eb7e804e6d233012c60b3dc760f91 Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Sat, 14 Jun 2025 17:29:01 +0200 Subject: [PATCH 035/274] feat(gas_oracle): implement median-based priority fee suggestion for Optimism (#16794) --- crates/optimism/rpc/src/eth/mod.rs | 3 +- crates/rpc/rpc-eth-types/src/gas_oracle.rs | 74 +++++++++++++++++++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index df709baedc9..7d37873a4d4 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -244,9 +244,8 @@ where } async fn suggested_priority_fee(&self) -> Result { - let base_tip = self.inner.eth_api.gas_oracle().suggest_tip_cap().await?; let min_tip = U256::from(self.inner.min_suggested_priority_fee); - Ok(base_tip.max(min_tip)) + self.inner.eth_api.gas_oracle().op_suggest_tip_cap(min_tip).await.map_err(Into::into) } } diff --git a/crates/rpc/rpc-eth-types/src/gas_oracle.rs b/crates/rpc/rpc-eth-types/src/gas_oracle.rs index 27b23b54e40..795363f3dfd 100644 --- a/crates/rpc/rpc-eth-types/src/gas_oracle.rs +++ b/crates/rpc/rpc-eth-types/src/gas_oracle.rs @@ -2,7 +2,7 @@ //! previous blocks. use super::{EthApiError, EthResult, EthStateCache, RpcInvalidTransactionError}; -use alloy_consensus::{constants::GWEI_TO_WEI, BlockHeader, Transaction}; +use alloy_consensus::{constants::GWEI_TO_WEI, BlockHeader, Transaction, TxReceipt}; use alloy_eips::BlockNumberOrTag; use alloy_primitives::{B256, U256}; use alloy_rpc_types_eth::BlockId; @@ -273,6 +273,78 @@ where Ok(Some((parent_hash, prices))) } + /// Suggests a max priority fee value using a simplified and more predictable algorithm + /// appropriate for chains like Optimism with a single known block builder. + /// + /// It returns either: + /// - The minimum suggested priority fee when blocks have capacity + /// - 10% above the median effective priority fee from the last block when at capacity + /// + /// A block is considered at capacity if its total gas used plus the maximum single transaction + /// gas would exceed the block's gas limit. + pub async fn op_suggest_tip_cap(&self, min_suggested_priority_fee: U256) -> EthResult { + let header = self + .provider + .sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)? + .ok_or(EthApiError::HeaderNotFound(BlockId::latest()))?; + + let mut inner = self.inner.lock().await; + + // if we have stored a last price, then we check whether or not it was for the same head + if inner.last_price.block_hash == header.hash() { + return Ok(inner.last_price.price); + } + + let mut suggestion = min_suggested_priority_fee; + + // find the maximum gas used by any of the transactions in the block to use as the + // capacity margin for the block, if no receipts are found return the + // suggested_min_priority_fee + let Some(max_tx_gas_used) = self + .cache + .get_receipts(header.hash()) + .await? + .ok_or(EthApiError::ReceiptsNotFound(BlockId::latest()))? + // get the gas used by each transaction in the block, by subtracting the + // cumulative gas used of the previous transaction from the cumulative gas used of the + // current transaction. This is because there is no gas_used() method on the Receipt + // trait. + .windows(2) + .map(|window| { + let prev = window[0].cumulative_gas_used(); + let curr = window[1].cumulative_gas_used(); + curr - prev + }) + .max() + else { + return Ok(suggestion); + }; + + // if the block is at capacity, the suggestion must be increased + if header.gas_used() + max_tx_gas_used > header.gas_limit() { + let Some(median_tip) = self.get_block_median_tip(header.hash()).await? else { + return Ok(suggestion); + }; + + let new_suggestion = median_tip + median_tip / U256::from(10); + + if new_suggestion > suggestion { + suggestion = new_suggestion; + } + } + + // constrain to the max price + if let Some(max_price) = self.oracle_config.max_price { + if suggestion > max_price { + suggestion = max_price; + } + } + + inner.last_price = GasPriceOracleResult { block_hash: header.hash(), price: suggestion }; + + Ok(suggestion) + } + /// Get the median tip value for the given block. This is useful for determining /// tips when a block is at capacity. /// From 9dcea7c3fa2c1985db40388ea870fe7a3de92cb3 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra <160333583+Soubhik-10@users.noreply.github.com> Date: Sun, 15 Jun 2025 13:51:29 +0530 Subject: [PATCH 036/274] chore: removed legacy.rs (#16817) --- crates/storage/storage-api/src/legacy.rs | 84 ------------------------ crates/storage/storage-api/src/lib.rs | 3 - 2 files changed, 87 deletions(-) delete mode 100644 crates/storage/storage-api/src/legacy.rs diff --git a/crates/storage/storage-api/src/legacy.rs b/crates/storage/storage-api/src/legacy.rs deleted file mode 100644 index bb6a21e4e15..00000000000 --- a/crates/storage/storage-api/src/legacy.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Traits used by the legacy execution engine. -//! -//! This module is scheduled for removal in the future. - -use alloc::boxed::Box; -use alloy_eips::BlockNumHash; -use alloy_primitives::{BlockHash, BlockNumber}; -use auto_impl::auto_impl; -use reth_execution_types::ExecutionOutcome; -use reth_storage_errors::provider::{ProviderError, ProviderResult}; - -/// Blockchain trait provider that gives access to the blockchain state that is not yet committed -/// (pending). -pub trait BlockchainTreePendingStateProvider: Send + Sync { - /// Returns a state provider that includes all state changes of the given (pending) block hash. - /// - /// In other words, the state provider will return the state after all transactions of the given - /// hash have been executed. - fn pending_state_provider( - &self, - block_hash: BlockHash, - ) -> ProviderResult> { - self.find_pending_state_provider(block_hash) - .ok_or(ProviderError::StateForHashNotFound(block_hash)) - } - - /// Returns state provider if a matching block exists. - fn find_pending_state_provider( - &self, - block_hash: BlockHash, - ) -> Option>; -} - -/// Provides data required for post-block execution. -/// -/// This trait offers methods to access essential post-execution data, including the state changes -/// in accounts and storage, as well as block hashes for both the pending and canonical chains. -/// -/// The trait includes: -/// * [`ExecutionOutcome`] - Captures all account and storage changes in the pending chain. -/// * Block hashes - Provides access to the block hashes of both the pending chain and canonical -/// blocks. -#[auto_impl(&, Box)] -pub trait ExecutionDataProvider: Send + Sync { - /// Return the execution outcome. - fn execution_outcome(&self) -> &ExecutionOutcome; - /// Return block hash by block number of pending or canonical chain. - fn block_hash(&self, block_number: BlockNumber) -> Option; -} - -impl ExecutionDataProvider for ExecutionOutcome { - fn execution_outcome(&self) -> &ExecutionOutcome { - self - } - - /// Always returns [None] because we don't have any information about the block header. - fn block_hash(&self, _block_number: BlockNumber) -> Option { - None - } -} - -/// Fork data needed for execution on it. -/// -/// It contains a canonical fork, the block on what pending chain was forked from. -#[auto_impl(&, Box)] -pub trait BlockExecutionForkProvider { - /// Return canonical fork, the block on what post state was forked from. - /// - /// Needed to create state provider. - fn canonical_fork(&self) -> BlockNumHash; -} - -/// Provides comprehensive post-execution state data required for further execution. -/// -/// This trait is used to create a state provider over the pending state and is a combination of -/// [`ExecutionDataProvider`] and [`BlockExecutionForkProvider`]. -/// -/// The pending state includes: -/// * `ExecutionOutcome`: Contains all changes to accounts and storage within the pending chain. -/// * Block hashes: Represents hashes of both the pending chain and canonical blocks. -/// * Canonical fork: Denotes the block from which the pending chain forked. -pub trait FullExecutionDataProvider: ExecutionDataProvider + BlockExecutionForkProvider {} - -impl FullExecutionDataProvider for T where T: ExecutionDataProvider + BlockExecutionForkProvider {} diff --git a/crates/storage/storage-api/src/lib.rs b/crates/storage/storage-api/src/lib.rs index a82f6092494..7b326c6c82e 100644 --- a/crates/storage/storage-api/src/lib.rs +++ b/crates/storage/storage-api/src/lib.rs @@ -79,9 +79,6 @@ mod stats; #[cfg(feature = "db-api")] pub use stats::*; -mod legacy; -pub use legacy::*; - mod primitives; pub use primitives::*; From 746e80c819b4e1d1d48dcaf4c422e30ac2ad99b0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 15 Jun 2025 10:39:12 +0200 Subject: [PATCH 037/274] feat(op): export OpEthApiBuilder from reth-optimism-rpc (#16815) --- crates/optimism/rpc/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs index 6c782bb086e..e5e142f815d 100644 --- a/crates/optimism/rpc/src/lib.rs +++ b/crates/optimism/rpc/src/lib.rs @@ -20,5 +20,5 @@ pub mod witness; pub use engine::OpEngineApiClient; pub use engine::{OpEngineApi, OpEngineApiServer, OP_ENGINE_CAPABILITIES}; pub use error::{OpEthApiError, OpInvalidTransactionError, SequencerClientError}; -pub use eth::{OpEthApi, OpReceiptBuilder}; +pub use eth::{OpEthApi, OpEthApiBuilder, OpReceiptBuilder}; pub use sequencer::SequencerClient; From e0acdb102d0c02a95e5e749b782ce2d528c00af9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 09:08:48 +0000 Subject: [PATCH 038/274] chore(deps): weekly `cargo update` (#16719) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> Co-authored-by: Matthias Seitz --- Cargo.lock | 328 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 183 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 377f2d19076..f19247b55ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -97,9 +97,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6967ca1ed656766e471bc323da42fb0db320ca5e1418b408650e98e4757b3d2" +checksum = "19a9cc9d81ace3da457883b0bdf76776e55f1b84219a9e9d55c27ad308548d3f" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -508,7 +508,7 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -754,7 +754,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -770,7 +770,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "syn-solidity", "tiny-keccak", ] @@ -787,7 +787,7 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "syn-solidity", ] @@ -997,7 +997,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1139,7 +1139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1177,7 +1177,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1266,7 +1266,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1339,9 +1339,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.23" +version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37fc50485c4f3f736a4fb14199f6d5f5ba008d7f28fe710306c92780f004c07" +checksum = "d615619615a650c571269c00dca41db04b9210037fa76ed8239f70404ab56985" dependencies = [ "brotli", "flate2", @@ -1386,7 +1386,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1397,7 +1397,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1435,7 +1435,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1561,7 +1561,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -1773,7 +1773,7 @@ checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -1886,9 +1886,9 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" dependencies = [ "bytemuck_derive", ] @@ -1901,7 +1901,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2029,9 +2029,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -2104,9 +2104,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -2114,9 +2114,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.39" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -2126,21 +2126,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cmake" @@ -2608,7 +2608,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2632,7 +2632,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2643,7 +2643,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2696,7 +2696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2755,7 +2755,7 @@ checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2766,7 +2766,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2787,7 +2787,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2797,7 +2797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -2818,7 +2818,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "unicode-xid", ] @@ -2877,7 +2877,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.0", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -2932,7 +2932,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3002,7 +3002,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3104,7 +3104,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3124,7 +3124,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3200,7 +3200,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3829,7 +3829,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -3889,7 +3889,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows 0.61.1", + "windows 0.61.3", ] [[package]] @@ -3924,7 +3924,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -4129,9 +4129,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -4600,7 +4600,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4657,7 +4657,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -4760,7 +4760,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5020,7 +5020,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5165,9 +5165,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.173" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" [[package]] name = "libgit2-sys" @@ -5188,7 +5188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.0", + "windows-targets 0.53.2", ] [[package]] @@ -5411,9 +5411,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +checksum = "2c592ad9fbc1b7838633b3ae55ce69b17d01150c72fcef229fbb819d39ee51ee" [[package]] name = "mach2" @@ -5432,7 +5432,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5446,9 +5446,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -5487,7 +5487,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -5597,9 +5597,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "serde", @@ -5613,7 +5613,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -5866,7 +5866,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6209,7 +6209,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6325,7 +6325,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6354,7 +6354,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6482,12 +6482,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6538,7 +6538,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6613,7 +6613,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6636,7 +6636,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -6652,15 +6652,15 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -6935,9 +6935,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags 2.9.1", ] @@ -6964,6 +6964,26 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "regex" version = "1.11.1" @@ -7026,9 +7046,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.19" +version = "0.12.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f8e5513d63f2e5b386eb5106dc67eaf3f84e95258e210489136b8b92ad6119" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" dependencies = [ "base64 0.22.1", "bytes", @@ -7041,11 +7061,8 @@ dependencies = [ "hyper", "hyper-rustls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", @@ -7388,7 +7405,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -10842,7 +10859,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.101", + "syn 2.0.103", "unicode-ident", ] @@ -10882,9 +10899,9 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -11074,6 +11091,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schnellru" version = "0.2.4" @@ -11221,7 +11250,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11271,15 +11300,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -11289,14 +11319,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11620,7 +11650,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11633,7 +11663,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11655,9 +11685,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" dependencies = [ "proc-macro2", "quote", @@ -11673,7 +11703,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11693,7 +11723,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11775,7 +11805,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11786,7 +11816,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "test-case-core", ] @@ -11826,7 +11856,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11874,7 +11904,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -11885,17 +11915,16 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -12052,7 +12081,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12265,7 +12294,7 @@ checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12426,7 +12455,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12707,7 +12736,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -12752,9 +12781,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" @@ -12787,7 +12816,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-shared", ] @@ -12822,7 +12851,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12978,9 +13007,9 @@ dependencies = [ [[package]] name = "windows" -version = "0.61.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", "windows-core 0.61.2", @@ -13055,7 +13084,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13066,7 +13095,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13077,7 +13106,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13088,7 +13117,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13099,7 +13128,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13110,14 +13139,14 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -13211,6 +13240,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -13259,9 +13297,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.0" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", @@ -13464,9 +13502,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] @@ -13510,9 +13548,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "ws_stream_wasm" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +checksum = "6c173014acad22e83f16403ee360115b38846fe754e735c5d9d3803fe70c6abc" dependencies = [ "async_io_stream", "futures", @@ -13521,7 +13559,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper 0.6.0", - "thiserror 1.0.69", + "thiserror 2.0.12", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -13584,7 +13622,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -13596,7 +13634,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -13617,7 +13655,7 @@ checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13637,7 +13675,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", "synstructure", ] @@ -13658,7 +13696,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13702,7 +13740,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] @@ -13713,7 +13751,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.103", ] [[package]] From fb477d8c28b41df73da6c449a0bbc3d072aca73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Sun, 15 Jun 2025 11:34:18 +0200 Subject: [PATCH 039/274] feat(examples): Add `extension` into `engine_getPayload` RPC method response in `custom_node` example (#16772) --- examples/custom-node/src/engine_api.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/custom-node/src/engine_api.rs b/examples/custom-node/src/engine_api.rs index 0484be19d45..bc92ffb8a99 100644 --- a/examples/custom-node/src/engine_api.rs +++ b/examples/custom-node/src/engine_api.rs @@ -27,15 +27,20 @@ pub struct CustomExecutionPayloadInput {} #[derive(Clone, serde::Serialize)] pub struct CustomExecutionPayloadEnvelope { execution_payload: ExecutionPayloadV3, + extension: u64, } impl From for CustomExecutionPayloadEnvelope { fn from(value: CustomBuiltPayload) -> Self { let sealed_block = value.0.into_sealed_block(); let hash = sealed_block.hash(); + let extension = sealed_block.header().extension; let block = sealed_block.into_block(); - Self { execution_payload: ExecutionPayloadV3::from_block_unchecked(hash, &block.clone()) } + Self { + execution_payload: ExecutionPayloadV3::from_block_unchecked(hash, &block.clone()), + extension, + } } } From 0b2336ddb626abd94f5caeee305f89f7d972fa5c Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:54:26 +0200 Subject: [PATCH 040/274] feat(stateless): simplify `Database` implementation for `WitnessDatabase` (#16820) --- crates/stateless/src/witness_db.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/stateless/src/witness_db.rs b/crates/stateless/src/witness_db.rs index 35585948b46..531aa2a27b1 100644 --- a/crates/stateless/src/witness_db.rs +++ b/crates/stateless/src/witness_db.rs @@ -67,16 +67,14 @@ impl Database for WitnessDatabase<'_> { /// /// Returns `Ok(None)` if the account is not found in the trie. fn basic(&mut self, address: Address) -> Result, Self::Error> { - if let Some(account) = self.trie.account(address)? { - return Ok(Some(AccountInfo { + self.trie.account(address).map(|opt| { + opt.map(|account| AccountInfo { balance: account.balance, nonce: account.nonce, code_hash: account.code_hash, code: None, - })); - }; - - Ok(None) + }) + }) } /// Get storage value of an account at a specific slot. @@ -90,11 +88,9 @@ impl Database for WitnessDatabase<'_> { /// /// Returns an error if the bytecode for the given hash is not found in the map. fn code_by_hash(&mut self, code_hash: B256) -> Result { - let bytecode = self.bytecode.get(&code_hash).ok_or_else(|| { + self.bytecode.get(&code_hash).cloned().ok_or_else(|| { ProviderError::TrieWitnessError(format!("bytecode for {code_hash} not found")) - })?; - - Ok(bytecode.clone()) + }) } /// Get block hash by block number from the provided ancestor hashes map. From e2e54d813ef970827750ee55aeab097784f9e946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 16 Jun 2025 11:10:50 +0200 Subject: [PATCH 041/274] fix(era): Commit all writers and save stages checkpoint per file in `import` (#16810) --- Cargo.lock | 1 + crates/cli/commands/src/import_era.rs | 5 +- crates/era-utils/Cargo.toml | 1 + crates/era-utils/src/history.rs | 99 ++++++++++++++++++++------ crates/era-utils/src/lib.rs | 4 +- crates/era-utils/tests/it/history.rs | 3 +- crates/stages/stages/src/stages/era.rs | 27 ++----- 7 files changed, 91 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f19247b55ba..d9f96371594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8008,6 +8008,7 @@ dependencies = [ "reth-fs-util", "reth-primitives-traits", "reth-provider", + "reth-stages-types", "reth-storage-api", "tempfile", "tokio", diff --git a/crates/cli/commands/src/import_era.rs b/crates/cli/commands/src/import_era.rs index 4b22799b7e4..7d25ce6a83a 100644 --- a/crates/cli/commands/src/import_era.rs +++ b/crates/cli/commands/src/import_era.rs @@ -71,12 +71,11 @@ impl> ImportEraC let Environment { provider_factory, config, .. } = self.env.init::(AccessRights::RW)?; let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir); - let provider_factory = &provider_factory.provider_rw()?.0; if let Some(path) = self.import.path { let stream = read_dir(path, 0)?; - era::import(stream, provider_factory, &mut hash_collector)?; + era::import(stream, &provider_factory, &mut hash_collector)?; } else { let url = match self.import.url { Some(url) => url, @@ -90,7 +89,7 @@ impl> ImportEraC let client = EraClient::new(Client::new(), url, folder); let stream = EraStream::new(client, EraStreamConfig::default()); - era::import(stream, provider_factory, &mut hash_collector)?; + era::import(stream, &provider_factory, &mut hash_collector)?; } Ok(()) diff --git a/crates/era-utils/Cargo.toml b/crates/era-utils/Cargo.toml index 46764560a67..c24843c7a0d 100644 --- a/crates/era-utils/Cargo.toml +++ b/crates/era-utils/Cargo.toml @@ -20,6 +20,7 @@ reth-era-downloader.workspace = true reth-etl.workspace = true reth-fs-util.workspace = true reth-provider.workspace = true +reth-stages-types.workspace = true reth-storage-api.workspace = true reth-primitives-traits.workspace = true diff --git a/crates/era-utils/src/history.rs b/crates/era-utils/src/history.rs index 029b310b820..75eaa4591cf 100644 --- a/crates/era-utils/src/history.rs +++ b/crates/era-utils/src/history.rs @@ -17,10 +17,16 @@ use reth_etl::Collector; use reth_fs_util as fs; use reth_primitives_traits::{Block, FullBlockBody, FullBlockHeader, NodePrimitives}; use reth_provider::{ - providers::StaticFileProviderRWRefMut, BlockWriter, ProviderError, StaticFileProviderFactory, - StaticFileSegment, StaticFileWriter, + providers::StaticFileProviderRWRefMut, writer::UnifiedStorageWriter, BlockWriter, + ProviderError, StaticFileProviderFactory, StaticFileSegment, StaticFileWriter, +}; +use reth_stages_types::{ + CheckpointBlockRange, EntitiesCheckpoint, HeadersCheckpoint, StageCheckpoint, StageId, +}; +use reth_storage_api::{ + errors::ProviderResult, DBProvider, DatabaseProviderFactory, HeaderProvider, + NodePrimitivesProvider, StageCheckpointWriter, StorageLocation, }; -use reth_storage_api::{DBProvider, HeaderProvider, NodePrimitivesProvider, StorageLocation}; use std::{ collections::Bound, error::Error, @@ -35,22 +41,26 @@ use tracing::info; /// Imports blocks from `downloader` using `provider`. /// /// Returns current block height. -pub fn import( +pub fn import( mut downloader: Downloader, - provider: &P, + provider_factory: &PF, hash_collector: &mut Collector, ) -> eyre::Result where B: Block

, BH: FullBlockHeader + Value, BB: FullBlockBody< - Transaction = <

::Primitives as NodePrimitives>::SignedTx, + Transaction = <<::ProviderRW as NodePrimitivesProvider>::Primitives as NodePrimitives>::SignedTx, OmmerHeader = BH, >, Downloader: Stream> + Send + 'static + Unpin, Era: EraMeta + Send + 'static, - P: DBProvider + StaticFileProviderFactory + BlockWriter, -

::Primitives: NodePrimitives, + PF: DatabaseProviderFactory< + ProviderRW: BlockWriter + + DBProvider + + StaticFileProviderFactory> + + StageCheckpointWriter, + > + StaticFileProviderFactory::ProviderRW as NodePrimitivesProvider>::Primitives>, { let (tx, rx) = mpsc::channel(); @@ -62,31 +72,74 @@ where tx.send(None) }); - let static_file_provider = provider.static_file_provider(); + let static_file_provider = provider_factory.static_file_provider(); // Consistency check of expected headers in static files vs DB is done on provider::sync_gap // when poll_execute_ready is polled. - let mut last_header_number = static_file_provider + let mut height = static_file_provider .get_highest_static_file_block(StaticFileSegment::Headers) .unwrap_or_default(); // Find the latest total difficulty let mut td = static_file_provider - .header_td_by_number(last_header_number)? - .ok_or(ProviderError::TotalDifficultyNotFound(last_header_number))?; - - // Although headers were downloaded in reverse order, the collector iterates it in ascending - // order - let mut writer = static_file_provider.latest_writer(StaticFileSegment::Headers)?; + .header_td_by_number(height)? + .ok_or(ProviderError::TotalDifficultyNotFound(height))?; while let Some(meta) = rx.recv()? { - last_header_number = - process(&meta?, &mut writer, provider, hash_collector, &mut td, last_header_number..)?; + let from = height; + let provider = provider_factory.database_provider_rw()?; + + height = process( + &meta?, + &mut static_file_provider.latest_writer(StaticFileSegment::Headers)?, + &provider, + hash_collector, + &mut td, + height.., + )?; + + save_stage_checkpoints(&provider, from, height, height, height)?; + + UnifiedStorageWriter::commit(provider)?; } - build_index(provider, hash_collector)?; + let provider = provider_factory.database_provider_rw()?; - Ok(last_header_number) + build_index(&provider, hash_collector)?; + + UnifiedStorageWriter::commit(provider)?; + + Ok(height) +} + +/// Saves progress of ERA import into stages sync. +/// +/// Since the ERA import does the same work as `HeaderStage` and `BodyStage`, it needs to inform +/// these stages that this work has already been done. Otherwise, there might be some conflict with +/// database integrity. +pub fn save_stage_checkpoints

( + provider: &P, + from: BlockNumber, + to: BlockNumber, + processed: u64, + total: u64, +) -> ProviderResult<()> +where + P: StageCheckpointWriter, +{ + provider.save_stage_checkpoint( + StageId::Headers, + StageCheckpoint::new(to).with_headers_stage_checkpoint(HeadersCheckpoint { + block_range: CheckpointBlockRange { from, to }, + progress: EntitiesCheckpoint { processed, total }, + }), + )?; + provider.save_stage_checkpoint( + StageId::Bodies, + StageCheckpoint::new(to) + .with_entities_stage_checkpoint(EntitiesCheckpoint { processed, total }), + )?; + Ok(()) } /// Extracts block headers and bodies from `meta` and appends them using `writer` and `provider`. @@ -116,7 +169,7 @@ where OmmerHeader = BH, >, Era: EraMeta + ?Sized, - P: DBProvider + StaticFileProviderFactory + BlockWriter, + P: DBProvider + NodePrimitivesProvider + BlockWriter,

::Primitives: NodePrimitives, { let reader = open(meta)?; @@ -226,7 +279,7 @@ where Transaction = <

::Primitives as NodePrimitives>::SignedTx, OmmerHeader = BH, >, - P: DBProvider + StaticFileProviderFactory + BlockWriter, + P: DBProvider + NodePrimitivesProvider + BlockWriter,

::Primitives: NodePrimitives, { let mut last_header_number = match block_numbers.start_bound() { @@ -287,7 +340,7 @@ where Transaction = <

::Primitives as NodePrimitives>::SignedTx, OmmerHeader = BH, >, - P: DBProvider + StaticFileProviderFactory + BlockWriter, + P: DBProvider + NodePrimitivesProvider + BlockWriter,

::Primitives: NodePrimitives, { let total_headers = hash_collector.len(); diff --git a/crates/era-utils/src/lib.rs b/crates/era-utils/src/lib.rs index ce3e70246e7..a2c6cdaedb7 100644 --- a/crates/era-utils/src/lib.rs +++ b/crates/era-utils/src/lib.rs @@ -5,4 +5,6 @@ mod history; /// Imports history from ERA files. -pub use history::{build_index, decode, import, open, process, process_iter, ProcessIter}; +pub use history::{ + build_index, decode, import, open, process, process_iter, save_stage_checkpoints, ProcessIter, +}; diff --git a/crates/era-utils/tests/it/history.rs b/crates/era-utils/tests/it/history.rs index e392c5d8ade..65153b8d599 100644 --- a/crates/era-utils/tests/it/history.rs +++ b/crates/era-utils/tests/it/history.rs @@ -31,8 +31,7 @@ async fn test_history_imports_from_fresh_state_successfully() { let mut hash_collector = Collector::new(4096, folder); let expected_block_number = 8191; - let actual_block_number = - reth_era_utils::import(stream, &pf.provider_rw().unwrap().0, &mut hash_collector).unwrap(); + let actual_block_number = reth_era_utils::import(stream, &pf, &mut hash_collector).unwrap(); assert_eq!(actual_block_number, expected_block_number); } diff --git a/crates/stages/stages/src/stages/era.rs b/crates/stages/stages/src/stages/era.rs index ea0ca7a5cd0..dde3131994e 100644 --- a/crates/stages/stages/src/stages/era.rs +++ b/crates/stages/stages/src/stages/era.rs @@ -13,10 +13,7 @@ use reth_provider::{ BlockReader, BlockWriter, DBProvider, HeaderProvider, StageCheckpointWriter, StaticFileProviderFactory, StaticFileWriter, }; -use reth_stages_api::{ - CheckpointBlockRange, EntitiesCheckpoint, ExecInput, ExecOutput, HeadersCheckpoint, Stage, - StageError, UnwindInput, UnwindOutput, -}; +use reth_stages_api::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_static_file_types::StaticFileSegment; use reth_storage_errors::ProviderError; use std::{ @@ -205,22 +202,12 @@ where self.hash_collector.clear(); } - provider.save_stage_checkpoint( - StageId::Headers, - StageCheckpoint::new(height).with_headers_stage_checkpoint(HeadersCheckpoint { - block_range: CheckpointBlockRange { - from: input.checkpoint().block_number, - to: height, - }, - progress: EntitiesCheckpoint { processed: height, total: input.target() }, - }), - )?; - provider.save_stage_checkpoint( - StageId::Bodies, - StageCheckpoint::new(height).with_entities_stage_checkpoint(EntitiesCheckpoint { - processed: height, - total: input.target(), - }), + era::save_stage_checkpoints( + &provider, + input.checkpoint().block_number, + height, + height, + input.target(), )?; height From b8e4cd3ace1677a8db9032c61d49c8c973475673 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 16 Jun 2025 11:15:16 +0200 Subject: [PATCH 042/274] fix: change some rpc response codes to eth invalid input (#16745) --- crates/rpc/rpc-eth-types/src/error/mod.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 24771717617..ac56ea9c608 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -528,7 +528,11 @@ impl RpcInvalidTransactionError { Self::InvalidChainId | Self::GasTooLow | Self::GasTooHigh | - Self::GasRequiredExceedsAllowance { .. } => EthRpcErrorCode::InvalidInput.code(), + Self::GasRequiredExceedsAllowance { .. } | + Self::NonceTooLow { .. } | + Self::NonceTooHigh { .. } | + Self::FeeCapTooLow | + Self::FeeCapVeryHigh => EthRpcErrorCode::InvalidInput.code(), Self::Revert(_) => EthRpcErrorCode::ExecutionError.code(), _ => EthRpcErrorCode::TransactionRejected.code(), } @@ -782,7 +786,22 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { RpcPoolError::TxPoolOverflow => { rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), error.to_string()) } - error => internal_rpc_err(error.to_string()), + RpcPoolError::AlreadyKnown | + RpcPoolError::InvalidSender | + RpcPoolError::Underpriced | + RpcPoolError::ReplaceUnderpriced | + RpcPoolError::ExceedsGasLimit | + RpcPoolError::ExceedsFeeCap { .. } | + RpcPoolError::NegativeValue | + RpcPoolError::OversizedData | + RpcPoolError::ExceedsMaxInitCodeSize | + RpcPoolError::PoolTransactionError(_) | + RpcPoolError::Eip4844(_) | + RpcPoolError::Eip7702(_) | + RpcPoolError::AddressAlreadyReserved => { + rpc_error_with_code(EthRpcErrorCode::InvalidInput.code(), error.to_string()) + } + RpcPoolError::Other(other) => internal_rpc_err(other.to_string()), } } } From 11df5a1d303011d16822e98d3ff285c9ad398a52 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 16 Jun 2025 11:16:07 +0200 Subject: [PATCH 043/274] feat: re-export MerklePatriciaTrie from reth-ethereum and reth-op (#16814) Co-authored-by: Claude --- Cargo.lock | 2 ++ crates/ethereum/reth/Cargo.toml | 5 ++++- crates/ethereum/reth/src/lib.rs | 4 ++++ crates/optimism/reth/Cargo.toml | 5 ++++- crates/optimism/reth/src/lib.rs | 4 ++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9f96371594..b28e3092017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8123,6 +8123,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie", + "reth-trie-db", ] [[package]] @@ -9019,6 +9020,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie", + "reth-trie-db", ] [[package]] diff --git a/crates/ethereum/reth/Cargo.toml b/crates/ethereum/reth/Cargo.toml index f6f45922583..45e046b89ef 100644 --- a/crates/ethereum/reth/Cargo.toml +++ b/crates/ethereum/reth/Cargo.toml @@ -33,6 +33,7 @@ reth-rpc-eth-types = { workspace = true, optional = true } reth-rpc-builder = { workspace = true, optional = true } reth-exex = { workspace = true, optional = true } reth-trie = { workspace = true, optional = true } +reth-trie-db = { workspace = true, optional = true } reth-node-builder = { workspace = true, optional = true } reth-tasks = { workspace = true, optional = true } reth-cli-util = { workspace = true, optional = true } @@ -91,6 +92,7 @@ test-utils = [ "reth-transaction-pool?/test-utils", "reth-evm-ethereum?/test-utils", "reth-node-builder?/test-utils", + "reth-trie-db?/test-utils", ] full = [ @@ -122,7 +124,7 @@ node = [ "dep:reth-node-ethereum", "dep:reth-node-builder", "rpc", - "trie", + "trie-db", ] pool = ["dep:reth-transaction-pool"] rpc = [ @@ -140,3 +142,4 @@ network = ["dep:reth-network", "tasks", "dep:reth-network-api", "dep:reth-eth-wi provider = ["storage-api", "tasks", "dep:reth-provider", "dep:reth-db"] storage-api = ["dep:reth-storage-api"] trie = ["dep:reth-trie"] +trie-db = ["trie", "dep:reth-trie-db"] diff --git a/crates/ethereum/reth/src/lib.rs b/crates/ethereum/reth/src/lib.rs index 421cabe9968..b72e504e217 100644 --- a/crates/ethereum/reth/src/lib.rs +++ b/crates/ethereum/reth/src/lib.rs @@ -116,6 +116,10 @@ pub mod node { pub mod trie { #[doc(inline)] pub use reth_trie::*; + + #[cfg(feature = "trie-db")] + #[doc(inline)] + pub use reth_trie_db::*; } /// Re-exported rpc types diff --git a/crates/optimism/reth/Cargo.toml b/crates/optimism/reth/Cargo.toml index f00b52acbe9..f199aed7e78 100644 --- a/crates/optimism/reth/Cargo.toml +++ b/crates/optimism/reth/Cargo.toml @@ -33,6 +33,7 @@ reth-rpc-builder = { workspace = true, optional = true } reth-exex = { workspace = true, optional = true } reth-transaction-pool = { workspace = true, optional = true } reth-trie = { workspace = true, optional = true } +reth-trie-db = { workspace = true, optional = true } reth-node-builder = { workspace = true, optional = true } reth-tasks = { workspace = true, optional = true } reth-cli-util = { workspace = true, optional = true } @@ -84,6 +85,7 @@ test-utils = [ "reth-trie?/test-utils", "reth-transaction-pool?/test-utils", "reth-node-builder?/test-utils", + "reth-trie-db?/test-utils", ] full = ["consensus", "evm", "node", "provider", "rpc", "trie", "pool", "network"] @@ -106,7 +108,7 @@ node = [ "dep:reth-optimism-node", "dep:reth-node-builder", "rpc", - "trie", + "trie-db", ] rpc = [ "tasks", @@ -123,3 +125,4 @@ provider = ["storage-api", "tasks", "dep:reth-provider", "dep:reth-db"] pool = ["dep:reth-transaction-pool"] storage-api = ["dep:reth-storage-api"] trie = ["dep:reth-trie"] +trie-db = ["trie", "dep:reth-trie-db"] diff --git a/crates/optimism/reth/src/lib.rs b/crates/optimism/reth/src/lib.rs index abafb72c66c..5f029ce67da 100644 --- a/crates/optimism/reth/src/lib.rs +++ b/crates/optimism/reth/src/lib.rs @@ -124,6 +124,10 @@ pub mod node { pub mod trie { #[doc(inline)] pub use reth_trie::*; + + #[cfg(feature = "trie-db")] + #[doc(inline)] + pub use reth_trie_db::*; } /// Re-exported rpc types From 9d391a8b9298206c70da763ae784c4b542d66483 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Mon, 16 Jun 2025 11:17:52 +0200 Subject: [PATCH 044/274] feat(test): rewrite test_engine_tree_reorg_with_missing_ancestor_expecting_valid using e2e framework (#16761) --- .../src/testsuite/actions/produce_blocks.rs | 11 ++++-- crates/engine/tree/src/tree/e2e_tests.rs | 34 +++++++++++++++++ crates/engine/tree/src/tree/tests.rs | 37 ------------------- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs index 02f7155b66a..dfeb5a8fa84 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs @@ -1,7 +1,7 @@ //! Block production actions for the e2e testing framework. use crate::testsuite::{ - actions::{validate_fcu_response, Action, Sequence}, + actions::{expect_fcu_not_syncing_or_accepted, validate_fcu_response, Action, Sequence}, BlockInfo, Environment, }; use alloy_primitives::{Bytes, B256}; @@ -242,7 +242,9 @@ where debug!("FCU result: {:?}", fcu_result); // validate the FCU status before proceeding - validate_fcu_response(&fcu_result, "GenerateNextPayload")?; + // Note: In the context of GenerateNextPayload, Syncing usually means the engine + // doesn't have the requested head block, which should be an error + expect_fcu_not_syncing_or_accepted(&fcu_result, "GenerateNextPayload")?; let payload_id = if let Some(payload_id) = fcu_result.payload_id { debug!("Received new payload ID: {:?}", payload_id); @@ -269,7 +271,10 @@ where debug!("Fresh FCU result: {:?}", fresh_fcu_result); // validate the fresh FCU status - validate_fcu_response(&fresh_fcu_result, "GenerateNextPayload (fresh)")?; + expect_fcu_not_syncing_or_accepted( + &fresh_fcu_result, + "GenerateNextPayload (fresh)", + )?; if let Some(payload_id) = fresh_fcu_result.payload_id { payload_id diff --git a/crates/engine/tree/src/tree/e2e_tests.rs b/crates/engine/tree/src/tree/e2e_tests.rs index fbadae28698..ec74eecc200 100644 --- a/crates/engine/tree/src/tree/e2e_tests.rs +++ b/crates/engine/tree/src/tree/e2e_tests.rs @@ -151,3 +151,37 @@ async fn test_engine_tree_valid_and_invalid_forks_with_older_canonical_head_e2e( Ok(()) } + +/// Test that verifies engine tree behavior when handling invalid blocks. +/// This test demonstrates that invalid blocks are correctly rejected and that +/// attempts to build on top of them fail appropriately. +#[tokio::test] +async fn test_engine_tree_reorg_with_missing_ancestor_expecting_valid_e2e() -> Result<()> { + reth_tracing::init_test_tracing(); + + let test = TestBuilder::new() + .with_setup(default_engine_tree_setup()) + // build main chain (blocks 1-6) + .with_action(ProduceBlocks::::new(6)) + .with_action(MakeCanonical::new()) + .with_action(CaptureBlock::new("main_chain_tip")) + // create a valid fork first + .with_action(CreateFork::::new_from_tag("main_chain_tip", 5)) + .with_action(CaptureBlock::new("valid_fork_tip")) + // FCU to the valid fork should work + .with_action(ExpectFcuStatus::valid("valid_fork_tip")); + + test.run::().await?; + + // attempting to build invalid chains fails properly + let invalid_test = TestBuilder::new() + .with_setup(default_engine_tree_setup()) + .with_action(ProduceBlocks::::new(3)) + .with_action(MakeCanonical::new()) + // This should fail when trying to build subsequent blocks on the invalid block + .with_action(ProduceInvalidBlocks::::with_invalid_at(2, 0)); + + assert!(invalid_test.run::().await.is_err()); + + Ok(()) +} diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 9fa1d960486..68db6707547 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -381,14 +381,6 @@ impl TestHarness { self.setup_range_insertion_for_chain(chain, None) } - fn setup_range_insertion_for_invalid_chain( - &mut self, - chain: Vec>, - index: usize, - ) { - self.setup_range_insertion_for_chain(chain, Some(index)) - } - fn setup_range_insertion_for_chain( &mut self, chain: Vec>, @@ -1155,32 +1147,3 @@ async fn test_engine_tree_buffered_blocks_are_eventually_connected() { test_harness.check_canon_block_added(non_buffered_block_hash).await; test_harness.check_canon_block_added(buffered_block_hash).await; } - -#[tokio::test] -async fn test_engine_tree_reorg_with_missing_ancestor_expecting_valid() { - reth_tracing::init_test_tracing(); - let chain_spec = MAINNET.clone(); - let mut test_harness = TestHarness::new(chain_spec.clone()); - - let base_chain: Vec<_> = test_harness.block_builder.get_executed_blocks(0..6).collect(); - test_harness = test_harness.with_blocks(base_chain.clone()); - - // create a side chain with an invalid block - let side_chain = - test_harness.block_builder.create_fork(base_chain.last().unwrap().recovered_block(), 15); - let invalid_index = 9; - - test_harness.setup_range_insertion_for_invalid_chain(side_chain.clone(), invalid_index); - - for (index, block) in side_chain.iter().enumerate() { - test_harness.send_new_payload(block.clone()).await; - - if index < side_chain.len() - invalid_index - 1 { - test_harness.send_fcu(block.hash(), ForkchoiceStatus::Valid).await; - } - } - - // Try to do a forkchoice update to a block after the invalid one - let fork_tip_hash = side_chain.last().unwrap().hash(); - test_harness.send_fcu(fork_tip_hash, ForkchoiceStatus::Invalid).await; -} From c3caea204701b8e8068a3c3535192e16875d1444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 16 Jun 2025 11:39:02 +0200 Subject: [PATCH 045/274] refactor(era): Remove `start_from` from `EraClient` and use it instead of last file index (#16801) --- crates/era-downloader/src/client.rs | 34 ++++++++------------------ crates/era-downloader/src/fs.rs | 2 +- crates/era-downloader/src/lib.rs | 2 +- crates/era-downloader/src/stream.rs | 30 ++++++++++++----------- crates/stages/stages/src/stages/era.rs | 2 +- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/crates/era-downloader/src/client.rs b/crates/era-downloader/src/client.rs index 90ac03ef3a0..6c3a1c1b980 100644 --- a/crates/era-downloader/src/client.rs +++ b/crates/era-downloader/src/client.rs @@ -1,5 +1,4 @@ -use crate::BLOCKS_PER_FILE; -use alloy_primitives::{hex, hex::ToHexExt, BlockNumber}; +use alloy_primitives::{hex, hex::ToHexExt}; use bytes::Bytes; use eyre::{eyre, OptionExt}; use futures_util::{stream::StreamExt, Stream, TryStreamExt}; @@ -42,7 +41,6 @@ pub struct EraClient { client: Http, url: Url, folder: Box, - start_from: Option, } impl EraClient { @@ -50,15 +48,7 @@ impl EraClient { /// Constructs [`EraClient`] using `client` to download from `url` into `folder`. pub fn new(client: Http, url: Url, folder: impl Into>) -> Self { - Self { client, url, folder: folder.into(), start_from: None } - } - - /// Overrides the starting ERA file based on `block_number`. - /// - /// The normal behavior is that the index is recovered based on files contained in the `folder`. - pub const fn start_from(mut self, block_number: BlockNumber) -> Self { - self.start_from.replace(block_number / BLOCKS_PER_FILE); - self + Self { client, url, folder: folder.into() } } /// Performs a GET request on `url` and stores the response body into a file located within @@ -125,11 +115,7 @@ impl EraClient { } /// Recovers index of file following the latest downloaded file from a different run. - pub async fn recover_index(&self) -> u64 { - if let Some(block_number) = self.start_from { - return block_number; - } - + pub async fn recover_index(&self) -> Option { let mut max = None; if let Ok(mut dir) = fs::read_dir(&self.folder).await { @@ -137,18 +123,18 @@ impl EraClient { if let Some(name) = entry.file_name().to_str() { if let Some(number) = self.file_name_to_number(name) { if max.is_none() || matches!(max, Some(max) if number > max) { - max.replace(number); + max.replace(number + 1); } } } } } - max.map(|v| v + 1).unwrap_or(0) + max } /// Returns a download URL for the file corresponding to `number`. - pub async fn url(&self, number: u64) -> eyre::Result> { + pub async fn url(&self, number: usize) -> eyre::Result> { Ok(self.number_to_file_name(number).await?.map(|name| self.url.join(&name)).transpose()?) } @@ -229,7 +215,7 @@ impl EraClient { } /// Returns ERA1 file name that is ordered at `number`. - pub async fn number_to_file_name(&self, number: u64) -> eyre::Result> { + pub async fn number_to_file_name(&self, number: usize) -> eyre::Result> { let path = self.folder.to_path_buf().join("index"); let file = File::open(&path).await?; let reader = io::BufReader::new(file); @@ -241,8 +227,8 @@ impl EraClient { Ok(lines.next_line().await?) } - fn file_name_to_number(&self, file_name: &str) -> Option { - file_name.split('-').nth(1).and_then(|v| u64::from_str(v).ok()) + fn file_name_to_number(&self, file_name: &str) -> Option { + file_name.split('-').nth(1).and_then(|v| usize::from_str(v).ok()) } } @@ -262,7 +248,7 @@ mod tests { #[test_case("mainnet-00000-a81ae85f.era1", Some(0))] #[test_case("00000-a81ae85f.era1", None)] #[test_case("", None)] - fn test_file_name_to_number(file_name: &str, expected_number: Option) { + fn test_file_name_to_number(file_name: &str, expected_number: Option) { let client = EraClient::empty(); let actual_number = client.file_name_to_number(file_name); diff --git a/crates/era-downloader/src/fs.rs b/crates/era-downloader/src/fs.rs index 2fe40e86e7d..17a2d46d26a 100644 --- a/crates/era-downloader/src/fs.rs +++ b/crates/era-downloader/src/fs.rs @@ -45,7 +45,7 @@ pub fn read_dir( entries.sort_by(|(left, _), (right, _)| left.cmp(right)); - Ok(stream::iter(entries.into_iter().skip((start_from / BLOCKS_PER_FILE) as usize).map( + Ok(stream::iter(entries.into_iter().skip(start_from as usize / BLOCKS_PER_FILE).map( move |(_, path)| { let expected_checksum = checksums.next().transpose()?.ok_or_eyre("Got less checksums than ERA files")?; diff --git a/crates/era-downloader/src/lib.rs b/crates/era-downloader/src/lib.rs index b8722b02270..88afaa7af4e 100644 --- a/crates/era-downloader/src/lib.rs +++ b/crates/era-downloader/src/lib.rs @@ -42,4 +42,4 @@ pub use client::{EraClient, HttpClient}; pub use fs::read_dir; pub use stream::{EraMeta, EraStream, EraStreamConfig}; -pub(crate) const BLOCKS_PER_FILE: u64 = 8192; +pub(crate) const BLOCKS_PER_FILE: usize = 8192; diff --git a/crates/era-downloader/src/stream.rs b/crates/era-downloader/src/stream.rs index fad02ab6efb..ce66a904aeb 100644 --- a/crates/era-downloader/src/stream.rs +++ b/crates/era-downloader/src/stream.rs @@ -1,4 +1,4 @@ -use crate::{client::HttpClient, EraClient}; +use crate::{client::HttpClient, EraClient, BLOCKS_PER_FILE}; use alloy_primitives::BlockNumber; use futures_util::{stream::FuturesOrdered, FutureExt, Stream, StreamExt}; use reqwest::Url; @@ -24,7 +24,7 @@ use std::{ pub struct EraStreamConfig { max_files: usize, max_concurrent_downloads: usize, - start_from: Option, + start_from: Option, } impl Default for EraStreamConfig { @@ -47,11 +47,8 @@ impl EraStreamConfig { } /// Overrides the starting ERA file index to be the first one that contains `block_number`. - /// - /// The normal behavior is that the ERA file index is recovered from the last file inside the - /// download folder. pub const fn start_from(mut self, block_number: BlockNumber) -> Self { - self.start_from.replace(block_number); + self.start_from.replace(block_number as usize / BLOCKS_PER_FILE); self } } @@ -93,11 +90,12 @@ impl EraStream { client, files_count: Box::pin(async move { usize::MAX }), next_url: Box::pin(async move { Ok(None) }), - recover_index: Box::pin(async move { 0 }), + recover_index: Box::pin(async move { None }), fetch_file_list: Box::pin(async move { Ok(()) }), state: Default::default(), max_files: config.max_files, - index: 0, + index: config.start_from.unwrap_or_default(), + last: None, downloading: 0, }, } @@ -223,11 +221,12 @@ struct StartingStream { client: EraClient, files_count: Pin + Send + Sync + 'static>>, next_url: Pin>> + Send + Sync + 'static>>, - recover_index: Pin + Send + Sync + 'static>>, + recover_index: Pin> + Send + Sync + 'static>>, fetch_file_list: Pin> + Send + Sync + 'static>>, state: State, max_files: usize, - index: u64, + index: usize, + last: Option, downloading: usize, } @@ -274,15 +273,18 @@ impl Stream for Starti } if self.state == State::RecoverIndex { - if let Poll::Ready(index) = self.recover_index.poll_unpin(cx) { - self.index = index; + if let Poll::Ready(last) = self.recover_index.poll_unpin(cx) { + self.last = last; self.count_files(); } } if self.state == State::CountFiles { if let Poll::Ready(downloaded) = self.files_count.poll_unpin(cx) { - let max_missing = self.max_files.saturating_sub(downloaded + self.downloading); + let max_missing = self + .max_files + .saturating_sub(downloaded + self.downloading) + .max(self.last.unwrap_or_default().saturating_sub(self.index)); self.state = State::Missing(max_missing); } } @@ -349,7 +351,7 @@ impl StartingStream { self.state = State::CountFiles; } - fn next_url(&mut self, index: u64, max_missing: usize) { + fn next_url(&mut self, index: usize, max_missing: usize) { let client = self.client.clone(); Pin::new(&mut self.next_url).set(Box::pin(async move { client.url(index).await })); diff --git a/crates/stages/stages/src/stages/era.rs b/crates/stages/stages/src/stages/era.rs index dde3131994e..38b7f0c0db7 100644 --- a/crates/stages/stages/src/stages/era.rs +++ b/crates/stages/stages/src/stages/era.rs @@ -64,7 +64,7 @@ where let client = EraClient::new(Client::new(), url, folder); Self::convert(EraStream::new( - client.start_from(input.next_block()), + client, EraStreamConfig::default().start_from(input.next_block()), )) } From ad86321afb6273ba7fe4f3c2327b2fca5f585cbc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 16 Jun 2025 11:39:57 +0200 Subject: [PATCH 046/274] feat: relax EthereumAddons trait bounds to support generic validators (#16816) --- crates/ethereum/node/Cargo.toml | 2 +- crates/ethereum/node/src/node.rs | 114 +++++++++++++++++++++++------- crates/exex/test-utils/src/lib.rs | 16 ++--- 3 files changed, 95 insertions(+), 37 deletions(-) diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index 74b5867bca2..d3266bbb21b 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -26,8 +26,8 @@ reth-evm.workspace = true reth-evm-ethereum.workspace = true reth-consensus.workspace = true reth-rpc.workspace = true -reth-rpc-builder.workspace = true reth-rpc-api.workspace = true +reth-rpc-builder.workspace = true reth-rpc-server-types.workspace = true reth-node-api.workspace = true reth-chainspec.workspace = true diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index ac16b5f4dee..c0f33e21b38 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -3,8 +3,10 @@ pub use crate::{payload::EthereumPayloadBuilder, EthereumEngineValidator}; use crate::{EthEngineTypes, EthEvmConfig}; use alloy_eips::{eip7840::BlobParams, merge::EPOCH_SLOTS}; +use alloy_rpc_types_engine::ExecutionData; use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks, Hardforks}; use reth_consensus::{ConsensusError, FullConsensus}; +use reth_engine_primitives::EngineTypes; use reth_ethereum_consensus::EthBeaconConsensus; use reth_ethereum_engine_primitives::{ EthBuiltPayload, EthPayloadAttributes, EthPayloadBuilderAttributes, @@ -22,8 +24,8 @@ use reth_node_builder::{ }, node::{FullNodeTypes, NodeTypes}, rpc::{ - EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder, EthApiCtx, RethRpcAddOns, - RpcAddOns, RpcHandle, + BasicEngineApiBuilder, EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, + EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle, }, BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder, PayloadBuilderConfig, PayloadTypes, @@ -152,36 +154,75 @@ where /// Add-ons w.r.t. l1 ethereum. #[derive(Debug)] -pub struct EthereumAddOns +pub struct EthereumAddOns< + N: FullNodeComponents, + EthB: EthApiBuilder, + EV, + EB = BasicEngineApiBuilder, +> { + inner: RpcAddOns, +} + +impl Default for EthereumAddOns where - EthApiFor: FullEthApiServer, + N: FullNodeComponents, + EthereumEthApiBuilder: EthApiBuilder, { - inner: RpcAddOns, + fn default() -> Self { + Self { + inner: RpcAddOns::new( + EthereumEthApiBuilder, + EthereumEngineValidatorBuilder::default(), + BasicEngineApiBuilder::default(), + ), + } + } } -impl Default for EthereumAddOns +impl EthereumAddOns where - EthApiFor: FullEthApiServer, + N: FullNodeComponents, + EthB: EthApiBuilder, { - fn default() -> Self { - Self { inner: Default::default() } + /// Replace the engine API builder. + pub fn with_engine_api(self, engine_api_builder: T) -> EthereumAddOns + where + T: Send, + { + let Self { inner } = self; + EthereumAddOns { inner: inner.with_engine_api(engine_api_builder) } + } + + /// Replace the engine validator builder. + pub fn with_engine_validator( + self, + engine_validator_builder: T, + ) -> EthereumAddOns + where + T: Send, + { + let Self { inner } = self; + EthereumAddOns { inner: inner.with_engine_validator(engine_validator_builder) } } } -impl NodeAddOns for EthereumAddOns +impl NodeAddOns for EthereumAddOns where N: FullNodeComponents< Types: NodeTypes< - ChainSpec = ChainSpec, + ChainSpec: EthChainSpec + EthereumHardforks, Primitives = EthPrimitives, - Payload = EthEngineTypes, + Payload: EngineTypes, >, Evm: ConfigureEvm, >, + EthB: EthApiBuilder, + EV: EngineValidatorBuilder, + EB: EngineApiBuilder, EthApiError: FromEvmError, EvmFactoryFor: EvmFactory, { - type Handle = RpcHandle>; + type Handle = RpcHandle; async fn launch_add_ons( self, @@ -209,41 +250,49 @@ where } } -impl RethRpcAddOns for EthereumAddOns +impl RethRpcAddOns for EthereumAddOns where N: FullNodeComponents< Types: NodeTypes< - ChainSpec = ChainSpec, + ChainSpec: EthChainSpec + EthereumHardforks, Primitives = EthPrimitives, - Payload = EthEngineTypes, + Payload: EngineTypes, >, Evm: ConfigureEvm, >, + EthB: EthApiBuilder, + EV: EngineValidatorBuilder, + EB: EngineApiBuilder, EthApiError: FromEvmError, EvmFactoryFor: EvmFactory, { - type EthApi = EthApiFor; + type EthApi = EthB::EthApi; fn hooks_mut(&mut self) -> &mut reth_node_builder::rpc::RpcHooks { self.inner.hooks_mut() } } -impl EngineValidatorAddOn for EthereumAddOns +impl EngineValidatorAddOn for EthereumAddOns where N: FullNodeComponents< Types: NodeTypes< - ChainSpec = ChainSpec, + ChainSpec: EthChainSpec + EthereumHardforks, Primitives = EthPrimitives, - Payload = EthEngineTypes, + Payload: EngineTypes, >, + Evm: ConfigureEvm, >, - EthApiFor: FullEthApiServer, + EthB: EthApiBuilder, + EV: EngineValidatorBuilder, + EB: EngineApiBuilder, + EthApiError: FromEvmError, + EvmFactoryFor: EvmFactory, { - type Validator = EthereumEngineValidator; + type Validator = EV::Validator; async fn engine_validator(&self, ctx: &AddOnsContext<'_, N>) -> eyre::Result { - EthereumEngineValidatorBuilder::default().build(ctx).await + self.inner.engine_validator(ctx).await } } @@ -262,6 +311,8 @@ where type AddOns = EthereumAddOns< NodeAdapter>::Components>, + EthereumEthApiBuilder, + EthereumEngineValidatorBuilder, >; fn components_builder(&self) -> Self::ComponentsBuilder { @@ -412,14 +463,23 @@ where /// Builder for [`EthereumEngineValidator`]. #[derive(Debug, Default, Clone)] #[non_exhaustive] -pub struct EthereumEngineValidatorBuilder; +pub struct EthereumEngineValidatorBuilder { + _phantom: std::marker::PhantomData, +} -impl EngineValidatorBuilder for EthereumEngineValidatorBuilder +impl EngineValidatorBuilder + for EthereumEngineValidatorBuilder where - Types: NodeTypes, + Types: NodeTypes< + ChainSpec = ChainSpec, + Payload: EngineTypes + + PayloadTypes, + Primitives = EthPrimitives, + >, Node: FullNodeComponents, + ChainSpec: EthChainSpec + EthereumHardforks + Clone + 'static, { - type Validator = EthereumEngineValidator; + type Validator = EthereumEngineValidator; async fn build(self, ctx: &AddOnsContext<'_, Node>) -> eyre::Result { Ok(EthereumEngineValidator::new(ctx.config.chain.clone())) diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index a04a9da767e..00bcdcbbf70 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -41,7 +41,10 @@ use reth_node_builder::{ }; use reth_node_core::node_config::NodeConfig; use reth_node_ethereum::{ - node::{EthereumAddOns, EthereumNetworkBuilder, EthereumPayloadBuilder}, + node::{ + EthereumAddOns, EthereumEngineValidatorBuilder, EthereumEthApiBuilder, + EthereumNetworkBuilder, EthereumPayloadBuilder, + }, EthEngineTypes, }; use reth_payload_builder::noop::NoopPayloadBuilderService; @@ -120,14 +123,7 @@ impl NodeTypes for TestNode { impl Node for TestNode where - N: FullNodeTypes< - Types: NodeTypes< - Payload = EthEngineTypes, - ChainSpec = ChainSpec, - Primitives = EthPrimitives, - Storage = EthStorage, - >, - >, + N: FullNodeTypes, { type ComponentsBuilder = ComponentsBuilder< N, @@ -139,6 +135,8 @@ where >; type AddOns = EthereumAddOns< NodeAdapter>::Components>, + EthereumEthApiBuilder, + EthereumEngineValidatorBuilder, >; fn components_builder(&self) -> Self::ComponentsBuilder { From a1d216040e3b62637aca3c1f78e159e91fda97c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 16 Jun 2025 11:52:00 +0200 Subject: [PATCH 047/274] perf(cli): Start from next block based on imported headers in `import-era` command (#16803) --- crates/cli/commands/src/import_era.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/cli/commands/src/import_era.rs b/crates/cli/commands/src/import_era.rs index 7d25ce6a83a..7920fda3131 100644 --- a/crates/cli/commands/src/import_era.rs +++ b/crates/cli/commands/src/import_era.rs @@ -11,6 +11,8 @@ use reth_era_utils as era; use reth_etl::Collector; use reth_fs_util as fs; use reth_node_core::version::SHORT_VERSION; +use reth_provider::StaticFileProviderFactory; +use reth_static_file_types::StaticFileSegment; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -72,8 +74,14 @@ impl> ImportEraC let mut hash_collector = Collector::new(config.stages.etl.file_size, config.stages.etl.dir); + let next_block = provider_factory + .static_file_provider() + .get_highest_static_file_block(StaticFileSegment::Headers) + .unwrap_or_default() + + 1; + if let Some(path) = self.import.path { - let stream = read_dir(path, 0)?; + let stream = read_dir(path, next_block)?; era::import(stream, &provider_factory, &mut hash_collector)?; } else { @@ -86,8 +94,9 @@ impl> ImportEraC fs::create_dir_all(&folder)?; + let config = EraStreamConfig::default().start_from(next_block); let client = EraClient::new(Client::new(), url, folder); - let stream = EraStream::new(client, EraStreamConfig::default()); + let stream = EraStream::new(client, config); era::import(stream, &provider_factory, &mut hash_collector)?; } From 31300e4fde9c0ad6e3d274ed1415d1e0d11a5b9a Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 16 Jun 2025 11:52:21 +0200 Subject: [PATCH 048/274] feat: integrate tracing helpers (#16466) --- Cargo.lock | 29 +++--- Cargo.toml | 17 ++-- .../optimism/txpool/src/supervisor/errors.rs | 10 +- .../optimism/txpool/src/supervisor/metrics.rs | 26 ++--- crates/rpc/rpc-eth-api/src/helpers/trace.rs | 97 ++++++++----------- crates/rpc/rpc-eth-types/src/cache/db.rs | 9 +- crates/rpc/rpc/src/otterscan.rs | 7 +- crates/rpc/rpc/src/trace.rs | 26 ++--- 8 files changed, 108 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b28e3092017..81678ab067e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "394b09cf3a32773eedf11828987f9c72dfa74545040be0422e3f5f09a2a3fab9" +checksum = "7ed976ec0c32f8c6d79703e96249d0866b6c444c0a2649c10bfd5203e7e13adf" dependencies = [ "alloy-consensus", "alloy-eips", @@ -368,9 +368,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f32538cc243ec5d4603da9845cc2f5254c6a3a78e82475beb1a2a1de6c0d36c" +checksum = "e515335e1f7888d92ec9c459b5007204f62ede79bbb423ffc5415b950900eff7" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5925,9 +5925,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2423a125ef2daa0d15dacc361805a0b6f76d6acfc6e24a1ff6473582087fe75" +checksum = "931e9d8513773478289283064ee6b49b40247b57e2884782c5a1f01f6d76f93a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5951,9 +5951,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bac5140ed9a01112a1c63866da3c38c74eb387b95917d0f304a4bd4ee825986" +checksum = "a2a41ad5c040cf34e09cbd8278fb965f3c9c59ad80c4af8be7f2146697c8001e" dependencies = [ "alloy-consensus", "alloy-network", @@ -5967,9 +5967,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64cb0771602eb2b25e38817d64cd0f841ff07ef9df1e9ce96a53c1742776e874" +checksum = "3be4e3667dc3ced203d0d5356fa79817852d9deabbc697a8d2b7b4f4204d9e5b" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5977,9 +5977,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82a315004b6720fbf756afdcfdc97ea7ddbcdccfec86ea7df7562bb0da29a3f" +checksum = "37a3b08a5ffa27eb527dbde5b2452a9f15c142325e977bfacdaaf56b43de7a25" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5996,9 +5996,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aea08d8ad3f533df0c5082d3e93428a4c57898b7ade1be928fa03918f22e71" +checksum = "3524953ce0213b0c654c05ddc93dcb5676347ad05a84a5a25c140cb0c5807bc9" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6009,6 +6009,7 @@ dependencies = [ "arbitrary", "derive_more", "ethereum_ssz", + "ethereum_ssz_derive", "op-alloy-consensus", "serde", "snap", diff --git a/Cargo.toml b/Cargo.toml index 03283001c27..c51099f5b72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -463,7 +463,7 @@ revm-inspectors = "0.24.0" alloy-chains = { version = "0.2.0", default-features = false } alloy-dyn-abi = "1.1.0" alloy-eip2124 = { version = "0.2.0", default-features = false } -alloy-evm = { version = "0.10", default-features = false } +alloy-evm = { version = "0.11", default-features = false } alloy-primitives = { version = "1.1.0", default-features = false, features = ["map-foldhash"] } alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } alloy-sol-macro = "1.1.0" @@ -501,13 +501,13 @@ alloy-transport-ipc = { version = "1.0.9", default-features = false } alloy-transport-ws = { version = "1.0.9", default-features = false } # op -alloy-op-evm = { version = "0.10.0", default-features = false } +alloy-op-evm = { version = "0.11", default-features = false } alloy-op-hardforks = "0.2.2" -op-alloy-rpc-types = { version = "0.17.2", default-features = false } -op-alloy-rpc-types-engine = { version = "0.17.2", default-features = false } -op-alloy-network = { version = "0.17.2", default-features = false } -op-alloy-consensus = { version = "0.17.2", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.17.2", default-features = false } +op-alloy-rpc-types = { version = "0.18", default-features = false } +op-alloy-rpc-types-engine = { version = "0.18", default-features = false } +op-alloy-network = { version = "0.18", default-features = false } +op-alloy-consensus = { version = "0.18", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.18", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # misc @@ -729,9 +729,6 @@ vergen-git2 = "1.0.5" # alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", branch = "main" } # alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", branch = "main" } # alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", branch = "main" } - -# alloy-evm = { git = "https://github.com/alloy-rs/evm", branch = "main" } -# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", branch = "main" } # # op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } # op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } diff --git a/crates/optimism/txpool/src/supervisor/errors.rs b/crates/optimism/txpool/src/supervisor/errors.rs index bae6fe48d03..9993a5ca5d1 100644 --- a/crates/optimism/txpool/src/supervisor/errors.rs +++ b/crates/optimism/txpool/src/supervisor/errors.rs @@ -1,6 +1,6 @@ use alloy_json_rpc::RpcError; use core::error; -use op_alloy_rpc_types::InvalidInboxEntry; +use op_alloy_rpc_types::SuperchainDAError; /// Failures occurring during validation of inbox entries. #[derive(thiserror::Error, Debug)] @@ -11,7 +11,7 @@ pub enum InteropTxValidatorError { /// Message does not satisfy validation requirements #[error(transparent)] - InvalidEntry(#[from] InvalidInboxEntry), + InvalidEntry(#[from] SuperchainDAError), /// Catch-all variant. #[error("supervisor server error: {0}")] @@ -36,10 +36,10 @@ impl InteropTxValidatorError { { // Try to extract error details from the RPC error if let Some(error_payload) = err.as_error_resp() { - let code = error_payload.code; + let code = error_payload.code as i32; - // Try to convert the error code to an InvalidInboxEntry variant - if let Ok(invalid_entry) = InvalidInboxEntry::try_from(code) { + // Try to convert the error code to an SuperchainDAError variant + if let Ok(invalid_entry) = SuperchainDAError::try_from(code) { return Self::InvalidEntry(invalid_entry); } } diff --git a/crates/optimism/txpool/src/supervisor/metrics.rs b/crates/optimism/txpool/src/supervisor/metrics.rs index 0c66d0039ac..cbe08e7a442 100644 --- a/crates/optimism/txpool/src/supervisor/metrics.rs +++ b/crates/optimism/txpool/src/supervisor/metrics.rs @@ -1,7 +1,7 @@ //! Optimism supervisor and sequencer metrics use crate::supervisor::InteropTxValidatorError; -use op_alloy_rpc_types::InvalidInboxEntry; +use op_alloy_rpc_types::SuperchainDAError; use reth_metrics::{ metrics::{Counter, Histogram}, Metrics, @@ -50,22 +50,22 @@ impl SupervisorMetrics { pub fn increment_metrics_for_error(&self, error: &InteropTxValidatorError) { if let InteropTxValidatorError::InvalidEntry(inner) = error { match inner { - InvalidInboxEntry::SkippedData => self.skipped_data_count.increment(1), - InvalidInboxEntry::UnknownChain => self.unknown_chain_count.increment(1), - InvalidInboxEntry::ConflictingData => self.conflicting_data_count.increment(1), - InvalidInboxEntry::IneffectiveData => self.ineffective_data_count.increment(1), - InvalidInboxEntry::OutOfOrder => self.out_of_order_count.increment(1), - InvalidInboxEntry::AwaitingReplacement => { + SuperchainDAError::SkippedData => self.skipped_data_count.increment(1), + SuperchainDAError::UnknownChain => self.unknown_chain_count.increment(1), + SuperchainDAError::ConflictingData => self.conflicting_data_count.increment(1), + SuperchainDAError::IneffectiveData => self.ineffective_data_count.increment(1), + SuperchainDAError::OutOfOrder => self.out_of_order_count.increment(1), + SuperchainDAError::AwaitingReplacement => { self.awaiting_replacement_count.increment(1) } - InvalidInboxEntry::OutOfScope => self.out_of_scope_count.increment(1), - InvalidInboxEntry::NoParentForFirstBlock => { + SuperchainDAError::OutOfScope => self.out_of_scope_count.increment(1), + SuperchainDAError::NoParentForFirstBlock => { self.no_parent_for_first_block_count.increment(1) } - InvalidInboxEntry::FutureData => self.future_data_count.increment(1), - InvalidInboxEntry::MissedData => self.missed_data_count.increment(1), - InvalidInboxEntry::DataCorruption => self.data_corruption_count.increment(1), - InvalidInboxEntry::UninitializedChainDatabase => {} + SuperchainDAError::FutureData => self.future_data_count.increment(1), + SuperchainDAError::MissedData => self.missed_data_count.increment(1), + SuperchainDAError::DataCorruption => self.data_corruption_count.increment(1), + SuperchainDAError::UninitializedChainDatabase => {} } } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/helpers/trace.rs index e2f70602351..702f2681d51 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/trace.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/trace.rs @@ -9,22 +9,18 @@ use futures::Future; use reth_chainspec::ChainSpecProvider; use reth_errors::ProviderError; use reth_evm::{ - system_calls::SystemCaller, ConfigureEvm, Database, Evm, EvmEnvFor, HaltReasonFor, - InspectorFor, TxEnvFor, + evm::EvmFactoryExt, system_calls::SystemCaller, tracing::TracingCtx, ConfigureEvm, Database, + Evm, EvmEnvFor, EvmFor, HaltReasonFor, InspectorFor, TxEnvFor, }; use reth_node_api::NodePrimitives; -use reth_primitives_traits::{BlockBody, RecoveredBlock, SignedTransaction}; +use reth_primitives_traits::{BlockBody, Recovered, RecoveredBlock, SignedTransaction}; use reth_revm::{database::StateProviderDatabase, db::CacheDB}; use reth_rpc_eth_types::{ cache::db::{StateCacheDb, StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, EthApiError, }; use reth_storage_api::{BlockReader, ProviderBlock, ProviderHeader, ProviderTx}; -use revm::{ - context_interface::result::{ExecutionResult, ResultAndState}, - state::EvmState, - DatabaseCommit, -}; +use revm::{context_interface::result::ResultAndState, DatabaseCommit}; use revm_inspectors::tracing::{TracingInspector, TracingInspectorConfig}; use std::sync::Arc; @@ -242,10 +238,11 @@ pub trait Trace: Self: LoadBlock, F: Fn( TransactionInfo, - TracingInspector, - ExecutionResult>, - &EvmState, - &StateCacheDb<'_>, + TracingCtx< + '_, + Recovered<&ProviderTx>, + EvmFor, TracingInspector>, + >, ) -> Result + Send + 'static, @@ -282,15 +279,16 @@ pub trait Trace: Self: LoadBlock, F: Fn( TransactionInfo, - Insp, - ExecutionResult>, - &EvmState, - &StateCacheDb<'_>, + TracingCtx< + '_, + Recovered<&ProviderTx>, + EvmFor, Insp>, + >, ) -> Result + Send + 'static, Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a, 'b> InspectorFor>, + Insp: Clone + for<'a, 'b> InspectorFor>, R: Send + 'static, { async move { @@ -334,43 +332,26 @@ pub trait Trace: // 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 - .transactions_recovered() - .take(max_transactions) - .enumerate() - .map(|(idx, tx)| { + let mut idx = 0; + + let results = this + .evm_config() + .evm_factory() + .create_tracer(StateCacheDbRefMutWrapper(&mut db), evm_env, inspector_setup()) + .try_trace_many(block.transactions_recovered().take(max_transactions), |ctx| { let tx_info = TransactionInfo { - hash: Some(*tx.tx_hash()), - index: Some(idx as u64), + hash: Some(*ctx.tx.tx_hash()), + index: Some(idx), block_hash: Some(block_hash), block_number: Some(block_number), base_fee: Some(base_fee), }; - let tx_env = this.evm_config().tx_env(tx); - (tx_info, tx_env) - }) - .peekable(); + idx += 1; - while let Some((tx_info, tx)) = transactions.next() { - let mut inspector = inspector_setup(); - let (res, _) = this.inspect( - StateCacheDbRefMutWrapper(&mut db), - evm_env.clone(), - tx, - &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) - } - } + f(tx_info, ctx) + }) + .collect::>()?; Ok(Some(results)) }) @@ -401,10 +382,11 @@ pub trait Trace: // state and db F: Fn( TransactionInfo, - TracingInspector, - ExecutionResult>, - &EvmState, - &StateCacheDb<'_>, + TracingCtx< + '_, + Recovered<&ProviderTx>, + EvmFor, TracingInspector>, + >, ) -> Result + Send + 'static, @@ -421,7 +403,7 @@ pub trait Trace: /// 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 + /// _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)` /// @@ -440,15 +422,16 @@ pub trait Trace: // state and db F: Fn( TransactionInfo, - Insp, - ExecutionResult>, - &EvmState, - &StateCacheDb<'_>, + TracingCtx< + '_, + Recovered<&ProviderTx>, + EvmFor, Insp>, + >, ) -> Result + Send + 'static, Setup: FnMut() -> Insp + Send + 'static, - Insp: for<'a, 'b> InspectorFor>, + Insp: Clone + for<'a, 'b> InspectorFor>, R: Send + 'static, { self.trace_block_until_with_inspector(block_id, block, None, insp_setup, f) diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 633a4482e74..7f45b6407f0 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -9,8 +9,9 @@ use reth_storage_api::{HashedPostStateProvider, StateProvider}; use reth_trie::{HashedStorage, MultiProofTargets}; use revm::{ database::{BundleState, CacheDB}, + primitives::HashMap, state::{AccountInfo, Bytecode}, - Database, + Database, DatabaseCommit, }; /// Helper alias type for the state's [`CacheDB`] @@ -220,3 +221,9 @@ impl<'a> DatabaseRef for StateCacheDbRefMutWrapper<'a, '_> { self.0.block_hash_ref(number) } } + +impl DatabaseCommit for StateCacheDbRefMutWrapper<'_, '_> { + fn commit(&mut self, changes: HashMap) { + self.0.commit(changes) + } +} diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index 502a71ec6d4..3ca257346e7 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -338,8 +338,11 @@ where num.into(), None, TracingInspectorConfig::default_parity(), - |tx_info, inspector, _, _, _| { - Ok(inspector.into_parity_builder().into_localized_transaction_traces(tx_info)) + |tx_info, ctx| { + Ok(ctx + .inspector + .into_parity_builder() + .into_localized_transaction_traces(tx_info)) }, ) .await diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index a2c621045d0..5adc54168ef 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -402,9 +402,11 @@ where Some(block.clone()), None, TracingInspectorConfig::default_parity(), - move |tx_info, inspector, _, _, _| { - let mut traces = - inspector.into_parity_builder().into_localized_transaction_traces(tx_info); + move |tx_info, ctx| { + let mut traces = ctx + .inspector + .into_parity_builder() + .into_localized_transaction_traces(tx_info); traces.retain(|trace| matcher.matches(&trace.trace)); Ok(Some(traces)) }, @@ -469,9 +471,9 @@ where block_id, None, TracingInspectorConfig::default_parity(), - |tx_info, inspector, _, _, _| { + |tx_info, ctx| { let traces = - inspector.into_parity_builder().into_localized_transaction_traces(tx_info); + ctx.inspector.into_parity_builder().into_localized_transaction_traces(tx_info); Ok(traces) }, ); @@ -506,14 +508,16 @@ where block_id, None, TracingInspectorConfig::from_parity_config(&trace_types), - move |tx_info, inspector, res, state, db| { - let mut full_trace = - inspector.into_parity_builder().into_trace_results(&res, &trace_types); + move |tx_info, ctx| { + let mut full_trace = ctx + .inspector + .into_parity_builder() + .into_trace_results(&ctx.result, &trace_types); // If statediffs were requested, populate them with the account balance and // nonce from pre-state if let Some(ref mut state_diff) = full_trace.state_diff { - populate_state_diff(state_diff, db, state.iter()) + populate_state_diff(state_diff, &ctx.db, ctx.state.iter()) .map_err(Eth::Error::from_eth_err)?; } @@ -541,10 +545,10 @@ where block_id, None, OpcodeGasInspector::default, - move |tx_info, inspector, _res, _, _| { + move |tx_info, ctx| { let trace = TransactionOpcodeGas { transaction_hash: tx_info.hash.expect("tx hash is set"), - opcode_gas: inspector.opcode_gas_iter().collect(), + opcode_gas: ctx.inspector.opcode_gas_iter().collect(), }; Ok(trace) }, From 68efe4f02de1520e2cd676b7fcfacc62f80e7ed3 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Mon, 16 Jun 2025 12:12:37 +0200 Subject: [PATCH 049/274] chore(op/cli): Rm unused cli arg `rollup.enable-genesis-walkback` (#16824) --- book/run/optimism.md | 1 - crates/optimism/node/src/args.rs | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/book/run/optimism.md b/book/run/optimism.md index aa85d1aa93b..a3f747dd1f9 100644 --- a/book/run/optimism.md +++ b/book/run/optimism.md @@ -51,7 +51,6 @@ For the sake of this tutorial, we'll use the reference implementation of the Rol op-reth supports additional OP Stack specific CLI arguments: 1. `--rollup.sequencer-http ` - The sequencer endpoint to connect to. Transactions sent to the `op-reth` EL are also forwarded to this sequencer endpoint for inclusion, as the sequencer is the entity that builds blocks on OP Stack chains. 1. `--rollup.disable-tx-pool-gossip` - Disables gossiping of transactions in the mempool to peers. This can be omitted for personal nodes, though providers should always opt to enable this flag. -1. `--rollup.enable-genesis-walkback` - Disables setting the forkchoice status to tip on startup, making the `op-node` walk back to genesis and verify the integrity of the chain before starting to sync. This can be omitted unless a corruption of local chainstate is suspected. 1. `--rollup.discovery.v4` - Enables the discovery v4 protocol for peer discovery. By default, op-reth, similar to op-geth, has discovery v5 enabled and discovery v4 disabled, whereas regular reth has discovery v4 enabled and discovery v5 disabled. First, ensure that your L1 archival node is running and synced to tip. Also make sure that the beacon node / consensus layer client is running and has http APIs enabled. Then, start `op-reth` with the `--rollup.sequencer-http` flag set to the `Base Mainnet` sequencer endpoint: diff --git a/crates/optimism/node/src/args.rs b/crates/optimism/node/src/args.rs index 3276abf2e78..f968a5e2351 100644 --- a/crates/optimism/node/src/args.rs +++ b/crates/optimism/node/src/args.rs @@ -17,11 +17,6 @@ pub struct RollupArgs { #[arg(long = "rollup.disable-tx-pool-gossip")] pub disable_txpool_gossip: bool, - /// Enable walkback to genesis on startup. This is useful for re-validating the existing DB - /// prior to beginning normal syncing. - #[arg(long = "rollup.enable-genesis-walkback")] - pub enable_genesis_walkback: bool, - /// By default the pending block equals the latest block /// to save resources and not leak txs from the tx-pool, /// this flag enables computing of the pending block @@ -70,7 +65,6 @@ impl Default for RollupArgs { Self { sequencer: None, disable_txpool_gossip: false, - enable_genesis_walkback: false, compute_pending_block: false, discovery_v4: false, enable_tx_conditional: false, @@ -101,15 +95,6 @@ mod tests { assert_eq!(args, default_args); } - #[test] - fn test_parse_optimism_walkback_args() { - let expected_args = RollupArgs { enable_genesis_walkback: true, ..Default::default() }; - let args = - CommandParser::::parse_from(["reth", "--rollup.enable-genesis-walkback"]) - .args; - assert_eq!(args, expected_args); - } - #[test] fn test_parse_optimism_compute_pending_block_args() { let expected_args = RollupArgs { compute_pending_block: true, ..Default::default() }; @@ -162,7 +147,6 @@ mod tests { let expected_args = RollupArgs { disable_txpool_gossip: true, compute_pending_block: true, - enable_genesis_walkback: true, enable_tx_conditional: true, sequencer: Some("http://host:port".into()), ..Default::default() @@ -171,7 +155,6 @@ mod tests { "reth", "--rollup.disable-tx-pool-gossip", "--rollup.compute-pending-block", - "--rollup.enable-genesis-walkback", "--rollup.enable-tx-conditional", "--rollup.sequencer-http", "http://host:port", From fcc935e215b9cbd3db4799e6c3fbe45510daa5e0 Mon Sep 17 00:00:00 2001 From: 0xNarumi <122093865+0xNarumi@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:28:24 +0900 Subject: [PATCH 050/274] fix: make `EthPubSub` pub to allow composition and fallback overrides (#16823) --- crates/rpc/rpc/src/eth/pubsub.rs | 265 +++++++++++++++++-------------- 1 file changed, 148 insertions(+), 117 deletions(-) diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index b91318d498b..c5fd1604562 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -55,43 +55,156 @@ impl EthPubSub { } } -#[async_trait::async_trait] -impl EthPubSubApiServer> for EthPubSub +impl EthPubSub where Eth: RpcNodeCore< - Provider: BlockNumReader + CanonStateSubscriptions, + Provider: BlockNumReader + CanonStateSubscriptions, Pool: TransactionPool, Network: NetworkInfo, > + EthApiTypes< TransactionCompat: TransactionCompat< Primitives: NodePrimitives>, >, - > + 'static, + >, { - /// Handler for `eth_subscribe` - async fn subscribe( + /// Returns the current sync status for the `syncing` subscription + pub fn sync_status(&self, is_syncing: bool) -> PubSubSyncStatus { + self.inner.sync_status(is_syncing) + } + + /// Returns a stream that yields all transaction hashes emitted by the txpool. + pub fn pending_transaction_hashes_stream(&self) -> impl Stream { + self.inner.pending_transaction_hashes_stream() + } + + /// Returns a stream that yields all transactions emitted by the txpool. + pub fn full_pending_transaction_stream( &self, - pending: PendingSubscriptionSink, + ) -> impl Stream::Transaction>> { + self.inner.full_pending_transaction_stream() + } + + /// Returns a stream that yields all new RPC blocks. + pub fn new_headers_stream(&self) -> impl Stream> { + self.inner.new_headers_stream() + } + + /// Returns a stream that yields all logs that match the given filter. + pub fn log_stream(&self, filter: Filter) -> impl Stream { + self.inner.log_stream(filter) + } + + /// The actual handler for an accepted [`EthPubSub::subscribe`] call. + pub async fn handle_accepted( + &self, + accepted_sink: SubscriptionSink, kind: SubscriptionKind, params: Option, - ) -> jsonrpsee::core::SubscriptionResult { - let sink = pending.accept().await?; - let pubsub = self.inner.clone(); - self.inner.subscription_task_spawner.spawn(Box::pin(async move { - let _ = handle_accepted(pubsub, sink, kind, params).await; - })); + ) -> Result<(), ErrorObject<'static>> { + match kind { + SubscriptionKind::NewHeads => { + pipe_from_stream(accepted_sink, self.new_headers_stream()).await + } + SubscriptionKind::Logs => { + // if no params are provided, used default filter params + let filter = match params { + Some(Params::Logs(filter)) => *filter, + Some(Params::Bool(_)) => { + return Err(invalid_params_rpc_err("Invalid params for logs")) + } + _ => Default::default(), + }; + pipe_from_stream(accepted_sink, self.log_stream(filter)).await + } + SubscriptionKind::NewPendingTransactions => { + if let Some(params) = params { + match params { + Params::Bool(true) => { + // full transaction objects requested + let stream = self.full_pending_transaction_stream().filter_map(|tx| { + let tx_value = match self + .inner + .eth_api + .tx_resp_builder() + .fill_pending(tx.transaction.to_consensus()) + { + Ok(tx) => Some(tx), + Err(err) => { + error!(target = "rpc", + %err, + "Failed to fill transaction with block context" + ); + None + } + }; + std::future::ready(tx_value) + }); + return pipe_from_stream(accepted_sink, stream).await + } + Params::Bool(false) | Params::None => { + // only hashes requested + } + Params::Logs(_) => { + return Err(invalid_params_rpc_err( + "Invalid params for newPendingTransactions", + )) + } + } + } - Ok(()) + pipe_from_stream(accepted_sink, self.pending_transaction_hashes_stream()).await + } + SubscriptionKind::Syncing => { + // get new block subscription + let mut canon_state = BroadcastStream::new( + self.inner.eth_api.provider().subscribe_to_canonical_state(), + ); + // get current sync status + let mut initial_sync_status = self.inner.eth_api.network().is_syncing(); + let current_sub_res = self.sync_status(initial_sync_status); + + // send the current status immediately + let msg = SubscriptionMessage::new( + accepted_sink.method_name(), + accepted_sink.subscription_id(), + ¤t_sub_res, + ) + .map_err(SubscriptionSerializeError::new)?; + + if accepted_sink.send(msg).await.is_err() { + return Ok(()) + } + + while canon_state.next().await.is_some() { + let current_syncing = self.inner.eth_api.network().is_syncing(); + // Only send a new response if the sync status has changed + if current_syncing != initial_sync_status { + // Update the sync status on each new block + initial_sync_status = current_syncing; + + // send a new message now that the status changed + let sync_status = self.sync_status(current_syncing); + let msg = SubscriptionMessage::new( + accepted_sink.method_name(), + accepted_sink.subscription_id(), + &sync_status, + ) + .map_err(SubscriptionSerializeError::new)?; + + if accepted_sink.send(msg).await.is_err() { + break + } + } + } + + Ok(()) + } + } } } -/// The actual handler for an accepted [`EthPubSub::subscribe`] call. -async fn handle_accepted( - pubsub: Arc>, - accepted_sink: SubscriptionSink, - kind: SubscriptionKind, - params: Option, -) -> Result<(), ErrorObject<'static>> +#[async_trait::async_trait] +impl EthPubSubApiServer> for EthPubSub where Eth: RpcNodeCore< Provider: BlockNumReader + CanonStateSubscriptions, @@ -101,104 +214,22 @@ where TransactionCompat: TransactionCompat< Primitives: NodePrimitives>, >, - >, + > + 'static, { - match kind { - SubscriptionKind::NewHeads => { - pipe_from_stream(accepted_sink, pubsub.new_headers_stream()).await - } - SubscriptionKind::Logs => { - // if no params are provided, used default filter params - let filter = match params { - Some(Params::Logs(filter)) => *filter, - Some(Params::Bool(_)) => { - return Err(invalid_params_rpc_err("Invalid params for logs")) - } - _ => Default::default(), - }; - pipe_from_stream(accepted_sink, pubsub.log_stream(filter)).await - } - SubscriptionKind::NewPendingTransactions => { - if let Some(params) = params { - match params { - Params::Bool(true) => { - // full transaction objects requested - let stream = pubsub.full_pending_transaction_stream().filter_map(|tx| { - let tx_value = match pubsub - .eth_api - .tx_resp_builder() - .fill_pending(tx.transaction.to_consensus()) - { - Ok(tx) => Some(tx), - Err(err) => { - error!(target = "rpc", - %err, - "Failed to fill transaction with block context" - ); - None - } - }; - std::future::ready(tx_value) - }); - return pipe_from_stream(accepted_sink, stream).await - } - Params::Bool(false) | Params::None => { - // only hashes requested - } - Params::Logs(_) => { - return Err(invalid_params_rpc_err( - "Invalid params for newPendingTransactions", - )) - } - } - } - - pipe_from_stream(accepted_sink, pubsub.pending_transaction_hashes_stream()).await - } - SubscriptionKind::Syncing => { - // get new block subscription - let mut canon_state = - BroadcastStream::new(pubsub.eth_api.provider().subscribe_to_canonical_state()); - // get current sync status - let mut initial_sync_status = pubsub.eth_api.network().is_syncing(); - let current_sub_res = pubsub.sync_status(initial_sync_status); - - // send the current status immediately - let msg = SubscriptionMessage::new( - accepted_sink.method_name(), - accepted_sink.subscription_id(), - ¤t_sub_res, - ) - .map_err(SubscriptionSerializeError::new)?; - - if accepted_sink.send(msg).await.is_err() { - return Ok(()) - } - - while canon_state.next().await.is_some() { - let current_syncing = pubsub.eth_api.network().is_syncing(); - // Only send a new response if the sync status has changed - if current_syncing != initial_sync_status { - // Update the sync status on each new block - initial_sync_status = current_syncing; - - // send a new message now that the status changed - let sync_status = pubsub.sync_status(current_syncing); - let msg = SubscriptionMessage::new( - accepted_sink.method_name(), - accepted_sink.subscription_id(), - &sync_status, - ) - .map_err(SubscriptionSerializeError::new)?; - - if accepted_sink.send(msg).await.is_err() { - break - } - } - } + /// Handler for `eth_subscribe` + async fn subscribe( + &self, + pending: PendingSubscriptionSink, + kind: SubscriptionKind, + params: Option, + ) -> jsonrpsee::core::SubscriptionResult { + let sink = pending.accept().await?; + let pubsub = self.clone(); + self.inner.subscription_task_spawner.spawn(Box::pin(async move { + let _ = pubsub.handle_accepted(sink, kind, params).await; + })); - Ok(()) - } + Ok(()) } } From 5f1353c410d896af14242eb19f1df2e747280f35 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 16 Jun 2025 13:33:44 +0200 Subject: [PATCH 051/274] feat: add alloy-provider crate for RPC-based state access (#16809) Co-authored-by: Claude Co-authored-by: Federico Gimenez --- .github/assets/check_wasm.sh | 1 + Cargo.lock | 28 + Cargo.toml | 2 + crates/alloy-provider/Cargo.toml | 48 + crates/alloy-provider/README.md | 60 ++ crates/alloy-provider/src/lib.rs | 1460 ++++++++++++++++++++++++++++++ 6 files changed, 1599 insertions(+) create mode 100644 crates/alloy-provider/Cargo.toml create mode 100644 crates/alloy-provider/README.md create mode 100644 crates/alloy-provider/src/lib.rs diff --git a/.github/assets/check_wasm.sh b/.github/assets/check_wasm.sh index 5504cb17e62..c5639c710d2 100755 --- a/.github/assets/check_wasm.sh +++ b/.github/assets/check_wasm.sh @@ -58,6 +58,7 @@ exclude_crates=( reth-ress-provider # The following are not supposed to be working reth # all of the crates below + reth-alloy-provider reth-invalid-block-hooks # reth-provider reth-libmdbx # mdbx reth-mdbx-sys # mdbx diff --git a/Cargo.lock b/Cargo.lock index 81678ab067e..d6791b90888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7142,6 +7142,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-alloy-provider" +version = "1.4.8" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-db-api", + "reth-errors", + "reth-execution-types", + "reth-node-types", + "reth-primitives", + "reth-provider", + "reth-prune-types", + "reth-stages-types", + "reth-storage-api", + "reth-trie", + "revm", + "revm-primitives", + "tokio", + "tracing", +] + [[package]] name = "reth-basic-payload-builder" version = "1.4.8" diff --git a/Cargo.toml b/Cargo.toml index c51099f5b72..89086c7027c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ exclude = [".github/"] members = [ "bin/reth-bench/", "bin/reth/", + "crates/alloy-provider/", "crates/chain-state/", "crates/chainspec/", "crates/cli/cli/", @@ -319,6 +320,7 @@ codegen-units = 1 # reth op-reth = { path = "crates/optimism/bin" } reth = { path = "bin/reth" } +reth-alloy-provider = { path = "crates/alloy-provider" } reth-basic-payload-builder = { path = "crates/payload/basic" } reth-bench = { path = "bin/reth-bench" } reth-chain-state = { path = "crates/chain-state" } diff --git a/crates/alloy-provider/Cargo.toml b/crates/alloy-provider/Cargo.toml new file mode 100644 index 00000000000..6eb47e1f4d5 --- /dev/null +++ b/crates/alloy-provider/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "reth-alloy-provider" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Alloy provider implementation for reth that fetches state via RPC" + +[lints] +workspace = true + +[dependencies] +# reth +reth-storage-api.workspace = true +reth-chainspec.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true +reth-errors.workspace = true +reth-execution-types.workspace = true +reth-prune-types.workspace = true +reth-node-types.workspace = true +reth-trie.workspace = true +reth-stages-types.workspace = true +reth-db-api.workspace = true + +# alloy +alloy-provider.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-rpc-types.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-eips.workspace = true + +# async +tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] } + +# other +tracing.workspace = true + +# revm +revm.workspace = true +revm-primitives.workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/crates/alloy-provider/README.md b/crates/alloy-provider/README.md new file mode 100644 index 00000000000..37a75f1b328 --- /dev/null +++ b/crates/alloy-provider/README.md @@ -0,0 +1,60 @@ +# Alloy Provider for Reth + +This crate provides an implementation of reth's `StateProviderFactory` and related traits that fetches state data via RPC instead of from a local database. + +Originally created by [cakevm](https://github.com/cakevm/alloy-reth-provider). + +## Features + +- Implements `StateProviderFactory` for remote RPC state access +- Supports Ethereum networks +- Useful for testing without requiring a full database +- Can be used with reth ExEx (Execution Extensions) for testing + +## Usage + +```rust +use alloy_provider::ProviderBuilder; +use reth_alloy_provider::AlloyRethProvider; +use reth_ethereum_node::EthereumNode; + +// Initialize provider +let provider = ProviderBuilder::new() + .builtin("https://eth.merkle.io") + .await + .unwrap(); + +// Create database provider with NodeTypes +let db_provider = AlloyRethProvider::new(provider, EthereumNode); + +// Get state at specific block +let state = db_provider.state_by_block_id(BlockId::number(16148323)).unwrap(); +``` + +## Configuration + +The provider can be configured with custom settings: + +```rust +use reth_alloy_provider::{AlloyRethProvider, AlloyRethProviderConfig}; +use reth_ethereum_node::EthereumNode; + +let config = AlloyRethProviderConfig { + compute_state_root: true, // Enable state root computation +}; + +let db_provider = AlloyRethProvider::new_with_config(provider, EthereumNode, config); +``` + +## Technical Details + +The provider uses `alloy_network::AnyNetwork` for network operations, providing compatibility with various Ethereum-based networks while maintaining the expected block structure with headers. + +## License + +Licensed under either of: + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. \ No newline at end of file diff --git a/crates/alloy-provider/src/lib.rs b/crates/alloy-provider/src/lib.rs new file mode 100644 index 00000000000..318ecec8b23 --- /dev/null +++ b/crates/alloy-provider/src/lib.rs @@ -0,0 +1,1460 @@ +//! # Alloy Provider for Reth +//! +//! This crate provides an implementation of reth's `StateProviderFactory` and related traits +//! that fetches state data via RPC instead of from a local database. +//! +//! Originally created by [cakevm](https://github.com/cakevm/alloy-reth-provider). +//! +//! ## Features +//! +//! - Implements `StateProviderFactory` for remote RPC state access +//! - Supports Ethereum and Optimism network +//! - Useful for testing without requiring a full database +//! - Can be used with reth ExEx (Execution Extensions) for testing + +#![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))] + +use alloy_consensus::BlockHeader; +use alloy_network::{primitives::HeaderResponse, BlockResponse}; +use alloy_primitives::{Address, BlockHash, BlockNumber, StorageKey, TxNumber, B256, U256}; +use alloy_provider::{network::Network, Provider}; +use alloy_rpc_types::BlockId; +use alloy_rpc_types_engine::ForkchoiceState; +use reth_chainspec::{ChainInfo, ChainSpecProvider}; +use reth_db_api::mock::{DatabaseMock, TxMock}; +use reth_errors::ProviderError; +use reth_node_types::{BlockTy, HeaderTy, NodeTypes, PrimitivesTy, ReceiptTy, TxTy}; +use reth_primitives::{ + Account, Bytecode, RecoveredBlock, SealedBlock, SealedHeader, TransactionMeta, +}; +use reth_provider::{ + AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, CanonChainTracker, + CanonStateNotification, CanonStateNotifications, CanonStateSubscriptions, + ChainStateBlockReader, ChainStateBlockWriter, ChangeSetReader, DatabaseProviderFactory, + HeaderProvider, PruneCheckpointReader, ReceiptProvider, StageCheckpointReader, StateProvider, + StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, StorageReader, + TransactionVariant, TransactionsProvider, +}; +use reth_prune_types::{PruneCheckpoint, PruneSegment}; +use reth_stages_types::{StageCheckpoint, StageId}; +use reth_storage_api::{BlockBodyIndicesProvider, DBProvider, NodePrimitivesProvider, StatsReader}; +use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, MultiProof, TrieInput}; +use std::{ + collections::BTreeMap, + future::Future, + ops::{RangeBounds, RangeInclusive}, + sync::Arc, +}; +use tokio::{runtime::Handle, sync::broadcast}; +use tracing::trace; + +/// Configuration for `AlloyRethProvider` +#[derive(Debug, Clone, Default)] +pub struct AlloyRethProviderConfig { + /// Whether to compute state root when creating execution outcomes + pub compute_state_root: bool, +} + +impl AlloyRethProviderConfig { + /// Sets whether to compute state root when creating execution outcomes + pub const fn with_compute_state_root(mut self, compute: bool) -> Self { + self.compute_state_root = compute; + self + } +} + +/// A provider implementation that uses Alloy RPC to fetch state data +/// +/// This provider implements reth's `StateProviderFactory` and related traits, +/// allowing it to be used as a drop-in replacement for database-backed providers +/// in scenarios where RPC access is preferred (e.g., testing). +/// +/// The provider type is generic over the network type N (defaulting to `AnyNetwork`), +/// but the current implementation is specialized for `alloy_network::AnyNetwork` +/// as it needs to access block header fields directly. +#[derive(Clone)] +pub struct AlloyRethProvider +where + Node: NodeTypes, +{ + /// The underlying Alloy provider + provider: P, + /// Node types marker + node_types: std::marker::PhantomData, + /// Network marker + network: std::marker::PhantomData, + /// Broadcast channel for canon state notifications + canon_state_notification: broadcast::Sender>>, + /// Configuration for the provider + config: AlloyRethProviderConfig, + /// Cached chain spec + chain_spec: Arc, +} + +impl std::fmt::Debug for AlloyRethProvider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AlloyRethProvider").field("config", &self.config).finish() + } +} + +impl AlloyRethProvider { + /// Creates a new `AlloyRethProvider` with default configuration + pub fn new(provider: P) -> Self + where + Node::ChainSpec: Default, + { + Self::new_with_config(provider, AlloyRethProviderConfig::default()) + } + + /// Creates a new `AlloyRethProvider` with custom configuration + pub fn new_with_config(provider: P, config: AlloyRethProviderConfig) -> Self + where + Node::ChainSpec: Default, + { + let (canon_state_notification, _) = broadcast::channel(1); + Self { + provider, + node_types: std::marker::PhantomData, + network: std::marker::PhantomData, + canon_state_notification, + config, + chain_spec: Arc::new(Node::ChainSpec::default()), + } + } + + /// Helper function to execute async operations in a blocking context + fn block_on_async(&self, fut: F) -> T + where + F: Future, + { + tokio::task::block_in_place(move || Handle::current().block_on(fut)) + } +} + +impl AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + /// Helper function to create a state provider for a given block ID + fn create_state_provider(&self, block_id: BlockId) -> AlloyRethStateProvider { + AlloyRethStateProvider::with_chain_spec( + self.provider.clone(), + block_id, + self.chain_spec.clone(), + ) + } + + /// Helper function to get state provider by block number + fn state_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result { + Ok(Box::new(self.create_state_provider(BlockId::number(block_number)))) + } +} + +// Implementation note: While the types are generic over Network N, the trait implementations +// are specialized for AnyNetwork because they need to access block header fields. +// This allows the types to be instantiated with any network while the actual functionality +// requires AnyNetwork. Future improvements could add trait bounds for networks with +// compatible block structures. +impl BlockHashReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_hash(&self, number: BlockNumber) -> Result, ProviderError> { + let block = self.block_on_async(async { + self.provider.get_block_by_number(number.into()).await.map_err(ProviderError::other) + })?; + Ok(block.map(|b| b.header().hash())) + } + + fn canonical_hashes_range( + &self, + _start: BlockNumber, + _end: BlockNumber, + ) -> Result, ProviderError> { + // Would need to make multiple RPC calls + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockNumReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn chain_info(&self) -> Result { + // For RPC provider, we can't get full chain info + Err(ProviderError::UnsupportedProvider) + } + + fn best_block_number(&self) -> Result { + self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + }) + } + + fn last_block_number(&self) -> Result { + self.best_block_number() + } + + fn block_number(&self, hash: B256) -> Result, ProviderError> { + let block = self.block_on_async(async { + self.provider.get_block_by_hash(hash).await.map_err(ProviderError::other) + })?; + Ok(block.map(|b| b.header().number())) + } +} + +impl BlockIdReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_number_for_id(&self, block_id: BlockId) -> Result, ProviderError> { + match block_id { + BlockId::Hash(hash) => { + let block = self.block_on_async(async { + self.provider + .get_block_by_hash(hash.block_hash) + .await + .map_err(ProviderError::other) + })?; + Ok(block.map(|b| b.header().number())) + } + BlockId::Number(number_or_tag) => match number_or_tag { + alloy_rpc_types::BlockNumberOrTag::Number(num) => Ok(Some(num)), + alloy_rpc_types::BlockNumberOrTag::Latest => self.block_on_async(async { + self.provider.get_block_number().await.map(Some).map_err(ProviderError::other) + }), + _ => Ok(None), + }, + } + } + + fn pending_block_num_hash(&self) -> Result, ProviderError> { + // RPC doesn't provide pending block number and hash together + Err(ProviderError::UnsupportedProvider) + } + + fn safe_block_num_hash(&self) -> Result, ProviderError> { + // RPC doesn't provide safe block number and hash + Err(ProviderError::UnsupportedProvider) + } + + fn finalized_block_num_hash(&self) -> Result, ProviderError> { + // RPC doesn't provide finalized block number and hash + Err(ProviderError::UnsupportedProvider) + } +} + +impl StateProviderFactory for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn latest(&self) -> Result { + trace!(target: "alloy-provider", "Getting latest state provider"); + + let block_number = self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + })?; + + self.state_by_block_number(block_number) + } + + fn state_by_block_id(&self, block_id: BlockId) -> Result { + Ok(Box::new(self.create_state_provider(block_id))) + } + + fn state_by_block_number_or_tag( + &self, + number_or_tag: alloy_rpc_types::BlockNumberOrTag, + ) -> Result { + match number_or_tag { + alloy_rpc_types::BlockNumberOrTag::Latest => self.latest(), + alloy_rpc_types::BlockNumberOrTag::Pending => self.pending(), + alloy_rpc_types::BlockNumberOrTag::Number(num) => self.state_by_block_number(num), + _ => Err(ProviderError::UnsupportedProvider), + } + } + + fn history_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result { + self.state_by_block_number(block_number) + } + + fn history_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + self.state_by_block_hash(block_hash) + } + + fn state_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + trace!(target: "alloy-provider", ?block_hash, "Getting state provider by block hash"); + + let block = self.block_on_async(async { + self.provider + .get_block_by_hash(block_hash) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::BlockHashNotFound(block_hash)) + })?; + + let block_number = block.header().number(); + Ok(Box::new(self.create_state_provider(BlockId::number(block_number)))) + } + + fn pending(&self) -> Result { + trace!(target: "alloy-provider", "Getting pending state provider"); + self.latest() + } + + fn pending_state_by_hash( + &self, + _block_hash: B256, + ) -> Result, ProviderError> { + // RPC provider doesn't support pending state by hash + Err(ProviderError::UnsupportedProvider) + } +} + +impl DatabaseProviderFactory for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type DB = DatabaseMock; + type ProviderRW = AlloyRethStateProvider; + type Provider = AlloyRethStateProvider; + + fn database_provider_ro(&self) -> Result { + // RPC provider returns a new state provider + let block_number = self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + })?; + + Ok(self.create_state_provider(BlockId::number(block_number))) + } + + fn database_provider_rw(&self) -> Result { + // RPC provider returns a new state provider + let block_number = self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + })?; + + Ok(self.create_state_provider(BlockId::number(block_number))) + } +} + +impl CanonChainTracker for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Header = alloy_consensus::Header; + fn on_forkchoice_update_received(&self, _update: &ForkchoiceState) { + // No-op for RPC provider + } + + fn last_received_update_timestamp(&self) -> Option { + None + } + + fn set_canonical_head(&self, _header: SealedHeader) { + // No-op for RPC provider + } + + fn set_safe(&self, _header: SealedHeader) { + // No-op for RPC provider + } + + fn set_finalized(&self, _header: SealedHeader) { + // No-op for RPC provider + } +} + +impl NodePrimitivesProvider for AlloyRethProvider +where + P: Send + Sync, + N: Send + Sync, + Node: NodeTypes, +{ + type Primitives = PrimitivesTy; +} + +impl CanonStateSubscriptions for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn subscribe_to_canonical_state(&self) -> CanonStateNotifications> { + trace!(target: "alloy-provider", "Subscribing to canonical state notifications"); + self.canon_state_notification.subscribe() + } +} + +impl ChainSpecProvider for AlloyRethProvider +where + P: Send + Sync, + N: Send + Sync, + Node: NodeTypes, + Node::ChainSpec: Default, +{ + type ChainSpec = Node::ChainSpec; + + fn chain_spec(&self) -> Arc { + self.chain_spec.clone() + } +} + +/// State provider implementation that fetches state via RPC +#[derive(Clone)] +pub struct AlloyRethStateProvider +where + Node: NodeTypes, +{ + /// The underlying Alloy provider + provider: P, + /// The block ID to fetch state at + block_id: BlockId, + /// Node types marker + node_types: std::marker::PhantomData, + /// Network marker + network: std::marker::PhantomData, + /// Cached chain spec (shared with parent provider) + chain_spec: Option>, +} + +impl std::fmt::Debug + for AlloyRethStateProvider +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AlloyRethStateProvider") + .field("provider", &self.provider) + .field("block_id", &self.block_id) + .finish() + } +} + +impl AlloyRethStateProvider { + /// Creates a new state provider for the given block + pub const fn new( + provider: P, + block_id: BlockId, + _primitives: std::marker::PhantomData, + ) -> Self { + Self { + provider, + block_id, + node_types: std::marker::PhantomData, + network: std::marker::PhantomData, + chain_spec: None, + } + } + + /// Creates a new state provider with a cached chain spec + pub const fn with_chain_spec( + provider: P, + block_id: BlockId, + chain_spec: Arc, + ) -> Self { + Self { + provider, + block_id, + node_types: std::marker::PhantomData, + network: std::marker::PhantomData, + chain_spec: Some(chain_spec), + } + } + + /// Helper function to execute async operations in a blocking context + fn block_on_async(&self, fut: F) -> T + where + F: Future, + { + tokio::task::block_in_place(move || Handle::current().block_on(fut)) + } + + /// Helper function to create a new state provider with a different block ID + fn with_block_id(&self, block_id: BlockId) -> Self { + Self { + provider: self.provider.clone(), + block_id, + node_types: self.node_types, + network: self.network, + chain_spec: self.chain_spec.clone(), + } + } + + /// Get account information from RPC + fn get_account(&self, address: Address) -> Result, ProviderError> + where + P: Provider + Clone + 'static, + N: Network, + { + self.block_on_async(async { + // Get account info in a single RPC call + let account_info = self + .provider + .get_account_info(address) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + // Only return account if it exists (has balance, nonce, or code) + if account_info.balance.is_zero() && + account_info.nonce == 0 && + account_info.code.is_empty() + { + Ok(None) + } else { + let bytecode = if account_info.code.is_empty() { + None + } else { + Some(Bytecode::new_raw(account_info.code)) + }; + + Ok(Some(Account { + balance: account_info.balance, + nonce: account_info.nonce, + bytecode_hash: bytecode.as_ref().map(|b| b.hash_slow()), + })) + } + }) + } +} + +impl StateProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn storage( + &self, + address: Address, + storage_key: StorageKey, + ) -> Result, ProviderError> { + self.block_on_async(async { + let value = self + .provider + .get_storage_at(address, storage_key.into()) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + if value.is_zero() { + Ok(None) + } else { + Ok(Some(value)) + } + }) + } + + fn bytecode_by_hash(&self, _code_hash: &B256) -> Result, ProviderError> { + // Cannot fetch bytecode by hash via RPC + Err(ProviderError::UnsupportedProvider) + } + + fn account_code(&self, addr: &Address) -> Result, ProviderError> { + self.block_on_async(async { + let code = self + .provider + .get_code_at(*addr) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + if code.is_empty() { + Ok(None) + } else { + Ok(Some(Bytecode::new_raw(code))) + } + }) + } + + fn account_balance(&self, addr: &Address) -> Result, ProviderError> { + self.get_account(*addr).map(|acc| acc.map(|a| a.balance)) + } + + fn account_nonce(&self, addr: &Address) -> Result, ProviderError> { + self.get_account(*addr).map(|acc| acc.map(|a| a.nonce)) + } +} + +impl AccountReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn basic_account(&self, address: &Address) -> Result, ProviderError> { + self.get_account(*address) + } +} + +impl StateRootProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn state_root(&self, _state: HashedPostState) -> Result { + // Return the state root from the block + self.block_on_async(async { + let block = self + .provider + .get_block(self.block_id) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::HeaderNotFound(0.into()))?; + + Ok(block.header().state_root()) + }) + } + + fn state_root_from_nodes(&self, _input: TrieInput) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn state_root_with_updates( + &self, + _state: HashedPostState, + ) -> Result<(B256, TrieUpdates), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn state_root_from_nodes_with_updates( + &self, + _input: TrieInput, + ) -> Result<(B256, TrieUpdates), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl StorageReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn plain_state_storages( + &self, + addresses_with_keys: impl IntoIterator)>, + ) -> Result)>, ProviderError> { + let mut results = Vec::new(); + + for (address, keys) in addresses_with_keys { + let mut values = Vec::new(); + for key in keys { + let value = self.storage(address, key)?.unwrap_or_default(); + values.push(reth_primitives::StorageEntry::new(key, value)); + } + results.push((address, values)); + } + + Ok(results) + } + + fn changed_storages_with_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Ok(BTreeMap::new()) + } + + fn changed_storages_and_blocks_with_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Ok(BTreeMap::new()) + } +} + +impl reth_storage_api::StorageRootProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn storage_root( + &self, + _address: Address, + _hashed_storage: reth_trie::HashedStorage, + ) -> Result { + // RPC doesn't provide storage root computation + Err(ProviderError::UnsupportedProvider) + } + + fn storage_proof( + &self, + _address: Address, + _slot: B256, + _hashed_storage: reth_trie::HashedStorage, + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn storage_multiproof( + &self, + _address: Address, + _slots: &[B256], + _hashed_storage: reth_trie::HashedStorage, + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } +} + +impl reth_storage_api::StateProofProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn proof( + &self, + _input: TrieInput, + _address: Address, + _slots: &[B256], + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn multiproof( + &self, + _input: TrieInput, + _targets: reth_trie::MultiProofTargets, + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn witness( + &self, + _input: TrieInput, + _target: HashedPostState, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl reth_storage_api::HashedPostStateProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn hashed_post_state(&self, _bundle_state: &revm::database::BundleState) -> HashedPostState { + // Return empty hashed post state for RPC provider + HashedPostState::default() + } +} + +impl StateReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Receipt = ReceiptTy; + + fn get_state( + &self, + _block: BlockNumber, + ) -> Result>, ProviderError> { + // RPC doesn't provide execution outcomes + Err(ProviderError::UnsupportedProvider) + } +} + +impl DBProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Tx = TxMock; + + fn tx_ref(&self) -> &Self::Tx { + // We can't use a static here since TxMock doesn't allow direct construction + // This is fine since we're just returning a mock transaction + unimplemented!("tx_ref not supported for RPC provider") + } + + fn tx_mut(&mut self) -> &mut Self::Tx { + unimplemented!("tx_mut not supported for RPC provider") + } + + fn into_tx(self) -> Self::Tx { + TxMock::default() + } + + fn prune_modes_ref(&self) -> &reth_prune_types::PruneModes { + unimplemented!("prune modes not supported for RPC provider") + } + + fn disable_long_read_transaction_safety(self) -> Self { + // No-op for RPC provider + self + } +} + +impl BlockNumReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn chain_info(&self) -> Result { + self.block_on_async(async { + let block = self + .provider + .get_block(self.block_id) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::HeaderNotFound(0.into()))?; + + Ok(ChainInfo { best_hash: block.header().hash(), best_number: block.header().number() }) + }) + } + + fn best_block_number(&self) -> Result { + self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + }) + } + + fn last_block_number(&self) -> Result { + self.best_block_number() + } + + fn block_number(&self, hash: B256) -> Result, ProviderError> { + self.block_on_async(async { + let block = + self.provider.get_block_by_hash(hash).await.map_err(ProviderError::other)?; + + Ok(block.map(|b| b.header().number())) + }) + } +} + +impl BlockHashReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_hash(&self, number: u64) -> Result, ProviderError> { + self.block_on_async(async { + let block = self + .provider + .get_block_by_number(number.into()) + .await + .map_err(ProviderError::other)?; + + Ok(block.map(|b| b.header().hash())) + }) + } + + fn canonical_hashes_range( + &self, + _start: BlockNumber, + _end: BlockNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockIdReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_number_for_id( + &self, + _block_id: BlockId, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block_num_hash(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn safe_block_num_hash(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn finalized_block_num_hash(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Block = BlockTy; + + fn find_block_by_hash( + &self, + _hash: B256, + _source: reth_provider::BlockSource, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block( + &self, + _id: alloy_rpc_types::BlockHashOrNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block(&self) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block_and_receipts( + &self, + ) -> Result, Vec)>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn recovered_block( + &self, + _id: alloy_rpc_types::BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_block_with_senders( + &self, + _id: alloy_rpc_types::BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_range( + &self, + _range: RangeInclusive, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_with_senders_range( + &self, + _range: RangeInclusive, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn recovered_block_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl TransactionsProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Transaction = TxTy; + + fn transaction_id(&self, _tx_hash: B256) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_id(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_id_unhashed( + &self, + _id: TxNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_hash(&self, _hash: B256) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_hash_with_meta( + &self, + _hash: B256, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_block(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_block( + &self, + _block: alloy_rpc_types::BlockHashOrNumber, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_block_range( + &self, + _range: impl RangeBounds, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn senders_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_sender(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ReceiptProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Receipt = ReceiptTy; + + fn receipt(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipt_by_hash(&self, _hash: B256) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_block( + &self, + _block: alloy_rpc_types::BlockHashOrNumber, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_block_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl HeaderProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Header = HeaderTy; + + fn header(&self, _block_hash: &BlockHash) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_by_number(&self, _num: BlockNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_td(&self, _hash: &BlockHash) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_td_by_number(&self, _number: BlockNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn headers_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_header( + &self, + _number: BlockNumber, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_headers_range( + &self, + _range: impl RangeBounds, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_headers_while( + &self, + _range: impl RangeBounds, + _predicate: impl FnMut(&SealedHeader>) -> bool, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl PruneCheckpointReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn get_prune_checkpoint( + &self, + _segment: PruneSegment, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn get_prune_checkpoints(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl StageCheckpointReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn get_stage_checkpoint(&self, _id: StageId) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn get_stage_checkpoint_progress( + &self, + _id: StageId, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn get_all_checkpoints(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ChangeSetReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn account_block_changeset( + &self, + _block_number: BlockNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl StateProviderFactory for AlloyRethStateProvider +where + P: Provider + Clone + 'static + Send + Sync, + Node: NodeTypes + 'static, + Node::ChainSpec: Send + Sync, + N: Network, + Self: Clone + 'static, +{ + fn latest(&self) -> Result { + Ok(Box::new(self.clone()) as StateProviderBox) + } + + fn state_by_block_id(&self, block_id: BlockId) -> Result { + Ok(Box::new(self.with_block_id(block_id))) + } + + fn state_by_block_number_or_tag( + &self, + number_or_tag: alloy_rpc_types::BlockNumberOrTag, + ) -> Result { + match number_or_tag { + alloy_rpc_types::BlockNumberOrTag::Latest => self.latest(), + alloy_rpc_types::BlockNumberOrTag::Pending => self.pending(), + alloy_rpc_types::BlockNumberOrTag::Number(num) => self.history_by_block_number(num), + _ => Err(ProviderError::UnsupportedProvider), + } + } + + fn history_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result { + Ok(Box::new(Self::new( + self.provider.clone(), + BlockId::number(block_number), + self.node_types, + ))) + } + + fn history_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + Ok(Box::new(self.with_block_id(BlockId::hash(block_hash)))) + } + + fn state_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + self.history_by_block_hash(block_hash) + } + + fn pending(&self) -> Result { + Ok(Box::new(self.clone())) + } + + fn pending_state_by_hash( + &self, + _block_hash: B256, + ) -> Result, ProviderError> { + // RPC provider doesn't support pending state by hash + Err(ProviderError::UnsupportedProvider) + } +} + +impl ChainSpecProvider for AlloyRethStateProvider +where + P: Send + Sync + std::fmt::Debug, + N: Send + Sync, + Node: NodeTypes, + Node::ChainSpec: Default, +{ + type ChainSpec = Node::ChainSpec; + + fn chain_spec(&self) -> Arc { + if let Some(chain_spec) = &self.chain_spec { + chain_spec.clone() + } else { + // Fallback for when chain_spec is not provided + Arc::new(Node::ChainSpec::default()) + } + } +} + +// Note: FullExecutionDataProvider is already implemented via the blanket implementation +// for types that implement both ExecutionDataProvider and BlockExecutionForkProvider + +impl StatsReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn count_entries(&self) -> Result { + Ok(0) + } +} + +impl BlockBodyIndicesProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_body_indices( + &self, + _num: u64, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_body_indices_range( + &self, + _range: RangeInclusive, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl NodePrimitivesProvider for AlloyRethStateProvider +where + P: Send + Sync + std::fmt::Debug, + N: Send + Sync, + Node: NodeTypes, +{ + type Primitives = PrimitivesTy; +} + +impl ChainStateBlockReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn last_finalized_block_number(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn last_safe_block_number(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ChainStateBlockWriter for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn save_finalized_block_number(&self, _block_number: BlockNumber) -> Result<(), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn save_safe_block_number(&self, _block_number: BlockNumber) -> Result<(), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +// Async database wrapper for revm compatibility +#[allow(dead_code)] +#[derive(Debug, Clone)] +struct AsyncDbWrapper { + provider: P, + block_id: BlockId, + network: std::marker::PhantomData, +} + +#[allow(dead_code)] +impl AsyncDbWrapper { + const fn new(provider: P, block_id: BlockId) -> Self { + Self { provider, block_id, network: std::marker::PhantomData } + } + + /// Helper function to execute async operations in a blocking context + fn block_on_async(&self, fut: F) -> T + where + F: Future, + { + tokio::task::block_in_place(move || Handle::current().block_on(fut)) + } +} + +impl revm::Database for AsyncDbWrapper +where + P: Provider + Clone + 'static, + N: Network, +{ + type Error = ProviderError; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + self.block_on_async(async { + let account_info = self + .provider + .get_account_info(address) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + // Only return account if it exists + if account_info.balance.is_zero() && + account_info.nonce == 0 && + account_info.code.is_empty() + { + Ok(None) + } else { + let code_hash = if account_info.code.is_empty() { + revm_primitives::KECCAK_EMPTY + } else { + revm_primitives::keccak256(&account_info.code) + }; + + Ok(Some(revm::state::AccountInfo { + balance: account_info.balance, + nonce: account_info.nonce, + code_hash, + code: if account_info.code.is_empty() { + None + } else { + Some(revm::bytecode::Bytecode::new_raw(account_info.code)) + }, + })) + } + }) + } + + fn code_by_hash(&mut self, _code_hash: B256) -> Result { + // Cannot fetch bytecode by hash via RPC + Ok(revm::bytecode::Bytecode::default()) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + let index = B256::from(index); + + self.block_on_async(async { + self.provider + .get_storage_at(address, index.into()) + .block_id(self.block_id) + .await + .map_err(ProviderError::other) + }) + } + + fn block_hash(&mut self, number: u64) -> Result { + self.block_on_async(async { + let block = self + .provider + .get_block_by_number(number.into()) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::HeaderNotFound(number.into()))?; + + Ok(block.header().hash()) + }) + } +} From 259a443bab8b6bb2fc83c77617f263a40f62536a Mon Sep 17 00:00:00 2001 From: Odinson Date: Mon, 16 Jun 2025 18:03:19 +0530 Subject: [PATCH 052/274] feat(network): Added Option for dispatching range updates to remote peer (#16776) Co-authored-by: Matthias Seitz --- crates/net/network/src/session/active.rs | 19 +++++++++++++++++++ crates/net/network/src/session/mod.rs | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 45a46081788..cd49344d8ac 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -43,10 +43,16 @@ use tokio_stream::wrappers::ReceiverStream; use tokio_util::sync::PollSender; use tracing::{debug, trace}; +/// The recommended interval at which a new range update should be sent to the remote peer. +/// +/// This is set to 120 seconds (2 minutes) as per the Ethereum specification for eth69. +pub(super) const RANGE_UPDATE_INTERVAL: Duration = Duration::from_secs(120); + // Constants for timeout updating. /// Minimum timeout value const MINIMUM_TIMEOUT: Duration = Duration::from_secs(2); + /// Maximum timeout value const MAXIMUM_TIMEOUT: Duration = INITIAL_REQUEST_TIMEOUT; /// How much the new measurements affect the current timeout (X percent) @@ -119,6 +125,9 @@ pub(crate) struct ActiveSession { /// The eth69 range info for the local node (this node). /// This represents the range of blocks that this node can serve to other peers. pub(crate) local_range_info: BlockRangeInfo, + /// Optional interval for sending periodic range updates to the remote peer (eth69+) + /// Recommended frequency is ~2 minutes per spec + pub(crate) range_update_interval: Option, } impl ActiveSession { @@ -707,6 +716,15 @@ impl Future for ActiveSession { } } + if let Some(interval) = &mut this.range_update_interval { + // queue in new range updates if the interval is ready + while interval.poll_tick(cx).is_ready() { + this.queued_outgoing.push_back( + EthMessage::BlockRangeUpdate(this.local_range_info.to_message()).into(), + ); + } + } + while this.internal_request_timeout_interval.poll_tick(cx).is_ready() { // check for timed out requests if this.check_timed_out_requests(Instant::now()) { @@ -1010,6 +1028,7 @@ mod tests { 1000, alloy_primitives::B256::ZERO, ), + range_update_interval: None, } } ev => { diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index f7487fa1c77..ec54dcedd87 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -48,6 +48,7 @@ use tokio_stream::wrappers::ReceiverStream; use tokio_util::sync::PollSender; use tracing::{debug, instrument, trace}; +use crate::session::active::RANGE_UPDATE_INTERVAL; pub use conn::EthRlpxConnection; pub use handle::{ ActiveSessionHandle, ActiveSessionMessage, PendingSessionEvent, PendingSessionHandle, @@ -532,6 +533,14 @@ impl SessionManager { // negotiated version let version = conn.version(); + // Configure the interval at which the range information is updated, starting with + // ETH69 + let range_update_interval = (conn.version() >= EthVersion::Eth69).then(|| { + let mut interval = tokio::time::interval(RANGE_UPDATE_INTERVAL); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + interval + }); + let session = ActiveSession { next_id: 0, remote_peer_id: peer_id, @@ -556,6 +565,7 @@ impl SessionManager { terminate_message: None, range_info: None, local_range_info: self.local_range_info.clone(), + range_update_interval, }; self.spawn(session); From a8522e6a2577c8ccef768e9320e945dca2ceb20d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 16 Jun 2025 15:14:42 +0200 Subject: [PATCH 053/274] fix: validate BlockRangeUpdate message per devp2p spec (#16826) --- crates/net/eth-wire-types/src/message.rs | 5 ++++- crates/net/network/src/session/active.rs | 13 ++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index 7c47618b5dc..ac2d099cff8 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -16,7 +16,7 @@ use crate::{ status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives, RawCapabilityMessage, Receipts69, SharedTransactions, }; -use alloc::{boxed::Box, sync::Arc}; +use alloc::{boxed::Box, string::String, sync::Arc}; use alloy_primitives::{ bytes::{Buf, BufMut}, Bytes, @@ -37,6 +37,9 @@ pub enum MessageError { /// Thrown when rlp decoding a message failed. #[error("RLP error: {0}")] RlpError(#[from] alloy_rlp::Error), + /// Other message error with custom message + #[error("{0}")] + Other(String), } /// An `eth` protocol message, containing a message ID and payload. diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index cd49344d8ac..b627d0c3ab8 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -24,7 +24,7 @@ use futures::{stream::Fuse, SinkExt, StreamExt}; use metrics::Gauge; use reth_eth_wire::{ errors::{EthHandshakeError, EthStreamError}, - message::{EthBroadcastMessage, RequestPair}, + message::{EthBroadcastMessage, MessageError, RequestPair}, Capabilities, DisconnectP2P, DisconnectReason, EthMessage, NetworkPrimitives, NewBlockPayload, }; use reth_eth_wire_types::RawCapabilityMessage; @@ -282,6 +282,17 @@ impl ActiveSession { on_response!(resp, GetReceipts) } EthMessage::BlockRangeUpdate(msg) => { + // Validate that earliest <= latest according to the spec + if msg.earliest > msg.latest { + return OnIncomingMessageOutcome::BadMessage { + error: EthStreamError::InvalidMessage(MessageError::Other(format!( + "invalid block range: earliest ({}) > latest ({})", + msg.earliest, msg.latest + ))), + message: EthMessage::BlockRangeUpdate(msg), + }; + } + if let Some(range_info) = self.range_info.as_ref() { range_info.update(msg.earliest, msg.latest, msg.latest_hash); } From 3e0960cb11d969fb41b07f472b71371a490fb6e9 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:17:00 +0200 Subject: [PATCH 054/274] perf: reuse accounts trie in payload processing (#16181) Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- crates/engine/tree/benches/state_root_task.rs | 2 +- crates/engine/tree/src/tree/mod.rs | 5 +- .../tree/src/tree/payload_processor/mod.rs | 20 ++- .../src/tree/payload_processor/sparse_trie.rs | 46 +++++- crates/trie/sparse/src/state.rs | 28 +++- crates/trie/sparse/src/trie.rs | 156 +++++++++++++++--- 6 files changed, 221 insertions(+), 36 deletions(-) diff --git a/crates/engine/tree/benches/state_root_task.rs b/crates/engine/tree/benches/state_root_task.rs index d705bfecd8f..1eeb7a47f50 100644 --- a/crates/engine/tree/benches/state_root_task.rs +++ b/crates/engine/tree/benches/state_root_task.rs @@ -227,7 +227,7 @@ fn bench_state_root(c: &mut Criterion) { (genesis_hash, payload_processor, provider, state_updates) }, - |(genesis_hash, payload_processor, provider, state_updates)| { + |(genesis_hash, mut payload_processor, provider, state_updates)| { black_box({ let mut handle = payload_processor.spawn( Default::default(), diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 7b8454175ec..26cc096535f 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2283,7 +2283,7 @@ where // background task or try to compute it in parallel if use_state_root_task { match handle.state_root() { - Ok(StateRootComputeOutcome { state_root, trie_updates }) => { + Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) => { let elapsed = execution_finish.elapsed(); info!(target: "engine::tree", ?state_root, ?elapsed, "State root task finished"); // we double check the state root here for good measure @@ -2297,6 +2297,9 @@ where "State root task returned incorrect state root" ); } + + // hold on to the sparse trie for the next payload + self.payload_processor.set_sparse_trie(trie); } Err(error) => { debug!(target: "engine::tree", %error, "Background parallel state root computation failed"); diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 5c782fbd4bb..118a77521b7 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -28,6 +28,7 @@ use reth_trie_parallel::{ proof_task::{ProofTaskCtx, ProofTaskManager}, root::ParallelStateRootError, }; +use reth_trie_sparse::SparseTrieState; use std::{ collections::VecDeque, sync::{ @@ -67,6 +68,9 @@ where precompile_cache_disabled: bool, /// Precompile cache map. precompile_cache_map: PrecompileCacheMap>, + /// A sparse trie, kept around to be used for the state root computation so that allocations + /// can be minimized. + sparse_trie: Option, _marker: std::marker::PhantomData, } @@ -91,6 +95,7 @@ where evm_config, precompile_cache_disabled: config.precompile_cache_disabled(), precompile_cache_map, + sparse_trie: None, _marker: Default::default(), } } @@ -134,7 +139,7 @@ where /// This returns a handle to await the final state root and to interact with the tasks (e.g. /// canceling) pub fn spawn

( - &self, + &mut self, header: SealedHeaderFor, transactions: VecDeque>, provider_builder: StateProviderBuilder, @@ -191,11 +196,15 @@ where multi_proof_task.run(); }); - let mut sparse_trie_task = SparseTrieTask::new( + // take the sparse trie if it was set + let sparse_trie = self.sparse_trie.take(); + + let mut sparse_trie_task = SparseTrieTask::new_with_stored_trie( self.executor.clone(), sparse_trie_rx, proof_task.handle(), self.trie_metrics.clone(), + sparse_trie, ); // wire the sparse trie to the state root response receiver @@ -241,6 +250,11 @@ where PayloadHandle { to_multi_proof: None, prewarm_handle, state_root: None } } + /// Sets the sparse trie to be kept around for the state root computation. + pub(super) fn set_sparse_trie(&mut self, sparse_trie: SparseTrieState) { + self.sparse_trie = Some(sparse_trie); + } + /// Spawn prewarming optionally wired to the multiproof task for target updates. fn spawn_caching_with

( &self, @@ -566,7 +580,7 @@ mod tests { } } - let payload_processor = PayloadProcessor::::new( + let mut payload_processor = PayloadProcessor::::new( WorkloadExecutor::default(), EthEvmConfig::new(factory.chain_spec()), &TreeConfig::default(), diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs index 93f00491090..c8de07c1ec5 100644 --- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs @@ -11,7 +11,7 @@ use reth_trie_parallel::root::ParallelStateRootError; use reth_trie_sparse::{ blinded::{BlindedProvider, BlindedProviderFactory}, errors::{SparseStateTrieResult, SparseTrieErrorKind}, - SparseStateTrie, + SparseStateTrie, SparseTrieState, }; use std::{ sync::mpsc, @@ -63,6 +63,43 @@ where } } + /// Creates a new sparse trie, populating the accounts trie with the given cleared + /// `SparseTrieState` if it exists. + pub(super) fn new_with_stored_trie( + executor: WorkloadExecutor, + updates: mpsc::Receiver, + blinded_provider_factory: BPF, + trie_metrics: MultiProofTaskMetrics, + sparse_trie_state: Option, + ) -> Self { + if let Some(sparse_trie_state) = sparse_trie_state { + Self::with_accounts_trie( + executor, + updates, + blinded_provider_factory, + trie_metrics, + sparse_trie_state, + ) + } else { + Self::new(executor, updates, blinded_provider_factory, trie_metrics) + } + } + + /// Creates a new sparse trie task, using the given cleared `SparseTrieState` for the accounts + /// trie. + pub(super) fn with_accounts_trie( + executor: WorkloadExecutor, + updates: mpsc::Receiver, + blinded_provider_factory: BPF, + metrics: MultiProofTaskMetrics, + sparse_trie_state: SparseTrieState, + ) -> Self { + let mut trie = SparseStateTrie::new(blinded_provider_factory).with_updates(true); + trie.populate_from(sparse_trie_state); + + Self { executor, updates, metrics, trie } + } + /// Runs the sparse trie task to completion. /// /// This waits for new incoming [`SparseTrieUpdate`]. @@ -109,7 +146,10 @@ where self.metrics.sparse_trie_final_update_duration_histogram.record(start.elapsed()); self.metrics.sparse_trie_total_duration_histogram.record(now.elapsed()); - Ok(StateRootComputeOutcome { state_root, trie_updates }) + // take the account trie + let trie = self.trie.take_cleared_account_trie_state(); + + Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) } } @@ -121,6 +161,8 @@ pub struct StateRootComputeOutcome { pub state_root: B256, /// The trie updates. pub trie_updates: TrieUpdates, + /// The account state trie. + pub trie: SparseTrieState, } /// Updates the sparse trie with the given proofs and state, and returns the elapsed time. diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 39e305f4981..dc8ac3506f8 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -1,6 +1,6 @@ use crate::{ blinded::{BlindedProvider, BlindedProviderFactory, DefaultBlindedProviderFactory}, - LeafLookup, RevealedSparseTrie, SparseTrie, TrieMasks, + LeafLookup, RevealedSparseTrie, SparseTrie, SparseTrieState, TrieMasks, }; use alloc::{collections::VecDeque, vec::Vec}; use alloy_primitives::{ @@ -107,6 +107,19 @@ impl SparseStateTrie { self.revealed_account_paths.contains(&Nibbles::unpack(account)) } + /// Uses the input `SparseTrieState` to populate the backing data structures in the `state` + /// trie. + pub fn populate_from(&mut self, trie: SparseTrieState) { + if let Some(new_trie) = self.state.as_revealed_mut() { + new_trie.use_allocated_state(trie); + } else { + self.state = SparseTrie::revealed_with_provider( + self.provider_factory.account_node_provider(), + trie, + ) + } + } + /// Was the account witness for `address` complete? pub fn check_valid_account_witness(&self, address: B256) -> bool { let path = Nibbles::unpack(address); @@ -343,7 +356,7 @@ impl SparseStateTrie { ) -> SparseStateTrieResult<()> { let FilteredProofNodes { nodes, - new_nodes, + new_nodes: _, total_nodes: _total_nodes, skipped_nodes: _skipped_nodes, } = filter_revealed_nodes(account_subtree, &self.revealed_account_paths)?; @@ -366,9 +379,6 @@ impl SparseStateTrie { self.retain_updates, )?; - // Reserve the capacity for new nodes ahead of time. - trie.reserve_nodes(new_nodes); - // Reveal the remaining proof nodes. for (path, node) in account_nodes { let (hash_mask, tree_mask) = if let TrieNode::Branch(_) = node { @@ -650,7 +660,7 @@ impl SparseStateTrie { &mut self, ) -> SparseStateTrieResult<&mut RevealedSparseTrie> { match self.state { - SparseTrie::Blind => { + SparseTrie::Blind | SparseTrie::AllocatedEmpty { .. } => { let (root_node, hash_mask, tree_mask) = self .provider_factory .account_node_provider() @@ -868,6 +878,12 @@ impl SparseStateTrie { storage_trie.remove_leaf(slot)?; Ok(()) } + + /// Clears and takes the account trie. + pub fn take_cleared_account_trie_state(&mut self) -> SparseTrieState { + let trie = core::mem::take(&mut self.state); + trie.cleared() + } } /// Result of [`filter_revealed_nodes`]. diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 5759b5d4b89..8636383a05b 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -52,6 +52,19 @@ impl TrieMasks { } } +/// A struct for keeping the hashmaps from `RevealedSparseTrie`. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct SparseTrieState { + /// Map from a path (nibbles) to its corresponding sparse trie node. + nodes: HashMap, + /// When a branch is set, the corresponding child subtree is stored in the database. + branch_node_tree_masks: HashMap, + /// When a bit is set, the corresponding child is stored as a hash in the database. + branch_node_hash_masks: HashMap, + /// Map from leaf key paths to their values. + values: HashMap>, +} + /// A sparse trie that is either in a "blind" state (no nodes are revealed, root node hash is /// unknown) or in a "revealed" state (root node has been revealed and the trie can be updated). /// @@ -64,8 +77,15 @@ impl TrieMasks { /// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted /// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie. /// This is what gives rise to the notion of a "sparse" trie. -#[derive(PartialEq, Eq, Default)] +#[derive(PartialEq, Eq, Default, Clone)] pub enum SparseTrie

{ + /// This is a variant that can be used to store a previously allocated trie. In these cases, + /// the trie will still be treated as blind, but the allocated trie will be reused if the trie + /// becomes revealed. + AllocatedEmpty { + /// This is the state of the allocated trie. + allocated: SparseTrieState, + }, /// The trie is blind -- no nodes have been revealed /// /// This is the default state. In this state, @@ -83,6 +103,7 @@ pub enum SparseTrie

{ impl

fmt::Debug for SparseTrie

{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::AllocatedEmpty { .. } => write!(f, "AllocatedEmpty"), Self::Blind => write!(f, "Blind"), Self::Revealed(revealed) => write!(f, "Revealed({revealed:?})"), } @@ -184,17 +205,54 @@ impl

SparseTrie

{ masks: TrieMasks, retain_updates: bool, ) -> SparseTrieResult<&mut RevealedSparseTrie

> { + // we take the allocated state here, which will make sure we are either `Blind` or + // `Revealed`, and giving us the allocated state if we were `AllocatedEmpty`. + let allocated = self.take_allocated_state(); + + // if `Blind`, we initialize the revealed trie if self.is_blind() { - *self = Self::Revealed(Box::new(RevealedSparseTrie::from_provider_and_root( - provider, - root, - masks, - retain_updates, - )?)) + let mut revealed = + RevealedSparseTrie::from_provider_and_root(provider, root, masks, retain_updates)?; + + // If we had an allocated state, we use its maps internally. use_allocated_state copies + // over any information we had from revealing. + if let Some(allocated) = allocated { + revealed.use_allocated_state(allocated); + } + + *self = Self::Revealed(Box::new(revealed)); } Ok(self.as_revealed_mut().unwrap()) } + /// Take the allocated state if this is `AllocatedEmpty`, otherwise returns `None`. + /// + /// Converts this `SparseTrie` into `Blind` if this was `AllocatedEmpty`. + pub fn take_allocated_state(&mut self) -> Option { + if let Self::AllocatedEmpty { allocated } = self { + let state = core::mem::take(allocated); + *self = Self::Blind; + Some(state) + } else { + None + } + } + + /// Creates a new trie with the given provider and sparse trie state. + pub fn revealed_with_provider(provider: P, revealed_state: SparseTrieState) -> Self { + let revealed = RevealedSparseTrie { + provider, + nodes: revealed_state.nodes, + branch_node_tree_masks: revealed_state.branch_node_tree_masks, + branch_node_hash_masks: revealed_state.branch_node_hash_masks, + values: revealed_state.values, + prefix_set: PrefixSetMut::default(), + updates: None, + rlp_buf: Vec::new(), + }; + Self::Revealed(Box::new(revealed)) + } + /// Wipes the trie by removing all nodes and values, /// and resetting the trie to only contain an empty root node. /// @@ -205,6 +263,16 @@ impl

SparseTrie

{ Ok(()) } + /// Returns a `SparseTrieState` obtained by clearing the sparse trie state and reusing the + /// allocated state if it was `AllocatedEmpty` or `Revealed`. + pub fn cleared(self) -> SparseTrieState { + match self { + Self::Revealed(revealed) => revealed.cleared_state(), + Self::AllocatedEmpty { allocated } => allocated, + Self::Blind => Default::default(), + } + } + /// Calculates the root hash of the trie. /// /// This will update any remaining dirty nodes before computing the root hash. @@ -481,6 +549,37 @@ impl

RevealedSparseTrie

{ } } + /// Sets the fields of this `RevealedSparseTrie` to the fields of the input + /// `SparseTrieState`. + /// + /// This is meant for reusing the allocated maps contained in the `SparseTrieState`. + /// + /// Copies over any existing nodes, branch masks, and values. + pub fn use_allocated_state(&mut self, mut other: SparseTrieState) { + for (path, node) in self.nodes.drain() { + other.nodes.insert(path, node); + } + for (path, mask) in self.branch_node_tree_masks.drain() { + other.branch_node_tree_masks.insert(path, mask); + } + for (path, mask) in self.branch_node_hash_masks.drain() { + other.branch_node_hash_masks.insert(path, mask); + } + for (path, value) in self.values.drain() { + other.values.insert(path, value); + } + + self.nodes = other.nodes; + self.branch_node_tree_masks = other.branch_node_tree_masks; + self.branch_node_hash_masks = other.branch_node_hash_masks; + self.values = other.values; + } + + /// Set the provider for the trie. + pub fn set_provider(&mut self, provider: P) { + self.provider = provider; + } + /// Configures the trie to retain information about updates. /// /// If `retain_updates` is true, the trie will record branch node updates and deletions. @@ -839,6 +938,33 @@ impl

RevealedSparseTrie

{ self.updates = self.updates.is_some().then(SparseTrieUpdates::wiped); } + /// This clears all data structures in the sparse trie, keeping the backing data structures + /// allocated. + /// + /// This is useful for reusing the trie without needing to reallocate memory. + pub fn clear(&mut self) { + self.nodes.clear(); + self.branch_node_tree_masks.clear(); + self.branch_node_hash_masks.clear(); + self.values.clear(); + self.prefix_set.clear(); + if let Some(updates) = self.updates.as_mut() { + updates.clear() + } + self.rlp_buf.clear(); + } + + /// Returns the cleared `SparseTrieState` for this `RevealedSparseTrie`. + pub fn cleared_state(mut self) -> SparseTrieState { + self.clear(); + SparseTrieState { + nodes: self.nodes, + branch_node_tree_masks: self.branch_node_tree_masks, + branch_node_hash_masks: self.branch_node_hash_masks, + values: self.values, + } + } + /// Calculates and returns the root hash of the trie. /// /// Before computing the hash, this function processes any remaining (dirty) nodes by @@ -1325,22 +1451,6 @@ pub enum LeafLookup { } impl RevealedSparseTrie

{ - /// This clears all data structures in the sparse trie, keeping the backing data structures - /// allocated. - /// - /// This is useful for reusing the trie without needing to reallocate memory. - pub fn clear(&mut self) { - self.nodes.clear(); - self.branch_node_tree_masks.clear(); - self.branch_node_hash_masks.clear(); - self.values.clear(); - self.prefix_set.clear(); - if let Some(updates) = self.updates.as_mut() { - updates.clear() - } - self.rlp_buf.clear(); - } - /// Attempts to find a leaf node at the specified path. /// /// This method traverses the trie from the root down to the given path, checking From a1a1c0c6bccd32cfd512462085400f0dfa0a17fd Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 16 Jun 2025 16:18:58 +0200 Subject: [PATCH 055/274] chore: update op-alloy deps to 0.18.2 (#16827) --- Cargo.lock | 20 ++++++------- Cargo.toml | 10 +++---- .../primitives/src/transaction/mod.rs | 29 +------------------ crates/primitives-traits/src/extended.rs | 24 +++++++++++++-- examples/custom-node/src/primitives/tx.rs | 8 +++-- 5 files changed, 44 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d6791b90888..2adedd3f23a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5925,9 +5925,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931e9d8513773478289283064ee6b49b40247b57e2884782c5a1f01f6d76f93a" +checksum = "1f83ed68f4d32a807e25e8efa5ea4ca432bbbcffce3223571a0f92fc6d4941fc" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5951,9 +5951,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a41ad5c040cf34e09cbd8278fb965f3c9c59ad80c4af8be7f2146697c8001e" +checksum = "734b2d75375cf1ddff6d71be332021383bb04dd2ab753a29892312e4b3ab387d" dependencies = [ "alloy-consensus", "alloy-network", @@ -5967,9 +5967,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be4e3667dc3ced203d0d5356fa79817852d9deabbc697a8d2b7b4f4204d9e5b" +checksum = "3cfd25bef0af26c85011e98ca9be0713f855d47e110c6be4a36eebefa1b002f2" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5977,9 +5977,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a3b08a5ffa27eb527dbde5b2452a9f15c142325e977bfacdaaf56b43de7a25" +checksum = "59bd889eddcb7d4faec487a6c0f67127218c9b2daadc061177164d6cac552c38" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5996,9 +5996,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3524953ce0213b0c654c05ddc93dcb5676347ad05a84a5a25c140cb0c5807bc9" +checksum = "708c2101fb6ad607ebaf13ee830550c782d551557fd8d1f5f53bba60ac39a9ea" dependencies = [ "alloy-consensus", "alloy-eips", diff --git a/Cargo.toml b/Cargo.toml index 89086c7027c..4f44f7d0cae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -505,11 +505,11 @@ alloy-transport-ws = { version = "1.0.9", default-features = false } # op alloy-op-evm = { version = "0.11", default-features = false } alloy-op-hardforks = "0.2.2" -op-alloy-rpc-types = { version = "0.18", default-features = false } -op-alloy-rpc-types-engine = { version = "0.18", default-features = false } -op-alloy-network = { version = "0.18", default-features = false } -op-alloy-consensus = { version = "0.18", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.18", default-features = false } +op-alloy-rpc-types = { version = "0.18.2", default-features = false } +op-alloy-rpc-types-engine = { version = "0.18.2", default-features = false } +op-alloy-network = { version = "0.18.2", default-features = false } +op-alloy-consensus = { version = "0.18.2", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.18.2", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # misc diff --git a/crates/optimism/primitives/src/transaction/mod.rs b/crates/optimism/primitives/src/transaction/mod.rs index d24acaa08b7..3284b67fcbf 100644 --- a/crates/optimism/primitives/src/transaction/mod.rs +++ b/crates/optimism/primitives/src/transaction/mod.rs @@ -6,34 +6,7 @@ mod tx_type; #[cfg(test)] mod signed; -pub use op_alloy_consensus::{OpTxType, OpTypedTransaction}; -use reth_primitives_traits::Extended; +pub use op_alloy_consensus::{OpTransaction, OpTxType, OpTypedTransaction}; /// Signed transaction. pub type OpTransactionSigned = op_alloy_consensus::OpTxEnvelope; - -/// A trait that represents an optimism transaction, mainly used to indicate whether or not the -/// transaction is a deposit transaction. -pub trait OpTransaction { - /// Whether or not the transaction is a dpeosit transaction. - fn is_deposit(&self) -> bool; -} - -impl OpTransaction for op_alloy_consensus::OpTxEnvelope { - fn is_deposit(&self) -> bool { - Self::is_deposit(self) - } -} - -impl OpTransaction for Extended -where - B: OpTransaction, - T: OpTransaction, -{ - fn is_deposit(&self) -> bool { - match self { - Self::BuiltIn(b) => b.is_deposit(), - Self::Other(t) => t.is_deposit(), - } - } -} diff --git a/crates/primitives-traits/src/extended.rs b/crates/primitives-traits/src/extended.rs index 94c35d0190b..69aa3efa63c 100644 --- a/crates/primitives-traits/src/extended.rs +++ b/crates/primitives-traits/src/extended.rs @@ -274,8 +274,28 @@ impl From> for Extended OpTransaction for Extended + where + B: OpTransaction, + T: OpTransaction, + { + fn is_deposit(&self) -> bool { + match self { + Self::BuiltIn(b) => b.is_deposit(), + Self::Other(t) => t.is_deposit(), + } + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + Self::BuiltIn(b) => b.as_deposit(), + Self::Other(t) => t.as_deposit(), + } + } + } impl TryFrom> for Extended { type Error = >::Error; diff --git a/examples/custom-node/src/primitives/tx.rs b/examples/custom-node/src/primitives/tx.rs index ce365b2c405..06840399528 100644 --- a/examples/custom-node/src/primitives/tx.rs +++ b/examples/custom-node/src/primitives/tx.rs @@ -8,9 +8,9 @@ use alloy_consensus::{ SignableTransaction, Signed, Transaction, }; use alloy_eips::{eip2718::Eip2718Result, Decodable2718, Encodable2718, Typed2718}; -use alloy_primitives::{keccak256, Signature, TxHash}; +use alloy_primitives::{keccak256, Sealed, Signature, TxHash}; use alloy_rlp::{BufMut, Decodable, Encodable, Result as RlpResult}; -use op_alloy_consensus::OpTxEnvelope; +use op_alloy_consensus::{OpTxEnvelope, TxDeposit}; use reth_codecs::{ alloy::transaction::{FromTxCompact, ToTxCompact}, Compact, @@ -243,4 +243,8 @@ impl OpTransaction for CustomTransactionEnvelope { fn is_deposit(&self) -> bool { false } + + fn as_deposit(&self) -> Option<&Sealed> { + None + } } From d12a9788d9d58d18cb7fc9a6afa533fda9a97d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 16 Jun 2025 18:23:09 +0200 Subject: [PATCH 056/274] feat(rpc): Add `FromConsensusTx` and implement `IntoRpcTx` for generic RPC transaction (#16784) --- .../rpc/rpc-types-compat/src/transaction.rs | 75 +++++++++++++------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 06f50aebf8b..3802e102f99 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -2,7 +2,7 @@ use crate::fees::{CallFees, CallFeesError}; use alloy_consensus::{ - error::ValueError, transaction::Recovered, EthereumTxEnvelope, Extended, SignableTransaction, + error::ValueError, transaction::Recovered, EthereumTxEnvelope, SignableTransaction, Transaction as ConsensusTransaction, TxEip4844, }; use alloy_network::Network; @@ -82,38 +82,63 @@ pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { ) -> Result; } -/// Converts `self` into `T`. +/// Converts `self` into `T`. The opposite of [`FromConsensusTx`]. /// /// Should create an RPC transaction response object based on a consensus transaction, its signer -/// [`Address`] and an additional context. +/// [`Address`] and an additional context [`IntoRpcTx::TxInfo`]. +/// +/// Avoid implementing [`IntoRpcTx`] and use [`FromConsensusTx`] instead. Implementing it +/// automatically provides an implementation of [`IntoRpcTx`] thanks to the blanket implementation +/// in this crate. +/// +/// Prefer using [`IntoRpcTx`] over [`FromConsensusTx`] when specifying trait bounds on a generic +/// function to ensure that types that only implement [`IntoRpcTx`] can be used as well. pub trait IntoRpcTx { /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some /// implementation specific extra information. type TxInfo; - /// Performs the conversion. + /// Performs the conversion consuming `self` with `signer` and `tx_info`. See [`IntoRpcTx`] + /// for details. fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> T; } -impl IntoRpcTx> for Extended -where - BuiltIn: ConsensusTransaction, - Other: ConsensusTransaction, -{ +/// Converts `T` into `self`. It is reciprocal of [`IntoRpcTx`]. +/// +/// Should create an RPC transaction response object based on a consensus transaction, its signer +/// [`Address`] and an additional context [`FromConsensusTx::TxInfo`]. +/// +/// Prefer implementing [`FromConsensusTx`] over [`IntoRpcTx`] because it automatically provides an +/// implementation of [`IntoRpcTx`] thanks to the blanket implementation in this crate. +/// +/// Prefer using [`IntoRpcTx`] over using [`FromConsensusTx`] when specifying trait bounds on a +/// generic function. This way, types that directly implement [`IntoRpcTx`] can be used as arguments +/// as well. +pub trait FromConsensusTx { + /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some + /// implementation specific extra information. + type TxInfo; + + /// Performs the conversion consuming `tx` with `signer` and `tx_info`. See [`FromConsensusTx`] + /// for details. + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self; +} + +impl FromConsensusTx for Transaction { type TxInfo = TransactionInfo; - fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> Transaction { + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { let TransactionInfo { block_hash, block_number, index: transaction_index, base_fee, .. } = tx_info; let effective_gas_price = base_fee .map(|base_fee| { - self.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 + tx.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 }) - .unwrap_or_else(|| self.max_fee_per_gas()); + .unwrap_or_else(|| tx.max_fee_per_gas()); - Transaction { - inner: Recovered::new_unchecked(self, signer), + Self { + inner: Recovered::new_unchecked(tx, signer), block_hash, block_number, transaction_index, @@ -122,6 +147,18 @@ where } } +impl IntoRpcTx for ConsensusTx +where + ConsensusTx: ConsensusTransaction, + RpcTx: FromConsensusTx, +{ + type TxInfo = RpcTx::TxInfo; + + fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> RpcTx { + RpcTx::from_consensus_tx(self, signer, tx_info) + } +} + /// Converts `self` into `T`. /// /// Should create a fake transaction for simulation using [`TransactionRequest`]. @@ -189,15 +226,11 @@ pub fn try_into_op_tx_info>( Ok(OpTransactionInfo::new(tx_info, deposit_meta)) } -impl IntoRpcTx for OpTxEnvelope { +impl FromConsensusTx for op_alloy_rpc_types::Transaction { type TxInfo = OpTransactionInfo; - fn into_rpc_tx( - self, - signer: Address, - tx_info: OpTransactionInfo, - ) -> op_alloy_rpc_types::Transaction { - op_alloy_rpc_types::Transaction::from_transaction(self.with_signer(signer), tx_info) + fn from_consensus_tx(tx: OpTxEnvelope, signer: Address, tx_info: Self::TxInfo) -> Self { + Self::from_transaction(tx.with_signer(signer), tx_info) } } From c4da80abaa5fa7285f3593e9d522977655bf672c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:16:49 -0400 Subject: [PATCH 057/274] revert: "perf: reuse accounts trie in payload processing (#16181)" (#16834) --- crates/engine/tree/benches/state_root_task.rs | 2 +- crates/engine/tree/src/tree/mod.rs | 5 +- .../tree/src/tree/payload_processor/mod.rs | 20 +-- .../src/tree/payload_processor/sparse_trie.rs | 46 +----- crates/trie/sparse/src/state.rs | 28 +--- crates/trie/sparse/src/trie.rs | 156 +++--------------- 6 files changed, 36 insertions(+), 221 deletions(-) diff --git a/crates/engine/tree/benches/state_root_task.rs b/crates/engine/tree/benches/state_root_task.rs index 1eeb7a47f50..d705bfecd8f 100644 --- a/crates/engine/tree/benches/state_root_task.rs +++ b/crates/engine/tree/benches/state_root_task.rs @@ -227,7 +227,7 @@ fn bench_state_root(c: &mut Criterion) { (genesis_hash, payload_processor, provider, state_updates) }, - |(genesis_hash, mut payload_processor, provider, state_updates)| { + |(genesis_hash, payload_processor, provider, state_updates)| { black_box({ let mut handle = payload_processor.spawn( Default::default(), diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 26cc096535f..7b8454175ec 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2283,7 +2283,7 @@ where // background task or try to compute it in parallel if use_state_root_task { match handle.state_root() { - Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) => { + Ok(StateRootComputeOutcome { state_root, trie_updates }) => { let elapsed = execution_finish.elapsed(); info!(target: "engine::tree", ?state_root, ?elapsed, "State root task finished"); // we double check the state root here for good measure @@ -2297,9 +2297,6 @@ where "State root task returned incorrect state root" ); } - - // hold on to the sparse trie for the next payload - self.payload_processor.set_sparse_trie(trie); } Err(error) => { debug!(target: "engine::tree", %error, "Background parallel state root computation failed"); diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 118a77521b7..5c782fbd4bb 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -28,7 +28,6 @@ use reth_trie_parallel::{ proof_task::{ProofTaskCtx, ProofTaskManager}, root::ParallelStateRootError, }; -use reth_trie_sparse::SparseTrieState; use std::{ collections::VecDeque, sync::{ @@ -68,9 +67,6 @@ where precompile_cache_disabled: bool, /// Precompile cache map. precompile_cache_map: PrecompileCacheMap>, - /// A sparse trie, kept around to be used for the state root computation so that allocations - /// can be minimized. - sparse_trie: Option, _marker: std::marker::PhantomData, } @@ -95,7 +91,6 @@ where evm_config, precompile_cache_disabled: config.precompile_cache_disabled(), precompile_cache_map, - sparse_trie: None, _marker: Default::default(), } } @@ -139,7 +134,7 @@ where /// This returns a handle to await the final state root and to interact with the tasks (e.g. /// canceling) pub fn spawn

( - &mut self, + &self, header: SealedHeaderFor, transactions: VecDeque>, provider_builder: StateProviderBuilder, @@ -196,15 +191,11 @@ where multi_proof_task.run(); }); - // take the sparse trie if it was set - let sparse_trie = self.sparse_trie.take(); - - let mut sparse_trie_task = SparseTrieTask::new_with_stored_trie( + let mut sparse_trie_task = SparseTrieTask::new( self.executor.clone(), sparse_trie_rx, proof_task.handle(), self.trie_metrics.clone(), - sparse_trie, ); // wire the sparse trie to the state root response receiver @@ -250,11 +241,6 @@ where PayloadHandle { to_multi_proof: None, prewarm_handle, state_root: None } } - /// Sets the sparse trie to be kept around for the state root computation. - pub(super) fn set_sparse_trie(&mut self, sparse_trie: SparseTrieState) { - self.sparse_trie = Some(sparse_trie); - } - /// Spawn prewarming optionally wired to the multiproof task for target updates. fn spawn_caching_with

( &self, @@ -580,7 +566,7 @@ mod tests { } } - let mut payload_processor = PayloadProcessor::::new( + let payload_processor = PayloadProcessor::::new( WorkloadExecutor::default(), EthEvmConfig::new(factory.chain_spec()), &TreeConfig::default(), diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs index c8de07c1ec5..93f00491090 100644 --- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs @@ -11,7 +11,7 @@ use reth_trie_parallel::root::ParallelStateRootError; use reth_trie_sparse::{ blinded::{BlindedProvider, BlindedProviderFactory}, errors::{SparseStateTrieResult, SparseTrieErrorKind}, - SparseStateTrie, SparseTrieState, + SparseStateTrie, }; use std::{ sync::mpsc, @@ -63,43 +63,6 @@ where } } - /// Creates a new sparse trie, populating the accounts trie with the given cleared - /// `SparseTrieState` if it exists. - pub(super) fn new_with_stored_trie( - executor: WorkloadExecutor, - updates: mpsc::Receiver, - blinded_provider_factory: BPF, - trie_metrics: MultiProofTaskMetrics, - sparse_trie_state: Option, - ) -> Self { - if let Some(sparse_trie_state) = sparse_trie_state { - Self::with_accounts_trie( - executor, - updates, - blinded_provider_factory, - trie_metrics, - sparse_trie_state, - ) - } else { - Self::new(executor, updates, blinded_provider_factory, trie_metrics) - } - } - - /// Creates a new sparse trie task, using the given cleared `SparseTrieState` for the accounts - /// trie. - pub(super) fn with_accounts_trie( - executor: WorkloadExecutor, - updates: mpsc::Receiver, - blinded_provider_factory: BPF, - metrics: MultiProofTaskMetrics, - sparse_trie_state: SparseTrieState, - ) -> Self { - let mut trie = SparseStateTrie::new(blinded_provider_factory).with_updates(true); - trie.populate_from(sparse_trie_state); - - Self { executor, updates, metrics, trie } - } - /// Runs the sparse trie task to completion. /// /// This waits for new incoming [`SparseTrieUpdate`]. @@ -146,10 +109,7 @@ where self.metrics.sparse_trie_final_update_duration_histogram.record(start.elapsed()); self.metrics.sparse_trie_total_duration_histogram.record(now.elapsed()); - // take the account trie - let trie = self.trie.take_cleared_account_trie_state(); - - Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) + Ok(StateRootComputeOutcome { state_root, trie_updates }) } } @@ -161,8 +121,6 @@ pub struct StateRootComputeOutcome { pub state_root: B256, /// The trie updates. pub trie_updates: TrieUpdates, - /// The account state trie. - pub trie: SparseTrieState, } /// Updates the sparse trie with the given proofs and state, and returns the elapsed time. diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index dc8ac3506f8..39e305f4981 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -1,6 +1,6 @@ use crate::{ blinded::{BlindedProvider, BlindedProviderFactory, DefaultBlindedProviderFactory}, - LeafLookup, RevealedSparseTrie, SparseTrie, SparseTrieState, TrieMasks, + LeafLookup, RevealedSparseTrie, SparseTrie, TrieMasks, }; use alloc::{collections::VecDeque, vec::Vec}; use alloy_primitives::{ @@ -107,19 +107,6 @@ impl SparseStateTrie { self.revealed_account_paths.contains(&Nibbles::unpack(account)) } - /// Uses the input `SparseTrieState` to populate the backing data structures in the `state` - /// trie. - pub fn populate_from(&mut self, trie: SparseTrieState) { - if let Some(new_trie) = self.state.as_revealed_mut() { - new_trie.use_allocated_state(trie); - } else { - self.state = SparseTrie::revealed_with_provider( - self.provider_factory.account_node_provider(), - trie, - ) - } - } - /// Was the account witness for `address` complete? pub fn check_valid_account_witness(&self, address: B256) -> bool { let path = Nibbles::unpack(address); @@ -356,7 +343,7 @@ impl SparseStateTrie { ) -> SparseStateTrieResult<()> { let FilteredProofNodes { nodes, - new_nodes: _, + new_nodes, total_nodes: _total_nodes, skipped_nodes: _skipped_nodes, } = filter_revealed_nodes(account_subtree, &self.revealed_account_paths)?; @@ -379,6 +366,9 @@ impl SparseStateTrie { self.retain_updates, )?; + // Reserve the capacity for new nodes ahead of time. + trie.reserve_nodes(new_nodes); + // Reveal the remaining proof nodes. for (path, node) in account_nodes { let (hash_mask, tree_mask) = if let TrieNode::Branch(_) = node { @@ -660,7 +650,7 @@ impl SparseStateTrie { &mut self, ) -> SparseStateTrieResult<&mut RevealedSparseTrie> { match self.state { - SparseTrie::Blind | SparseTrie::AllocatedEmpty { .. } => { + SparseTrie::Blind => { let (root_node, hash_mask, tree_mask) = self .provider_factory .account_node_provider() @@ -878,12 +868,6 @@ impl SparseStateTrie { storage_trie.remove_leaf(slot)?; Ok(()) } - - /// Clears and takes the account trie. - pub fn take_cleared_account_trie_state(&mut self) -> SparseTrieState { - let trie = core::mem::take(&mut self.state); - trie.cleared() - } } /// Result of [`filter_revealed_nodes`]. diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 8636383a05b..5759b5d4b89 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -52,19 +52,6 @@ impl TrieMasks { } } -/// A struct for keeping the hashmaps from `RevealedSparseTrie`. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct SparseTrieState { - /// Map from a path (nibbles) to its corresponding sparse trie node. - nodes: HashMap, - /// When a branch is set, the corresponding child subtree is stored in the database. - branch_node_tree_masks: HashMap, - /// When a bit is set, the corresponding child is stored as a hash in the database. - branch_node_hash_masks: HashMap, - /// Map from leaf key paths to their values. - values: HashMap>, -} - /// A sparse trie that is either in a "blind" state (no nodes are revealed, root node hash is /// unknown) or in a "revealed" state (root node has been revealed and the trie can be updated). /// @@ -77,15 +64,8 @@ pub struct SparseTrieState { /// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted /// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie. /// This is what gives rise to the notion of a "sparse" trie. -#[derive(PartialEq, Eq, Default, Clone)] +#[derive(PartialEq, Eq, Default)] pub enum SparseTrie

{ - /// This is a variant that can be used to store a previously allocated trie. In these cases, - /// the trie will still be treated as blind, but the allocated trie will be reused if the trie - /// becomes revealed. - AllocatedEmpty { - /// This is the state of the allocated trie. - allocated: SparseTrieState, - }, /// The trie is blind -- no nodes have been revealed /// /// This is the default state. In this state, @@ -103,7 +83,6 @@ pub enum SparseTrie

{ impl

fmt::Debug for SparseTrie

{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::AllocatedEmpty { .. } => write!(f, "AllocatedEmpty"), Self::Blind => write!(f, "Blind"), Self::Revealed(revealed) => write!(f, "Revealed({revealed:?})"), } @@ -205,54 +184,17 @@ impl

SparseTrie

{ masks: TrieMasks, retain_updates: bool, ) -> SparseTrieResult<&mut RevealedSparseTrie

> { - // we take the allocated state here, which will make sure we are either `Blind` or - // `Revealed`, and giving us the allocated state if we were `AllocatedEmpty`. - let allocated = self.take_allocated_state(); - - // if `Blind`, we initialize the revealed trie if self.is_blind() { - let mut revealed = - RevealedSparseTrie::from_provider_and_root(provider, root, masks, retain_updates)?; - - // If we had an allocated state, we use its maps internally. use_allocated_state copies - // over any information we had from revealing. - if let Some(allocated) = allocated { - revealed.use_allocated_state(allocated); - } - - *self = Self::Revealed(Box::new(revealed)); + *self = Self::Revealed(Box::new(RevealedSparseTrie::from_provider_and_root( + provider, + root, + masks, + retain_updates, + )?)) } Ok(self.as_revealed_mut().unwrap()) } - /// Take the allocated state if this is `AllocatedEmpty`, otherwise returns `None`. - /// - /// Converts this `SparseTrie` into `Blind` if this was `AllocatedEmpty`. - pub fn take_allocated_state(&mut self) -> Option { - if let Self::AllocatedEmpty { allocated } = self { - let state = core::mem::take(allocated); - *self = Self::Blind; - Some(state) - } else { - None - } - } - - /// Creates a new trie with the given provider and sparse trie state. - pub fn revealed_with_provider(provider: P, revealed_state: SparseTrieState) -> Self { - let revealed = RevealedSparseTrie { - provider, - nodes: revealed_state.nodes, - branch_node_tree_masks: revealed_state.branch_node_tree_masks, - branch_node_hash_masks: revealed_state.branch_node_hash_masks, - values: revealed_state.values, - prefix_set: PrefixSetMut::default(), - updates: None, - rlp_buf: Vec::new(), - }; - Self::Revealed(Box::new(revealed)) - } - /// Wipes the trie by removing all nodes and values, /// and resetting the trie to only contain an empty root node. /// @@ -263,16 +205,6 @@ impl

SparseTrie

{ Ok(()) } - /// Returns a `SparseTrieState` obtained by clearing the sparse trie state and reusing the - /// allocated state if it was `AllocatedEmpty` or `Revealed`. - pub fn cleared(self) -> SparseTrieState { - match self { - Self::Revealed(revealed) => revealed.cleared_state(), - Self::AllocatedEmpty { allocated } => allocated, - Self::Blind => Default::default(), - } - } - /// Calculates the root hash of the trie. /// /// This will update any remaining dirty nodes before computing the root hash. @@ -549,37 +481,6 @@ impl

RevealedSparseTrie

{ } } - /// Sets the fields of this `RevealedSparseTrie` to the fields of the input - /// `SparseTrieState`. - /// - /// This is meant for reusing the allocated maps contained in the `SparseTrieState`. - /// - /// Copies over any existing nodes, branch masks, and values. - pub fn use_allocated_state(&mut self, mut other: SparseTrieState) { - for (path, node) in self.nodes.drain() { - other.nodes.insert(path, node); - } - for (path, mask) in self.branch_node_tree_masks.drain() { - other.branch_node_tree_masks.insert(path, mask); - } - for (path, mask) in self.branch_node_hash_masks.drain() { - other.branch_node_hash_masks.insert(path, mask); - } - for (path, value) in self.values.drain() { - other.values.insert(path, value); - } - - self.nodes = other.nodes; - self.branch_node_tree_masks = other.branch_node_tree_masks; - self.branch_node_hash_masks = other.branch_node_hash_masks; - self.values = other.values; - } - - /// Set the provider for the trie. - pub fn set_provider(&mut self, provider: P) { - self.provider = provider; - } - /// Configures the trie to retain information about updates. /// /// If `retain_updates` is true, the trie will record branch node updates and deletions. @@ -938,33 +839,6 @@ impl

RevealedSparseTrie

{ self.updates = self.updates.is_some().then(SparseTrieUpdates::wiped); } - /// This clears all data structures in the sparse trie, keeping the backing data structures - /// allocated. - /// - /// This is useful for reusing the trie without needing to reallocate memory. - pub fn clear(&mut self) { - self.nodes.clear(); - self.branch_node_tree_masks.clear(); - self.branch_node_hash_masks.clear(); - self.values.clear(); - self.prefix_set.clear(); - if let Some(updates) = self.updates.as_mut() { - updates.clear() - } - self.rlp_buf.clear(); - } - - /// Returns the cleared `SparseTrieState` for this `RevealedSparseTrie`. - pub fn cleared_state(mut self) -> SparseTrieState { - self.clear(); - SparseTrieState { - nodes: self.nodes, - branch_node_tree_masks: self.branch_node_tree_masks, - branch_node_hash_masks: self.branch_node_hash_masks, - values: self.values, - } - } - /// Calculates and returns the root hash of the trie. /// /// Before computing the hash, this function processes any remaining (dirty) nodes by @@ -1451,6 +1325,22 @@ pub enum LeafLookup { } impl RevealedSparseTrie

{ + /// This clears all data structures in the sparse trie, keeping the backing data structures + /// allocated. + /// + /// This is useful for reusing the trie without needing to reallocate memory. + pub fn clear(&mut self) { + self.nodes.clear(); + self.branch_node_tree_masks.clear(); + self.branch_node_hash_masks.clear(); + self.values.clear(); + self.prefix_set.clear(); + if let Some(updates) = self.updates.as_mut() { + updates.clear() + } + self.rlp_buf.clear(); + } + /// Attempts to find a leaf node at the specified path. /// /// This method traverses the trie from the root down to the given path, checking From b4a08230637e7debad7b364f4d060d5e0c15f754 Mon Sep 17 00:00:00 2001 From: Skylar Ray <137945430+sky-coderay@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:20:29 +0300 Subject: [PATCH 058/274] docs: clarify txpool docs (#16833) --- crates/storage/storage-api/src/transactions.rs | 2 +- crates/transaction-pool/src/pool/state.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/storage/storage-api/src/transactions.rs b/crates/storage/storage-api/src/transactions.rs index 96e6a1997e7..732d0437592 100644 --- a/crates/storage/storage-api/src/transactions.rs +++ b/crates/storage/storage-api/src/transactions.rs @@ -9,7 +9,7 @@ use reth_storage_errors::provider::{ProviderError, ProviderResult}; /// Enum to control transaction hash inclusion. /// -/// This serves as a hint to the provider to include or omit exclude hashes because hashes are +/// This serves as a hint to the provider to include or omit hashes because hashes are /// stored separately and are not always needed. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum TransactionVariant { diff --git a/crates/transaction-pool/src/pool/state.rs b/crates/transaction-pool/src/pool/state.rs index d65fc05b03f..e04b463343e 100644 --- a/crates/transaction-pool/src/pool/state.rs +++ b/crates/transaction-pool/src/pool/state.rs @@ -14,7 +14,7 @@ bitflags::bitflags! { pub(crate) struct TxState: u8 { /// Set to `1` if all ancestor transactions are pending. const NO_PARKED_ANCESTORS = 0b10000000; - /// Set to `1` of the transaction is either the next transaction of the sender (on chain nonce == tx.nonce) or all prior transactions are also present in the pool. + /// Set to `1` if the transaction is either the next transaction of the sender (on chain nonce == tx.nonce) or all prior transactions are also present in the pool. const NO_NONCE_GAPS = 0b01000000; /// Bit derived from the sender's balance. /// From 519cd3e3076d3b93913ee63145261d21bfa29c4c Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:39:03 -0400 Subject: [PATCH 059/274] perf: reuse accounts trie in payload processing (#16836) --- crates/engine/tree/benches/state_root_task.rs | 2 +- crates/engine/tree/src/tree/mod.rs | 5 +- .../tree/src/tree/payload_processor/mod.rs | 20 ++- .../src/tree/payload_processor/sparse_trie.rs | 46 +++++- crates/trie/sparse/src/state.rs | 25 +++- crates/trie/sparse/src/trie.rs | 141 +++++++++++++++--- 6 files changed, 203 insertions(+), 36 deletions(-) diff --git a/crates/engine/tree/benches/state_root_task.rs b/crates/engine/tree/benches/state_root_task.rs index d705bfecd8f..1eeb7a47f50 100644 --- a/crates/engine/tree/benches/state_root_task.rs +++ b/crates/engine/tree/benches/state_root_task.rs @@ -227,7 +227,7 @@ fn bench_state_root(c: &mut Criterion) { (genesis_hash, payload_processor, provider, state_updates) }, - |(genesis_hash, payload_processor, provider, state_updates)| { + |(genesis_hash, mut payload_processor, provider, state_updates)| { black_box({ let mut handle = payload_processor.spawn( Default::default(), diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 7b8454175ec..26cc096535f 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2283,7 +2283,7 @@ where // background task or try to compute it in parallel if use_state_root_task { match handle.state_root() { - Ok(StateRootComputeOutcome { state_root, trie_updates }) => { + Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) => { let elapsed = execution_finish.elapsed(); info!(target: "engine::tree", ?state_root, ?elapsed, "State root task finished"); // we double check the state root here for good measure @@ -2297,6 +2297,9 @@ where "State root task returned incorrect state root" ); } + + // hold on to the sparse trie for the next payload + self.payload_processor.set_sparse_trie(trie); } Err(error) => { debug!(target: "engine::tree", %error, "Background parallel state root computation failed"); diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 5c782fbd4bb..118a77521b7 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -28,6 +28,7 @@ use reth_trie_parallel::{ proof_task::{ProofTaskCtx, ProofTaskManager}, root::ParallelStateRootError, }; +use reth_trie_sparse::SparseTrieState; use std::{ collections::VecDeque, sync::{ @@ -67,6 +68,9 @@ where precompile_cache_disabled: bool, /// Precompile cache map. precompile_cache_map: PrecompileCacheMap>, + /// A sparse trie, kept around to be used for the state root computation so that allocations + /// can be minimized. + sparse_trie: Option, _marker: std::marker::PhantomData, } @@ -91,6 +95,7 @@ where evm_config, precompile_cache_disabled: config.precompile_cache_disabled(), precompile_cache_map, + sparse_trie: None, _marker: Default::default(), } } @@ -134,7 +139,7 @@ where /// This returns a handle to await the final state root and to interact with the tasks (e.g. /// canceling) pub fn spawn

( - &self, + &mut self, header: SealedHeaderFor, transactions: VecDeque>, provider_builder: StateProviderBuilder, @@ -191,11 +196,15 @@ where multi_proof_task.run(); }); - let mut sparse_trie_task = SparseTrieTask::new( + // take the sparse trie if it was set + let sparse_trie = self.sparse_trie.take(); + + let mut sparse_trie_task = SparseTrieTask::new_with_stored_trie( self.executor.clone(), sparse_trie_rx, proof_task.handle(), self.trie_metrics.clone(), + sparse_trie, ); // wire the sparse trie to the state root response receiver @@ -241,6 +250,11 @@ where PayloadHandle { to_multi_proof: None, prewarm_handle, state_root: None } } + /// Sets the sparse trie to be kept around for the state root computation. + pub(super) fn set_sparse_trie(&mut self, sparse_trie: SparseTrieState) { + self.sparse_trie = Some(sparse_trie); + } + /// Spawn prewarming optionally wired to the multiproof task for target updates. fn spawn_caching_with

( &self, @@ -566,7 +580,7 @@ mod tests { } } - let payload_processor = PayloadProcessor::::new( + let mut payload_processor = PayloadProcessor::::new( WorkloadExecutor::default(), EthEvmConfig::new(factory.chain_spec()), &TreeConfig::default(), diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs index 93f00491090..c8de07c1ec5 100644 --- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs @@ -11,7 +11,7 @@ use reth_trie_parallel::root::ParallelStateRootError; use reth_trie_sparse::{ blinded::{BlindedProvider, BlindedProviderFactory}, errors::{SparseStateTrieResult, SparseTrieErrorKind}, - SparseStateTrie, + SparseStateTrie, SparseTrieState, }; use std::{ sync::mpsc, @@ -63,6 +63,43 @@ where } } + /// Creates a new sparse trie, populating the accounts trie with the given cleared + /// `SparseTrieState` if it exists. + pub(super) fn new_with_stored_trie( + executor: WorkloadExecutor, + updates: mpsc::Receiver, + blinded_provider_factory: BPF, + trie_metrics: MultiProofTaskMetrics, + sparse_trie_state: Option, + ) -> Self { + if let Some(sparse_trie_state) = sparse_trie_state { + Self::with_accounts_trie( + executor, + updates, + blinded_provider_factory, + trie_metrics, + sparse_trie_state, + ) + } else { + Self::new(executor, updates, blinded_provider_factory, trie_metrics) + } + } + + /// Creates a new sparse trie task, using the given cleared `SparseTrieState` for the accounts + /// trie. + pub(super) fn with_accounts_trie( + executor: WorkloadExecutor, + updates: mpsc::Receiver, + blinded_provider_factory: BPF, + metrics: MultiProofTaskMetrics, + sparse_trie_state: SparseTrieState, + ) -> Self { + let mut trie = SparseStateTrie::new(blinded_provider_factory).with_updates(true); + trie.populate_from(sparse_trie_state); + + Self { executor, updates, metrics, trie } + } + /// Runs the sparse trie task to completion. /// /// This waits for new incoming [`SparseTrieUpdate`]. @@ -109,7 +146,10 @@ where self.metrics.sparse_trie_final_update_duration_histogram.record(start.elapsed()); self.metrics.sparse_trie_total_duration_histogram.record(now.elapsed()); - Ok(StateRootComputeOutcome { state_root, trie_updates }) + // take the account trie + let trie = self.trie.take_cleared_account_trie_state(); + + Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) } } @@ -121,6 +161,8 @@ pub struct StateRootComputeOutcome { pub state_root: B256, /// The trie updates. pub trie_updates: TrieUpdates, + /// The account state trie. + pub trie: SparseTrieState, } /// Updates the sparse trie with the given proofs and state, and returns the elapsed time. diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 39e305f4981..84a5a03c12c 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -1,6 +1,6 @@ use crate::{ blinded::{BlindedProvider, BlindedProviderFactory, DefaultBlindedProviderFactory}, - LeafLookup, RevealedSparseTrie, SparseTrie, TrieMasks, + LeafLookup, RevealedSparseTrie, SparseTrie, SparseTrieState, TrieMasks, }; use alloc::{collections::VecDeque, vec::Vec}; use alloy_primitives::{ @@ -107,6 +107,16 @@ impl SparseStateTrie { self.revealed_account_paths.contains(&Nibbles::unpack(account)) } + /// Uses the input `SparseTrieState` to populate the backing data structures in the `state` + /// trie. + pub fn populate_from(&mut self, trie: SparseTrieState) { + if let Some(new_trie) = self.state.as_revealed_mut() { + new_trie.use_allocated_state(trie); + } else { + self.state = SparseTrie::AllocatedEmpty { allocated: trie }; + } + } + /// Was the account witness for `address` complete? pub fn check_valid_account_witness(&self, address: B256) -> bool { let path = Nibbles::unpack(address); @@ -343,7 +353,7 @@ impl SparseStateTrie { ) -> SparseStateTrieResult<()> { let FilteredProofNodes { nodes, - new_nodes, + new_nodes: _, total_nodes: _total_nodes, skipped_nodes: _skipped_nodes, } = filter_revealed_nodes(account_subtree, &self.revealed_account_paths)?; @@ -366,9 +376,6 @@ impl SparseStateTrie { self.retain_updates, )?; - // Reserve the capacity for new nodes ahead of time. - trie.reserve_nodes(new_nodes); - // Reveal the remaining proof nodes. for (path, node) in account_nodes { let (hash_mask, tree_mask) = if let TrieNode::Branch(_) = node { @@ -650,7 +657,7 @@ impl SparseStateTrie { &mut self, ) -> SparseStateTrieResult<&mut RevealedSparseTrie> { match self.state { - SparseTrie::Blind => { + SparseTrie::Blind | SparseTrie::AllocatedEmpty { .. } => { let (root_node, hash_mask, tree_mask) = self .provider_factory .account_node_provider() @@ -868,6 +875,12 @@ impl SparseStateTrie { storage_trie.remove_leaf(slot)?; Ok(()) } + + /// Clears and takes the account trie. + pub fn take_cleared_account_trie_state(&mut self) -> SparseTrieState { + let trie = core::mem::take(&mut self.state); + trie.cleared() + } } /// Result of [`filter_revealed_nodes`]. diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 5759b5d4b89..aee54417d1c 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -52,6 +52,19 @@ impl TrieMasks { } } +/// A struct for keeping the hashmaps from `RevealedSparseTrie`. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct SparseTrieState { + /// Map from a path (nibbles) to its corresponding sparse trie node. + nodes: HashMap, + /// When a branch is set, the corresponding child subtree is stored in the database. + branch_node_tree_masks: HashMap, + /// When a bit is set, the corresponding child is stored as a hash in the database. + branch_node_hash_masks: HashMap, + /// Map from leaf key paths to their values. + values: HashMap>, +} + /// A sparse trie that is either in a "blind" state (no nodes are revealed, root node hash is /// unknown) or in a "revealed" state (root node has been revealed and the trie can be updated). /// @@ -64,8 +77,15 @@ impl TrieMasks { /// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted /// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie. /// This is what gives rise to the notion of a "sparse" trie. -#[derive(PartialEq, Eq, Default)] +#[derive(PartialEq, Eq, Default, Clone)] pub enum SparseTrie

{ + /// This is a variant that can be used to store a previously allocated trie. In these cases, + /// the trie will still be treated as blind, but the allocated trie will be reused if the trie + /// becomes revealed. + AllocatedEmpty { + /// This is the state of the allocated trie. + allocated: SparseTrieState, + }, /// The trie is blind -- no nodes have been revealed /// /// This is the default state. In this state, @@ -83,6 +103,7 @@ pub enum SparseTrie

{ impl

fmt::Debug for SparseTrie

{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::AllocatedEmpty { .. } => write!(f, "AllocatedEmpty"), Self::Blind => write!(f, "Blind"), Self::Revealed(revealed) => write!(f, "Revealed({revealed:?})"), } @@ -184,17 +205,39 @@ impl

SparseTrie

{ masks: TrieMasks, retain_updates: bool, ) -> SparseTrieResult<&mut RevealedSparseTrie

> { + // we take the allocated state here, which will make sure we are either `Blind` or + // `Revealed`, and giving us the allocated state if we were `AllocatedEmpty`. + let allocated = self.take_allocated_state(); + + // if `Blind`, we initialize the revealed trie if self.is_blind() { - *self = Self::Revealed(Box::new(RevealedSparseTrie::from_provider_and_root( - provider, - root, - masks, - retain_updates, - )?)) + let mut revealed = + RevealedSparseTrie::from_provider_and_root(provider, root, masks, retain_updates)?; + + // If we had an allocated state, we use its maps internally. use_allocated_state copies + // over any information we had from revealing. + if let Some(allocated) = allocated { + revealed.use_allocated_state(allocated); + } + + *self = Self::Revealed(Box::new(revealed)); } Ok(self.as_revealed_mut().unwrap()) } + /// Take the allocated state if this is `AllocatedEmpty`, otherwise returns `None`. + /// + /// Converts this `SparseTrie` into `Blind` if this was `AllocatedEmpty`. + pub fn take_allocated_state(&mut self) -> Option { + if let Self::AllocatedEmpty { allocated } = self { + let state = core::mem::take(allocated); + *self = Self::Blind; + Some(state) + } else { + None + } + } + /// Wipes the trie by removing all nodes and values, /// and resetting the trie to only contain an empty root node. /// @@ -205,6 +248,16 @@ impl

SparseTrie

{ Ok(()) } + /// Returns a `SparseTrieState` obtained by clearing the sparse trie state and reusing the + /// allocated state if it was `AllocatedEmpty` or `Revealed`. + pub fn cleared(self) -> SparseTrieState { + match self { + Self::Revealed(revealed) => revealed.cleared_state(), + Self::AllocatedEmpty { allocated } => allocated, + Self::Blind => Default::default(), + } + } + /// Calculates the root hash of the trie. /// /// This will update any remaining dirty nodes before computing the root hash. @@ -481,6 +534,37 @@ impl

RevealedSparseTrie

{ } } + /// Sets the fields of this `RevealedSparseTrie` to the fields of the input + /// `SparseTrieState`. + /// + /// This is meant for reusing the allocated maps contained in the `SparseTrieState`. + /// + /// Copies over any existing nodes, branch masks, and values. + pub fn use_allocated_state(&mut self, mut other: SparseTrieState) { + for (path, node) in self.nodes.drain() { + other.nodes.insert(path, node); + } + for (path, mask) in self.branch_node_tree_masks.drain() { + other.branch_node_tree_masks.insert(path, mask); + } + for (path, mask) in self.branch_node_hash_masks.drain() { + other.branch_node_hash_masks.insert(path, mask); + } + for (path, value) in self.values.drain() { + other.values.insert(path, value); + } + + self.nodes = other.nodes; + self.branch_node_tree_masks = other.branch_node_tree_masks; + self.branch_node_hash_masks = other.branch_node_hash_masks; + self.values = other.values; + } + + /// Set the provider for the trie. + pub fn set_provider(&mut self, provider: P) { + self.provider = provider; + } + /// Configures the trie to retain information about updates. /// /// If `retain_updates` is true, the trie will record branch node updates and deletions. @@ -839,6 +923,33 @@ impl

RevealedSparseTrie

{ self.updates = self.updates.is_some().then(SparseTrieUpdates::wiped); } + /// This clears all data structures in the sparse trie, keeping the backing data structures + /// allocated. + /// + /// This is useful for reusing the trie without needing to reallocate memory. + pub fn clear(&mut self) { + self.nodes.clear(); + self.branch_node_tree_masks.clear(); + self.branch_node_hash_masks.clear(); + self.values.clear(); + self.prefix_set.clear(); + if let Some(updates) = self.updates.as_mut() { + updates.clear() + } + self.rlp_buf.clear(); + } + + /// Returns the cleared `SparseTrieState` for this `RevealedSparseTrie`. + pub fn cleared_state(mut self) -> SparseTrieState { + self.clear(); + SparseTrieState { + nodes: self.nodes, + branch_node_tree_masks: self.branch_node_tree_masks, + branch_node_hash_masks: self.branch_node_hash_masks, + values: self.values, + } + } + /// Calculates and returns the root hash of the trie. /// /// Before computing the hash, this function processes any remaining (dirty) nodes by @@ -1325,22 +1436,6 @@ pub enum LeafLookup { } impl RevealedSparseTrie

{ - /// This clears all data structures in the sparse trie, keeping the backing data structures - /// allocated. - /// - /// This is useful for reusing the trie without needing to reallocate memory. - pub fn clear(&mut self) { - self.nodes.clear(); - self.branch_node_tree_masks.clear(); - self.branch_node_hash_masks.clear(); - self.values.clear(); - self.prefix_set.clear(); - if let Some(updates) = self.updates.as_mut() { - updates.clear() - } - self.rlp_buf.clear(); - } - /// Attempts to find a leaf node at the specified path. /// /// This method traverses the trie from the root down to the given path, checking From f22c8bdedb80cb67fb895ee3d68159e55df58e94 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:39:16 -0400 Subject: [PATCH 060/274] feat: add parallel sparse trie skeleton (#16837) --- crates/trie/sparse/src/lib.rs | 3 ++ crates/trie/sparse/src/parallel_trie.rs | 49 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 crates/trie/sparse/src/parallel_trie.rs diff --git a/crates/trie/sparse/src/lib.rs b/crates/trie/sparse/src/lib.rs index 617622d194f..c6b31bdb74f 100644 --- a/crates/trie/sparse/src/lib.rs +++ b/crates/trie/sparse/src/lib.rs @@ -11,6 +11,9 @@ pub use state::*; mod trie; pub use trie::*; +mod parallel_trie; +pub use parallel_trie::*; + pub mod blinded; #[cfg(feature = "metrics")] diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs new file mode 100644 index 00000000000..591b6aa2620 --- /dev/null +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -0,0 +1,49 @@ +use crate::{SparseNode, SparseTrieUpdates}; +use alloc::vec::Vec; +use alloy_primitives::map::HashMap; +use reth_trie_common::Nibbles; + +/// A revealed sparse trie with subtries that can be updated in parallel. +/// +/// ## Invariants +/// +/// - Each leaf entry in the `subtries` and `upper_trie` collection must have a corresponding entry +/// in `values` collection. If the root node is a leaf, it must also have an entry in `values`. +/// - All keys in `values` collection are full leaf paths. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ParallelSparseTrie { + /// The root of the sparse trie. + root_node: SparseNode, + /// Map from a path (nibbles) to its corresponding sparse trie node. + /// This contains the trie nodes for the upper part of the trie. + upper_trie: HashMap, + /// An array containing the subtries at the second level of the trie. + subtries: [Option; 256], + /// Map from leaf key paths to their values. + /// All values are stored here instead of directly in leaf nodes. + values: HashMap>, + /// Optional tracking of trie updates for later use. + updates: Option, +} + +impl Default for ParallelSparseTrie { + fn default() -> Self { + Self { + root_node: SparseNode::Empty, + upper_trie: HashMap::default(), + subtries: [const { None }; 256], + values: HashMap::default(), + updates: None, + } + } +} + +/// This is a subtrie of the `ParallelSparseTrie` that contains a map from path to sparse trie +/// nodes. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct SparseSubtrie { + /// The root path of this subtrie. + path: Nibbles, + /// The map from paths to sparse trie nodes within this subtrie. + nodes: HashMap, +} From 5efd3c0c57f8d74c5b5ba12531c8b106fba4f076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 17 Jun 2025 10:06:15 +0200 Subject: [PATCH 061/274] deps: Upgrade `op-alloy` version `0.18.2` => `0.18.3` and all other deps minor versions (#16835) --- Cargo.lock | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2adedd3f23a..0d65ab76eeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2749,9 +2749,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", @@ -5925,9 +5925,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f83ed68f4d32a807e25e8efa5ea4ca432bbbcffce3223571a0f92fc6d4941fc" +checksum = "c63a84d99657aa23b7bd7393ceca738200e14e4152e5bba5835210f3329bdb17" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5951,9 +5951,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734b2d75375cf1ddff6d71be332021383bb04dd2ab753a29892312e4b3ab387d" +checksum = "df19c524993ebd1030cdbf6bac0ab7c2b57c76eefe4cf64728f021b6d2b239c8" dependencies = [ "alloy-consensus", "alloy-network", @@ -5967,9 +5967,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cfd25bef0af26c85011e98ca9be0713f855d47e110c6be4a36eebefa1b002f2" +checksum = "aedc46e9756cf7b1041422e091727449c1b800513e9973ef0f96aa293328c0ed" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5977,9 +5977,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59bd889eddcb7d4faec487a6c0f67127218c9b2daadc061177164d6cac552c38" +checksum = "04cc10cd568d40251452b438897dbe010f97d85491e6a777f9bfe4b33dbd9c46" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5996,9 +5996,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c2101fb6ad607ebaf13ee830550c782d551557fd8d1f5f53bba60ac39a9ea" +checksum = "9202f24fa1f918c6ea72f57247674d22783c1a920585013f34ac54951fc8d91a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6276,9 +6276,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -11002,9 +11002,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "log", "once_cell", @@ -11552,12 +11552,9 @@ checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" From ecb92f307c27d25756df367a694f87504778c6a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:07:07 +0000 Subject: [PATCH 062/274] chore(deps): bump dprint/check from 2.2 to 2.3 (#16839) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5d67bee3de0..ffa9f8edc30 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -209,7 +209,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Run dprint - uses: dprint/check@v2.2 + uses: dprint/check@v2.3 with: config-path: dprint.json From ee2e60c144ccfd71e5ef21f6aab5fb98ef51bb86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:13:25 +0000 Subject: [PATCH 063/274] chore(deps): bump dawidd6/action-homebrew-bump-formula from 4 to 5 (#16838) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release-dist.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-dist.yml b/.github/workflows/release-dist.yml index f7df80e81f9..57a6f311d0b 100644 --- a/.github/workflows/release-dist.yml +++ b/.github/workflows/release-dist.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Update Homebrew formula - uses: dawidd6/action-homebrew-bump-formula@v4 + uses: dawidd6/action-homebrew-bump-formula@v5 with: token: ${{ secrets.HOMEBREW }} no_fork: true From 46780aec289a882fe6d0e02926f6dde066e2e00e Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Tue, 17 Jun 2025 13:45:06 +0530 Subject: [PATCH 064/274] feat: introduced fn earliest_block_number for BlockNumReader (#16831) --- crates/storage/storage-api/src/block_id.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/storage/storage-api/src/block_id.rs b/crates/storage/storage-api/src/block_id.rs index 9fe35a5a00b..d00f78df1d1 100644 --- a/crates/storage/storage-api/src/block_id.rs +++ b/crates/storage/storage-api/src/block_id.rs @@ -19,6 +19,11 @@ pub trait BlockNumReader: BlockHashReader + Send + Sync { /// Returns the last block number associated with the last canonical header in the database. fn last_block_number(&self) -> ProviderResult; + /// Returns earliest block number to keep track of the expired block range. + fn earliest_block_number(&self) -> ProviderResult { + Ok(0) + } + /// Gets the `BlockNumber` for the given hash. Returns `None` if no block with this hash exists. fn block_number(&self, hash: B256) -> ProviderResult>; From 8477d652f6d76f24405d376f771573924053ea4f Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:21:10 +0530 Subject: [PATCH 065/274] refactor: replaced update_status with update_block_range (#16840) Co-authored-by: Matthias Seitz --- crates/node/builder/src/launch/engine.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 29e8bccc5ab..18f106686f0 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -20,7 +20,7 @@ use reth_engine_tree::{ }; use reth_engine_util::EngineMessageStreamExt; use reth_exex::ExExManagerHandle; -use reth_network::{NetworkSyncUpdater, SyncState}; +use reth_network::{types::BlockRangeUpdate, NetworkSyncUpdater, SyncState}; use reth_network_api::BlockDownloaderProvider; use reth_node_api::{ BeaconConsensusEngineHandle, BuiltPayload, FullNodeTypes, NodeTypes, NodeTypesWithDBAdapter, @@ -301,6 +301,7 @@ where .map_err(|e| eyre::eyre!("Failed to subscribe to payload builder events: {:?}", e))? .into_built_payload_stream() .fuse(); + let chainspec = ctx.chain_spec(); let (exit, rx) = oneshot::channel(); let terminate_after_backfill = ctx.terminate_after_initial_backfill(); @@ -345,7 +346,7 @@ where if let Some(head) = ev.canonical_header() { // Once we're progressing via live sync, we can consider the node is not syncing anymore network_handle.update_sync_state(SyncState::Idle); - let head_block = Head { + let head_block = Head { number: head.number(), hash: head.hash(), difficulty: head.difficulty(), @@ -353,6 +354,13 @@ where total_difficulty: chainspec.final_paris_total_difficulty().filter(|_| chainspec.is_paris_active_at_block(head.number())).unwrap_or_default(), }; network_handle.update_status(head_block); + + let updated=BlockRangeUpdate{ + earliest:0, + latest:head.number(), + latest_hash:head.hash() + }; + network_handle.update_block_range(updated); } event_sender.notify(ev); } From 3096e9520d3a223fbf3f1afef06fa2a2d54070b8 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Tue, 17 Jun 2025 10:59:49 +0200 Subject: [PATCH 066/274] chore(ci): pin kurtosis-op optimism package (#16842) --- .github/workflows/kurtosis-op.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/kurtosis-op.yml b/.github/workflows/kurtosis-op.yml index 5fb26acefa9..e77d5528feb 100644 --- a/.github/workflows/kurtosis-op.yml +++ b/.github/workflows/kurtosis-op.yml @@ -62,7 +62,9 @@ jobs: sudo apt update sudo apt install kurtosis-cli kurtosis engine start - kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package --args-file .github/assets/kurtosis_op_network_params.yaml + # TODO: unpin optimism-package when https://github.com/ethpandaops/optimism-package/issues/340 is fixed + # kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package --args-file .github/assets/kurtosis_op_network_params.yaml + kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package@452133367b693e3ba22214a6615c86c60a1efd5e --args-file .github/assets/kurtosis_op_network_params.yaml ENCLAVE_ID=$(curl http://127.0.0.1:9779/api/enclaves | jq --raw-output 'keys[0]') GETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-1-op-geth-op-node-op-kurtosis".public_ports.rpc.number') RETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-2-op-reth-op-node-op-kurtosis".public_ports.rpc.number') From 820c334a4a0aad5e4f7e5021d35dafa5f4e5b60d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 17 Jun 2025 11:06:01 +0200 Subject: [PATCH 067/274] feat(era): Delete files outside the range before counting them (#16805) --- crates/era-downloader/src/client.rs | 21 +++++++++++++++++++ crates/era-downloader/src/stream.rs | 31 +++++++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/crates/era-downloader/src/client.rs b/crates/era-downloader/src/client.rs index 6c3a1c1b980..5d5d4033f5f 100644 --- a/crates/era-downloader/src/client.rs +++ b/crates/era-downloader/src/client.rs @@ -133,6 +133,27 @@ impl EraClient { max } + /// Deletes files that are outside-of the working range. + pub async fn delete_outside_range(&self, index: usize, max_files: usize) -> eyre::Result<()> { + let last = index + max_files; + + if let Ok(mut dir) = fs::read_dir(&self.folder).await { + while let Ok(Some(entry)) = dir.next_entry().await { + if let Some(name) = entry.file_name().to_str() { + if let Some(number) = self.file_name_to_number(name) { + if number < index || number >= last { + eprintln!("Deleting kokot {}", entry.path().display()); + eprintln!("{number} < {index} || {number} > {last}"); + reth_fs_util::remove_file(entry.path())?; + } + } + } + } + } + + Ok(()) + } + /// Returns a download URL for the file corresponding to `number`. pub async fn url(&self, number: usize) -> eyre::Result> { Ok(self.number_to_file_name(number).await?.map(|name| self.url.join(&name)).transpose()?) diff --git a/crates/era-downloader/src/stream.rs b/crates/era-downloader/src/stream.rs index ce66a904aeb..a488e098ab0 100644 --- a/crates/era-downloader/src/stream.rs +++ b/crates/era-downloader/src/stream.rs @@ -90,6 +90,7 @@ impl EraStream { client, files_count: Box::pin(async move { usize::MAX }), next_url: Box::pin(async move { Ok(None) }), + delete_outside_range: Box::pin(async move { Ok(()) }), recover_index: Box::pin(async move { None }), fetch_file_list: Box::pin(async move { Ok(()) }), state: Default::default(), @@ -221,6 +222,7 @@ struct StartingStream { client: EraClient, files_count: Pin + Send + Sync + 'static>>, next_url: Pin>> + Send + Sync + 'static>>, + delete_outside_range: Pin> + Send + Sync + 'static>>, recover_index: Pin> + Send + Sync + 'static>>, fetch_file_list: Pin> + Send + Sync + 'static>>, state: State, @@ -245,6 +247,7 @@ enum State { #[default] Initial, FetchFileList, + DeleteOutsideRange, RecoverIndex, CountFiles, Missing(usize), @@ -262,11 +265,24 @@ impl Stream for Starti if self.state == State::FetchFileList { if let Poll::Ready(result) = self.fetch_file_list.poll_unpin(cx) { match result { - Ok(_) => self.recover_index(), + Ok(_) => self.delete_outside_range(), Err(e) => { self.fetch_file_list(); - return Poll::Ready(Some(Box::pin(async move { Err(e) }))) + return Poll::Ready(Some(Box::pin(async move { Err(e) }))); + } + } + } + } + + if self.state == State::DeleteOutsideRange { + if let Poll::Ready(result) = self.delete_outside_range.poll_unpin(cx) { + match result { + Ok(_) => self.recover_index(), + Err(e) => { + self.delete_outside_range(); + + return Poll::Ready(Some(Box::pin(async move { Err(e) }))); } } } @@ -334,6 +350,17 @@ impl StartingStream { self.state = State::FetchFileList; } + fn delete_outside_range(&mut self) { + let index = self.index; + let max_files = self.max_files; + let client = self.client.clone(); + + Pin::new(&mut self.delete_outside_range) + .set(Box::pin(async move { client.delete_outside_range(index, max_files).await })); + + self.state = State::DeleteOutsideRange; + } + fn recover_index(&mut self) { let client = self.client.clone(); From 5d754195a3a2484309ef85de1fec00e82292013e Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Tue, 17 Jun 2025 14:19:07 +0200 Subject: [PATCH 068/274] chore: bump alloy (#16828) --- Cargo.lock | 135 ++++++++++-------- Cargo.toml | 120 ++++++++-------- crates/chainspec/src/spec.rs | 41 +----- crates/rpc/rpc-api/src/mev.rs | 4 +- crates/rpc/rpc-engine-api/tests/it/payload.rs | 4 +- .../rpc/rpc-types-compat/src/transaction.rs | 2 +- 6 files changed, 142 insertions(+), 164 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d65ab76eeb..d4365b8ada8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,15 +112,16 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad451f9a70c341d951bca4e811d74dbe1e193897acd17e9dbac1353698cc430b" +checksum = "a0dc46cb8a5322c5a65509622a111f1c3f4161f7bfaae883f0c939bcd50d0fd6" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-serde", "alloy-trie", + "alloy-tx-macros", "arbitrary", "auto_impl", "c-kzg", @@ -137,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142daffb15d5be1a2b20d2cd540edbcef03037b55d4ff69dc06beb4d06286dba" +checksum = "2492d3548408746e0d3fddcaeb033b1ee9041aa449dcf7df35fb18008fe921a2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -152,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf25443920ecb9728cb087fe4dc04a0b290bd6ac85638c58fe94aba70f1a44e" +checksum = "9c9a46cef9fa15c2fef07294e6ebf9c8913646b957dcfd2547041350679fa288" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -168,6 +169,7 @@ dependencies = [ "alloy-transport", "futures", "futures-util", + "serde_json", "thiserror 2.0.12", ] @@ -234,9 +236,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3056872f6da48046913e76edb5ddced272861f6032f09461aea1a2497be5ae5d" +checksum = "d3165f56b57a11d2b77742ed2f48319ac9857de122855b43773bd0e26478131d" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -276,9 +278,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c98fb40f07997529235cc474de814cd7bd9de561e101716289095696c0e4639d" +checksum = "833b10a7a4bece11e7e2cc2193894dcb07014fa5fdcb23ab4cfa94d4e0667581" dependencies = [ "alloy-eips", "alloy-primitives", @@ -315,12 +317,13 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc08b31ebf9273839bd9a01f9333cbb7a3abb4e820c312ade349dd18bdc79581" +checksum = "83dc6510a04ef2db88fa26e6389814a6966edd7f01e56ef69f56964ee33c58a5" dependencies = [ "alloy-primitives", "alloy-sol-types", + "http", "serde", "serde_json", "thiserror 2.0.12", @@ -329,9 +332,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed117b08f0cc190312bf0c38c34cf4f0dabfb4ea8f330071c587cd7160a88cb2" +checksum = "3cd33e08e9922768f970fdf5bbc617fb8add3161d273e168d623dcdb4c45cd98" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -355,9 +358,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7162ff7be8649c0c391f4e248d1273e85c62076703a1f3ec7daf76b283d886d" +checksum = "3596f4179ab18a06f2bd6c14db01e0c29263a5a7a31732a54dfd05b07d428002" dependencies = [ "alloy-consensus", "alloy-eips", @@ -427,9 +430,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84eba1fd8b6fe8b02f2acd5dd7033d0f179e304bd722d11e817db570d1fa6c4" +checksum = "303e09afbbc561cf8ba16c9e860515c5c62bbaf7e622b004f00c6b09edf65020" dependencies = [ "alloy-chains", "alloy-consensus", @@ -455,6 +458,7 @@ dependencies = [ "either", "futures", "futures-utils-wasm", + "http", "lru 0.13.0", "parking_lot", "pin-project", @@ -470,9 +474,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8550f7306e0230fc835eb2ff4af0a96362db4b6fc3f25767d161e0ad0ac765bf" +checksum = "be5cf6a5e35d8ebb202fb924617c79616884e081fb3f766a4b6793b57960a16b" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -513,9 +517,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518a699422a3eab800f3dac2130d8f2edba8e4fff267b27a9c7dc6a2b0d313ee" +checksum = "abf2a479318b304148897bd6ee50fbbe94fa7f7c54fda6c33848af274a5c8351" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -541,9 +545,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c000cab4ec26a4b3e29d144e999e1c539c2fa0abed871bf90311eb3466187ca8" +checksum = "11e31c3a481349044899e47c73cca6047563bf359b203861627e53bea679a757" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -554,9 +558,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ebdc864f573645c5288370c208912b85b5cacc8025b700c50c2b74d06ab9830" +checksum = "3111649049b68878d4c4c4897db4ec699dee7c9edd5b37643ade946b133bbf3d" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -566,9 +570,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8abecc34549a208b5f91bc7f02df3205c36e2aa6586f1d9375c3382da1066b3b" +checksum = "c0a8bf5eed865c9123b944ea9cd09c36a5c973b1605846a7cdc8ea7b6681702c" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -578,9 +582,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "508b2fbe66d952089aa694e53802327798806498cd29ff88c75135770ecaabfc" +checksum = "193dcf549a9383bc855eb1f209eb3aa0d2b1bcbc45998b92f905565f243cf130" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -589,9 +593,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241aba7808bddc3ad1c6228e296a831f326f89118b1017012090709782a13334" +checksum = "9caa1bec0258fbebc7803d7da33e250de2d1c6e97e34a2b8a4ece83b31d8de43" dependencies = [ "alloy-eips", "alloy-primitives", @@ -607,9 +611,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c832f2e851801093928dbb4b7bd83cd22270faf76b2e080646b806a285c8757" +checksum = "395dd78499c49adaf6a078f1ccb5856359c5f678306484a3d631511649b37a61" dependencies = [ "alloy-primitives", "serde", @@ -617,9 +621,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab52691970553d84879d777419fa7b6a2e92e9fe8641f9324cc071008c2f656" +checksum = "49377b741a373b8e3b69b90abf40ec255c1f6a344ab4156c2a0e4ee9204c5996" dependencies = [ "alloy-consensus", "alloy-eips", @@ -638,9 +642,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf7dff0fdd756a714d58014f4f8354a1706ebf9fa2cf73431e0aeec3c9431e" +checksum = "9c5740de94e37e8977aef7d8e084eba8fbce485c43f04d32de55b629e42b32f0" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -651,7 +655,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arbitrary", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "thiserror 2.0.12", @@ -659,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18bd1c5d7b9f3f1caeeaa1c082aa28ba7ce2d67127b12b2a9b462712c8f6e1c5" +checksum = "0f83a35afc1e29e3556f0a01735a3b1aae5185511f6cef0843befa5e99bb2ac3" dependencies = [ "alloy-consensus", "alloy-eips", @@ -674,9 +678,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3507a04e868dd83219ad3cd6a8c58aefccb64d33f426b3934423a206343e84" +checksum = "cc9d38c9df26dc9824e5e5a81c42879f5793f89adaff1fbe25833c52c4103d75" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -688,9 +692,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eec36272621c3ac82b47dd77f0508346687730b1c2e3e10d3715705c217c0a05" +checksum = "f8d078f0cc054bb846f2b3f9b92cef2502459f9bf67bea94049e877d4fb07bc0" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -700,9 +704,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730e8f2edf2fc224cabd1c25d090e1655fa6137b2e409f92e5eec735903f1507" +checksum = "61b6134af56345296cc2f624ebf621f79330cb8a80b0d7bb29e4e41ee729dcaf" dependencies = [ "alloy-primitives", "arbitrary", @@ -712,9 +716,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0d2428445ec13edc711909e023d7779618504c4800be055a5b940025dbafe3" +checksum = "2f9fbc2e1283d3e939d4f203fdb00922dcbb231db75b9db5b8de3c4ef4be3e2e" dependencies = [ "alloy-primitives", "async-trait", @@ -727,9 +731,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14fe6fedb7fe6e0dfae47fe020684f1d8e063274ef14bca387ddb7a6efa8ec1" +checksum = "7aaf3564568c9c2199375dbf6b3520cc3f71997c2513ba544dda9a540e5e63cf" dependencies = [ "alloy-consensus", "alloy-network", @@ -815,9 +819,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a712bdfeff42401a7dd9518f72f617574c36226a9b5414537fedc34350b73bf9" +checksum = "8ecf50e4efa5b4f91c6050ce558cc06c6890d7db754246e534acc3ac76c796d9" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -838,9 +842,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea5a76d7f2572174a382aedf36875bedf60bcc41116c9f031cf08040703a2dc" +checksum = "915aceee2aecf8f2c38e6c5bdf2703bf2bed32ebd91121b5640dbc23818e97f9" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -853,9 +857,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606af17a7e064d219746f6d2625676122c79d78bf73dfe746d6db9ecd7dbcb85" +checksum = "174da2b07cca2e0438ec49ca12b065f75ff2e3c21a237b26d8478d14ec477aac" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -873,9 +877,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c6f9b37cd8d44aab959613966cc9d4d7a9b429c575cec43b3e5b46ea109a79" +checksum = "5933ae8a0256290c7310b788d046604f2e46d4e2e1f305f1dd5437cb610fbc3c" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -909,6 +913,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "alloy-tx-macros" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde95928532e9c79f8c7488da0170bc9e94c6e7d02a09aa21d8a1bdd8e84e2ab" +dependencies = [ + "alloy-primitives", + "darling", + "proc-macro2", + "quote", + "syn 2.0.103", +] + [[package]] name = "android-tzdata" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 4f44f7d0cae..a9ae78552d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -474,33 +474,33 @@ alloy-trie = { version = "0.8.1", default-features = false } alloy-hardforks = "0.2.2" -alloy-consensus = { version = "1.0.9", default-features = false } -alloy-contract = { version = "1.0.9", default-features = false } -alloy-eips = { version = "1.0.9", default-features = false } -alloy-genesis = { version = "1.0.9", default-features = false } -alloy-json-rpc = { version = "1.0.9", default-features = false } -alloy-network = { version = "1.0.9", default-features = false } -alloy-network-primitives = { version = "1.0.9", default-features = false } -alloy-provider = { version = "1.0.9", features = ["reqwest"], default-features = false } -alloy-pubsub = { version = "1.0.9", default-features = false } -alloy-rpc-client = { version = "1.0.9", default-features = false } -alloy-rpc-types = { version = "1.0.9", features = ["eth"], default-features = false } -alloy-rpc-types-admin = { version = "1.0.9", default-features = false } -alloy-rpc-types-anvil = { version = "1.0.9", default-features = false } -alloy-rpc-types-beacon = { version = "1.0.9", default-features = false } -alloy-rpc-types-debug = { version = "1.0.9", default-features = false } -alloy-rpc-types-engine = { version = "1.0.9", default-features = false } -alloy-rpc-types-eth = { version = "1.0.9", default-features = false } -alloy-rpc-types-mev = { version = "1.0.9", default-features = false } -alloy-rpc-types-trace = { version = "1.0.9", default-features = false } -alloy-rpc-types-txpool = { version = "1.0.9", default-features = false } -alloy-serde = { version = "1.0.9", default-features = false } -alloy-signer = { version = "1.0.9", default-features = false } -alloy-signer-local = { version = "1.0.9", default-features = false } -alloy-transport = { version = "1.0.9" } -alloy-transport-http = { version = "1.0.9", features = ["reqwest-rustls-tls"], default-features = false } -alloy-transport-ipc = { version = "1.0.9", default-features = false } -alloy-transport-ws = { version = "1.0.9", default-features = false } +alloy-consensus = { version = "1.0.10", default-features = false } +alloy-contract = { version = "1.0.10", default-features = false } +alloy-eips = { version = "1.0.10", default-features = false } +alloy-genesis = { version = "1.0.10", default-features = false } +alloy-json-rpc = { version = "1.0.10", default-features = false } +alloy-network = { version = "1.0.10", default-features = false } +alloy-network-primitives = { version = "1.0.10", default-features = false } +alloy-provider = { version = "1.0.10", features = ["reqwest"], default-features = false } +alloy-pubsub = { version = "1.0.10", default-features = false } +alloy-rpc-client = { version = "1.0.10", default-features = false } +alloy-rpc-types = { version = "1.0.10", features = ["eth"], default-features = false } +alloy-rpc-types-admin = { version = "1.0.10", default-features = false } +alloy-rpc-types-anvil = { version = "1.0.10", default-features = false } +alloy-rpc-types-beacon = { version = "1.0.10", default-features = false } +alloy-rpc-types-debug = { version = "1.0.10", default-features = false } +alloy-rpc-types-engine = { version = "1.0.10", default-features = false } +alloy-rpc-types-eth = { version = "1.0.10", default-features = false } +alloy-rpc-types-mev = { version = "1.0.10", default-features = false } +alloy-rpc-types-trace = { version = "1.0.10", default-features = false } +alloy-rpc-types-txpool = { version = "1.0.10", default-features = false } +alloy-serde = { version = "1.0.10", default-features = false } +alloy-signer = { version = "1.0.10", default-features = false } +alloy-signer-local = { version = "1.0.10", default-features = false } +alloy-transport = { version = "1.0.10" } +alloy-transport-http = { version = "1.0.10", features = ["reqwest-rustls-tls"], default-features = false } +alloy-transport-ipc = { version = "1.0.10", default-features = false } +alloy-transport-ws = { version = "1.0.10", default-features = false } # op alloy-op-evm = { version = "0.11", default-features = false } @@ -704,39 +704,39 @@ walkdir = "2.3.3" vergen-git2 = "1.0.5" [patch.crates-io] -# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-contract = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-eips = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-network = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-provider = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-serde = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-signer = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-transport = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", branch = "main" } -# -# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } -# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } -# op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } -# op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } -# op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", rev = "ad607c1" } +# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } + +# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } +# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } +# op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } +# op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } +# op-alloy-rpc-jsonrpsee = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } # # revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "1207e33" } # diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index b3161700b6e..a2ada0621ee 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -1033,18 +1033,13 @@ mod tests { use super::*; use alloy_chains::Chain; use alloy_consensus::constants::ETH_TO_WEI; - use alloy_eips::{eip4844::BLOB_TX_MIN_BLOB_GASPRICE, eip7840::BlobParams}; use alloy_evm::block::calc::{base_block_reward, block_reward}; use alloy_genesis::{ChainConfig, GenesisAccount}; use alloy_primitives::{b256, hex}; use alloy_trie::{TrieAccount, EMPTY_ROOT_HASH}; use core::ops::Deref; use reth_ethereum_forks::{ForkCondition, ForkHash, ForkId, Head}; - use std::{ - collections::{BTreeMap, HashMap}, - str::FromStr, - string::String, - }; + use std::{collections::HashMap, str::FromStr}; fn test_hardfork_fork_ids(spec: &ChainSpec, cases: &[(EthereumHardfork, ForkId)]) { for (hardfork, expected_id) in cases { @@ -2506,38 +2501,4 @@ Post-merge hard forks (timestamp based): assert_eq!(block_reward(base_reward, num_ommers), expected_reward); } } - - #[test] - fn blob_params_from_genesis() { - let s = r#"{ - "cancun":{ - "baseFeeUpdateFraction":3338477, - "max":6, - "target":3 - }, - "prague":{ - "baseFeeUpdateFraction":3338477, - "max":6, - "target":3 - } - }"#; - let schedule: BTreeMap = serde_json::from_str(s).unwrap(); - let hardfork_params = BlobScheduleBlobParams::from_schedule(&schedule); - let expected = BlobScheduleBlobParams { - cancun: BlobParams { - target_blob_count: 3, - max_blob_count: 6, - update_fraction: 3338477, - min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE, - }, - prague: BlobParams { - target_blob_count: 3, - max_blob_count: 6, - update_fraction: 3338477, - min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE, - }, - ..Default::default() - }; - assert_eq!(hardfork_params, expected); - } } diff --git a/crates/rpc/rpc-api/src/mev.rs b/crates/rpc/rpc-api/src/mev.rs index 4980b5cc671..76de76a079b 100644 --- a/crates/rpc/rpc-api/src/mev.rs +++ b/crates/rpc/rpc-api/src/mev.rs @@ -1,5 +1,5 @@ use alloy_rpc_types_mev::{ - SendBundleRequest, SendBundleResponse, SimBundleOverrides, SimBundleResponse, + EthBundleHash, SendBundleRequest, SimBundleOverrides, SimBundleResponse, }; use jsonrpsee::proc_macros::rpc; @@ -27,7 +27,7 @@ pub trait MevFullApi { async fn send_bundle( &self, request: SendBundleRequest, - ) -> jsonrpsee::core::RpcResult; + ) -> jsonrpsee::core::RpcResult; /// Similar to `mev_sendBundle` but instead of submitting a bundle to the relay, it returns /// a simulation result. Only fully matched bundles can be simulated. diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index 477fda2b1f5..fad5b7bc654 100644 --- a/crates/rpc/rpc-engine-api/tests/it/payload.rs +++ b/crates/rpc/rpc-engine-api/tests/it/payload.rs @@ -2,7 +2,7 @@ use alloy_eips::eip4895::Withdrawals; use alloy_primitives::Bytes; -use alloy_rlp::{Decodable, Error as RlpError}; +use alloy_rlp::Decodable; use alloy_rpc_types_engine::{ ExecutionPayload, ExecutionPayloadBodyV1, ExecutionPayloadSidecar, ExecutionPayloadV1, PayloadError, @@ -105,5 +105,5 @@ fn payload_validation_conversion() { *tx = Bytes::new(); }); let payload_with_invalid_txs = payload_with_invalid_txs.try_into_block::(); - assert_matches!(payload_with_invalid_txs, Err(PayloadError::Decode(RlpError::InputTooShort))); + assert_matches!(payload_with_invalid_txs, Err(PayloadError::Decode(_))); } diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 3802e102f99..4a7af2c8270 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -180,7 +180,7 @@ impl IntoRpcTx for EthereumTxEnvelope { type TxInfo = TransactionInfo; fn into_rpc_tx(self, signer: Address, tx_info: TransactionInfo) -> Transaction { - Transaction::from_transaction(self.with_signer(signer).convert(), tx_info) + Transaction::::from_transaction(self.with_signer(signer).convert(), tx_info) } } From 7bc6939d538fd25ab6caadfa66a9b368c46158d3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 17 Jun 2025 14:22:18 +0200 Subject: [PATCH 069/274] chore: use earliest block number from provider (#16848) --- crates/node/builder/src/launch/engine.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 18f106686f0..433f34bfb1e 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -33,7 +33,10 @@ use reth_node_core::{ primitives::Head, }; use reth_node_events::{cl::ConsensusLayerHealthEvents, node}; -use reth_provider::providers::{BlockchainProvider, NodeTypesForProvider}; +use reth_provider::{ + providers::{BlockchainProvider, NodeTypesForProvider}, + BlockNumReader, +}; use reth_stages::stages::EraImportSource; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; @@ -303,6 +306,7 @@ where .fuse(); let chainspec = ctx.chain_spec(); + let provider = ctx.blockchain_db().clone(); let (exit, rx) = oneshot::channel(); let terminate_after_backfill = ctx.terminate_after_initial_backfill(); @@ -355,8 +359,8 @@ where }; network_handle.update_status(head_block); - let updated=BlockRangeUpdate{ - earliest:0, + let updated = BlockRangeUpdate { + earliest: provider.earliest_block_number().unwrap_or_default(), latest:head.number(), latest_hash:head.hash() }; From 34ef2a27e0fbe9651b7ab2aceceb73b24cb55154 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 17 Jun 2025 14:31:19 +0200 Subject: [PATCH 070/274] feat: add RlpBincode helper (#16849) --- .../src/serde_bincode_compat.rs | 23 +++++++++++++ examples/custom-node/src/primitives/header.rs | 32 ++----------------- examples/custom-node/src/primitives/tx.rs | 17 ++-------- .../custom-node/src/primitives/tx_custom.rs | 17 ++-------- 4 files changed, 29 insertions(+), 60 deletions(-) diff --git a/crates/primitives-traits/src/serde_bincode_compat.rs b/crates/primitives-traits/src/serde_bincode_compat.rs index 8b3ca7a594b..fa18ffc0ad2 100644 --- a/crates/primitives-traits/src/serde_bincode_compat.rs +++ b/crates/primitives-traits/src/serde_bincode_compat.rs @@ -1,3 +1,5 @@ +use alloc::vec::Vec; +use alloy_primitives::Bytes; use core::fmt::Debug; use serde::{de::DeserializeOwned, Serialize}; @@ -39,6 +41,27 @@ impl SerdeBincodeCompat for alloy_consensus::Header { /// Type alias for the [`SerdeBincodeCompat::BincodeRepr`] associated type. pub type BincodeReprFor<'a, T> = ::BincodeRepr<'a>; +/// A helper trait for using RLP-encoding for providing bincode-compatible serialization. +/// +/// By implementing this trait, [`SerdeBincodeCompat`] will be automatically implemented for the +/// type and RLP encoding will be used for serialization and deserialization for bincode +/// compatibility. +pub trait RlpBincode: alloy_rlp::Encodable + alloy_rlp::Decodable {} + +impl SerdeBincodeCompat for T { + type BincodeRepr<'a> = Bytes; + + fn as_repr(&self) -> Self::BincodeRepr<'_> { + let mut buf = Vec::new(); + self.encode(&mut buf); + buf.into() + } + + fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { + Self::decode(&mut repr.as_ref()).expect("Failed to decode bincode rlp representation") + } +} + mod block_bincode { use crate::serde_bincode_compat::SerdeBincodeCompat; use alloc::{borrow::Cow, vec::Vec}; diff --git a/examples/custom-node/src/primitives/header.rs b/examples/custom-node/src/primitives/header.rs index 806560eaf84..0a832d690c9 100644 --- a/examples/custom-node/src/primitives/header.rs +++ b/examples/custom-node/src/primitives/header.rs @@ -4,7 +4,7 @@ use alloy_primitives::{ }; use alloy_rlp::{Encodable, RlpDecodable, RlpEncodable}; use reth_codecs::Compact; -use reth_ethereum::primitives::{BlockHeader, InMemorySize}; +use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, BlockHeader, InMemorySize}; use revm_primitives::keccak256; use serde::{Deserialize, Serialize}; @@ -183,32 +183,4 @@ impl reth_db_api::table::Decompress for CustomHeader { impl BlockHeader for CustomHeader {} -mod serde_bincode_compat { - use alloy_consensus::serde_bincode_compat::Header; - use reth_ethereum::primitives::serde_bincode_compat::SerdeBincodeCompat; - use serde::{Deserialize, Serialize}; - - #[derive(Serialize, Deserialize, Debug)] - pub struct CustomHeader<'a> { - inner: Header<'a>, - extension: u64, - } - - impl From> for super::CustomHeader { - fn from(value: CustomHeader) -> Self { - Self { inner: value.inner.into(), extension: value.extension } - } - } - - impl SerdeBincodeCompat for super::CustomHeader { - type BincodeRepr<'a> = CustomHeader<'a>; - - fn as_repr(&self) -> Self::BincodeRepr<'_> { - CustomHeader { inner: self.inner.as_repr(), extension: self.extension } - } - - fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { - repr.into() - } - } -} +impl RlpBincode for CustomHeader {} diff --git a/examples/custom-node/src/primitives/tx.rs b/examples/custom-node/src/primitives/tx.rs index 06840399528..b96ffed76ba 100644 --- a/examples/custom-node/src/primitives/tx.rs +++ b/examples/custom-node/src/primitives/tx.rs @@ -15,7 +15,7 @@ use reth_codecs::{ alloy::transaction::{FromTxCompact, ToTxCompact}, Compact, }; -use reth_ethereum::primitives::{serde_bincode_compat::SerdeBincodeCompat, InMemorySize}; +use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, InMemorySize}; use reth_op::{ primitives::{Extended, SignedTransaction}, OpTransaction, @@ -198,20 +198,7 @@ impl ToTxCompact for CustomTransactionEnvelope { } } -#[derive(Debug, Serialize, Deserialize)] -pub struct BincodeCompatSignedTxCustom(pub Signed); - -impl SerdeBincodeCompat for CustomTransactionEnvelope { - type BincodeRepr<'a> = BincodeCompatSignedTxCustom; - - fn as_repr(&self) -> Self::BincodeRepr<'_> { - BincodeCompatSignedTxCustom(self.inner.clone()) - } - - fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { - Self { inner: repr.0.clone() } - } -} +impl RlpBincode for CustomTransactionEnvelope {} impl reth_codecs::alloy::transaction::Envelope for CustomTransactionEnvelope { fn signature(&self) -> &Signature { diff --git a/examples/custom-node/src/primitives/tx_custom.rs b/examples/custom-node/src/primitives/tx_custom.rs index c44a5e9c4db..8ff92d2f626 100644 --- a/examples/custom-node/src/primitives/tx_custom.rs +++ b/examples/custom-node/src/primitives/tx_custom.rs @@ -7,7 +7,7 @@ use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization, Typed2718}; use alloy_primitives::{Address, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{BufMut, Decodable, Encodable}; use core::mem; -use reth_ethereum::primitives::{serde_bincode_compat::SerdeBincodeCompat, InMemorySize}; +use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, InMemorySize}; /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)). #[derive( @@ -285,17 +285,4 @@ impl InMemorySize for TxPayment { } } -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct BincodeCompatTxCustom(pub TxPayment); - -impl SerdeBincodeCompat for TxPayment { - type BincodeRepr<'a> = BincodeCompatTxCustom; - - fn as_repr(&self) -> Self::BincodeRepr<'_> { - BincodeCompatTxCustom(self.clone()) - } - - fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { - repr.0.clone() - } -} +impl RlpBincode for TxPayment {} From 41c93a1134fbd9bc66b16d9a41573e1954249eb1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 17 Jun 2025 14:42:37 +0200 Subject: [PATCH 071/274] chore: bump alloy 1.0.11 (#16853) --- Cargo.lock | 126 +++++++++--------- Cargo.toml | 64 ++++----- .../rpc/rpc-types-compat/src/transaction.rs | 2 +- 3 files changed, 96 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4365b8ada8..ac74a392d54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0dc46cb8a5322c5a65509622a111f1c3f4161f7bfaae883f0c939bcd50d0fd6" +checksum = "659c33e85c4a9f8bb1b9a2400f4f3d0dd52fbc4bd3650e08d22df1e17d5d92ee" dependencies = [ "alloy-eips", "alloy-primitives", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9a46cef9fa15c2fef07294e6ebf9c8913646b957dcfd2547041350679fa288" +checksum = "c711bfed1579611565ab831166c7bbaf123baea785ea945f02ed3620950f6fe1" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3165f56b57a11d2b77742ed2f48319ac9857de122855b43773bd0e26478131d" +checksum = "8390cb5c872d53560635dabc02d616c1bb626dd0f7d6893f8725edb822573fed" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833b10a7a4bece11e7e2cc2193894dcb07014fa5fdcb23ab4cfa94d4e0667581" +checksum = "a18ce1538291d8409d4a7d826176d461a6f9eb28632d7185f801bda43a138260" dependencies = [ "alloy-eips", "alloy-primitives", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83dc6510a04ef2db88fa26e6389814a6966edd7f01e56ef69f56964ee33c58a5" +checksum = "0b91481d12dcd964f4a838271d6abffac2d4082695fc3f73a15429166ea1692d" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd33e08e9922768f970fdf5bbc617fb8add3161d273e168d623dcdb4c45cd98" +checksum = "c8b245fa9d76cc9fc58cf78844f2d4e481333449ba679b2044f09b983fc96f85" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303e09afbbc561cf8ba16c9e860515c5c62bbaf7e622b004f00c6b09edf65020" +checksum = "ecac2cbea1cb3da53b4e68a078e57f9da8d12d86e2017db1240df222e2498397" dependencies = [ "alloy-chains", "alloy-consensus", @@ -474,9 +474,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5cf6a5e35d8ebb202fb924617c79616884e081fb3f766a4b6793b57960a16b" +checksum = "db1d3c2316590910ba697485aa75cdafef89735010d338d197f8af5baa79df92" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -517,9 +517,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2a479318b304148897bd6ee50fbbe94fa7f7c54fda6c33848af274a5c8351" +checksum = "e0bed8157038003c702dd1861a6b72d4b1a8f46aeffad35e81580223642170fa" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11e31c3a481349044899e47c73cca6047563bf359b203861627e53bea679a757" +checksum = "82fed036edc62cd79476fe0340277a1c47b07c173f6ac0244f24193e1183b8e4" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3111649049b68878d4c4c4897db4ec699dee7c9edd5b37643ade946b133bbf3d" +checksum = "b3ca809955fc14d8bd681470613295eacdc6e62515a13aa3871ab6f7decfd740" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -570,9 +570,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a8bf5eed865c9123b944ea9cd09c36a5c973b1605846a7cdc8ea7b6681702c" +checksum = "9f2e3dc925ec6722524f8d7412b9a6845a3350c7037f8a37892ada00c9018125" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -593,9 +593,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9caa1bec0258fbebc7803d7da33e250de2d1c6e97e34a2b8a4ece83b31d8de43" +checksum = "497cf019e28c3538d83b3791b780047792a453c693fcca9ded49d9c81550663e" dependencies = [ "alloy-eips", "alloy-primitives", @@ -611,9 +611,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395dd78499c49adaf6a078f1ccb5856359c5f678306484a3d631511649b37a61" +checksum = "0e982f72ff47c0f754cb6aa579e456220d768e1ec07675e66cfce970dad70292" dependencies = [ "alloy-primitives", "serde", @@ -621,9 +621,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49377b741a373b8e3b69b90abf40ec255c1f6a344ab4156c2a0e4ee9204c5996" +checksum = "505224e162e239980c6df7632c99f0bc5abbcf630017502810979e9e01f3c86e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5740de94e37e8977aef7d8e084eba8fbce485c43f04d32de55b629e42b32f0" +checksum = "20ff509ca40537042b7cc9bede6b415ef807c9c5c48024e9fe10b8c8ad0757ef" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -655,7 +655,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arbitrary", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "thiserror 2.0.12", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f83a35afc1e29e3556f0a01735a3b1aae5185511f6cef0843befa5e99bb2ac3" +checksum = "6bfb385704970757f21cfc6f79adc22c743523242d032c9ca42d70773a0f7b7c" dependencies = [ "alloy-consensus", "alloy-eips", @@ -678,9 +678,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9d38c9df26dc9824e5e5a81c42879f5793f89adaff1fbe25833c52c4103d75" +checksum = "51dc49d5865f2227c810a416c8d14141db7716a0174bfa6cff1c1a984b678b5e" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -692,9 +692,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d078f0cc054bb846f2b3f9b92cef2502459f9bf67bea94049e877d4fb07bc0" +checksum = "c962ec5193084873353ad7a65568056b4e704203302e6ba81374e95a22deba4d" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61b6134af56345296cc2f624ebf621f79330cb8a80b0d7bb29e4e41ee729dcaf" +checksum = "f9873512b1e99505f4a65e1d3a3105cb689f112f8e3cab3c632b20a97a46adae" dependencies = [ "alloy-primitives", "arbitrary", @@ -716,9 +716,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9fbc2e1283d3e939d4f203fdb00922dcbb231db75b9db5b8de3c4ef4be3e2e" +checksum = "c2d4d95d8431a11e0daee724c3b7635dc8e9d3d60d0b803023a8125c74a77899" dependencies = [ "alloy-primitives", "async-trait", @@ -731,9 +731,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aaf3564568c9c2199375dbf6b3520cc3f71997c2513ba544dda9a540e5e63cf" +checksum = "cb03eca937485b258d8e791d143e95b50dbfae0e18f92e1b1271c38959cd00fb" dependencies = [ "alloy-consensus", "alloy-network", @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf50e4efa5b4f91c6050ce558cc06c6890d7db754246e534acc3ac76c796d9" +checksum = "468a871d7ea52e31ef3abf5ccde612cb3723794f484d26dca6a04a3a776db739" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -842,9 +842,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aceee2aecf8f2c38e6c5bdf2703bf2bed32ebd91121b5640dbc23818e97f9" +checksum = "6e969c254b189f7da95f07bab53673dd418f8595abfe3397b2cf8d7ba7955487" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -857,9 +857,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "174da2b07cca2e0438ec49ca12b065f75ff2e3c21a237b26d8478d14ec477aac" +checksum = "cb134aaa80c2e1e03eebc101e7c513f08a529726738506d8c306ec9f3c9a7f3b" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5933ae8a0256290c7310b788d046604f2e46d4e2e1f305f1dd5437cb610fbc3c" +checksum = "e57f13346af9441cafa99d5b80d95c2480870dd18bd274464f7131df01ad692a" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -5942,9 +5942,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c63a84d99657aa23b7bd7393ceca738200e14e4152e5bba5835210f3329bdb17" +checksum = "af5ca65048a25811d284618d45f8c3c7911a56ea9dad52bd0e827f703a282077" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5968,9 +5968,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df19c524993ebd1030cdbf6bac0ab7c2b57c76eefe4cf64728f021b6d2b239c8" +checksum = "c57bb857b905b2002df39fce8e3d8a1604a9b338092cf91af431dc60dd81c5c9" dependencies = [ "alloy-consensus", "alloy-network", @@ -5984,9 +5984,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedc46e9756cf7b1041422e091727449c1b800513e9973ef0f96aa293328c0ed" +checksum = "e0cd410cb51886914d002ac27b82c387473f8f578ec25cb910e18e7e4b1b6ba7" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5994,9 +5994,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cc10cd568d40251452b438897dbe010f97d85491e6a777f9bfe4b33dbd9c46" +checksum = "f7ac804bff85a0ab18f67cc13f76fb6450c06530791c1412e71dc1ac7c7f6338" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6013,9 +6013,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9202f24fa1f918c6ea72f57247674d22783c1a920585013f34ac54951fc8d91a" +checksum = "5260b3018bde2f290c1b3bccb1b007c281373345e9d4889790950e595996af82" dependencies = [ "alloy-consensus", "alloy-eips", diff --git a/Cargo.toml b/Cargo.toml index a9ae78552d9..ccb384dbded 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -474,42 +474,42 @@ alloy-trie = { version = "0.8.1", default-features = false } alloy-hardforks = "0.2.2" -alloy-consensus = { version = "1.0.10", default-features = false } -alloy-contract = { version = "1.0.10", default-features = false } -alloy-eips = { version = "1.0.10", default-features = false } -alloy-genesis = { version = "1.0.10", default-features = false } -alloy-json-rpc = { version = "1.0.10", default-features = false } -alloy-network = { version = "1.0.10", default-features = false } -alloy-network-primitives = { version = "1.0.10", default-features = false } -alloy-provider = { version = "1.0.10", features = ["reqwest"], default-features = false } -alloy-pubsub = { version = "1.0.10", default-features = false } -alloy-rpc-client = { version = "1.0.10", default-features = false } -alloy-rpc-types = { version = "1.0.10", features = ["eth"], default-features = false } -alloy-rpc-types-admin = { version = "1.0.10", default-features = false } -alloy-rpc-types-anvil = { version = "1.0.10", default-features = false } -alloy-rpc-types-beacon = { version = "1.0.10", default-features = false } -alloy-rpc-types-debug = { version = "1.0.10", default-features = false } -alloy-rpc-types-engine = { version = "1.0.10", default-features = false } -alloy-rpc-types-eth = { version = "1.0.10", default-features = false } -alloy-rpc-types-mev = { version = "1.0.10", default-features = false } -alloy-rpc-types-trace = { version = "1.0.10", default-features = false } -alloy-rpc-types-txpool = { version = "1.0.10", default-features = false } -alloy-serde = { version = "1.0.10", default-features = false } -alloy-signer = { version = "1.0.10", default-features = false } -alloy-signer-local = { version = "1.0.10", default-features = false } -alloy-transport = { version = "1.0.10" } -alloy-transport-http = { version = "1.0.10", features = ["reqwest-rustls-tls"], default-features = false } -alloy-transport-ipc = { version = "1.0.10", default-features = false } -alloy-transport-ws = { version = "1.0.10", default-features = false } +alloy-consensus = { version = "1.0.11", default-features = false } +alloy-contract = { version = "1.0.11", default-features = false } +alloy-eips = { version = "1.0.11", default-features = false } +alloy-genesis = { version = "1.0.11", default-features = false } +alloy-json-rpc = { version = "1.0.11", default-features = false } +alloy-network = { version = "1.0.11", default-features = false } +alloy-network-primitives = { version = "1.0.11", default-features = false } +alloy-provider = { version = "1.0.11", features = ["reqwest"], default-features = false } +alloy-pubsub = { version = "1.0.11", default-features = false } +alloy-rpc-client = { version = "1.0.11", default-features = false } +alloy-rpc-types = { version = "1.0.11", features = ["eth"], default-features = false } +alloy-rpc-types-admin = { version = "1.0.11", default-features = false } +alloy-rpc-types-anvil = { version = "1.0.11", default-features = false } +alloy-rpc-types-beacon = { version = "1.0.11", default-features = false } +alloy-rpc-types-debug = { version = "1.0.11", default-features = false } +alloy-rpc-types-engine = { version = "1.0.11", default-features = false } +alloy-rpc-types-eth = { version = "1.0.11", default-features = false } +alloy-rpc-types-mev = { version = "1.0.11", default-features = false } +alloy-rpc-types-trace = { version = "1.0.11", default-features = false } +alloy-rpc-types-txpool = { version = "1.0.11", default-features = false } +alloy-serde = { version = "1.0.11", default-features = false } +alloy-signer = { version = "1.0.11", default-features = false } +alloy-signer-local = { version = "1.0.11", default-features = false } +alloy-transport = { version = "1.0.11" } +alloy-transport-http = { version = "1.0.11", features = ["reqwest-rustls-tls"], default-features = false } +alloy-transport-ipc = { version = "1.0.11", default-features = false } +alloy-transport-ws = { version = "1.0.11", default-features = false } # op alloy-op-evm = { version = "0.11", default-features = false } alloy-op-hardforks = "0.2.2" -op-alloy-rpc-types = { version = "0.18.2", default-features = false } -op-alloy-rpc-types-engine = { version = "0.18.2", default-features = false } -op-alloy-network = { version = "0.18.2", default-features = false } -op-alloy-consensus = { version = "0.18.2", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.18.2", default-features = false } +op-alloy-rpc-types = { version = "0.18.4", default-features = false } +op-alloy-rpc-types-engine = { version = "0.18.4", default-features = false } +op-alloy-network = { version = "0.18.4", default-features = false } +op-alloy-consensus = { version = "0.18.4", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.18.4", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # misc diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 4a7af2c8270..3802e102f99 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -180,7 +180,7 @@ impl IntoRpcTx for EthereumTxEnvelope { type TxInfo = TransactionInfo; fn into_rpc_tx(self, signer: Address, tx_info: TransactionInfo) -> Transaction { - Transaction::::from_transaction(self.with_signer(signer).convert(), tx_info) + Transaction::from_transaction(self.with_signer(signer).convert(), tx_info) } } From a38428eb05517f7ff5992fa33b401b184d474019 Mon Sep 17 00:00:00 2001 From: Bilog WEB3 <155262265+Bilogweb3@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:50:04 +0200 Subject: [PATCH 072/274] docs: update comment for is_eip7702() method (#16852) --- crates/transaction-pool/src/test_utils/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 9612ad5ee9d..9ddde67ba59 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -653,7 +653,7 @@ impl MockTransaction { matches!(self, Self::Eip2930 { .. }) } - /// Checks if the transaction is of the EIP-2930 type. + /// Checks if the transaction is of the EIP-7702 type. pub const fn is_eip7702(&self) -> bool { matches!(self, Self::Eip7702 { .. }) } From 71a057bcbe7f69ec37ea5528f7424f6c16885ef7 Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Tue, 17 Jun 2025 21:27:22 +0800 Subject: [PATCH 073/274] perf: Reduce unnecessary memory copies in `compare_storage_trie_updates` (#16841) Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> --- crates/engine/tree/src/tree/mod.rs | 11 ++++++----- crates/engine/tree/src/tree/trie_updates.rs | 17 ++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 26cc096535f..cf2ba9ab0c3 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -1505,8 +1505,8 @@ where fn canonical_block_by_hash(&self, hash: B256) -> ProviderResult>> { trace!(target: "engine::tree", ?hash, "Fetching executed block by hash"); // check memory first - if let Some(block) = self.state.tree_state.executed_block_by_hash(hash).cloned() { - return Ok(Some(block.block)) + if let Some(block) = self.state.tree_state.executed_block_by_hash(hash) { + return Ok(Some(block.block.clone())) } let (block, senders) = self @@ -1914,12 +1914,13 @@ where let old = old .iter() .filter_map(|block| { - let (_, trie) = self + let trie = self .state .tree_state .persisted_trie_updates - .get(&block.recovered_block.hash()) - .cloned()?; + .get(&block.recovered_block.hash())? + .1 + .clone(); Some(ExecutedBlockWithTrieUpdates { block: block.clone(), trie: ExecutedTrieUpdates::Present(trie), diff --git a/crates/engine/tree/src/tree/trie_updates.rs b/crates/engine/tree/src/tree/trie_updates.rs index 4f2e3c40eb1..e84cfe6e564 100644 --- a/crates/engine/tree/src/tree/trie_updates.rs +++ b/crates/engine/tree/src/tree/trie_updates.rs @@ -218,22 +218,21 @@ fn compare_storage_trie_updates( // compare removed nodes let mut storage_trie_cursor = trie_cursor()?; - for key in task - .removed_nodes - .iter() - .chain(regular.removed_nodes.iter()) - .cloned() - .collect::>() + for key in + task.removed_nodes.iter().chain(regular.removed_nodes.iter()).collect::>() { let (task_removed, regular_removed) = - (task.removed_nodes.contains(&key), regular.removed_nodes.contains(&key)); + (task.removed_nodes.contains(key), regular.removed_nodes.contains(key)); + if task_removed == regular_removed { + continue; + } let database_not_exists = storage_trie_cursor.seek_exact(key.clone())?.map(|x| x.1).is_none(); // If the deletion is a no-op, meaning that the entry is not in the // database, do not add it to the diff. - if task_removed != regular_removed && !database_not_exists { + if !database_not_exists { diff.removed_nodes.insert( - key, + key.clone(), EntryDiff { task: task_removed, regular: regular_removed, From 576cef4b13c236d78bf3a7049518922b081df8fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 17 Jun 2025 15:33:10 +0200 Subject: [PATCH 074/274] feat(rpc): Implement `FromConsensusTx` for generic `OpTransaction` (#16832) --- crates/rpc/rpc-types-compat/src/transaction.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 3802e102f99..625cf585b27 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -2,8 +2,7 @@ use crate::fees::{CallFees, CallFeesError}; use alloy_consensus::{ - error::ValueError, transaction::Recovered, EthereumTxEnvelope, SignableTransaction, - Transaction as ConsensusTransaction, TxEip4844, + error::ValueError, transaction::Recovered, EthereumTxEnvelope, SignableTransaction, TxEip4844, }; use alloy_network::Network; use alloy_primitives::{Address, Bytes, Signature, TxKind, U256}; @@ -124,7 +123,7 @@ pub trait FromConsensusTx { fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self; } -impl FromConsensusTx for Transaction { +impl FromConsensusTx for Transaction { type TxInfo = TransactionInfo; fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { @@ -149,7 +148,7 @@ impl FromConsensusTx for Transaction { impl IntoRpcTx for ConsensusTx where - ConsensusTx: ConsensusTransaction, + ConsensusTx: alloy_consensus::Transaction, RpcTx: FromConsensusTx, { type TxInfo = RpcTx::TxInfo; @@ -226,11 +225,13 @@ pub fn try_into_op_tx_info>( Ok(OpTransactionInfo::new(tx_info, deposit_meta)) } -impl FromConsensusTx for op_alloy_rpc_types::Transaction { +impl FromConsensusTx + for op_alloy_rpc_types::Transaction +{ type TxInfo = OpTransactionInfo; - fn from_consensus_tx(tx: OpTxEnvelope, signer: Address, tx_info: Self::TxInfo) -> Self { - Self::from_transaction(tx.with_signer(signer), tx_info) + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { + Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info) } } From bcb4fd3711cc651ff86f4ff521329de899c7350e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 17 Jun 2025 16:06:49 +0200 Subject: [PATCH 075/274] feat(rpc): Replace manual `IntoRpcTx` implementation with `FromConsensusTx` using an additional generic (#16855) --- crates/rpc/rpc-types-compat/src/transaction.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 625cf585b27..bbef07474ee 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -22,7 +22,7 @@ use reth_evm::{ ConfigureEvm, TxEnvFor, }; use reth_optimism_primitives::DepositReceipt; -use reth_primitives_traits::{NodePrimitives, SignedTransaction, TxTy}; +use reth_primitives_traits::{NodePrimitives, TxTy}; use reth_storage_api::{errors::ProviderError, ReceiptProvider}; use revm_context::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Serialize}; @@ -123,10 +123,12 @@ pub trait FromConsensusTx { fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self; } -impl FromConsensusTx for Transaction { +impl> + FromConsensusTx for Transaction +{ type TxInfo = TransactionInfo; - fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { + fn from_consensus_tx(tx: TxIn, signer: Address, tx_info: Self::TxInfo) -> Self { let TransactionInfo { block_hash, block_number, index: transaction_index, base_fee, .. } = tx_info; @@ -137,7 +139,7 @@ impl FromConsensusTx for Transaction { .unwrap_or_else(|| tx.max_fee_per_gas()); Self { - inner: Recovered::new_unchecked(tx, signer), + inner: Recovered::new_unchecked(tx, signer).convert(), block_hash, block_number, transaction_index, @@ -175,14 +177,6 @@ where fn try_into_sim_tx(self) -> Result>; } -impl IntoRpcTx for EthereumTxEnvelope { - type TxInfo = TransactionInfo; - - fn into_rpc_tx(self, signer: Address, tx_info: TransactionInfo) -> Transaction { - Transaction::from_transaction(self.with_signer(signer).convert(), tx_info) - } -} - /// Adds extra context to [`TransactionInfo`]. pub trait TxInfoMapper { /// An associated output type that carries [`TransactionInfo`] with some extra context. From dd1d42655582f0d5e9cb3255bd46e181ab58c1d8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 17 Jun 2025 16:40:08 +0200 Subject: [PATCH 076/274] perf: avoid duplicate peer lookup (#16846) --- crates/net/network/src/transactions/mod.rs | 142 ++++++++++----------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index 78e55c344ff..e09c6dc6b26 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -1357,91 +1357,89 @@ where // tracks the quality of the given transactions let mut has_bad_transactions = false; - // 2. filter out transactions that are invalid or already pending import - if let Some(peer) = self.peers.get_mut(&peer_id) { - // pre-size to avoid reallocations - let mut new_txs = Vec::with_capacity(transactions.len()); - for tx in transactions { - // recover transaction - let tx = match tx.try_into_recovered() { - Ok(tx) => tx, - Err(badtx) => { + // 2. filter out transactions that are invalid or already pending import pre-size to avoid + // reallocations + let mut new_txs = Vec::with_capacity(transactions.len()); + for tx in transactions { + // recover transaction + let tx = match tx.try_into_recovered() { + Ok(tx) => tx, + Err(badtx) => { + trace!(target: "net::tx", + peer_id=format!("{peer_id:#}"), + hash=%badtx.tx_hash(), + client_version=%peer.client_version, + "failed ecrecovery for transaction" + ); + has_bad_transactions = true; + continue + } + }; + + match self.transactions_by_peers.entry(*tx.tx_hash()) { + Entry::Occupied(mut entry) => { + // transaction was already inserted + entry.get_mut().insert(peer_id); + } + Entry::Vacant(entry) => { + if self.bad_imports.contains(tx.tx_hash()) { trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), - hash=%badtx.tx_hash(), + hash=%tx.tx_hash(), client_version=%peer.client_version, - "failed ecrecovery for transaction" + "received a known bad transaction from peer" ); has_bad_transactions = true; - continue - } - }; + } else { + // this is a new transaction that should be imported into the pool - match self.transactions_by_peers.entry(*tx.tx_hash()) { - Entry::Occupied(mut entry) => { - // transaction was already inserted - entry.get_mut().insert(peer_id); - } - Entry::Vacant(entry) => { - if self.bad_imports.contains(tx.tx_hash()) { - trace!(target: "net::tx", - peer_id=format!("{peer_id:#}"), - hash=%tx.tx_hash(), - client_version=%peer.client_version, - "received a known bad transaction from peer" - ); - has_bad_transactions = true; - } else { - // this is a new transaction that should be imported into the pool - - let pool_transaction = Pool::Transaction::from_pooled(tx); - new_txs.push(pool_transaction); - - entry.insert(HashSet::from([peer_id])); - } + let pool_transaction = Pool::Transaction::from_pooled(tx); + new_txs.push(pool_transaction); + + entry.insert(HashSet::from([peer_id])); } } } - new_txs.shrink_to_fit(); + } + new_txs.shrink_to_fit(); - // 3. import new transactions as a batch to minimize lock contention on the underlying - // pool - if !new_txs.is_empty() { - let pool = self.pool.clone(); - // update metrics - let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone(); - metric_pending_pool_imports.increment(new_txs.len() as f64); + // 3. import new transactions as a batch to minimize lock contention on the underlying + // pool + if !new_txs.is_empty() { + let pool = self.pool.clone(); + // update metrics + let metric_pending_pool_imports = self.metrics.pending_pool_imports.clone(); + metric_pending_pool_imports.increment(new_txs.len() as f64); + + // update self-monitoring info + self.pending_pool_imports_info + .pending_pool_imports + .fetch_add(new_txs.len(), Ordering::Relaxed); + let tx_manager_info_pending_pool_imports = + self.pending_pool_imports_info.pending_pool_imports.clone(); + + trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions"); + let import = Box::pin(async move { + let added = new_txs.len(); + let res = pool.add_external_transactions(new_txs).await; + // update metrics + metric_pending_pool_imports.decrement(added as f64); // update self-monitoring info - self.pending_pool_imports_info - .pending_pool_imports - .fetch_add(new_txs.len(), Ordering::Relaxed); - let tx_manager_info_pending_pool_imports = - self.pending_pool_imports_info.pending_pool_imports.clone(); - - trace!(target: "net::tx::propagation", new_txs_len=?new_txs.len(), "Importing new transactions"); - let import = Box::pin(async move { - let added = new_txs.len(); - let res = pool.add_external_transactions(new_txs).await; - - // update metrics - metric_pending_pool_imports.decrement(added as f64); - // update self-monitoring info - tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed); - - res - }); - - self.pool_imports.push(import); - } + tx_manager_info_pending_pool_imports.fetch_sub(added, Ordering::Relaxed); - if num_already_seen_by_peer > 0 { - self.metrics.messages_with_transactions_already_seen_by_peer.increment(1); - self.metrics - .occurrences_of_transaction_already_seen_by_peer - .increment(num_already_seen_by_peer); - trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=?peer.client_version, "Peer sent already seen transactions"); - } + res + }); + + self.pool_imports.push(import); + } + + if num_already_seen_by_peer > 0 { + self.metrics.messages_with_transactions_already_seen_by_peer.increment(1); + self.metrics + .occurrences_of_transaction_already_seen_by_peer + .increment(num_already_seen_by_peer); + trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=?peer.client_version, "Peer sent already seen transactions"); } if has_bad_transactions { From d6eb7891091741bb404b303d80de4627bc2a9765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 17 Jun 2025 16:47:34 +0200 Subject: [PATCH 077/274] test(chainspec): Test conversion from blob params in genesis config (#16854) --- crates/chainspec/src/spec.rs | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index a2ada0621ee..2ed1f769608 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -1033,6 +1033,7 @@ mod tests { use super::*; use alloy_chains::Chain; use alloy_consensus::constants::ETH_TO_WEI; + use alloy_eips::{eip4844::BLOB_TX_MIN_BLOB_GASPRICE, eip7840::BlobParams}; use alloy_evm::block::calc::{base_block_reward, block_reward}; use alloy_genesis::{ChainConfig, GenesisAccount}; use alloy_primitives::{b256, hex}; @@ -2501,4 +2502,42 @@ Post-merge hard forks (timestamp based): assert_eq!(block_reward(base_reward, num_ommers), expected_reward); } } + + #[test] + fn blob_params_from_genesis() { + let s = r#"{ + "blobSchedule": { + "cancun":{ + "baseFeeUpdateFraction":3338477, + "max":6, + "target":3 + }, + "prague":{ + "baseFeeUpdateFraction":3338477, + "max":6, + "target":3 + } + } + }"#; + let config: ChainConfig = serde_json::from_str(s).unwrap(); + let hardfork_params = config.blob_schedule_blob_params(); + let expected = BlobScheduleBlobParams { + cancun: BlobParams { + target_blob_count: 3, + max_blob_count: 6, + update_fraction: 3338477, + min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE, + max_blobs_per_tx: 6, + }, + prague: BlobParams { + target_blob_count: 3, + max_blob_count: 6, + update_fraction: 3338477, + min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE, + max_blobs_per_tx: 6, + }, + ..Default::default() + }; + assert_eq!(hardfork_params, expected); + } } From 8857c5da030056068d083792e034b35160815a2c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 17 Jun 2025 17:32:01 +0200 Subject: [PATCH 078/274] fix: handle forced propagations (#16845) --- crates/net/network/src/transactions/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index e09c6dc6b26..7c73025a213 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -1166,7 +1166,8 @@ where } TransactionsCommand::PropagateTransactions(txs) => self.propagate_all(txs), TransactionsCommand::BroadcastTransactions(txs) => { - self.propagate_transactions(txs, PropagationMode::Forced); + let propagated = self.propagate_transactions(txs, PropagationMode::Forced); + self.pool.on_propagated(propagated); } TransactionsCommand::GetTransactionHashes { peers, tx } => { let mut res = HashMap::with_capacity(peers.len()); From 7f815bbd8d45fcef59a25eba125dc49269fcf4c3 Mon Sep 17 00:00:00 2001 From: Eth161dm <115899965+Eth161dm@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:32:33 +0200 Subject: [PATCH 079/274] fix: dead link in tracking-state.md (#16857) --- book/developers/exex/tracking-state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/developers/exex/tracking-state.md b/book/developers/exex/tracking-state.md index d2a9fe6ca3e..92e4ee0f184 100644 --- a/book/developers/exex/tracking-state.md +++ b/book/developers/exex/tracking-state.md @@ -27,7 +27,7 @@ but let's unpack what's going on here: 1. Our ExEx is now a `struct` that contains the context and implements the `Future` trait. It's now pollable (hence `await`-able). 1. We can't use `self` directly inside our `poll` method, and instead need to acquire a mutable reference to the data inside of the `Pin`. - Read more about pinning in [the book](https://rust-lang.github.io/async-book/04_pinning/01_chapter.html). + Read more about pinning in [the book](https://rust-lang.github.io/async-book/part-reference/pinning.html). 1. We also can't use `await` directly inside `poll`, and instead need to poll futures manually. We wrap the call to `poll_recv(cx)` into a [`ready!`](https://doc.rust-lang.org/std/task/macro.ready.html) macro, so that if the channel of notifications has no value ready, we will instantly return `Poll::Pending` from our Future. From 58cfd2e02be3bead9742c5e8fe412c0dbb196466 Mon Sep 17 00:00:00 2001 From: Alessandro Mazza <121622391+alessandromazza98@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:55:47 +0200 Subject: [PATCH 080/274] fix(provider): fix doc comments errors (#16749) Co-authored-by: Matthias Seitz --- .../src/providers/database/provider.rs | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 27dc5266893..38dffcc88df 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -452,18 +452,20 @@ impl< } } -/// For a given key, unwind all history shards that are below the given block number. +/// For a given key, unwind all history shards that contain block numbers at or above the given +/// block number. /// /// S - Sharded key subtype. /// T - Table to walk over. /// C - Cursor implementation. /// /// This function walks the entries from the given start key and deletes all shards that belong to -/// the key and are below the given block number. +/// the key and contain block numbers at or above the given block number. Shards entirely below +/// the block number are preserved. /// -/// The boundary shard (the shard is split by the block number) is removed from the database. Any -/// indices that are above the block number are filtered out. The boundary shard is returned for -/// reinsertion (if it's not empty). +/// The boundary shard (the shard that spans across the block number) is removed from the database. +/// Any indices that are below the block number are filtered out and returned for reinsertion. +/// The boundary shard is returned for reinsertion (if it's not empty). fn unwind_history_shards( cursor: &mut C, start_key: T::Key, @@ -475,27 +477,41 @@ where T::Key: AsRef>, C: DbCursorRO + DbCursorRW, { + // Start from the given key and iterate through shards let mut item = cursor.seek_exact(start_key)?; while let Some((sharded_key, list)) = item { // If the shard does not belong to the key, break. if !shard_belongs_to_key(&sharded_key) { break } + + // Always delete the current shard from the database first + // We'll decide later what (if anything) to reinsert cursor.delete_current()?; - // Check the first item. - // If it is greater or eq to the block number, delete it. + // Get the first (lowest) block number in this shard + // All block numbers in a shard are sorted in ascending order let first = list.iter().next().expect("List can't be empty"); + + // Case 1: Entire shard is at or above the unwinding point + // Keep it deleted (don't return anything for reinsertion) if first >= block_number { item = cursor.prev()?; continue - } else if block_number <= sharded_key.as_ref().highest_block_number { - // Filter out all elements greater than block number. + } + // Case 2: This is a boundary shard (spans across the unwinding point) + // The shard contains some blocks below and some at/above the unwinding point + else if block_number <= sharded_key.as_ref().highest_block_number { + // Return only the block numbers that are below the unwinding point + // These will be reinserted to preserve the historical data return Ok(list.iter().take_while(|i| *i < block_number).collect::>()) } + // Case 3: Entire shard is below the unwinding point + // Return all block numbers for reinsertion (preserve entire shard) return Ok(list.iter().collect::>()) } + // No shards found or all processed Ok(Vec::new()) } From 051cef53bc7372c22d545a583427b0e598529e87 Mon Sep 17 00:00:00 2001 From: Josh_dfG <126518346+JoshdfG@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:14:11 +0100 Subject: [PATCH 081/274] chore: add rpc-compat feature in reth primitives-traits (#16608) Co-authored-by: Matthias Seitz --- Cargo.lock | 1 + crates/primitives-traits/Cargo.toml | 5 + .../primitives-traits/src/block/recovered.rs | 199 ++++++++++++++++++ crates/primitives-traits/src/lib.rs | 2 + crates/rpc/rpc-eth-api/Cargo.toml | 2 +- crates/rpc/rpc-eth-api/src/helpers/block.rs | 6 +- crates/rpc/rpc-eth-types/Cargo.toml | 2 +- crates/rpc/rpc-eth-types/src/simulate.rs | 4 +- crates/rpc/rpc-types-compat/src/block.rs | 102 --------- crates/rpc/rpc-types-compat/src/lib.rs | 2 - 10 files changed, 215 insertions(+), 110 deletions(-) delete mode 100644 crates/rpc/rpc-types-compat/src/block.rs diff --git a/Cargo.lock b/Cargo.lock index ac74a392d54..a01ed82a10d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9548,6 +9548,7 @@ dependencies = [ "alloy-genesis", "alloy-primitives", "alloy-rlp", + "alloy-rpc-types-eth", "alloy-trie", "arbitrary", "auto_impl", diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index f4dcb4dcf3b..12bce2a1e5c 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -50,6 +50,7 @@ arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } proptest-arbitrary-interop = { workspace = true, optional = true } rayon = { workspace = true, optional = true } +alloy-rpc-types-eth = { workspace = true, optional = true } [dev-dependencies] reth-codecs.workspace = true @@ -93,6 +94,7 @@ std = [ "reth-chainspec/std", "revm-bytecode/std", "revm-state/std", + "alloy-rpc-types-eth?/std", ] secp256k1 = ["alloy-consensus/secp256k1"] test-utils = [ @@ -115,6 +117,7 @@ arbitrary = [ "op-alloy-consensus?/arbitrary", "alloy-trie/arbitrary", "reth-chainspec/arbitrary", + "alloy-rpc-types-eth?/arbitrary", ] serde-bincode-compat = [ "serde", @@ -140,6 +143,7 @@ serde = [ "revm-bytecode/serde", "revm-state/serde", "rand_08/serde", + "alloy-rpc-types-eth?/serde", ] reth-codec = [ "dep:reth-codecs", @@ -153,3 +157,4 @@ op = [ rayon = [ "dep:rayon", ] +rpc-compat = ["alloy-rpc-types-eth"] diff --git a/crates/primitives-traits/src/block/recovered.rs b/crates/primitives-traits/src/block/recovered.rs index 44631cd38ec..3b45dd46acc 100644 --- a/crates/primitives-traits/src/block/recovered.rs +++ b/crates/primitives-traits/src/block/recovered.rs @@ -532,6 +532,205 @@ impl RecoveredBlock { } } +#[cfg(feature = "rpc-compat")] +mod rpc_compat { + use super::{ + Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction, + }; + use crate::block::error::BlockRecoveryError; + use alloc::vec::Vec; + use alloy_consensus::{ + transaction::Recovered, Block as CBlock, BlockBody, BlockHeader, Sealable, + }; + use alloy_primitives::U256; + use alloy_rpc_types_eth::{ + Block, BlockTransactions, BlockTransactionsKind, Header, TransactionInfo, + }; + + impl RecoveredBlock + where + B: BlockTrait, + { + /// Converts the block block into an RPC [`Block`] instance with the given + /// [`BlockTransactionsKind`]. + /// + /// The `tx_resp_builder` closure is used to build the transaction response for each + /// transaction. + pub fn into_rpc_block( + self, + kind: BlockTransactionsKind, + tx_resp_builder: F, + ) -> Result>, E> + where + F: Fn( + Recovered<<::Body as BlockBodyTrait>::Transaction>, + TransactionInfo, + ) -> Result, + { + match kind { + BlockTransactionsKind::Hashes => Ok(self.into_rpc_block_with_tx_hashes()), + BlockTransactionsKind::Full => self.into_rpc_block_full(tx_resp_builder), + } + } + + /// Clones the block and converts it into a [`Block`] response with the given + /// [`BlockTransactionsKind`] + /// + /// This is a convenience method that avoids the need to explicitly clone the block + /// before calling [`Self::into_rpc_block`]. For transaction hashes, it only clones + /// the necessary parts for better efficiency. + /// + /// The `tx_resp_builder` closure is used to build the transaction response for each + /// transaction. + pub fn clone_into_rpc_block( + &self, + kind: BlockTransactionsKind, + tx_resp_builder: F, + ) -> Result>, E> + where + F: Fn( + Recovered<<::Body as BlockBodyTrait>::Transaction>, + TransactionInfo, + ) -> Result, + { + match kind { + BlockTransactionsKind::Hashes => Ok(self.to_rpc_block_with_tx_hashes()), + BlockTransactionsKind::Full => self.clone().into_rpc_block_full(tx_resp_builder), + } + } + + /// Create a new [`Block`] instance from a [`RecoveredBlock`] reference. + /// + /// This will populate the `transactions` field with only the hashes of the transactions in + /// the block: [`BlockTransactions::Hashes`] + /// + /// This method only clones the necessary parts and avoids cloning the entire block. + pub fn to_rpc_block_with_tx_hashes(&self) -> Block> { + let transactions = self.body().transaction_hashes_iter().copied().collect(); + let rlp_length = self.rlp_length(); + let header = self.clone_sealed_header(); + let withdrawals = self.body().withdrawals().cloned(); + + let transactions = BlockTransactions::Hashes(transactions); + let uncles = + self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect(); + let header = Header::from_consensus(header.into(), None, Some(U256::from(rlp_length))); + + Block { header, uncles, transactions, withdrawals } + } + + /// Create a new [`Block`] response from a [`RecoveredBlock`], using the + /// total difficulty to populate its field in the rpc response. + /// + /// This will populate the `transactions` field with only the hashes of the transactions in + /// the block: [`BlockTransactions::Hashes`] + pub fn into_rpc_block_with_tx_hashes(self) -> Block> { + let transactions = self.body().transaction_hashes_iter().copied().collect(); + let rlp_length = self.rlp_length(); + let (header, body) = self.into_sealed_block().split_sealed_header_body(); + let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body(); + + let transactions = BlockTransactions::Hashes(transactions); + let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect(); + let header = Header::from_consensus(header.into(), None, Some(U256::from(rlp_length))); + + Block { header, uncles, transactions, withdrawals } + } + + /// Create a new [`Block`] response from a [`RecoveredBlock`], using the given closure to + /// create the rpc transactions. + /// + /// This will populate the `transactions` field with the _full_ + /// transaction objects: [`BlockTransactions::Full`] + pub fn into_rpc_block_full( + self, + tx_resp_builder: F, + ) -> Result>, E> + where + F: Fn( + Recovered<<::Body as BlockBodyTrait>::Transaction>, + TransactionInfo, + ) -> Result, + { + let block_number = self.header().number(); + let base_fee = self.header().base_fee_per_gas(); + let block_length = self.rlp_length(); + let block_hash = Some(self.hash()); + + let (block, senders) = self.split_sealed(); + let (header, body) = block.split_sealed_header_body(); + let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body(); + + let transactions = transactions + .into_iter() + .zip(senders) + .enumerate() + .map(|(idx, (tx, sender))| { + let tx_info = TransactionInfo { + hash: Some(*tx.tx_hash()), + block_hash, + block_number: Some(block_number), + base_fee, + index: Some(idx as u64), + }; + + tx_resp_builder(Recovered::new_unchecked(tx, sender), tx_info) + }) + .collect::, E>>()?; + + let transactions = BlockTransactions::Full(transactions); + let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect(); + let header = + Header::from_consensus(header.into(), None, Some(U256::from(block_length))); + + let block = Block { header, uncles, transactions, withdrawals }; + + Ok(block) + } + } + + impl RecoveredBlock> + where + T: SignedTransaction, + { + /// Create a `RecoveredBlock` from an alloy RPC block. + /// + /// # Examples + /// ```ignore + /// // Works with default Transaction type + /// let rpc_block: alloy_rpc_types_eth::Block = get_rpc_block(); + /// let recovered = RecoveredBlock::from_rpc_block(rpc_block)?; + /// + /// // Also works with custom transaction types that implement From + /// let custom_rpc_block: alloy_rpc_types_eth::Block = get_custom_rpc_block(); + /// let recovered = RecoveredBlock::from_rpc_block(custom_rpc_block)?; + /// ``` + pub fn from_rpc_block( + block: alloy_rpc_types_eth::Block, + ) -> Result>> + where + T: From, + { + // Convert to consensus block and then convert transactions + let consensus_block = block.into_consensus().convert_transactions(); + + // Try to recover the block + consensus_block.try_into_recovered() + } + } + + impl TryFrom> for RecoveredBlock> + where + T: SignedTransaction + From, + { + type Error = BlockRecoveryError>; + + fn try_from(block: alloy_rpc_types_eth::Block) -> Result { + Self::from_rpc_block(block) + } + } +} + /// Bincode-compatible [`RecoveredBlock`] serde implementation. #[cfg(feature = "serde-bincode-compat")] pub(super) mod serde_bincode_compat { diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index c2d563a16a5..54f0d42f140 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -13,6 +13,8 @@ //! types. //! - `reth-codec`: Enables db codec support for reth types including zstd compression for certain //! types. +//! - `rpc-compat`: Adds RPC compatibility functions for the types in this crate, e.g. rpc type +//! conversions. //! - `serde`: Adds serde support for all types. //! - `secp256k1`: Adds secp256k1 support for transaction signing/recovery. (By default the no-std //! friendly `k256` is used) diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index bc431891c48..9a9dbc1bf81 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -15,7 +15,7 @@ workspace = true # reth revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip3607", "optional_no_base_fee"] } revm-inspectors.workspace = true -reth-primitives-traits.workspace = true +reth-primitives-traits = { workspace = true, features = ["rpc-compat"] } reth-errors.workspace = true reth-evm.workspace = true reth-storage-api.workspace = true diff --git a/crates/rpc/rpc-eth-api/src/helpers/block.rs b/crates/rpc/rpc-eth-api/src/helpers/block.rs index 24992560126..1c1bade5ad5 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/block.rs @@ -13,7 +13,7 @@ use futures::Future; use reth_evm::ConfigureEvm; use reth_node_api::BlockBody; use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock}; -use reth_rpc_types_compat::block::from_block; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use std::sync::Arc; @@ -59,7 +59,9 @@ pub trait EthBlocks: LoadBlock { async move { let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) }; - let block = from_block((*block).clone(), full.into(), self.tx_resp_builder())?; + let block = block.clone_into_rpc_block(full.into(), |tx, tx_info| { + self.tx_resp_builder().fill(tx, tx_info) + })?; Ok(Some(block)) } } diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml index 20254eea731..5ba7e438f87 100644 --- a/crates/rpc/rpc-eth-types/Cargo.toml +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -19,7 +19,7 @@ reth-evm.workspace = true reth-execution-types.workspace = true reth-metrics.workspace = true reth-ethereum-primitives.workspace = true -reth-primitives-traits.workspace = true +reth-primitives-traits = { workspace = true, features = ["rpc-compat"] } reth-storage-api.workspace = true reth-revm.workspace = true reth-rpc-server-types.workspace = true diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 1b4ed709eff..51a30e861b7 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -23,7 +23,7 @@ use reth_primitives_traits::{ block::BlockTx, BlockBody as _, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction, }; use reth_rpc_server_types::result::rpc_err; -use reth_rpc_types_compat::{block::from_block, TransactionCompat}; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::noop::NoopProvider; use revm::{ context_interface::result::ExecutionResult, @@ -259,6 +259,6 @@ where let txs_kind = if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes }; - let block = from_block(block, txs_kind, tx_resp_builder)?; + let block = block.into_rpc_block(txs_kind, |tx, tx_info| tx_resp_builder.fill(tx, tx_info))?; Ok(SimulatedBlock { inner: block, calls }) } diff --git a/crates/rpc/rpc-types-compat/src/block.rs b/crates/rpc/rpc-types-compat/src/block.rs deleted file mode 100644 index 92f90f3c150..00000000000 --- a/crates/rpc/rpc-types-compat/src/block.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Compatibility functions for rpc `Block` type. - -use crate::transaction::TransactionCompat; -use alloy_consensus::{transaction::Recovered, BlockBody, BlockHeader, Sealable}; -use alloy_primitives::U256; -use alloy_rpc_types_eth::{ - Block, BlockTransactions, BlockTransactionsKind, Header, TransactionInfo, -}; -use reth_primitives_traits::{ - Block as BlockTrait, BlockBody as BlockBodyTrait, NodePrimitives, RecoveredBlock, - SignedTransaction, -}; - -/// Converts the given primitive block into a [`Block`] response with the given -/// [`BlockTransactionsKind`] -/// -/// If a `block_hash` is provided, then this is used, otherwise the block hash is computed. -#[expect(clippy::type_complexity)] -pub fn from_block( - block: RecoveredBlock, - kind: BlockTransactionsKind, - tx_resp_builder: &T, -) -> Result>, T::Error> -where - T: TransactionCompat, - B: BlockTrait::SignedTx>>, -{ - match kind { - BlockTransactionsKind::Hashes => Ok(from_block_with_tx_hashes::(block)), - BlockTransactionsKind::Full => from_block_full::(block, tx_resp_builder), - } -} - -/// Create a new [`Block`] response from a [`RecoveredBlock`], using the -/// total difficulty to populate its field in the rpc response. -/// -/// This will populate the `transactions` field with only the hashes of the transactions in the -/// block: [`BlockTransactions::Hashes`] -pub fn from_block_with_tx_hashes(block: RecoveredBlock) -> Block> -where - B: BlockTrait, -{ - let transactions = block.body().transaction_hashes_iter().copied().collect(); - let rlp_length = block.rlp_length(); - let (header, body) = block.into_sealed_block().split_sealed_header_body(); - let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body(); - - let transactions = BlockTransactions::Hashes(transactions); - let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect(); - let header = Header::from_consensus(header.into(), None, Some(U256::from(rlp_length))); - - Block { header, uncles, transactions, withdrawals } -} - -/// Create a new [`Block`] response from a [`RecoveredBlock`], using the -/// total difficulty to populate its field in the rpc response. -/// -/// This will populate the `transactions` field with the _full_ -/// [`TransactionCompat::Transaction`] objects: [`BlockTransactions::Full`] -#[expect(clippy::type_complexity)] -pub fn from_block_full( - block: RecoveredBlock, - tx_resp_builder: &T, -) -> Result>, T::Error> -where - T: TransactionCompat, - B: BlockTrait::SignedTx>>, -{ - let block_number = block.header().number(); - let base_fee = block.header().base_fee_per_gas(); - let block_length = block.rlp_length(); - let block_hash = Some(block.hash()); - - let (block, senders) = block.split_sealed(); - let (header, body) = block.split_sealed_header_body(); - let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body(); - - let transactions = transactions - .into_iter() - .zip(senders) - .enumerate() - .map(|(idx, (tx, sender))| { - let tx_info = TransactionInfo { - hash: Some(*tx.tx_hash()), - block_hash, - block_number: Some(block_number), - base_fee, - index: Some(idx as u64), - }; - - tx_resp_builder.fill(Recovered::new_unchecked(tx, sender), tx_info) - }) - .collect::, T::Error>>()?; - - let transactions = BlockTransactions::Full(transactions); - let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect(); - let header = Header::from_consensus(header.into(), None, Some(U256::from(block_length))); - - let block = Block { header, uncles, transactions, withdrawals }; - - Ok(block) -} diff --git a/crates/rpc/rpc-types-compat/src/lib.rs b/crates/rpc/rpc-types-compat/src/lib.rs index 86ed5bd255c..33e8c2bb725 100644 --- a/crates/rpc/rpc-types-compat/src/lib.rs +++ b/crates/rpc/rpc-types-compat/src/lib.rs @@ -10,10 +10,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -pub mod block; mod fees; pub mod transaction; - pub use fees::{CallFees, CallFeesError}; pub use transaction::{ try_into_op_tx_info, EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, From e8d305bcce5dbfc59b97118c2466da97cf8f21bf Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:42:53 +0530 Subject: [PATCH 082/274] refactor: used new fn earliest_block_number for ::Earliest tag (#16859) --- crates/storage/provider/src/providers/blockchain_provider.rs | 4 +++- crates/storage/provider/src/providers/consistent.rs | 4 ++-- crates/storage/provider/src/test_utils/mock.rs | 4 +++- crates/storage/storage-api/src/block_id.rs | 4 ++-- crates/storage/storage-api/src/noop.rs | 4 +++- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 88cb18ac445..b7b31cd4937 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -599,7 +599,9 @@ impl StateProviderFactory for BlockchainProvider { let hash = self.safe_block_hash()?.ok_or(ProviderError::SafeBlockNotFound)?; self.state_by_block_hash(hash) } - BlockNumberOrTag::Earliest => self.history_by_block_number(0), + BlockNumberOrTag::Earliest => { + self.history_by_block_number(self.earliest_block_number()?) + } BlockNumberOrTag::Pending => self.pending(), BlockNumberOrTag::Number(num) => { let hash = self diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index b4fcfa6c7ff..bb24cddb34b 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -1250,7 +1250,7 @@ impl BlockReaderIdExt for ConsistentProvider { BlockNumberOrTag::Safe => { self.canonical_in_memory_state.get_safe_header().map(|h| h.unseal()) } - BlockNumberOrTag::Earliest => self.header_by_number(0)?, + BlockNumberOrTag::Earliest => self.header_by_number(self.earliest_block_number()?)?, BlockNumberOrTag::Pending => self.canonical_in_memory_state.pending_header(), BlockNumberOrTag::Number(num) => self.header_by_number(num)?, @@ -1270,7 +1270,7 @@ impl BlockReaderIdExt for ConsistentProvider { } BlockNumberOrTag::Safe => Ok(self.canonical_in_memory_state.get_safe_header()), BlockNumberOrTag::Earliest => self - .header_by_number(0)? + .header_by_number(self.earliest_block_number()?)? .map_or_else(|| Ok(None), |h| Ok(Some(SealedHeader::seal_slow(h)))), BlockNumberOrTag::Pending => Ok(self.canonical_in_memory_state.pending_sealed_header()), BlockNumberOrTag::Number(num) => self diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 6480e4d9253..17c29549b0a 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -917,7 +917,9 @@ impl StatePr self.history_by_block_hash(hash) } - BlockNumberOrTag::Earliest => self.history_by_block_number(0), + BlockNumberOrTag::Earliest => { + self.history_by_block_number(self.earliest_block_number()?) + } BlockNumberOrTag::Pending => self.pending(), BlockNumberOrTag::Number(num) => self.history_by_block_number(num), } diff --git a/crates/storage/storage-api/src/block_id.rs b/crates/storage/storage-api/src/block_id.rs index d00f78df1d1..e00ad950e2d 100644 --- a/crates/storage/storage-api/src/block_id.rs +++ b/crates/storage/storage-api/src/block_id.rs @@ -61,7 +61,7 @@ pub trait BlockIdReader: BlockNumReader + Send + Sync { fn convert_block_number(&self, num: BlockNumberOrTag) -> ProviderResult> { let num = match num { BlockNumberOrTag::Latest => self.best_block_number()?, - BlockNumberOrTag::Earliest => 0, + BlockNumberOrTag::Earliest => self.earliest_block_number()?, BlockNumberOrTag::Pending => { return self .pending_block_num_hash() @@ -89,7 +89,7 @@ pub trait BlockIdReader: BlockNumReader + Send + Sync { .map(|res_opt| res_opt.map(|num_hash| num_hash.hash)), BlockNumberOrTag::Finalized => self.finalized_block_hash(), BlockNumberOrTag::Safe => self.safe_block_hash(), - BlockNumberOrTag::Earliest => self.block_hash(0), + BlockNumberOrTag::Earliest => self.block_hash(self.earliest_block_number()?), BlockNumberOrTag::Number(num) => self.block_hash(num), }, } diff --git a/crates/storage/storage-api/src/noop.rs b/crates/storage/storage-api/src/noop.rs index eca0beb0f7b..3a48aecd695 100644 --- a/crates/storage/storage-api/src/noop.rs +++ b/crates/storage/storage-api/src/noop.rs @@ -486,7 +486,9 @@ impl StateProviderFactory for NoopP self.history_by_block_hash(hash) } - BlockNumberOrTag::Earliest => self.history_by_block_number(0), + BlockNumberOrTag::Earliest => { + self.history_by_block_number(self.earliest_block_number()?) + } BlockNumberOrTag::Pending => self.pending(), BlockNumberOrTag::Number(num) => self.history_by_block_number(num), } From 759101d35089a074a7fae94305a6932cae96d3dc Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 17 Jun 2025 20:29:37 +0200 Subject: [PATCH 083/274] feat: introduce script to compare reth-bench latency CSVs (#16862) --- .../scripts/compare_newpayload_latency.py | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 bin/reth-bench/scripts/compare_newpayload_latency.py diff --git a/bin/reth-bench/scripts/compare_newpayload_latency.py b/bin/reth-bench/scripts/compare_newpayload_latency.py new file mode 100755 index 00000000000..55c90615cee --- /dev/null +++ b/bin/reth-bench/scripts/compare_newpayload_latency.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 + +# A simple script which plots graphs comparing two combined_latency.csv files +# output by reth-bench. The graphs which are plotted are: +# +# - A histogram of the percent change between latencies, bucketed by 1% +# increments. +# +# - A simple line graph plotting the latencies of the two files against each +# other. + + +import argparse +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np +import sys + +def main(): + parser = argparse.ArgumentParser(description='Generate histogram of total_latency percent differences between two CSV files') + parser.add_argument('baseline_csv', help='First CSV file, used as the baseline/control') + parser.add_argument('comparison_csv', help='Second CSV file, which is being compared to the baseline') + parser.add_argument('-o', '--output', default='latency.png', help='Output image file (default: latency.png)') + + args = parser.parse_args() + + try: + df1 = pd.read_csv(args.baseline_csv) + df2 = pd.read_csv(args.comparison_csv) + except FileNotFoundError as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error reading CSV files: {e}", file=sys.stderr) + sys.exit(1) + + if 'total_latency' not in df1.columns: + print(f"Error: 'total_latency' column not found in {args.baseline_csv}", file=sys.stderr) + sys.exit(1) + + if 'total_latency' not in df2.columns: + print(f"Error: 'total_latency' column not found in {args.comparison_csv}", file=sys.stderr) + sys.exit(1) + + if len(df1) != len(df2): + print("Warning: CSV files have different number of rows. Using minimum length.", file=sys.stderr) + min_len = min(len(df1), len(df2)) + df1 = df1.head(min_len) + df2 = df2.head(min_len) + + latency1 = df1['total_latency'].values + latency2 = df2['total_latency'].values + + # Handle division by zero + with np.errstate(divide='ignore', invalid='ignore'): + percent_diff = ((latency2 - latency1) / latency1) * 100 + + # Remove infinite and NaN values + percent_diff = percent_diff[np.isfinite(percent_diff)] + + if len(percent_diff) == 0: + print("Error: No valid percent differences could be calculated", file=sys.stderr) + sys.exit(1) + + # Create histogram with 1% buckets + min_diff = np.floor(percent_diff.min()) + max_diff = np.ceil(percent_diff.max()) + + bins = np.arange(min_diff, max_diff + 1, 1) + + # Create figure with two subplots + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 12)) + + # Top subplot: Histogram + ax1.hist(percent_diff, bins=bins, edgecolor='black', alpha=0.7) + ax1.set_xlabel('Percent Difference (%)') + ax1.set_ylabel('Number of Blocks') + ax1.set_title(f'Total Latency Percent Difference Histogram\n({args.baseline_csv} vs {args.comparison_csv})') + ax1.grid(True, alpha=0.3) + + # Add statistics to the histogram + mean_diff = np.mean(percent_diff) + median_diff = np.median(percent_diff) + ax1.axvline(mean_diff, color='red', linestyle='--', label=f'Mean: {mean_diff:.2f}%') + ax1.axvline(median_diff, color='orange', linestyle='--', label=f'Median: {median_diff:.2f}%') + ax1.legend() + + # Bottom subplot: Latency vs Block Number + if 'block_number' in df1.columns and 'block_number' in df2.columns: + block_numbers = df1['block_number'].values[:len(percent_diff)] + ax2.plot(block_numbers, latency1[:len(percent_diff)], 'b-', alpha=0.7, label=f'Baseline ({args.baseline_csv})') + ax2.plot(block_numbers, latency2[:len(percent_diff)], 'r-', alpha=0.7, label=f'Comparison ({args.comparison_csv})') + ax2.set_xlabel('Block Number') + ax2.set_ylabel('Total Latency (ms)') + ax2.set_title('Total Latency vs Block Number') + ax2.grid(True, alpha=0.3) + ax2.legend() + else: + # If no block_number column, use index + indices = np.arange(len(percent_diff)) + ax2.plot(indices, latency1[:len(percent_diff)], 'b-', alpha=0.7, label=f'Baseline ({args.baseline_csv})') + ax2.plot(indices, latency2[:len(percent_diff)], 'r-', alpha=0.7, label=f'Comparison ({args.comparison_csv})') + ax2.set_xlabel('Block Index') + ax2.set_ylabel('Total Latency (ms)') + ax2.set_title('Total Latency vs Block Index') + ax2.grid(True, alpha=0.3) + ax2.legend() + + plt.tight_layout() + plt.savefig(args.output, dpi=300, bbox_inches='tight') + print(f"Histogram and latency graph saved to {args.output}") + + print(f"\nStatistics:") + print(f"Mean percent difference: {mean_diff:.2f}%") + print(f"Median percent difference: {median_diff:.2f}%") + print(f"Standard deviation: {np.std(percent_diff):.2f}%") + print(f"Min: {percent_diff.min():.2f}%") + print(f"Max: {percent_diff.max():.2f}%") + print(f"Total blocks analyzed: {len(percent_diff)}") + +if __name__ == '__main__': + main() From 5c6f236e925e07f40a1a429924b02bbe9b3404ec Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Tue, 17 Jun 2025 11:29:40 -0700 Subject: [PATCH 084/274] feat: use configurable instance label for overview dashboard (#16633) --- etc/grafana/dashboards/overview.json | 406 ++++++++++++++------------- 1 file changed, 209 insertions(+), 197 deletions(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index edfabec7f17..0beec770921 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -177,7 +177,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_info{job=\"$job\"}", + "expr": "reth_info{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{version}}", "range": false, @@ -245,7 +245,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_info{job=\"$job\"}", + "expr": "reth_info{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{build_timestamp}}", "range": false, @@ -313,7 +313,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_info{job=\"$job\"}", + "expr": "reth_info{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{git_sha}}", "range": false, @@ -381,7 +381,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_info{job=\"$job\"}", + "expr": "reth_info{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{build_profile}}", "range": false, @@ -449,7 +449,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_info{job=\"$job\"}", + "expr": "reth_info{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{target_triple}}", "range": false, @@ -517,7 +517,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_info{job=\"$job\"}", + "expr": "reth_info{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{cargo_features}}", "range": false, @@ -594,7 +594,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_network_connected_peers{job=\"$job\"}", + "expr": "reth_network_connected_peers{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "__auto", "range": false, @@ -668,7 +668,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_sync_checkpoint{job=\"$job\"}", + "expr": "reth_sync_checkpoint{$instance_label=\"$instance\"}", "instant": true, "legendFormat": "{{stage}}", "range": false, @@ -767,7 +767,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum(reth_db_table_size{job=\"$job\"})", + "expr": "sum(reth_db_table_size{$instance_label=\"$instance\"})", "legendFormat": "Database", "range": true, "refId": "A" @@ -778,7 +778,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(reth_db_freelist{job=\"$job\"} * reth_db_page_size{job=\"$job\"})", + "expr": "sum(reth_db_freelist{$instance_label=\"$instance\"} * reth_db_page_size{$instance_label=\"$instance\"})", "hide": false, "instant": false, "legendFormat": "Freelist", @@ -791,7 +791,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum(reth_static_files_segment_size{job=\"$job\"})", + "expr": "sum(reth_static_files_segment_size{$instance_label=\"$instance\"})", "hide": false, "instant": false, "legendFormat": "Static Files", @@ -804,7 +804,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "sum(reth_db_table_size{job=\"$job\"}) + sum(reth_db_freelist{job=\"$job\"} * reth_db_page_size{job=\"$job\"}) + sum(reth_static_files_segment_size{job=\"$job\"})", + "expr": "sum(reth_db_table_size{$instance_label=\"$instance\"}) + sum(reth_db_freelist{$instance_label=\"$instance\"} * reth_db_page_size{$instance_label=\"$instance\"}) + sum(reth_static_files_segment_size{$instance_label=\"$instance\"})", "hide": false, "instant": false, "legendFormat": "Total", @@ -903,7 +903,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_sync_entities_processed{job=\"$job\"} / reth_sync_entities_total{job=\"$job\"}", + "expr": "reth_sync_entities_processed{$instance_label=\"$instance\"} / reth_sync_entities_total{$instance_label=\"$instance\"}", "legendFormat": "{{stage}}", "range": true, "refId": "A" @@ -1000,7 +1000,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_sync_checkpoint{job=\"$job\"}", + "expr": "reth_sync_checkpoint{$instance_label=\"$instance\"}", "legendFormat": "{{stage}}", "range": true, "refId": "A" @@ -1121,7 +1121,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v1{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v1{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "includeNullMetadata": true, "legendFormat": "engine_forkchoiceUpdatedV1 min", @@ -1136,7 +1136,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v1{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v1{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1152,7 +1152,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v1{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v1{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1168,7 +1168,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v1{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v1{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1184,7 +1184,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v1{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v1{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1200,7 +1200,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v2{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v2{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1216,7 +1216,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v2{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v2{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1232,7 +1232,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v2{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v2{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1248,7 +1248,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v2{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v2{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1264,7 +1264,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v2{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v2{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1280,7 +1280,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v3{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v3{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1296,7 +1296,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v3{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v3{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1312,7 +1312,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v3{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v3{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1328,7 +1328,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v3{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v3{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1344,7 +1344,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_fork_choice_updated_v3{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_fork_choice_updated_v3{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1469,7 +1469,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v1{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_new_payload_v1{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "includeNullMetadata": true, "legendFormat": "engine_newPayloadV1 min", @@ -1484,7 +1484,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v1{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_new_payload_v1{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1500,7 +1500,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v1{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_new_payload_v1{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1516,7 +1516,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v1{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_new_payload_v1{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1532,7 +1532,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v1{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_new_payload_v1{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1548,7 +1548,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v2{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_new_payload_v2{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1564,7 +1564,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v2{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_new_payload_v2{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1580,7 +1580,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v2{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_new_payload_v2{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1596,7 +1596,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v2{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_new_payload_v2{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1612,7 +1612,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v2{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_new_payload_v2{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1628,7 +1628,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v3{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_new_payload_v3{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1644,7 +1644,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v3{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_new_payload_v3{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1660,7 +1660,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v3{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_new_payload_v3{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1676,7 +1676,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v3{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_new_payload_v3{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1692,7 +1692,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v3{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_new_payload_v3{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1708,7 +1708,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v4{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_new_payload_v4{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1724,7 +1724,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v4{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_new_payload_v4{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1740,7 +1740,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v4{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_new_payload_v4{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1756,7 +1756,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v4{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_new_payload_v4{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1772,7 +1772,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_new_payload_v4{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_new_payload_v4{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1871,7 +1871,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sync_execution_gas_per_second{job=\"$job\"}", + "expr": "reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}", "legendFormat": "Gas/s", "range": true, "refId": "A" @@ -1883,7 +1883,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "avg_over_time(reth_sync_execution_gas_per_second{job=\"$job\"}[1m])", + "expr": "avg_over_time(reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}[1m])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1899,7 +1899,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "avg_over_time(reth_sync_execution_gas_per_second{job=\"$job\"}[5m])", + "expr": "avg_over_time(reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}[5m])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1915,7 +1915,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "avg_over_time(reth_sync_execution_gas_per_second{job=\"$job\"}[10m])", + "expr": "avg_over_time(reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}[10m])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1931,7 +1931,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "avg_over_time(reth_sync_execution_gas_per_second{job=\"$job\"}[30m])", + "expr": "avg_over_time(reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}[30m])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1947,7 +1947,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "avg_over_time(reth_sync_execution_gas_per_second{job=\"$job\"}[1h])", + "expr": "avg_over_time(reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}[1h])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -1963,7 +1963,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "avg_over_time(reth_sync_execution_gas_per_second{job=\"$job\"}[24h])", + "expr": "avg_over_time(reth_sync_execution_gas_per_second{$instance_label=\"$instance\"}[24h])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2066,7 +2066,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_sync_block_validation_state_root_duration{job=\"$job\"}", + "expr": "reth_sync_block_validation_state_root_duration{$instance_label=\"$instance\"}", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -2082,7 +2082,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_sync_execution_execution_duration{job=\"$job\"}", + "expr": "reth_sync_execution_execution_duration{$instance_label=\"$instance\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2199,7 +2199,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(reth_consensus_engine_beacon_forkchoice_updated_messages{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_consensus_engine_beacon_forkchoice_updated_messages{$instance_label=\"$instance\"}[$__rate_interval])", "legendFormat": "forkchoiceUpdated", "range": true, "refId": "A" @@ -2210,7 +2210,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(reth_consensus_engine_beacon_new_payload_messages{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_consensus_engine_beacon_new_payload_messages{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "newPayload", "range": true, @@ -2310,7 +2310,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(reth_consensus_engine_beacon_failed_new_payload_response_deliveries{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_consensus_engine_beacon_failed_new_payload_response_deliveries{$instance_label=\"$instance\"}[$__rate_interval])", "legendFormat": "newPayload", "range": true, "refId": "A" @@ -2321,7 +2321,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(reth_consensus_engine_beacon_failed_forkchoice_updated_response_deliveries{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_consensus_engine_beacon_failed_forkchoice_updated_response_deliveries{$instance_label=\"$instance\"}[$__rate_interval])", "legendFormat": "forkchoiceUpdated", "range": true, "refId": "B" @@ -2420,7 +2420,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "reth_engine_rpc_new_payload_forkchoice_updated_time_diff{job=\"$job\"}", + "expr": "reth_engine_rpc_new_payload_forkchoice_updated_time_diff{$instance_label=\"$instance\"}", "legendFormat": "p{{quantile}}", "range": true, "refId": "A" @@ -2520,7 +2520,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2536,7 +2536,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2552,7 +2552,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2568,7 +2568,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2584,7 +2584,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_hash_v1{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2600,7 +2600,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2616,7 +2616,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2632,7 +2632,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{job=\"$job\", quantile=\"0.9\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{$instance_label=\"$instance\", quantile=\"0.9\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2648,7 +2648,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2664,7 +2664,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_get_payload_bodies_by_range_v1{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2762,7 +2762,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(reth_engine_rpc_blobs_blob_count{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_engine_rpc_blobs_blob_count{$instance_label=\"$instance\"}[$__rate_interval])", "legendFormat": "Found", "range": true, "refId": "A" @@ -2773,7 +2773,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(reth_engine_rpc_blobs_blob_misses{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_engine_rpc_blobs_blob_misses{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Missed", "range": true, @@ -2873,7 +2873,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{job=\"$job\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2889,7 +2889,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{job=\"$job\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2905,7 +2905,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{job=\"$job\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2921,7 +2921,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{job=\"$job\", quantile=\"0\"}", + "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -2937,7 +2937,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{job=\"$job\", quantile=\"1\"}", + "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"1\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -3039,7 +3039,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_consensus_engine_beacon_pipeline_runs{job=\"$job\"}", + "expr": "reth_consensus_engine_beacon_pipeline_runs{$instance_label=\"$instance\"}", "legendFormat": "Pipeline runs", "range": true, "refId": "A" @@ -3137,7 +3137,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_consensus_engine_beacon_active_block_downloads{job=\"$job\"}", + "expr": "reth_consensus_engine_beacon_active_block_downloads{$instance_label=\"$instance\"}", "legendFormat": "Active block downloads", "range": true, "refId": "A" @@ -3250,7 +3250,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_sync_block_validation_state_root_duration{job=\"$job\"}", + "expr": "reth_sync_block_validation_state_root_duration{$instance_label=\"$instance\"}", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -3266,7 +3266,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_sync_execution_execution_duration{job=\"$job\"}", + "expr": "reth_sync_execution_execution_duration{$instance_label=\"$instance\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -3370,7 +3370,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "reth_sync_caching_account_cache_hits{job=\"$job\"} / (reth_sync_caching_account_cache_hits{job=\"$job\"} + reth_sync_caching_account_cache_misses{job=\"$job\"})", + "expr": "reth_sync_caching_account_cache_hits{$instance_label=\"$instance\"} / (reth_sync_caching_account_cache_hits{$instance_label=\"$instance\"} + reth_sync_caching_account_cache_misses{$instance_label=\"$instance\"})", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -3387,7 +3387,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "reth_sync_caching_storage_cache_hits{job=\"$job\"} / (reth_sync_caching_storage_cache_hits{job=\"$job\"} + reth_sync_caching_storage_cache_misses{job=\"$job\"})", + "expr": "reth_sync_caching_storage_cache_hits{$instance_label=\"$instance\"} / (reth_sync_caching_storage_cache_hits{$instance_label=\"$instance\"} + reth_sync_caching_storage_cache_misses{$instance_label=\"$instance\"})", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -3404,7 +3404,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "reth_sync_caching_code_cache_hits{job=\"$job\"} / (reth_sync_caching_code_cache_hits{job=\"$job\"} + reth_sync_caching_code_cache_misses{job=\"$job\"})", + "expr": "reth_sync_caching_code_cache_hits{$instance_label=\"$instance\"} / (reth_sync_caching_code_cache_hits{$instance_label=\"$instance\"} + reth_sync_caching_code_cache_misses{$instance_label=\"$instance\"})", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -3509,7 +3509,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "reth_sync_block_validation_trie_input_duration{job=\"$job\", quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sync_block_validation_trie_input_duration{$instance_label=\"$instance\", quantile=~\"(0|0.5|0.9|0.95|1)\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -3624,7 +3624,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_tree_root_proofs_processed_histogram{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_tree_root_proofs_processed_histogram{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "{{quantile}} percentile", "range": true, @@ -3723,7 +3723,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_tree_root_proof_calculation_duration_histogram{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_tree_root_proof_calculation_duration_histogram{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "{{quantile}} percentile", "range": true, @@ -3822,7 +3822,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_tree_root_pending_multiproofs_histogram{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_tree_root_pending_multiproofs_histogram{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "{{quantile}} percentile", "range": true, @@ -3921,7 +3921,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_tree_root_inflight_multiproofs_histogram{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_tree_root_inflight_multiproofs_histogram{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "{{quantile}} percentile", "range": true, @@ -4020,7 +4020,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_skipped_storage_nodes{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_skipped_storage_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "Storage {{quantile}} percentile", "range": true, @@ -4119,7 +4119,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_skipped_account_nodes{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_skipped_account_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "Account {{quantile}} percentile", "range": true, @@ -4218,7 +4218,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_total_account_nodes{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_total_account_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "hide": false, "instant": false, "legendFormat": "Account {{quantile}} percentile", @@ -4318,7 +4318,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_total_storage_nodes{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_total_storage_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "Storage {{quantile}} percentile", "range": true, @@ -4418,7 +4418,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_tree_root_multiproof_task_total_duration_histogram{job=\"$job\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_tree_root_multiproof_task_total_duration_histogram{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "hide": false, "instant": false, "legendFormat": "Task duration {{quantile}} percentile", @@ -4531,7 +4531,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "avg(rate(reth_database_transaction_close_duration_seconds_sum{job=\"$job\", outcome=\"commit\"}[$__rate_interval]) / rate(reth_database_transaction_close_duration_seconds_count{job=\"$job\", outcome=\"commit\"}[$__rate_interval]) >= 0)", + "expr": "avg(rate(reth_database_transaction_close_duration_seconds_sum{$instance_label=\"$instance\", outcome=\"commit\"}[$__rate_interval]) / rate(reth_database_transaction_close_duration_seconds_count{$instance_label=\"$instance\", outcome=\"commit\"}[$__rate_interval]) >= 0)", "format": "time_series", "instant": false, "legendFormat": "Commit time", @@ -4621,7 +4621,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "avg(max_over_time(reth_database_transaction_close_duration_seconds{job=\"$job\", outcome=\"commit\"}[$__rate_interval])) by (quantile)", + "expr": "avg(max_over_time(reth_database_transaction_close_duration_seconds{$instance_label=\"$instance\", outcome=\"commit\"}[$__rate_interval])) by (quantile)", "format": "time_series", "instant": false, "legendFormat": "{{quantile}}", @@ -4720,7 +4720,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sum(rate(reth_database_transaction_open_duration_seconds_sum{job=\"$job\", outcome!=\"\"}[$__rate_interval]) / rate(reth_database_transaction_open_duration_seconds_count{job=\"$job\", outcome!=\"\"}[$__rate_interval])) by (outcome, mode)", + "expr": "sum(rate(reth_database_transaction_open_duration_seconds_sum{$instance_label=\"$instance\", outcome!=\"\"}[$__rate_interval]) / rate(reth_database_transaction_open_duration_seconds_count{$instance_label=\"$instance\", outcome!=\"\"}[$__rate_interval])) by (outcome, mode)", "format": "time_series", "instant": false, "legendFormat": "{{mode}}, {{outcome}}", @@ -4818,7 +4818,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "max(max_over_time(reth_database_transaction_open_duration_seconds{job=\"$job\", outcome!=\"\", quantile=\"1\"}[$__interval])) by (outcome, mode)", + "expr": "max(max_over_time(reth_database_transaction_open_duration_seconds{$instance_label=\"$instance\", outcome!=\"\", quantile=\"1\"}[$__interval])) by (outcome, mode)", "format": "time_series", "instant": false, "legendFormat": "{{mode}}, {{outcome}}", @@ -4948,7 +4948,7 @@ "disableTextWrap": false, "editorMode": "code", "exemplar": false, - "expr": "sum(reth_database_transaction_opened_total{job=\"$job\", mode=\"read-write\"})", + "expr": "sum(reth_database_transaction_opened_total{$instance_label=\"$instance\", mode=\"read-write\"})", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -4965,7 +4965,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sum(reth_database_transaction_closed_total{job=\"$job\", mode=\"read-write\"})", + "expr": "sum(reth_database_transaction_closed_total{$instance_label=\"$instance\", mode=\"read-write\"})", "format": "time_series", "instant": false, "legendFormat": "Closed {{mode}}", @@ -5105,7 +5105,7 @@ "disableTextWrap": false, "editorMode": "builder", "exemplar": false, - "expr": "reth_database_transaction_opened_total{job=\"$job\", mode=\"read-only\"}", + "expr": "reth_database_transaction_opened_total{$instance_label=\"$instance\", mode=\"read-only\"}", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -5123,7 +5123,7 @@ "disableTextWrap": false, "editorMode": "builder", "exemplar": false, - "expr": "sum(reth_database_transaction_closed_total{job=\"$job\", mode=\"read-only\"})", + "expr": "sum(reth_database_transaction_closed_total{$instance_label=\"$instance\", mode=\"read-only\"})", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -5205,7 +5205,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_db_table_size{job=\"$job\"}", + "expr": "reth_db_table_size{$instance_label=\"$instance\"}", "interval": "", "legendFormat": "{{table}}", "range": true, @@ -5306,7 +5306,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "max(max_over_time(reth_database_operation_large_value_duration_seconds{job=\"$job\", quantile=\"1\"}[$__interval]) > 0) by (table)", + "expr": "max(max_over_time(reth_database_operation_large_value_duration_seconds{$instance_label=\"$instance\", quantile=\"1\"}[$__interval]) > 0) by (table)", "format": "time_series", "instant": false, "legendFormat": "{{table}}", @@ -5374,7 +5374,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "sum by (type) ( reth_db_table_pages{job=\"$job\"} )", + "expr": "sum by (type) ( reth_db_table_pages{$instance_label=\"$instance\"} )", "legendFormat": "__auto", "range": true, "refId": "A" @@ -5474,7 +5474,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum by (job) ( reth_db_table_size{job=\"$job\"} )", + "expr": "sum by (job) ( reth_db_table_size{$instance_label=\"$instance\"} )", "legendFormat": "Size ({{job}})", "range": true, "refId": "A" @@ -5573,7 +5573,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum(reth_db_freelist{job=\"$job\"}) by (job)", + "expr": "sum(reth_db_freelist{$instance_label=\"$instance\"}) by (job)", "legendFormat": "Pages ({{job}})", "range": true, "refId": "A" @@ -5731,7 +5731,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(reth_db_table_pages{job=\"$job\", type=\"overflow\"} != 0)", + "expr": "sort_desc(reth_db_table_pages{$instance_label=\"$instance\", type=\"overflow\"} != 0)", "format": "table", "instant": true, "legendFormat": "__auto", @@ -5813,7 +5813,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_static_files_segment_size{job=\"$job\"}", + "expr": "reth_static_files_segment_size{$instance_label=\"$instance\"}", "interval": "", "legendFormat": "{{segment}}", "range": true, @@ -5960,7 +5960,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "reth_static_files_segment_entries{job=\"$job\"}", + "expr": "reth_static_files_segment_entries{$instance_label=\"$instance\"}", "format": "table", "instant": true, "legendFormat": "__auto", @@ -6108,7 +6108,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "reth_static_files_segment_files{job=\"$job\"}", + "expr": "reth_static_files_segment_files{$instance_label=\"$instance\"}", "format": "table", "instant": true, "legendFormat": "__auto", @@ -6209,7 +6209,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum by (job) ( reth_static_files_segment_size{job=\"$job\"} )", + "expr": "sum by (job) ( reth_static_files_segment_size{$instance_label=\"$instance\"} )", "legendFormat": "__auto", "range": true, "refId": "A" @@ -6308,7 +6308,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "max(max_over_time(reth_static_files_jar_provider_write_duration_seconds{job=\"$job\", operation=\"commit-writer\", quantile=\"1\"}[$__interval]) > 0) by (segment)", + "expr": "max(max_over_time(reth_static_files_jar_provider_write_duration_seconds{$instance_label=\"$instance\", operation=\"commit-writer\", quantile=\"1\"}[$__interval]) > 0) by (segment)", "legendFormat": "{{segment}}", "range": true, "refId": "A" @@ -6420,7 +6420,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_blockchain_tree_canonical_chain_height{job=\"$job\"}", + "expr": "reth_blockchain_tree_canonical_chain_height{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Canonical chain height", "range": true, @@ -6519,7 +6519,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_blockchain_tree_block_buffer_blocks{job=\"$job\"}", + "expr": "reth_blockchain_tree_block_buffer_blocks{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks", "range": true, @@ -6618,7 +6618,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "increase(reth_blockchain_tree_reorgs{job=\"$job\"}[$__rate_interval])", + "expr": "increase(reth_blockchain_tree_reorgs{$instance_label=\"$instance\"}[$__rate_interval])", "instant": false, "legendFormat": "__auto", "range": true, @@ -6717,7 +6717,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_blockchain_tree_latest_reorg_depth{job=\"$job\"}", + "expr": "reth_blockchain_tree_latest_reorg_depth{$instance_label=\"$instance\"}", "instant": false, "legendFormat": "__auto", "range": true, @@ -6856,7 +6856,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "sum(reth_rpc_server_connections_connections_opened_total{job=\"$job\"} - reth_rpc_server_connections_connections_closed_total{job=\"$job\"}) by (transport)", + "expr": "sum(reth_rpc_server_connections_connections_opened_total{$instance_label=\"$instance\"} - reth_rpc_server_connections_connections_closed_total{$instance_label=\"$instance\"}) by (transport)", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -6948,7 +6948,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "avg(max_over_time(reth_rpc_server_connections_request_time_seconds{job=\"$job\"}[$__rate_interval]) > 0) by (quantile)", + "expr": "avg(max_over_time(reth_rpc_server_connections_request_time_seconds{$instance_label=\"$instance\"}[$__rate_interval]) > 0) by (quantile)", "format": "time_series", "instant": false, "legendFormat": "__auto", @@ -7048,7 +7048,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "max(max_over_time(reth_rpc_server_calls_time_seconds{job=\"$job\"}[$__rate_interval])) by (method) > 0", + "expr": "max(max_over_time(reth_rpc_server_calls_time_seconds{$instance_label=\"$instance\"}[$__rate_interval])) by (method) > 0", "instant": false, "legendFormat": "__auto", "range": true, @@ -7137,7 +7137,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "avg(max_over_time(reth_rpc_server_calls_time_seconds{job=\"$job\"}[$__rate_interval]) > 0) by (quantile)", + "expr": "avg(max_over_time(reth_rpc_server_calls_time_seconds{$instance_label=\"$instance\"}[$__rate_interval]) > 0) by (quantile)", "format": "time_series", "instant": false, "legendFormat": "{{quantile}}", @@ -7274,7 +7274,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_cached_count{job=\"$job\", cache=\"headers\"}", + "expr": "reth_rpc_eth_cache_cached_count{$instance_label=\"$instance\", cache=\"headers\"}", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -7290,7 +7290,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_queued_consumers_count{job=\"$job\", cache=\"receipts\"}", + "expr": "reth_rpc_eth_cache_queued_consumers_count{$instance_label=\"$instance\", cache=\"receipts\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7307,7 +7307,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_queued_consumers_count{job=\"$job\", cache=\"headers\"}", + "expr": "reth_rpc_eth_cache_queued_consumers_count{$instance_label=\"$instance\", cache=\"headers\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7324,7 +7324,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_queued_consumers_count{job=\"$job\", cache=\"blocks\"}", + "expr": "reth_rpc_eth_cache_queued_consumers_count{$instance_label=\"$instance\", cache=\"blocks\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7341,7 +7341,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_memory_usage{job=\"$job\", cache=\"blocks\"}", + "expr": "reth_rpc_eth_cache_memory_usage{$instance_label=\"$instance\", cache=\"blocks\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7358,7 +7358,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_cached_count{job=\"$job\", cache=\"receipts\"}", + "expr": "reth_rpc_eth_cache_cached_count{$instance_label=\"$instance\", cache=\"receipts\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7375,7 +7375,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_memory_usage{job=\"$job\", cache=\"receipts\"}", + "expr": "reth_rpc_eth_cache_memory_usage{$instance_label=\"$instance\", cache=\"receipts\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7392,7 +7392,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_rpc_eth_cache_cached_count{job=\"$job\", cache=\"blocks\"}", + "expr": "reth_rpc_eth_cache_cached_count{$instance_label=\"$instance\", cache=\"blocks\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, @@ -7632,7 +7632,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_total_downloaded{job=\"$job\"}", + "expr": "reth_downloaders_headers_total_downloaded{$instance_label=\"$instance\"}", "legendFormat": "Downloaded", "range": true, "refId": "A" @@ -7643,7 +7643,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_total_flushed{job=\"$job\"}", + "expr": "reth_downloaders_headers_total_flushed{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Flushed", "range": true, @@ -7655,7 +7655,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_total_downloaded{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_total_downloaded{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "instant": false, "legendFormat": "Downloaded/s", @@ -7668,7 +7668,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_total_flushed{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_total_flushed{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Flushed/s", "range": true, @@ -7768,7 +7768,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_timeout_errors{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_timeout_errors{$instance_label=\"$instance\"}[$__rate_interval])", "legendFormat": "Request timed out", "range": true, "refId": "A" @@ -7779,7 +7779,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_unexpected_errors{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_unexpected_errors{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Unexpected error", "range": true, @@ -7791,7 +7791,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_validation_errors{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_validation_errors{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Invalid response", "range": true, @@ -7890,7 +7890,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_in_flight_requests{job=\"$job\"}", + "expr": "reth_downloaders_headers_in_flight_requests{$instance_label=\"$instance\"}", "legendFormat": "In flight requests", "range": true, "refId": "A" @@ -7901,7 +7901,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_network_connected_peers{job=\"$job\"}", + "expr": "reth_network_connected_peers{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Connected peers", "range": true, @@ -8045,7 +8045,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_total_downloaded{job=\"$job\"}", + "expr": "reth_downloaders_bodies_total_downloaded{$instance_label=\"$instance\"}", "legendFormat": "Downloaded", "range": true, "refId": "A" @@ -8056,7 +8056,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_total_flushed{job=\"$job\"}", + "expr": "reth_downloaders_bodies_total_flushed{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Flushed", "range": true, @@ -8068,7 +8068,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_total_flushed{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_total_flushed{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Flushed/s", "range": true, @@ -8080,7 +8080,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_total_downloaded{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_total_downloaded{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Downloaded/s", "range": true, @@ -8092,7 +8092,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_responses{job=\"$job\"}", + "expr": "reth_downloaders_bodies_buffered_responses{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Buffered responses", "range": true, @@ -8104,7 +8104,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_blocks{job=\"$job\"}", + "expr": "reth_downloaders_bodies_buffered_blocks{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks", "range": true, @@ -8116,7 +8116,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_queued_blocks{job=\"$job\"}", + "expr": "reth_downloaders_bodies_queued_blocks{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Queued blocks", "range": true, @@ -8210,7 +8210,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_timeout_errors{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_timeout_errors{$instance_label=\"$instance\"}[$__rate_interval])", "legendFormat": "Request timed out", "range": true, "refId": "A" @@ -8221,7 +8221,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_unexpected_errors{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_unexpected_errors{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Unexpected error", "range": true, @@ -8233,7 +8233,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_validation_errors{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_validation_errors{$instance_label=\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Invalid response", "range": true, @@ -8329,7 +8329,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_in_flight_requests{job=\"$job\"}", + "expr": "reth_downloaders_bodies_in_flight_requests{$instance_label=\"$instance\"}", "legendFormat": "In flight requests", "range": true, "refId": "A" @@ -8340,7 +8340,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_connected_peers{job=\"$job\"}", + "expr": "reth_network_connected_peers{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Connected peers", "range": true, @@ -8454,7 +8454,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_blocks_size_bytes{job=\"$job\"}", + "expr": "reth_downloaders_bodies_buffered_blocks_size_bytes{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks size ", "range": true, @@ -8466,7 +8466,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_blocks{job=\"$job\"}", + "expr": "reth_downloaders_bodies_buffered_blocks{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks", "range": true, @@ -8580,7 +8580,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "reth_downloaders_bodies_response_response_size_bytes{job=\"$job\"}", + "expr": "reth_downloaders_bodies_response_response_size_bytes{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Response size", "range": true, @@ -8592,7 +8592,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_response_response_length{job=\"$job\"}", + "expr": "reth_downloaders_bodies_response_response_length{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Individual response length (number of bodies in response)", "range": true, @@ -8604,7 +8604,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_response_response_size_bytes / reth_downloaders_bodies_response_response_length{job=\"$job\"}", + "expr": "reth_downloaders_bodies_response_response_size_bytes / reth_downloaders_bodies_response_response_length{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Mean body size in response", @@ -8742,7 +8742,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "rate(reth_network_eth_headers_requests_received_total{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_network_eth_headers_requests_received_total{$instance_label=\"$instance\"}[$__rate_interval])", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -8869,7 +8869,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "rate(reth_network_eth_receipts_requests_received_total{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_network_eth_receipts_requests_received_total{$instance_label=\"$instance\"}[$__rate_interval])", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -8996,7 +8996,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "rate(reth_network_eth_bodies_requests_received_total{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_network_eth_bodies_requests_received_total{$instance_label=\"$instance\"}[$__rate_interval])", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -9123,7 +9123,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "rate(reth_network_eth_node_data_requests_received_total{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_network_eth_node_data_requests_received_total{$instance_label=\"$instance\"}[$__rate_interval])", "format": "time_series", "fullMetaSearch": false, "includeNullMetadata": true, @@ -9236,7 +9236,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_payloads_active_jobs{job=\"$job\"}", + "expr": "reth_payloads_active_jobs{$instance_label=\"$instance\"}", "legendFormat": "Active Jobs", "range": true, "refId": "A" @@ -9331,7 +9331,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_payloads_initiated_jobs{job=\"$job\"}", + "expr": "reth_payloads_initiated_jobs{$instance_label=\"$instance\"}", "legendFormat": "Initiated Jobs", "range": true, "refId": "A" @@ -9426,7 +9426,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_payloads_failed_jobs{job=\"$job\"}", + "expr": "reth_payloads_failed_jobs{$instance_label=\"$instance\"}", "legendFormat": "Failed Jobs", "range": true, "refId": "A" @@ -9535,7 +9535,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(reth_pruner_duration_seconds_sum{job=\"$job\"}[$__rate_interval]) / rate(reth_pruner_duration_seconds_count{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_pruner_duration_seconds_sum{$instance_label=\"$instance\"}[$__rate_interval]) / rate(reth_pruner_duration_seconds_count{$instance_label=\"$instance\"}[$__rate_interval])", "instant": false, "legendFormat": "__auto", "range": true, @@ -9632,7 +9632,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "rate(reth_pruner_segments_duration_seconds_sum{job=\"$job\"}[$__rate_interval]) / rate(reth_pruner_segments_duration_seconds_count{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_pruner_segments_duration_seconds_sum{$instance_label=\"$instance\"}[$__rate_interval]) / rate(reth_pruner_segments_duration_seconds_count{$instance_label=\"$instance\"}[$__rate_interval])", "instant": false, "legendFormat": "{{segment}}", "range": true, @@ -9728,7 +9728,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "reth_pruner_segments_highest_pruned_block{job=\"$job\"}", + "expr": "reth_pruner_segments_highest_pruned_block{$instance_label=\"$instance\"}", "instant": false, "legendFormat": "{{segment}}", "range": true, @@ -9850,7 +9850,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_jemalloc_active{job=\"$job\"}", + "expr": "reth_jemalloc_active{$instance_label=\"$instance\"}", "instant": false, "legendFormat": "Active", "range": true, @@ -9862,7 +9862,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_jemalloc_allocated{job=\"$job\"}", + "expr": "reth_jemalloc_allocated{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Allocated", @@ -9875,7 +9875,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_jemalloc_mapped{job=\"$job\"}", + "expr": "reth_jemalloc_mapped{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Mapped", @@ -9888,7 +9888,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_jemalloc_metadata{job=\"$job\"}", + "expr": "reth_jemalloc_metadata{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Metadata", @@ -9901,7 +9901,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_jemalloc_resident{job=\"$job\"}", + "expr": "reth_jemalloc_resident{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Resident", @@ -9914,7 +9914,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_jemalloc_retained{job=\"$job\"}", + "expr": "reth_jemalloc_retained{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Retained", @@ -10012,7 +10012,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_process_resident_memory_bytes{job=\"$job\"}", + "expr": "reth_process_resident_memory_bytes{$instance_label=\"$instance\"}", "instant": false, "legendFormat": "Resident", "range": true, @@ -10109,7 +10109,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "avg(rate(reth_process_cpu_seconds_total{job=\"$job\"}[1m]))", + "expr": "avg(rate(reth_process_cpu_seconds_total{$instance_label=\"$instance\"}[1m]))", "instant": false, "legendFormat": "Process", "range": true, @@ -10206,7 +10206,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_process_open_fds{job=\"$job\"}", + "expr": "reth_process_open_fds{$instance_label=\"$instance\"}", "instant": false, "legendFormat": "Open", "range": true, @@ -10304,7 +10304,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_executor_spawn_critical_tasks_total{job=\"$job\"}- reth_executor_spawn_finished_critical_tasks_total{job=\"$job\"}", + "expr": "reth_executor_spawn_critical_tasks_total{$instance_label=\"$instance\"}- reth_executor_spawn_finished_critical_tasks_total{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Tasks running", @@ -10418,7 +10418,7 @@ "disableTextWrap": false, "editorMode": "code", "exemplar": false, - "expr": "rate(reth_executor_spawn_regular_tasks_total{job=\"$job\"}[$__rate_interval])", + "expr": "rate(reth_executor_spawn_regular_tasks_total{$instance_label=\"$instance\"}[$__rate_interval])", "fullMetaSearch": false, "hide": false, "includeNullMetadata": false, @@ -10434,7 +10434,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "reth_executor_spawn_regular_tasks_total{job=\"$job\"} - reth_executor_spawn_finished_regular_tasks_total{job=\"$job\"}", + "expr": "reth_executor_spawn_regular_tasks_total{$instance_label=\"$instance\"} - reth_executor_spawn_finished_regular_tasks_total{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Tasks running", @@ -10541,7 +10541,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_exex_notifications_sent_total{job=\"$job\"}", + "expr": "reth_exex_notifications_sent_total{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Total Notifications Sent", "range": true, @@ -10637,7 +10637,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_exex_events_sent_total{job=\"$job\"}", + "expr": "reth_exex_events_sent_total{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Total Events Sent", "range": true, @@ -10733,7 +10733,7 @@ "uid": "${datasource}" }, "editorMode": "builder", - "expr": "reth_exex_manager_current_capacity{job=\"$job\"}", + "expr": "reth_exex_manager_current_capacity{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Current size", "range": true, @@ -10745,7 +10745,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "max_over_time(reth_exex_manager_max_capacity{job=\"$job\"}[1h])", + "expr": "max_over_time(reth_exex_manager_max_capacity{$instance_label=\"$instance\"}[1h])", "hide": false, "legendFormat": "Max size", "range": true, @@ -10841,7 +10841,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_exex_manager_buffer_size{job=\"$job\"}", + "expr": "reth_exex_manager_buffer_size{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Max size", "range": true, @@ -10909,7 +10909,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_exex_manager_num_exexs{job=\"$job\"}", + "expr": "reth_exex_manager_num_exexs{$instance_label=\"$instance\"}", "hide": false, "legendFormat": "Number of ExExs", "range": true, @@ -11020,7 +11020,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_exex_wal_lowest_committed_block_height{job=\"$job\"}", + "expr": "reth_exex_wal_lowest_committed_block_height{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Lowest Block", @@ -11033,7 +11033,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "reth_exex_wal_highest_committed_block_height{job=\"$job\"}", + "expr": "reth_exex_wal_highest_committed_block_height{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Highest Block", @@ -11131,7 +11131,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "reth_exex_wal_committed_blocks_count{job=\"$job\"}", + "expr": "reth_exex_wal_committed_blocks_count{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Committed Blocks", @@ -11144,7 +11144,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_exex_wal_notifications_count{job=\"$job\"}", + "expr": "reth_exex_wal_notifications_count{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Notifications", @@ -11243,7 +11243,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_exex_wal_size_bytes{job=\"$job\"}", + "expr": "reth_exex_wal_size_bytes{$instance_label=\"$instance\"}", "hide": false, "instant": false, "legendFormat": "__auto", @@ -11270,18 +11270,22 @@ "type": "prometheus", "uid": "${datasource}" }, - "definition": "query_result(reth_info)", + "definition": "label_values(reth_info,$instance_label)", + "hide": 0, "includeAll": false, - "label": "Job", - "name": "job", + "label": "Instance", + "multi": false, + "name": "instance", "options": [], "query": { - "qryType": 3, - "query": "query_result(reth_info)", + "qryType": 1, + "query": "label_values(reth_info,$instance_label)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 1, - "regex": "/.*job=\\\"([^\\\"]*).*/", + "regex": "", + "skipUrlSync": false, + "sort": 1, "type": "query" }, { @@ -11294,6 +11298,14 @@ "refresh": 1, "regex": "", "type": "datasource" + }, + { + "hide": 2, + "label": "Instance Label", + "name": "instance_label", + "query": "job", + "skipUrlSync": false, + "type": "constant" } ] }, From 243a52314962b154b8a1369de84b635f4b4627c9 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 17 Jun 2025 16:26:10 -0400 Subject: [PATCH 085/274] feat: add CLAUDE.md (#16864) --- CLAUDE.md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..99b06b1f783 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,311 @@ +# Reth Development Guide for AI Agents + +This guide provides comprehensive instructions for AI agents working on the Reth codebase. It covers the architecture, development workflows, and critical guidelines for effective contributions. + +## Project Overview + +Reth is a high-performance Ethereum execution client written in Rust, focusing on modularity, performance, and contributor-friendliness. The codebase is organized into well-defined crates with clear boundaries and responsibilities. + +## Architecture Overview + +### Core Components + +1. **Consensus (`crates/consensus/`)**: Validates blocks according to Ethereum consensus rules +2. **Storage (`crates/storage/`)**: Hybrid database using MDBX + static files for optimal performance +3. **Networking (`crates/net/`)**: P2P networking stack with discovery, sync, and transaction propagation +4. **RPC (`crates/rpc/`)**: JSON-RPC server supporting all standard Ethereum APIs +5. **Execution (`crates/evm/`, `crates/ethereum/`)**: Transaction execution and state transitions +6. **Pipeline (`crates/stages/`)**: Staged sync architecture for blockchain synchronization +7. **Trie (`crates/trie/`)**: Merkle Patricia Trie implementation with parallel state root computation +8. **Node Builder (`crates/node/`)**: High-level node orchestration and configuration +9 **The Consensus Engine (`crates/engine/`)**: Handles processing blocks received from the consensus layer with the Engine API (newPayload, forkchoiceUpdated) + +### Key Design Principles + +- **Modularity**: Each crate can be used as a standalone library +- **Performance**: Extensive use of parallelism, memory-mapped I/O, and optimized data structures +- **Extensibility**: Traits and generic types allow for different implementations (Ethereum, Optimism, etc.) +- **Type Safety**: Strong typing throughout with minimal use of dynamic dispatch + +## Development Workflow + +### Code Style and Standards + +1. **Formatting**: Always use nightly rustfmt + ```bash + cargo +nightly fmt --all + ``` + +2. **Linting**: Run clippy with all features + ```bash + RUSTFLAGS="-D warnings" cargo clippy --workspace --lib --examples --tests --benches --all-features --locked + ``` + +3. **Testing**: Use nextest for faster test execution + ```bash + cargo nextest run --workspace + ``` + +### Common Contribution Types + +Based on actual recent PRs, here are typical contribution patterns: + +#### 1. Small Bug Fixes (1-10 lines) +Real example: Fixing beacon block root handling ([#16767](https://github.com/paradigmxyz/reth/pull/16767)) +```rust +// Changed a single line to fix logic error +- parent_beacon_block_root: parent.parent_beacon_block_root(), ++ parent_beacon_block_root: parent.parent_beacon_block_root().map(|_| B256::ZERO), +``` + +#### 2. Integration with Upstream Changes +Real example: Integrating revm updates ([#16752](https://github.com/paradigmxyz/reth/pull/16752)) +```rust +// Update code to use new APIs from dependencies +- if self.fork_tracker.is_shanghai_activated() { +- if let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) { ++ if let Some(init_code_size_limit) = self.fork_tracker.max_initcode_size() { ++ if let Err(err) = transaction.ensure_max_init_code_size(init_code_size_limit) { +``` + +#### 3. Adding Comprehensive Tests +Real example: ETH69 protocol tests ([#16759](https://github.com/paradigmxyz/reth/pull/16759)) +```rust +#[tokio::test(flavor = "multi_thread")] +async fn test_eth69_peers_can_connect() { + // Create test network with specific protocol versions + let p0 = PeerConfig::with_protocols(NoopProvider::default(), Some(EthVersion::Eth69.into())); + // Test connection and version negotiation +} +``` + +#### 4. Making Components Generic +Real example: Making EthEvmConfig generic over chainspec ([#16758](https://github.com/paradigmxyz/reth/pull/16758)) +```rust +// Before: Hardcoded to ChainSpec +- pub struct EthEvmConfig { +- pub executor_factory: EthBlockExecutorFactory, EvmFactory>, + +// After: Generic over any chain spec type ++ pub struct EthEvmConfig ++ where ++ C: EthereumHardforks, ++ { ++ pub executor_factory: EthBlockExecutorFactory, EvmFactory>, +``` + +#### 5. Resource Management Improvements +Real example: ETL directory cleanup ([#16770](https://github.com/paradigmxyz/reth/pull/16770)) +```rust +// Add cleanup logic on startup ++ if let Err(err) = fs::remove_dir_all(&etl_path) { ++ warn!(target: "reth::cli", ?etl_path, %err, "Failed to remove ETL path on launch"); ++ } +``` + +#### 6. Feature Additions +Real example: Sharded mempool support ([#16756](https://github.com/paradigmxyz/reth/pull/16756)) +```rust +// Add new filtering policies for transaction announcements +pub struct ShardedMempoolAnnouncementFilter { + pub inner: T, + pub shard_bits: u8, + pub node_id: Option, +} +``` + +### Testing Guidelines + +1. **Unit Tests**: Test individual functions and components +2. **Integration Tests**: Test interactions between components +3. **Benchmarks**: For performance-critical code +4. **Fuzz Tests**: For parsing and serialization code +5. **Property Tests**: For checking component correctness on a wide variety of inputs + +Example test structure: +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_component_behavior() { + // Arrange + let component = Component::new(); + + // Act + let result = component.operation(); + + // Assert + assert_eq!(result, expected); + } +} +``` + +### Performance Considerations + +1. **Avoid Allocations in Hot Paths**: Use references and borrowing +2. **Parallel Processing**: Use rayon for CPU-bound parallel work +3. **Async/Await**: Use tokio for I/O-bound operations + +### Common Pitfalls + +1. **Don't Block Async Tasks**: Use `spawn_blocking` for CPU-intensive work or work with lots of blocking I/O +2. **Handle Errors Properly**: Use `?` operator and proper error types + +### What to Avoid + +Based on PR patterns, avoid: + +1. **Large, sweeping changes**: Keep PRs focused and reviewable +2. **Mixing unrelated changes**: One logical change per PR +3. **Ignoring CI failures**: All checks must pass +4. **Incomplete implementations**: Finish features before submitting + +### CI Requirements + +Before submitting changes, ensure: + +1. **Format Check**: `cargo +nightly fmt --all --check` +2. **Clippy**: No warnings with `RUSTFLAGS="-D warnings"` +3. **Tests Pass**: All unit and integration tests +4. **Documentation**: Update relevant docs and add doc comments with `cargo docs --document-private-items` +5. **Commit Messages**: Follow conventional format (feat:, fix:, chore:, etc.) + + +### Opening PRs against + +Label PRs appropriately, first check the available labels and then apply the relevant ones: +* when changes are RPC related, add A-rpc label +* when changes are docs related, add C-docs label +* when changes are optimism related (e.g. new feature or exlusive changes to crates/optimism), add A-op-reth label +* ... and so on, check the available labels for more options. + +If changes in reth include changes to dependencies, run commands `zepter` and `make lint-toml` before finalizing the pr. Assume `zepter` binary is installed. + +### Debugging Tips + +1. **Logging**: Use `tracing` crate with appropriate levels + ```rust + tracing::debug!(target: "reth::component", ?value, "description"); + ``` + +2. **Metrics**: Add metrics for monitoring + ```rust + metrics::counter!("reth_component_operations").increment(1); + ``` + +3. **Test Isolation**: Use separate test databases/directories + +### Finding Where to Contribute + +1. **Check Issues**: Look for issues labeled `good-first-issue` or `help-wanted` +2. **Review TODOs**: Search for `TODO` comments in the codebase +3. **Improve Tests**: Areas with low test coverage are good targets +4. **Documentation**: Improve code comments and documentation +5. **Performance**: Profile and optimize hot paths (with benchmarks) + +### Common PR Patterns + +#### Small, Focused Changes +Most PRs change only 1-5 files. Examples: +- Single-line bug fixes +- Adding a missing trait implementation +- Updating error messages +- Adding test cases for edge conditions + +#### Integration Work +When dependencies update (especially revm), code needs updating: +- Check for breaking API changes +- Update to use new features (like EIP implementations) +- Ensure compatibility with new versions + +#### Test Improvements +Tests often need expansion for: +- New protocol versions (ETH68, ETH69) +- Edge cases in state transitions +- Network behavior under specific conditions +- Concurrent operations + +#### Making Code More Generic +Common refactoring pattern: +- Replace concrete types with generics +- Add trait bounds for flexibility +- Enable reuse across different chain types (Ethereum, Optimism) + +### Example Contribution Workflow + +Let's say you want to fix a bug where external IP resolution fails on startup: + +1. **Create a branch**: + ```bash + git checkout -b fix-external-ip-resolution + ``` + +2. **Find the relevant code**: + ```bash + # Search for IP resolution code + rg "external.*ip" --type rust + ``` + +3. **Reason about the problem, when the problem is identified, make the fix**: + ```rust + // In crates/net/discv4/src/lib.rs + pub fn resolve_external_ip() -> Option { + // Add fallback mechanism + nat::external_ip() + .or_else(|| nat::external_ip_from_stun()) + .or_else(|| Some(DEFAULT_IP)) + } + ``` + +4. **Add a test**: + ```rust + #[test] + fn test_external_ip_fallback() { + // Test that resolution has proper fallbacks + } + ``` + +5. **Run checks**: + ```bash + cargo +nightly fmt --all + cargo clippy --all-features + cargo test -p reth-discv4 + ``` + +6. **Commit with clear message**: + ```bash + git commit -m "fix: add fallback for external IP resolution + + Previously, node startup could fail if external IP resolution + failed. This adds fallback mechanisms to ensure the node can + always start with a reasonable default." + ``` + +## Quick Reference + +### Essential Commands + +```bash +# Format code +cargo +nightly fmt --all + +# Run lints +RUSTFLAGS="-D warnings" cargo clippy --workspace --all-features --locked + +# Run tests +cargo nextest run --workspace + +# Run specific benchmark +cargo bench --bench bench_name + +# Build optimized binary +cargo build --release --features "jemalloc asm-keccak" + +# Check compilation for all features +cargo check --workspace --all-features + +# Check documentation +cargo docs --document-private-items +``` \ No newline at end of file From 1d01f2a46d360cc7f380517093a1af1515f50ff2 Mon Sep 17 00:00:00 2001 From: Suyash Nayan <89125422+7suyash7@users.noreply.github.com> Date: Wed, 18 Jun 2025 01:58:07 +0530 Subject: [PATCH 086/274] feat(trie): Decode storage proofs in parallel tasks (#16400) Signed-off-by: 7suyash7 --- crates/trie/parallel/src/proof.rs | 96 +++++++++++++------------- crates/trie/parallel/src/proof_task.rs | 17 +++-- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/crates/trie/parallel/src/proof.rs b/crates/trie/parallel/src/proof.rs index f090ec98c86..e183f566948 100644 --- a/crates/trie/parallel/src/proof.rs +++ b/crates/trie/parallel/src/proof.rs @@ -25,10 +25,10 @@ use reth_trie::{ trie_cursor::{InMemoryTrieCursorFactory, TrieCursorFactory}, updates::TrieUpdatesSorted, walker::TrieWalker, - DecodedMultiProof, DecodedStorageMultiProof, HashBuilder, HashedPostStateSorted, MultiProof, - MultiProofTargets, Nibbles, StorageMultiProof, TRIE_ACCOUNT_RLP_MAX_SIZE, + DecodedMultiProof, DecodedStorageMultiProof, HashBuilder, HashedPostStateSorted, + MultiProofTargets, Nibbles, TRIE_ACCOUNT_RLP_MAX_SIZE, }; -use reth_trie_common::proof::ProofRetainer; +use reth_trie_common::proof::{DecodedProofNodes, ProofRetainer}; use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; use std::sync::{mpsc::Receiver, Arc}; use tracing::debug; @@ -97,7 +97,7 @@ where hashed_address: B256, prefix_set: PrefixSet, target_slots: B256Set, - ) -> Receiver> { + ) -> Receiver> { let input = StorageProofInput::new( hashed_address, prefix_set, @@ -116,7 +116,7 @@ where self, hashed_address: B256, target_slots: B256Set, - ) -> Result { + ) -> Result { let total_targets = target_slots.len(); let prefix_set = PrefixSetMut::from(target_slots.iter().map(Nibbles::unpack)); let prefix_set = prefix_set.freeze(); @@ -152,19 +152,14 @@ where hashed_address: B256, target_slots: B256Set, ) -> Result { - let proof = self.storage_proof(hashed_address, target_slots)?; - - // Now decode the nodes of the proof - let proof = proof.try_into()?; - - Ok(proof) + self.storage_proof(hashed_address, target_slots) } /// Generate a state multiproof according to specified targets. - pub fn multiproof( + pub fn decoded_multiproof( self, targets: MultiProofTargets, - ) -> Result { + ) -> Result { let mut tracker = ParallelTrieTracker::default(); // Extend prefix sets with targets @@ -199,7 +194,7 @@ where // stores the receiver for the storage proof outcome for the hashed addresses // this way we can lazily await the outcome when we iterate over the map - let mut storage_proofs = + let mut storage_proof_receivers = B256Map::with_capacity_and_hasher(storage_root_targets.len(), Default::default()); for (hashed_address, prefix_set) in @@ -210,7 +205,7 @@ where // store the receiver for that result with the hashed address so we can await this in // place when we iterate over the trie - storage_proofs.insert(hashed_address, receiver); + storage_proof_receivers.insert(hashed_address, receiver); } let provider_ro = self.view.provider_ro()?; @@ -238,8 +233,8 @@ where // Initialize all storage multiproofs as empty. // Storage multiproofs for non empty tries will be overwritten if necessary. - let mut storages: B256Map<_> = - targets.keys().map(|key| (*key, StorageMultiProof::empty())).collect(); + let mut collected_decoded_storages: B256Map = + targets.keys().map(|key| (*key, DecodedStorageMultiProof::empty())).collect(); let mut account_rlp = Vec::with_capacity(TRIE_ACCOUNT_RLP_MAX_SIZE); let mut account_node_iter = TrieNodeIter::state_trie( walker, @@ -253,11 +248,13 @@ where hash_builder.add_branch(node.key, node.value, node.children_are_in_trie); } TrieElement::Leaf(hashed_address, account) => { - let storage_multiproof = match storage_proofs.remove(&hashed_address) { - Some(rx) => rx.recv().map_err(|_| { + let decoded_storage_multiproof = match storage_proof_receivers + .remove(&hashed_address) + { + Some(rx) => rx.recv().map_err(|e| { ParallelStateRootError::StorageRoot(StorageRootError::Database( DatabaseError::Other(format!( - "channel closed for {hashed_address}" + "channel closed for {hashed_address}: {e}" )), )) })??, @@ -265,7 +262,8 @@ where // be a possibility of re-adding a non-modified leaf to the hash builder. None => { tracker.inc_missed_leaves(); - StorageProof::new_hashed( + + let raw_fallback_proof = StorageProof::new_hashed( trie_cursor_factory.clone(), hashed_cursor_factory.clone(), hashed_address, @@ -278,20 +276,23 @@ where ParallelStateRootError::StorageRoot(StorageRootError::Database( DatabaseError::Other(e.to_string()), )) - })? + })?; + + raw_fallback_proof.try_into()? } }; // Encode account account_rlp.clear(); - let account = account.into_trie_account(storage_multiproof.root); + let account = account.into_trie_account(decoded_storage_multiproof.root); account.encode(&mut account_rlp as &mut dyn BufMut); hash_builder.add_leaf(Nibbles::unpack(hashed_address), &account_rlp); // We might be adding leaves that are not necessarily our proof targets. if targets.contains_key(&hashed_address) { - storages.insert(hashed_address, storage_multiproof); + collected_decoded_storages + .insert(hashed_address, decoded_storage_multiproof); } } } @@ -302,7 +303,9 @@ where #[cfg(feature = "metrics")] self.metrics.record(stats); - let account_subtree = hash_builder.take_proof_nodes(); + let account_subtree_raw_nodes = hash_builder.take_proof_nodes(); + let decoded_account_subtree = DecodedProofNodes::try_from(account_subtree_raw_nodes)?; + let (branch_node_hash_masks, branch_node_tree_masks) = if self.collect_branch_node_masks { let updated_branch_nodes = hash_builder.updated_branch_nodes.unwrap_or_default(); ( @@ -327,25 +330,15 @@ where leaves_added = stats.leaves_added(), missed_leaves = stats.missed_leaves(), precomputed_storage_roots = stats.precomputed_storage_roots(), - "Calculated proof" + "Calculated decoded proof" ); - Ok(MultiProof { account_subtree, branch_node_hash_masks, branch_node_tree_masks, storages }) - } - - /// Returns a [`DecodedMultiProof`] for the given proof. - /// - /// Uses `multiproof` first to get the proof, and then decodes the nodes of the multiproof. - pub fn decoded_multiproof( - self, - targets: MultiProofTargets, - ) -> Result { - let multiproof = self.multiproof(targets)?; - - // Now decode the nodes of the multiproof - let multiproof = multiproof.try_into()?; - - Ok(multiproof) + Ok(DecodedMultiProof { + account_subtree: decoded_account_subtree, + branch_node_hash_masks, + branch_node_tree_masks, + storages: collected_decoded_storages, + }) } } @@ -446,26 +439,31 @@ mod tests { Default::default(), proof_task_handle.clone(), ) - .multiproof(targets.clone()) + .decoded_multiproof(targets.clone()) .unwrap(); - let sequential_result = - Proof::new(trie_cursor_factory, hashed_cursor_factory).multiproof(targets).unwrap(); + let sequential_result_raw = Proof::new(trie_cursor_factory, hashed_cursor_factory) + .multiproof(targets.clone()) + .unwrap(); // targets might be consumed by parallel_result + let sequential_result_decoded: DecodedMultiProof = sequential_result_raw + .try_into() + .expect("Failed to decode sequential_result for test comparison"); // to help narrow down what is wrong - first compare account subtries - assert_eq!(parallel_result.account_subtree, sequential_result.account_subtree); + assert_eq!(parallel_result.account_subtree, sequential_result_decoded.account_subtree); // then compare length of all storage subtries - assert_eq!(parallel_result.storages.len(), sequential_result.storages.len()); + assert_eq!(parallel_result.storages.len(), sequential_result_decoded.storages.len()); // then compare each storage subtrie for (hashed_address, storage_proof) in ¶llel_result.storages { - let sequential_storage_proof = sequential_result.storages.get(hashed_address).unwrap(); + let sequential_storage_proof = + sequential_result_decoded.storages.get(hashed_address).unwrap(); assert_eq!(storage_proof, sequential_storage_proof); } // then compare the entire thing for any mask differences - assert_eq!(parallel_result, sequential_result); + assert_eq!(parallel_result, sequential_result_decoded); // drop the handle to terminate the task and then block on the proof task handle to make // sure it does not return any errors diff --git a/crates/trie/parallel/src/proof_task.rs b/crates/trie/parallel/src/proof_task.rs index 516d92c4daa..70dea2cf22f 100644 --- a/crates/trie/parallel/src/proof_task.rs +++ b/crates/trie/parallel/src/proof_task.rs @@ -22,7 +22,7 @@ use reth_trie::{ proof::{ProofBlindedProviderFactory, StorageProof}, trie_cursor::InMemoryTrieCursorFactory, updates::TrieUpdatesSorted, - HashedPostStateSorted, Nibbles, StorageMultiProof, + DecodedStorageMultiProof, HashedPostStateSorted, Nibbles, }; use reth_trie_common::prefix_set::{PrefixSet, PrefixSetMut}; use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; @@ -39,7 +39,7 @@ use std::{ use tokio::runtime::Handle; use tracing::debug; -type StorageProofResult = Result; +type StorageProofResult = Result; type BlindedNodeResult = Result, SparseTrieError>; /// A task that manages sending multiproof requests to a number of tasks that have longer-running @@ -244,7 +244,7 @@ where let target_slots_len = input.target_slots.len(); let proof_start = Instant::now(); - let result = StorageProof::new_hashed( + let raw_proof_result = StorageProof::new_hashed( trie_cursor_factory, hashed_cursor_factory, input.hashed_address, @@ -254,6 +254,15 @@ where .storage_multiproof(input.target_slots) .map_err(|e| ParallelStateRootError::Other(e.to_string())); + let decoded_result = raw_proof_result.and_then(|raw_proof| { + raw_proof.try_into().map_err(|e: alloy_rlp::Error| { + ParallelStateRootError::Other(format!( + "Failed to decode storage proof for {}: {}", + input.hashed_address, e + )) + }) + }); + debug!( target: "trie::proof_task", hashed_address=?input.hashed_address, @@ -264,7 +273,7 @@ where ); // send the result back - if let Err(error) = result_sender.send(result) { + if let Err(error) = result_sender.send(decoded_result) { debug!( target: "trie::proof_task", hashed_address = ?input.hashed_address, From cb11ab04751cebea8c39cb8b44ee0559695c9e44 Mon Sep 17 00:00:00 2001 From: rotcan <50956594+rotcan@users.noreply.github.com> Date: Wed, 18 Jun 2025 02:01:08 +0530 Subject: [PATCH 087/274] feat(engine): Compare sorted trie updates in witness invalid block hook#15689 (#16481) --- .../engine/invalid-block-hooks/src/witness.rs | 6 +- crates/trie/common/src/updates.rs | 58 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 7ddf593d337..b3b54281128 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -317,13 +317,15 @@ where if &trie_output != original_updates { // Trie updates are too big to diff, so we just save the original and re-executed + let trie_output_sorted = &trie_output.into_sorted_ref(); + let original_updates_sorted = &original_updates.into_sorted_ref(); let original_path = self.save_file( format!("{}_{}.trie_updates.original.json", block.number(), block.hash()), - original_updates, + original_updates_sorted, )?; let re_executed_path = self.save_file( format!("{}_{}.trie_updates.re_executed.json", block.number(), block.hash()), - &trie_output, + trie_output_sorted, )?; warn!( target: "engine::invalid_block_hooks::witness", diff --git a/crates/trie/common/src/updates.rs b/crates/trie/common/src/updates.rs index d4362542f00..477e35b2c70 100644 --- a/crates/trie/common/src/updates.rs +++ b/crates/trie/common/src/updates.rs @@ -1,8 +1,11 @@ use crate::{BranchNodeCompact, HashBuilder, Nibbles}; -use alloc::vec::Vec; +use alloc::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + vec::Vec, +}; use alloy_primitives::{ map::{B256Map, B256Set, HashMap, HashSet}, - B256, + FixedBytes, B256, }; /// The aggregation of trie updates. @@ -114,6 +117,22 @@ impl TrieUpdates { .collect(); TrieUpdatesSorted { removed_nodes: self.removed_nodes, account_nodes, storage_tries } } + + /// Converts trie updates into [`TrieUpdatesSortedRef`]. + pub fn into_sorted_ref<'a>(&'a self) -> TrieUpdatesSortedRef<'a> { + let mut account_nodes = self.account_nodes.iter().collect::>(); + account_nodes.sort_unstable_by(|a, b| a.0.cmp(b.0)); + + TrieUpdatesSortedRef { + removed_nodes: self.removed_nodes.iter().collect::>(), + account_nodes, + storage_tries: self + .storage_tries + .iter() + .map(|m| (*m.0, m.1.into_sorted_ref().clone())) + .collect(), + } + } } /// Trie updates for storage trie of a single account. @@ -225,6 +244,15 @@ impl StorageTrieUpdates { storage_nodes, } } + + /// Convert storage trie updates into [`StorageTrieUpdatesSortedRef`]. + pub fn into_sorted_ref(&self) -> StorageTrieUpdatesSortedRef<'_> { + StorageTrieUpdatesSortedRef { + is_deleted: self.is_deleted, + removed_nodes: self.removed_nodes.iter().collect::>(), + storage_nodes: self.storage_nodes.iter().collect::>(), + } + } } /// Serializes and deserializes any [`HashSet`] that includes [`Nibbles`] elements, by using the @@ -350,8 +378,21 @@ mod serde_nibbles_map { } } +/// Sorted trie updates reference used for serializing trie to file. +#[derive(PartialEq, Eq, Clone, Default, Debug)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize))] +pub struct TrieUpdatesSortedRef<'a> { + /// Sorted collection of updated state nodes with corresponding paths. + pub account_nodes: Vec<(&'a Nibbles, &'a BranchNodeCompact)>, + /// The set of removed state node keys. + pub removed_nodes: BTreeSet<&'a Nibbles>, + /// Storage tries stored by hashed address of the account the trie belongs to. + pub storage_tries: BTreeMap, StorageTrieUpdatesSortedRef<'a>>, +} + /// Sorted trie updates used for lookups and insertions. #[derive(PartialEq, Eq, Clone, Default, Debug)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] pub struct TrieUpdatesSorted { /// Sorted collection of updated state nodes with corresponding paths. pub account_nodes: Vec<(Nibbles, BranchNodeCompact)>, @@ -378,8 +419,21 @@ impl TrieUpdatesSorted { } } +/// Sorted storage trie updates reference used for serializing to file. +#[derive(PartialEq, Eq, Clone, Default, Debug)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize))] +pub struct StorageTrieUpdatesSortedRef<'a> { + /// Flag indicating whether the trie has been deleted/wiped. + pub is_deleted: bool, + /// Sorted collection of updated storage nodes with corresponding paths. + pub storage_nodes: BTreeMap<&'a Nibbles, &'a BranchNodeCompact>, + /// The set of removed storage node keys. + pub removed_nodes: BTreeSet<&'a Nibbles>, +} + /// Sorted trie updates used for lookups and insertions. #[derive(PartialEq, Eq, Clone, Default, Debug)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] pub struct StorageTrieUpdatesSorted { /// Flag indicating whether the trie has been deleted/wiped. pub is_deleted: bool, From a808533f35974cae29eac57e70509efa1dc98e88 Mon Sep 17 00:00:00 2001 From: 0xsensei Date: Wed, 18 Jun 2025 03:03:58 +0530 Subject: [PATCH 088/274] fix(pipeline): prevent unwind beyond history limits (#16593) Co-authored-by: Aditya Pandey --- crates/prune/types/src/lib.rs | 2 +- crates/prune/types/src/target.rs | 58 +++++++++++++++++++++++++++ crates/stages/api/src/error.rs | 5 ++- crates/stages/api/src/pipeline/mod.rs | 11 ++++- 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/crates/prune/types/src/lib.rs b/crates/prune/types/src/lib.rs index ef8ef882b8c..c1d268a0fb7 100644 --- a/crates/prune/types/src/lib.rs +++ b/crates/prune/types/src/lib.rs @@ -30,7 +30,7 @@ pub use pruner::{ SegmentOutputCheckpoint, }; pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError}; -pub use target::{PruneModes, MINIMUM_PRUNING_DISTANCE}; +pub use target::{PruneModes, UnwindTargetPrunedError, MINIMUM_PRUNING_DISTANCE}; /// Configuration for pruning receipts not associated with logs emitted by the specified contracts. #[derive(Debug, Clone, PartialEq, Eq, Default)] diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 9edeb71ec97..c0f9515fa60 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -1,3 +1,7 @@ +use alloy_primitives::BlockNumber; +use derive_more::Display; +use thiserror::Error; + use crate::{PruneMode, ReceiptsLogPruneConfig}; /// Minimum distance from the tip necessary for the node to work correctly: @@ -7,6 +11,31 @@ use crate::{PruneMode, ReceiptsLogPruneConfig}; /// unwind is required. pub const MINIMUM_PRUNING_DISTANCE: u64 = 32 * 2 + 10_000; +/// Type of history that can be pruned +#[derive(Debug, Error, PartialEq, Eq, Clone)] +pub enum UnwindTargetPrunedError { + /// The target block is beyond the history limit + #[error("Cannot unwind to block {target_block} as it is beyond the {history_type} limit. Latest block: {latest_block}, History limit: {limit}")] + TargetBeyondHistoryLimit { + /// The latest block number + latest_block: BlockNumber, + /// The target block number + target_block: BlockNumber, + /// The type of history that is beyond the limit + history_type: HistoryType, + /// The limit of the history + limit: u64, + }, +} + +#[derive(Debug, Display, Clone, PartialEq, Eq)] +pub enum HistoryType { + /// Account history + AccountHistory, + /// Storage history + StorageHistory, +} + /// Pruning configuration for every segment of the data that can be pruned. #[derive(Debug, Clone, Default, Eq, PartialEq)] #[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] @@ -81,6 +110,35 @@ impl PruneModes { pub fn is_empty(&self) -> bool { self == &Self::none() } + + /// Returns true if target block is within history limit + pub fn ensure_unwind_target_unpruned( + &self, + latest_block: u64, + target_block: u64, + ) -> Result<(), UnwindTargetPrunedError> { + let distance = latest_block.saturating_sub(target_block); + [ + (self.account_history, HistoryType::AccountHistory), + (self.storage_history, HistoryType::StorageHistory), + ] + .iter() + .find_map(|(prune_mode, history_type)| { + if let Some(PruneMode::Distance(limit)) = prune_mode { + (distance > *limit).then_some(Err( + UnwindTargetPrunedError::TargetBeyondHistoryLimit { + latest_block, + target_block, + history_type: history_type.clone(), + limit: *limit, + }, + )) + } else { + None + } + }) + .unwrap_or(Ok(())) + } } /// Deserializes [`Option`] and validates that the value is not less than the const diff --git a/crates/stages/api/src/error.rs b/crates/stages/api/src/error.rs index 92b1d974542..b4bbf390e22 100644 --- a/crates/stages/api/src/error.rs +++ b/crates/stages/api/src/error.rs @@ -4,7 +4,7 @@ use reth_consensus::ConsensusError; use reth_errors::{BlockExecutionError, DatabaseError, RethError}; use reth_network_p2p::error::DownloadError; use reth_provider::ProviderError; -use reth_prune::{PruneSegment, PruneSegmentError, PrunerError}; +use reth_prune::{PruneSegment, PruneSegmentError, PrunerError, UnwindTargetPrunedError}; use reth_static_file_types::StaticFileSegment; use thiserror::Error; use tokio::sync::broadcast::error::SendError; @@ -163,4 +163,7 @@ pub enum PipelineError { /// The pipeline encountered an unwind when `fail_on_unwind` was set to `true`. #[error("unexpected unwind")] UnexpectedUnwind, + /// Unwind target pruned error. + #[error(transparent)] + UnwindTargetPruned(#[from] UnwindTargetPrunedError), } diff --git a/crates/stages/api/src/pipeline/mod.rs b/crates/stages/api/src/pipeline/mod.rs index a064dd471be..b8d41e9e552 100644 --- a/crates/stages/api/src/pipeline/mod.rs +++ b/crates/stages/api/src/pipeline/mod.rs @@ -7,7 +7,7 @@ pub use event::*; use futures_util::Future; use reth_primitives_traits::constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH; use reth_provider::{ - providers::ProviderNodeTypes, writer::UnifiedStorageWriter, BlockHashReader, + providers::ProviderNodeTypes, writer::UnifiedStorageWriter, BlockHashReader, BlockNumReader, ChainStateBlockReader, ChainStateBlockWriter, DatabaseProviderFactory, ProviderFactory, StageCheckpointReader, StageCheckpointWriter, }; @@ -294,6 +294,15 @@ impl Pipeline { to: BlockNumber, bad_block: Option, ) -> Result<(), PipelineError> { + // Add validation before starting unwind + let provider = self.provider_factory.provider()?; + let latest_block = provider.last_block_number()?; + + // Get the actual pruning configuration + let prune_modes = provider.prune_modes_ref(); + + prune_modes.ensure_unwind_target_unpruned(latest_block, to)?; + // Unwind stages in reverse order of execution let unwind_pipeline = self.stages.iter_mut().rev(); From 671f0fe566a4906a582027e2781c52b1e2bf3104 Mon Sep 17 00:00:00 2001 From: Odinson Date: Wed, 18 Jun 2025 03:07:27 +0530 Subject: [PATCH 089/274] feat: introduced loop with range of chunks in the incremental root stage (#16178) Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- crates/cli/commands/src/stage/dump/merkle.rs | 7 +- crates/cli/commands/src/stage/run.rs | 7 +- crates/config/src/config.rs | 14 +- crates/stages/stages/benches/criterion.rs | 4 +- crates/stages/stages/src/sets.rs | 5 +- crates/stages/stages/src/stages/execution.rs | 10 +- crates/stages/stages/src/stages/merkle.rs | 135 ++++++++++++++++--- crates/stages/stages/src/stages/mod.rs | 2 +- 8 files changed, 150 insertions(+), 34 deletions(-) diff --git a/crates/cli/commands/src/stage/dump/merkle.rs b/crates/cli/commands/src/stage/dump/merkle.rs index 904d43dbade..2c0e784006f 100644 --- a/crates/cli/commands/src/stage/dump/merkle.rs +++ b/crates/cli/commands/src/stage/dump/merkle.rs @@ -18,7 +18,7 @@ use reth_provider::{ use reth_stages::{ stages::{ AccountHashingStage, ExecutionStage, MerkleStage, StorageHashingStage, - MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, }, ExecutionStageThresholds, Stage, StageCheckpoint, UnwindInput, }; @@ -108,7 +108,7 @@ fn unwind_and_copy( max_cumulative_gas: None, max_duration: None, }, - MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, ExExManagerHandle::empty(), ); @@ -161,7 +161,8 @@ where let mut stage = MerkleStage::Execution { // Forces updating the root instead of calculating from scratch - clean_threshold: u64::MAX, + rebuild_threshold: u64::MAX, + incremental_threshold: u64::MAX, }; loop { diff --git a/crates/cli/commands/src/stage/run.rs b/crates/cli/commands/src/stage/run.rs index e21f3996edc..312e9059b9f 100644 --- a/crates/cli/commands/src/stage/run.rs +++ b/crates/cli/commands/src/stage/run.rs @@ -271,7 +271,7 @@ impl max_cumulative_gas: None, max_duration: None, }, - config.stages.merkle.clean_threshold, + config.stages.merkle.incremental_threshold, ExExManagerHandle::empty(), )), None, @@ -299,7 +299,10 @@ impl None, ), StageEnum::Merkle => ( - Box::new(MerkleStage::new_execution(config.stages.merkle.clean_threshold)), + Box::new(MerkleStage::new_execution( + config.stages.merkle.rebuild_threshold, + config.stages.merkle.incremental_threshold, + )), Some(Box::new(MerkleStage::default_unwind())), ), StageEnum::AccountHistory => ( diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 55883d04e8d..5c187074000 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -136,7 +136,7 @@ impl StageConfig { /// `ExecutionStage` pub fn execution_external_clean_threshold(&self) -> u64 { self.merkle - .clean_threshold + .incremental_threshold .max(self.account_hashing.clean_threshold) .max(self.storage_hashing.clean_threshold) } @@ -342,14 +342,22 @@ impl Default for HashingConfig { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct MerkleConfig { + /// The number of blocks we will run the incremental root method for when we are catching up on + /// the merkle stage for a large number of blocks. + /// + /// When we are catching up for a large number of blocks, we can only run the incremental root + /// for a limited number of blocks, otherwise the incremental root method may cause the node to + /// OOM. This number determines how many blocks in a row we will run the incremental root + /// method for. + pub incremental_threshold: u64, /// The threshold (in number of blocks) for switching from incremental trie building of changes /// to whole rebuild. - pub clean_threshold: u64, + pub rebuild_threshold: u64, } impl Default for MerkleConfig { fn default() -> Self { - Self { clean_threshold: 5_000 } + Self { incremental_threshold: 7_000, rebuild_threshold: 100_000 } } } diff --git a/crates/stages/stages/benches/criterion.rs b/crates/stages/stages/benches/criterion.rs index 08f700789ab..c804d582363 100644 --- a/crates/stages/stages/benches/criterion.rs +++ b/crates/stages/stages/benches/criterion.rs @@ -113,7 +113,7 @@ fn merkle(c: &mut Criterion, runtime: &Runtime) { let db = setup::txs_testdata(DEFAULT_NUM_BLOCKS); - let stage = MerkleStage::Both { clean_threshold: u64::MAX }; + let stage = MerkleStage::Both { rebuild_threshold: u64::MAX, incremental_threshold: u64::MAX }; measure_stage( runtime, &mut group, @@ -124,7 +124,7 @@ fn merkle(c: &mut Criterion, runtime: &Runtime) { "Merkle-incremental".to_string(), ); - let stage = MerkleStage::Both { clean_threshold: 0 }; + let stage = MerkleStage::Both { rebuild_threshold: 0, incremental_threshold: 0 }; measure_stage( runtime, &mut group, diff --git a/crates/stages/stages/src/sets.rs b/crates/stages/stages/src/sets.rs index 6f0f055a2ca..512e4571c96 100644 --- a/crates/stages/stages/src/sets.rs +++ b/crates/stages/stages/src/sets.rs @@ -411,7 +411,10 @@ where self.stages_config.storage_hashing, self.stages_config.etl.clone(), )) - .add_stage(MerkleStage::new_execution(self.stages_config.merkle.clean_threshold)) + .add_stage(MerkleStage::new_execution( + self.stages_config.merkle.rebuild_threshold, + self.stages_config.merkle.incremental_threshold, + )) } } diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 6833eddc1f5..9de94ee197f 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -1,4 +1,4 @@ -use crate::stages::MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD; +use crate::stages::MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD; use alloy_consensus::{BlockHeader, Header}; use alloy_primitives::BlockNumber; use num_traits::Zero; @@ -119,7 +119,7 @@ where /// Create an execution stage with the provided executor. /// - /// The commit threshold will be set to [`MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD`]. + /// The commit threshold will be set to [`MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD`]. pub fn new_with_executor( evm_config: E, consensus: Arc>, @@ -128,7 +128,7 @@ where evm_config, consensus, ExecutionStageThresholds::default(), - MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD, ExExManagerHandle::empty(), ) } @@ -656,7 +656,7 @@ fn calculate_gas_used_from_headers( #[cfg(test)] mod tests { use super::*; - use crate::test_utils::TestStageDB; + use crate::{stages::MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, test_utils::TestStageDB}; use alloy_primitives::{address, hex_literal::hex, keccak256, Address, B256, U256}; use alloy_rlp::Decodable; use assert_matches::assert_matches; @@ -693,7 +693,7 @@ mod tests { max_cumulative_gas: None, max_duration: None, }, - MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, ExExManagerHandle::empty(), ) } diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 55173876e96..4f8016b4568 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -40,7 +40,13 @@ Once you have this information, please submit a github issue at https://github.c /// The default threshold (in number of blocks) for switching from incremental trie building /// of changes to whole rebuild. -pub const MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD: u64 = 5_000; +pub const MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD: u64 = 100_000; + +/// The default threshold (in number of blocks) to run the stage in incremental mode. The +/// incremental mode will calculate the state root for a large range of blocks by calculating the +/// new state root for this many blocks, in batches, repeating until we reach the desired block +/// number. +pub const MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD: u64 = 7_000; /// The merkle hashing stage uses input from /// [`AccountHashingStage`][crate::stages::AccountHashingStage] and @@ -67,9 +73,15 @@ pub const MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD: u64 = 5_000; pub enum MerkleStage { /// The execution portion of the merkle stage. Execution { + // TODO: make struct for holding incremental settings, for code reuse between `Execution` + // variant and `Both` /// The threshold (in number of blocks) for switching from incremental trie building /// of changes to whole rebuild. - clean_threshold: u64, + rebuild_threshold: u64, + /// The threshold (in number of blocks) to run the stage in incremental mode. The + /// incremental mode will calculate the state root by calculating the new state root for + /// some number of blocks, repeating until we reach the desired block number. + incremental_threshold: u64, }, /// The unwind portion of the merkle stage. Unwind, @@ -78,14 +90,21 @@ pub enum MerkleStage { Both { /// The threshold (in number of blocks) for switching from incremental trie building /// of changes to whole rebuild. - clean_threshold: u64, + rebuild_threshold: u64, + /// The threshold (in number of blocks) to run the stage in incremental mode. The + /// incremental mode will calculate the state root by calculating the new state root for + /// some number of blocks, repeating until we reach the desired block number. + incremental_threshold: u64, }, } impl MerkleStage { /// Stage default for the [`MerkleStage::Execution`]. pub const fn default_execution() -> Self { - Self::Execution { clean_threshold: MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD } + Self::Execution { + rebuild_threshold: MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, + incremental_threshold: MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD, + } } /// Stage default for the [`MerkleStage::Unwind`]. @@ -94,8 +113,8 @@ impl MerkleStage { } /// Create new instance of [`MerkleStage::Execution`]. - pub const fn new_execution(clean_threshold: u64) -> Self { - Self::Execution { clean_threshold } + pub const fn new_execution(rebuild_threshold: u64, incremental_threshold: u64) -> Self { + Self::Execution { rebuild_threshold, incremental_threshold } } /// Gets the hashing progress @@ -154,14 +173,18 @@ where /// Execute the stage. fn execute(&mut self, provider: &Provider, input: ExecInput) -> Result { - let threshold = match self { + let (threshold, incremental_threshold) = match self { Self::Unwind => { info!(target: "sync::stages::merkle::unwind", "Stage is always skipped"); return Ok(ExecOutput::done(StageCheckpoint::new(input.target()))) } - Self::Execution { clean_threshold } => *clean_threshold, + Self::Execution { rebuild_threshold, incremental_threshold } => { + (*rebuild_threshold, *incremental_threshold) + } #[cfg(any(test, feature = "test-utils"))] - Self::Both { clean_threshold } => *clean_threshold, + Self::Both { rebuild_threshold, incremental_threshold } => { + (*rebuild_threshold, *incremental_threshold) + } }; let range = input.next_block_range(); @@ -251,15 +274,25 @@ where } } } else { - debug!(target: "sync::stages::merkle::exec", current = ?current_block_number, target = ?to_block, "Updating trie"); - let (root, updates) = - StateRoot::incremental_root_with_updates(provider.tx_ref(), range) + debug!(target: "sync::stages::merkle::exec", current = ?current_block_number, target = ?to_block, "Updating trie in chunks"); + let mut final_root = None; + for start_block in range.step_by(incremental_threshold as usize) { + let chunk_to = std::cmp::min(start_block + incremental_threshold, to_block); + let chunk_range = start_block..=chunk_to; + let (root, updates) = + StateRoot::incremental_root_with_updates(provider.tx_ref(), chunk_range) .map_err(|e| { error!(target: "sync::stages::merkle", %e, ?current_block_number, ?to_block, "Incremental state root failed! {INVALID_STATE_ROOT_ERROR_MESSAGE}"); StageError::Fatal(Box::new(e)) })?; + provider.write_trie_updates(&updates)?; + final_root = Some(root); + } - provider.write_trie_updates(&updates)?; + // if we had no final root, we must have not looped above, which should not be possible + let final_root = final_root.ok_or(StageError::Fatal( + "Incremental merkle hashing did not produce a final root".into(), + ))?; let total_hashed_entries = (provider.count_entries::()? + provider.count_entries::()?) @@ -272,8 +305,8 @@ where processed: total_hashed_entries, total: total_hashed_entries, }; - - (root, entities_checkpoint) + // Save the checkpoint + (final_root, entities_checkpoint) }; // Reset the checkpoint @@ -468,14 +501,79 @@ mod tests { assert!(runner.validate_execution(input, result.ok()).is_ok(), "execution validation"); } + #[tokio::test] + async fn execute_chunked_merkle() { + let (previous_stage, stage_progress) = (200, 100); + let clean_threshold = 100; + let incremental_threshold = 10; + + // Set up the runner + let mut runner = + MerkleTestRunner { db: TestStageDB::default(), clean_threshold, incremental_threshold }; + + let input = ExecInput { + target: Some(previous_stage), + checkpoint: Some(StageCheckpoint::new(stage_progress)), + }; + + runner.seed_execution(input).expect("failed to seed execution"); + let rx = runner.execute(input); + + // Assert the successful result + let result = rx.await.unwrap(); + assert_matches!( + result, + Ok(ExecOutput { + checkpoint: StageCheckpoint { + block_number, + stage_checkpoint: Some(StageUnitCheckpoint::Entities(EntitiesCheckpoint { + processed, + total + })) + }, + done: true + }) if block_number == previous_stage && processed == total && + total == ( + runner.db.table::().unwrap().len() + + runner.db.table::().unwrap().len() + ) as u64 + ); + + // Validate the stage execution + let provider = runner.db.factory.provider().unwrap(); + let header = provider.header_by_number(previous_stage).unwrap().unwrap(); + let expected_root = header.state_root; + + let actual_root = runner + .db + .query(|tx| { + Ok(StateRoot::incremental_root_with_updates( + tx, + stage_progress + 1..=previous_stage, + )) + }) + .unwrap(); + + assert_eq!( + actual_root.unwrap().0, + expected_root, + "State root mismatch after chunked processing" + ); + } + struct MerkleTestRunner { db: TestStageDB, clean_threshold: u64, + incremental_threshold: u64, } impl Default for MerkleTestRunner { fn default() -> Self { - Self { db: TestStageDB::default(), clean_threshold: 10000 } + Self { + db: TestStageDB::default(), + clean_threshold: 10000, + incremental_threshold: 10000, + } } } @@ -487,7 +585,10 @@ mod tests { } fn stage(&self) -> Self::S { - Self::S::Both { clean_threshold: self.clean_threshold } + Self::S::Both { + rebuild_threshold: self.clean_threshold, + incremental_threshold: self.incremental_threshold, + } } } diff --git a/crates/stages/stages/src/stages/mod.rs b/crates/stages/stages/src/stages/mod.rs index e1b952db79f..726609b2350 100644 --- a/crates/stages/stages/src/stages/mod.rs +++ b/crates/stages/stages/src/stages/mod.rs @@ -169,7 +169,7 @@ mod tests { max_cumulative_gas: None, max_duration: None, }, - MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, ExExManagerHandle::empty(), ); From 55134742d693cebbee1d1cbe19366458a36b3c14 Mon Sep 17 00:00:00 2001 From: Shane K Moore <41407272+shane-moore@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:24:54 -0700 Subject: [PATCH 090/274] chore: add block gas limit to block added log (#16875) --- crates/node/events/src/node.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index 10173bafdda..bd583c45e42 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -259,6 +259,7 @@ impl NodeState { txs=block.body().transactions().len(), gas=%format_gas(block.gas_used()), gas_throughput=%format_gas_throughput(block.gas_used(), elapsed), + gas_limit=%format_gas(block.gas_limit()), full=%format!("{:.1}%", block.gas_used() as f64 * 100.0 / block.gas_limit() as f64), base_fee=%format!("{:.2}gwei", block.base_fee_per_gas().unwrap_or(0) as f64 / GWEI_TO_WEI as f64), blobs=block.blob_gas_used().unwrap_or(0) / alloy_eips::eip4844::DATA_GAS_PER_BLOB, From 8dbbe7bda42f8c0705576a9885a40b0aa6bde1c2 Mon Sep 17 00:00:00 2001 From: Yeongjong Pyo Date: Wed, 18 Jun 2025 17:33:49 +0900 Subject: [PATCH 091/274] fix(test): handle getting the last base_fee_per_gas (#16881) --- crates/rpc/rpc/src/eth/core.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index cf70176ebb5..6a079b821bd 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -582,11 +582,9 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); - base_fees_per_gas.push(BaseFeeParams::ethereum().next_block_base_fee( - last_header.gas_used, - last_header.gas_limit, - last_header.base_fee_per_gas.unwrap_or_default(), - ) as u128); + base_fees_per_gas + .push(last_header.next_block_base_fee(BaseFeeParams::ethereum()).unwrap_or_default() + as u128); let eth_api = build_test_eth_api(mock_provider); From 5dc47e149ba559d5a1153d3af77c7e9300251e34 Mon Sep 17 00:00:00 2001 From: Krishang Shah <93703995+kamuik16@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:24:37 +0530 Subject: [PATCH 092/274] fix(op-reth, rpc): eth_getBlockReceipts err for genesis block in op-reth (#16879) --- crates/optimism/rpc/src/eth/block.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/optimism/rpc/src/eth/block.rs b/crates/optimism/rpc/src/eth/block.rs index 12f3c168d3f..34ce4081b2e 100644 --- a/crates/optimism/rpc/src/eth/block.rs +++ b/crates/optimism/rpc/src/eth/block.rs @@ -40,7 +40,17 @@ where let excess_blob_gas = block.excess_blob_gas(); let timestamp = block.timestamp(); - let mut l1_block_info = reth_optimism_evm::extract_l1_info(block.body())?; + let mut l1_block_info = match reth_optimism_evm::extract_l1_info(block.body()) { + Ok(l1_block_info) => l1_block_info, + Err(err) => { + // If it is the genesis block (i.e block number is 0), there is no L1 info, so + // we return an empty l1_block_info. + if block_number == 0 { + return Ok(Some(vec![])); + } + return Err(err.into()); + } + }; return block .body() From 619c8917ca0cc9ec2b23da7cc7d7c1a68d223d29 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 10:59:57 +0200 Subject: [PATCH 093/274] docs: enhance DebugNode trait documentation (#16872) Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- crates/node/builder/src/launch/debug.rs | 67 +++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/crates/node/builder/src/launch/debug.rs b/crates/node/builder/src/launch/debug.rs index 7609616b031..3fa6da72118 100644 --- a/crates/node/builder/src/launch/debug.rs +++ b/crates/node/builder/src/launch/debug.rs @@ -7,18 +7,78 @@ use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider, use reth_node_api::{BlockTy, FullNodeComponents}; use std::sync::Arc; use tracing::info; -/// [`Node`] extension with support for debugging utilities, see [`DebugNodeLauncher`] for more -/// context. + +/// [`Node`] extension with support for debugging utilities. +/// +/// This trait provides additional necessary conversion from RPC block type to the node's +/// primitive block type, e.g. `alloy_rpc_types_eth::Block` to the node's internal block +/// representation. +/// +/// This is used in conjunction with the [`DebugNodeLauncher`] to enable debugging features such as: +/// +/// - **Etherscan Integration**: Use Etherscan as a consensus client to follow the chain and submit +/// blocks to the local engine. +/// - **RPC Consensus Client**: Connect to an external RPC endpoint to fetch blocks and submit them +/// to the local engine to follow the chain. +/// +/// See [`DebugNodeLauncher`] for the launcher that enables these features. +/// +/// # Implementation +/// +/// To implement this trait, you need to: +/// 1. Define the RPC block type (typically `alloy_rpc_types_eth::Block`) +/// 2. Implement the conversion from RPC format to your primitive block type +/// +/// # Example +/// +/// ```ignore +/// impl> DebugNode for MyNode { +/// type RpcBlock = alloy_rpc_types_eth::Block; +/// +/// fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy { +/// // Convert from RPC format to primitive format by converting the transactions +/// rpc_block.into_consensus().convert_transactions() +/// } +/// } +/// ``` pub trait DebugNode: Node { /// RPC block type. Used by [`DebugConsensusClient`] to fetch blocks and submit them to the - /// engine. + /// engine. This is inteded to match the block format returned by the external RPC endpoint. type RpcBlock: Serialize + DeserializeOwned + 'static; /// Converts an RPC block to a primitive block. + /// + /// This method handles the conversion between the RPC block format and the internal primitive + /// block format used by the node's consensus engine. + /// + /// # Example + /// + /// For Ethereum nodes, this typically converts from `alloy_rpc_types_eth::Block` + /// to the node's internal block representation. fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy; } /// Node launcher with support for launching various debugging utilities. +/// +/// This launcher wraps an existing launcher and adds debugging capabilities when +/// certain debug flags are enabled. It provides two main debugging features: +/// +/// ## RPC Consensus Client +/// +/// When `--debug.rpc-consensus-ws ` is provided, the launcher will: +/// - Connect to an external RPC `WebSocket` endpoint +/// - Fetch blocks from that endpoint +/// - Submit them to the local engine for execution +/// - Useful for testing engine behavior with real network data +/// +/// ## Etherscan Consensus Client +/// +/// When `--debug.etherscan [URL]` is provided, the launcher will: +/// - Use Etherscan API as a consensus client +/// - Fetch recent blocks from Etherscan +/// - Submit them to the local engine +/// - Requires `ETHERSCAN_API_KEY` environment variable +/// - Falls back to default Etherscan URL for the chain if URL not provided #[derive(Debug, Clone)] pub struct DebugNodeLauncher { inner: L, @@ -66,7 +126,6 @@ where }); } - // TODO: migrate to devmode with https://github.com/paradigmxyz/reth/issues/10104 if let Some(maybe_custom_etherscan_url) = config.debug.etherscan.clone() { info!(target: "reth::cli", "Using etherscan as consensus client"); From 239aa08923d7979e13c95c02e62dcd3e618f38fb Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 18 Jun 2025 13:01:32 +0100 Subject: [PATCH 094/274] ci: pin nextest version (#16887) --- .github/workflows/integration.yml | 30 +++++++++++++++++------------- .github/workflows/unit.yml | 8 ++++++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index aad87b0fea8..7b23f8cc2ff 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -8,8 +8,8 @@ on: push: branches: [main] schedule: - # Run once a day at 3:00 UTC - - cron: '0 3 * * *' + # Run once a day at 3:00 UTC + - cron: "0 3 * * *" env: CARGO_TERM_COLOR: always @@ -37,7 +37,9 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install Geth run: .github/assets/install_geth.sh - - uses: taiki-e/install-action@nextest + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -66,17 +68,19 @@ jobs: with: jobs: ${{ toJSON(needs) }} - era-files: + era-files: name: era1 file integration tests once a day if: github.event_name == 'schedule' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: run era1 files integration tests - run: cargo nextest run --package reth-era --test it -- --ignored + - uses: actions/checkout@v4 + - uses: rui314/setup-mold@v1 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: run era1 files integration tests + run: cargo nextest run --package reth-era --test it -- --ignored diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 767a3e5c0ad..d3ee31e5db9 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -54,7 +54,9 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - uses: taiki-e/install-action@nextest + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 - if: "${{ matrix.type == 'book' }}" uses: arduino/setup-protoc@v3 with: @@ -87,7 +89,9 @@ jobs: fetch-depth: 1 - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true From 5437d2614d74803ee7580d97785d6bf28b0337cd Mon Sep 17 00:00:00 2001 From: Alessandro Mazza <121622391+alessandromazza98@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:47:49 +0200 Subject: [PATCH 095/274] test: add walk_dup test with not existing key (#16562) --- .../storage/db/src/implementation/mdbx/mod.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index f07837ab347..d536e69a270 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -1250,6 +1250,34 @@ mod tests { } } + #[test] + fn db_walk_dup_with_not_existing_key() { + let env = create_test_db(DatabaseEnvKind::RW); + let key = Address::from_str("0xa2c122be93b0074270ebee7f6b7292c7deb45047") + .expect(ERROR_ETH_ADDRESS); + + // PUT (0,0) + let value00 = StorageEntry::default(); + env.update(|tx| tx.put::(key, value00).expect(ERROR_PUT)).unwrap(); + + // PUT (2,2) + let value22 = StorageEntry { key: B256::with_last_byte(2), value: U256::from(2) }; + env.update(|tx| tx.put::(key, value22).expect(ERROR_PUT)).unwrap(); + + // PUT (1,1) + let value11 = StorageEntry { key: B256::with_last_byte(1), value: U256::from(1) }; + env.update(|tx| tx.put::(key, value11).expect(ERROR_PUT)).unwrap(); + + // Try to walk_dup with not existing key should immediately return None + { + let tx = env.tx().expect(ERROR_INIT_TX); + let mut cursor = tx.cursor_dup_read::().unwrap(); + let not_existing_key = Address::ZERO; + let mut walker = cursor.walk_dup(Some(not_existing_key), None).unwrap(); + assert_eq!(walker.next(), None); + } + } + #[test] fn db_iterate_over_all_dup_values() { let env = create_test_db(DatabaseEnvKind::RW); From 7c0e95bd37c0d0fed1c92c1efbf09dea7af938b5 Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:30:59 +0530 Subject: [PATCH 096/274] feat: added experimental eth_sendrawtransaction endpoint (wip) (#16683) Co-authored-by: Matthias Seitz --- Cargo.lock | 1 + crates/rpc/rpc-eth-api/Cargo.toml | 1 + .../rpc-eth-api/src/helpers/transaction.rs | 49 ++++++++++++++++++- crates/rpc/rpc-eth-api/src/types.rs | 5 +- crates/rpc/rpc-eth-types/src/error/mod.rs | 15 +++++- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a01ed82a10d..4b2bceb1dd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9979,6 +9979,7 @@ dependencies = [ "jsonrpsee", "jsonrpsee-types", "parking_lot", + "reth-chain-state", "reth-chainspec", "reth-errors", "reth-evm", diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index 9a9dbc1bf81..a6d9bea6193 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -14,6 +14,7 @@ workspace = true [dependencies] # reth revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip3607", "optional_no_base_fee"] } +reth-chain-state.workspace = true revm-inspectors.workspace = true reth-primitives-traits = { workspace = true, features = ["rpc-compat"] } reth-errors.workspace = true diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 4c7377a30fd..df53fbf6f25 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -15,10 +15,14 @@ use alloy_eips::{eip2718::Encodable2718, BlockId}; use alloy_network::TransactionBuilder; use alloy_primitives::{Address, Bytes, TxHash, B256}; use alloy_rpc_types_eth::{transaction::TransactionRequest, BlockNumberOrTag, TransactionInfo}; -use futures::Future; +use futures::{Future, StreamExt}; +use reth_chain_state::CanonStateSubscriptions; use reth_node_api::BlockBody; use reth_primitives_traits::{RecoveredBlock, SignedTransaction}; -use reth_rpc_eth_types::{utils::binary_search, EthApiError, SignError, TransactionSource}; +use reth_rpc_eth_types::{ + utils::binary_search, EthApiError, EthApiError::TransactionConfirmationTimeout, SignError, + TransactionSource, +}; use reth_rpc_types_compat::transaction::TransactionCompat; use reth_storage_api::{ BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider, @@ -64,6 +68,47 @@ pub trait EthTransactions: LoadTransaction { tx: Bytes, ) -> impl Future> + Send; + /// Decodes and recovers the transaction and submits it to the pool. + /// + /// And awaits the receipt. + fn send_raw_transaction_sync( + &self, + tx: Bytes, + ) -> impl Future, Self::Error>> + Send + where + Self: LoadReceipt + 'static, + { + let this = self.clone(); + async move { + let hash = EthTransactions::send_raw_transaction(&this, tx).await?; + let mut stream = this.provider().canonical_state_stream(); + const TIMEOUT_DURATION: tokio::time::Duration = tokio::time::Duration::from_secs(30); + tokio::time::timeout(TIMEOUT_DURATION, async { + while let Some(notification) = stream.next().await { + let chain = notification.committed(); + for block in chain.blocks_iter() { + if block.body().contains_transaction(&hash) { + if let Some(receipt) = this.transaction_receipt(hash).await? { + return Ok(receipt); + } + } + } + } + Err(Self::Error::from_eth_err(TransactionConfirmationTimeout { + hash, + duration: TIMEOUT_DURATION, + })) + }) + .await + .unwrap_or_else(|_elapsed| { + Err(Self::Error::from_eth_err(TransactionConfirmationTimeout { + hash, + duration: TIMEOUT_DURATION, + })) + }) + } + } + /// Returns the transaction by hash. /// /// Checks the pool and state. diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index bdc8d615737..5eafcdd0b01 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -4,6 +4,7 @@ use crate::{AsEthApiError, FromEthApiError, RpcNodeCore}; use alloy_json_rpc::RpcObject; use alloy_network::{Network, ReceiptResponse, TransactionResponse}; use alloy_rpc_types_eth::Block; +use reth_chain_state::CanonStateSubscriptions; use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{ProviderTx, ReceiptProvider, TransactionsProvider}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; @@ -77,7 +78,7 @@ pub type RpcError = ::Error; pub trait FullEthApiTypes where Self: RpcNodeCore< - Provider: TransactionsProvider + ReceiptProvider, + Provider: TransactionsProvider + ReceiptProvider + CanonStateSubscriptions, Pool: TransactionPool< Transaction: PoolTransaction>, >, @@ -93,7 +94,7 @@ where impl FullEthApiTypes for T where T: RpcNodeCore< - Provider: TransactionsProvider + ReceiptProvider, + Provider: TransactionsProvider + ReceiptProvider + CanonStateSubscriptions, Pool: TransactionPool< Transaction: PoolTransaction>, >, diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index ac56ea9c608..d0d698949ef 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -3,7 +3,7 @@ pub mod api; use crate::error::api::FromEvmHalt; use alloy_eips::BlockId; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError}; use alloy_sol_types::{ContractError, RevertReason}; pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError}; @@ -155,6 +155,16 @@ pub enum EthApiError { /// Error thrown when tracing with a muxTracer fails #[error(transparent)] MuxTracerError(#[from] MuxError), + /// Error thrown when waiting for transaction confirmation times out + #[error( + "Transaction {hash} was added to the mempool but wasn't confirmed within {duration:?}." + )] + TransactionConfirmationTimeout { + /// Hash of the transaction that timed out + hash: B256, + /// Duration that was waited before timing out + duration: Duration, + }, /// Any other error #[error("{0}")] Other(Box), @@ -222,6 +232,9 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { block_id_to_str(end_id), ), ), + err @ EthApiError::TransactionConfirmationTimeout { .. } => { + rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), err.to_string()) + } EthApiError::Unsupported(msg) => internal_rpc_err(msg), EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg), EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg), From 95cd15e5953c19562753ac1175c133edc798471f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Wed, 18 Jun 2025 15:20:13 +0200 Subject: [PATCH 097/274] perf(era): Skip download if ERA file with verified checksum exists (#16804) --- crates/era-downloader/src/client.rs | 154 ++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 40 deletions(-) diff --git a/crates/era-downloader/src/client.rs b/crates/era-downloader/src/client.rs index 5d5d4033f5f..56d0c789c47 100644 --- a/crates/era-downloader/src/client.rs +++ b/crates/era-downloader/src/client.rs @@ -7,7 +7,7 @@ use sha2::{Digest, Sha256}; use std::{future::Future, path::Path, str::FromStr}; use tokio::{ fs::{self, File}, - io::{self, AsyncBufReadExt, AsyncWriteExt}, + io::{self, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWriteExt}, join, try_join, }; @@ -65,50 +65,34 @@ impl EraClient { .ok_or_eyre("empty path segments")?; let path = path.join(file_name); - let number = - self.file_name_to_number(file_name).ok_or_eyre("Cannot parse number from file name")?; + if !self.is_downloaded(file_name, &path).await? { + let number = self + .file_name_to_number(file_name) + .ok_or_eyre("Cannot parse number from file name")?; + + let mut tries = 1..3; + let mut actual_checksum: eyre::Result<_>; + loop { + actual_checksum = async { + let mut file = File::create(&path).await?; + let mut stream = client.get(url.clone()).await?; + let mut hasher = Sha256::new(); + + while let Some(item) = stream.next().await.transpose()? { + io::copy(&mut item.as_ref(), &mut file).await?; + hasher.update(item); + } - let mut tries = 1..3; - let mut actual_checksum: eyre::Result<_>; - loop { - actual_checksum = async { - let mut file = File::create(&path).await?; - let mut stream = client.get(url.clone()).await?; - let mut hasher = Sha256::new(); - - while let Some(item) = stream.next().await.transpose()? { - io::copy(&mut item.as_ref(), &mut file).await?; - hasher.update(item); + Ok(hasher.finalize().to_vec()) } + .await; - Ok(hasher.finalize().to_vec()) - } - .await; - - if actual_checksum.is_ok() || tries.next().is_none() { - break; + if actual_checksum.is_ok() || tries.next().is_none() { + break; + } } - } - - let actual_checksum = actual_checksum?; - - let file = File::open(self.folder.join(Self::CHECKSUMS)).await?; - let reader = io::BufReader::new(file); - let mut lines = reader.lines(); - - for _ in 0..number { - lines.next_line().await?; - } - let expected_checksum = - lines.next_line().await?.ok_or_else(|| eyre!("Missing hash for number {number}"))?; - let expected_checksum = hex::decode(expected_checksum)?; - if actual_checksum != expected_checksum { - return Err(eyre!( - "Checksum mismatch, got: {}, expected: {}", - actual_checksum.encode_hex(), - expected_checksum.encode_hex() - )); + self.assert_checksum(number, actual_checksum?).await?; } Ok(path.into_boxed_path()) @@ -248,11 +232,101 @@ impl EraClient { Ok(lines.next_line().await?) } + async fn is_downloaded(&self, name: &str, path: impl AsRef) -> eyre::Result { + let path = path.as_ref(); + + match File::open(path).await { + Ok(file) => { + let number = self + .file_name_to_number(name) + .ok_or_else(|| eyre!("Cannot parse ERA number from {name}"))?; + + let actual_checksum = checksum(file).await?; + let is_verified = self.verify_checksum(number, actual_checksum).await?; + + if !is_verified { + fs::remove_file(path).await?; + } + + Ok(is_verified) + } + Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(e)?, + } + } + + /// Returns `true` if `actual_checksum` matches expected checksum of the ERA1 file indexed by + /// `number` based on the [file list]. + /// + /// [file list]: Self::fetch_file_list + async fn verify_checksum(&self, number: usize, actual_checksum: Vec) -> eyre::Result { + Ok(actual_checksum == self.expected_checksum(number).await?) + } + + /// Returns `Ok` if `actual_checksum` matches expected checksum of the ERA1 file indexed by + /// `number` based on the [file list]. + /// + /// [file list]: Self::fetch_file_list + async fn assert_checksum(&self, number: usize, actual_checksum: Vec) -> eyre::Result<()> { + let expected_checksum = self.expected_checksum(number).await?; + + if actual_checksum == expected_checksum { + Ok(()) + } else { + Err(eyre!( + "Checksum mismatch, got: {}, expected: {}", + actual_checksum.encode_hex(), + expected_checksum.encode_hex() + )) + } + } + + /// Returns SHA-256 checksum for ERA1 file indexed by `number` based on the [file list]. + /// + /// [file list]: Self::fetch_file_list + async fn expected_checksum(&self, number: usize) -> eyre::Result> { + let file = File::open(self.folder.join(Self::CHECKSUMS)).await?; + let reader = io::BufReader::new(file); + let mut lines = reader.lines(); + + for _ in 0..number { + lines.next_line().await?; + } + let expected_checksum = + lines.next_line().await?.ok_or_else(|| eyre!("Missing hash for number {number}"))?; + let expected_checksum = hex::decode(expected_checksum)?; + + Ok(expected_checksum) + } + fn file_name_to_number(&self, file_name: &str) -> Option { file_name.split('-').nth(1).and_then(|v| usize::from_str(v).ok()) } } +async fn checksum(mut reader: impl AsyncRead + Unpin) -> eyre::Result> { + let mut hasher = Sha256::new(); + + // Create a buffer to read data into, sized for performance. + let mut data = vec![0; 64 * 1024]; + + loop { + // Read data from the reader into the buffer. + let len = reader.read(&mut data).await?; + if len == 0 { + break; + } // Exit loop if no more data. + + // Update the hash with the data read. + hasher.update(&data[..len]); + } + + // Finalize the hash after all data has been processed. + let hash = hasher.finalize().to_vec(); + + Ok(hash) +} + #[cfg(test)] mod tests { use super::*; From 04f09f920871470ca2bc98a47ff0cbf33acdcb15 Mon Sep 17 00:00:00 2001 From: Krishang Shah <93703995+kamuik16@users.noreply.github.com> Date: Wed, 18 Jun 2025 18:54:01 +0530 Subject: [PATCH 098/274] chore(tx-pool): use max_blobs_per_tx in validating eip4844 txs (#16888) Co-authored-by: Matthias Seitz --- Cargo.lock | 4 ++-- crates/transaction-pool/src/validate/eth.rs | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b2bceb1dd1..d7fe7807307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8390cb5c872d53560635dabc02d616c1bb626dd0f7d6893f8725edb822573fed" +checksum = "4f853de9ca1819f54de80de5d03bfc1bb7c9fafcf092b480a654447141bc354d" dependencies = [ "alloy-eip2124", "alloy-eip2930", diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index a5f85ac6edb..2e30691916d 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -691,7 +691,7 @@ where { self.fork_tracker .max_blob_count - .store(blob_params.max_blob_count, std::sync::atomic::Ordering::Relaxed); + .store(blob_params.max_blobs_per_tx, std::sync::atomic::Ordering::Relaxed); } self.block_gas_limit.store(new_tip_block.gas_limit(), std::sync::atomic::Ordering::Relaxed); @@ -781,7 +781,7 @@ impl EthTransactionValidatorBuilder { osaka: false, // max blob count is prague by default - max_blob_count: BlobParams::prague().max_blob_count, + max_blob_count: BlobParams::prague().max_blobs_per_tx, } } @@ -905,7 +905,7 @@ impl EthTransactionValidatorBuilder { .chain_spec() .blob_params_at_timestamp(timestamp) .unwrap_or_else(BlobParams::cancun) - .max_blob_count; + .max_blobs_per_tx; self } @@ -955,11 +955,10 @@ impl EthTransactionValidatorBuilder { .. } = self; - // TODO: use osaka max blob count once is released let max_blob_count = if prague { - BlobParams::prague().max_blob_count + BlobParams::prague().max_blobs_per_tx } else { - BlobParams::cancun().max_blob_count + BlobParams::cancun().max_blobs_per_tx }; let fork_tracker = ForkTracker { @@ -1045,7 +1044,7 @@ pub struct ForkTracker { pub prague: AtomicBool, /// Tracks if osaka is activated at the block's timestamp. pub osaka: AtomicBool, - /// Tracks max blob count at the block's timestamp. + /// Tracks max blob count per transaction at the block's timestamp. pub max_blob_count: AtomicU64, } @@ -1070,7 +1069,7 @@ impl ForkTracker { self.osaka.load(std::sync::atomic::Ordering::Relaxed) } - /// Returns the max blob count. + /// Returns the max allowed blob count per transaction. pub fn max_blob_count(&self) -> u64 { self.max_blob_count.load(std::sync::atomic::Ordering::Relaxed) } From 21cf573d9755c9fd6a1dea9506c5be38487cde2f Mon Sep 17 00:00:00 2001 From: 0xNarumi <122093865+0xNarumi@users.noreply.github.com> Date: Wed, 18 Jun 2025 22:29:11 +0900 Subject: [PATCH 099/274] fix: move `bytecode_by_hash` from `StateProvider` to a dedicated `BytecodeReader` (#16886) --- crates/alloy-provider/src/lib.rs | 21 ++++++++++++------- crates/chain-state/src/in_memory.rs | 6 ++++-- crates/chain-state/src/memory_overlay.rs | 6 ++++-- crates/engine/tree/src/tree/cached_state.rs | 6 ++++-- .../tree/src/tree/instrumented_state.rs | 6 ++++-- crates/revm/src/database.rs | 4 ++-- crates/revm/src/test_utils.rs | 6 ++++-- crates/rpc/rpc-eth-types/src/cache/db.rs | 18 +++++++++------- crates/rpc/rpc/src/debug.rs | 2 +- .../src/providers/state/historical.rs | 7 ++++++- .../provider/src/providers/state/latest.rs | 6 +++++- .../provider/src/providers/state/macros.rs | 2 ++ .../storage/provider/src/test_utils/mock.rs | 12 ++++++++--- crates/storage/storage-api/src/noop.rs | 10 +++++---- crates/storage/storage-api/src/state.rs | 11 +++++++--- 15 files changed, 83 insertions(+), 40 deletions(-) diff --git a/crates/alloy-provider/src/lib.rs b/crates/alloy-provider/src/lib.rs index 318ecec8b23..5b4df72ece0 100644 --- a/crates/alloy-provider/src/lib.rs +++ b/crates/alloy-provider/src/lib.rs @@ -34,8 +34,8 @@ use reth_primitives::{ Account, Bytecode, RecoveredBlock, SealedBlock, SealedHeader, TransactionMeta, }; use reth_provider::{ - AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, CanonChainTracker, - CanonStateNotification, CanonStateNotifications, CanonStateSubscriptions, + AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BytecodeReader, + CanonChainTracker, CanonStateNotification, CanonStateNotifications, CanonStateSubscriptions, ChainStateBlockReader, ChainStateBlockWriter, ChangeSetReader, DatabaseProviderFactory, HeaderProvider, PruneCheckpointReader, ReceiptProvider, StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, StorageReader, @@ -575,11 +575,6 @@ where }) } - fn bytecode_by_hash(&self, _code_hash: &B256) -> Result, ProviderError> { - // Cannot fetch bytecode by hash via RPC - Err(ProviderError::UnsupportedProvider) - } - fn account_code(&self, addr: &Address) -> Result, ProviderError> { self.block_on_async(async { let code = self @@ -606,6 +601,18 @@ where } } +impl BytecodeReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn bytecode_by_hash(&self, _code_hash: &B256) -> Result, ProviderError> { + // Cannot fetch bytecode by hash via RPC + Err(ProviderError::UnsupportedProvider) + } +} + impl AccountReader for AlloyRethStateProvider where P: Provider + Clone + 'static, diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index 7e8e3e7027a..20f2a2a4c21 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -996,8 +996,8 @@ mod tests { use reth_ethereum_primitives::{EthPrimitives, Receipt}; use reth_primitives_traits::{Account, Bytecode}; use reth_storage_api::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, + StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider, }; use reth_trie::{ AccountProof, HashedStorage, MultiProof, MultiProofTargets, StorageMultiProof, @@ -1045,7 +1045,9 @@ mod tests { ) -> ProviderResult> { Ok(None) } + } + impl BytecodeReader for MockStateProvider { fn bytecode_by_hash(&self, _code_hash: &B256) -> ProviderResult> { Ok(None) } diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index e454b84b700..dfb76d0e583 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -4,8 +4,8 @@ use alloy_primitives::{keccak256, Address, BlockNumber, Bytes, StorageKey, Stora use reth_errors::ProviderResult; use reth_primitives_traits::{Account, Bytecode, NodePrimitives}; use reth_storage_api::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider, + StateProvider, StateRootProvider, StorageRootProvider, }; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, @@ -222,7 +222,9 @@ impl StateProvider for MemoryOverlayStateProviderRef<'_, N> { self.historical.storage(address, storage_key) } +} +impl BytecodeReader for MemoryOverlayStateProviderRef<'_, N> { fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { for block in &self.in_memory { if let Some(contract) = block.execution_output.bytecode(code_hash) { diff --git a/crates/engine/tree/src/tree/cached_state.rs b/crates/engine/tree/src/tree/cached_state.rs index a6e16a7503a..bce9949564f 100644 --- a/crates/engine/tree/src/tree/cached_state.rs +++ b/crates/engine/tree/src/tree/cached_state.rs @@ -6,8 +6,8 @@ use reth_errors::ProviderResult; use reth_metrics::Metrics; use reth_primitives_traits::{Account, Bytecode}; use reth_provider::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider, + StateProvider, StateRootProvider, StorageRootProvider, }; use reth_revm::db::BundleState; use reth_trie::{ @@ -162,7 +162,9 @@ impl StateProvider for CachedStateProvider { } } } +} +impl BytecodeReader for CachedStateProvider { fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { if let Some(res) = self.caches.code_cache.get(code_hash) { self.metrics.code_cache_hits.increment(1); diff --git a/crates/engine/tree/src/tree/instrumented_state.rs b/crates/engine/tree/src/tree/instrumented_state.rs index ab6707972ec..9d96aca3a2e 100644 --- a/crates/engine/tree/src/tree/instrumented_state.rs +++ b/crates/engine/tree/src/tree/instrumented_state.rs @@ -5,8 +5,8 @@ use reth_errors::ProviderResult; use reth_metrics::Metrics; use reth_primitives_traits::{Account, Bytecode}; use reth_provider::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider, + StateProvider, StateRootProvider, StorageRootProvider, }; use reth_trie::{ updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof, @@ -191,7 +191,9 @@ impl StateProvider for InstrumentedStateProvider { self.record_storage_fetch(start.elapsed()); res } +} +impl BytecodeReader for InstrumentedStateProvider { fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { let start = Instant::now(); let res = self.state_provider.bytecode_by_hash(code_hash); diff --git a/crates/revm/src/database.rs b/crates/revm/src/database.rs index fafe990c3b1..50415815759 100644 --- a/crates/revm/src/database.rs +++ b/crates/revm/src/database.rs @@ -2,7 +2,7 @@ use crate::primitives::alloy_primitives::{BlockNumber, StorageKey, StorageValue} use alloy_primitives::{Address, B256, U256}; use core::ops::{Deref, DerefMut}; use reth_primitives_traits::Account; -use reth_storage_api::{AccountReader, BlockHashReader, StateProvider}; +use reth_storage_api::{AccountReader, BlockHashReader, BytecodeReader, StateProvider}; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use revm::{bytecode::Bytecode, state::AccountInfo, Database, DatabaseRef}; @@ -47,7 +47,7 @@ impl EvmStateProvider for T { &self, code_hash: &B256, ) -> ProviderResult> { - ::bytecode_by_hash(self, code_hash) + ::bytecode_by_hash(self, code_hash) } fn storage( diff --git a/crates/revm/src/test_utils.rs b/crates/revm/src/test_utils.rs index d32f7a9e7a7..e0d40070878 100644 --- a/crates/revm/src/test_utils.rs +++ b/crates/revm/src/test_utils.rs @@ -4,8 +4,8 @@ use alloy_primitives::{ }; use reth_primitives_traits::{Account, Bytecode}; use reth_storage_api::{ - AccountReader, BlockHashReader, HashedPostStateProvider, StateProofProvider, StateProvider, - StateRootProvider, StorageRootProvider, + AccountReader, BlockHashReader, BytecodeReader, HashedPostStateProvider, StateProofProvider, + StateProvider, StateRootProvider, StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -158,7 +158,9 @@ impl StateProvider for StateProviderTest { ) -> ProviderResult> { Ok(self.accounts.get(&account).and_then(|(storage, _)| storage.get(&storage_key).copied())) } +} +impl BytecodeReader for StateProviderTest { fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { Ok(self.contracts.get(code_hash).cloned()) } diff --git a/crates/rpc/rpc-eth-types/src/cache/db.rs b/crates/rpc/rpc-eth-types/src/cache/db.rs index 7f45b6407f0..7c1bedb8224 100644 --- a/crates/rpc/rpc-eth-types/src/cache/db.rs +++ b/crates/rpc/rpc-eth-types/src/cache/db.rs @@ -5,7 +5,7 @@ use alloy_primitives::{Address, B256, U256}; use reth_errors::ProviderResult; use reth_revm::{database::StateProviderDatabase, DatabaseRef}; -use reth_storage_api::{HashedPostStateProvider, StateProvider}; +use reth_storage_api::{BytecodeReader, HashedPostStateProvider, StateProvider}; use reth_trie::{HashedStorage, MultiProofTargets}; use revm::{ database::{BundleState, CacheDB}, @@ -155,13 +155,6 @@ impl StateProvider for StateProviderTraitObjWrapper<'_> { self.0.storage(account, storage_key) } - fn bytecode_by_hash( - &self, - code_hash: &B256, - ) -> reth_errors::ProviderResult> { - self.0.bytecode_by_hash(code_hash) - } - fn account_code( &self, addr: &Address, @@ -178,6 +171,15 @@ impl StateProvider for StateProviderTraitObjWrapper<'_> { } } +impl BytecodeReader for StateProviderTraitObjWrapper<'_> { + fn bytecode_by_hash( + &self, + code_hash: &B256, + ) -> reth_errors::ProviderResult> { + self.0.bytecode_by_hash(code_hash) + } +} + /// Hack to get around 'higher-ranked lifetime error', see /// #[expect(missing_debug_implementations)] diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 4ca8317f5c3..b95b749de7b 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -34,7 +34,7 @@ use reth_rpc_eth_types::{EthApiError, StateCacheDb}; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_storage_api::{ BlockIdReader, BlockReaderIdExt, HeaderProvider, ProviderBlock, ReceiptProviderIdExt, - StateProofProvider, StateProvider, StateProviderFactory, StateRootProvider, TransactionVariant, + StateProofProvider, StateProviderFactory, StateRootProvider, TransactionVariant, }; use reth_tasks::pool::BlockingTaskGuard; use reth_trie_common::{updates::TrieUpdates, HashedPostState}; diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index b39b5e20a68..e815f98740c 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -14,7 +14,8 @@ use reth_db_api::{ }; use reth_primitives_traits::{Account, Bytecode}; use reth_storage_api::{ - BlockNumReader, DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, + BlockNumReader, BytecodeReader, DBProvider, StateCommitmentProvider, StateProofProvider, + StorageRootProvider, }; use reth_storage_errors::provider::ProviderResult; use reth_trie::{ @@ -433,7 +434,11 @@ impl BytecodeReader + for HistoricalStateProviderRef<'_, Provider> +{ /// Get account code by its hash fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { self.tx().get_by_encoded_key::(code_hash).map_err(Into::into) diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 8443e6b4c58..334e0109dcc 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -6,7 +6,7 @@ use alloy_primitives::{Address, BlockNumber, Bytes, StorageKey, StorageValue, B2 use reth_db_api::{cursor::DbDupCursorRO, tables, transaction::DbTx}; use reth_primitives_traits::{Account, Bytecode}; use reth_storage_api::{ - DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, + BytecodeReader, DBProvider, StateCommitmentProvider, StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::{ProviderError, ProviderResult}; use reth_trie::{ @@ -177,7 +177,11 @@ impl StateProv } Ok(None) } +} +impl BytecodeReader + for LatestStateProviderRef<'_, Provider> +{ /// Get account code by its hash fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { self.tx().get_by_encoded_key::(code_hash).map_err(Into::into) diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 36216755ec8..74bb371819f 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -39,6 +39,8 @@ macro_rules! delegate_provider_impls { } StateProvider $(where [$($generics)*])? { fn storage(&self, account: alloy_primitives::Address, storage_key: alloy_primitives::StorageKey) -> reth_storage_errors::provider::ProviderResult>; + } + BytecodeReader $(where [$($generics)*])? { fn bytecode_by_hash(&self, code_hash: &alloy_primitives::B256) -> reth_storage_errors::provider::ProviderResult>; } StateRootProvider $(where [$($generics)*])? { diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 17c29549b0a..2d0cfb665df 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -29,9 +29,9 @@ use reth_primitives_traits::{ use reth_prune_types::PruneModes; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ - BlockBodyIndicesProvider, DBProvider, DatabaseProviderFactory, HashedPostStateProvider, - NodePrimitivesProvider, StageCheckpointReader, StateCommitmentProvider, StateProofProvider, - StorageRootProvider, + BlockBodyIndicesProvider, BytecodeReader, DBProvider, DatabaseProviderFactory, + HashedPostStateProvider, NodePrimitivesProvider, StageCheckpointReader, + StateCommitmentProvider, StateProofProvider, StorageRootProvider, }; use reth_storage_errors::provider::{ConsistentViewError, ProviderError, ProviderResult}; use reth_trie::{ @@ -876,7 +876,13 @@ where let lock = self.accounts.lock(); Ok(lock.get(&account).and_then(|account| account.storage.get(&storage_key)).copied()) } +} +impl BytecodeReader for MockEthProvider +where + T: NodePrimitives, + ChainSpec: Send + Sync, +{ fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult> { let lock = self.accounts.lock(); Ok(lock.values().find_map(|account| { diff --git a/crates/storage/storage-api/src/noop.rs b/crates/storage/storage-api/src/noop.rs index 3a48aecd695..2afa4b616f5 100644 --- a/crates/storage/storage-api/src/noop.rs +++ b/crates/storage/storage-api/src/noop.rs @@ -2,10 +2,10 @@ use crate::{ AccountReader, BlockBodyIndicesProvider, BlockHashReader, BlockIdReader, BlockNumReader, - BlockReader, BlockReaderIdExt, BlockSource, ChangeSetReader, HashedPostStateProvider, - HeaderProvider, NodePrimitivesProvider, PruneCheckpointReader, ReceiptProvider, - ReceiptProviderIdExt, StageCheckpointReader, StateProofProvider, StateProvider, - StateProviderBox, StateProviderFactory, StateRootProvider, StorageRootProvider, + BlockReader, BlockReaderIdExt, BlockSource, BytecodeReader, ChangeSetReader, + HashedPostStateProvider, HeaderProvider, NodePrimitivesProvider, PruneCheckpointReader, + ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProofProvider, + StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, StorageRootProvider, TransactionVariant, TransactionsProvider, }; use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; @@ -455,7 +455,9 @@ impl StateProvider for NoopProvider { ) -> ProviderResult> { Ok(None) } +} +impl BytecodeReader for NoopProvider { fn bytecode_by_hash(&self, _code_hash: &B256) -> ProviderResult> { Ok(None) } diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 301aebaa78a..f70a3b34c41 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -34,6 +34,7 @@ pub type StateProviderBox = Box; pub trait StateProvider: BlockHashReader + AccountReader + + BytecodeReader + StateRootProvider + StorageRootProvider + StateProofProvider @@ -48,9 +49,6 @@ pub trait StateProvider: storage_key: StorageKey, ) -> ProviderResult>; - /// Get account code by its hash - fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult>; - /// Get account code by its address. /// /// Returns `None` if the account doesn't exist or account is not a contract @@ -110,6 +108,13 @@ pub trait HashedPostStateProvider: Send + Sync { fn hashed_post_state(&self, bundle_state: &BundleState) -> HashedPostState; } +/// Trait for reading bytecode associated with a given code hash. +#[auto_impl(&, Arc, Box)] +pub trait BytecodeReader: Send + Sync { + /// Get account code by its hash + fn bytecode_by_hash(&self, code_hash: &B256) -> ProviderResult>; +} + /// Trait implemented for database providers that can be converted into a historical state provider. pub trait TryIntoHistoricalStateProvider { /// Returns a historical [`StateProvider`] indexed by the given historic block number. From 8758d82456d88243747c54db2886ab9b880e00c1 Mon Sep 17 00:00:00 2001 From: Ashutosh Varma Date: Wed, 18 Jun 2025 19:53:57 +0530 Subject: [PATCH 100/274] feat: add abstractions for permit in metered channel (#16882) --- crates/metrics/src/common/mpsc.rs | 81 +++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/crates/metrics/src/common/mpsc.rs b/crates/metrics/src/common/mpsc.rs index 2de8ddf9d53..b347440203f 100644 --- a/crates/metrics/src/common/mpsc.rs +++ b/crates/metrics/src/common/mpsc.rs @@ -11,7 +11,6 @@ use std::{ use tokio::sync::mpsc::{ self, error::{SendError, TryRecvError, TrySendError}, - OwnedPermit, }; use tokio_util::sync::{PollSendError, PollSender}; @@ -144,11 +143,38 @@ impl MeteredSender { Self { sender, metrics: MeteredSenderMetrics::new(scope) } } - /// Tries to acquire a permit to send a message. + /// Tries to acquire a permit to send a message without waiting. /// /// See also [Sender](mpsc::Sender)'s `try_reserve_owned`. - pub fn try_reserve_owned(&self) -> Result, TrySendError>> { - self.sender.clone().try_reserve_owned() + pub fn try_reserve_owned(self) -> Result, TrySendError> { + let Self { sender, metrics } = self; + sender.try_reserve_owned().map(|permit| OwnedPermit::new(permit, metrics.clone())).map_err( + |err| match err { + TrySendError::Full(sender) => TrySendError::Full(Self { sender, metrics }), + TrySendError::Closed(sender) => TrySendError::Closed(Self { sender, metrics }), + }, + ) + } + + /// Waits to acquire a permit to send a message and return owned permit. + /// + /// See also [Sender](mpsc::Sender)'s `reserve_owned`. + pub async fn reserve_owned(self) -> Result, SendError<()>> { + self.sender.reserve_owned().await.map(|permit| OwnedPermit::new(permit, self.metrics)) + } + + /// Waits to acquire a permit to send a message. + /// + /// See also [Sender](mpsc::Sender)'s `reserve`. + pub async fn reserve(&self) -> Result, SendError<()>> { + self.sender.reserve().await.map(|permit| Permit::new(permit, &self.metrics)) + } + + /// Tries to acquire a permit to send a message without waiting. + /// + /// See also [Sender](mpsc::Sender)'s `try_reserve`. + pub fn try_reserve(&self) -> Result, TrySendError<()>> { + self.sender.try_reserve().map(|permit| Permit::new(permit, &self.metrics)) } /// Returns the underlying [Sender](mpsc::Sender). @@ -193,6 +219,51 @@ impl Clone for MeteredSender { } } +/// A wrapper type around [`OwnedPermit`](mpsc::OwnedPermit) that updates metrics accounting +/// when sending +#[derive(Debug)] +pub struct OwnedPermit { + permit: mpsc::OwnedPermit, + /// Holds metrics for this type + metrics: MeteredSenderMetrics, +} + +impl OwnedPermit { + /// Creates a new [`OwnedPermit`] wrapping the provided [`mpsc::OwnedPermit`] with given metrics + /// handle. + pub const fn new(permit: mpsc::OwnedPermit, metrics: MeteredSenderMetrics) -> Self { + Self { permit, metrics } + } + + /// Sends a value using the reserved capacity and update metrics accordingly. + pub fn send(self, value: T) -> MeteredSender { + let Self { permit, metrics } = self; + metrics.messages_sent_total.increment(1); + MeteredSender { sender: permit.send(value), metrics } + } +} + +/// A wrapper type around [Permit](mpsc::Permit) that updates metrics accounting +/// when sending +#[derive(Debug)] +pub struct Permit<'a, T> { + permit: mpsc::Permit<'a, T>, + metrics_ref: &'a MeteredSenderMetrics, +} + +impl<'a, T> Permit<'a, T> { + /// Creates a new [`Permit`] wrapping the provided [`mpsc::Permit`] with given metrics ref. + pub const fn new(permit: mpsc::Permit<'a, T>, metrics_ref: &'a MeteredSenderMetrics) -> Self { + Self { permit, metrics_ref } + } + + /// Sends a value using the reserved capacity and updates metrics accordingly. + pub fn send(self, value: T) { + self.metrics_ref.messages_sent_total.increment(1); + self.permit.send(value); + } +} + /// A wrapper type around [Receiver](mpsc::Receiver) that updates metrics on receive. #[derive(Debug)] pub struct MeteredReceiver { @@ -252,7 +323,7 @@ impl Stream for MeteredReceiver { /// Throughput metrics for [`MeteredSender`] #[derive(Clone, Metrics)] #[metrics(dynamic = true)] -struct MeteredSenderMetrics { +pub struct MeteredSenderMetrics { /// Number of messages sent messages_sent_total: Counter, /// Number of failed message deliveries From d29f83e5638c279983401778b1840f0ef7d623ba Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:26:47 +0100 Subject: [PATCH 101/274] feat: add newPayload throughput and total gas charts to Grafana (#16901) --- etc/grafana/dashboards/overview.json | 483 +++++++++++++++++++++------ 1 file changed, 378 insertions(+), 105 deletions(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 0beec770921..88a8d437971 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -14,6 +14,13 @@ "description": "", "type": "datasource", "pluginId": "__expr__" + }, + { + "name": "VAR_INSTANCE_LABEL", + "type": "constant", + "label": "Instance Label", + "value": "job", + "description": "" } ], "__elements": {}, @@ -1785,6 +1792,268 @@ "title": "Engine API newPayload Latency", "type": "timeseries" }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "The metric is the amount of gas processed in a block", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "sishort" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 29 + }, + "id": 1004, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_total_gas{$instance_label=\"$instance\", quantile=\"0.5\"}", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_total_gas{$instance_label=\"$instance\", quantile=\"0.9\"}", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_total_gas{$instance_label=\"$instance\", quantile=\"0.95\"}", + "hide": false, + "legendFormat": "p95", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_total_gas{$instance_label=\"$instance\", quantile=\"0.99\"}", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "D" + } + ], + "title": "Engine API newPayload Total Gas", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "description": "The throughput of the Engine API newPayload method. The metric is the amount of gas processed in a block, divided by the time it took to process the newPayload request.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "si: gas/s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 29 + }, + "id": 1003, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_gas_per_second{$instance_label=\"$instance\", quantile=\"0.5\"}", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_gas_per_second{$instance_label=\"$instance\", quantile=\"0.9\"}", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_gas_per_second{$instance_label=\"$instance\", quantile=\"0.95\"}", + "hide": false, + "legendFormat": "p95", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_engine_rpc_new_payload_gas_per_second{$instance_label=\"$instance\", quantile=\"0.99\"}", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "D" + } + ], + "title": "Engine API newPayload Throughput", + "type": "timeseries" + }, { "datasource": { "type": "prometheus", @@ -1847,7 +2116,7 @@ "h": 8, "w": 12, "x": 0, - "y": 29 + "y": 37 }, "id": 56, "options": { @@ -2041,7 +2310,7 @@ "h": 8, "w": 12, "x": 12, - "y": 29 + "y": 37 }, "id": 240, "options": { @@ -2102,7 +2371,7 @@ "h": 1, "w": 24, "x": 0, - "y": 37 + "y": 45 }, "id": 87, "panels": [], @@ -2175,7 +2444,7 @@ "h": 8, "w": 12, "x": 0, - "y": 38 + "y": 46 }, "id": 84, "options": { @@ -2286,7 +2555,7 @@ "h": 8, "w": 12, "x": 12, - "y": 38 + "y": 46 }, "id": 249, "options": { @@ -2396,7 +2665,7 @@ "h": 8, "w": 12, "x": 0, - "y": 46 + "y": 54 }, "id": 213, "options": { @@ -2495,7 +2764,7 @@ "h": 8, "w": 12, "x": 12, - "y": 46 + "y": 54 }, "id": 212, "options": { @@ -2738,7 +3007,7 @@ "h": 8, "w": 12, "x": 0, - "y": 54 + "y": 62 }, "id": 1000, "options": { @@ -2848,7 +3117,7 @@ "h": 8, "w": 12, "x": 12, - "y": 54 + "y": 62 }, "id": 258, "options": { @@ -3015,7 +3284,7 @@ "h": 8, "w": 12, "x": 0, - "y": 62 + "y": 70 }, "id": 85, "options": { @@ -3113,7 +3382,7 @@ "h": 8, "w": 12, "x": 12, - "y": 62 + "y": 70 }, "id": 83, "options": { @@ -3152,7 +3421,7 @@ "h": 1, "w": 24, "x": 0, - "y": 70 + "y": 78 }, "id": 46, "panels": [], @@ -3225,7 +3494,7 @@ "h": 8, "w": 12, "x": 0, - "y": 71 + "y": 79 }, "id": 1001, "options": { @@ -3345,7 +3614,7 @@ "h": 8, "w": 12, "x": 12, - "y": 71 + "y": 79 }, "id": 251, "options": { @@ -3484,7 +3753,7 @@ "h": 8, "w": 12, "x": 0, - "y": 79 + "y": 87 }, "id": 252, "options": { @@ -3529,7 +3798,7 @@ "h": 1, "w": 24, "x": 0, - "y": 87 + "y": 95 }, "id": 214, "panels": [], @@ -3600,7 +3869,7 @@ "h": 8, "w": 12, "x": 0, - "y": 88 + "y": 96 }, "id": 255, "options": { @@ -3699,7 +3968,7 @@ "h": 8, "w": 12, "x": 12, - "y": 88 + "y": 96 }, "id": 254, "options": { @@ -3798,7 +4067,7 @@ "h": 8, "w": 12, "x": 0, - "y": 96 + "y": 104 }, "id": 257, "options": { @@ -3897,7 +4166,7 @@ "h": 8, "w": 12, "x": 12, - "y": 96 + "y": 104 }, "id": 256, "options": { @@ -3996,7 +4265,7 @@ "h": 8, "w": 12, "x": 0, - "y": 104 + "y": 112 }, "id": 260, "options": { @@ -4095,7 +4364,7 @@ "h": 8, "w": 12, "x": 12, - "y": 104 + "y": 112 }, "id": 259, "options": { @@ -4194,7 +4463,7 @@ "h": 8, "w": 12, "x": 0, - "y": 112 + "y": 120 }, "id": 262, "options": { @@ -4294,7 +4563,7 @@ "h": 8, "w": 12, "x": 12, - "y": 112 + "y": 120 }, "id": 261, "options": { @@ -4394,7 +4663,7 @@ "h": 8, "w": 12, "x": 12, - "y": 120 + "y": 128 }, "id": 263, "options": { @@ -4435,7 +4704,7 @@ "h": 1, "w": 24, "x": 0, - "y": 128 + "y": 136 }, "id": 38, "panels": [], @@ -4506,7 +4775,7 @@ "h": 8, "w": 12, "x": 0, - "y": 129 + "y": 137 }, "id": 40, "options": { @@ -4567,7 +4836,7 @@ "h": 8, "w": 12, "x": 12, - "y": 129 + "y": 137 }, "id": 42, "maxDataPoints": 25, @@ -4695,7 +4964,7 @@ "h": 8, "w": 12, "x": 0, - "y": 137 + "y": 145 }, "id": 117, "options": { @@ -4793,7 +5062,7 @@ "h": 8, "w": 12, "x": 12, - "y": 137 + "y": 145 }, "id": 116, "options": { @@ -4922,7 +5191,7 @@ "h": 8, "w": 12, "x": 0, - "y": 145 + "y": 153 }, "id": 119, "options": { @@ -5079,7 +5348,7 @@ "h": 8, "w": 12, "x": 12, - "y": 145 + "y": 153 }, "id": 250, "options": { @@ -5174,7 +5443,7 @@ "h": 8, "w": 12, "x": 0, - "y": 153 + "y": 161 }, "id": 48, "options": { @@ -5281,7 +5550,7 @@ "h": 8, "w": 12, "x": 12, - "y": 153 + "y": 161 }, "id": 118, "options": { @@ -5344,7 +5613,7 @@ "h": 8, "w": 12, "x": 0, - "y": 161 + "y": 169 }, "id": 50, "options": { @@ -5450,7 +5719,7 @@ "h": 8, "w": 12, "x": 12, - "y": 161 + "y": 169 }, "id": 52, "options": { @@ -5549,7 +5818,7 @@ "h": 8, "w": 12, "x": 0, - "y": 169 + "y": 177 }, "id": 113, "options": { @@ -5709,7 +5978,7 @@ "h": 8, "w": 12, "x": 12, - "y": 169 + "y": 177 }, "id": 58, "options": { @@ -5748,7 +6017,7 @@ "h": 1, "w": 24, "x": 0, - "y": 177 + "y": 185 }, "id": 203, "panels": [], @@ -5782,7 +6051,7 @@ "h": 8, "w": 8, "x": 0, - "y": 178 + "y": 186 }, "id": 202, "options": { @@ -5938,7 +6207,7 @@ "h": 8, "w": 8, "x": 8, - "y": 178 + "y": 186 }, "id": 204, "options": { @@ -6086,7 +6355,7 @@ "h": 8, "w": 8, "x": 16, - "y": 178 + "y": 186 }, "id": 205, "options": { @@ -6185,7 +6454,7 @@ "h": 8, "w": 12, "x": 0, - "y": 186 + "y": 194 }, "id": 206, "options": { @@ -6284,7 +6553,7 @@ "h": 8, "w": 12, "x": 12, - "y": 186 + "y": 194 }, "id": 207, "options": { @@ -6323,7 +6592,7 @@ "h": 1, "w": 24, "x": 0, - "y": 194 + "y": 202 }, "id": 79, "panels": [], @@ -6396,7 +6665,7 @@ "h": 8, "w": 12, "x": 0, - "y": 195 + "y": 203 }, "id": 74, "options": { @@ -6495,7 +6764,7 @@ "h": 8, "w": 12, "x": 12, - "y": 195 + "y": 203 }, "id": 80, "options": { @@ -6594,7 +6863,7 @@ "h": 8, "w": 12, "x": 0, - "y": 203 + "y": 211 }, "id": 1002, "options": { @@ -6693,7 +6962,7 @@ "h": 8, "w": 12, "x": 12, - "y": 203 + "y": 211 }, "id": 190, "options": { @@ -6733,7 +7002,7 @@ "h": 1, "w": 24, "x": 0, - "y": 211 + "y": 219 }, "id": 108, "panels": [], @@ -6831,7 +7100,7 @@ "h": 8, "w": 12, "x": 0, - "y": 212 + "y": 220 }, "id": 109, "options": { @@ -6894,7 +7163,7 @@ "h": 8, "w": 12, "x": 12, - "y": 212 + "y": 220 }, "id": 111, "maxDataPoints": 25, @@ -7024,7 +7293,7 @@ "h": 8, "w": 12, "x": 0, - "y": 220 + "y": 228 }, "id": 120, "options": { @@ -7083,7 +7352,7 @@ "h": 8, "w": 12, "x": 12, - "y": 220 + "y": 228 }, "id": 112, "maxDataPoints": 25, @@ -7196,8 +7465,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -7249,7 +7517,7 @@ "h": 8, "w": 12, "x": 0, - "y": 228 + "y": 236 }, "id": 198, "options": { @@ -7454,8 +7722,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -7471,7 +7738,7 @@ "h": 8, "w": 12, "x": 12, - "y": 228 + "y": 236 }, "id": 246, "options": { @@ -7511,7 +7778,7 @@ "h": 1, "w": 24, "x": 0, - "y": 236 + "y": 244 }, "id": 24, "panels": [], @@ -7567,8 +7834,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -7608,7 +7874,7 @@ "h": 8, "w": 12, "x": 0, - "y": 237 + "y": 245 }, "id": 26, "options": { @@ -7727,8 +7993,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -7744,7 +8009,7 @@ "h": 8, "w": 12, "x": 12, - "y": 237 + "y": 245 }, "id": 33, "options": { @@ -7850,8 +8115,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -7866,7 +8130,7 @@ "h": 8, "w": 12, "x": 0, - "y": 245 + "y": 253 }, "id": 36, "options": { @@ -7917,7 +8181,7 @@ "h": 1, "w": 24, "x": 0, - "y": 253 + "y": 261 }, "id": 32, "panels": [], @@ -8022,7 +8286,7 @@ "h": 8, "w": 12, "x": 0, - "y": 254 + "y": 262 }, "id": 30, "options": { @@ -8187,7 +8451,7 @@ "h": 8, "w": 12, "x": 12, - "y": 254 + "y": 262 }, "id": 28, "options": { @@ -8306,7 +8570,7 @@ "h": 8, "w": 12, "x": 0, - "y": 262 + "y": 270 }, "id": 35, "options": { @@ -8431,7 +8695,7 @@ "h": 8, "w": 12, "x": 12, - "y": 262 + "y": 270 }, "id": 73, "options": { @@ -8557,7 +8821,7 @@ "h": 8, "w": 12, "x": 0, - "y": 270 + "y": 278 }, "id": 102, "options": { @@ -8621,7 +8885,7 @@ "h": 1, "w": 24, "x": 0, - "y": 278 + "y": 286 }, "id": 226, "panels": [], @@ -8717,7 +8981,7 @@ "h": 8, "w": 12, "x": 0, - "y": 279 + "y": 287 }, "id": 225, "options": { @@ -8844,7 +9108,7 @@ "h": 8, "w": 12, "x": 12, - "y": 279 + "y": 287 }, "id": 227, "options": { @@ -8971,7 +9235,7 @@ "h": 8, "w": 12, "x": 0, - "y": 287 + "y": 295 }, "id": 235, "options": { @@ -9098,7 +9362,7 @@ "h": 8, "w": 12, "x": 12, - "y": 287 + "y": 295 }, "id": 234, "options": { @@ -9142,7 +9406,7 @@ "h": 1, "w": 24, "x": 0, - "y": 295 + "y": 303 }, "id": 68, "panels": [], @@ -9213,7 +9477,7 @@ "h": 8, "w": 12, "x": 0, - "y": 296 + "y": 304 }, "id": 60, "options": { @@ -9308,7 +9572,7 @@ "h": 8, "w": 12, "x": 12, - "y": 296 + "y": 304 }, "id": 62, "options": { @@ -9403,7 +9667,7 @@ "h": 8, "w": 12, "x": 0, - "y": 304 + "y": 312 }, "id": 64, "options": { @@ -9441,7 +9705,7 @@ "h": 1, "w": 24, "x": 0, - "y": 312 + "y": 320 }, "id": 105, "panels": [], @@ -9512,7 +9776,7 @@ "h": 8, "w": 12, "x": 0, - "y": 313 + "y": 321 }, "id": 106, "options": { @@ -9609,7 +9873,7 @@ "h": 8, "w": 12, "x": 12, - "y": 313 + "y": 321 }, "id": 107, "options": { @@ -9705,7 +9969,7 @@ "h": 8, "w": 12, "x": 0, - "y": 321 + "y": 329 }, "id": 217, "options": { @@ -9744,7 +10008,7 @@ "h": 1, "w": 24, "x": 0, - "y": 329 + "y": 337 }, "id": 97, "panels": [], @@ -9827,7 +10091,7 @@ "h": 8, "w": 12, "x": 0, - "y": 330 + "y": 338 }, "id": 98, "options": { @@ -9989,7 +10253,7 @@ "h": 8, "w": 12, "x": 12, - "y": 330 + "y": 338 }, "id": 101, "options": { @@ -10086,7 +10350,7 @@ "h": 8, "w": 12, "x": 0, - "y": 338 + "y": 346 }, "id": 99, "options": { @@ -10183,7 +10447,7 @@ "h": 8, "w": 12, "x": 12, - "y": 338 + "y": 346 }, "id": 100, "options": { @@ -10280,7 +10544,7 @@ "h": 8, "w": 12, "x": 0, - "y": 346 + "y": 354 }, "id": 248, "options": { @@ -10392,7 +10656,7 @@ "h": 8, "w": 12, "x": 12, - "y": 346 + "y": 354 }, "id": 247, "options": { @@ -10451,7 +10715,7 @@ "h": 1, "w": 24, "x": 0, - "y": 354 + "y": 362 }, "id": 236, "panels": [ @@ -10929,7 +11193,7 @@ "h": 1, "w": 24, "x": 0, - "y": 355 + "y": 363 }, "id": 241, "panels": [ @@ -11271,10 +11535,8 @@ "uid": "${datasource}" }, "definition": "label_values(reth_info,$instance_label)", - "hide": 0, "includeAll": false, "label": "Instance", - "multi": false, "name": "instance", "options": [], "query": { @@ -11284,7 +11546,6 @@ }, "refresh": 1, "regex": "", - "skipUrlSync": false, "sort": 1, "type": "query" }, @@ -11303,9 +11564,21 @@ "hide": 2, "label": "Instance Label", "name": "instance_label", - "query": "job", - "skipUrlSync": false, - "type": "constant" + "query": "${VAR_INSTANCE_LABEL}", + "skipUrlSync": true, + "type": "constant", + "current": { + "value": "${VAR_INSTANCE_LABEL}", + "text": "${VAR_INSTANCE_LABEL}", + "selected": false + }, + "options": [ + { + "value": "${VAR_INSTANCE_LABEL}", + "text": "${VAR_INSTANCE_LABEL}", + "selected": false + } + ] } ] }, @@ -11317,6 +11590,6 @@ "timezone": "", "title": "Reth", "uid": "2k8BXz24x", - "version": 4, + "version": 10, "weekStart": "" } From 9bb5558616b36771e6cb2c8e46174cc904377e84 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:28:44 -0400 Subject: [PATCH 102/274] feat: add from_root for ParallelSparseTrie (#16865) --- crates/trie/sparse/src/parallel_trie.rs | 55 ++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index 591b6aa2620..ee5706abb2b 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -1,7 +1,8 @@ -use crate::{SparseNode, SparseTrieUpdates}; +use crate::{SparseNode, SparseTrieUpdates, TrieMasks}; use alloc::vec::Vec; use alloy_primitives::map::HashMap; -use reth_trie_common::Nibbles; +use reth_execution_errors::SparseTrieResult; +use reth_trie_common::{Nibbles, TrieNode}; /// A revealed sparse trie with subtries that can be updated in parallel. /// @@ -38,6 +39,56 @@ impl Default for ParallelSparseTrie { } } +impl ParallelSparseTrie { + /// Creates a new revealed sparse trie from the given root node. + /// + /// # Returns + /// + /// A [`ParallelSparseTrie`] if successful, or an error if revealing fails. + pub fn from_root( + root_node: TrieNode, + masks: TrieMasks, + retain_updates: bool, + ) -> SparseTrieResult { + let mut trie = Self::default().with_updates(retain_updates); + trie.reveal_node(Nibbles::default(), root_node, masks)?; + Ok(trie) + } + + /// Reveals a trie node if it has not been revealed before. + /// + /// This internal function decodes a trie node and inserts it into the nodes map. + /// It handles different node types (leaf, extension, branch) by appropriately + /// adding them to the trie structure and recursively revealing their children. + /// + /// + /// # Returns + /// + /// `Ok(())` if successful, or an error if node was not revealed. + pub fn reveal_node( + &mut self, + path: Nibbles, + node: TrieNode, + masks: TrieMasks, + ) -> SparseTrieResult<()> { + let _path = path; + let _node = node; + let _masks = masks; + todo!() + } + + /// Configures the trie to retain information about updates. + /// + /// If `retain_updates` is true, the trie will record branch node updates and deletions. + /// This information can then be used to efficiently update an external database. + pub fn with_updates(mut self, retain_updates: bool) -> Self { + if retain_updates { + self.updates = Some(SparseTrieUpdates::default()); + } + self + } +} + /// This is a subtrie of the `ParallelSparseTrie` that contains a map from path to sparse trie /// nodes. #[derive(Clone, PartialEq, Eq, Debug)] From d25b11fd7706852a9363224da26cf5e815edd503 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:09:44 +0100 Subject: [PATCH 103/274] chore: add @mediocregopher to trie codeowners (#16904) --- .github/CODEOWNERS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1596d90b30f..12f3b7a7238 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,19 +1,19 @@ * @gakonst bin/ @onbjerg -crates/blockchain-tree/ @rakita @rkrasiuk @mattsse @Rjected crates/blockchain-tree-api/ @rakita @rkrasiuk @mattsse @Rjected -crates/chainspec/ @Rjected @joshieDo @mattsse +crates/blockchain-tree/ @rakita @rkrasiuk @mattsse @Rjected crates/chain-state/ @fgimenez @mattsse @rkrasiuk +crates/chainspec/ @Rjected @joshieDo @mattsse crates/cli/ @onbjerg @mattsse crates/config/ @onbjerg crates/consensus/ @rkrasiuk @mattsse @Rjected -crates/engine @rkrasiuk @mattsse @Rjected crates/e2e-test-utils/ @mattsse @Rjected +crates/engine @rkrasiuk @mattsse @Rjected crates/engine/ @rkrasiuk @mattsse @Rjected @fgimenez -crates/errors/ @mattsse crates/era/ @mattsse -crates/ethereum/ @mattsse @Rjected +crates/errors/ @mattsse crates/ethereum-forks/ @mattsse @Rjected +crates/ethereum/ @mattsse @Rjected crates/etl/ @joshieDo @shekhirin crates/evm/ @rakita @mattsse @Rjected crates/exex/ @onbjerg @shekhirin @@ -24,17 +24,18 @@ crates/net/downloaders/ @onbjerg @rkrasiuk crates/node/ @mattsse @Rjected @onbjerg @klkvr crates/optimism/ @mattsse @Rjected @fgimenez crates/payload/ @mattsse @Rjected -crates/primitives/ @Rjected @mattsse @klkvr crates/primitives-traits/ @Rjected @joshieDo @mattsse @klkvr +crates/primitives/ @Rjected @mattsse @klkvr crates/prune/ @shekhirin @joshieDo +crates/ress @rkrasiuk crates/revm/ @mattsse @rakita crates/rpc/ @mattsse @Rjected crates/stages/ @onbjerg @rkrasiuk @shekhirin crates/static-file/ @joshieDo @shekhirin crates/storage/codecs/ @joshieDo -crates/storage/db/ @joshieDo @rakita crates/storage/db-api/ @joshieDo @rakita crates/storage/db-common/ @Rjected @onbjerg +crates/storage/db/ @joshieDo @rakita crates/storage/errors/ @rakita @onbjerg crates/storage/libmdbx-rs/ @rakita @shekhirin crates/storage/nippy-jar/ @joshieDo @shekhirin @@ -44,7 +45,6 @@ crates/tasks/ @mattsse crates/tokio-util/ @fgimenez crates/tracing/ @onbjerg crates/transaction-pool/ @mattsse -crates/trie/ @rkrasiuk @Rjected @shekhirin -crates/ress @rkrasiuk +crates/trie/ @rkrasiuk @Rjected @shekhirin @mediocregopher etc/ @Rjected @onbjerg @shekhirin .github/ @onbjerg @gakonst @DaniPopes From 9002d3a203095db5a8e40079e0e0912ff04b1b36 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:12:40 +0100 Subject: [PATCH 104/274] feat(trie): sparse subtrie type (#16903) --- crates/trie/sparse/src/parallel_trie.rs | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index ee5706abb2b..e798456e424 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -98,3 +98,76 @@ pub struct SparseSubtrie { /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, } + +/// Sparse Subtrie Type. +/// +/// Used to determine the type of subtrie a certain path belongs to: +/// - Paths in the range `0x..=0xff` belong to the upper subtrie. +/// - Paths in the range `0x000..` belong to one of the lower subtries. The index of the lower +/// subtrie is determined by the path first nibbles of the path. +/// +/// There can be at most 256 lower subtries. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SparseSubtrieType { + /// Upper subtrie with paths in the range `0x..=0xff` + Upper, + /// Lower subtrie with paths in the range `0x000..`. Includes the index of the subtrie, + /// according to the path prefix. + Lower(usize), +} + +impl SparseSubtrieType { + /// Returns the type of subtrie based on the given path. + pub fn from_path(path: &Nibbles) -> Self { + if path.len() <= 2 { + Self::Upper + } else { + // Convert first two nibbles of the path into a number. + let index = (path[0] << 4 | path[1]) as usize; + Self::Lower(index) + } + } +} + +#[cfg(test)] +mod tests { + use alloy_trie::Nibbles; + + use crate::parallel_trie::SparseSubtrieType; + + #[test] + fn sparse_subtrie_type() { + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 0])), + SparseSubtrieType::Upper + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 15])), + SparseSubtrieType::Upper + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 0, 0])), + SparseSubtrieType::Lower(0) + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 1, 0])), + SparseSubtrieType::Lower(1) + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 15, 0])), + SparseSubtrieType::Lower(15) + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 0, 0])), + SparseSubtrieType::Lower(240) + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 1, 0])), + SparseSubtrieType::Lower(241) + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 15, 0])), + SparseSubtrieType::Lower(255) + ); + } +} From e81747371d68fd95d6cb5fe0b21d25d917f2c976 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 17:23:57 +0200 Subject: [PATCH 105/274] docs: improve reth-primitives-traits documentation (#16870) --- crates/primitives-traits/src/block/mod.rs | 24 +++++- .../primitives-traits/src/block/recovered.rs | 68 ++++++++------- crates/primitives-traits/src/header/error.rs | 8 -- crates/primitives-traits/src/header/mod.rs | 3 - crates/primitives-traits/src/lib.rs | 75 +++++++++++++---- .../src/serde_bincode_compat.rs | 82 ++++++++++++++++++- .../primitives-traits/src/transaction/mod.rs | 11 +++ .../src/transaction/signed.rs | 9 ++ crates/primitives/src/lib.rs | 4 +- 9 files changed, 221 insertions(+), 63 deletions(-) delete mode 100644 crates/primitives-traits/src/header/error.rs diff --git a/crates/primitives-traits/src/block/mod.rs b/crates/primitives-traits/src/block/mod.rs index f3ac7f2bc7c..35ecb171440 100644 --- a/crates/primitives-traits/src/block/mod.rs +++ b/crates/primitives-traits/src/block/mod.rs @@ -1,4 +1,26 @@ //! Block abstraction. +//! +//! This module provides the core block types and transformations: +//! +//! ```rust +//! # use reth_primitives_traits::{Block, SealedBlock, RecoveredBlock}; +//! # fn example(block: B) -> Result<(), Box> +//! # where B::Body: reth_primitives_traits::BlockBody { +//! // Basic block flow +//! let block: B = block; +//! +//! // Seal (compute hash) +//! let sealed: SealedBlock = block.seal(); +//! +//! // Recover senders +//! let recovered: RecoveredBlock = sealed.try_recover()?; +//! +//! // Access components +//! let senders = recovered.senders(); +//! let hash = recovered.hash(); +//! # Ok(()) +//! # } +//! ``` pub(crate) mod sealed; pub use sealed::SealedBlock; @@ -47,7 +69,7 @@ pub type BlockTx = <::Body as BlockBody>::Transaction; /// /// This type defines the structure of a block in the blockchain. /// A [`Block`] is composed of a header and a body. -/// It is expected that a block can always be completely reconstructed from its header and body. +/// It is expected that a block can always be completely reconstructed from its header and body pub trait Block: Send + Sync diff --git a/crates/primitives-traits/src/block/recovered.rs b/crates/primitives-traits/src/block/recovered.rs index 3b45dd46acc..4f06cd8b76f 100644 --- a/crates/primitives-traits/src/block/recovered.rs +++ b/crates/primitives-traits/src/block/recovered.rs @@ -13,7 +13,22 @@ use derive_more::Deref; /// A block with senders recovered from the block's transactions. /// -/// This type is a [`SealedBlock`] with a list of senders that match the transactions in the block. +/// This type represents a [`SealedBlock`] where all transaction senders have been +/// recovered and verified. Recovery is an expensive operation that extracts the +/// sender address from each transaction's signature. +/// +/// # Construction +/// +/// - [`RecoveredBlock::new`] / [`RecoveredBlock::new_unhashed`] - Create with pre-recovered senders +/// (unchecked) +/// - [`RecoveredBlock::try_new`] / [`RecoveredBlock::try_new_unhashed`] - Create with validation +/// - [`RecoveredBlock::try_recover`] - Recover from a block +/// - [`RecoveredBlock::try_recover_sealed`] - Recover from a sealed block +/// +/// # Performance +/// +/// Sender recovery is computationally expensive. Cache recovered blocks when possible +/// to avoid repeated recovery operations. /// /// ## Sealing /// @@ -551,11 +566,10 @@ mod rpc_compat { where B: BlockTrait, { - /// Converts the block block into an RPC [`Block`] instance with the given - /// [`BlockTransactionsKind`]. + /// Converts the block into an RPC [`Block`] with the given [`BlockTransactionsKind`]. /// - /// The `tx_resp_builder` closure is used to build the transaction response for each - /// transaction. + /// The `tx_resp_builder` closure transforms each transaction into the desired response + /// type. pub fn into_rpc_block( self, kind: BlockTransactionsKind, @@ -573,15 +587,13 @@ mod rpc_compat { } } - /// Clones the block and converts it into a [`Block`] response with the given - /// [`BlockTransactionsKind`] + /// Converts the block to an RPC [`Block`] without consuming self. /// - /// This is a convenience method that avoids the need to explicitly clone the block - /// before calling [`Self::into_rpc_block`]. For transaction hashes, it only clones - /// the necessary parts for better efficiency. + /// For transaction hashes, only necessary parts are cloned for efficiency. + /// For full transactions, the entire block is cloned. /// - /// The `tx_resp_builder` closure is used to build the transaction response for each - /// transaction. + /// The `tx_resp_builder` closure transforms each transaction into the desired response + /// type. pub fn clone_into_rpc_block( &self, kind: BlockTransactionsKind, @@ -599,12 +611,10 @@ mod rpc_compat { } } - /// Create a new [`Block`] instance from a [`RecoveredBlock`] reference. + /// Creates an RPC [`Block`] with transaction hashes from a reference. /// - /// This will populate the `transactions` field with only the hashes of the transactions in - /// the block: [`BlockTransactions::Hashes`] - /// - /// This method only clones the necessary parts and avoids cloning the entire block. + /// Returns [`BlockTransactions::Hashes`] containing only transaction hashes. + /// Efficiently clones only necessary parts, not the entire block. pub fn to_rpc_block_with_tx_hashes(&self) -> Block> { let transactions = self.body().transaction_hashes_iter().copied().collect(); let rlp_length = self.rlp_length(); @@ -619,11 +629,10 @@ mod rpc_compat { Block { header, uncles, transactions, withdrawals } } - /// Create a new [`Block`] response from a [`RecoveredBlock`], using the - /// total difficulty to populate its field in the rpc response. + /// Converts the block into an RPC [`Block`] with transaction hashes. /// - /// This will populate the `transactions` field with only the hashes of the transactions in - /// the block: [`BlockTransactions::Hashes`] + /// Consumes self and returns [`BlockTransactions::Hashes`] containing only transaction + /// hashes. pub fn into_rpc_block_with_tx_hashes(self) -> Block> { let transactions = self.body().transaction_hashes_iter().copied().collect(); let rlp_length = self.rlp_length(); @@ -637,11 +646,10 @@ mod rpc_compat { Block { header, uncles, transactions, withdrawals } } - /// Create a new [`Block`] response from a [`RecoveredBlock`], using the given closure to - /// create the rpc transactions. + /// Converts the block into an RPC [`Block`] with full transaction objects. /// - /// This will populate the `transactions` field with the _full_ - /// transaction objects: [`BlockTransactions::Full`] + /// Returns [`BlockTransactions::Full`] with complete transaction data. + /// The `tx_resp_builder` closure transforms each transaction with its metadata. pub fn into_rpc_block_full( self, tx_resp_builder: F, @@ -693,17 +701,15 @@ mod rpc_compat { where T: SignedTransaction, { - /// Create a `RecoveredBlock` from an alloy RPC block. + /// Creates a `RecoveredBlock` from an RPC block. + /// + /// Converts the RPC block to consensus format and recovers transaction senders. + /// Works with any transaction type `U` that can be converted to `T`. /// /// # Examples /// ```ignore - /// // Works with default Transaction type /// let rpc_block: alloy_rpc_types_eth::Block = get_rpc_block(); /// let recovered = RecoveredBlock::from_rpc_block(rpc_block)?; - /// - /// // Also works with custom transaction types that implement From - /// let custom_rpc_block: alloy_rpc_types_eth::Block = get_custom_rpc_block(); - /// let recovered = RecoveredBlock::from_rpc_block(custom_rpc_block)?; /// ``` pub fn from_rpc_block( block: alloy_rpc_types_eth::Block, diff --git a/crates/primitives-traits/src/header/error.rs b/crates/primitives-traits/src/header/error.rs deleted file mode 100644 index 3905d831053..00000000000 --- a/crates/primitives-traits/src/header/error.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// Errors that can occur during header sanity checks. -#[derive(Debug, PartialEq, Eq)] -pub enum HeaderError { - /// Represents an error when the block difficulty is too large. - LargeDifficulty, - /// Represents an error when the block extra data is too large. - LargeExtraData, -} diff --git a/crates/primitives-traits/src/header/mod.rs b/crates/primitives-traits/src/header/mod.rs index 7f3a5ab0660..198b9cb3c8f 100644 --- a/crates/primitives-traits/src/header/mod.rs +++ b/crates/primitives-traits/src/header/mod.rs @@ -1,9 +1,6 @@ mod sealed; pub use sealed::{Header, SealedHeader, SealedHeaderFor}; -mod error; -pub use error::HeaderError; - #[cfg(any(test, feature = "test-utils", feature = "arbitrary"))] pub mod test_utils; diff --git a/crates/primitives-traits/src/lib.rs b/crates/primitives-traits/src/lib.rs index 54f0d42f140..60d265d2be6 100644 --- a/crates/primitives-traits/src/lib.rs +++ b/crates/primitives-traits/src/lib.rs @@ -1,10 +1,18 @@ //! Commonly used types and traits in Reth. //! -//! This crate contains various primitive traits used across reth's components. -//! It provides the [`Block`] trait which is used to represent a block and all its components. -//! A [`Block`] is composed of a [`Header`] and a [`BlockBody`]. In ethereum (and optimism), a block -//! body consists of a list of transactions, a list of uncle headers, and a list of withdrawals. For -//! optimism, uncle headers and withdrawals are always empty lists. +//! ## Overview +//! +//! This crate defines various traits and types that form the foundation of the reth stack. +//! The top-level trait is [`Block`] which represents a block in the blockchain. A [`Block`] is +//! composed of a [`Header`] and a [`BlockBody`]. A [`BlockBody`] contains the transactions in the +//! block and additional data that is part of the block. In ethereum, this includes uncle headers +//! and withdrawals. For optimism, uncle headers and withdrawals are always empty lists. +//! +//! The most common types you'll use are: +//! - [`Block`] - A basic block with header and body +//! - [`SealedBlock`] - A block with its hash cached +//! - [`SealedHeader`] - A header with its hash cached +//! - [`RecoveredBlock`] - A sealed block with sender addresses recovered //! //! ## Feature Flags //! @@ -21,14 +29,6 @@ //! - `rayon`: Uses `rayon` for parallel transaction sender recovery in [`BlockBody`] by default. //! - `serde-bincode-compat` provides helpers for dealing with the `bincode` crate. //! -//! ## Overview -//! -//! This crate defines various traits and types that form the foundation of the reth stack. -//! The top-level trait is [`Block`] which represents a block in the blockchain. A [`Block`] is -//! composed of a [`Header`] and a [`BlockBody`]. A [`BlockBody`] contains the transactions in the -//! block any additional data that is part of the block. A [`Header`] contains the metadata of the -//! block. -//! //! ### Sealing (Hashing) //! //! The block hash is derived from the [`Header`] and is used to uniquely identify the block. This @@ -55,14 +55,55 @@ //! mainnet. Newer transactions must always be recovered with the regular `recover` functions, see //! also [`recover_signer`](crypto::secp256k1::recover_signer). //! +//! ## Error Handling +//! +//! Most operations that can fail return `Result` types: +//! - [`RecoveryError`](transaction::signed::RecoveryError) - Transaction signature recovery failed +//! - [`BlockRecoveryError`](block::error::BlockRecoveryError) - Block-level recovery failed +//! - [`GotExpected`] / [`GotExpectedBoxed`] - Generic error for mismatched values +//! +//! Recovery errors typically indicate invalid signatures or corrupted data. The block recovery +//! error preserves the original block for further inspection. +//! +//! ### Example +//! +//! ```rust +//! # use reth_primitives_traits::{SealedBlock, RecoveredBlock}; +//! # use reth_primitives_traits::block::error::BlockRecoveryError; +//! # fn example(sealed_block: SealedBlock) -> Result<(), BlockRecoveryError>> +//! # where B::Body: reth_primitives_traits::BlockBody { +//! // Attempt to recover senders from a sealed block +//! match sealed_block.try_recover() { +//! Ok(recovered) => { +//! // Successfully recovered all senders +//! println!("Recovered {} senders", recovered.senders().len()); +//! Ok(()) +//! } +//! Err(err) => { +//! // Recovery failed - the block is returned in the error +//! println!("Failed to recover senders for block"); +//! // You can still access the original block +//! let block = err.into_inner(); +//! let hash = block.hash(); +//! Err(BlockRecoveryError::new(block)) +//! } +//! } +//! # } +//! ``` +//! +//! ## Performance Considerations +//! +//! - **Hashing**: Block hashing is expensive. Use [`SealedBlock`] to cache hashes. +//! - **Recovery**: Sender recovery is CPU-intensive. Use [`RecoveredBlock`] to cache results. +//! - **Parallel Recovery**: Enable the `rayon` feature for parallel transaction recovery. +//! //! ## Bincode serde compatibility //! //! The [bincode-crate](https://github.com/bincode-org/bincode) is often used by additional tools when sending data over the network. //! `bincode` crate doesn't work well with optionally serializable serde fields, but some of the consensus types require optional serialization for RPC compatibility. Read more: //! -//! As a workaround this crate introduces the -//! [`SerdeBincodeCompat`](serde_bincode_compat::SerdeBincodeCompat) trait used to a bincode -//! compatible serde representation. +//! As a workaround this crate introduces the `SerdeBincodeCompat` trait (available with the +//! `serde-bincode-compat` feature) used to provide a bincode compatible serde representation. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", @@ -128,7 +169,7 @@ mod extended; pub use extended::Extended; /// Common header types pub mod header; -pub use header::{Header, HeaderError, SealedHeader, SealedHeaderFor}; +pub use header::{Header, SealedHeader, SealedHeaderFor}; /// Bincode-compatible serde implementations for common abstracted types in Reth. /// diff --git a/crates/primitives-traits/src/serde_bincode_compat.rs b/crates/primitives-traits/src/serde_bincode_compat.rs index fa18ffc0ad2..217ad5ff332 100644 --- a/crates/primitives-traits/src/serde_bincode_compat.rs +++ b/crates/primitives-traits/src/serde_bincode_compat.rs @@ -1,3 +1,36 @@ +//! Bincode compatibility support for reth primitive types. +//! +//! This module provides traits and implementations to work around bincode's limitations +//! with optional serde fields. The bincode crate requires all fields to be present during +//! serialization, which conflicts with types that have `#[serde(skip_serializing_if)]` +//! attributes for RPC compatibility. +//! +//! # Overview +//! +//! The main trait is `SerdeBincodeCompat`, which provides a conversion mechanism between +//! types and their bincode-compatible representations. There are two main ways to implement +//! this trait: +//! +//! 1. **Using RLP encoding** - Implement `RlpBincode` for types that already support RLP +//! 2. **Custom implementation** - Define a custom representation type +//! +//! # Examples +//! +//! ## Using with `serde_with` +//! +//! ```rust +//! # use reth_primitives_traits::serde_bincode_compat::{self, SerdeBincodeCompat}; +//! # use serde::{Deserialize, Serialize}; +//! # use serde_with::serde_as; +//! # use alloy_consensus::Header; +//! #[serde_as] +//! #[derive(Serialize, Deserialize)] +//! struct MyStruct { +//! #[serde_as(as = "serde_bincode_compat::BincodeReprFor<'_, Header>")] +//! data: Header, +//! } +//! ``` + use alloc::vec::Vec; use alloy_primitives::Bytes; use core::fmt::Debug; @@ -11,8 +44,26 @@ pub use block_bincode::{Block, BlockBody}; /// Trait for types that can be serialized and deserialized using bincode. /// +/// This trait provides a workaround for bincode's incompatibility with optional +/// serde fields. It ensures all fields are serialized, making the type bincode-compatible. +/// +/// # Implementation +/// +/// The easiest way to implement this trait is using [`RlpBincode`] for RLP-encodable types: +/// +/// ```rust +/// # use reth_primitives_traits::serde_bincode_compat::RlpBincode; +/// # use alloy_rlp::{RlpEncodable, RlpDecodable}; +/// # #[derive(RlpEncodable, RlpDecodable)] +/// # struct MyType; +/// impl RlpBincode for MyType {} +/// // SerdeBincodeCompat is automatically implemented +/// ``` +/// +/// For custom implementations, see the examples in the `block` module. +/// /// The recommended way to add bincode compatible serialization is via the -/// [`serde_with`] crate and the `serde_as` macro that. See for reference [`header`]. +/// [`serde_with`] crate and the `serde_as` macro. See for reference [`header`]. pub trait SerdeBincodeCompat: Sized + 'static { /// Serde representation of the type for bincode serialization. /// @@ -39,6 +90,18 @@ impl SerdeBincodeCompat for alloy_consensus::Header { } /// Type alias for the [`SerdeBincodeCompat::BincodeRepr`] associated type. +/// +/// This provides a convenient way to refer to the bincode representation type +/// without having to write out the full associated type projection. +/// +/// # Example +/// +/// ```rust +/// # use reth_primitives_traits::serde_bincode_compat::{SerdeBincodeCompat, BincodeReprFor}; +/// fn serialize_to_bincode(value: &T) -> BincodeReprFor<'_, T> { +/// value.as_repr() +/// } +/// ``` pub type BincodeReprFor<'a, T> = ::BincodeRepr<'a>; /// A helper trait for using RLP-encoding for providing bincode-compatible serialization. @@ -46,6 +109,23 @@ pub type BincodeReprFor<'a, T> = ::BincodeRepr<'a>; /// By implementing this trait, [`SerdeBincodeCompat`] will be automatically implemented for the /// type and RLP encoding will be used for serialization and deserialization for bincode /// compatibility. +/// +/// # Example +/// +/// ```rust +/// # use reth_primitives_traits::serde_bincode_compat::RlpBincode; +/// # use alloy_rlp::{RlpEncodable, RlpDecodable}; +/// #[derive(RlpEncodable, RlpDecodable)] +/// struct MyCustomType { +/// value: u64, +/// data: Vec, +/// } +/// +/// // Simply implement the marker trait +/// impl RlpBincode for MyCustomType {} +/// +/// // Now MyCustomType can be used with bincode through RLP encoding +/// ``` pub trait RlpBincode: alloy_rlp::Encodable + alloy_rlp::Decodable {} impl SerdeBincodeCompat for T { diff --git a/crates/primitives-traits/src/transaction/mod.rs b/crates/primitives-traits/src/transaction/mod.rs index 5137c756445..f11c3346aec 100644 --- a/crates/primitives-traits/src/transaction/mod.rs +++ b/crates/primitives-traits/src/transaction/mod.rs @@ -1,4 +1,15 @@ //! Transaction abstraction +//! +//! This module provides traits for working with blockchain transactions: +//! - [`Transaction`] - Basic transaction interface +//! - [`signed::SignedTransaction`] - Transaction with signature and recovery methods +//! - [`FullTransaction`] - Transaction with database encoding support +//! +//! # Transaction Recovery +//! +//! Transaction senders are not stored directly but recovered from signatures. +//! Use `recover_signer` for post-EIP-2 transactions or `recover_signer_unchecked` +//! for historical transactions. pub mod execute; pub mod signature; diff --git a/crates/primitives-traits/src/transaction/signed.rs b/crates/primitives-traits/src/transaction/signed.rs index 1142664851b..84cf2769a01 100644 --- a/crates/primitives-traits/src/transaction/signed.rs +++ b/crates/primitives-traits/src/transaction/signed.rs @@ -21,6 +21,15 @@ pub trait FullSignedTx: SignedTransaction + MaybeCompact + MaybeSerdeBincodeComp impl FullSignedTx for T where T: SignedTransaction + MaybeCompact + MaybeSerdeBincodeCompat {} /// A signed transaction. +/// +/// # Recovery Methods +/// +/// This trait provides two types of recovery methods: +/// - Standard methods (e.g., `try_recover`) - enforce EIP-2 low-s signature requirement +/// - Unchecked methods (e.g., `try_recover_unchecked`) - skip EIP-2 validation for pre-EIP-2 +/// transactions +/// +/// Use unchecked methods only when dealing with historical pre-EIP-2 transactions. #[auto_impl::auto_impl(&, Arc)] pub trait SignedTransaction: Send diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 2aa550807d7..47ae3683434 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -31,8 +31,8 @@ pub use block::{BlockWithSenders, SealedBlockFor, SealedBlockWithSenders}; pub use receipt::{gas_spent_by_transactions, Receipt}; pub use reth_primitives_traits::{ logs_bloom, Account, BlockTy, BodyTy, Bytecode, GotExpected, GotExpectedBoxed, Header, - HeaderError, HeaderTy, Log, LogData, NodePrimitives, ReceiptTy, RecoveredBlock, SealedHeader, - StorageEntry, TxTy, + HeaderTy, Log, LogData, NodePrimitives, ReceiptTy, RecoveredBlock, SealedHeader, StorageEntry, + TxTy, }; pub use static_file::StaticFileSegment; From 8d8d197466beec8a8d54b5e3b4c3701ba84f546d Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:28:00 +0100 Subject: [PATCH 106/274] feat: sparse trie update benchmarks (#16748) --- crates/trie/sparse/Cargo.toml | 4 ++ crates/trie/sparse/benches/update.rs | 98 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 crates/trie/sparse/benches/update.rs diff --git a/crates/trie/sparse/Cargo.toml b/crates/trie/sparse/Cargo.toml index 8b40a72da2a..f78322f04b7 100644 --- a/crates/trie/sparse/Cargo.toml +++ b/crates/trie/sparse/Cargo.toml @@ -88,3 +88,7 @@ harness = false [[bench]] name = "rlp_node" harness = false + +[[bench]] +name = "update" +harness = false diff --git a/crates/trie/sparse/benches/update.rs b/crates/trie/sparse/benches/update.rs new file mode 100644 index 00000000000..efb4c5a410c --- /dev/null +++ b/crates/trie/sparse/benches/update.rs @@ -0,0 +1,98 @@ +#![allow(missing_docs)] + +use alloy_primitives::{B256, U256}; +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use proptest::{prelude::*, strategy::ValueTree}; +use rand::seq::IteratorRandom; +use reth_trie_common::Nibbles; +use reth_trie_sparse::SparseTrie; + +const LEAF_COUNTS: [usize; 3] = [100, 1_000, 5_000]; + +fn update_leaf(c: &mut Criterion) { + let mut group = c.benchmark_group("update_leaf"); + + for leaf_count in LEAF_COUNTS { + group.bench_function(BenchmarkId::from_parameter(leaf_count), |b| { + let leaves = generate_leaves(leaf_count); + b.iter_with_setup( + || { + // Start with an empty trie + let mut trie = SparseTrie::revealed_empty(); + // Pre-populate with data + for (path, value) in &leaves { + trie.update_leaf(path.clone(), value.clone()).unwrap(); + } + + let new_leaves = leaves + .iter() + // Update 10% of existing leaves with new values + .choose_multiple(&mut rand::rng(), leaf_count / 10) + .into_iter() + .map(|(path, _)| { + ( + path.clone(), + alloy_rlp::encode_fixed_size(&U256::from(path.len() * 2)).to_vec(), + ) + }) + .collect::>(); + + (trie, new_leaves) + }, + |(mut trie, new_leaves)| { + for (path, new_value) in new_leaves { + trie.update_leaf(path, new_value).unwrap(); + } + trie + }, + ); + }); + } +} + +fn remove_leaf(c: &mut Criterion) { + let mut group = c.benchmark_group("remove_leaf"); + + for leaf_count in LEAF_COUNTS { + group.bench_function(BenchmarkId::from_parameter(leaf_count), |b| { + let leaves = generate_leaves(leaf_count); + b.iter_with_setup( + || { + // Start with an empty trie + let mut trie = SparseTrie::revealed_empty(); + // Pre-populate with data + for (path, value) in &leaves { + trie.update_leaf(path.clone(), value.clone()).unwrap(); + } + + let delete_leaves = leaves + .iter() + .map(|(path, _)| path) + // Remove 10% leaves + .choose_multiple(&mut rand::rng(), leaf_count / 10); + + (trie, delete_leaves) + }, + |(mut trie, delete_leaves)| { + for path in delete_leaves { + trie.remove_leaf(path).unwrap(); + } + trie + }, + ); + }); + } +} + +fn generate_leaves(size: usize) -> Vec<(Nibbles, Vec)> { + proptest::collection::hash_map(any::(), any::(), size) + .new_tree(&mut Default::default()) + .unwrap() + .current() + .iter() + .map(|(key, value)| (Nibbles::unpack(key), alloy_rlp::encode_fixed_size(value).to_vec())) + .collect() +} + +criterion_group!(benches, update_leaf, remove_leaf); +criterion_main!(benches); From 96c7381932a73c87552f29d15df519e25a96cae5 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 18 Jun 2025 17:48:27 +0200 Subject: [PATCH 107/274] feat(trie): Embed a SparseSubtrie into the ParallelSparseTrie as its upper trie (#16905) --- crates/trie/sparse/src/parallel_trie.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index e798456e424..742833bf176 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -13,16 +13,10 @@ use reth_trie_common::{Nibbles, TrieNode}; /// - All keys in `values` collection are full leaf paths. #[derive(Clone, PartialEq, Eq, Debug)] pub struct ParallelSparseTrie { - /// The root of the sparse trie. - root_node: SparseNode, - /// Map from a path (nibbles) to its corresponding sparse trie node. /// This contains the trie nodes for the upper part of the trie. - upper_trie: HashMap, + upper_subtrie: SparseSubtrie, /// An array containing the subtries at the second level of the trie. subtries: [Option; 256], - /// Map from leaf key paths to their values. - /// All values are stored here instead of directly in leaf nodes. - values: HashMap>, /// Optional tracking of trie updates for later use. updates: Option, } @@ -30,10 +24,8 @@ pub struct ParallelSparseTrie { impl Default for ParallelSparseTrie { fn default() -> Self { Self { - root_node: SparseNode::Empty, - upper_trie: HashMap::default(), + upper_subtrie: SparseSubtrie::default(), subtries: [const { None }; 256], - values: HashMap::default(), updates: None, } } @@ -91,12 +83,15 @@ impl ParallelSparseTrie { /// This is a subtrie of the `ParallelSparseTrie` that contains a map from path to sparse trie /// nodes. -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct SparseSubtrie { /// The root path of this subtrie. path: Nibbles, /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, + /// Map from leaf key paths to their values. + /// All values are stored here instead of directly in leaf nodes. + values: HashMap>, } /// Sparse Subtrie Type. From a86e18fa1bdfaa94982cd82ec75fcce6d3596834 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra <160333583+Soubhik-10@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:32:14 +0530 Subject: [PATCH 108/274] chore: added all version const (#16880) Co-authored-by: Matthias Seitz --- crates/net/eth-wire-types/src/broadcast.rs | 2 +- crates/net/eth-wire-types/src/status.rs | 2 +- crates/net/eth-wire-types/src/version.rs | 3 +++ crates/net/eth-wire/src/ethstream.rs | 3 +++ crates/net/eth-wire/src/hello.rs | 1 + crates/net/network/src/session/active.rs | 8 +++++++- crates/net/network/src/session/mod.rs | 6 +++--- crates/net/network/tests/it/session.rs | 2 +- 8 files changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/net/eth-wire-types/src/broadcast.rs b/crates/net/eth-wire-types/src/broadcast.rs index fac61392711..c877b673c78 100644 --- a/crates/net/eth-wire-types/src/broadcast.rs +++ b/crates/net/eth-wire-types/src/broadcast.rs @@ -169,7 +169,7 @@ impl NewPooledTransactionHashes { matches!(version, EthVersion::Eth67 | EthVersion::Eth66) } Self::Eth68(_) => { - matches!(version, EthVersion::Eth68) + matches!(version, EthVersion::Eth68 | EthVersion::Eth69) } } } diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 0ef0358f77e..8f90058639c 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -111,7 +111,7 @@ impl UnifiedStatus { /// Convert this `UnifiedStatus` into the appropriate `StatusMessage` variant based on version. pub fn into_message(self) -> StatusMessage { - if self.version == EthVersion::Eth69 { + if self.version >= EthVersion::Eth69 { StatusMessage::Eth69(self.into_eth69()) } else { StatusMessage::Legacy(self.into_legacy()) diff --git a/crates/net/eth-wire-types/src/version.rs b/crates/net/eth-wire-types/src/version.rs index 172d2b1af45..b36a9a59e45 100644 --- a/crates/net/eth-wire-types/src/version.rs +++ b/crates/net/eth-wire-types/src/version.rs @@ -33,6 +33,9 @@ impl EthVersion { /// The latest known eth version pub const LATEST: Self = Self::Eth68; + /// All known eth vesions + pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66]; + /// Returns the total number of messages the protocol version supports. pub const fn total_messages(&self) -> u8 { match self { diff --git a/crates/net/eth-wire/src/ethstream.rs b/crates/net/eth-wire/src/ethstream.rs index 87345d80e96..415603c8c2b 100644 --- a/crates/net/eth-wire/src/ethstream.rs +++ b/crates/net/eth-wire/src/ethstream.rs @@ -64,6 +64,9 @@ where /// Consumes the [`UnauthedEthStream`] and returns an [`EthStream`] after the `Status` /// handshake is completed successfully. This also returns the `Status` message sent by the /// remote peer. + /// + /// Caution: This expects that the [`UnifiedStatus`] has the proper eth version configured, with + /// ETH69 the initial status message changed. pub async fn handshake( self, status: UnifiedStatus, diff --git a/crates/net/eth-wire/src/hello.rs b/crates/net/eth-wire/src/hello.rs index 3490f0b2e7a..49876a47fb7 100644 --- a/crates/net/eth-wire/src/hello.rs +++ b/crates/net/eth-wire/src/hello.rs @@ -206,6 +206,7 @@ impl HelloMessageBuilder { client_version: client_version.unwrap_or_else(|| RETH_CLIENT_VERSION.to_string()), protocols: protocols.unwrap_or_else(|| { vec![EthVersion::Eth68.into(), EthVersion::Eth67.into(), EthVersion::Eth66.into()] + // TODO: enable: EthVersion::ALL_VERSIONS.iter().copied().map(Into::into).collect() }), port: port.unwrap_or(DEFAULT_TCP_PORT), id, diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index b627d0c3ab8..827c4bfb190 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -330,6 +330,8 @@ impl ActiveSession { PeerMessage::PooledTransactions(msg) => { if msg.is_valid_for_version(self.conn.version()) { self.queued_outgoing.push_back(EthMessage::from(msg).into()); + } else { + debug!(target: "net", ?msg, version=?self.conn.version(), "Message is invalid for connection version, skipping"); } } PeerMessage::EthRequest(req) => { @@ -828,6 +830,7 @@ enum RequestState { } /// Outgoing messages that can be sent over the wire. +#[derive(Debug)] pub(crate) enum OutgoingMessage { /// A message that is owned. Eth(EthMessage), @@ -951,7 +954,7 @@ mod tests { F: FnOnce(EthStream>, N>) -> O + Send + 'static, O: Future + Send + Sync, { - let status = self.status; + let mut status = self.status; let fork_filter = self.fork_filter.clone(); let local_peer_id = self.local_peer_id; let mut hello = self.hello.clone(); @@ -963,6 +966,9 @@ mod tests { let (p2p_stream, _) = UnauthedP2PStream::new(sink).handshake(hello).await.unwrap(); + let eth_version = p2p_stream.shared_capabilities().eth_version().unwrap(); + status.set_eth_version(eth_version); + let (client_stream, _) = UnauthedEthStream::new(p2p_stream) .handshake(status, fork_filter) .await diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index ec54dcedd87..5aad90cbb6f 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -1104,12 +1104,12 @@ async fn authenticate_stream( } }; + // Before trying status handshake, set up the version to negotiated shared version + status.set_eth_version(eth_version); + let (conn, their_status) = if p2p_stream.shared_capabilities().len() == 1 { // if the shared caps are 1, we know both support the eth version // if the hello handshake was successful we can try status handshake - // - // Before trying status handshake, set up the version to negotiated shared version - status.set_eth_version(eth_version); // perform the eth protocol handshake match handshake diff --git a/crates/net/network/tests/it/session.rs b/crates/net/network/tests/it/session.rs index a83a1e652e3..24875a0f410 100644 --- a/crates/net/network/tests/it/session.rs +++ b/crates/net/network/tests/it/session.rs @@ -37,7 +37,7 @@ async fn test_session_established_with_highest_version() { NetworkEvent::ActivePeerSession { info, .. } => { let SessionInfo { peer_id, status, .. } = info; assert_eq!(handle1.peer_id(), &peer_id); - assert_eq!(status.version, EthVersion::Eth68); + assert_eq!(status.version, EthVersion::LATEST); } ev => { panic!("unexpected event {ev:?}") From f6ad01de4a19d6925fb4f7808557f200680edd7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Wed, 18 Jun 2025 18:34:22 +0200 Subject: [PATCH 109/274] refactor(rpc): Delegate `FromConsensusTx` conversion for `EthereumTxEnvelope` to `alloy` (#16909) --- crates/rpc/rpc-types-compat/src/transaction.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index bbef07474ee..2ed20a040fd 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -129,22 +129,7 @@ impl Self { - let TransactionInfo { - block_hash, block_number, index: transaction_index, base_fee, .. - } = tx_info; - let effective_gas_price = base_fee - .map(|base_fee| { - tx.effective_tip_per_gas(base_fee).unwrap_or_default() + base_fee as u128 - }) - .unwrap_or_else(|| tx.max_fee_per_gas()); - - Self { - inner: Recovered::new_unchecked(tx, signer).convert(), - block_hash, - block_number, - transaction_index, - effective_gas_price: Some(effective_gas_price), - } + Self::from_transaction(Recovered::new_unchecked(tx.into(), signer), tx_info) } } From da42c0c5820f86ae385b0d898f4e58428fc902e3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 19:11:28 +0200 Subject: [PATCH 110/274] fix: prevent invalid range in fee_history when newest_block is pending (#16910) Co-authored-by: Claude --- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 2 -- crates/rpc/rpc-eth-types/src/fee_history.rs | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index da354181aff..75dcb8673e8 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -86,8 +86,6 @@ pub trait EthFees: LoadFee { if newest_block.is_pending() { // cap the target block since we don't have fee history for the pending block newest_block = BlockNumberOrTag::Latest; - // account for missing pending block - block_count = block_count.saturating_sub(1); } let end_block = self diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 0425a38629b..6d64c668a4f 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -132,7 +132,7 @@ impl FeeHistoryCache { self.inner.lower_bound.load(SeqCst) } - /// Collect fee history for given range. + /// Collect fee history for the given range (inclusive `start_block..=end_block`). /// /// This function retrieves fee history entries from the cache for the specified range. /// If the requested range (`start_block` to `end_block`) is within the cache bounds, @@ -143,6 +143,10 @@ impl FeeHistoryCache { start_block: u64, end_block: u64, ) -> Option> { + if end_block < start_block { + // invalid range, return None + return None + } let lower_bound = self.lower_bound(); let upper_bound = self.upper_bound(); if start_block >= lower_bound && end_block <= upper_bound { From e3a78c01e1d0a32d4aa4a4010a80ff3754f83be1 Mon Sep 17 00:00:00 2001 From: Femi Bankole Date: Wed, 18 Jun 2025 18:22:06 +0100 Subject: [PATCH 111/274] feat: load KZG settings on EthTransactionValidator startup (#16889) --- crates/transaction-pool/src/validate/eth.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 2e30691916d..99294e53962 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -969,6 +969,9 @@ impl EthTransactionValidatorBuilder { max_blob_count: AtomicU64::new(max_blob_count), }; + // Ensure the kzg setup is loaded right away. + let _kzg_settings = kzg_settings.get(); + let inner = EthTransactionValidatorInner { client, eip2718, From 2fa02b7931245cd63c4f4c036bd0a28b30ded8a5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 19:50:34 +0200 Subject: [PATCH 112/274] fix: allow eth69 block propagation (#16915) --- crates/net/eth-wire-types/src/message.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index ac2d099cff8..37ff6b73e50 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -72,15 +72,9 @@ impl ProtocolMessage { StatusMessage::Eth69(StatusEth69::decode(buf)?) }), EthMessageID::NewBlockHashes => { - if version.is_eth69() { - return Err(MessageError::Invalid(version, EthMessageID::NewBlockHashes)); - } EthMessage::NewBlockHashes(NewBlockHashes::decode(buf)?) } EthMessageID::NewBlock => { - if version.is_eth69() { - return Err(MessageError::Invalid(version, EthMessageID::NewBlock)); - } EthMessage::NewBlock(Box::new(N::NewBlockPayload::decode(buf)?)) } EthMessageID::Transactions => EthMessage::Transactions(Transactions::decode(buf)?), From 5f45e30025e5e364dad57b6760775c7b171bd615 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 18 Jun 2025 14:30:50 -0400 Subject: [PATCH 113/274] docs(trie): mention that SparseSubtrie path is a full path (#16917) --- crates/trie/sparse/src/parallel_trie.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index 742833bf176..7231910da6f 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -81,11 +81,14 @@ impl ParallelSparseTrie { } } -/// This is a subtrie of the `ParallelSparseTrie` that contains a map from path to sparse trie +/// This is a subtrie of the [`ParallelSparseTrie`] that contains a map from path to sparse trie /// nodes. #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct SparseSubtrie { /// The root path of this subtrie. + /// + /// This is the _full_ path to this subtrie, meaning it includes the first two nibbles that we + /// also use for indexing subtries in the [`ParallelSparseTrie`]. path: Nibbles, /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, From 9d7f0b2e2b8f0de4eb02c7404eae9545bc05102c Mon Sep 17 00:00:00 2001 From: cakevm Date: Wed, 18 Jun 2025 21:30:45 +0200 Subject: [PATCH 114/274] feat(alloy-provider): stub out required trait implementations (#16919) --- crates/alloy-provider/src/lib.rs | 291 ++++++++++++++++++++++++++++++- 1 file changed, 287 insertions(+), 4 deletions(-) diff --git a/crates/alloy-provider/src/lib.rs b/crates/alloy-provider/src/lib.rs index 5b4df72ece0..52334d23317 100644 --- a/crates/alloy-provider/src/lib.rs +++ b/crates/alloy-provider/src/lib.rs @@ -21,14 +21,18 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use alloy_consensus::BlockHeader; +use alloy_eips::BlockHashOrNumber; use alloy_network::{primitives::HeaderResponse, BlockResponse}; -use alloy_primitives::{Address, BlockHash, BlockNumber, StorageKey, TxNumber, B256, U256}; +use alloy_primitives::{Address, BlockHash, BlockNumber, StorageKey, TxHash, TxNumber, B256, U256}; use alloy_provider::{network::Network, Provider}; use alloy_rpc_types::BlockId; use alloy_rpc_types_engine::ForkchoiceState; use reth_chainspec::{ChainInfo, ChainSpecProvider}; -use reth_db_api::mock::{DatabaseMock, TxMock}; -use reth_errors::ProviderError; +use reth_db_api::{ + mock::{DatabaseMock, TxMock}, + models::StoredBlockBodyIndices, +}; +use reth_errors::{ProviderError, ProviderResult}; use reth_node_types::{BlockTy, HeaderTy, NodeTypes, PrimitivesTy, ReceiptTy, TxTy}; use reth_primitives::{ Account, Bytecode, RecoveredBlock, SealedBlock, SealedHeader, TransactionMeta, @@ -43,7 +47,10 @@ use reth_provider::{ }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; -use reth_storage_api::{BlockBodyIndicesProvider, DBProvider, NodePrimitivesProvider, StatsReader}; +use reth_storage_api::{ + BlockBodyIndicesProvider, BlockReaderIdExt, BlockSource, DBProvider, NodePrimitivesProvider, + ReceiptProviderIdExt, StatsReader, +}; use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, MultiProof, TrieInput}; use std::{ collections::BTreeMap, @@ -135,6 +142,13 @@ impl AlloyRethProvider { { tokio::task::block_in_place(move || Handle::current().block_on(fut)) } + + /// Get a reference to the conon state notification sender + pub const fn canon_state_notification( + &self, + ) -> &broadcast::Sender>> { + &self.canon_state_notification + } } impl AlloyRethProvider @@ -261,6 +275,275 @@ where } } +impl HeaderProvider for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Header = HeaderTy; + + fn header(&self, _block_hash: &BlockHash) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_by_number(&self, _num: u64) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_td(&self, _hash: &BlockHash) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_td_by_number(&self, _number: BlockNumber) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn headers_range( + &self, + _range: impl RangeBounds, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_header( + &self, + _number: BlockNumber, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_headers_while( + &self, + _range: impl RangeBounds, + _predicate: impl FnMut(&SealedHeader) -> bool, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockBodyIndicesProvider for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_body_indices(&self, _num: u64) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_body_indices_range( + &self, + _range: RangeInclusive, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Block = BlockTy; + + fn find_block_by_hash( + &self, + _hash: B256, + _source: BlockSource, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn block(&self, _id: BlockHashOrNumber) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block(&self) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block_and_receipts( + &self, + ) -> ProviderResult, Vec)>> { + Err(ProviderError::UnsupportedProvider) + } + + fn recovered_block( + &self, + _id: BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_block_with_senders( + &self, + _id: BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_range(&self, _range: RangeInclusive) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_with_senders_range( + &self, + _range: RangeInclusive, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn recovered_block_range( + &self, + _range: RangeInclusive, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockReaderIdExt for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_by_id(&self, _id: BlockId) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_header_by_id( + &self, + _id: BlockId, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_by_id(&self, _id: BlockId) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ReceiptProvider for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Receipt = ReceiptTy; + + fn receipt(&self, _id: TxNumber) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipt_by_hash(&self, _hash: TxHash) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_block( + &self, + _block: BlockHashOrNumber, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_block_range( + &self, + _block_range: RangeInclusive, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ReceiptProviderIdExt for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ +} + +impl TransactionsProvider for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Transaction = TxTy; + + fn transaction_id(&self, _tx_hash: TxHash) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_id(&self, _id: TxNumber) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_id_unhashed( + &self, + _id: TxNumber, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_hash(&self, _hash: TxHash) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_hash_with_meta( + &self, + _hash: TxHash, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_block(&self, _id: TxNumber) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_block( + &self, + _block: BlockHashOrNumber, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_block_range( + &self, + _range: impl RangeBounds, + ) -> ProviderResult>> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn senders_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_sender(&self, _id: TxNumber) -> ProviderResult> { + Err(ProviderError::UnsupportedProvider) + } +} + impl StateProviderFactory for AlloyRethProvider where P: Provider + Clone + 'static, From d9512e2ca6a7845977f8a98ee88bcd8959c0d4c6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 21:46:42 +0200 Subject: [PATCH 115/274] docs: improve transaction-related trait documentation (#16920) --- crates/net/eth-wire-types/src/primitives.rs | 33 +++- crates/primitives-traits/src/node.rs | 7 + crates/transaction-pool/src/traits.rs | 157 +++++++++++++++++--- 3 files changed, 174 insertions(+), 23 deletions(-) diff --git a/crates/net/eth-wire-types/src/primitives.rs b/crates/net/eth-wire-types/src/primitives.rs index 7fc1000339d..25f08f35efc 100644 --- a/crates/net/eth-wire-types/src/primitives.rs +++ b/crates/net/eth-wire-types/src/primitives.rs @@ -9,8 +9,23 @@ use reth_primitives_traits::{ Block, BlockBody, BlockHeader, BlockTy, NodePrimitives, SignedTransaction, }; -/// Abstraction over primitive types which might appear in network messages. See -/// [`crate::EthMessage`] for more context. +/// Abstraction over primitive types which might appear in network messages. +/// +/// This trait defines the types used in the Ethereum Wire Protocol (devp2p) for +/// peer-to-peer communication. While [`NodePrimitives`] defines the core types +/// used throughout the node (consensus format), `NetworkPrimitives` defines how +/// these types are represented when transmitted over the network. +/// +/// The key distinction is in transaction handling: +/// - [`NodePrimitives`] defines `SignedTx` - the consensus format stored in blocks +/// - `NetworkPrimitives` defines `BroadcastedTransaction` and `PooledTransaction` - the formats +/// used for network propagation with additional data like blob sidecars +/// +/// These traits work together through implementations like [`NetPrimitivesFor`], +/// which ensures type compatibility between a node's internal representation and +/// its network representation. +/// +/// See [`crate::EthMessage`] for more context. pub trait NetworkPrimitives: Send + Sync + Unpin + Clone + Debug + 'static { /// The block header type. type BlockHeader: BlockHeader + 'static; @@ -24,12 +39,20 @@ pub trait NetworkPrimitives: Send + Sync + Unpin + Clone + Debug + 'static { + Decodable + 'static; - /// The transaction type which peers announce in `Transactions` messages. It is different from - /// `PooledTransactions` to account for Ethereum case where EIP-4844 transactions are not being - /// announced and can only be explicitly requested from peers. + /// The transaction type which peers announce in `Transactions` messages. + /// + /// This is different from `PooledTransactions` to account for the Ethereum case where + /// EIP-4844 blob transactions are not announced over the network and can only be + /// explicitly requested from peers. This is because blob transactions can be quite + /// large and broadcasting them to all peers would cause + /// significant bandwidth usage. type BroadcastedTransaction: SignedTransaction + 'static; /// The transaction type which peers return in `PooledTransactions` messages. + /// + /// For EIP-4844 blob transactions, this includes the full blob sidecar with + /// KZG commitments and proofs that are needed for validation but are not + /// included in the consensus block format. type PooledTransaction: SignedTransaction + TryFrom + 'static; /// The transaction type which peers return in `GetReceipts` messages. diff --git a/crates/primitives-traits/src/node.rs b/crates/primitives-traits/src/node.rs index 59181a412cd..42f7c74b1d3 100644 --- a/crates/primitives-traits/src/node.rs +++ b/crates/primitives-traits/src/node.rs @@ -5,6 +5,10 @@ use crate::{ use core::fmt; /// Configures all the primitive types of the node. +/// +/// This trait defines the core types used throughout the node for representing +/// blockchain data. It serves as the foundation for type consistency across +/// different node implementations. pub trait NodePrimitives: Send + Sync + Unpin + Clone + Default + fmt::Debug + PartialEq + Eq + 'static { @@ -15,6 +19,9 @@ pub trait NodePrimitives: /// Block body primitive. type BlockBody: FullBlockBody; /// Signed version of the transaction type. + /// + /// This represents the transaction as it exists in the blockchain - the consensus + /// format that includes the signature and can be included in a block. type SignedTx: FullSignedTx; /// A receipt. type Receipt: Receipt; diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index 0e010845def..e9f58c27a32 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -1,3 +1,55 @@ +//! Transaction Pool Traits and Types +//! +//! This module defines the core abstractions for transaction pool implementations, +//! handling the complexity of different transaction representations across the +//! network, mempool, and the chain itself. +//! +//! ## Key Concepts +//! +//! ### Transaction Representations +//! +//! Transactions exist in different formats throughout their lifecycle: +//! +//! 1. **Consensus Format** ([`PoolTransaction::Consensus`]) +//! - The canonical format stored in blocks +//! - Minimal size for efficient storage +//! - Example: EIP-4844 transactions store only blob hashes: ([`TransactionSigned::Eip4844`]) +//! +//! 2. **Pooled Format** ([`PoolTransaction::Pooled`]) +//! - Extended format for network propagation +//! - Includes additional validation data +//! - Example: EIP-4844 transactions include full blob sidecars: ([`PooledTransactionVariant`]) +//! +//! ### Type Relationships +//! +//! ```text +//! NodePrimitives::SignedTx ←── NetworkPrimitives::BroadcastedTransaction +//! │ │ +//! │ (consensus format) │ (announced to peers) +//! │ │ +//! └──────────┐ ┌────────────────┘ +//! ▼ ▼ +//! PoolTransaction::Consensus +//! │ ▲ +//! │ │ from pooled (always succeeds) +//! │ │ +//! ▼ │ try_from consensus (may fail) +//! PoolTransaction::Pooled ←──→ NetworkPrimitives::PooledTransaction +//! (sent on request) +//! ``` +//! +//! ### Special Cases +//! +//! #### EIP-4844 Blob Transactions +//! - Consensus format: Only blob hashes (32 bytes each) +//! - Pooled format: Full blobs + commitments + proofs (large data per blob) +//! - Network behavior: Not broadcast automatically, only sent on explicit request +//! +//! #### Optimism Deposit Transactions +//! - Only exist in consensus format +//! - Never enter the mempool (system transactions) +//! - Conversion from consensus to pooled always fails + use crate::{ blobstore::BlobStoreError, error::{InvalidPoolTransactionError, PoolResult}, @@ -932,19 +984,62 @@ impl BestTransactionsAttributes { } } -/// Trait for transaction types used inside the pool. +/// Trait for transaction types stored in the transaction pool. +/// +/// This trait represents the actual transaction object stored in the mempool, which includes not +/// only the transaction data itself but also additional metadata needed for efficient pool +/// operations. Implementations typically cache values that are frequently accessed during +/// transaction ordering, validation, and eviction. +/// +/// ## Key Responsibilities +/// +/// 1. **Metadata Caching**: Store computed values like address, cost and encoded size +/// 2. **Representation Conversion**: Handle conversions between consensus and pooled +/// representations +/// 3. **Validation Support**: Provide methods for pool-specific validation rules +/// +/// ## Cached Metadata +/// +/// Implementations should cache frequently accessed values to avoid recomputation: +/// - **Address**: Recovered sender address of the transaction +/// - **Cost**: Max amount spendable (gas × price + value + blob costs) +/// - **Size**: RLP encoded length for mempool size limits +/// +/// See [`EthPooledTransaction`] for a reference implementation. +/// +/// ## Transaction Representations +/// +/// This trait abstracts over the different representations a transaction can have: +/// +/// 1. **Consensus representation** (`Consensus` associated type): The canonical form included in +/// blocks +/// - Compact representation without networking metadata +/// - For EIP-4844: includes only blob hashes, not the actual blobs +/// - Used for block execution and state transitions +/// +/// 2. **Pooled representation** (`Pooled` associated type): The form used for network propagation +/// - May include additional data for validation +/// - For EIP-4844: includes full blob sidecars (blobs, commitments, proofs) +/// - Used for mempool validation and p2p gossiping /// -/// This supports two transaction formats -/// - Consensus format: the form the transaction takes when it is included in a block. -/// - Pooled format: the form the transaction takes when it is gossiping around the network. +/// ## Why Two Representations? /// -/// This distinction is necessary for the EIP-4844 blob transactions, which require an additional -/// sidecar when they are gossiped around the network. It is expected that the `Consensus` format is -/// a subset of the `Pooled` format. +/// This distinction is necessary because: /// -/// The assumption is that fallible conversion from `Consensus` to `Pooled` will encapsulate -/// handling of all valid `Consensus` transactions that can't be pooled (e.g Deposit transactions or -/// blob-less EIP-4844 transactions). +/// - **EIP-4844 blob transactions**: Require large blob sidecars for validation that would bloat +/// blocks if included. Only blob hashes are stored on-chain. +/// +/// - **Network efficiency**: Blob transactions are not broadcast to all peers automatically but +/// must be explicitly requested to reduce bandwidth usage. +/// +/// - **Special transactions**: Some transactions (like OP deposit transactions) exist only in +/// consensus format and are never in the mempool. +/// +/// ## Conversion Rules +/// +/// - `Consensus` → `Pooled`: May fail for transactions that cannot be pooled (e.g., OP deposit +/// transactions, blob transactions without sidecars) +/// - `Pooled` → `Consensus`: Always succeeds (pooled is a superset) pub trait PoolTransaction: alloy_consensus::Transaction + InMemorySize + Debug + Send + Sync + Clone { @@ -959,8 +1054,13 @@ pub trait PoolTransaction: /// Define a method to convert from the `Consensus` type to `Self` /// - /// Note: this _must_ fail on any transactions that cannot be pooled (e.g OP Deposit - /// transactions). + /// This conversion may fail for transactions that are valid for inclusion in blocks + /// but cannot exist in the transaction pool. Examples include: + /// + /// - **OP Deposit transactions**: These are special system transactions that are directly + /// included in blocks by the sequencer/validator and never enter the mempool + /// - **Blob transactions without sidecars**: After being included in a block, the sidecar data + /// is pruned, making the consensus transaction unpoolable fn try_from_consensus( tx: Recovered, ) -> Result { @@ -1079,8 +1179,14 @@ pub trait EthPoolTransaction: PoolTransaction { /// The default [`PoolTransaction`] for the [Pool](crate::Pool) for Ethereum. /// -/// This type is essentially a wrapper around [`Recovered`] with additional -/// fields derived from the transaction that are frequently used by the pools for ordering. +/// This type wraps a consensus transaction with additional cached data that's +/// frequently accessed by the pool for transaction ordering and validation: +/// +/// - `cost`: Pre-calculated max cost (gas * price + value + blob costs) +/// - `encoded_length`: Cached RLP encoding length for size limits +/// - `blob_sidecar`: Blob data state (None/Missing/Present) +/// +/// This avoids recalculating these values repeatedly during pool operations. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EthPooledTransaction { /// `EcRecovered` transaction, the consensus format. @@ -1330,16 +1436,31 @@ impl EthPoolTransaction for EthPooledTransaction { } /// Represents the blob sidecar of the [`EthPooledTransaction`]. +/// +/// EIP-4844 blob transactions require additional data (blobs, commitments, proofs) +/// for validation that is not included in the consensus format. This enum tracks +/// the sidecar state throughout the transaction's lifecycle in the pool. #[derive(Debug, Clone, PartialEq, Eq)] pub enum EthBlobTransactionSidecar { /// This transaction does not have a blob sidecar + /// (applies to all non-EIP-4844 transaction types) None, - /// This transaction has a blob sidecar (EIP-4844) but it is missing + /// This transaction has a blob sidecar (EIP-4844) but it is missing. /// - /// It was either extracted after being inserted into the pool or re-injected after reorg - /// without the blob sidecar + /// This can happen when: + /// - The sidecar was extracted after the transaction was added to the pool + /// - The transaction was re-injected after a reorg without its sidecar + /// - The transaction was recovered from the consensus format (e.g., from a block) Missing, - /// The eip-4844 transaction was pulled from the network and still has its blob sidecar + /// The EIP-4844 transaction was received from the network with its complete sidecar. + /// + /// This sidecar contains: + /// - The actual blob data (large data per blob) + /// - KZG commitments for each blob + /// - KZG proofs for validation + /// + /// The sidecar is required for validating the transaction but is not included + /// in blocks (only the blob hashes are included in the consensus format). Present(BlobTransactionSidecarVariant), } From de56409a5173d58e936a8ce82c50181e47667e1b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 21:50:39 +0200 Subject: [PATCH 116/274] chore: add missing receipts69 handling (#16913) --- crates/net/eth-wire-types/src/message.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index 37ff6b73e50..0e54b86222f 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -325,6 +325,7 @@ impl EthMessage { self, Self::PooledTransactions(_) | Self::Receipts(_) | + Self::Receipts69(_) | Self::BlockHeaders(_) | Self::BlockBodies(_) | Self::NodeData(_) From 5cbb1f650b0a8c960901376f50f94e60ba696604 Mon Sep 17 00:00:00 2001 From: FT <140458077+zeevick10@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:51:13 +0200 Subject: [PATCH 117/274] fix: typos in documentation and source code (#16916) --- CLAUDE.md | 4 ++-- crates/net/eth-wire-types/src/version.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 99b06b1f783..80c3e7af032 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -178,7 +178,7 @@ Before submitting changes, ensure: Label PRs appropriately, first check the available labels and then apply the relevant ones: * when changes are RPC related, add A-rpc label * when changes are docs related, add C-docs label -* when changes are optimism related (e.g. new feature or exlusive changes to crates/optimism), add A-op-reth label +* when changes are optimism related (e.g. new feature or exclusive changes to crates/optimism), add A-op-reth label * ... and so on, check the available labels for more options. If changes in reth include changes to dependencies, run commands `zepter` and `make lint-toml` before finalizing the pr. Assume `zepter` binary is installed. @@ -308,4 +308,4 @@ cargo check --workspace --all-features # Check documentation cargo docs --document-private-items -``` \ No newline at end of file +``` diff --git a/crates/net/eth-wire-types/src/version.rs b/crates/net/eth-wire-types/src/version.rs index b36a9a59e45..7b461aec89d 100644 --- a/crates/net/eth-wire-types/src/version.rs +++ b/crates/net/eth-wire-types/src/version.rs @@ -33,7 +33,7 @@ impl EthVersion { /// The latest known eth version pub const LATEST: Self = Self::Eth68; - /// All known eth vesions + /// All known eth versions pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66]; /// Returns the total number of messages the protocol version supports. From dbe828546d0486761edb0d0753ec938c1d05904e Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:53:39 -0400 Subject: [PATCH 118/274] chore(trie): add more stubs for ParallelSparseTrie (#16918) --- crates/trie/sparse/src/parallel_trie.rs | 85 ++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index 7231910da6f..b0dabab774f 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -1,8 +1,9 @@ -use crate::{SparseNode, SparseTrieUpdates, TrieMasks}; +use crate::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; use alloc::vec::Vec; -use alloy_primitives::map::HashMap; +use alloy_primitives::{map::HashMap, B256}; use reth_execution_errors::SparseTrieResult; -use reth_trie_common::{Nibbles, TrieNode}; +use reth_trie_common::{prefix_set::PrefixSet, Nibbles, TrieNode}; +use tracing::trace; /// A revealed sparse trie with subtries that can be updated in parallel. /// @@ -69,6 +70,75 @@ impl ParallelSparseTrie { todo!() } + /// Updates or inserts a leaf node at the specified key path with the provided RLP-encoded + /// value. + /// + /// This method updates the internal prefix set and, if the leaf did not previously exist, + /// adjusts the trie structure by inserting new leaf nodes, splitting branch nodes, or + /// collapsing extension nodes as needed. + /// + /// # Returns + /// + /// Returns `Ok(())` if the update is successful. + /// + /// Note: If an update requires revealing a blinded node, an error is returned if the blinded + /// provider returns an error. + pub fn update_leaf( + &mut self, + key_path: Nibbles, + value: Vec, + masks: TrieMasks, + provider: impl BlindedProvider, + ) -> SparseTrieResult<()> { + let _key_path = key_path; + let _value = value; + let _masks = masks; + let _provider = provider; + todo!() + } + + /// Removes a leaf node from the trie at the specified key path. + /// + /// This function removes the leaf value from the internal values map and then traverses + /// the trie to remove or adjust intermediate nodes, merging or collapsing them as necessary. + /// + /// # Returns + /// + /// Returns `Ok(())` if the leaf is successfully removed, otherwise returns an error + /// if the leaf is not present or if a blinded node prevents removal. + pub fn remove_leaf( + &mut self, + path: &Nibbles, + provider: impl BlindedProvider, + ) -> SparseTrieResult<()> { + let _path = path; + let _provider = provider; + todo!() + } + + /// Recalculates and updates the RLP hashes of nodes up to level 2 of the trie. + /// + /// The root node is considered to be at level 0. This method is useful for optimizing + /// hash recalculations after localized changes to the trie structure. + /// + /// This function first identifies all nodes that have changed (based on the prefix set) below + /// level 2 of the trie, then recalculates their RLP representation. + pub fn update_subtrie_hashes(&mut self) -> SparseTrieResult<()> { + trace!(target: "trie::parallel_sparse", "Updating subtrie hashes"); + todo!() + } + + /// Calculates and returns the root hash of the trie. + /// + /// Before computing the hash, this function processes any remaining (dirty) nodes by + /// updating their RLP encodings. The root hash is either: + /// 1. The cached hash (if no dirty nodes were found) + /// 2. The keccak256 hash of the root node's RLP representation + pub fn root(&mut self) -> B256 { + trace!(target: "trie::parallel_sparse", "Calculating trie root hash"); + todo!() + } + /// Configures the trie to retain information about updates. /// /// If `retain_updates` is true, the trie will record branch node updates and deletions. @@ -97,6 +167,15 @@ pub struct SparseSubtrie { values: HashMap>, } +impl SparseSubtrie { + /// Recalculates and updates the RLP hashes for the changed nodes in this subtrie. + pub fn update_hashes(&mut self, prefix_set: &mut PrefixSet) -> SparseTrieResult<()> { + trace!(target: "trie::parallel_sparse", path=?self.path, "Updating subtrie hashes"); + let _prefix_set = prefix_set; + todo!() + } +} + /// Sparse Subtrie Type. /// /// Used to determine the type of subtrie a certain path belongs to: From 8bcf1906f6929b1b4cc5b622d765594a689e6d4f Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 18 Jun 2025 16:55:44 -0400 Subject: [PATCH 119/274] chore(engine): add log showing which root algorithm is being used (#16924) --- crates/engine/tree/src/tree/mod.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index cf2ba9ab0c3..982a8f5418e 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -2184,9 +2184,21 @@ where // root task proof calculation will include a lot of unrelated paths in the prefix sets. // It's cheaper to run a parallel state root that does one walk over trie tables while // accounting for the prefix sets. + let has_ancestors_with_missing_trie_updates = + self.has_ancestors_with_missing_trie_updates(block.sealed_header()); let mut use_state_root_task = run_parallel_state_root && self.config.use_state_root_task() && - !self.has_ancestors_with_missing_trie_updates(block.sealed_header()); + !has_ancestors_with_missing_trie_updates; + + debug!( + target: "engine::tree", + block=?block_num_hash, + run_parallel_state_root, + has_ancestors_with_missing_trie_updates, + use_state_root_task, + config_allows_state_root_task=self.config.use_state_root_task(), + "Deciding which state root algorithm to run" + ); // use prewarming background task let header = block.clone_sealed_header(); @@ -2226,6 +2238,7 @@ where &self.config, ) } else { + debug!(target: "engine::tree", block=?block_num_hash, "Disabling state root task due to non-empty prefix sets"); use_state_root_task = false; self.payload_processor.spawn_cache_exclusive(header, txs, provider_builder) } @@ -2283,6 +2296,7 @@ where // if we new payload extends the current canonical change we attempt to use the // background task or try to compute it in parallel if use_state_root_task { + debug!(target: "engine::tree", block=?block_num_hash, "Using sparse trie state root algorithm"); match handle.state_root() { Ok(StateRootComputeOutcome { state_root, trie_updates, trie }) => { let elapsed = execution_finish.elapsed(); @@ -2307,6 +2321,7 @@ where } } } else { + debug!(target: "engine::tree", block=?block_num_hash, "Using parallel state root algorithm"); match self.compute_state_root_parallel( persisting_kind, block.header().parent_hash(), From fea711e7ded158d26b23b291548b3fec1696aa13 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 23:23:27 +0200 Subject: [PATCH 120/274] deps: update alloy dependencies to latest patch versions (#16922) Co-authored-by: Arsenii Kulikov --- Cargo.lock | 148 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 72 +++++++++++++------------- 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7fe7807307..27925ad48b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659c33e85c4a9f8bb1b9a2400f4f3d0dd52fbc4bd3650e08d22df1e17d5d92ee" +checksum = "2bcb57295c4b632b6b3941a089ee82d00ff31ff9eb3eac801bf605ffddc81041" dependencies = [ "alloy-eips", "alloy-primitives", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711bfed1579611565ab831166c7bbaf123baea785ea945f02ed3620950f6fe1" +checksum = "8ba5d28e15c14226f243d6e329611840135e1b0fa31feaea57c461e0b03b4c7b" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18ce1538291d8409d4a7d826176d461a6f9eb28632d7185f801bda43a138260" +checksum = "8500bcc1037901953771c25cb77e0d4ec0bffd938d93a04715390230d21a612d" dependencies = [ "alloy-eips", "alloy-primitives", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b91481d12dcd964f4a838271d6abffac2d4082695fc3f73a15429166ea1692d" +checksum = "f4997a9873c8639d079490f218e50e5fa07e70f957e9fc187c0a0535977f482f" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b245fa9d76cc9fc58cf78844f2d4e481333449ba679b2044f09b983fc96f85" +checksum = "a0306e8d148b7b94d988615d367443c1b9d6d2e9fecd2e1f187ac5153dce56f5" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -358,9 +358,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3596f4179ab18a06f2bd6c14db01e0c29263a5a7a31732a54dfd05b07d428002" +checksum = "3eef189583f4c53d231dd1297b28a675ff842b551fb34715f562868a1937431a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -430,9 +430,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecac2cbea1cb3da53b4e68a078e57f9da8d12d86e2017db1240df222e2498397" +checksum = "ea624ddcdad357c33652b86aa7df9bd21afd2080973389d3facf1a221c573948" dependencies = [ "alloy-chains", "alloy-consensus", @@ -474,9 +474,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db1d3c2316590910ba697485aa75cdafef89735010d338d197f8af5baa79df92" +checksum = "1ea3227fa5f627d22b7781e88bc2fe79ba1792d5535b4161bc8fc99cdcd8bedd" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -517,9 +517,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0bed8157038003c702dd1861a6b72d4b1a8f46aeffad35e81580223642170fa" +checksum = "e43d00b4de38432304c4e4b01ae6a3601490fd9824c852329d158763ec18663c" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fed036edc62cd79476fe0340277a1c47b07c173f6ac0244f24193e1183b8e4" +checksum = "3bf22ddb69a436f28bbdda7daf34fe011ee9926fa13bfce89fa023aca9ce2b2f" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ca809955fc14d8bd681470613295eacdc6e62515a13aa3871ab6f7decfd740" +checksum = "5fa84dd9d9465d6d3718e91b017d42498ec51a702d9712ebce64c2b0b7ed9383" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -570,9 +570,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2e3dc925ec6722524f8d7412b9a6845a3350c7037f8a37892ada00c9018125" +checksum = "1ecd1c60085d8cbc3562e16e264a3cd68f42e54dc16b0d40645e5e42841bc042" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -593,9 +593,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497cf019e28c3538d83b3791b780047792a453c693fcca9ded49d9c81550663e" +checksum = "e83868430cddb02bb952d858f125b824ddbc74dde0fb4cdc5c345c732d66936b" dependencies = [ "alloy-eips", "alloy-primitives", @@ -611,9 +611,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e982f72ff47c0f754cb6aa579e456220d768e1ec07675e66cfce970dad70292" +checksum = "c293df0c58d15330e65599f776e30945ea078c93b1594e5c4ba0efaad3f0a739" dependencies = [ "alloy-primitives", "serde", @@ -621,9 +621,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505224e162e239980c6df7632c99f0bc5abbcf630017502810979e9e01f3c86e" +checksum = "2e5b09d86d0c015cb8400c5d1d0483425670bef4fc1260336aea9ef6d4b9540c" dependencies = [ "alloy-consensus", "alloy-eips", @@ -642,9 +642,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ff509ca40537042b7cc9bede6b415ef807c9c5c48024e9fe10b8c8ad0757ef" +checksum = "1826285e4ffc2372a8c061d5cc145858e67a0be3309b768c5b77ddb6b9e6cbc7" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -663,9 +663,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bfb385704970757f21cfc6f79adc22c743523242d032c9ca42d70773a0f7b7c" +checksum = "b94fb27232aac9ee5785bee2ebfc3f9c6384a890a658737263c861c203165355" dependencies = [ "alloy-consensus", "alloy-eips", @@ -678,9 +678,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51dc49d5865f2227c810a416c8d14141db7716a0174bfa6cff1c1a984b678b5e" +checksum = "d1e8f7fa774a1d6f7b3654686f68955631e55f687e03da39c0bd77a9418d57a1" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -692,9 +692,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c962ec5193084873353ad7a65568056b4e704203302e6ba81374e95a22deba4d" +checksum = "d39d9218a0fd802dbccd7e0ce601a6bdefb61190386e97a437d97a31661cd358" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9873512b1e99505f4a65e1d3a3105cb689f112f8e3cab3c632b20a97a46adae" +checksum = "906ce0190afeded19cb2e963cb8507c975a7862216b9e74f39bf91ddee6ae74b" dependencies = [ "alloy-primitives", "arbitrary", @@ -716,9 +716,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d4d95d8431a11e0daee724c3b7635dc8e9d3d60d0b803023a8125c74a77899" +checksum = "c89baab06195c4be9c5d66f15c55e948013d1aff3ec1cfb0ed469e1423313fce" dependencies = [ "alloy-primitives", "async-trait", @@ -731,9 +731,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb03eca937485b258d8e791d143e95b50dbfae0e18f92e1b1271c38959cd00fb" +checksum = "8a249a923e302ac6db932567c43945392f0b6832518aab3c4274858f58756774" dependencies = [ "alloy-consensus", "alloy-network", @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "468a871d7ea52e31ef3abf5ccde612cb3723794f484d26dca6a04a3a776db739" +checksum = "6d1ae10b1bc77fde38161e242749e41e65e34000d05da0a3d3f631e03bfcb19e" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -842,9 +842,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e969c254b189f7da95f07bab53673dd418f8595abfe3397b2cf8d7ba7955487" +checksum = "b234272ee449e32c9f1afbbe4ee08ea7c4b52f14479518f95c844ab66163c545" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -857,9 +857,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb134aaa80c2e1e03eebc101e7c513f08a529726738506d8c306ec9f3c9a7f3b" +checksum = "061672d736144eb5aae13ca67cfec8e5e69a65bef818cb1a2ab2345d55c50ab4" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -877,9 +877,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57f13346af9441cafa99d5b80d95c2480870dd18bd274464f7131df01ad692a" +checksum = "40b01f10382c2aea797d710279b24687a1e9e09a09ecd145f84f636f2a8a3fcc" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde95928532e9c79f8c7488da0170bc9e94c6e7d02a09aa21d8a1bdd8e84e2ab" +checksum = "b75ef8609ea2b31c799b0a56c724dca4c73105c5ccc205d9dfeb1d038df6a1da" dependencies = [ "alloy-primitives", "darling", @@ -2287,7 +2287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -3157,7 +3157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4849,7 +4849,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5942,9 +5942,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af5ca65048a25811d284618d45f8c3c7911a56ea9dad52bd0e827f703a282077" +checksum = "a5448a4ef9ef1ee241a67fe533ae3563716bbcd719acbd0544ad1ac9f03cfde6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5968,9 +5968,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57bb857b905b2002df39fce8e3d8a1604a9b338092cf91af431dc60dd81c5c9" +checksum = "f4220106305f58d92e566be1a644d776ccfb3bafa6279a2203112d799ea20f5d" dependencies = [ "alloy-consensus", "alloy-network", @@ -5984,9 +5984,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd410cb51886914d002ac27b82c387473f8f578ec25cb910e18e7e4b1b6ba7" +checksum = "d8666d4478630ef2a9b2b5f7d73b4d94f2ff43ce4132d30b433825ffc869aa70" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5994,9 +5994,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ac804bff85a0ab18f67cc13f76fb6450c06530791c1412e71dc1ac7c7f6338" +checksum = "bcbf5ee458a2ad66833e7dcc28d78f33d3d4eba53f432c93d7030f5c02331861" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6013,9 +6013,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.4" +version = "0.18.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5260b3018bde2f290c1b3bccb1b007c281373345e9d4889790950e595996af82" +checksum = "712c2b1be5c3414f29800d1b29cd16f0049b847687143adb19814de587ecb3f6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6750,7 +6750,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11003,7 +11003,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11016,7 +11016,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11074,7 +11074,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11832,7 +11832,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -12478,7 +12478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c032d68a49d25d9012a864fef1c64ac17aee43c87e0477bf7301d8ae8bfea7b7" dependencies = [ "cc", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -13024,7 +13024,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ccb384dbded..7e2c4a8b1dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -463,53 +463,53 @@ revm-inspectors = "0.24.0" # eth alloy-chains = { version = "0.2.0", default-features = false } -alloy-dyn-abi = "1.1.0" +alloy-dyn-abi = "1.2.0" alloy-eip2124 = { version = "0.2.0", default-features = false } alloy-evm = { version = "0.11", default-features = false } -alloy-primitives = { version = "1.1.0", default-features = false, features = ["map-foldhash"] } +alloy-primitives = { version = "1.2.0", default-features = false, features = ["map-foldhash"] } alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } -alloy-sol-macro = "1.1.0" -alloy-sol-types = { version = "1.1.0", default-features = false } +alloy-sol-macro = "1.2.0" +alloy-sol-types = { version = "1.2.0", default-features = false } alloy-trie = { version = "0.8.1", default-features = false } alloy-hardforks = "0.2.2" -alloy-consensus = { version = "1.0.11", default-features = false } -alloy-contract = { version = "1.0.11", default-features = false } -alloy-eips = { version = "1.0.11", default-features = false } -alloy-genesis = { version = "1.0.11", default-features = false } -alloy-json-rpc = { version = "1.0.11", default-features = false } -alloy-network = { version = "1.0.11", default-features = false } -alloy-network-primitives = { version = "1.0.11", default-features = false } -alloy-provider = { version = "1.0.11", features = ["reqwest"], default-features = false } -alloy-pubsub = { version = "1.0.11", default-features = false } -alloy-rpc-client = { version = "1.0.11", default-features = false } -alloy-rpc-types = { version = "1.0.11", features = ["eth"], default-features = false } -alloy-rpc-types-admin = { version = "1.0.11", default-features = false } -alloy-rpc-types-anvil = { version = "1.0.11", default-features = false } -alloy-rpc-types-beacon = { version = "1.0.11", default-features = false } -alloy-rpc-types-debug = { version = "1.0.11", default-features = false } -alloy-rpc-types-engine = { version = "1.0.11", default-features = false } -alloy-rpc-types-eth = { version = "1.0.11", default-features = false } -alloy-rpc-types-mev = { version = "1.0.11", default-features = false } -alloy-rpc-types-trace = { version = "1.0.11", default-features = false } -alloy-rpc-types-txpool = { version = "1.0.11", default-features = false } -alloy-serde = { version = "1.0.11", default-features = false } -alloy-signer = { version = "1.0.11", default-features = false } -alloy-signer-local = { version = "1.0.11", default-features = false } -alloy-transport = { version = "1.0.11" } -alloy-transport-http = { version = "1.0.11", features = ["reqwest-rustls-tls"], default-features = false } -alloy-transport-ipc = { version = "1.0.11", default-features = false } -alloy-transport-ws = { version = "1.0.11", default-features = false } +alloy-consensus = { version = "1.0.12", default-features = false } +alloy-contract = { version = "1.0.12", default-features = false } +alloy-eips = { version = "1.0.12", default-features = false } +alloy-genesis = { version = "1.0.12", default-features = false } +alloy-json-rpc = { version = "1.0.12", default-features = false } +alloy-network = { version = "1.0.12", default-features = false } +alloy-network-primitives = { version = "1.0.12", default-features = false } +alloy-provider = { version = "1.0.12", features = ["reqwest"], default-features = false } +alloy-pubsub = { version = "1.0.12", default-features = false } +alloy-rpc-client = { version = "1.0.12", default-features = false } +alloy-rpc-types = { version = "1.0.12", features = ["eth"], default-features = false } +alloy-rpc-types-admin = { version = "1.0.12", default-features = false } +alloy-rpc-types-anvil = { version = "1.0.12", default-features = false } +alloy-rpc-types-beacon = { version = "1.0.12", default-features = false } +alloy-rpc-types-debug = { version = "1.0.12", default-features = false } +alloy-rpc-types-engine = { version = "1.0.12", default-features = false } +alloy-rpc-types-eth = { version = "1.0.12", default-features = false } +alloy-rpc-types-mev = { version = "1.0.12", default-features = false } +alloy-rpc-types-trace = { version = "1.0.12", default-features = false } +alloy-rpc-types-txpool = { version = "1.0.12", default-features = false } +alloy-serde = { version = "1.0.12", default-features = false } +alloy-signer = { version = "1.0.12", default-features = false } +alloy-signer-local = { version = "1.0.12", default-features = false } +alloy-transport = { version = "1.0.12" } +alloy-transport-http = { version = "1.0.12", features = ["reqwest-rustls-tls"], default-features = false } +alloy-transport-ipc = { version = "1.0.12", default-features = false } +alloy-transport-ws = { version = "1.0.12", default-features = false } # op alloy-op-evm = { version = "0.11", default-features = false } alloy-op-hardforks = "0.2.2" -op-alloy-rpc-types = { version = "0.18.4", default-features = false } -op-alloy-rpc-types-engine = { version = "0.18.4", default-features = false } -op-alloy-network = { version = "0.18.4", default-features = false } -op-alloy-consensus = { version = "0.18.4", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.18.4", default-features = false } +op-alloy-rpc-types = { version = "0.18.6", default-features = false } +op-alloy-rpc-types-engine = { version = "0.18.6", default-features = false } +op-alloy-network = { version = "0.18.6", default-features = false } +op-alloy-consensus = { version = "0.18.6", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.18.6", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # misc From b5f5a3a069b8938770c9987ad8db4d2ed3350cab Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 18 Jun 2025 17:22:26 -0400 Subject: [PATCH 121/274] chore(net): document test_trusted_peer_only, fix incoming local_addr (#16925) --- crates/net/network/tests/it/connect.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/net/network/tests/it/connect.rs b/crates/net/network/tests/it/connect.rs index ab6ddac7345..0297dc6b6b3 100644 --- a/crates/net/network/tests/it/connect.rs +++ b/crates/net/network/tests/it/connect.rs @@ -361,7 +361,15 @@ async fn test_shutdown() { async fn test_trusted_peer_only() { let net = Testnet::create(2).await; let mut handles = net.handles(); + + // handle0 is used to test that: + // * outgoing connections to untrusted peers are not allowed + // * outgoing connections to trusted peers are allowed and succeed let handle0 = handles.next().unwrap(); + + // handle1 is used to test that: + // * incoming connections from untrusted peers are not allowed + // * incoming connections from trusted peers are allowed and succeed let handle1 = handles.next().unwrap(); drop(handles); @@ -390,8 +398,8 @@ async fn test_trusted_peer_only() { // connect to an untrusted peer should fail. handle.add_peer(*handle0.peer_id(), handle0.local_addr()); - // wait 2 seconds, the number of connection is still 0. - tokio::time::sleep(Duration::from_secs(2)).await; + // wait 1 second, the number of connection is still 0. + tokio::time::sleep(Duration::from_secs(1)).await; assert_eq!(handle.num_connected_peers(), 0); // add to trusted peer. @@ -402,18 +410,22 @@ async fn test_trusted_peer_only() { assert_eq!(handle.num_connected_peers(), 1); // only receive connections from trusted peers. + handle1.add_peer(*handle.peer_id(), handle.local_addr()); - handle1.add_peer(*handle.peer_id(), handle0.local_addr()); - - // wait 2 seconds, the number of connections is still 1, because peer1 is untrusted. - tokio::time::sleep(Duration::from_secs(2)).await; + // wait 1 second, the number of connections is still 1, because peer1 is untrusted. + tokio::time::sleep(Duration::from_secs(1)).await; assert_eq!(handle.num_connected_peers(), 1); handle1.add_trusted_peer(*handle.peer_id(), handle.local_addr()); + // wait for the next session established event to check the handle1 incoming connection let outgoing_peer_id1 = event_stream.next_session_established().await.unwrap(); assert_eq!(outgoing_peer_id1, *handle1.peer_id()); assert_eq!(handle.num_connected_peers(), 2); + + // check that handle0 and handle1 both have peers. + assert_eq!(handle0.num_connected_peers(), 1); + assert_eq!(handle1.num_connected_peers(), 1); } #[tokio::test(flavor = "multi_thread")] From c0c2eeaa364f077a53197aef8d4efe9b70bbf145 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 18 Jun 2025 23:24:03 +0200 Subject: [PATCH 122/274] chore: remove unused approx_capacity_get_pooled_transactions_req (#16907) Co-authored-by: Claude --- .../net/network/src/transactions/constants.rs | 29 +++---------------- .../net/network/src/transactions/fetcher.rs | 16 ++-------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/crates/net/network/src/transactions/constants.rs b/crates/net/network/src/transactions/constants.rs index 4213c171e05..905c5931e9e 100644 --- a/crates/net/network/src/transactions/constants.rs +++ b/crates/net/network/src/transactions/constants.rs @@ -57,7 +57,6 @@ pub mod tx_manager { /// Constants used by [`TransactionFetcher`](super::TransactionFetcher). pub mod tx_fetcher { - use crate::transactions::fetcher::TransactionFetcherInfo; use reth_network_types::peers::config::{ DEFAULT_MAX_COUNT_PEERS_INBOUND, DEFAULT_MAX_COUNT_PEERS_OUTBOUND, }; @@ -202,14 +201,16 @@ pub mod tx_fetcher { /// Default divisor of the max inflight request when calculating search breadth of the search /// for any idle peer to which to send a request filled with hashes pending fetch. The max - /// inflight requests is configured in [`TransactionFetcherInfo`]. + /// inflight requests is configured in + /// [`TransactionFetcherInfo`](crate::transactions::fetcher::TransactionFetcherInfo). /// /// Default is 3 requests. pub const DEFAULT_DIVISOR_MAX_COUNT_INFLIGHT_REQUESTS_ON_FIND_IDLE_PEER: usize = 3; /// Default divisor of the max inflight request when calculating search breadth of the search /// for the intersection of hashes announced by a peer and hashes pending fetch. The max - /// inflight requests is configured in [`TransactionFetcherInfo`]. + /// inflight requests is configured in + /// [`TransactionFetcherInfo`](crate::transactions::fetcher::TransactionFetcherInfo). /// /// Default is 3 requests. pub const DEFAULT_DIVISOR_MAX_COUNT_INFLIGHT_REQUESTS_ON_FIND_INTERSECTION: usize = 3; @@ -256,26 +257,4 @@ pub mod tx_fetcher { /// /// Default is 8 hashes. pub const DEFAULT_MARGINAL_COUNT_HASHES_GET_POOLED_TRANSACTIONS_REQUEST: usize = 8; - - /// Returns the approx number of transaction hashes that a - /// [`GetPooledTransactions`](reth_eth_wire::GetPooledTransactions) request will have capacity - /// for w.r.t. the [`Eth68`](reth_eth_wire::EthVersion::Eth68) protocol version. This is useful - /// for preallocating memory. - pub const fn approx_capacity_get_pooled_transactions_req_eth68( - info: &TransactionFetcherInfo, - ) -> usize { - let max_size_expected_response = - info.soft_limit_byte_size_pooled_transactions_response_on_pack_request; - - max_size_expected_response / MEDIAN_BYTE_SIZE_SMALL_LEGACY_TX_ENCODED + - DEFAULT_MARGINAL_COUNT_HASHES_GET_POOLED_TRANSACTIONS_REQUEST - } - - /// Returns the approx number of transactions that a - /// [`GetPooledTransactions`](reth_eth_wire::GetPooledTransactions) request will - /// have capacity for w.r.t. the [`Eth66`](reth_eth_wire::EthVersion::Eth66) protocol version. - /// This is useful for preallocating memory. - pub const fn approx_capacity_get_pooled_transactions_req_eth66() -> usize { - SOFT_LIMIT_COUNT_HASHES_IN_GET_POOLED_TRANSACTIONS_REQUEST - } } diff --git a/crates/net/network/src/transactions/fetcher.rs b/crates/net/network/src/transactions/fetcher.rs index c1fdf0e1064..a6a7b902d80 100644 --- a/crates/net/network/src/transactions/fetcher.rs +++ b/crates/net/network/src/transactions/fetcher.rs @@ -41,7 +41,7 @@ use derive_more::{Constructor, Deref}; use futures::{stream::FuturesUnordered, Future, FutureExt, Stream, StreamExt}; use pin_project::pin_project; use reth_eth_wire::{ - DedupPayload, EthVersion, GetPooledTransactions, HandleMempoolData, HandleVersionedMempoolData, + DedupPayload, GetPooledTransactions, HandleMempoolData, HandleVersionedMempoolData, PartiallyValidData, RequestTxHashes, ValidAnnouncementData, }; use reth_eth_wire_types::{EthNetworkPrimitives, NetworkPrimitives}; @@ -840,19 +840,6 @@ impl TransactionFetcher { } } - /// Returns the approx number of transactions that a [`GetPooledTransactions`] request will - /// have capacity for w.r.t. the given version of the protocol. - pub const fn approx_capacity_get_pooled_transactions_req( - &self, - announcement_version: EthVersion, - ) -> usize { - if announcement_version.is_eth68() { - approx_capacity_get_pooled_transactions_req_eth68(&self.info) - } else { - approx_capacity_get_pooled_transactions_req_eth66() - } - } - /// Processes a resolved [`GetPooledTransactions`] request. Queues the outcome as a /// [`FetchEvent`], which will then be streamed by /// [`TransactionsManager`](super::TransactionsManager). @@ -1298,6 +1285,7 @@ mod test { use alloy_primitives::{hex, B256}; use alloy_rlp::Decodable; use derive_more::IntoIterator; + use reth_eth_wire_types::EthVersion; use reth_ethereum_primitives::TransactionSigned; use std::{collections::HashSet, str::FromStr}; From 57281834ec700ea72b8fbc135e63e9d8073e89b5 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Wed, 18 Jun 2025 23:25:48 +0200 Subject: [PATCH 123/274] feat(test): rewrite test_engine_tree_buffered_blocks_are_eventually_connected using e2e framework (#16830) --- crates/e2e-test-utils/src/lib.rs | 47 ++- .../src/testsuite/actions/engine_api.rs | 350 ++++++++++++++++++ .../src/testsuite/actions/mod.rs | 85 ++++- .../src/testsuite/actions/produce_blocks.rs | 158 ++++++-- crates/e2e-test-utils/src/testsuite/setup.rs | 21 +- crates/engine/tree/src/tree/e2e_tests.rs | 60 ++- crates/engine/tree/src/tree/tests.rs | 74 +--- 7 files changed, 657 insertions(+), 138 deletions(-) create mode 100644 crates/e2e-test-utils/src/testsuite/actions/engine_api.rs diff --git a/crates/e2e-test-utils/src/lib.rs b/crates/e2e-test-utils/src/lib.rs index 99d48c93739..0a2aa467e7d 100644 --- a/crates/e2e-test-utils/src/lib.rs +++ b/crates/e2e-test-utils/src/lib.rs @@ -114,6 +114,35 @@ pub async fn setup_engine( TaskManager, Wallet, )> +where + N: NodeBuilderHelper, + LocalPayloadAttributesBuilder: + PayloadAttributesBuilder<::PayloadAttributes>, +{ + setup_engine_with_connection::( + num_nodes, + chain_spec, + is_dev, + tree_config, + attributes_generator, + true, + ) + .await +} + +/// Creates the initial setup with `num_nodes` started and optionally interconnected. +pub async fn setup_engine_with_connection( + num_nodes: usize, + chain_spec: Arc, + is_dev: bool, + tree_config: reth_node_api::TreeConfig, + attributes_generator: impl Fn(u64) -> <::Payload as PayloadTypes>::PayloadBuilderAttributes + Send + Sync + Copy + 'static, + connect_nodes: bool, +) -> eyre::Result<( + Vec>>>, + TaskManager, + Wallet, +)> where N: NodeBuilderHelper, LocalPayloadAttributesBuilder: @@ -165,15 +194,17 @@ where let genesis = node.block_hash(0); node.update_forkchoice(genesis, genesis).await?; - // Connect each node in a chain. - if let Some(previous_node) = nodes.last_mut() { - previous_node.connect(&mut node).await; - } + // Connect each node in a chain if requested. + if connect_nodes { + if let Some(previous_node) = nodes.last_mut() { + previous_node.connect(&mut node).await; + } - // Connect last node with the first if there are more than two - if idx + 1 == num_nodes && num_nodes > 2 { - if let Some(first_node) = nodes.first_mut() { - node.connect(first_node).await; + // Connect last node with the first if there are more than two + if idx + 1 == num_nodes && num_nodes > 2 { + if let Some(first_node) = nodes.first_mut() { + node.connect(first_node).await; + } } } diff --git a/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs b/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs new file mode 100644 index 00000000000..655a6d723a0 --- /dev/null +++ b/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs @@ -0,0 +1,350 @@ +//! Engine API specific actions for testing. + +use crate::testsuite::{Action, Environment}; +use alloy_primitives::B256; +use alloy_rpc_types_engine::{ + ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, +}; +use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction}; +use eyre::Result; +use futures_util::future::BoxFuture; +use reth_node_api::{EngineTypes, PayloadTypes}; +use reth_rpc_api::clients::{EngineApiClient, EthApiClient}; +use std::marker::PhantomData; +use tracing::debug; + +/// Action that sends a newPayload request to a specific node. +#[derive(Debug)] +pub struct SendNewPayload +where + Engine: EngineTypes, +{ + /// The node index to send to + pub node_idx: usize, + /// The block number to send + pub block_number: u64, + /// The source node to get the block from + pub source_node_idx: usize, + /// Expected payload status + pub expected_status: ExpectedPayloadStatus, + _phantom: PhantomData, +} + +/// Expected status for a payload +#[derive(Debug, Clone)] +pub enum ExpectedPayloadStatus { + /// Expect the payload to be valid + Valid, + /// Expect the payload to be invalid + Invalid, + /// Expect the payload to be syncing or accepted (buffered) + SyncingOrAccepted, +} + +impl SendNewPayload +where + Engine: EngineTypes, +{ + /// Create a new `SendNewPayload` action + pub fn new( + node_idx: usize, + block_number: u64, + source_node_idx: usize, + expected_status: ExpectedPayloadStatus, + ) -> Self { + Self { + node_idx, + block_number, + source_node_idx, + expected_status, + _phantom: Default::default(), + } + } +} + +impl Action for SendNewPayload +where + Engine: EngineTypes + PayloadTypes, +{ + fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { + Box::pin(async move { + if self.node_idx >= env.node_clients.len() { + return Err(eyre::eyre!("Target node index out of bounds: {}", self.node_idx)); + } + if self.source_node_idx >= env.node_clients.len() { + return Err(eyre::eyre!( + "Source node index out of bounds: {}", + self.source_node_idx + )); + } + + // Get the block from the source node with retries + let source_rpc = &env.node_clients[self.source_node_idx].rpc; + let mut block = None; + let mut retries = 0; + const MAX_RETRIES: u32 = 5; + + while retries < MAX_RETRIES { + match EthApiClient::::block_by_number( + source_rpc, + alloy_eips::BlockNumberOrTag::Number(self.block_number), + true, // include transactions + ) + .await + { + Ok(Some(b)) => { + block = Some(b); + break; + } + Ok(None) => { + debug!( + "Block {} not found on source node {} (attempt {}/{})", + self.block_number, + self.source_node_idx, + retries + 1, + MAX_RETRIES + ); + retries += 1; + if retries < MAX_RETRIES { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + } + Err(e) => return Err(e.into()), + } + } + + let block = block.ok_or_else(|| { + eyre::eyre!( + "Block {} not found on source node {} after {} retries", + self.block_number, + self.source_node_idx, + MAX_RETRIES + ) + })?; + + // Convert block to ExecutionPayloadV3 + let payload = block_to_payload_v3(block.clone()); + + // Send the payload to the target node + let target_engine = env.node_clients[self.node_idx].engine.http_client(); + let result = EngineApiClient::::new_payload_v3( + &target_engine, + payload, + vec![], + B256::ZERO, // parent_beacon_block_root + ) + .await?; + + debug!( + "Node {}: new_payload for block {} response - status: {:?}, latest_valid_hash: {:?}", + self.node_idx, self.block_number, result.status, result.latest_valid_hash + ); + + // Validate the response based on expectations + match (&result.status, &self.expected_status) { + (PayloadStatusEnum::Valid, ExpectedPayloadStatus::Valid) => { + debug!( + "Node {}: Block {} marked as VALID as expected", + self.node_idx, self.block_number + ); + Ok(()) + } + ( + PayloadStatusEnum::Invalid { validation_error }, + ExpectedPayloadStatus::Invalid, + ) => { + debug!( + "Node {}: Block {} marked as INVALID as expected: {:?}", + self.node_idx, self.block_number, validation_error + ); + Ok(()) + } + ( + PayloadStatusEnum::Syncing | PayloadStatusEnum::Accepted, + ExpectedPayloadStatus::SyncingOrAccepted, + ) => { + debug!( + "Node {}: Block {} marked as SYNCING/ACCEPTED as expected (buffered)", + self.node_idx, self.block_number + ); + Ok(()) + } + (status, expected) => Err(eyre::eyre!( + "Node {}: Unexpected payload status for block {}. Got {:?}, expected {:?}", + self.node_idx, + self.block_number, + status, + expected + )), + } + }) + } +} + +/// Action that sends multiple blocks to a node in a specific order. +#[derive(Debug)] +pub struct SendNewPayloads +where + Engine: EngineTypes, +{ + /// The node index to send to + target_node: Option, + /// The source node to get the blocks from + source_node: Option, + /// The starting block number + start_block: Option, + /// The total number of blocks to send + total_blocks: Option, + /// Whether to send in reverse order + reverse_order: bool, + /// Custom block numbers to send (if not using `start_block` + `total_blocks`) + custom_block_numbers: Option>, + _phantom: PhantomData, +} + +impl SendNewPayloads +where + Engine: EngineTypes, +{ + /// Create a new `SendNewPayloads` action builder + pub fn new() -> Self { + Self { + target_node: None, + source_node: None, + start_block: None, + total_blocks: None, + reverse_order: false, + custom_block_numbers: None, + _phantom: Default::default(), + } + } + + /// Set the target node index + pub const fn with_target_node(mut self, node_idx: usize) -> Self { + self.target_node = Some(node_idx); + self + } + + /// Set the source node index + pub const fn with_source_node(mut self, node_idx: usize) -> Self { + self.source_node = Some(node_idx); + self + } + + /// Set the starting block number + pub const fn with_start_block(mut self, block_num: u64) -> Self { + self.start_block = Some(block_num); + self + } + + /// Set the total number of blocks to send + pub const fn with_total_blocks(mut self, count: u64) -> Self { + self.total_blocks = Some(count); + self + } + + /// Send blocks in reverse order (useful for testing buffering) + pub const fn in_reverse_order(mut self) -> Self { + self.reverse_order = true; + self + } + + /// Set custom block numbers to send + pub fn with_block_numbers(mut self, block_numbers: Vec) -> Self { + self.custom_block_numbers = Some(block_numbers); + self + } +} + +impl Default for SendNewPayloads +where + Engine: EngineTypes, +{ + fn default() -> Self { + Self::new() + } +} + +impl Action for SendNewPayloads +where + Engine: EngineTypes + PayloadTypes, +{ + fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { + Box::pin(async move { + // Validate required fields + let target_node = + self.target_node.ok_or_else(|| eyre::eyre!("Target node not specified"))?; + let source_node = + self.source_node.ok_or_else(|| eyre::eyre!("Source node not specified"))?; + + // Determine block numbers to send + let block_numbers = if let Some(custom_numbers) = &self.custom_block_numbers { + custom_numbers.clone() + } else { + let start = + self.start_block.ok_or_else(|| eyre::eyre!("Start block not specified"))?; + let count = + self.total_blocks.ok_or_else(|| eyre::eyre!("Total blocks not specified"))?; + + if self.reverse_order { + // Send blocks in reverse order (e.g., for count=2, start=1: [2, 1]) + (0..count).map(|i| start + count - 1 - i).collect() + } else { + // Send blocks in normal order + (0..count).map(|i| start + i).collect() + } + }; + + for &block_number in &block_numbers { + // For the first block in reverse order, expect buffering + // For subsequent blocks, they might connect immediately + let expected_status = + if self.reverse_order && block_number == *block_numbers.first().unwrap() { + ExpectedPayloadStatus::SyncingOrAccepted + } else { + ExpectedPayloadStatus::Valid + }; + + let mut action = SendNewPayload::::new( + target_node, + block_number, + source_node, + expected_status, + ); + + action.execute(env).await?; + } + + Ok(()) + }) + } +} + +/// Helper function to convert a block to `ExecutionPayloadV3` +fn block_to_payload_v3(block: Block) -> ExecutionPayloadV3 { + use alloy_primitives::U256; + + ExecutionPayloadV3 { + payload_inner: ExecutionPayloadV2 { + payload_inner: ExecutionPayloadV1 { + parent_hash: block.header.inner.parent_hash, + fee_recipient: block.header.inner.beneficiary, + state_root: block.header.inner.state_root, + receipts_root: block.header.inner.receipts_root, + logs_bloom: block.header.inner.logs_bloom, + prev_randao: block.header.inner.mix_hash, + block_number: block.header.inner.number, + gas_limit: block.header.inner.gas_limit, + gas_used: block.header.inner.gas_used, + timestamp: block.header.inner.timestamp, + extra_data: block.header.inner.extra_data.clone(), + base_fee_per_gas: U256::from(block.header.inner.base_fee_per_gas.unwrap_or(0)), + block_hash: block.header.hash, + transactions: vec![], // No transactions needed for buffering tests + }, + withdrawals: block.withdrawals.unwrap_or_default().to_vec(), + }, + blob_gas_used: block.header.inner.blob_gas_used.unwrap_or(0), + excess_blob_gas: block.header.inner.excess_blob_gas.unwrap_or(0), + } +} diff --git a/crates/e2e-test-utils/src/testsuite/actions/mod.rs b/crates/e2e-test-utils/src/testsuite/actions/mod.rs index 7f09c283568..a5ed75ed441 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/mod.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/mod.rs @@ -1,24 +1,27 @@ //! Actions that can be performed in tests. use crate::testsuite::Environment; -use alloy_rpc_types_engine::{ForkchoiceUpdated, PayloadStatusEnum}; +use alloy_rpc_types_engine::{ForkchoiceState, ForkchoiceUpdated, PayloadStatusEnum}; use eyre::Result; use futures_util::future::BoxFuture; use reth_node_api::EngineTypes; +use reth_rpc_api::clients::EngineApiClient; use std::future::Future; use tracing::debug; +pub mod engine_api; pub mod fork; pub mod node_ops; pub mod produce_blocks; pub mod reorg; +pub use engine_api::{ExpectedPayloadStatus, SendNewPayload, SendNewPayloads}; pub use fork::{CreateFork, ForkBase, SetForkBase, SetForkBaseFromBlockInfo, ValidateFork}; pub use node_ops::{CaptureBlockOnNode, CompareNodeChainTips, SelectActiveNode, ValidateBlockTag}; pub use produce_blocks::{ AssertMineBlock, BroadcastLatestForkchoice, BroadcastNextNewPayload, CheckPayloadAccepted, ExpectFcuStatus, GenerateNextPayload, GeneratePayloadAttributes, PickNextBlockProducer, - ProduceBlocks, ProduceInvalidBlocks, TestFcuToTag, UpdateBlockInfo, + ProduceBlocks, ProduceBlocksLocally, ProduceInvalidBlocks, TestFcuToTag, UpdateBlockInfo, UpdateBlockInfoToLatestPayload, ValidateCanonicalTag, }; pub use reorg::{ReorgTarget, ReorgTo, SetReorgTarget}; @@ -102,12 +105,20 @@ where /// Action that makes the current latest block canonical by broadcasting a forkchoice update #[derive(Debug, Default)] -pub struct MakeCanonical {} +pub struct MakeCanonical { + /// If true, only send to the active node. If false, broadcast to all nodes. + active_node_only: bool, +} impl MakeCanonical { /// Create a new `MakeCanonical` action pub const fn new() -> Self { - Self {} + Self { active_node_only: false } + } + + /// Create a new `MakeCanonical` action that only applies to the active node + pub const fn with_active_node() -> Self { + Self { active_node_only: true } } } @@ -120,23 +131,59 @@ where { fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { Box::pin(async move { - let mut actions: Vec>> = vec![ - Box::new(BroadcastLatestForkchoice::default()), - Box::new(UpdateBlockInfo::default()), - ]; - - // if we're on a fork, validate it now that it's canonical - if let Ok(active_state) = env.active_node_state() { - if let Some(fork_base) = active_state.current_fork_base { - debug!("MakeCanonical: Adding fork validation from base block {}", fork_base); - actions.push(Box::new(ValidateFork::new(fork_base))); - // clear the fork base since we're now canonical - env.active_node_state_mut()?.current_fork_base = None; + if self.active_node_only { + // Only update the active node + let latest_block = env + .current_block_info() + .ok_or_else(|| eyre::eyre!("No latest block information available"))?; + + let fork_choice_state = ForkchoiceState { + head_block_hash: latest_block.hash, + safe_block_hash: latest_block.hash, + finalized_block_hash: latest_block.hash, + }; + + let active_idx = env.active_node_idx; + let engine = env.node_clients[active_idx].engine.http_client(); + + let fcu_response = EngineApiClient::::fork_choice_updated_v3( + &engine, + fork_choice_state, + None, + ) + .await?; + + debug!( + "Active node {}: Forkchoice update status: {:?}", + active_idx, fcu_response.payload_status.status + ); + + validate_fcu_response(&fcu_response, &format!("Active node {active_idx}"))?; + + Ok(()) + } else { + // Original broadcast behavior + let mut actions: Vec>> = vec![ + Box::new(BroadcastLatestForkchoice::default()), + Box::new(UpdateBlockInfo::default()), + ]; + + // if we're on a fork, validate it now that it's canonical + if let Ok(active_state) = env.active_node_state() { + if let Some(fork_base) = active_state.current_fork_base { + debug!( + "MakeCanonical: Adding fork validation from base block {}", + fork_base + ); + actions.push(Box::new(ValidateFork::new(fork_base))); + // clear the fork base since we're now canonical + env.active_node_state_mut()?.current_fork_base = None; + } } - } - let mut sequence = Sequence::new(actions); - sequence.execute(env).await + let mut sequence = Sequence::new(actions); + sequence.execute(env).await + } }) } } diff --git a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs index dfeb5a8fa84..a6ceec2eee7 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs @@ -599,7 +599,17 @@ where /// Action that broadcasts the next new payload #[derive(Debug, Default)] -pub struct BroadcastNextNewPayload {} +pub struct BroadcastNextNewPayload { + /// If true, only send to the active node. If false, broadcast to all nodes. + active_node_only: bool, +} + +impl BroadcastNextNewPayload { + /// Create a new `BroadcastNextNewPayload` action that only sends to the active node + pub const fn with_active_node() -> Self { + Self { active_node_only: true } + } +} impl Action for BroadcastNextNewPayload where @@ -630,14 +640,11 @@ where let execution_payload_envelope: ExecutionPayloadEnvelopeV3 = payload_envelope.into(); let execution_payload = execution_payload_envelope.execution_payload; - // Loop through all clients and broadcast the next new payload - let mut broadcast_results = Vec::new(); - let mut first_valid_seen = false; + if self.active_node_only { + // Send only to the active node + let active_idx = env.active_node_idx; + let engine = env.node_clients[active_idx].engine.http_client(); - for (idx, client) in env.node_clients.iter().enumerate() { - let engine = client.engine.http_client(); - - // Broadcast the execution payload let result = EngineApiClient::::new_payload_v3( &engine, execution_payload.clone(), @@ -646,35 +653,70 @@ where ) .await?; - broadcast_results.push((idx, result.status.clone())); - debug!("Node {}: new_payload broadcast status: {:?}", idx, result.status); + debug!("Active node {}: new_payload status: {:?}", active_idx, result.status); - // Check if this node accepted the payload - if result.status == PayloadStatusEnum::Valid && !first_valid_seen { - first_valid_seen = true; - } else if let PayloadStatusEnum::Invalid { validation_error } = result.status { - debug!( - "Node {}: Invalid payload status returned from broadcast: {:?}", - idx, validation_error - ); + // Validate the response + match result.status { + PayloadStatusEnum::Valid => { + env.active_node_state_mut()?.latest_payload_executed = + Some(next_new_payload); + Ok(()) + } + other => Err(eyre::eyre!( + "Active node {}: Unexpected payload status: {:?}", + active_idx, + other + )), } - } + } else { + // Loop through all clients and broadcast the next new payload + let mut broadcast_results = Vec::new(); + let mut first_valid_seen = false; + + for (idx, client) in env.node_clients.iter().enumerate() { + let engine = client.engine.http_client(); + + // Broadcast the execution payload + let result = EngineApiClient::::new_payload_v3( + &engine, + execution_payload.clone(), + vec![], + parent_beacon_block_root, + ) + .await?; - // Update the executed payload state after broadcasting to all nodes - if first_valid_seen { - env.active_node_state_mut()?.latest_payload_executed = Some(next_new_payload); - } + broadcast_results.push((idx, result.status.clone())); + debug!("Node {}: new_payload broadcast status: {:?}", idx, result.status); - // Check if at least one node accepted the payload - let any_valid = - broadcast_results.iter().any(|(_, status)| *status == PayloadStatusEnum::Valid); - if !any_valid { - return Err(eyre::eyre!("Failed to successfully broadcast payload to any client")); - } + // Check if this node accepted the payload + if result.status == PayloadStatusEnum::Valid && !first_valid_seen { + first_valid_seen = true; + } else if let PayloadStatusEnum::Invalid { validation_error } = result.status { + debug!( + "Node {}: Invalid payload status returned from broadcast: {:?}", + idx, validation_error + ); + } + } - debug!("Broadcast complete. Results: {:?}", broadcast_results); + // Update the executed payload state after broadcasting to all nodes + if first_valid_seen { + env.active_node_state_mut()?.latest_payload_executed = Some(next_new_payload); + } - Ok(()) + // Check if at least one node accepted the payload + let any_valid = + broadcast_results.iter().any(|(_, status)| *status == PayloadStatusEnum::Valid); + if !any_valid { + return Err(eyre::eyre!( + "Failed to successfully broadcast payload to any client" + )); + } + + debug!("Broadcast complete. Results: {:?}", broadcast_results); + + Ok(()) + } }) } } @@ -873,6 +915,60 @@ where } } +/// Action that produces blocks locally without broadcasting to other nodes +/// This sends the payload only to the active node to ensure it's available locally +#[derive(Debug)] +pub struct ProduceBlocksLocally { + /// Number of blocks to produce + pub num_blocks: u64, + /// Tracks engine type + _phantom: PhantomData, +} + +impl ProduceBlocksLocally { + /// Create a new `ProduceBlocksLocally` action + pub fn new(num_blocks: u64) -> Self { + Self { num_blocks, _phantom: Default::default() } + } +} + +impl Default for ProduceBlocksLocally { + fn default() -> Self { + Self::new(0) + } +} + +impl Action for ProduceBlocksLocally +where + Engine: EngineTypes + PayloadTypes, + Engine::PayloadAttributes: From + Clone, + Engine::ExecutionPayloadEnvelopeV3: Into, +{ + fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { + Box::pin(async move { + // Remember the active node to ensure all blocks are produced on the same node + let producer_idx = env.active_node_idx; + + for _ in 0..self.num_blocks { + // Ensure we always use the same producer + env.last_producer_idx = Some(producer_idx); + + // create a sequence that produces blocks and sends only to active node + let mut sequence = Sequence::new(vec![ + // Skip PickNextBlockProducer to maintain the same producer + Box::new(GeneratePayloadAttributes::default()), + Box::new(GenerateNextPayload::default()), + // Send payload only to the active node to make it available + Box::new(BroadcastNextNewPayload::with_active_node()), + Box::new(UpdateBlockInfoToLatestPayload::default()), + ]); + sequence.execute(env).await?; + } + Ok(()) + }) + } +} + /// Action that produces a sequence of blocks where some blocks are intentionally invalid #[derive(Debug)] pub struct ProduceInvalidBlocks { diff --git a/crates/e2e-test-utils/src/testsuite/setup.rs b/crates/e2e-test-utils/src/testsuite/setup.rs index 9198851af52..c51fd3c0bfe 100644 --- a/crates/e2e-test-utils/src/testsuite/setup.rs +++ b/crates/e2e-test-utils/src/testsuite/setup.rs @@ -1,6 +1,9 @@ //! Test setup utilities for configuring the initial state. -use crate::{setup_engine, testsuite::Environment, NodeBuilderHelper, PayloadAttributesBuilder}; +use crate::{ + setup_engine_with_connection, testsuite::Environment, NodeBuilderHelper, + PayloadAttributesBuilder, +}; use alloy_eips::BlockNumberOrTag; use alloy_primitives::B256; use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes}; @@ -157,12 +160,13 @@ where ) }; - let result = setup_engine::( + let result = setup_engine_with_connection::( node_count, Arc::::new((*chain_spec).clone().into()), is_dev, self.tree_config.clone(), attributes_generator, + self.network.connect_nodes, ) .await; @@ -292,16 +296,23 @@ pub struct Genesis {} pub struct NetworkSetup { /// Number of nodes to create pub node_count: usize, + /// Whether nodes should be connected to each other + pub connect_nodes: bool, } impl NetworkSetup { /// Create a new network setup with a single node pub const fn single_node() -> Self { - Self { node_count: 1 } + Self { node_count: 1, connect_nodes: true } } - /// Create a new network setup with multiple nodes + /// Create a new network setup with multiple nodes (connected) pub const fn multi_node(count: usize) -> Self { - Self { node_count: count } + Self { node_count: count, connect_nodes: true } + } + + /// Create a new network setup with multiple nodes (disconnected) + pub const fn multi_node_unconnected(count: usize) -> Self { + Self { node_count: count, connect_nodes: false } } } diff --git a/crates/engine/tree/src/tree/e2e_tests.rs b/crates/engine/tree/src/tree/e2e_tests.rs index ec74eecc200..0bbd92b8dfd 100644 --- a/crates/engine/tree/src/tree/e2e_tests.rs +++ b/crates/engine/tree/src/tree/e2e_tests.rs @@ -5,8 +5,9 @@ use eyre::Result; use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_e2e_test_utils::testsuite::{ actions::{ - CaptureBlock, CreateFork, ExpectFcuStatus, MakeCanonical, ProduceBlocks, - ProduceInvalidBlocks, ReorgTo, ValidateCanonicalTag, + CaptureBlock, CompareNodeChainTips, CreateFork, ExpectFcuStatus, MakeCanonical, + ProduceBlocks, ProduceBlocksLocally, ProduceInvalidBlocks, ReorgTo, SelectActiveNode, + SendNewPayloads, UpdateBlockInfo, ValidateCanonicalTag, }, setup::{NetworkSetup, Setup}, TestBuilder, @@ -185,3 +186,58 @@ async fn test_engine_tree_reorg_with_missing_ancestor_expecting_valid_e2e() -> R Ok(()) } + +/// Test that verifies buffered blocks are eventually connected when sent in reverse order. +#[tokio::test] +async fn test_engine_tree_buffered_blocks_are_eventually_connected_e2e() -> Result<()> { + reth_tracing::init_test_tracing(); + + let test = TestBuilder::new() + .with_setup( + Setup::default() + .with_chain_spec(Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis( + serde_json::from_str(include_str!( + "../../../../e2e-test-utils/src/testsuite/assets/genesis.json" + )) + .unwrap(), + ) + .cancun_activated() + .build(), + )) + .with_network(NetworkSetup::multi_node_unconnected(2)) // Need 2 disconnected nodes + .with_tree_config( + TreeConfig::default() + .with_legacy_state_root(false) + .with_has_enough_parallelism(true), + ), + ) + // node 0 produces blocks 1 and 2 locally without broadcasting + .with_action(SelectActiveNode::new(0)) + .with_action(ProduceBlocksLocally::::new(2)) + // make the blocks canonical on node 0 so they're available via RPC + .with_action(MakeCanonical::with_active_node()) + // send blocks in reverse order (2, then 1) from node 0 to node 1 + .with_action( + SendNewPayloads::::new() + .with_target_node(1) + .with_source_node(0) + .with_start_block(1) + .with_total_blocks(2) + .in_reverse_order(), + ) + // update node 1's view to recognize the new blocks + .with_action(SelectActiveNode::new(1)) + // get the latest block from node 1's RPC and update environment + .with_action(UpdateBlockInfo::default()) + // make block 2 canonical on node 1 with a forkchoice update + .with_action(MakeCanonical::with_active_node()) + // verify both nodes eventually have the same chain tip + .with_action(CompareNodeChainTips::expect_same(0, 1)); + + test.run::().await?; + + Ok(()) +} diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 68db6707547..43891c6fb76 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -6,10 +6,7 @@ use alloy_primitives::{ Bytes, B256, }; use alloy_rlp::Decodable; -use alloy_rpc_types_engine::{ - CancunPayloadFields, ExecutionData, ExecutionPayloadSidecar, ExecutionPayloadV1, - ExecutionPayloadV3, -}; +use alloy_rpc_types_engine::{ExecutionData, ExecutionPayloadSidecar, ExecutionPayloadV1}; use assert_matches::assert_matches; use reth_chain_state::{test_utils::TestBlockBuilder, BlockState}; use reth_chainspec::{ChainSpec, HOLESKY, MAINNET}; @@ -289,22 +286,6 @@ impl TestHarness { } } - async fn send_new_payload(&mut self, block: RecoveredBlock) { - let payload = ExecutionPayloadV3::from_block_unchecked( - block.hash(), - &block.clone_sealed_block().into_block(), - ); - self.tree - .on_new_payload(ExecutionData { - payload: payload.into(), - sidecar: ExecutionPayloadSidecar::v3(CancunPayloadFields { - parent_beacon_block_root: block.parent_beacon_block_root.unwrap(), - versioned_hashes: vec![], - }), - }) - .unwrap(); - } - async fn insert_chain( &mut self, chain: impl IntoIterator> + Clone, @@ -349,18 +330,6 @@ impl TestHarness { } } - async fn check_block_received(&mut self, hash: B256) { - let event = self.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::BeaconConsensus(BeaconConsensusEngineEvent::BlockReceived( - num_hash, - )) => { - assert_eq!(num_hash.hash, hash); - } - _ => panic!("Unexpected event: {event:#?}"), - } - } - fn persist_blocks(&self, blocks: Vec>) { let mut block_data: Vec<(B256, Block)> = Vec::with_capacity(blocks.len()); let mut headers_data: Vec<(B256, Header)> = Vec::with_capacity(blocks.len()); @@ -1106,44 +1075,3 @@ async fn test_engine_tree_live_sync_fcu_extends_canon_chain() { test_harness.check_fcu(main_last_hash, ForkchoiceStatus::Valid).await; test_harness.check_canon_head(main_last_hash); } - -#[tokio::test] -async fn test_engine_tree_buffered_blocks_are_eventually_connected() { - let chain_spec = MAINNET.clone(); - let mut test_harness = TestHarness::new(chain_spec.clone()); - - let base_chain: Vec<_> = test_harness.block_builder.get_executed_blocks(0..1).collect(); - test_harness = test_harness.with_blocks(base_chain.clone()); - - // side chain consisting of two blocks, the last will be inserted first - // so that we force it to be buffered - let side_chain = - test_harness.block_builder.create_fork(base_chain.last().unwrap().recovered_block(), 2); - - // buffer last block of side chain - let buffered_block = side_chain.last().unwrap(); - let buffered_block_hash = buffered_block.hash(); - - test_harness.setup_range_insertion_for_valid_chain(vec![buffered_block.clone()]); - test_harness.send_new_payload(buffered_block.clone()).await; - - assert!(test_harness.tree.state.buffer.block(&buffered_block_hash).is_some()); - - let non_buffered_block = side_chain.first().unwrap(); - let non_buffered_block_hash = non_buffered_block.hash(); - - // insert block that continues the canon chain, should not be buffered - test_harness.setup_range_insertion_for_valid_chain(vec![non_buffered_block.clone()]); - test_harness.send_new_payload(non_buffered_block.clone()).await; - assert!(test_harness.tree.state.buffer.block(&non_buffered_block_hash).is_none()); - - // the previously buffered block should be connected now - assert!(test_harness.tree.state.buffer.block(&buffered_block_hash).is_none()); - - // both blocks are added to the canon chain in order - // note that the buffered block is received first, but added last - test_harness.check_block_received(buffered_block_hash).await; - test_harness.check_block_received(non_buffered_block_hash).await; - test_harness.check_canon_block_added(non_buffered_block_hash).await; - test_harness.check_canon_block_added(buffered_block_hash).await; -} From 67e3c11135777ac5e0ff89196e5b48b528a8fffb Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 18 Jun 2025 23:51:10 +0100 Subject: [PATCH 124/274] perf(trie): `ParallelSparseTrie::get_changed_subtries` method (#16908) Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com> --- crates/trie/common/src/prefix_set.rs | 5 + crates/trie/sparse/src/parallel_trie.rs | 163 +++++++++++++++++++++++- 2 files changed, 163 insertions(+), 5 deletions(-) diff --git a/crates/trie/common/src/prefix_set.rs b/crates/trie/common/src/prefix_set.rs index 844f3de1b62..c8db8c6eaa4 100644 --- a/crates/trie/common/src/prefix_set.rs +++ b/crates/trie/common/src/prefix_set.rs @@ -222,6 +222,11 @@ impl PrefixSet { self.keys.iter() } + /// Returns true if every entry should be considered changed. + pub const fn all(&self) -> bool { + self.all + } + /// Returns the number of elements in the set. pub fn len(&self) -> usize { self.keys.len() diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index b0dabab774f..ee41bd10681 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -2,7 +2,10 @@ use crate::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; use alloc::vec::Vec; use alloy_primitives::{map::HashMap, B256}; use reth_execution_errors::SparseTrieResult; -use reth_trie_common::{prefix_set::PrefixSet, Nibbles, TrieNode}; +use reth_trie_common::{ + prefix_set::{PrefixSet, PrefixSetMut}, + Nibbles, TrieNode, +}; use tracing::trace; /// A revealed sparse trie with subtries that can be updated in parallel. @@ -149,6 +152,49 @@ impl ParallelSparseTrie { } self } + + /// Returns a list of [subtries](SparseSubtrie) identifying the subtries that have changed + /// according to the provided [prefix set](PrefixSet). + /// + /// Along with the subtries, prefix sets are returned. Each prefix set contains the keys from + /// the original prefix set that belong to the subtrie. + /// + /// This method helps optimize hash recalculations by identifying which specific + /// subtries need to be updated. Each subtrie can then be updated in parallel. + #[allow(unused)] + fn get_changed_subtries( + &mut self, + prefix_set: &mut PrefixSet, + ) -> Vec<(SparseSubtrie, PrefixSet)> { + // Clone the prefix set to iterate over its keys. Cloning is cheap, it's just an Arc. + let prefix_set_clone = prefix_set.clone(); + let mut prefix_set_iter = prefix_set_clone.into_iter(); + + let mut subtries = Vec::new(); + for subtrie in &mut self.subtries { + if let Some(subtrie) = subtrie.take_if(|subtrie| prefix_set.contains(&subtrie.path)) { + let prefix_set = if prefix_set.all() { + PrefixSetMut::all() + } else { + // Take those keys from the original prefix set that start with the subtrie path + // + // Subtries are stored in the order of their paths, so we can use the same + // prefix set iterator. + PrefixSetMut::from( + prefix_set_iter + .by_ref() + .skip_while(|key| key < &&subtrie.path) + .take_while(|key| key.has_prefix(&subtrie.path)) + .cloned(), + ) + } + .freeze(); + + subtries.push((subtrie, prefix_set)); + } + } + subtries + } } /// This is a subtrie of the [`ParallelSparseTrie`] that contains a map from path to sparse trie @@ -168,6 +214,11 @@ pub struct SparseSubtrie { } impl SparseSubtrie { + /// Creates a new sparse subtrie with the given root path. + pub fn new(path: Nibbles) -> Self { + Self { path, ..Default::default() } + } + /// Recalculates and updates the RLP hashes for the changed nodes in this subtrie. pub fn update_hashes(&mut self, prefix_set: &mut PrefixSet) -> SparseTrieResult<()> { trace!(target: "trie::parallel_sparse", path=?self.path, "Updating subtrie hashes"); @@ -199,18 +250,120 @@ impl SparseSubtrieType { if path.len() <= 2 { Self::Upper } else { - // Convert first two nibbles of the path into a number. - let index = (path[0] << 4 | path[1]) as usize; - Self::Lower(index) + Self::Lower(path_subtrie_index_unchecked(path)) } } } +/// Convert first two nibbles of the path into a lower subtrie index in the range [0, 255]. +/// +/// # Panics +/// +/// If the path is shorter than two nibbles. +fn path_subtrie_index_unchecked(path: &Nibbles) -> usize { + (path[0] << 4 | path[1]) as usize +} + #[cfg(test)] mod tests { use alloy_trie::Nibbles; + use reth_trie_common::prefix_set::{PrefixSet, PrefixSetMut}; - use crate::parallel_trie::SparseSubtrieType; + use crate::{ + parallel_trie::{path_subtrie_index_unchecked, SparseSubtrieType}, + ParallelSparseTrie, SparseSubtrie, + }; + + #[test] + fn test_get_changed_subtries_empty() { + let mut trie = ParallelSparseTrie::default(); + let mut prefix_set = PrefixSet::default(); + + let changed = trie.get_changed_subtries(&mut prefix_set); + assert!(changed.is_empty()); + } + + #[test] + fn test_get_changed_subtries() { + // Create a trie with three subtries + let mut trie = ParallelSparseTrie::default(); + let subtrie_1 = SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0])); + let subtrie_1_index = path_subtrie_index_unchecked(&subtrie_1.path); + let subtrie_2 = SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0])); + let subtrie_2_index = path_subtrie_index_unchecked(&subtrie_2.path); + let subtrie_3 = SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0])); + let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); + + // Add subtries at specific positions + trie.subtries[subtrie_1_index] = Some(subtrie_1.clone()); + trie.subtries[subtrie_2_index] = Some(subtrie_2.clone()); + trie.subtries[subtrie_3_index] = Some(subtrie_3); + + // Create a prefix set with the keys that match only the second subtrie + let mut prefix_set = PrefixSetMut::from([ + // Doesn't match any subtries + Nibbles::from_nibbles_unchecked([0x0]), + // Match second subtrie + Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), + Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]), + // Doesn't match any subtries + Nibbles::from_nibbles_unchecked([0x2, 0x0, 0x0]), + ]) + .freeze(); + + // Second subtrie should be removed and returned + let changed = trie.get_changed_subtries(&mut prefix_set); + assert_eq!( + changed + .into_iter() + .map(|(subtrie, prefix_set)| { + (subtrie, prefix_set.iter().cloned().collect::>()) + }) + .collect::>(), + vec![( + subtrie_2, + vec![ + Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), + Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]) + ] + )] + ); + assert!(trie.subtries[subtrie_2_index].is_none()); + + // First subtrie should remain unchanged + assert_eq!(trie.subtries[subtrie_1_index], Some(subtrie_1)); + } + + #[test] + fn test_get_changed_subtries_all() { + // Create a trie with three subtries + let mut trie = ParallelSparseTrie::default(); + let subtrie_1 = SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0])); + let subtrie_1_index = path_subtrie_index_unchecked(&subtrie_1.path); + let subtrie_2 = SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0])); + let subtrie_2_index = path_subtrie_index_unchecked(&subtrie_2.path); + let subtrie_3 = SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0])); + let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); + + // Add subtries at specific positions + trie.subtries[subtrie_1_index] = Some(subtrie_1.clone()); + trie.subtries[subtrie_2_index] = Some(subtrie_2.clone()); + trie.subtries[subtrie_3_index] = Some(subtrie_3.clone()); + + // Create a prefix set that matches any key + let mut prefix_set = PrefixSetMut::all().freeze(); + + // All subtries should be removed and returned + let changed = trie.get_changed_subtries(&mut prefix_set); + assert_eq!( + changed + .into_iter() + .map(|(subtrie, prefix_set)| { (subtrie, prefix_set.all()) }) + .collect::>(), + vec![(subtrie_1, true), (subtrie_2, true), (subtrie_3, true)] + ); + assert!(trie.subtries.iter().all(Option::is_none)); + } #[test] fn sparse_subtrie_type() { From cdb5b69d2422e42dc395901475b15b7954908dcd Mon Sep 17 00:00:00 2001 From: Hai | RISE <150876604+hai-rise@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:21:57 +0700 Subject: [PATCH 125/274] chore(tx-validation): remove redundant validate methods (#16929) --- crates/optimism/txpool/src/validator.rs | 41 ++++----------------- crates/transaction-pool/src/validate/eth.rs | 41 ++------------------- 2 files changed, 11 insertions(+), 71 deletions(-) diff --git a/crates/optimism/txpool/src/validator.rs b/crates/optimism/txpool/src/validator.rs index 6f739553906..cd9b74f1ddf 100644 --- a/crates/optimism/txpool/src/validator.rs +++ b/crates/optimism/txpool/src/validator.rs @@ -215,37 +215,6 @@ where self.apply_op_checks(outcome) } - /// Validates all given transactions. - /// - /// Returns all outcomes for the given transactions in the same order. - /// - /// See also [`Self::validate_one`] - pub async fn validate_all( - &self, - transactions: Vec<(TransactionOrigin, Tx)>, - ) -> Vec> { - futures_util::future::join_all( - transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)), - ) - .await - } - - /// Validates all given transactions with the specified origin parameter. - /// - /// Returns all outcomes for the given transactions in the same order. - /// - /// See also [`Self::validate_one`] - pub async fn validate_all_with_origin( - &self, - origin: TransactionOrigin, - transactions: impl IntoIterator + Send, - ) -> Vec> { - futures_util::future::join_all( - transactions.into_iter().map(|tx| self.validate_one(origin, tx)), - ) - .await - } - /// Performs the necessary opstack specific checks based on top of the regular eth outcome. fn apply_op_checks( &self, @@ -341,7 +310,10 @@ where &self, transactions: Vec<(TransactionOrigin, Self::Transaction)>, ) -> Vec> { - self.validate_all(transactions).await + futures_util::future::join_all( + transactions.into_iter().map(|(origin, tx)| self.validate_one(origin, tx)), + ) + .await } async fn validate_transactions_with_origin( @@ -349,7 +321,10 @@ where origin: TransactionOrigin, transactions: impl IntoIterator + Send, ) -> Vec> { - self.validate_all_with_origin(origin, transactions).await + futures_util::future::join_all( + transactions.into_iter().map(|tx| self.validate_one(origin, tx)), + ) + .await } fn on_new_head_block(&self, new_tip_block: &SealedBlock) diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 99294e53962..0c62412c184 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -76,7 +76,7 @@ where origin: TransactionOrigin, transaction: Tx, ) -> TransactionValidationOutcome { - self.inner.validate_one(origin, transaction) + self.inner.validate_one_with_provider(origin, transaction, &mut None) } /// Validates a single transaction with the provided state provider. @@ -93,31 +93,6 @@ where ) -> TransactionValidationOutcome { self.inner.validate_one_with_provider(origin, transaction, state) } - - /// Validates all given transactions. - /// - /// Returns all outcomes for the given transactions in the same order. - /// - /// See also [`Self::validate_one`] - pub fn validate_all( - &self, - transactions: Vec<(TransactionOrigin, Tx)>, - ) -> Vec> { - self.inner.validate_batch(transactions) - } - - /// Validates all given transactions with origin. - /// - /// Returns all outcomes for the given transactions in the same order. - /// - /// See also [`Self::validate_one`] - pub fn validate_all_with_origin( - &self, - origin: TransactionOrigin, - transactions: impl IntoIterator + Send, - ) -> Vec> { - self.inner.validate_batch_with_origin(origin, transactions) - } } impl TransactionValidator for EthTransactionValidator @@ -139,7 +114,7 @@ where &self, transactions: Vec<(TransactionOrigin, Self::Transaction)>, ) -> Vec> { - self.validate_all(transactions) + self.inner.validate_batch(transactions) } async fn validate_transactions_with_origin( @@ -147,7 +122,7 @@ where origin: TransactionOrigin, transactions: impl IntoIterator + Send, ) -> Vec> { - self.validate_all_with_origin(origin, transactions) + self.inner.validate_batch_with_origin(origin, transactions) } fn on_new_head_block(&self, new_tip_block: &SealedBlock) @@ -633,16 +608,6 @@ where } } - /// Validates a single transaction. - fn validate_one( - &self, - origin: TransactionOrigin, - transaction: Tx, - ) -> TransactionValidationOutcome { - let mut provider = None; - self.validate_one_with_provider(origin, transaction, &mut provider) - } - /// Validates all given transactions. fn validate_batch( &self, From 20800be462f621f74a7db9fdab27ab833b2469f6 Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:22:41 +0300 Subject: [PATCH 126/274] docs: Fix Typo in DebugNode Trait Documentation (#16932) --- crates/node/builder/src/launch/debug.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/node/builder/src/launch/debug.rs b/crates/node/builder/src/launch/debug.rs index 3fa6da72118..dfc3ba27d56 100644 --- a/crates/node/builder/src/launch/debug.rs +++ b/crates/node/builder/src/launch/debug.rs @@ -43,7 +43,7 @@ use tracing::info; /// ``` pub trait DebugNode: Node { /// RPC block type. Used by [`DebugConsensusClient`] to fetch blocks and submit them to the - /// engine. This is inteded to match the block format returned by the external RPC endpoint. + /// engine. This is intended to match the block format returned by the external RPC endpoint. type RpcBlock: Serialize + DeserializeOwned + 'static; /// Converts an RPC block to a primitive block. From 53cd4b2397c78286b4561116d8ce18c41d8edc50 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 19 Jun 2025 12:38:29 +0200 Subject: [PATCH 127/274] chore: add type alias for PayloadAttributes (#16933) --- crates/node/types/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/node/types/src/lib.rs b/crates/node/types/src/lib.rs index 13245a18b9b..5e90376a7e9 100644 --- a/crates/node/types/src/lib.rs +++ b/crates/node/types/src/lib.rs @@ -220,3 +220,6 @@ pub type PrimitivesTy = ::Primitives; /// Helper type for getting the `Primitives` associated type from a [`NodeTypes`]. pub type KeyHasherTy = <::StateCommitment as StateCommitment>::KeyHasher; + +/// Helper adapter type for accessing [`PayloadTypes::PayloadAttributes`] on [`NodeTypes`]. +pub type PayloadAttrTy = <::Payload as PayloadTypes>::PayloadAttributes; From 2ebb519287ffc4dcfa75743337b10cd1d68aac2d Mon Sep 17 00:00:00 2001 From: Solar Mithril Date: Thu, 19 Jun 2025 16:13:52 +0500 Subject: [PATCH 128/274] chore: Expose payload_id (#16931) Co-authored-by: Solar Mithril --- crates/ethereum/engine-primitives/src/lib.rs | 2 +- crates/ethereum/engine-primitives/src/payload.rs | 2 +- crates/optimism/payload/src/lib.rs | 4 +++- crates/optimism/payload/src/payload.rs | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/ethereum/engine-primitives/src/lib.rs b/crates/ethereum/engine-primitives/src/lib.rs index ceb3a3b0a84..dcd73232db6 100644 --- a/crates/ethereum/engine-primitives/src/lib.rs +++ b/crates/ethereum/engine-primitives/src/lib.rs @@ -12,7 +12,7 @@ extern crate alloc; mod payload; -pub use payload::{BlobSidecars, EthBuiltPayload, EthPayloadBuilderAttributes}; +pub use payload::{payload_id, BlobSidecars, EthBuiltPayload, EthPayloadBuilderAttributes}; mod error; pub use error::*; diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index ec8152dd805..444747716ee 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -404,7 +404,7 @@ impl PayloadBuilderAttributes for EthPayloadBuilderAttributes { /// Generates the payload id for the configured payload from the [`PayloadAttributes`]. /// /// Returns an 8-byte identifier by hashing the payload components with sha256 hash. -pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId { +pub fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId { use sha2::Digest; let mut hasher = sha2::Sha256::new(); hasher.update(parent.as_slice()); diff --git a/crates/optimism/payload/src/lib.rs b/crates/optimism/payload/src/lib.rs index 03545863e81..19ef8f3b218 100644 --- a/crates/optimism/payload/src/lib.rs +++ b/crates/optimism/payload/src/lib.rs @@ -16,7 +16,9 @@ pub use builder::OpPayloadBuilder; pub mod error; pub mod payload; use op_alloy_rpc_types_engine::OpExecutionData; -pub use payload::{OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilderAttributes}; +pub use payload::{ + payload_id_optimism, OpBuiltPayload, OpPayloadAttributes, OpPayloadBuilderAttributes, +}; mod traits; use reth_optimism_primitives::OpPrimitives; use reth_payload_primitives::{BuiltPayload, PayloadTypes}; diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index f32f19ff6f9..0416cf68bab 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -327,7 +327,7 @@ where /// Generates the payload id for the configured payload from the [`OpPayloadAttributes`]. /// /// Returns an 8-byte identifier by hashing the payload components with sha256 hash. -pub(crate) fn payload_id_optimism( +pub fn payload_id_optimism( parent: &B256, attributes: &OpPayloadAttributes, payload_version: u8, From 55dd16ac20a1fdb0ab1e054cb8afe83aac230edb Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:23:32 +0100 Subject: [PATCH 129/274] perf(trie): box subtries in parallel sparse trie (#16938) --- crates/trie/sparse/src/parallel_trie.rs | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index ee41bd10681..6351999c5d6 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -1,6 +1,7 @@ use crate::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; -use alloc::vec::Vec; +use alloc::{boxed::Box, vec::Vec}; use alloy_primitives::{map::HashMap, B256}; +use alloy_trie::TrieMask; use reth_execution_errors::SparseTrieResult; use reth_trie_common::{ prefix_set::{PrefixSet, PrefixSetMut}, @@ -20,7 +21,7 @@ pub struct ParallelSparseTrie { /// This contains the trie nodes for the upper part of the trie. upper_subtrie: SparseSubtrie, /// An array containing the subtries at the second level of the trie. - subtries: [Option; 256], + lower_subtries: Box<[Option; 256]>, /// Optional tracking of trie updates for later use. updates: Option, } @@ -29,7 +30,7 @@ impl Default for ParallelSparseTrie { fn default() -> Self { Self { upper_subtrie: SparseSubtrie::default(), - subtries: [const { None }; 256], + lower_subtries: Box::new([const { None }; 256]), updates: None, } } @@ -171,7 +172,7 @@ impl ParallelSparseTrie { let mut prefix_set_iter = prefix_set_clone.into_iter(); let mut subtries = Vec::new(); - for subtrie in &mut self.subtries { + for subtrie in self.lower_subtries.iter_mut() { if let Some(subtrie) = subtrie.take_if(|subtrie| prefix_set.contains(&subtrie.path)) { let prefix_set = if prefix_set.all() { PrefixSetMut::all() @@ -208,6 +209,10 @@ pub struct SparseSubtrie { path: Nibbles, /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, + /// When a branch is set, the corresponding child subtree is stored in the database. + branch_node_tree_masks: HashMap, + /// When a bit is set, the corresponding child is stored as a hash in the database. + branch_node_hash_masks: HashMap, /// Map from leaf key paths to their values. /// All values are stored here instead of directly in leaf nodes. values: HashMap>, @@ -295,9 +300,9 @@ mod tests { let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); // Add subtries at specific positions - trie.subtries[subtrie_1_index] = Some(subtrie_1.clone()); - trie.subtries[subtrie_2_index] = Some(subtrie_2.clone()); - trie.subtries[subtrie_3_index] = Some(subtrie_3); + trie.lower_subtries[subtrie_1_index] = Some(subtrie_1.clone()); + trie.lower_subtries[subtrie_2_index] = Some(subtrie_2.clone()); + trie.lower_subtries[subtrie_3_index] = Some(subtrie_3); // Create a prefix set with the keys that match only the second subtrie let mut prefix_set = PrefixSetMut::from([ @@ -328,10 +333,10 @@ mod tests { ] )] ); - assert!(trie.subtries[subtrie_2_index].is_none()); + assert!(trie.lower_subtries[subtrie_2_index].is_none()); // First subtrie should remain unchanged - assert_eq!(trie.subtries[subtrie_1_index], Some(subtrie_1)); + assert_eq!(trie.lower_subtries[subtrie_1_index], Some(subtrie_1)); } #[test] @@ -346,9 +351,9 @@ mod tests { let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); // Add subtries at specific positions - trie.subtries[subtrie_1_index] = Some(subtrie_1.clone()); - trie.subtries[subtrie_2_index] = Some(subtrie_2.clone()); - trie.subtries[subtrie_3_index] = Some(subtrie_3.clone()); + trie.lower_subtries[subtrie_1_index] = Some(subtrie_1.clone()); + trie.lower_subtries[subtrie_2_index] = Some(subtrie_2.clone()); + trie.lower_subtries[subtrie_3_index] = Some(subtrie_3.clone()); // Create a prefix set that matches any key let mut prefix_set = PrefixSetMut::all().freeze(); @@ -362,7 +367,7 @@ mod tests { .collect::>(), vec![(subtrie_1, true), (subtrie_2, true), (subtrie_3, true)] ); - assert!(trie.subtries.iter().all(Option::is_none)); + assert!(trie.lower_subtries.iter().all(Option::is_none)); } #[test] From 2f9c5ace378fa54d42f1440488e5f042b5c58e46 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 19 Jun 2025 13:59:20 +0200 Subject: [PATCH 130/274] test: flaky connection test (#16939) --- crates/net/network/tests/it/connect.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/net/network/tests/it/connect.rs b/crates/net/network/tests/it/connect.rs index 0297dc6b6b3..f4c4aa159b3 100644 --- a/crates/net/network/tests/it/connect.rs +++ b/crates/net/network/tests/it/connect.rs @@ -19,7 +19,9 @@ use reth_network_p2p::{ sync::{NetworkSyncUpdater, SyncState}, }; use reth_network_peers::{mainnet_nodes, NodeRecord, TrustedPeer}; +use reth_network_types::peers::config::PeerBackoffDurations; use reth_storage_api::noop::NoopProvider; +use reth_tracing::init_test_tracing; use reth_transaction_pool::test_utils::testing_pool; use secp256k1::SecretKey; use std::time::Duration; @@ -359,6 +361,7 @@ async fn test_shutdown() { #[tokio::test(flavor = "multi_thread")] async fn test_trusted_peer_only() { + init_test_tracing(); let net = Testnet::create(2).await; let mut handles = net.handles(); @@ -376,7 +379,10 @@ async fn test_trusted_peer_only() { let _handle = net.spawn(); let secret_key = SecretKey::new(&mut rand_08::thread_rng()); - let peers_config = PeersConfig::default().with_trusted_nodes_only(true); + let peers_config = PeersConfig::default() + .with_backoff_durations(PeerBackoffDurations::test()) + .with_ban_duration(Duration::from_millis(200)) + .with_trusted_nodes_only(true); let config = NetworkConfigBuilder::eth(secret_key) .listener_port(0) @@ -416,11 +422,13 @@ async fn test_trusted_peer_only() { tokio::time::sleep(Duration::from_secs(1)).await; assert_eq!(handle.num_connected_peers(), 1); - handle1.add_trusted_peer(*handle.peer_id(), handle.local_addr()); + handle.add_trusted_peer(*handle1.peer_id(), handle1.local_addr()); // wait for the next session established event to check the handle1 incoming connection let outgoing_peer_id1 = event_stream.next_session_established().await.unwrap(); assert_eq!(outgoing_peer_id1, *handle1.peer_id()); + + tokio::time::sleep(Duration::from_secs(1)).await; assert_eq!(handle.num_connected_peers(), 2); // check that handle0 and handle1 both have peers. From aa725dd0cf84462cd3bf13257f0aed8e1a9eebe4 Mon Sep 17 00:00:00 2001 From: Rose Jethani <101273941+rose2221@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:51:05 +0530 Subject: [PATCH 131/274] feat: add Historical RPC Forwarder Service (#16724) Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: Matthias Seitz --- Cargo.lock | 1 + crates/optimism/rpc/Cargo.toml | 1 + crates/optimism/rpc/src/historical.rs | 161 +++++++++++++++++++++++++- 3 files changed, 161 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27925ad48b7..92a8612a720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9389,6 +9389,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "revm", + "serde_json", "thiserror 2.0.12", "tokio", "tracing", diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 1187076f5d3..a29b2406b7f 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -67,6 +67,7 @@ async-trait.workspace = true jsonrpsee-core.workspace = true jsonrpsee-types.workspace = true jsonrpsee.workspace = true +serde_json.workspace = true # misc eyre.workspace = true diff --git a/crates/optimism/rpc/src/historical.rs b/crates/optimism/rpc/src/historical.rs index ac9320d4fff..c364271ae31 100644 --- a/crates/optimism/rpc/src/historical.rs +++ b/crates/optimism/rpc/src/historical.rs @@ -1,10 +1,18 @@ //! Client support for optimism historical RPC requests. use crate::sequencer::Error; +use alloy_eips::BlockId; use alloy_json_rpc::{RpcRecv, RpcSend}; +use alloy_primitives::BlockNumber; use alloy_rpc_client::RpcClient; -use std::sync::Arc; -use tracing::warn; +use jsonrpsee_core::{ + middleware::{Batch, Notification, RpcServiceT}, + server::MethodResponse, +}; +use jsonrpsee_types::{Params, Request}; +use reth_storage_api::BlockReaderIdExt; +use std::{future::Future, sync::Arc}; +use tracing::{debug, warn}; /// A client that can be used to forward RPC requests for historical data to an endpoint. /// @@ -66,3 +74,152 @@ struct HistoricalRpcClientInner { historical_endpoint: String, client: RpcClient, } + +/// A service that intercepts RPC calls and forwards pre-bedrock historical requests +/// to a dedicated endpoint. +/// +/// This checks if the request is for a pre-bedrock block and forwards it via the configured +/// historical RPC client. +#[derive(Debug, Clone)] +pub struct HistoricalRpcService { + /// The inner service that handles regular RPC requests + inner: S, + /// Client used to forward historical requests + historical_client: HistoricalRpcClient, + /// Provider used to determine if a block is pre-bedrock + provider: P, + /// Bedrock transition block number + bedrock_block: BlockNumber, +} + +impl RpcServiceT for HistoricalRpcService +where + S: RpcServiceT + Send + Sync + Clone + 'static, + + P: BlockReaderIdExt + Send + Sync + Clone + 'static, +{ + type MethodResponse = S::MethodResponse; + type NotificationResponse = S::NotificationResponse; + type BatchResponse = S::BatchResponse; + + fn call<'a>(&self, req: Request<'a>) -> impl Future + Send + 'a { + let inner_service = self.inner.clone(); + let historical_client = self.historical_client.clone(); + let provider = self.provider.clone(); + let bedrock_block = self.bedrock_block; + + Box::pin(async move { + let maybe_block_id = match req.method_name() { + "eth_getBlockByNumber" | "eth_getBlockByHash" => { + parse_block_id_from_params(&req.params(), 0) + } + "eth_getBalance" | + "eth_getStorageAt" | + "eth_getCode" | + "eth_getTransactionCount" | + "eth_call" => parse_block_id_from_params(&req.params(), 1), + _ => None, + }; + + // if we've extracted a block ID, check if it's pre-Bedrock + if let Some(block_id) = maybe_block_id { + let is_pre_bedrock = if let Ok(Some(num)) = provider.block_number_for_id(block_id) { + num < bedrock_block + } else { + // If we can't convert the hash to a number, assume it's post-Bedrock + debug!(target: "rpc::historical", ?block_id, "hash unknown; not forwarding"); + false + }; + + // if the block is pre-Bedrock, forward the request to the historical client + if is_pre_bedrock { + debug!(target: "rpc::historical", method = %req.method_name(), ?block_id, params=?req.params(), "forwarding pre-Bedrock request"); + + let params = req.params(); + let params = params.as_str().unwrap_or("[]"); + if let Ok(params) = serde_json::from_str::(params) { + if let Ok(raw) = historical_client + .request::<_, serde_json::Value>(req.method_name(), params) + .await + { + let payload = + jsonrpsee_types::ResponsePayload::success(raw.to_string()).into(); + return MethodResponse::response(req.id, payload, usize::MAX); + } + } + } + } + + // handle the request with the inner service + inner_service.call(req).await + }) + } + + fn batch<'a>(&self, req: Batch<'a>) -> impl Future + Send + 'a { + self.inner.batch(req) + } + + fn notification<'a>( + &self, + n: Notification<'a>, + ) -> impl Future + Send + 'a { + self.inner.notification(n) + } +} + +/// Parses a `BlockId` from the given parameters at the specified position. +fn parse_block_id_from_params(params: &Params<'_>, position: usize) -> Option { + let values: Vec = params.parse().ok()?; + let val = values.into_iter().nth(position)?; + serde_json::from_value::(val).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_eips::{BlockId, BlockNumberOrTag}; + use jsonrpsee::types::Params; + + /// Tests that various valid id types can be parsed from the first parameter. + #[test] + fn parses_block_id_from_first_param() { + // Test with a block number + let params_num = Params::new(Some(r#"["0x64"]"#)); // 100 + assert_eq!( + parse_block_id_from_params(¶ms_num, 0).unwrap(), + BlockId::Number(BlockNumberOrTag::Number(100)) + ); + + // Test with the "earliest" tag + let params_tag = Params::new(Some(r#"["earliest"]"#)); + assert_eq!( + parse_block_id_from_params(¶ms_tag, 0).unwrap(), + BlockId::Number(BlockNumberOrTag::Earliest) + ); + } + + /// Tests that the function correctly parses from a position other than 0. + #[test] + fn parses_block_id_from_second_param() { + let params = + Params::new(Some(r#"["0x0000000000000000000000000000000000000000", "latest"]"#)); + let result = parse_block_id_from_params(¶ms, 1).unwrap(); + assert_eq!(result, BlockId::Number(BlockNumberOrTag::Latest)); + } + + /// Tests that the function returns nothing if the parameter is missing or empty. + #[test] + fn defaults_to_latest_when_param_is_missing() { + let params = Params::new(Some(r#"["0x0000000000000000000000000000000000000000"]"#)); + let result = parse_block_id_from_params(¶ms, 1); + assert!(result.is_none()); + } + + /// Tests that the function doesn't parse anyhing if the parameter is not a valid block id. + #[test] + fn returns_error_for_invalid_input() { + let params = Params::new(Some(r#"[true]"#)); + let result = parse_block_id_from_params(¶ms, 0); + assert!(result.is_none()); + } +} From ebd57f77bcdc3891c9d4960bed9af0fa88182e46 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 19 Jun 2025 15:13:12 +0200 Subject: [PATCH 132/274] perf(trie): `ParallelSparseTrie::reveal_node` (#16894) Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> --- crates/trie/sparse/src/parallel_trie.rs | 488 +++++++++++++++++++++++- 1 file changed, 474 insertions(+), 14 deletions(-) diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse/src/parallel_trie.rs index 6351999c5d6..0e7a97efacc 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse/src/parallel_trie.rs @@ -1,11 +1,15 @@ use crate::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; use alloc::{boxed::Box, vec::Vec}; -use alloy_primitives::{map::HashMap, B256}; +use alloy_primitives::{ + map::{Entry, HashMap}, + B256, +}; +use alloy_rlp::Decodable; use alloy_trie::TrieMask; -use reth_execution_errors::SparseTrieResult; +use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult}; use reth_trie_common::{ prefix_set::{PrefixSet, PrefixSetMut}, - Nibbles, TrieNode, + Nibbles, TrieNode, CHILD_INDEX_RANGE, }; use tracing::trace; @@ -37,6 +41,22 @@ impl Default for ParallelSparseTrie { } impl ParallelSparseTrie { + /// Returns mutable ref to the lower `SparseSubtrie` for the given path, or None if the path + /// belongs to the upper trie. + fn lower_subtrie_for_path(&mut self, path: &Nibbles) -> Option<&mut SparseSubtrie> { + match SparseSubtrieType::from_path(path) { + SparseSubtrieType::Upper => None, + SparseSubtrieType::Lower(idx) => { + if self.lower_subtries[idx].is_none() { + let upper_path = path.slice(..2); + self.lower_subtries[idx] = Some(SparseSubtrie::new(upper_path)); + } + + self.lower_subtries[idx].as_mut() + } + } + } + /// Creates a new revealed sparse trie from the given root node. /// /// # Returns @@ -58,7 +78,6 @@ impl ParallelSparseTrie { /// It handles different node types (leaf, extension, branch) by appropriately /// adding them to the trie structure and recursively revealing their children. /// - /// /// # Returns /// /// `Ok(())` if successful, or an error if node was not revealed. @@ -68,10 +87,50 @@ impl ParallelSparseTrie { node: TrieNode, masks: TrieMasks, ) -> SparseTrieResult<()> { - let _path = path; - let _node = node; - let _masks = masks; - todo!() + // TODO parallelize + if let Some(subtrie) = self.lower_subtrie_for_path(&path) { + return subtrie.reveal_node(path, &node, masks); + } + + // If there is no subtrie for the path it means the path is 2 or less nibbles, and so + // belongs to the upper trie. + self.upper_subtrie.reveal_node(path.clone(), &node, masks)?; + + // The previous upper_trie.reveal_node call will not have revealed any child nodes via + // reveal_node_or_hash if the child node would be found on a lower subtrie. We handle that + // here by manually checking the specific cases where this could happen, and calling + // reveal_node_or_hash for each. + match node { + TrieNode::Branch(branch) => { + // If a branch is at the second level of the trie then it will be in the upper trie, + // but all of its children will be in the lower trie. + if path.len() == 2 { + let mut stack_ptr = branch.as_ref().first_child_index(); + for idx in CHILD_INDEX_RANGE { + if branch.state_mask.is_bit_set(idx) { + let mut child_path = path.clone(); + child_path.push_unchecked(idx); + self.lower_subtrie_for_path(&child_path) + .expect("child_path must have a lower subtrie") + .reveal_node_or_hash(child_path, &branch.stack[stack_ptr])?; + stack_ptr += 1; + } + } + } + } + TrieNode::Extension(ext) => { + let mut child_path = path.clone(); + child_path.extend_from_slice_unchecked(&ext.key); + if child_path.len() > 2 { + self.lower_subtrie_for_path(&child_path) + .expect("child_path must have a lower subtrie") + .reveal_node_or_hash(child_path, &ext.child)?; + } + } + TrieNode::EmptyRoot | TrieNode::Leaf(_) => (), + } + + Ok(()) } /// Updates or inserts a leaf node at the specified key path with the provided RLP-encoded @@ -219,11 +278,213 @@ pub struct SparseSubtrie { } impl SparseSubtrie { - /// Creates a new sparse subtrie with the given root path. - pub fn new(path: Nibbles) -> Self { + fn new(path: Nibbles) -> Self { Self { path, ..Default::default() } } + /// Returns true if the current path and its child are both found in the same level. This + /// function assumes that if `current_path` is in a lower level then `child_path` is too. + fn is_child_same_level(current_path: &Nibbles, child_path: &Nibbles) -> bool { + let current_level = core::mem::discriminant(&SparseSubtrieType::from_path(current_path)); + let child_level = core::mem::discriminant(&SparseSubtrieType::from_path(child_path)); + current_level == child_level + } + + /// Internal implementation of the method of the same name on `ParallelSparseTrie`. + fn reveal_node( + &mut self, + path: Nibbles, + node: &TrieNode, + masks: TrieMasks, + ) -> SparseTrieResult<()> { + // If the node is already revealed and it's not a hash node, do nothing. + if self.nodes.get(&path).is_some_and(|node| !node.is_hash()) { + return Ok(()) + } + + if let Some(tree_mask) = masks.tree_mask { + self.branch_node_tree_masks.insert(path.clone(), tree_mask); + } + if let Some(hash_mask) = masks.hash_mask { + self.branch_node_hash_masks.insert(path.clone(), hash_mask); + } + + match node { + TrieNode::EmptyRoot => { + // For an empty root, ensure that we are at the root path, and at the upper subtrie. + debug_assert!(path.is_empty()); + debug_assert!(self.path.is_empty()); + self.nodes.insert(path, SparseNode::Empty); + } + TrieNode::Branch(branch) => { + // For a branch node, iterate over all potential children + let mut stack_ptr = branch.as_ref().first_child_index(); + for idx in CHILD_INDEX_RANGE { + if branch.state_mask.is_bit_set(idx) { + let mut child_path = path.clone(); + child_path.push_unchecked(idx); + if Self::is_child_same_level(&path, &child_path) { + // Reveal each child node or hash it has, but only if the child is on + // the same level as the parent. + self.reveal_node_or_hash(child_path, &branch.stack[stack_ptr])?; + } + stack_ptr += 1; + } + } + // Update the branch node entry in the nodes map, handling cases where a blinded + // node is now replaced with a revealed node. + match self.nodes.entry(path) { + Entry::Occupied(mut entry) => match entry.get() { + // Replace a hash node with a fully revealed branch node. + SparseNode::Hash(hash) => { + entry.insert(SparseNode::Branch { + state_mask: branch.state_mask, + // Memoize the hash of a previously blinded node in a new branch + // node. + hash: Some(*hash), + store_in_db_trie: Some( + masks.hash_mask.is_some_and(|mask| !mask.is_empty()) || + masks.tree_mask.is_some_and(|mask| !mask.is_empty()), + ), + }); + } + // Branch node already exists, or an extension node was placed where a + // branch node was before. + SparseNode::Branch { .. } | SparseNode::Extension { .. } => {} + // All other node types can't be handled. + node @ (SparseNode::Empty | SparseNode::Leaf { .. }) => { + return Err(SparseTrieErrorKind::Reveal { + path: entry.key().clone(), + node: Box::new(node.clone()), + } + .into()) + } + }, + Entry::Vacant(entry) => { + entry.insert(SparseNode::new_branch(branch.state_mask)); + } + } + } + TrieNode::Extension(ext) => match self.nodes.entry(path.clone()) { + Entry::Occupied(mut entry) => match entry.get() { + // Replace a hash node with a revealed extension node. + SparseNode::Hash(hash) => { + let mut child_path = entry.key().clone(); + child_path.extend_from_slice_unchecked(&ext.key); + entry.insert(SparseNode::Extension { + key: ext.key.clone(), + // Memoize the hash of a previously blinded node in a new extension + // node. + hash: Some(*hash), + store_in_db_trie: None, + }); + if Self::is_child_same_level(&path, &child_path) { + self.reveal_node_or_hash(child_path, &ext.child)?; + } + } + // Extension node already exists, or an extension node was placed where a branch + // node was before. + SparseNode::Extension { .. } | SparseNode::Branch { .. } => {} + // All other node types can't be handled. + node @ (SparseNode::Empty | SparseNode::Leaf { .. }) => { + return Err(SparseTrieErrorKind::Reveal { + path: entry.key().clone(), + node: Box::new(node.clone()), + } + .into()) + } + }, + Entry::Vacant(entry) => { + let mut child_path = entry.key().clone(); + child_path.extend_from_slice_unchecked(&ext.key); + entry.insert(SparseNode::new_ext(ext.key.clone())); + if Self::is_child_same_level(&path, &child_path) { + self.reveal_node_or_hash(child_path, &ext.child)?; + } + } + }, + TrieNode::Leaf(leaf) => match self.nodes.entry(path) { + Entry::Occupied(mut entry) => match entry.get() { + // Replace a hash node with a revealed leaf node and store leaf node value. + SparseNode::Hash(hash) => { + let mut full = entry.key().clone(); + full.extend_from_slice_unchecked(&leaf.key); + self.values.insert(full, leaf.value.clone()); + entry.insert(SparseNode::Leaf { + key: leaf.key.clone(), + // Memoize the hash of a previously blinded node in a new leaf + // node. + hash: Some(*hash), + }); + } + // Leaf node already exists. + SparseNode::Leaf { .. } => {} + // All other node types can't be handled. + node @ (SparseNode::Empty | + SparseNode::Extension { .. } | + SparseNode::Branch { .. }) => { + return Err(SparseTrieErrorKind::Reveal { + path: entry.key().clone(), + node: Box::new(node.clone()), + } + .into()) + } + }, + Entry::Vacant(entry) => { + let mut full = entry.key().clone(); + full.extend_from_slice_unchecked(&leaf.key); + entry.insert(SparseNode::new_leaf(leaf.key.clone())); + self.values.insert(full, leaf.value.clone()); + } + }, + } + + Ok(()) + } + + /// Reveals either a node or its hash placeholder based on the provided child data. + /// + /// When traversing the trie, we often encounter references to child nodes that + /// are either directly embedded or represented by their hash. This method + /// handles both cases: + /// + /// 1. If the child data represents a hash (32+1=33 bytes), store it as a hash node + /// 2. Otherwise, decode the data as a [`TrieNode`] and recursively reveal it using + /// `reveal_node` + /// + /// # Returns + /// + /// Returns `Ok(())` if successful, or an error if the node cannot be revealed. + /// + /// # Error Handling + /// + /// Will error if there's a conflict between a new hash node and an existing one + /// at the same path + fn reveal_node_or_hash(&mut self, path: Nibbles, child: &[u8]) -> SparseTrieResult<()> { + if child.len() == B256::len_bytes() + 1 { + let hash = B256::from_slice(&child[1..]); + match self.nodes.entry(path) { + Entry::Occupied(entry) => match entry.get() { + // Hash node with a different hash can't be handled. + SparseNode::Hash(previous_hash) if previous_hash != &hash => { + return Err(SparseTrieErrorKind::Reveal { + path: entry.key().clone(), + node: Box::new(SparseNode::Hash(hash)), + } + .into()) + } + _ => {} + }, + Entry::Vacant(entry) => { + entry.insert(SparseNode::Hash(hash)); + } + } + return Ok(()) + } + + self.reveal_node(path, &TrieNode::decode(&mut &child[..])?, TrieMasks::none()) + } + /// Recalculates and updates the RLP hashes for the changed nodes in this subtrie. pub fn update_hashes(&mut self, prefix_set: &mut PrefixSet) -> SparseTrieResult<()> { trace!(target: "trie::parallel_sparse", path=?self.path, "Updating subtrie hashes"); @@ -271,14 +532,58 @@ fn path_subtrie_index_unchecked(path: &Nibbles) -> usize { #[cfg(test)] mod tests { - use alloy_trie::Nibbles; - use reth_trie_common::prefix_set::{PrefixSet, PrefixSetMut}; - use crate::{ parallel_trie::{path_subtrie_index_unchecked, SparseSubtrieType}, - ParallelSparseTrie, SparseSubtrie, + ParallelSparseTrie, SparseNode, SparseSubtrie, TrieMasks, + }; + use alloy_primitives::B256; + use alloy_rlp::Encodable; + use alloy_trie::Nibbles; + use assert_matches::assert_matches; + use reth_primitives_traits::Account; + use reth_trie_common::{ + prefix_set::{PrefixSet, PrefixSetMut}, + BranchNode, ExtensionNode, LeafNode, RlpNode, TrieMask, TrieNode, EMPTY_ROOT_HASH, }; + // Test helpers + fn encode_account_value(nonce: u64) -> Vec { + let account = Account { nonce, ..Default::default() }; + let trie_account = account.into_trie_account(EMPTY_ROOT_HASH); + let mut buf = Vec::new(); + trie_account.encode(&mut buf); + buf + } + + fn create_leaf_node(key: &[u8], value_nonce: u64) -> TrieNode { + TrieNode::Leaf(LeafNode::new( + Nibbles::from_nibbles_unchecked(key), + encode_account_value(value_nonce), + )) + } + + fn create_extension_node(key: &[u8], child_hash: B256) -> TrieNode { + TrieNode::Extension(ExtensionNode::new( + Nibbles::from_nibbles_unchecked(key), + RlpNode::word_rlp(&child_hash), + )) + } + + fn create_branch_node_with_children( + children_indices: &[u8], + child_hashes: &[B256], + ) -> TrieNode { + let mut stack = Vec::new(); + let mut state_mask = 0u16; + + for (&idx, &hash) in children_indices.iter().zip(child_hashes.iter()) { + state_mask |= 1 << idx; + stack.push(RlpNode::word_rlp(&hash)); + } + + TrieNode::Branch(BranchNode::new(stack, TrieMask::new(state_mask))) + } + #[test] fn test_get_changed_subtries_empty() { let mut trie = ParallelSparseTrie::default(); @@ -405,4 +710,159 @@ mod tests { SparseSubtrieType::Lower(255) ); } + + #[test] + fn reveal_node_leaves() { + let mut trie = ParallelSparseTrie::default(); + + // Reveal leaf in the upper trie + { + let path = Nibbles::from_nibbles([0x1, 0x2]); + let node = create_leaf_node(&[0x3, 0x4], 42); + let masks = TrieMasks::none(); + + trie.reveal_node(path.clone(), node, masks).unwrap(); + + assert_matches!( + trie.upper_subtrie.nodes.get(&path), + Some(SparseNode::Leaf { key, hash: None }) + if key == &Nibbles::from_nibbles([0x3, 0x4]) + ); + + let full_path = Nibbles::from_nibbles([0x1, 0x2, 0x3, 0x4]); + assert_eq!(trie.upper_subtrie.values.get(&full_path), Some(&encode_account_value(42))); + } + + // Reveal leaf in a lower trie + { + let path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); + let node = create_leaf_node(&[0x4, 0x5], 42); + let masks = TrieMasks::none(); + + trie.reveal_node(path.clone(), node, masks).unwrap(); + + // Check that the lower subtrie was created + let idx = path_subtrie_index_unchecked(&path); + assert!(trie.lower_subtries[idx].is_some()); + + let lower_subtrie = trie.lower_subtries[idx].as_ref().unwrap(); + assert_matches!( + lower_subtrie.nodes.get(&path), + Some(SparseNode::Leaf { key, hash: None }) + if key == &Nibbles::from_nibbles([0x4, 0x5]) + ); + } + } + + #[test] + fn reveal_node_extension_all_upper() { + let mut trie = ParallelSparseTrie::default(); + let path = Nibbles::from_nibbles([0x1]); + let child_hash = B256::repeat_byte(0xab); + let node = create_extension_node(&[0x2], child_hash); + let masks = TrieMasks::none(); + + trie.reveal_node(path.clone(), node, masks).unwrap(); + + assert_matches!( + trie.upper_subtrie.nodes.get(&path), + Some(SparseNode::Extension { key, hash: None, .. }) + if key == &Nibbles::from_nibbles([0x2]) + ); + + // Child path should be in upper trie + let child_path = Nibbles::from_nibbles([0x1, 0x2]); + assert_eq!(trie.upper_subtrie.nodes.get(&child_path), Some(&SparseNode::Hash(child_hash))); + } + + #[test] + fn reveal_node_extension_cross_level() { + let mut trie = ParallelSparseTrie::default(); + let path = Nibbles::from_nibbles([0x1, 0x2]); + let child_hash = B256::repeat_byte(0xcd); + let node = create_extension_node(&[0x3], child_hash); + let masks = TrieMasks::none(); + + trie.reveal_node(path.clone(), node, masks).unwrap(); + + // Extension node should be in upper trie + assert_matches!( + trie.upper_subtrie.nodes.get(&path), + Some(SparseNode::Extension { key, hash: None, .. }) + if key == &Nibbles::from_nibbles([0x3]) + ); + + // Child path (0x1, 0x2, 0x3) should be in lower trie + let child_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); + let idx = path_subtrie_index_unchecked(&child_path); + assert!(trie.lower_subtries[idx].is_some()); + + let lower_subtrie = trie.lower_subtries[idx].as_ref().unwrap(); + assert_eq!(lower_subtrie.nodes.get(&child_path), Some(&SparseNode::Hash(child_hash))); + } + + #[test] + fn reveal_node_branch_all_upper() { + let mut trie = ParallelSparseTrie::default(); + let path = Nibbles::from_nibbles([0x1]); + let child_hashes = [B256::repeat_byte(0x11), B256::repeat_byte(0x22)]; + let node = create_branch_node_with_children(&[0x0, 0x5], &child_hashes); + let masks = TrieMasks::none(); + + trie.reveal_node(path.clone(), node, masks).unwrap(); + + // Branch node should be in upper trie + assert_matches!( + trie.upper_subtrie.nodes.get(&path), + Some(SparseNode::Branch { state_mask, hash: None, .. }) + if *state_mask == 0b0000000000100001.into() + ); + + // Children should be in upper trie (paths of length 2) + let child_path_0 = Nibbles::from_nibbles([0x1, 0x0]); + let child_path_5 = Nibbles::from_nibbles([0x1, 0x5]); + assert_eq!( + trie.upper_subtrie.nodes.get(&child_path_0), + Some(&SparseNode::Hash(child_hashes[0])) + ); + assert_eq!( + trie.upper_subtrie.nodes.get(&child_path_5), + Some(&SparseNode::Hash(child_hashes[1])) + ); + } + + #[test] + fn reveal_node_branch_cross_level() { + let mut trie = ParallelSparseTrie::default(); + let path = Nibbles::from_nibbles([0x1, 0x2]); // Exactly 2 nibbles - boundary case + let child_hashes = + [B256::repeat_byte(0x33), B256::repeat_byte(0x44), B256::repeat_byte(0x55)]; + let node = create_branch_node_with_children(&[0x0, 0x7, 0xf], &child_hashes); + let masks = TrieMasks::none(); + + trie.reveal_node(path.clone(), node, masks).unwrap(); + + // Branch node should be in upper trie + assert_matches!( + trie.upper_subtrie.nodes.get(&path), + Some(SparseNode::Branch { state_mask, hash: None, .. }) + if *state_mask == 0b1000000010000001.into() + ); + + // All children should be in lower tries since they have paths of length 3 + let child_paths = [ + Nibbles::from_nibbles([0x1, 0x2, 0x0]), + Nibbles::from_nibbles([0x1, 0x2, 0x7]), + Nibbles::from_nibbles([0x1, 0x2, 0xf]), + ]; + + for (i, child_path) in child_paths.iter().enumerate() { + let idx = path_subtrie_index_unchecked(child_path); + let lower_subtrie = trie.lower_subtries[idx].as_ref().unwrap(); + assert_eq!( + lower_subtrie.nodes.get(child_path), + Some(&SparseNode::Hash(child_hashes[i])), + ); + } + } } From 6aa73f14808491aae77fc7c6eb4f0aa63bef7e6e Mon Sep 17 00:00:00 2001 From: Hai | RISE <150876604+hai-rise@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:58:57 +0700 Subject: [PATCH 133/274] feat: require only account & bytecode reader for tx validation (#16930) --- crates/optimism/txpool/src/validator.rs | 4 ++-- crates/storage/storage-api/src/state.rs | 4 ++++ crates/transaction-pool/src/validate/eth.rs | 10 +++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/optimism/txpool/src/validator.rs b/crates/optimism/txpool/src/validator.rs index cd9b74f1ddf..6c986e9498f 100644 --- a/crates/optimism/txpool/src/validator.rs +++ b/crates/optimism/txpool/src/validator.rs @@ -8,7 +8,7 @@ use reth_optimism_forks::OpHardforks; use reth_primitives_traits::{ transaction::error::InvalidTransactionError, Block, BlockBody, GotExpected, SealedBlock, }; -use reth_storage_api::{BlockReaderIdExt, StateProvider, StateProviderFactory}; +use reth_storage_api::{AccountInfoReader, BlockReaderIdExt, StateProviderFactory}; use reth_transaction_pool::{ error::InvalidPoolTransactionError, EthPoolTransaction, EthTransactionValidator, TransactionOrigin, TransactionValidationOutcome, TransactionValidator, @@ -181,7 +181,7 @@ where &self, origin: TransactionOrigin, transaction: Tx, - state: &mut Option>, + state: &mut Option>, ) -> TransactionValidationOutcome { if transaction.is_eip4844() { return TransactionValidationOutcome::Invalid( diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index f70a3b34c41..5581248d3eb 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -92,6 +92,10 @@ pub trait StateProvider: } } +/// Minimal requirements to read a full account, for example, to validate its new transactions +pub trait AccountInfoReader: AccountReader + BytecodeReader {} +impl AccountInfoReader for T {} + /// Trait implemented for database providers that can provide the [`reth_trie_db::StateCommitment`] /// type. #[cfg(feature = "db-api")] diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 0c62412c184..7a6dbd06589 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -28,7 +28,7 @@ use reth_primitives_traits::{ constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Block, GotExpected, SealedBlock, }; -use reth_storage_api::{StateProvider, StateProviderFactory}; +use reth_storage_api::{AccountInfoReader, StateProviderFactory}; use reth_tasks::TaskSpawner; use std::{ marker::PhantomData, @@ -89,7 +89,7 @@ where &self, origin: TransactionOrigin, transaction: Tx, - state: &mut Option>, + state: &mut Option>, ) -> TransactionValidationOutcome { self.inner.validate_one_with_provider(origin, transaction, state) } @@ -207,7 +207,7 @@ where &self, origin: TransactionOrigin, transaction: Tx, - maybe_state: &mut Option>, + maybe_state: &mut Option>, ) -> TransactionValidationOutcome { match self.validate_one_no_state(origin, transaction) { Ok(transaction) => { @@ -216,7 +216,7 @@ where if maybe_state.is_none() { match self.client.latest() { Ok(new_state) => { - *maybe_state = Some(new_state); + *maybe_state = Some(Box::new(new_state)); } Err(err) => { return TransactionValidationOutcome::Error( @@ -456,7 +456,7 @@ where state: P, ) -> TransactionValidationOutcome where - P: StateProvider, + P: AccountInfoReader, { // Use provider to get account info let account = match state.basic_account(transaction.sender_ref()) { From 4be22262352480539ba342d601f57681571f0953 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 19 Jun 2025 15:52:05 +0200 Subject: [PATCH 134/274] perf: Reuse CachedPrecompileMetrics across block executions (#16944) --- crates/engine/tree/src/tree/mod.rs | 7 +++- .../src/tree/payload_processor/prewarm.rs | 1 + .../engine/tree/src/tree/precompile_cache.rs | 38 ++++++++++++++----- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 982a8f5418e..a6816d29312 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -18,7 +18,7 @@ use error::{InsertBlockError, InsertBlockErrorKind, InsertBlockFatalError}; use instrumented_state::InstrumentedStateProvider; use payload_processor::sparse_trie::StateRootComputeOutcome; use persistence_state::CurrentPersistenceAction; -use precompile_cache::{CachedPrecompile, PrecompileCacheMap}; +use precompile_cache::{CachedPrecompile, CachedPrecompileMetrics, PrecompileCacheMap}; use reth_chain_state::{ CanonicalInMemoryState, ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates, MemoryOverlayStateProvider, NewCanonicalChain, @@ -276,6 +276,9 @@ where evm_config: C, /// Precompile cache map. precompile_cache_map: PrecompileCacheMap>, + /// Metrics for precompile cache, saved between block executions so we don't re-allocate for + /// every block. + precompile_cache_metrics: CachedPrecompileMetrics, } impl std::fmt::Debug @@ -370,6 +373,7 @@ where payload_processor, evm_config, precompile_cache_map, + precompile_cache_metrics: Default::default(), } } @@ -2443,6 +2447,7 @@ where precompile, self.precompile_cache_map.cache_for_address(*address), *self.evm_config.evm_env(block.header()).spec_id(), + Some(self.precompile_cache_metrics.clone()), ) }); } diff --git a/crates/engine/tree/src/tree/payload_processor/prewarm.rs b/crates/engine/tree/src/tree/payload_processor/prewarm.rs index 2153f6ee753..c69df3172fe 100644 --- a/crates/engine/tree/src/tree/payload_processor/prewarm.rs +++ b/crates/engine/tree/src/tree/payload_processor/prewarm.rs @@ -275,6 +275,7 @@ where precompile, precompile_cache_map.cache_for_address(*address), spec_id, + None, // CachedPrecompileMetrics ) }); } diff --git a/crates/engine/tree/src/tree/precompile_cache.rs b/crates/engine/tree/src/tree/precompile_cache.rs index 47d985a9296..066873d2c72 100644 --- a/crates/engine/tree/src/tree/precompile_cache.rs +++ b/crates/engine/tree/src/tree/precompile_cache.rs @@ -123,7 +123,7 @@ where /// The precompile. precompile: DynPrecompile, /// Cache metrics. - metrics: CachedPrecompileMetrics, + metrics: Option, /// Spec id associated to the EVM from which this cached precompile was created. spec_id: S, } @@ -133,30 +133,48 @@ where S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static, { /// `CachedPrecompile` constructor. - pub(crate) fn new(precompile: DynPrecompile, cache: PrecompileCache, spec_id: S) -> Self { - Self { precompile, cache, spec_id, metrics: Default::default() } + pub(crate) const fn new( + precompile: DynPrecompile, + cache: PrecompileCache, + spec_id: S, + metrics: Option, + ) -> Self { + Self { precompile, cache, spec_id, metrics } } pub(crate) fn wrap( precompile: DynPrecompile, cache: PrecompileCache, spec_id: S, + metrics: Option, ) -> DynPrecompile { - let wrapped = Self::new(precompile, cache, spec_id); + let wrapped = Self::new(precompile, cache, spec_id, metrics); move |data: &[u8], gas_limit: u64| -> PrecompileResult { wrapped.call(data, gas_limit) } .into() } fn increment_by_one_precompile_cache_hits(&self) { - self.metrics.precompile_cache_hits.increment(1); + if let Some(metrics) = &self.metrics { + metrics.precompile_cache_hits.increment(1); + } } fn increment_by_one_precompile_cache_misses(&self) { - self.metrics.precompile_cache_misses.increment(1); + if let Some(metrics) = &self.metrics { + metrics.precompile_cache_misses.increment(1); + } + } + + fn set_precompile_cache_size_metric(&self, to: f64) { + if let Some(metrics) = &self.metrics { + metrics.precompile_cache_size.set(to); + } } fn increment_by_one_precompile_errors(&self) { - self.metrics.precompile_errors.increment(1); + if let Some(metrics) = &self.metrics { + metrics.precompile_errors.increment(1); + } } } @@ -180,7 +198,7 @@ where Ok(output) => { let key = CacheKey::new(self.spec_id.clone(), Bytes::copy_from_slice(data)); let size = self.cache.insert(key, CacheEntry(output.clone())); - self.metrics.precompile_cache_size.set(size as f64); + self.set_precompile_cache_size_metric(size as f64); self.increment_by_one_precompile_cache_misses(); } _ => { @@ -241,7 +259,7 @@ mod tests { .into(); let cache = - CachedPrecompile::new(dyn_precompile, PrecompileCache::default(), SpecId::PRAGUE); + CachedPrecompile::new(dyn_precompile, PrecompileCache::default(), SpecId::PRAGUE, None); let output = PrecompileOutput { gas_used: 50, @@ -298,11 +316,13 @@ mod tests { precompile1, cache_map.cache_for_address(address1), SpecId::PRAGUE, + None, ); let wrapped_precompile2 = CachedPrecompile::wrap( precompile2, cache_map.cache_for_address(address2), SpecId::PRAGUE, + None, ); // first invocation of precompile1 (cache miss) From 0288a2d14dd73db35a974cf3c4fd50bf5dae41f6 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Thu, 19 Jun 2025 14:54:47 +0100 Subject: [PATCH 135/274] bench(trie): prepare trie outside of routine, use large input size (#16945) --- .github/workflows/pr-title.yml | 8 ++++-- crates/trie/sparse/benches/update.rs | 42 +++++++++++++++------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 7977c11d8bb..1d422ad45cf 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -30,6 +30,7 @@ jobs: fix chore test + bench perf refactor docs @@ -55,23 +56,24 @@ jobs: - `fix`: Patches a bug - `chore`: General maintenance tasks or updates - `test`: Adding new tests or modifying existing tests + - `bench`: Adding new benchmarks or modifying existing benchmarks - `perf`: Performance improvements - `refactor`: Changes to improve code structure - `docs`: Documentation updates - `ci`: Changes to CI/CD configurations - `revert`: Reverts a previously merged PR - `deps`: Updates dependencies - + **Breaking Changes** Breaking changes are noted by using an exclamation mark. For example: - `feat!: changed the API` - `chore(node)!: Removed unused public function` - + **Help** For more information, follow the guidelines here: https://www.conventionalcommits.org/en/v1.0.0/ - + - name: Remove Comment for Valid Title if: steps.lint_pr_title.outcome == 'success' uses: marocchino/sticky-pull-request-comment@v2 diff --git a/crates/trie/sparse/benches/update.rs b/crates/trie/sparse/benches/update.rs index efb4c5a410c..1b3ce121105 100644 --- a/crates/trie/sparse/benches/update.rs +++ b/crates/trie/sparse/benches/update.rs @@ -1,13 +1,13 @@ #![allow(missing_docs)] use alloy_primitives::{B256, U256}; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use proptest::{prelude::*, strategy::ValueTree}; use rand::seq::IteratorRandom; use reth_trie_common::Nibbles; use reth_trie_sparse::SparseTrie; -const LEAF_COUNTS: [usize; 3] = [100, 1_000, 5_000]; +const LEAF_COUNTS: [usize; 2] = [1_000, 5_000]; fn update_leaf(c: &mut Criterion) { let mut group = c.benchmark_group("update_leaf"); @@ -15,15 +15,15 @@ fn update_leaf(c: &mut Criterion) { for leaf_count in LEAF_COUNTS { group.bench_function(BenchmarkId::from_parameter(leaf_count), |b| { let leaves = generate_leaves(leaf_count); - b.iter_with_setup( - || { - // Start with an empty trie - let mut trie = SparseTrie::revealed_empty(); - // Pre-populate with data - for (path, value) in &leaves { - trie.update_leaf(path.clone(), value.clone()).unwrap(); - } + // Start with an empty trie + let mut trie = SparseTrie::revealed_empty(); + // Pre-populate with data + for (path, value) in leaves.iter().cloned() { + trie.update_leaf(path, value).unwrap(); + } + b.iter_batched( + || { let new_leaves = leaves .iter() // Update 10% of existing leaves with new values @@ -37,7 +37,7 @@ fn update_leaf(c: &mut Criterion) { }) .collect::>(); - (trie, new_leaves) + (trie.clone(), new_leaves) }, |(mut trie, new_leaves)| { for (path, new_value) in new_leaves { @@ -45,6 +45,7 @@ fn update_leaf(c: &mut Criterion) { } trie }, + BatchSize::LargeInput, ); }); } @@ -56,22 +57,22 @@ fn remove_leaf(c: &mut Criterion) { for leaf_count in LEAF_COUNTS { group.bench_function(BenchmarkId::from_parameter(leaf_count), |b| { let leaves = generate_leaves(leaf_count); - b.iter_with_setup( - || { - // Start with an empty trie - let mut trie = SparseTrie::revealed_empty(); - // Pre-populate with data - for (path, value) in &leaves { - trie.update_leaf(path.clone(), value.clone()).unwrap(); - } + // Start with an empty trie + let mut trie = SparseTrie::revealed_empty(); + // Pre-populate with data + for (path, value) in leaves.iter().cloned() { + trie.update_leaf(path, value).unwrap(); + } + b.iter_batched( + || { let delete_leaves = leaves .iter() .map(|(path, _)| path) // Remove 10% leaves .choose_multiple(&mut rand::rng(), leaf_count / 10); - (trie, delete_leaves) + (trie.clone(), delete_leaves) }, |(mut trie, delete_leaves)| { for path in delete_leaves { @@ -79,6 +80,7 @@ fn remove_leaf(c: &mut Criterion) { } trie }, + BatchSize::LargeInput, ); }); } From 54cd8b34a40d1c22c5c00bb49ec4eb855ae77690 Mon Sep 17 00:00:00 2001 From: nekomoto911 Date: Thu, 19 Jun 2025 22:14:07 +0800 Subject: [PATCH 136/274] perf: Reduce unnecessary MDBX transaction creation when constructing StateProvider (#16884) --- .../src/providers/blockchain_provider.rs | 12 ++-------- .../provider/src/providers/consistent.rs | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index b7b31cd4937..5ee86e8cd73 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -526,20 +526,12 @@ impl StateProviderFactory for BlockchainProvider { let hash = provider .block_hash(block_number)? .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; - self.history_by_block_hash(hash) + provider.into_state_provider_at_block_hash(hash) } fn history_by_block_hash(&self, block_hash: BlockHash) -> ProviderResult { trace!(target: "providers::blockchain", ?block_hash, "Getting history by block hash"); - - self.consistent_provider()?.get_in_memory_or_storage_by_block( - block_hash.into(), - |_| self.database.history_by_block_hash(block_hash), - |block_state| { - let state_provider = self.block_state_provider(block_state)?; - Ok(Box::new(state_provider)) - }, - ) + self.consistent_provider()?.into_state_provider_at_block_hash(block_hash) } fn state_by_block_hash(&self, hash: BlockHash) -> ProviderResult { diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index bb24cddb34b..3922e286c29 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -27,7 +27,7 @@ use reth_prune_types::{PruneCheckpoint, PruneSegment}; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ BlockBodyIndicesProvider, DatabaseProviderFactory, NodePrimitivesProvider, StateProvider, - StorageChangeSetReader, + StorageChangeSetReader, TryIntoHistoricalStateProvider, }; use reth_storage_errors::provider::ProviderResult; use revm_database::states::PlainStorageRevert; @@ -591,6 +591,28 @@ impl ConsistentProvider { } fetch_from_db(&self.storage_provider) } + + /// Consumes the provider and returns a state provider for the specific block hash. + pub(crate) fn into_state_provider_at_block_hash( + self, + block_hash: BlockHash, + ) -> ProviderResult> { + let Self { storage_provider, head_block, .. } = self; + let into_history_at_block_hash = |block_hash| -> ProviderResult> { + let block_number = storage_provider + .block_number(block_hash)? + .ok_or(ProviderError::BlockHashNotFound(block_hash))?; + storage_provider.try_into_history_at_block(block_number) + }; + if let Some(Some(block_state)) = + head_block.as_ref().map(|b| b.block_on_chain(block_hash.into())) + { + let anchor_hash = block_state.anchor().hash; + let latest_historical = into_history_at_block_hash(anchor_hash)?; + return Ok(Box::new(block_state.state_provider(latest_historical))); + } + into_history_at_block_hash(block_hash) + } } impl ConsistentProvider { From ad681775084da071749d45e12a6bbbaeaff90a5b Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:39:05 +0100 Subject: [PATCH 137/274] chore: move parallel sparse trie to its own crate (#16950) --- .github/assets/check_wasm.sh | 1 + Cargo.lock | 21 +++++++++++ Cargo.toml | 2 + crates/trie/sparse-parallel/Cargo.toml | 37 +++++++++++++++++++ crates/trie/sparse-parallel/src/lib.rs | 6 +++ .../src/trie.rs} | 9 ++--- crates/trie/sparse/src/lib.rs | 3 -- 7 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 crates/trie/sparse-parallel/Cargo.toml create mode 100644 crates/trie/sparse-parallel/src/lib.rs rename crates/trie/{sparse/src/parallel_trie.rs => sparse-parallel/src/trie.rs} (99%) diff --git a/.github/assets/check_wasm.sh b/.github/assets/check_wasm.sh index c5639c710d2..481bed3c0a3 100755 --- a/.github/assets/check_wasm.sh +++ b/.github/assets/check_wasm.sh @@ -70,6 +70,7 @@ exclude_crates=( reth-transaction-pool # c-kzg reth-payload-util # reth-transaction-pool reth-trie-parallel # tokio + reth-trie-sparse-parallel # rayon reth-testing-utils reth-optimism-txpool # reth-transaction-pool reth-era-downloader # tokio diff --git a/Cargo.lock b/Cargo.lock index 92a8612a720..1092a24a6bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10568,6 +10568,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-trie-sparse-parallel" +version = "1.4.8" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "arbitrary", + "assert_matches", + "itertools 0.14.0", + "proptest", + "proptest-arbitrary-interop", + "rand 0.8.5", + "rand 0.9.1", + "reth-execution-errors", + "reth-primitives-traits", + "reth-trie-common", + "reth-trie-sparse", + "tracing", +] + [[package]] name = "reth-zstd-compressors" version = "1.4.8" diff --git a/Cargo.toml b/Cargo.toml index 7e2c4a8b1dc..c4c16514254 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,6 +134,7 @@ members = [ "crates/trie/db", "crates/trie/parallel/", "crates/trie/sparse", + "crates/trie/sparse-parallel/", "crates/trie/trie", "examples/beacon-api-sidecar-fetcher/", "examples/beacon-api-sse/", @@ -443,6 +444,7 @@ reth-trie-common = { path = "crates/trie/common", default-features = false } reth-trie-db = { path = "crates/trie/db" } reth-trie-parallel = { path = "crates/trie/parallel" } reth-trie-sparse = { path = "crates/trie/sparse", default-features = false } +reth-trie-sparse-parallel = { path = "crates/trie/sparse-parallel" } reth-zstd-compressors = { path = "crates/storage/zstd-compressors", default-features = false } reth-ress-protocol = { path = "crates/ress/protocol" } reth-ress-provider = { path = "crates/ress/provider" } diff --git a/crates/trie/sparse-parallel/Cargo.toml b/crates/trie/sparse-parallel/Cargo.toml new file mode 100644 index 00000000000..fd425c1dbfb --- /dev/null +++ b/crates/trie/sparse-parallel/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "reth-trie-sparse-parallel" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Parallel Sparse MPT implementation" + +[lints] +workspace = true + +[dependencies] +# reth +reth-execution-errors.workspace = true +reth-trie-common.workspace = true +reth-trie-sparse.workspace = true +tracing.workspace = true +alloy-trie.workspace = true + +# alloy +alloy-primitives.workspace = true +alloy-rlp.workspace = true + +[dev-dependencies] +# reth +reth-primitives-traits.workspace = true +reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } + +arbitrary.workspace = true +assert_matches.workspace = true +itertools.workspace = true +proptest-arbitrary-interop.workspace = true +proptest.workspace = true +rand.workspace = true +rand_08.workspace = true diff --git a/crates/trie/sparse-parallel/src/lib.rs b/crates/trie/sparse-parallel/src/lib.rs new file mode 100644 index 00000000000..6a8a7048930 --- /dev/null +++ b/crates/trie/sparse-parallel/src/lib.rs @@ -0,0 +1,6 @@ +//! The implementation of parallel sparse MPT. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +mod trie; +pub use trie::*; diff --git a/crates/trie/sparse/src/parallel_trie.rs b/crates/trie/sparse-parallel/src/trie.rs similarity index 99% rename from crates/trie/sparse/src/parallel_trie.rs rename to crates/trie/sparse-parallel/src/trie.rs index 0e7a97efacc..c3a5a934af1 100644 --- a/crates/trie/sparse/src/parallel_trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -1,5 +1,3 @@ -use crate::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; -use alloc::{boxed::Box, vec::Vec}; use alloy_primitives::{ map::{Entry, HashMap}, B256, @@ -11,6 +9,7 @@ use reth_trie_common::{ prefix_set::{PrefixSet, PrefixSetMut}, Nibbles, TrieNode, CHILD_INDEX_RANGE, }; +use reth_trie_sparse::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; use tracing::trace; /// A revealed sparse trie with subtries that can be updated in parallel. @@ -532,9 +531,8 @@ fn path_subtrie_index_unchecked(path: &Nibbles) -> usize { #[cfg(test)] mod tests { - use crate::{ - parallel_trie::{path_subtrie_index_unchecked, SparseSubtrieType}, - ParallelSparseTrie, SparseNode, SparseSubtrie, TrieMasks, + use super::{ + path_subtrie_index_unchecked, ParallelSparseTrie, SparseSubtrie, SparseSubtrieType, }; use alloy_primitives::B256; use alloy_rlp::Encodable; @@ -545,6 +543,7 @@ mod tests { prefix_set::{PrefixSet, PrefixSetMut}, BranchNode, ExtensionNode, LeafNode, RlpNode, TrieMask, TrieNode, EMPTY_ROOT_HASH, }; + use reth_trie_sparse::{SparseNode, TrieMasks}; // Test helpers fn encode_account_value(nonce: u64) -> Vec { diff --git a/crates/trie/sparse/src/lib.rs b/crates/trie/sparse/src/lib.rs index c6b31bdb74f..617622d194f 100644 --- a/crates/trie/sparse/src/lib.rs +++ b/crates/trie/sparse/src/lib.rs @@ -11,9 +11,6 @@ pub use state::*; mod trie; pub use trie::*; -mod parallel_trie; -pub use parallel_trie::*; - pub mod blinded; #[cfg(feature = "metrics")] From f59a82e4c627cd0fa93b40321f29b7f98799ae92 Mon Sep 17 00:00:00 2001 From: Shane K Moore <41407272+shane-moore@users.noreply.github.com> Date: Thu, 19 Jun 2025 11:46:34 -0700 Subject: [PATCH 138/274] chore: add node synced helper (#16928) --- crates/rpc/rpc/src/eth/helpers/mod.rs | 3 + .../rpc/rpc/src/eth/helpers/sync_listener.rs | 133 ++++++++++++++++++ crates/rpc/rpc/src/eth/mod.rs | 2 +- crates/rpc/rpc/src/lib.rs | 2 +- 4 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 crates/rpc/rpc/src/eth/helpers/sync_listener.rs diff --git a/crates/rpc/rpc/src/eth/helpers/mod.rs b/crates/rpc/rpc/src/eth/helpers/mod.rs index 03e0443a15b..15fcf612d9a 100644 --- a/crates/rpc/rpc/src/eth/helpers/mod.rs +++ b/crates/rpc/rpc/src/eth/helpers/mod.rs @@ -2,6 +2,7 @@ //! files. pub mod signer; +pub mod sync_listener; pub mod types; mod block; @@ -13,3 +14,5 @@ mod spec; mod state; mod trace; mod transaction; + +pub use sync_listener::SyncListener; diff --git a/crates/rpc/rpc/src/eth/helpers/sync_listener.rs b/crates/rpc/rpc/src/eth/helpers/sync_listener.rs new file mode 100644 index 00000000000..13c8de19b0d --- /dev/null +++ b/crates/rpc/rpc/src/eth/helpers/sync_listener.rs @@ -0,0 +1,133 @@ +//! A utility Future to asynchronously wait until a node has finished syncing. + +use futures::Stream; +use pin_project::pin_project; +use reth_network_api::NetworkInfo; +use std::{ + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; + +/// This future resolves once the node is no longer syncing: [`NetworkInfo::is_syncing`]. +#[must_use = "futures do nothing unless polled"] +#[pin_project] +#[derive(Debug)] +pub struct SyncListener { + #[pin] + tick: St, + network_info: N, +} + +impl SyncListener { + /// Create a new [`SyncListener`] using the given tick stream. + pub const fn new(network_info: N, tick: St) -> Self { + Self { tick, network_info } + } +} + +impl Future for SyncListener +where + N: NetworkInfo, + St: Stream + Unpin, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + if !this.network_info.is_syncing() { + return Poll::Ready(()); + } + + loop { + let tick_event = ready!(this.tick.as_mut().poll_next(cx)); + + match tick_event { + Some(_) => { + if !this.network_info.is_syncing() { + return Poll::Ready(()); + } + } + None => return Poll::Ready(()), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rpc_types_admin::EthProtocolInfo; + use futures::stream; + use reth_network_api::{NetworkError, NetworkStatus}; + use std::{ + net::{IpAddr, SocketAddr}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + }; + + #[derive(Clone)] + struct TestNetwork { + syncing: Arc, + } + + impl NetworkInfo for TestNetwork { + fn local_addr(&self) -> SocketAddr { + (IpAddr::from([0, 0, 0, 0]), 0).into() + } + + async fn network_status(&self) -> Result { + #[allow(deprecated)] + Ok(NetworkStatus { + client_version: "test".to_string(), + protocol_version: 5, + eth_protocol_info: EthProtocolInfo { + network: 1, + difficulty: None, + genesis: Default::default(), + config: Default::default(), + head: Default::default(), + }, + }) + } + + fn chain_id(&self) -> u64 { + 1 + } + + fn is_syncing(&self) -> bool { + self.syncing.load(Ordering::SeqCst) + } + + fn is_initially_syncing(&self) -> bool { + self.is_syncing() + } + } + + #[tokio::test] + async fn completes_immediately_if_not_syncing() { + let network = TestNetwork { syncing: Arc::new(AtomicBool::new(false)) }; + let fut = SyncListener::new(network, stream::pending::<()>()); + fut.await; + } + + #[tokio::test] + async fn resolves_when_syncing_stops() { + use tokio::sync::mpsc::unbounded_channel; + use tokio_stream::wrappers::UnboundedReceiverStream; + + let syncing = Arc::new(AtomicBool::new(true)); + let network = TestNetwork { syncing: syncing.clone() }; + let (tx, rx) = unbounded_channel(); + let listener = SyncListener::new(network, UnboundedReceiverStream::new(rx)); + let handle = tokio::spawn(listener); + + syncing.store(false, Ordering::Relaxed); + let _ = tx.send(()); + + handle.await.unwrap(); + } +} diff --git a/crates/rpc/rpc/src/eth/mod.rs b/crates/rpc/rpc/src/eth/mod.rs index b4dca3b9f2b..af8619de867 100644 --- a/crates/rpc/rpc/src/eth/mod.rs +++ b/crates/rpc/rpc/src/eth/mod.rs @@ -15,6 +15,6 @@ pub use core::{EthApi, EthApiFor}; pub use filter::EthFilter; pub use pubsub::EthPubSub; -pub use helpers::signer::DevSigner; +pub use helpers::{signer::DevSigner, sync_listener::SyncListener}; pub use reth_rpc_eth_api::{EthApiServer, EthApiTypes, FullEthApiServer, RpcNodeCore}; diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index bac57b63035..690fb33e871 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -49,7 +49,7 @@ mod web3; pub use admin::AdminApi; pub use debug::DebugApi; pub use engine::{EngineApi, EngineEthApi}; -pub use eth::{EthApi, EthApiBuilder, EthBundle, EthFilter, EthPubSub}; +pub use eth::{helpers::SyncListener, EthApi, EthApiBuilder, EthBundle, EthFilter, EthPubSub}; pub use miner::MinerApi; pub use net::NetApi; pub use otterscan::OtterscanApi; From 9231652c6cc94a8ac26d0b7015dfe0d673bdaa54 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:29:06 +0100 Subject: [PATCH 139/274] perf(trie): `ParallelSparseTrie::update_subtrie_hashes` boilerplate (#16948) --- Cargo.lock | 1 + crates/trie/sparse-parallel/Cargo.toml | 3 + crates/trie/sparse-parallel/src/trie.rs | 289 +++++++++++++++++++----- crates/trie/sparse/src/trie.rs | 29 ++- 4 files changed, 251 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1092a24a6bc..20e79650468 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10586,6 +10586,7 @@ dependencies = [ "reth-primitives-traits", "reth-trie-common", "reth-trie-sparse", + "smallvec", "tracing", ] diff --git a/crates/trie/sparse-parallel/Cargo.toml b/crates/trie/sparse-parallel/Cargo.toml index fd425c1dbfb..4b6571f80e5 100644 --- a/crates/trie/sparse-parallel/Cargo.toml +++ b/crates/trie/sparse-parallel/Cargo.toml @@ -23,6 +23,9 @@ alloy-trie.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true +# misc +smallvec.workspace = true + [dev-dependencies] # reth reth-primitives-traits.workspace = true diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index c3a5a934af1..411f4275c8d 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -1,3 +1,5 @@ +use std::sync::mpsc; + use alloy_primitives::{ map::{Entry, HashMap}, B256, @@ -7,9 +9,13 @@ use alloy_trie::TrieMask; use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult}; use reth_trie_common::{ prefix_set::{PrefixSet, PrefixSetMut}, - Nibbles, TrieNode, CHILD_INDEX_RANGE, + Nibbles, RlpNode, TrieNode, CHILD_INDEX_RANGE, +}; +use reth_trie_sparse::{ + blinded::BlindedProvider, RlpNodePathStackItem, RlpNodeStackItem, SparseNode, + SparseTrieUpdates, TrieMasks, }; -use reth_trie_sparse::{blinded::BlindedProvider, SparseNode, SparseTrieUpdates, TrieMasks}; +use smallvec::SmallVec; use tracing::trace; /// A revealed sparse trie with subtries that can be updated in parallel. @@ -22,9 +28,12 @@ use tracing::trace; #[derive(Clone, PartialEq, Eq, Debug)] pub struct ParallelSparseTrie { /// This contains the trie nodes for the upper part of the trie. - upper_subtrie: SparseSubtrie, + upper_subtrie: Box, /// An array containing the subtries at the second level of the trie. - lower_subtries: Box<[Option; 256]>, + lower_subtries: [Option>; 256], + /// Set of prefixes (key paths) that have been marked as updated. + /// This is used to track which parts of the trie need to be recalculated. + prefix_set: PrefixSetMut, /// Optional tracking of trie updates for later use. updates: Option, } @@ -32,8 +41,9 @@ pub struct ParallelSparseTrie { impl Default for ParallelSparseTrie { fn default() -> Self { Self { - upper_subtrie: SparseSubtrie::default(), - lower_subtries: Box::new([const { None }; 256]), + upper_subtrie: Box::default(), + lower_subtries: [const { None }; 256], + prefix_set: PrefixSetMut::default(), updates: None, } } @@ -42,13 +52,13 @@ impl Default for ParallelSparseTrie { impl ParallelSparseTrie { /// Returns mutable ref to the lower `SparseSubtrie` for the given path, or None if the path /// belongs to the upper trie. - fn lower_subtrie_for_path(&mut self, path: &Nibbles) -> Option<&mut SparseSubtrie> { + fn lower_subtrie_for_path(&mut self, path: &Nibbles) -> Option<&mut Box> { match SparseSubtrieType::from_path(path) { SparseSubtrieType::Upper => None, SparseSubtrieType::Lower(idx) => { if self.lower_subtries[idx].is_none() { let upper_path = path.slice(..2); - self.lower_subtries[idx] = Some(SparseSubtrie::new(upper_path)); + self.lower_subtries[idx] = Some(Box::new(SparseSubtrie::new(upper_path))); } self.lower_subtries[idx].as_mut() @@ -185,9 +195,28 @@ impl ParallelSparseTrie { /// /// This function first identifies all nodes that have changed (based on the prefix set) below /// level 2 of the trie, then recalculates their RLP representation. - pub fn update_subtrie_hashes(&mut self) -> SparseTrieResult<()> { + pub fn update_subtrie_hashes(&mut self) { trace!(target: "trie::parallel_sparse", "Updating subtrie hashes"); - todo!() + + // Take changed subtries according to the prefix set + let mut prefix_set = core::mem::take(&mut self.prefix_set).freeze(); + let (subtries, unchanged_prefix_set) = self.take_changed_lower_subtries(&mut prefix_set); + + // Update the prefix set with the keys that didn't have matching subtries + self.prefix_set = unchanged_prefix_set; + + // Update subtrie hashes in parallel + // TODO: call `update_hashes` on each subtrie in parallel + let (tx, rx) = mpsc::channel(); + for subtrie in subtries { + tx.send((subtrie.index, subtrie.subtrie)).unwrap(); + } + drop(tx); + + // Return updated subtries back to the trie + for (index, subtrie) in rx { + self.lower_subtries[index] = Some(subtrie); + } } /// Calculates and returns the root hash of the trie. @@ -206,53 +235,82 @@ impl ParallelSparseTrie { /// If `retain_updates` is true, the trie will record branch node updates and deletions. /// This information can then be used to efficiently update an external database. pub fn with_updates(mut self, retain_updates: bool) -> Self { - if retain_updates { - self.updates = Some(SparseTrieUpdates::default()); - } + self.updates = retain_updates.then_some(SparseTrieUpdates::default()); self } - /// Returns a list of [subtries](SparseSubtrie) identifying the subtries that have changed - /// according to the provided [prefix set](PrefixSet). + /// Consumes and returns the currently accumulated trie updates. /// - /// Along with the subtries, prefix sets are returned. Each prefix set contains the keys from - /// the original prefix set that belong to the subtrie. + /// This is useful when you want to apply the updates to an external database, + /// and then start tracking a new set of updates. + pub fn take_updates(&mut self) -> SparseTrieUpdates { + core::iter::once(&mut self.upper_subtrie) + .chain(self.lower_subtries.iter_mut().flatten()) + .fold(SparseTrieUpdates::default(), |mut acc, subtrie| { + acc.extend(subtrie.take_updates()); + acc + }) + } + + /// Returns: + /// 1. List of lower [subtries](SparseSubtrie) that have changed according to the provided + /// [prefix set](PrefixSet). See documentation of [`ChangedSubtrie`] for more details. + /// 2. Prefix set of keys that do not belong to any lower subtrie. /// /// This method helps optimize hash recalculations by identifying which specific - /// subtries need to be updated. Each subtrie can then be updated in parallel. - #[allow(unused)] - fn get_changed_subtries( + /// lower subtries need to be updated. Each lower subtrie can then be updated in parallel. + /// + /// IMPORTANT: The method removes the subtries from `lower_subtries`, and the caller is + /// responsible for returning them back into the array. + fn take_changed_lower_subtries( &mut self, prefix_set: &mut PrefixSet, - ) -> Vec<(SparseSubtrie, PrefixSet)> { + ) -> (Vec, PrefixSetMut) { // Clone the prefix set to iterate over its keys. Cloning is cheap, it's just an Arc. let prefix_set_clone = prefix_set.clone(); - let mut prefix_set_iter = prefix_set_clone.into_iter(); + let mut prefix_set_iter = prefix_set_clone.into_iter().cloned().peekable(); + let mut changed_subtries = Vec::new(); + let mut unchanged_prefix_set = PrefixSetMut::default(); - let mut subtries = Vec::new(); - for subtrie in self.lower_subtries.iter_mut() { + for (index, subtrie) in self.lower_subtries.iter_mut().enumerate() { if let Some(subtrie) = subtrie.take_if(|subtrie| prefix_set.contains(&subtrie.path)) { let prefix_set = if prefix_set.all() { + unchanged_prefix_set = PrefixSetMut::all(); PrefixSetMut::all() } else { // Take those keys from the original prefix set that start with the subtrie path // // Subtries are stored in the order of their paths, so we can use the same // prefix set iterator. - PrefixSetMut::from( - prefix_set_iter - .by_ref() - .skip_while(|key| key < &&subtrie.path) - .take_while(|key| key.has_prefix(&subtrie.path)) - .cloned(), - ) + let mut new_prefix_set = Vec::new(); + while let Some(key) = prefix_set_iter.peek() { + if key.has_prefix(&subtrie.path) { + // If the key starts with the subtrie path, add it to the new prefix set + new_prefix_set.push(prefix_set_iter.next().unwrap()); + } else if new_prefix_set.is_empty() && key < &subtrie.path { + // If we didn't yet have any keys that belong to this subtrie, and the + // current key is still less than the subtrie path, add it to the + // unchanged prefix set + unchanged_prefix_set.insert(prefix_set_iter.next().unwrap()); + } else { + // If we're past the subtrie path, we're done with this subtrie. Do not + // advance the iterator, the next key will be processed either by the + // next subtrie or inserted into the unchanged prefix set. + break + } + } + PrefixSetMut::from(new_prefix_set) } .freeze(); - subtries.push((subtrie, prefix_set)); + changed_subtries.push(ChangedSubtrie { index, subtrie, prefix_set }); } } - subtries + + // Extend the unchanged prefix set with the remaining keys that are not part of any subtries + unchanged_prefix_set.extend_keys(prefix_set_iter); + + (changed_subtries, unchanged_prefix_set) } } @@ -274,6 +332,10 @@ pub struct SparseSubtrie { /// Map from leaf key paths to their values. /// All values are stored here instead of directly in leaf nodes. values: HashMap>, + /// Optional tracking of trie updates for later use. + updates: Option, + /// Reusable buffers for [`SparseSubtrie::update_hashes`]. + buffers: SparseSubtrieBuffers, } impl SparseSubtrie { @@ -281,6 +343,15 @@ impl SparseSubtrie { Self { path, ..Default::default() } } + /// Configures the subtrie to retain information about updates. + /// + /// If `retain_updates` is true, the trie will record branch node updates and deletions. + /// This information can then be used to efficiently update an external database. + pub fn with_updates(mut self, retain_updates: bool) -> Self { + self.updates = retain_updates.then_some(SparseTrieUpdates::default()); + self + } + /// Returns true if the current path and its child are both found in the same level. This /// function assumes that if `current_path` is in a lower level then `child_path` is too. fn is_child_same_level(current_path: &Nibbles, child_path: &Nibbles) -> bool { @@ -485,11 +556,19 @@ impl SparseSubtrie { } /// Recalculates and updates the RLP hashes for the changed nodes in this subtrie. - pub fn update_hashes(&mut self, prefix_set: &mut PrefixSet) -> SparseTrieResult<()> { + #[allow(unused)] + fn update_hashes(&self, _prefix_set: &mut PrefixSet) -> RlpNode { trace!(target: "trie::parallel_sparse", path=?self.path, "Updating subtrie hashes"); - let _prefix_set = prefix_set; todo!() } + + /// Consumes and returns the currently accumulated trie updates. + /// + /// This is useful when you want to apply the updates to an external database, + /// and then start tracking a new set of updates. + fn take_updates(&mut self) -> SparseTrieUpdates { + self.updates.take().unwrap_or_default() + } } /// Sparse Subtrie Type. @@ -518,6 +597,43 @@ impl SparseSubtrieType { Self::Lower(path_subtrie_index_unchecked(path)) } } + + /// Returns the index of the lower subtrie, if it exists. + pub const fn lower_index(&self) -> Option { + match self { + Self::Upper => None, + Self::Lower(index) => Some(*index), + } + } +} + +/// Collection of reusable buffers for calculating subtrie hashes. +/// +/// These buffers reduce allocations when computing RLP representations during trie updates. +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct SparseSubtrieBuffers { + /// Stack of RLP node paths + path_stack: Vec, + /// Stack of RLP nodes + rlp_node_stack: Vec, + /// Reusable branch child path + branch_child_buf: SmallVec<[Nibbles; 16]>, + /// Reusable branch value stack + branch_value_stack_buf: SmallVec<[RlpNode; 16]>, + /// Reusable RLP buffer + rlp_buf: Vec, +} + +/// Changed subtrie. +#[derive(Debug)] +struct ChangedSubtrie { + /// Lower subtrie index in the range [0, 255]. + index: usize, + /// Changed subtrie + subtrie: Box, + /// Prefix set of keys that belong to the subtrie. + #[allow(unused)] + prefix_set: PrefixSet, } /// Convert first two nibbles of the path into a lower subtrie index in the range [0, 255]. @@ -534,14 +650,15 @@ mod tests { use super::{ path_subtrie_index_unchecked, ParallelSparseTrie, SparseSubtrie, SparseSubtrieType, }; + use crate::trie::ChangedSubtrie; use alloy_primitives::B256; use alloy_rlp::Encodable; use alloy_trie::Nibbles; use assert_matches::assert_matches; use reth_primitives_traits::Account; use reth_trie_common::{ - prefix_set::{PrefixSet, PrefixSetMut}, - BranchNode, ExtensionNode, LeafNode, RlpNode, TrieMask, TrieNode, EMPTY_ROOT_HASH, + prefix_set::PrefixSetMut, BranchNode, ExtensionNode, LeafNode, RlpNode, TrieMask, TrieNode, + EMPTY_ROOT_HASH, }; use reth_trie_sparse::{SparseNode, TrieMasks}; @@ -586,21 +703,22 @@ mod tests { #[test] fn test_get_changed_subtries_empty() { let mut trie = ParallelSparseTrie::default(); - let mut prefix_set = PrefixSet::default(); + let mut prefix_set = PrefixSetMut::from([Nibbles::default()]).freeze(); - let changed = trie.get_changed_subtries(&mut prefix_set); - assert!(changed.is_empty()); + let (subtries, unchanged_prefix_set) = trie.take_changed_lower_subtries(&mut prefix_set); + assert!(subtries.is_empty()); + assert_eq!(unchanged_prefix_set, PrefixSetMut::from(prefix_set.iter().cloned())); } #[test] fn test_get_changed_subtries() { // Create a trie with three subtries let mut trie = ParallelSparseTrie::default(); - let subtrie_1 = SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0])); + let subtrie_1 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0]))); let subtrie_1_index = path_subtrie_index_unchecked(&subtrie_1.path); - let subtrie_2 = SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0])); + let subtrie_2 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0]))); let subtrie_2_index = path_subtrie_index_unchecked(&subtrie_2.path); - let subtrie_3 = SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0])); + let subtrie_3 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0]))); let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); // Add subtries at specific positions @@ -608,28 +726,30 @@ mod tests { trie.lower_subtries[subtrie_2_index] = Some(subtrie_2.clone()); trie.lower_subtries[subtrie_3_index] = Some(subtrie_3); + let unchanged_prefix_set = PrefixSetMut::from([ + Nibbles::from_nibbles_unchecked([0x0]), + Nibbles::from_nibbles_unchecked([0x2, 0x0, 0x0]), + ]); // Create a prefix set with the keys that match only the second subtrie let mut prefix_set = PrefixSetMut::from([ - // Doesn't match any subtries - Nibbles::from_nibbles_unchecked([0x0]), // Match second subtrie Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]), - // Doesn't match any subtries - Nibbles::from_nibbles_unchecked([0x2, 0x0, 0x0]), - ]) - .freeze(); + ]); + prefix_set.extend(unchanged_prefix_set); + let mut prefix_set = prefix_set.freeze(); // Second subtrie should be removed and returned - let changed = trie.get_changed_subtries(&mut prefix_set); + let (subtries, unchanged_prefix_set) = trie.take_changed_lower_subtries(&mut prefix_set); assert_eq!( - changed + subtries .into_iter() - .map(|(subtrie, prefix_set)| { - (subtrie, prefix_set.iter().cloned().collect::>()) + .map(|ChangedSubtrie { index, subtrie, prefix_set }| { + (index, subtrie, prefix_set.iter().cloned().collect::>()) }) .collect::>(), vec![( + subtrie_2_index, subtrie_2, vec![ Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), @@ -637,6 +757,7 @@ mod tests { ] )] ); + assert_eq!(unchanged_prefix_set, unchanged_prefix_set); assert!(trie.lower_subtries[subtrie_2_index].is_none()); // First subtrie should remain unchanged @@ -647,11 +768,11 @@ mod tests { fn test_get_changed_subtries_all() { // Create a trie with three subtries let mut trie = ParallelSparseTrie::default(); - let subtrie_1 = SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0])); + let subtrie_1 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0]))); let subtrie_1_index = path_subtrie_index_unchecked(&subtrie_1.path); - let subtrie_2 = SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0])); + let subtrie_2 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0]))); let subtrie_2_index = path_subtrie_index_unchecked(&subtrie_2.path); - let subtrie_3 = SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0])); + let subtrie_3 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0]))); let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); // Add subtries at specific positions @@ -663,14 +784,22 @@ mod tests { let mut prefix_set = PrefixSetMut::all().freeze(); // All subtries should be removed and returned - let changed = trie.get_changed_subtries(&mut prefix_set); + let (subtries, unchanged_prefix_set) = trie.take_changed_lower_subtries(&mut prefix_set); assert_eq!( - changed + subtries .into_iter() - .map(|(subtrie, prefix_set)| { (subtrie, prefix_set.all()) }) + .map(|ChangedSubtrie { index, subtrie, prefix_set }| { + (index, subtrie, prefix_set.all()) + }) .collect::>(), - vec![(subtrie_1, true), (subtrie_2, true), (subtrie_3, true)] + vec![ + (subtrie_1_index, subtrie_1, true), + (subtrie_2_index, subtrie_2, true), + (subtrie_3_index, subtrie_3, true) + ] ); + assert_eq!(unchanged_prefix_set, PrefixSetMut::all()); + assert!(trie.lower_subtries.iter().all(Option::is_none)); } @@ -864,4 +993,44 @@ mod tests { ); } } + + #[test] + fn test_update_subtrie_hashes() { + // Create a trie with three subtries + let mut trie = ParallelSparseTrie::default(); + let subtrie_1 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0]))); + let subtrie_1_index = path_subtrie_index_unchecked(&subtrie_1.path); + let subtrie_2 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0]))); + let subtrie_2_index = path_subtrie_index_unchecked(&subtrie_2.path); + let subtrie_3 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0]))); + let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); + + // Add subtries at specific positions + trie.lower_subtries[subtrie_1_index] = Some(subtrie_1); + trie.lower_subtries[subtrie_2_index] = Some(subtrie_2); + trie.lower_subtries[subtrie_3_index] = Some(subtrie_3); + + let unchanged_prefix_set = PrefixSetMut::from([ + Nibbles::from_nibbles_unchecked([0x0]), + Nibbles::from_nibbles_unchecked([0x2, 0x0, 0x0]), + ]); + // Create a prefix set with the keys that match only the second subtrie + let mut prefix_set = PrefixSetMut::from([ + // Match second subtrie + Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), + Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]), + ]); + prefix_set.extend(unchanged_prefix_set.clone()); + trie.prefix_set = prefix_set; + + // Update subtrie hashes + trie.update_subtrie_hashes(); + + // Check that the prefix set was updated + assert_eq!(trie.prefix_set, unchanged_prefix_set); + // Check that subtries were returned back to the array + assert!(trie.lower_subtries[subtrie_1_index].is_some()); + assert!(trie.lower_subtries[subtrie_2_index].is_some()); + assert!(trie.lower_subtries[subtrie_3_index].is_some()); + } } diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index aee54417d1c..45f1a266e47 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -1924,7 +1924,7 @@ impl RevealedSparseTrie

{ /// Enum representing sparse trie node type. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum SparseNodeType { +pub enum SparseNodeType { /// Empty trie node. Empty, /// A placeholder that stores only the hash for a node that has not been fully revealed. @@ -2101,25 +2101,25 @@ impl RlpNodeBuffers { } /// RLP node path stack item. -#[derive(Debug)] -struct RlpNodePathStackItem { +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RlpNodePathStackItem { /// Level at which the node is located. Higher numbers correspond to lower levels in the trie. - level: usize, + pub level: usize, /// Path to the node. - path: Nibbles, + pub path: Nibbles, /// Whether the path is in the prefix set. If [`None`], then unknown yet. - is_in_prefix_set: Option, + pub is_in_prefix_set: Option, } /// RLP node stack item. -#[derive(Debug)] -struct RlpNodeStackItem { +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RlpNodeStackItem { /// Path to the node. - path: Nibbles, + pub path: Nibbles, /// RLP node. - rlp_node: RlpNode, + pub rlp_node: RlpNode, /// Type of the node. - node_type: SparseNodeType, + pub node_type: SparseNodeType, } /// Tracks modifications to the sparse trie structure. @@ -2147,6 +2147,13 @@ impl SparseTrieUpdates { self.removed_nodes.clear(); self.wiped = false; } + + /// Extends the updates with another set of updates. + pub fn extend(&mut self, other: Self) { + self.updated_nodes.extend(other.updated_nodes); + self.removed_nodes.extend(other.removed_nodes); + self.wiped |= other.wiped; + } } #[cfg(test)] From ea5ffa51fc11021289ecf656f77001d95eb9b53e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:10:10 +0100 Subject: [PATCH 140/274] bench: disable sparse trie update bench as it's flaky (#16953) --- crates/trie/sparse/Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/trie/sparse/Cargo.toml b/crates/trie/sparse/Cargo.toml index f78322f04b7..8b40a72da2a 100644 --- a/crates/trie/sparse/Cargo.toml +++ b/crates/trie/sparse/Cargo.toml @@ -88,7 +88,3 @@ harness = false [[bench]] name = "rlp_node" harness = false - -[[bench]] -name = "update" -harness = false From 110cb84bdcffe311faf80ae11c1959de79367b52 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Thu, 19 Jun 2025 23:16:11 +0200 Subject: [PATCH 141/274] feat(test): rewrite test_engine_tree_live_sync_fcu_extends_canon_chain using e2e framework (#16949) --- crates/engine/tree/src/tree/e2e_tests.rs | 28 +++++++++++ crates/engine/tree/src/tree/tests.rs | 61 ------------------------ 2 files changed, 28 insertions(+), 61 deletions(-) diff --git a/crates/engine/tree/src/tree/e2e_tests.rs b/crates/engine/tree/src/tree/e2e_tests.rs index 0bbd92b8dfd..a2754d61adf 100644 --- a/crates/engine/tree/src/tree/e2e_tests.rs +++ b/crates/engine/tree/src/tree/e2e_tests.rs @@ -241,3 +241,31 @@ async fn test_engine_tree_buffered_blocks_are_eventually_connected_e2e() -> Resu Ok(()) } + +/// Test that verifies forkchoice updates can extend the canonical chain progressively. +/// +/// This test creates a longer chain of blocks, then uses forkchoice updates to make +/// different parts of the chain canonical in sequence, verifying that FCU properly +/// advances the canonical head when all blocks are already available. +#[tokio::test] +async fn test_engine_tree_fcu_extends_canon_chain_e2e() -> Result<()> { + reth_tracing::init_test_tracing(); + + let test = TestBuilder::new() + .with_setup(default_engine_tree_setup()) + // create and make canonical a base chain with 1 block + .with_action(ProduceBlocks::::new(1)) + .with_action(MakeCanonical::new()) + // extend the chain with 10 more blocks (total 11 blocks: 0-10) + .with_action(ProduceBlocks::::new(10)) + // capture block 6 as our intermediate target (from 0-indexed, this is block 6) + .with_action(CaptureBlock::new("target_block")) + // make the intermediate target canonical via FCU + .with_action(ReorgTo::::new_from_tag("target_block")) + // now make the chain tip canonical via FCU + .with_action(MakeCanonical::new()); + + test.run::().await?; + + Ok(()) +} diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 43891c6fb76..a00d4a56bdb 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -219,16 +219,6 @@ impl TestHarness { self.evm_config.extend(execution_outcomes); } - fn insert_block( - &mut self, - block: RecoveredBlock, - ) -> Result> { - let execution_outcome = self.block_builder.get_execution_outcome(block.clone()); - self.extend_execution_outcome([execution_outcome]); - self.tree.provider.add_state_root(block.state_root); - self.tree.insert_block(block) - } - async fn fcu_to(&mut self, block_hash: B256, fcu_status: impl Into) { let fcu_status = fcu_status.into(); @@ -286,16 +276,6 @@ impl TestHarness { } } - async fn insert_chain( - &mut self, - chain: impl IntoIterator> + Clone, - ) { - for block in chain.clone() { - self.insert_block(block.clone()).unwrap(); - } - self.check_canon_chain_insertion(chain).await; - } - async fn check_canon_commit(&mut self, hash: B256) { let event = self.from_tree_rx.recv().await.unwrap(); match event { @@ -1034,44 +1014,3 @@ async fn test_engine_tree_live_sync_transition_eventually_canonical() { // new head is the tip of the main chain test_harness.check_canon_head(main_chain_last_hash); } - -#[tokio::test] -async fn test_engine_tree_live_sync_fcu_extends_canon_chain() { - reth_tracing::init_test_tracing(); - - let chain_spec = MAINNET.clone(); - let mut test_harness = TestHarness::new(chain_spec.clone()); - - // create base chain and setup test harness with it - let base_chain: Vec<_> = test_harness.block_builder.get_executed_blocks(0..1).collect(); - test_harness = test_harness.with_blocks(base_chain.clone()); - - // fcu to the tip of base chain - test_harness - .fcu_to(base_chain.last().unwrap().recovered_block().hash(), ForkchoiceStatus::Valid) - .await; - - // create main chain, extension of base chain - let main_chain = test_harness.block_builder.create_fork(base_chain[0].recovered_block(), 10); - // determine target in the middle of main hain - let target = main_chain.get(5).unwrap(); - let target_hash = target.hash(); - let main_last = main_chain.last().unwrap(); - let main_last_hash = main_last.hash(); - - // insert main chain - test_harness.insert_chain(main_chain).await; - - // send fcu to target - test_harness.send_fcu(target_hash, ForkchoiceStatus::Valid).await; - - test_harness.check_canon_commit(target_hash).await; - test_harness.check_fcu(target_hash, ForkchoiceStatus::Valid).await; - - // send fcu to main tip - test_harness.send_fcu(main_last_hash, ForkchoiceStatus::Valid).await; - - test_harness.check_canon_commit(main_last_hash).await; - test_harness.check_fcu(main_last_hash, ForkchoiceStatus::Valid).await; - test_harness.check_canon_head(main_last_hash); -} From f318fc26a320c03ae62f7cf511e12aea0ee3cb48 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 00:31:23 +0200 Subject: [PATCH 142/274] chore: remove duplicate callfees (#16955) --- crates/rpc/rpc-eth-types/src/revm_utils.rs | 147 +-------------------- 1 file changed, 3 insertions(+), 144 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 53a75ebbb07..c4125bfb511 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -12,13 +12,12 @@ use revm::{ state::{Account, AccountStatus, Bytecode, EvmStorageSlot}, Database, DatabaseCommit, }; -use std::{ - cmp::min, - collections::{BTreeMap, HashMap}, -}; +use std::collections::{BTreeMap, HashMap}; use super::{EthApiError, EthResult, RpcInvalidTransactionError}; +pub use reth_rpc_types_compat::CallFees; + /// Calculates the caller gas allowance. /// /// `allowance = (account.balance - tx.value) / tx.gas_price` @@ -53,146 +52,6 @@ where .saturating_to()) } -/// Helper type for representing the fees of a `TransactionRequest` -#[derive(Debug)] -pub struct CallFees { - /// EIP-1559 priority fee - pub max_priority_fee_per_gas: Option, - /// Unified gas price setting - /// - /// Will be the configured `basefee` if unset in the request - /// - /// `gasPrice` for legacy, - /// `maxFeePerGas` for EIP-1559 - pub gas_price: U256, - /// Max Fee per Blob gas for EIP-4844 transactions - pub max_fee_per_blob_gas: Option, -} - -// === impl CallFees === - -impl CallFees { - /// Ensures the fields of a `TransactionRequest` are not conflicting. - /// - /// # EIP-4844 transactions - /// - /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. - /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 - /// transaction. - /// - /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` - /// is always `Some` - /// - /// ## Notable design decisions - /// - /// For compatibility reasons, this contains several exceptions when fee values are validated: - /// - If both `maxFeePerGas` and `maxPriorityFeePerGas` are set to `0` they are treated as - /// missing values, bypassing fee checks wrt. `baseFeePerGas`. - /// - /// This mirrors geth's behaviour when transaction requests are executed: - pub fn ensure_fees( - call_gas_price: Option, - call_max_fee: Option, - call_priority_fee: Option, - block_base_fee: U256, - blob_versioned_hashes: Option<&[B256]>, - max_fee_per_blob_gas: Option, - block_blob_fee: Option, - ) -> EthResult { - /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant - /// checks. - fn get_effective_gas_price( - max_fee_per_gas: Option, - max_priority_fee_per_gas: Option, - block_base_fee: U256, - ) -> EthResult { - match max_fee_per_gas { - Some(max_fee) => { - let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(U256::ZERO); - - // only enforce the fee cap if provided input is not zero - if !(max_fee.is_zero() && max_priority_fee_per_gas.is_zero()) && - max_fee < block_base_fee - { - // `base_fee_per_gas` is greater than the `max_fee_per_gas` - return Err(RpcInvalidTransactionError::FeeCapTooLow.into()) - } - if max_fee < max_priority_fee_per_gas { - return Err( - // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` - RpcInvalidTransactionError::TipAboveFeeCap.into(), - ) - } - // ref - Ok(min( - max_fee, - block_base_fee.checked_add(max_priority_fee_per_gas).ok_or_else(|| { - EthApiError::from(RpcInvalidTransactionError::TipVeryHigh) - })?, - )) - } - None => Ok(block_base_fee - .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) - .ok_or(EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?), - } - } - - let has_blob_hashes = - blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); - - match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { - (gas_price, None, None, None) => { - // either legacy transaction or no fee fields are specified - // when no fields are specified, set gas price to zero - let gas_price = gas_price.unwrap_or(U256::ZERO); - Ok(Self { - gas_price, - max_priority_fee_per_gas: None, - max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), - }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { - // request for eip-1559 transaction - let effective_gas_price = get_effective_gas_price( - max_fee_per_gas, - max_priority_fee_per_gas, - block_base_fee, - )?; - let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); - - Ok(Self { - gas_price: effective_gas_price, - max_priority_fee_per_gas, - max_fee_per_blob_gas, - }) - } - (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { - // request for eip-4844 transaction - let effective_gas_price = get_effective_gas_price( - max_fee_per_gas, - max_priority_fee_per_gas, - block_base_fee, - )?; - // Ensure blob_hashes are present - if !has_blob_hashes { - // Blob transaction but no blob hashes - return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into()) - } - - Ok(Self { - gas_price: effective_gas_price, - max_priority_fee_per_gas, - max_fee_per_blob_gas: Some(max_fee_per_blob_gas), - }) - } - _ => { - // this fallback covers incompatible combinations of fields - Err(EthApiError::ConflictingFeeFieldsInRequest) - } - } - } -} - /// Helper trait implemented for databases that support overriding block hashes. /// /// Used for applying [`BlockOverrides::block_hash`] From 5a5b58c6caf73cc377fcf584ce494bc9a0f64267 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 04:53:56 +0200 Subject: [PATCH 143/274] chore: update codeowners (#16957) --- .github/CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 12f3b7a7238..a0558be60fe 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,7 +10,7 @@ crates/consensus/ @rkrasiuk @mattsse @Rjected crates/e2e-test-utils/ @mattsse @Rjected crates/engine @rkrasiuk @mattsse @Rjected crates/engine/ @rkrasiuk @mattsse @Rjected @fgimenez -crates/era/ @mattsse +crates/era/ @mattsse @RomanHodulak crates/errors/ @mattsse crates/ethereum-forks/ @mattsse @Rjected crates/ethereum/ @mattsse @Rjected @@ -24,12 +24,12 @@ crates/net/downloaders/ @onbjerg @rkrasiuk crates/node/ @mattsse @Rjected @onbjerg @klkvr crates/optimism/ @mattsse @Rjected @fgimenez crates/payload/ @mattsse @Rjected -crates/primitives-traits/ @Rjected @joshieDo @mattsse @klkvr +crates/primitives-traits/ @Rjected @RomanHodulak @mattsse @klkvr crates/primitives/ @Rjected @mattsse @klkvr crates/prune/ @shekhirin @joshieDo crates/ress @rkrasiuk crates/revm/ @mattsse @rakita -crates/rpc/ @mattsse @Rjected +crates/rpc/ @mattsse @Rjected @RomanHodulak crates/stages/ @onbjerg @rkrasiuk @shekhirin crates/static-file/ @joshieDo @shekhirin crates/storage/codecs/ @joshieDo From 24f0365340889630358f1577b1fe9d7b296530b0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 10:43:21 +0200 Subject: [PATCH 144/274] chore: use revm tx trait directly (#16961) --- crates/rpc/rpc-eth-types/src/revm_utils.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index c4125bfb511..47671b89879 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -5,7 +5,6 @@ use alloy_rpc_types_eth::{ state::{AccountOverride, StateOverride}, BlockOverrides, }; -use reth_evm::TransactionEnv; use revm::{ context::BlockEnv, database::{CacheDB, State}, @@ -27,10 +26,11 @@ pub use reth_rpc_types_compat::CallFees; /// /// Note: this takes the mut [Database] trait because the loaded sender can be reused for the /// following operation like `eth_call`. -pub fn caller_gas_allowance(db: &mut DB, env: &impl TransactionEnv) -> EthResult +pub fn caller_gas_allowance(db: &mut DB, env: &T) -> EthResult where DB: Database, EthApiError: From<::Error>, + T: revm::context_interface::Transaction, { // Get the caller account. let caller = db.basic(env.caller())?; From 343983d0a17a816fbb7c450451ac8f52363bf18a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 11:51:16 +0200 Subject: [PATCH 145/274] chore: feature gate all op rpc types compat impl (#16964) --- crates/optimism/rpc/Cargo.toml | 2 +- crates/rpc/rpc-eth-api/Cargo.toml | 5 + crates/rpc/rpc-eth-api/src/lib.rs | 5 +- crates/rpc/rpc-types-compat/Cargo.toml | 22 ++- crates/rpc/rpc-types-compat/src/lib.rs | 7 +- .../rpc/rpc-types-compat/src/transaction.rs | 153 +++++++++--------- 6 files changed, 109 insertions(+), 85 deletions(-) diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index a29b2406b7f..3227dab63b9 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -17,7 +17,7 @@ reth-evm.workspace = true reth-primitives-traits.workspace = true reth-storage-api.workspace = true reth-chain-state.workspace = true -reth-rpc-eth-api.workspace = true +reth-rpc-eth-api = { workspace = true, features = ["op"] } reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-tasks = { workspace = true, features = ["rayon"] } diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index a6d9bea6193..25ff3b7c1a2 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -62,3 +62,8 @@ tracing.workspace = true [features] js-tracer = ["revm-inspectors/js-tracer", "reth-rpc-eth-types/js-tracer"] client = ["jsonrpsee/client", "jsonrpsee/async-client"] +op = [ + "reth-evm/op", + "reth-primitives-traits/op", + "reth-rpc-types-compat/op", +] diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs index 1a24a2cd4e2..e6d02a1644f 100644 --- a/crates/rpc/rpc-eth-api/src/lib.rs +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -30,10 +30,7 @@ pub use pubsub::EthPubSubApiServer; pub use reth_rpc_eth_types::error::{ AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError, }; -pub use reth_rpc_types_compat::{ - try_into_op_tx_info, CallFeesError, EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, - TransactionConversionError, TryIntoSimTx, TxInfoMapper, -}; +pub use reth_rpc_types_compat::*; pub use types::{EthApiTypes, FullEthApiTypes, RpcBlock, RpcHeader, RpcReceipt, RpcTransaction}; #[cfg(feature = "client")] diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index d56aa569bb2..822cb29076d 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] # reth reth-primitives-traits.workspace = true -reth-storage-api = { workspace = true, features = ["serde", "serde-bincode-compat"] } +reth-storage-api = { workspace = true, features = ["serde", "serde-bincode-compat"], optional = true } reth-evm.workspace = true # ethereum @@ -24,10 +24,10 @@ alloy-consensus.workspace = true alloy-network.workspace = true # optimism -op-alloy-consensus.workspace = true -op-alloy-rpc-types.workspace = true -reth-optimism-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] } -op-revm.workspace = true +op-alloy-consensus = { workspace = true, optional = true } +op-alloy-rpc-types = { workspace = true, optional = true } +reth-optimism-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"], optional = true } +op-revm = { workspace = true, optional = true } # revm revm-context.workspace = true @@ -38,3 +38,15 @@ jsonrpsee-types.workspace = true # error thiserror.workspace = true + +[features] +default = [] +op = [ + "dep:op-alloy-consensus", + "dep:op-alloy-rpc-types", + "dep:reth-optimism-primitives", + "dep:reth-storage-api", + "dep:op-revm", + "reth-evm/op", + "reth-primitives-traits/op", +] diff --git a/crates/rpc/rpc-types-compat/src/lib.rs b/crates/rpc/rpc-types-compat/src/lib.rs index 33e8c2bb725..a7e2ad99515 100644 --- a/crates/rpc/rpc-types-compat/src/lib.rs +++ b/crates/rpc/rpc-types-compat/src/lib.rs @@ -14,6 +14,9 @@ mod fees; pub mod transaction; pub use fees::{CallFees, CallFeesError}; pub use transaction::{ - try_into_op_tx_info, EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, - TransactionConversionError, TryIntoSimTx, TxInfoMapper, + EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, TransactionConversionError, + TryIntoSimTx, TxInfoMapper, }; + +#[cfg(feature = "op")] +pub use transaction::op::*; diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 2ed20a040fd..fc5afd3cf8e 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -1,29 +1,19 @@ //! Compatibility functions for rpc `Transaction` type. use crate::fees::{CallFees, CallFeesError}; -use alloy_consensus::{ - error::ValueError, transaction::Recovered, EthereumTxEnvelope, SignableTransaction, TxEip4844, -}; +use alloy_consensus::{error::ValueError, transaction::Recovered, EthereumTxEnvelope, TxEip4844}; use alloy_network::Network; -use alloy_primitives::{Address, Bytes, Signature, TxKind, U256}; +use alloy_primitives::{Address, TxKind, U256}; use alloy_rpc_types_eth::{ request::{TransactionInputError, TransactionRequest}, Transaction, TransactionInfo, }; use core::error; -use op_alloy_consensus::{ - transaction::{OpDepositInfo, OpTransactionInfo}, - OpTxEnvelope, -}; -use op_alloy_rpc_types::OpTransactionRequest; -use op_revm::OpTransaction; use reth_evm::{ revm::context_interface::{either::Either, Block}, ConfigureEvm, TxEnvFor, }; -use reth_optimism_primitives::DepositReceipt; use reth_primitives_traits::{NodePrimitives, TxTy}; -use reth_storage_api::{errors::ProviderError, ReceiptProvider}; use revm_context::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Serialize}; use std::{convert::Infallible, error::Error, fmt::Debug, marker::PhantomData}; @@ -182,58 +172,12 @@ impl TxInfoMapper<&T> for () { } } -/// Creates [`OpTransactionInfo`] by adding [`OpDepositInfo`] to [`TransactionInfo`] if `tx` is a -/// deposit. -pub fn try_into_op_tx_info>( - provider: &T, - tx: &OpTxEnvelope, - tx_info: TransactionInfo, -) -> Result { - let deposit_meta = if tx.is_deposit() { - provider.receipt_by_hash(tx.tx_hash())?.and_then(|receipt| { - receipt.as_deposit_receipt().map(|receipt| OpDepositInfo { - deposit_receipt_version: receipt.deposit_receipt_version, - deposit_nonce: receipt.deposit_nonce, - }) - }) - } else { - None - } - .unwrap_or_default(); - - Ok(OpTransactionInfo::new(tx_info, deposit_meta)) -} - -impl FromConsensusTx - for op_alloy_rpc_types::Transaction -{ - type TxInfo = OpTransactionInfo; - - fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { - Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info) - } -} - impl TryIntoSimTx> for TransactionRequest { fn try_into_sim_tx(self) -> Result, ValueError> { Self::build_typed_simulate_transaction(self) } } -impl TryIntoSimTx for TransactionRequest { - fn try_into_sim_tx(self) -> Result> { - let request: OpTransactionRequest = self.into(); - let tx = request.build_typed_tx().map_err(|request| { - ValueError::new(request.as_ref().clone(), "Required fields missing") - })?; - - // Create an empty signature for the transaction. - let signature = Signature::new(Default::default(), Default::default(), false); - - Ok(tx.into_signed(signature).into()) - } -} - /// Converts `self` into `T`. /// /// Should create an executable transaction environment using [`TransactionRequest`]. @@ -261,21 +205,6 @@ pub enum EthTxEnvError { Input(#[from] TransactionInputError), } -impl TryIntoTxEnv> for TransactionRequest { - type Err = EthTxEnvError; - - fn try_into_tx_env( - self, - cfg_env: &CfgEnv, - block_env: &BlockEnv, - ) -> Result, Self::Err> { - Ok(OpTransaction { - base: self.try_into_tx_env(cfg_env, block_env)?, - enveloped_tx: Some(Bytes::new()), - deposit: Default::default(), - }) - } -} impl TryIntoTxEnv for TransactionRequest { type Err = EthTxEnvError; @@ -477,3 +406,81 @@ where Ok(request.try_into_tx_env(cfg_env, block_env)?) } } + +/// Optimism specific RPC transaction compatibility implementations. +#[cfg(feature = "op")] +pub mod op { + use super::*; + use alloy_consensus::SignableTransaction; + use alloy_primitives::{Address, Bytes, Signature}; + use op_alloy_consensus::{ + transaction::{OpDepositInfo, OpTransactionInfo}, + OpTxEnvelope, + }; + use op_alloy_rpc_types::OpTransactionRequest; + use op_revm::OpTransaction; + use reth_optimism_primitives::DepositReceipt; + use reth_storage_api::{errors::ProviderError, ReceiptProvider}; + + /// Creates [`OpTransactionInfo`] by adding [`OpDepositInfo`] to [`TransactionInfo`] if `tx` is + /// a deposit. + pub fn try_into_op_tx_info>( + provider: &T, + tx: &OpTxEnvelope, + tx_info: TransactionInfo, + ) -> Result { + let deposit_meta = if tx.is_deposit() { + provider.receipt_by_hash(tx.tx_hash())?.and_then(|receipt| { + receipt.as_deposit_receipt().map(|receipt| OpDepositInfo { + deposit_receipt_version: receipt.deposit_receipt_version, + deposit_nonce: receipt.deposit_nonce, + }) + }) + } else { + None + } + .unwrap_or_default(); + + Ok(OpTransactionInfo::new(tx_info, deposit_meta)) + } + + impl FromConsensusTx + for op_alloy_rpc_types::Transaction + { + type TxInfo = OpTransactionInfo; + + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { + Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info) + } + } + + impl TryIntoSimTx for TransactionRequest { + fn try_into_sim_tx(self) -> Result> { + let request: OpTransactionRequest = self.into(); + let tx = request.build_typed_tx().map_err(|request| { + ValueError::new(request.as_ref().clone(), "Required fields missing") + })?; + + // Create an empty signature for the transaction. + let signature = Signature::new(Default::default(), Default::default(), false); + + Ok(tx.into_signed(signature).into()) + } + } + + impl TryIntoTxEnv> for TransactionRequest { + type Err = EthTxEnvError; + + fn try_into_tx_env( + self, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result, Self::Err> { + Ok(OpTransaction { + base: self.try_into_tx_env(cfg_env, block_env)?, + enveloped_tx: Some(Bytes::new()), + deposit: Default::default(), + }) + } + } +} From f9b4eba3b78408387a3bf9bbd14809955a08622f Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Fri, 20 Jun 2025 13:23:43 +0200 Subject: [PATCH 146/274] chore(trie): Replace magic numbers in ParallelSparseTrie code (#16960) Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> --- crates/trie/sparse-parallel/src/trie.rs | 47 +++++++++++++++---------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 411f4275c8d..daf65d8c077 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -18,6 +18,13 @@ use reth_trie_sparse::{ use smallvec::SmallVec; use tracing::trace; +/// The maximum length of a path, in nibbles, which belongs to the upper subtrie of a +/// [`ParallelSparseTrie`]. All longer paths belong to a lower subtrie. +pub const UPPER_TRIE_MAX_DEPTH: usize = 2; + +/// Number of lower subtries which are managed by the [`ParallelSparseTrie`]. +pub const NUM_LOWER_SUBTRIES: usize = 16usize.pow(UPPER_TRIE_MAX_DEPTH as u32); + /// A revealed sparse trie with subtries that can be updated in parallel. /// /// ## Invariants @@ -30,7 +37,7 @@ pub struct ParallelSparseTrie { /// This contains the trie nodes for the upper part of the trie. upper_subtrie: Box, /// An array containing the subtries at the second level of the trie. - lower_subtries: [Option>; 256], + lower_subtries: [Option>; NUM_LOWER_SUBTRIES], /// Set of prefixes (key paths) that have been marked as updated. /// This is used to track which parts of the trie need to be recalculated. prefix_set: PrefixSetMut, @@ -42,7 +49,7 @@ impl Default for ParallelSparseTrie { fn default() -> Self { Self { upper_subtrie: Box::default(), - lower_subtries: [const { None }; 256], + lower_subtries: [const { None }; NUM_LOWER_SUBTRIES], prefix_set: PrefixSetMut::default(), updates: None, } @@ -57,7 +64,7 @@ impl ParallelSparseTrie { SparseSubtrieType::Upper => None, SparseSubtrieType::Lower(idx) => { if self.lower_subtries[idx].is_none() { - let upper_path = path.slice(..2); + let upper_path = path.slice(..UPPER_TRIE_MAX_DEPTH); self.lower_subtries[idx] = Some(Box::new(SparseSubtrie::new(upper_path))); } @@ -101,8 +108,8 @@ impl ParallelSparseTrie { return subtrie.reveal_node(path, &node, masks); } - // If there is no subtrie for the path it means the path is 2 or less nibbles, and so - // belongs to the upper trie. + // If there is no subtrie for the path it means the path is UPPER_TRIE_MAX_DEPTH or less + // nibbles, and so belongs to the upper trie. self.upper_subtrie.reveal_node(path.clone(), &node, masks)?; // The previous upper_trie.reveal_node call will not have revealed any child nodes via @@ -111,9 +118,9 @@ impl ParallelSparseTrie { // reveal_node_or_hash for each. match node { TrieNode::Branch(branch) => { - // If a branch is at the second level of the trie then it will be in the upper trie, - // but all of its children will be in the lower trie. - if path.len() == 2 { + // If a branch is at the cutoff level of the trie then it will be in the upper trie, + // but all of its children will be in a lower trie. + if path.len() == UPPER_TRIE_MAX_DEPTH { let mut stack_ptr = branch.as_ref().first_child_index(); for idx in CHILD_INDEX_RANGE { if branch.state_mask.is_bit_set(idx) { @@ -130,7 +137,7 @@ impl ParallelSparseTrie { TrieNode::Extension(ext) => { let mut child_path = path.clone(); child_path.extend_from_slice_unchecked(&ext.key); - if child_path.len() > 2 { + if child_path.len() > UPPER_TRIE_MAX_DEPTH { self.lower_subtrie_for_path(&child_path) .expect("child_path must have a lower subtrie") .reveal_node_or_hash(child_path, &ext.child)?; @@ -188,13 +195,14 @@ impl ParallelSparseTrie { todo!() } - /// Recalculates and updates the RLP hashes of nodes up to level 2 of the trie. + /// Recalculates and updates the RLP hashes of nodes up to level [`UPPER_TRIE_MAX_DEPTH`] of the + /// trie. /// /// The root node is considered to be at level 0. This method is useful for optimizing /// hash recalculations after localized changes to the trie structure. /// /// This function first identifies all nodes that have changed (based on the prefix set) below - /// level 2 of the trie, then recalculates their RLP representation. + /// level [`UPPER_TRIE_MAX_DEPTH`] of the trie, then recalculates their RLP representation. pub fn update_subtrie_hashes(&mut self) { trace!(target: "trie::parallel_sparse", "Updating subtrie hashes"); @@ -320,8 +328,9 @@ impl ParallelSparseTrie { pub struct SparseSubtrie { /// The root path of this subtrie. /// - /// This is the _full_ path to this subtrie, meaning it includes the first two nibbles that we - /// also use for indexing subtries in the [`ParallelSparseTrie`]. + /// This is the _full_ path to this subtrie, meaning it includes the first + /// [`UPPER_TRIE_MAX_DEPTH`] nibbles that we also use for indexing subtries in the + /// [`ParallelSparseTrie`]. path: Nibbles, /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, @@ -578,7 +587,7 @@ impl SparseSubtrie { /// - Paths in the range `0x000..` belong to one of the lower subtries. The index of the lower /// subtrie is determined by the path first nibbles of the path. /// -/// There can be at most 256 lower subtries. +/// There can be at most [`NUM_LOWER_SUBTRIES`] lower subtries. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum SparseSubtrieType { /// Upper subtrie with paths in the range `0x..=0xff` @@ -591,7 +600,7 @@ pub enum SparseSubtrieType { impl SparseSubtrieType { /// Returns the type of subtrie based on the given path. pub fn from_path(path: &Nibbles) -> Self { - if path.len() <= 2 { + if path.len() <= UPPER_TRIE_MAX_DEPTH { Self::Upper } else { Self::Lower(path_subtrie_index_unchecked(path)) @@ -627,7 +636,7 @@ pub struct SparseSubtrieBuffers { /// Changed subtrie. #[derive(Debug)] struct ChangedSubtrie { - /// Lower subtrie index in the range [0, 255]. + /// Lower subtrie index in the range [0, [`NUM_LOWER_SUBTRIES`]). index: usize, /// Changed subtrie subtrie: Box, @@ -636,12 +645,14 @@ struct ChangedSubtrie { prefix_set: PrefixSet, } -/// Convert first two nibbles of the path into a lower subtrie index in the range [0, 255]. +/// Convert first [`UPPER_TRIE_MAX_DEPTH`] nibbles of the path into a lower subtrie index in the +/// range [0, [`NUM_LOWER_SUBTRIES`]). /// /// # Panics /// -/// If the path is shorter than two nibbles. +/// If the path is shorter than [`UPPER_TRIE_MAX_DEPTH`] nibbles. fn path_subtrie_index_unchecked(path: &Nibbles) -> usize { + debug_assert_eq!(UPPER_TRIE_MAX_DEPTH, 2); (path[0] << 4 | path[1]) as usize } From b45f84d78c08b8078c962eed7d75b63f5e0c0560 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 14:17:35 +0200 Subject: [PATCH 147/274] fix: check if dir exists before removing (#16968) --- crates/node/builder/src/launch/common.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index a123d6722c0..c5c4bf2c4eb 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -274,9 +274,11 @@ impl LaunchContextWith Self { if self.toml_config_mut().stages.etl.dir.is_none() { let etl_path = EtlConfig::from_datadir(self.data_dir().data_dir()); - // Remove etl-path files on launch - if let Err(err) = fs::remove_dir_all(&etl_path) { - warn!(target: "reth::cli", ?etl_path, %err, "Failed to remove ETL path on launch"); + if etl_path.exists() { + // Remove etl-path files on launch + if let Err(err) = fs::remove_dir_all(&etl_path) { + warn!(target: "reth::cli", ?etl_path, %err, "Failed to remove ETL path on launch"); + } } self.toml_config_mut().stages.etl.dir = Some(etl_path); } From 15529e7923941b581b1da666ac7b0b16e9a1f56e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 14:35:07 +0200 Subject: [PATCH 148/274] revert: "ci: pin nextest version" (#16890) Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> --- .github/workflows/integration.yml | 8 ++------ .github/workflows/unit.yml | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 7b23f8cc2ff..1369ba1502a 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,9 +37,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - name: Install Geth run: .github/assets/install_geth.sh - - uses: taiki-e/install-action@v2 - with: - tool: nextest@0.9.98 + - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -76,9 +74,7 @@ jobs: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@v2 - with: - tool: nextest@0.9.98 + - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index d3ee31e5db9..767a3e5c0ad 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -54,9 +54,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - uses: taiki-e/install-action@v2 - with: - tool: nextest@0.9.98 + - uses: taiki-e/install-action@nextest - if: "${{ matrix.type == 'book' }}" uses: arduino/setup-protoc@v3 with: @@ -89,9 +87,7 @@ jobs: fetch-depth: 1 - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@v2 - with: - tool: nextest@0.9.98 + - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true From 0ce46431fd5c753bf5100a8ff95527237f7318f5 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 20 Jun 2025 15:59:24 +0300 Subject: [PATCH 149/274] chore: propagate inner error in ef tests (#16970) --- testing/ef-tests/src/cases/blockchain_test.rs | 41 ++++++++++--------- testing/ef-tests/src/result.rs | 15 ++++++- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 1cf905ff2d1..4c463c612a6 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -108,7 +108,7 @@ impl BlockchainTestCase { } // A block processing failure occurred. - Err(Error::BlockProcessingFailed { block_number }) => match expectation { + err @ Err(Error::BlockProcessingFailed { block_number, .. }) => match expectation { // It happened on exactly the block we were told to fail on Some((expected, _)) if block_number == expected => Ok(()), @@ -122,7 +122,7 @@ impl BlockchainTestCase { ))), // No failure expected at all - bubble up original error. - None => Err(Error::BlockProcessingFailed { block_number }), + None => err, }, // Non‑processing error – forward as‑is. @@ -199,15 +199,15 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { provider .insert_block(genesis_block.clone(), StorageLocation::Database) - .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?; + .map_err(|err| Error::block_failed(0, err))?; let genesis_state = case.pre.clone().into_genesis_state(); insert_genesis_state(&provider, genesis_state.iter()) - .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?; + .map_err(|err| Error::block_failed(0, err))?; insert_genesis_hashes(&provider, genesis_state.iter()) - .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?; + .map_err(|err| Error::block_failed(0, err))?; insert_genesis_history(&provider, genesis_state.iter()) - .map_err(|_| Error::BlockProcessingFailed { block_number: 0 })?; + .map_err(|err| Error::block_failed(0, err))?; // Decode blocks let blocks = decode_blocks(&case.blocks)?; @@ -223,11 +223,11 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { // Insert the block into the database provider .insert_block(block.clone(), StorageLocation::Database) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; // Consensus checks before block execution pre_execution_checks(chain_spec.clone(), &parent, block) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; let mut witness_record = ExecutionWitnessRecord::default(); @@ -240,11 +240,11 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { .execute_with_state_closure(&(*block).clone(), |statedb: &State<_>| { witness_record.record_executed_state(statedb); }) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; // Consensus checks after block execution validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; // Generate the stateless witness // TODO: Most of this code is copy-pasted from debug_executionWitness @@ -278,9 +278,12 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { HashedPostState::from_bundle_state::(output.state.state()); let (computed_state_root, _) = StateRoot::overlay_root_with_updates(provider.tx_ref(), hashed_state.clone()) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; if computed_state_root != block.state_root { - return Err(Error::BlockProcessingFailed { block_number }) + return Err(Error::block_failed( + block_number, + Error::Assertion("state root mismatch".to_string()), + )) } // Commit the post state/state diff to the database @@ -290,14 +293,14 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { OriginalValuesKnown::Yes, StorageLocation::Database, ) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; provider .write_hashed_state(&hashed_state.into_sorted()) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; provider .update_history_indices(block.number..=block.number) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; // Since there were no errors, update the parent block parent = block.clone() @@ -341,12 +344,10 @@ fn decode_blocks( let block_number = (block_index + 1) as u64; let decoded = SealedBlock::::decode(&mut block.rlp.as_ref()) - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + .map_err(|err| Error::block_failed(block_number, err))?; - let recovered_block = decoded - .clone() - .try_recover() - .map_err(|_| Error::BlockProcessingFailed { block_number })?; + let recovered_block = + decoded.clone().try_recover().map_err(|err| Error::block_failed(block_number, err))?; blocks.push(recovered_block); } diff --git a/testing/ef-tests/src/result.rs b/testing/ef-tests/src/result.rs index a1bed359b07..f53a4fab256 100644 --- a/testing/ef-tests/src/result.rs +++ b/testing/ef-tests/src/result.rs @@ -23,10 +23,13 @@ pub enum Error { /// Block processing failed /// Note: This includes but is not limited to execution. /// For example, the header number could be incorrect. - #[error("block {block_number} failed to process")] + #[error("block {block_number} failed to process: {err}")] BlockProcessingFailed { /// The block number for the block that failed block_number: u64, + /// The specific error + #[source] + err: Box, }, /// An IO error occurred #[error("an error occurred interacting with the file system at {path}: {error}")] @@ -63,6 +66,16 @@ pub enum Error { ConsensusError(#[from] reth_consensus::ConsensusError), } +impl Error { + /// Create a new [`Error::BlockProcessingFailed`] error. + pub fn block_failed( + block_number: u64, + err: impl std::error::Error + Send + Sync + 'static, + ) -> Self { + Self::BlockProcessingFailed { block_number, err: Box::new(err) } + } +} + /// The result of running a test. #[derive(Debug)] pub struct CaseResult { From 8f16e2199f9d46a8dc511639a32d584dc1057834 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Fri, 20 Jun 2025 17:43:13 +0200 Subject: [PATCH 150/274] chore: resolve unused import warning in reth RPC API subscription attribute (#16975) --- crates/rpc/rpc-api/src/reth.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-api/src/reth.rs b/crates/rpc/rpc-api/src/reth.rs index cc72705fa54..de0402624a9 100644 --- a/crates/rpc/rpc-api/src/reth.rs +++ b/crates/rpc/rpc-api/src/reth.rs @@ -1,9 +1,11 @@ use alloy_eips::BlockId; use alloy_primitives::{Address, U256}; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -use reth_chain_state::CanonStateNotification; use std::collections::HashMap; +// Required for the subscription attribute below +use reth_chain_state as _; + /// Reth API namespace for reth-specific methods #[cfg_attr(not(feature = "client"), rpc(server, namespace = "reth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "reth"))] @@ -19,7 +21,7 @@ pub trait RethApi { #[subscription( name = "subscribeChainNotifications", unsubscribe = "unsubscribeChainNotifications", - item = CanonStateNotification + item = reth_chain_state::CanonStateNotification )] async fn reth_subscribe_chain_notifications(&self) -> jsonrpsee::core::SubscriptionResult; } From 85e6e979c298c295db6cdf53f5e6f01ed074da30 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 20 Jun 2025 13:55:40 -0400 Subject: [PATCH 151/274] chore(merkle): add debug log inside incremental loop (#16977) --- crates/stages/stages/src/stages/merkle.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 4f8016b4568..f1ce05f536b 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -279,6 +279,14 @@ where for start_block in range.step_by(incremental_threshold as usize) { let chunk_to = std::cmp::min(start_block + incremental_threshold, to_block); let chunk_range = start_block..=chunk_to; + debug!( + target: "sync::stages::merkle::exec", + current = ?current_block_number, + target = ?to_block, + incremental_threshold, + chunk_range = ?chunk_range, + "Processing chunk" + ); let (root, updates) = StateRoot::incremental_root_with_updates(provider.tx_ref(), chunk_range) .map_err(|e| { From 9961d46bb183c2d7117cbfd7f2aa5730b5b725f4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 20 Jun 2025 19:56:43 +0200 Subject: [PATCH 152/274] fix: add missing historical RPC endpoints for Optimism pre-bedrock (#16976) Co-authored-by: Claude --- crates/optimism/rpc/src/historical.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/optimism/rpc/src/historical.rs b/crates/optimism/rpc/src/historical.rs index c364271ae31..133bce6a7df 100644 --- a/crates/optimism/rpc/src/historical.rs +++ b/crates/optimism/rpc/src/historical.rs @@ -114,10 +114,12 @@ where parse_block_id_from_params(&req.params(), 0) } "eth_getBalance" | - "eth_getStorageAt" | "eth_getCode" | "eth_getTransactionCount" | - "eth_call" => parse_block_id_from_params(&req.params(), 1), + "eth_call" | + "eth_estimateGas" | + "eth_createAccessList" => parse_block_id_from_params(&req.params(), 1), + "eth_getStorageAt" | "eth_getProof" => parse_block_id_from_params(&req.params(), 2), _ => None, }; From 1339e8770e93603df170cb7c24f2f1901c53070e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 20 Jun 2025 23:19:58 +0200 Subject: [PATCH 153/274] feat(era): Attach file name and path to checksum error (#16974) --- crates/era-downloader/src/client.rs | 4 +++- crates/era-downloader/tests/it/checksums.rs | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/era-downloader/src/client.rs b/crates/era-downloader/src/client.rs index 56d0c789c47..ea4894cadbd 100644 --- a/crates/era-downloader/src/client.rs +++ b/crates/era-downloader/src/client.rs @@ -92,7 +92,9 @@ impl EraClient { } } - self.assert_checksum(number, actual_checksum?).await?; + self.assert_checksum(number, actual_checksum?) + .await + .map_err(|e| eyre!("{e} for {file_name} at {}", path.display()))?; } Ok(path.into_boxed_path()) diff --git a/crates/era-downloader/tests/it/checksums.rs b/crates/era-downloader/tests/it/checksums.rs index a98b4ae630f..46f889adf7c 100644 --- a/crates/era-downloader/tests/it/checksums.rs +++ b/crates/era-downloader/tests/it/checksums.rs @@ -23,16 +23,24 @@ async fn test_invalid_checksum_returns_error(url: &str) { ); let actual_err = stream.next().await.unwrap().unwrap_err().to_string(); - let expected_err = "Checksum mismatch, \ + let expected_err = format!( + "Checksum mismatch, \ got: 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7, \ -expected: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +expected: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ +for mainnet-00000-5ec1ffb8.era1 at {}/mainnet-00000-5ec1ffb8.era1", + folder.display() + ); assert_eq!(actual_err, expected_err); let actual_err = stream.next().await.unwrap().unwrap_err().to_string(); - let expected_err = "Checksum mismatch, \ + let expected_err = format!( + "Checksum mismatch, \ got: 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f, \ -expected: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +expected: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb \ +for mainnet-00001-a5364e9a.era1 at {}/mainnet-00001-a5364e9a.era1", + folder.display() + ); assert_eq!(actual_err, expected_err); } From f917cf0eb28adba52091a2b47528a5e4d2d08856 Mon Sep 17 00:00:00 2001 From: Amidamaru Date: Sat, 21 Jun 2025 11:40:53 +0700 Subject: [PATCH 154/274] perf(rpc): optimize EVM reuse in `eth_estimateGas` (#16958) --- .../rpc/rpc-eth-api/src/helpers/estimate.rs | 95 ++++++++++--------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs index 297559fbabf..efee8b5b58f 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs @@ -7,10 +7,10 @@ use alloy_rpc_types_eth::{state::StateOverride, transaction::TransactionRequest, use futures::Future; use reth_chainspec::MIN_TRANSACTION_GAS; use reth_errors::ProviderError; -use reth_evm::{Database, EvmEnvFor, TransactionEnv, TxEnvFor}; +use reth_evm::{ConfigureEvm, Database, Evm, EvmEnvFor, EvmFor, TransactionEnv, TxEnvFor}; use reth_revm::{database::StateProviderDatabase, db::CacheDB}; use reth_rpc_eth_types::{ - error::api::FromEvmHalt, + error::{api::FromEvmHalt, FromEvmError}, revm_utils::{apply_state_overrides, caller_gas_allowance}, EthApiError, RevertError, RpcInvalidTransactionError, }; @@ -81,25 +81,12 @@ pub trait EstimateCall: Call { apply_state_overrides(state_override, &mut db).map_err(Self::Error::from_eth_err)?; } - // Optimize for simple transfer transactions, potentially reducing the gas estimate. + // Check if this is a basic transfer (no input data to account with no code) + let mut is_basic_transfer = false; if tx_env.input().is_empty() { if let TxKind::Call(to) = tx_env.kind() { 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 tx_env = tx_env.clone(); - tx_env.set_gas_limit(MIN_TRANSACTION_GAS); - if let Ok(res) = self.transact(&mut db, evm_env.clone(), tx_env) { - if res.result.is_success() { - return Ok(U256::from(MIN_TRANSACTION_GAS)) - } - } - } + is_basic_transfer = code.map(|code| code.is_empty()).unwrap_or(true); } } } @@ -116,10 +103,31 @@ pub trait EstimateCall: Call { // If the provided gas limit is less than computed cap, use that tx_env.set_gas_limit(tx_env.gas_limit().min(highest_gas_limit)); - trace!(target: "rpc::eth::estimate", ?evm_env, ?tx_env, "Starting gas estimation"); + // Create EVM instance once and reuse it throughout the entire estimation process + let mut evm = self.evm_config().evm_with_env(&mut db, evm_env); + + // For basic transfers, try using minimum gas before running full binary search + if is_basic_transfer { + // 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 min_tx_env = tx_env.clone(); + min_tx_env.set_gas_limit(MIN_TRANSACTION_GAS); + + // Reuse the same EVM instance + if let Ok(res) = evm.transact(min_tx_env).map_err(Self::Error::from_evm_err) { + if res.result.is_success() { + return Ok(U256::from(MIN_TRANSACTION_GAS)) + } + } + } + + trace!(target: "rpc::eth::estimate", ?tx_env, gas_limit = tx_env.gas_limit(), is_basic_transfer, "Starting gas estimation"); // Execute the transaction with the highest possible gas limit. - let mut res = match self.transact(&mut db, evm_env.clone(), tx_env.clone()) { + let mut res = match evm.transact(tx_env.clone()).map_err(Self::Error::from_evm_err) { // 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 @@ -128,7 +136,7 @@ pub trait EstimateCall: Call { if err.is_gas_too_high() && (tx_request_gas_limit.is_some() || tx_request_gas_price.is_some()) => { - return Err(self.map_out_of_gas_err(block_env_gas_limit, evm_env, tx_env, &mut db)) + return Self::map_out_of_gas_err(&mut evm, tx_env, block_env_gas_limit); } Err(err) if err.is_gas_too_low() => { // This failed because the configured gas cost of the tx was lower than what @@ -155,7 +163,7 @@ pub trait EstimateCall: Call { // 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, evm_env, tx_env, &mut db)) + Self::map_out_of_gas_err(&mut evm, tx_env, block_env_gas_limit) } else { // the transaction did revert Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into_eth_err()) @@ -185,10 +193,13 @@ pub trait EstimateCall: Call { let optimistic_gas_limit = (gas_used + gas_refund + CALL_STIPEND_GAS) * 64 / 63; if optimistic_gas_limit < highest_gas_limit { // Set the transaction's gas limit to the calculated optimistic gas limit. - tx_env.set_gas_limit(optimistic_gas_limit); + let mut optimistic_tx_env = tx_env.clone(); + optimistic_tx_env.set_gas_limit(optimistic_gas_limit); + // Re-execute the transaction with the new gas limit and update the result and // environment. - res = self.transact(&mut db, evm_env.clone(), tx_env.clone())?; + res = evm.transact(optimistic_tx_env).map_err(Self::Error::from_evm_err)?; + // Update the gas used based on the new result. gas_used = res.result.gas_used(); // Update the gas limit estimates (highest and lowest) based on the execution result. @@ -206,7 +217,7 @@ pub trait EstimateCall: Call { ((highest_gas_limit as u128 + lowest_gas_limit as u128) / 2) as u64, ); - trace!(target: "rpc::eth::estimate", ?evm_env, ?tx_env, ?highest_gas_limit, ?lowest_gas_limit, ?mid_gas_limit, "Starting binary search for gas"); + trace!(target: "rpc::eth::estimate", ?highest_gas_limit, ?lowest_gas_limit, ?mid_gas_limit, "Starting binary search for gas"); // Binary search narrows the range to find the minimum gas limit needed for the transaction // to succeed. @@ -220,10 +231,11 @@ pub trait EstimateCall: Call { break }; - tx_env.set_gas_limit(mid_gas_limit); + let mut mid_tx_env = tx_env.clone(); + mid_tx_env.set_gas_limit(mid_gas_limit); // Execute transaction and handle potential gas errors, adjusting limits accordingly. - match self.transact(&mut db, evm_env.clone(), tx_env.clone()) { + match evm.transact(mid_tx_env).map_err(Self::Error::from_evm_err) { Err(err) if err.is_gas_too_high() => { // Decrease the highest gas limit if gas is too high highest_gas_limit = mid_gas_limit; @@ -278,34 +290,31 @@ pub trait EstimateCall: Call { /// or not #[inline] fn map_out_of_gas_err( - &self, - env_gas_limit: u64, - evm_env: EvmEnvFor, + evm: &mut EvmFor, mut tx_env: TxEnvFor, - db: &mut DB, - ) -> Self::Error + higher_gas_limit: u64, + ) -> Result where DB: Database, EthApiError: From, { let req_gas_limit = tx_env.gas_limit(); - tx_env.set_gas_limit(env_gas_limit); - let res = match self.transact(db, evm_env, tx_env) { - Ok(res) => res, - Err(err) => return err, - }; - match res.result { + tx_env.set_gas_limit(higher_gas_limit); + + let retry_res = evm.transact(tx_env).map_err(Self::Error::from_evm_err)?; + + match retry_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_eth_err() + // Transaction succeeded by manually increasing the gas limit, + // which means the caller lacks funds to pay for the tx + Err(RpcInvalidTransactionError::BasicOutOfGas(req_gas_limit).into_eth_err()) } ExecutionResult::Revert { output, .. } => { // reverted again after bumping the limit - RpcInvalidTransactionError::Revert(RevertError::new(output)).into_eth_err() + Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into_eth_err()) } ExecutionResult::Halt { reason, .. } => { - Self::Error::from_evm_halt(reason, req_gas_limit) + Err(Self::Error::from_evm_halt(reason, req_gas_limit)) } } } From b7867108167175808e310259aa38fbb13e1f4fff Mon Sep 17 00:00:00 2001 From: 0xMushow <105550256+0xMushow@users.noreply.github.com> Date: Sat, 21 Jun 2025 06:54:27 +0200 Subject: [PATCH 155/274] feat(transaction-pool): enforce EIP-2681 (#16967) --- crates/rpc/rpc-eth-types/src/error/mod.rs | 3 +++ crates/transaction-pool/src/error.rs | 5 +++++ crates/transaction-pool/src/validate/eth.rs | 9 +++++++++ 3 files changed, 17 insertions(+) diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index d0d698949ef..64630716fd2 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -851,6 +851,9 @@ impl From for RpcPoolError { } InvalidPoolTransactionError::OversizedData(_, _) => Self::OversizedData, InvalidPoolTransactionError::Underpriced => Self::Underpriced, + InvalidPoolTransactionError::Eip2681 => { + Self::Invalid(RpcInvalidTransactionError::NonceMaxValue) + } InvalidPoolTransactionError::Other(err) => Self::PoolTransactionError(err), InvalidPoolTransactionError::Eip4844(err) => Self::Eip4844(err), InvalidPoolTransactionError::Eip7702(err) => Self::Eip7702(err), diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs index e723dc0dc79..686c9456d39 100644 --- a/crates/transaction-pool/src/error.rs +++ b/crates/transaction-pool/src/error.rs @@ -247,6 +247,10 @@ pub enum InvalidPoolTransactionError { /// Balance of account. balance: U256, }, + /// EIP-2681 error thrown if the nonce is higher or equal than `U64::max` + /// `` + #[error("nonce exceeds u64 limit")] + Eip2681, /// EIP-4844 related errors #[error(transparent)] Eip4844(#[from] Eip4844PoolTransactionError), @@ -326,6 +330,7 @@ impl InvalidPoolTransactionError { Self::IntrinsicGasTooLow => true, Self::Overdraft { .. } => false, Self::Other(err) => err.is_bad_transaction(), + Self::Eip2681 => true, Self::Eip4844(eip4844_err) => { match eip4844_err { Eip4844PoolTransactionError::MissingEip4844BlobSidecar => { diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 7a6dbd06589..5044e2490cc 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -293,6 +293,15 @@ where } }; + // Reject transactions with a nonce equal to U64::max according to EIP-2681 + let tx_nonce = transaction.nonce(); + if tx_nonce == u64::MAX { + return Err(TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::Eip2681, + )) + } + // Reject transactions over defined size to prevent DOS attacks let tx_input_len = transaction.input().len(); if tx_input_len > self.max_tx_input_bytes { From 83802249ea6a43777ff060ad61ee66f0318c4fe0 Mon Sep 17 00:00:00 2001 From: kilavvy <140459108+kilavvy@users.noreply.github.com> Date: Sat, 21 Jun 2025 08:35:22 +0200 Subject: [PATCH 156/274] fix: Improve comment in historical RPC tests (#16971) --- crates/optimism/rpc/src/historical.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/optimism/rpc/src/historical.rs b/crates/optimism/rpc/src/historical.rs index 133bce6a7df..2f69b424fab 100644 --- a/crates/optimism/rpc/src/historical.rs +++ b/crates/optimism/rpc/src/historical.rs @@ -217,7 +217,7 @@ mod tests { assert!(result.is_none()); } - /// Tests that the function doesn't parse anyhing if the parameter is not a valid block id. + /// Tests that the function doesn't parse anything if the parameter is not a valid block id. #[test] fn returns_error_for_invalid_input() { let params = Params::new(Some(r#"[true]"#)); From 10f834486298707b8ea5488f2d9fe1e4c69330cc Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Sat, 21 Jun 2025 08:54:24 +0200 Subject: [PATCH 157/274] chore(sdk): Add default for noop component (#16570) Co-authored-by: Matthias Seitz --- crates/payload/builder/src/noop.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/payload/builder/src/noop.rs b/crates/payload/builder/src/noop.rs index cbf21f1cebf..6047bffa8b1 100644 --- a/crates/payload/builder/src/noop.rs +++ b/crates/payload/builder/src/noop.rs @@ -57,3 +57,10 @@ where } } } + +impl Default for NoopPayloadBuilderService { + fn default() -> Self { + let (service, _) = Self::new(); + service + } +} From 9939164d07b6b43eaa66a3834fa6ec3e4f0609d2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 21 Jun 2025 09:38:02 +0200 Subject: [PATCH 158/274] chore: remove unused features (#16963) --- crates/optimism/rpc/Cargo.toml | 2 +- crates/rpc/rpc-types-compat/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 3227dab63b9..58466d18c2b 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -35,7 +35,7 @@ reth-optimism-evm.workspace = true reth-optimism-payload-builder.workspace = true reth-optimism-txpool.workspace = true # TODO remove node-builder import -reth-optimism-primitives = { workspace = true, features = ["reth-codec", "serde-bincode-compat"] } +reth-optimism-primitives = { workspace = true, features = ["reth-codec", "serde-bincode-compat", "serde"] } reth-optimism-forks.workspace = true # ethereum diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index 822cb29076d..135fe9c07f5 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -14,19 +14,19 @@ workspace = true [dependencies] # reth reth-primitives-traits.workspace = true -reth-storage-api = { workspace = true, features = ["serde", "serde-bincode-compat"], optional = true } +reth-storage-api = { workspace = true, optional = true } reth-evm.workspace = true # ethereum alloy-primitives.workspace = true -alloy-rpc-types-eth = { workspace = true, default-features = false, features = ["serde"] } +alloy-rpc-types-eth = { workspace = true, features = ["serde"] } alloy-consensus.workspace = true alloy-network.workspace = true # optimism op-alloy-consensus = { workspace = true, optional = true } op-alloy-rpc-types = { workspace = true, optional = true } -reth-optimism-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"], optional = true } +reth-optimism-primitives = { workspace = true, optional = true } op-revm = { workspace = true, optional = true } # revm From 9cf910ce2eed65b7ff66a63adeec3e1a3470425a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 21 Jun 2025 11:46:52 +0200 Subject: [PATCH 159/274] refactor: remove CallFees re-export and relocate tests (#16981) --- crates/rpc/rpc-eth-types/src/revm_utils.rs | 113 -------------------- crates/rpc/rpc-types-compat/src/fees.rs | 116 +++++++++++++++++++++ 2 files changed, 116 insertions(+), 113 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 47671b89879..83deef53460 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -15,8 +15,6 @@ use std::collections::{BTreeMap, HashMap}; use super::{EthApiError, EthResult, RpcInvalidTransactionError}; -pub use reth_rpc_types_compat::CallFees; - /// Calculates the caller gas allowance. /// /// `allowance = (account.balance - tx.value) / tx.gas_price` @@ -206,120 +204,9 @@ where #[cfg(test)] mod tests { use super::*; - use alloy_consensus::constants::GWEI_TO_WEI; use alloy_primitives::{address, bytes}; use reth_revm::db::EmptyDB; - #[test] - fn test_ensure_0_fallback() { - let CallFees { gas_price, .. } = - CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) - .unwrap(); - assert!(gas_price.is_zero()); - } - - #[test] - fn test_ensure_max_fee_0_exception() { - let CallFees { gas_price, .. } = - CallFees::ensure_fees(None, Some(U256::ZERO), None, U256::from(99), None, None, None) - .unwrap(); - assert!(gas_price.is_zero()); - } - - #[test] - fn test_blob_fees() { - let CallFees { gas_price, max_fee_per_blob_gas, .. } = - CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) - .unwrap(); - assert!(gas_price.is_zero()); - assert_eq!(max_fee_per_blob_gas, None); - - let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( - None, - None, - None, - U256::from(99), - Some(&[B256::from(U256::ZERO)]), - None, - Some(U256::from(99)), - ) - .unwrap(); - assert!(gas_price.is_zero()); - assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); - } - - #[test] - fn test_eip_1559_fees() { - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(25 * GWEI_TO_WEI)), - Some(U256::from(15 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); - - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(25 * GWEI_TO_WEI)), - Some(U256::from(5 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); - - let CallFees { gas_price, .. } = CallFees::ensure_fees( - None, - Some(U256::from(30 * GWEI_TO_WEI)), - Some(U256::from(30 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ) - .unwrap(); - assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::from(30 * GWEI_TO_WEI)), - Some(U256::from(31 * GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::from(5 * GWEI_TO_WEI)), - Some(U256::from(GWEI_TO_WEI)), - U256::from(15 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - - let call_fees = CallFees::ensure_fees( - None, - Some(U256::MAX), - Some(U256::MAX), - U256::from(5 * GWEI_TO_WEI), - None, - None, - Some(U256::ZERO), - ); - assert!(call_fees.is_err()); - } - #[test] fn state_override_state() { let code = bytes!( diff --git a/crates/rpc/rpc-types-compat/src/fees.rs b/crates/rpc/rpc-types-compat/src/fees.rs index 45ee7628fc5..46f8fc8c207 100644 --- a/crates/rpc/rpc-types-compat/src/fees.rs +++ b/crates/rpc/rpc-types-compat/src/fees.rs @@ -163,3 +163,119 @@ pub enum CallFeesError { #[error("blob transaction missing blob hashes")] BlobTransactionMissingBlobHashes, } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::constants::GWEI_TO_WEI; + + #[test] + fn test_ensure_0_fallback() { + let CallFees { gas_price, .. } = + CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) + .unwrap(); + assert!(gas_price.is_zero()); + } + + #[test] + fn test_ensure_max_fee_0_exception() { + let CallFees { gas_price, .. } = + CallFees::ensure_fees(None, Some(U256::ZERO), None, U256::from(99), None, None, None) + .unwrap(); + assert!(gas_price.is_zero()); + } + + #[test] + fn test_blob_fees() { + let CallFees { gas_price, max_fee_per_blob_gas, .. } = + CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) + .unwrap(); + assert!(gas_price.is_zero()); + assert_eq!(max_fee_per_blob_gas, None); + + let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( + None, + None, + None, + U256::from(99), + Some(&[B256::from(U256::ZERO)]), + None, + Some(U256::from(99)), + ) + .unwrap(); + assert!(gas_price.is_zero()); + assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); + } + + #[test] + fn test_eip_1559_fees() { + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(25 * GWEI_TO_WEI)), + Some(U256::from(15 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); + + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(25 * GWEI_TO_WEI)), + Some(U256::from(5 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); + + let CallFees { gas_price, .. } = CallFees::ensure_fees( + None, + Some(U256::from(30 * GWEI_TO_WEI)), + Some(U256::from(30 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ) + .unwrap(); + assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::from(30 * GWEI_TO_WEI)), + Some(U256::from(31 * GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::from(5 * GWEI_TO_WEI)), + Some(U256::from(GWEI_TO_WEI)), + U256::from(15 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + + let call_fees = CallFees::ensure_fees( + None, + Some(U256::MAX), + Some(U256::MAX), + U256::from(5 * GWEI_TO_WEI), + None, + None, + Some(U256::ZERO), + ); + assert!(call_fees.is_err()); + } +} From ba1680447108076bf947b9847fd9edfd277734ce Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 21 Jun 2025 13:37:17 +0200 Subject: [PATCH 160/274] feat: add From impl for RecoveredBlock from blocks with recovered transactions (#16983) --- .../primitives-traits/src/block/recovered.rs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/crates/primitives-traits/src/block/recovered.rs b/crates/primitives-traits/src/block/recovered.rs index 4f06cd8b76f..3340342abbf 100644 --- a/crates/primitives-traits/src/block/recovered.rs +++ b/crates/primitives-traits/src/block/recovered.rs @@ -470,6 +470,44 @@ impl From> for Sealed { } } +/// Converts a block with recovered transactions into a [`RecoveredBlock`]. +/// +/// This implementation takes an `alloy_consensus::Block` where transactions are of type +/// `Recovered` (transactions with their recovered senders) and converts it into a +/// [`RecoveredBlock`] which stores transactions and senders separately for efficiency. +impl From, H>> + for RecoveredBlock> +where + T: SignedTransaction, + H: crate::block::header::BlockHeader, +{ + fn from(block: alloy_consensus::Block, H>) -> Self { + let header = block.header; + + // Split the recovered transactions into transactions and senders + let (transactions, senders): (Vec, Vec

) = block + .body + .transactions + .into_iter() + .map(|recovered| { + let (tx, sender) = recovered.into_parts(); + (tx, sender) + }) + .unzip(); + + // Reconstruct the block with regular transactions + let body = alloy_consensus::BlockBody { + transactions, + ommers: block.body.ommers, + withdrawals: block.body.withdrawals, + }; + + let block = alloy_consensus::Block::new(header, body); + + Self::new_unhashed(block, senders) + } +} + #[cfg(any(test, feature = "arbitrary"))] impl<'a, B> arbitrary::Arbitrary<'a> for RecoveredBlock where @@ -836,3 +874,48 @@ pub(super) mod serde_bincode_compat { } } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::{Header, TxLegacy}; + use alloy_primitives::{bytes, Signature, TxKind}; + + #[test] + fn test_from_block_with_recovered_transactions() { + let tx = TxLegacy { + chain_id: Some(1), + nonce: 0, + gas_price: 21_000_000_000, + gas_limit: 21_000, + to: TxKind::Call(Address::ZERO), + value: U256::ZERO, + input: bytes!(), + }; + + let signature = Signature::new(U256::from(1), U256::from(2), false); + let sender = Address::from([0x01; 20]); + + let signed_tx = alloy_consensus::TxEnvelope::Legacy( + alloy_consensus::Signed::new_unchecked(tx, signature, B256::ZERO), + ); + + let recovered_tx = Recovered::new_unchecked(signed_tx, sender); + + let header = Header::default(); + let body = alloy_consensus::BlockBody { + transactions: vec![recovered_tx], + ommers: vec![], + withdrawals: None, + }; + let block_with_recovered = alloy_consensus::Block::new(header, body); + + let recovered_block: RecoveredBlock< + alloy_consensus::Block, + > = block_with_recovered.into(); + + assert_eq!(recovered_block.senders().len(), 1); + assert_eq!(recovered_block.senders()[0], sender); + assert_eq!(recovered_block.body().transactions().count(), 1); + } +} From 9ce49a981ea2280d9c66dcd952cd319507a2bb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Narzis?= <78718413+lean-apple@users.noreply.github.com> Date: Sat, 21 Jun 2025 14:29:31 +0200 Subject: [PATCH 161/274] chore(era): complete doc for `ClientWithFakeIndex` (#16984) --- crates/era-utils/tests/it/history.rs | 46 +++------------------------- crates/era-utils/tests/it/main.rs | 41 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 41 deletions(-) diff --git a/crates/era-utils/tests/it/history.rs b/crates/era-utils/tests/it/history.rs index 65153b8d599..d1d9e994513 100644 --- a/crates/era-utils/tests/it/history.rs +++ b/crates/era-utils/tests/it/history.rs @@ -1,17 +1,16 @@ -use alloy_primitives::bytes::Bytes; -use futures_util::{Stream, TryStreamExt}; -use reqwest::{Client, IntoUrl, Url}; +use crate::{ClientWithFakeIndex, ITHACA_ERA_INDEX_URL}; +use reqwest::{Client, Url}; use reth_db_common::init::init_genesis; -use reth_era_downloader::{EraClient, EraStream, EraStreamConfig, HttpClient}; +use reth_era_downloader::{EraClient, EraStream, EraStreamConfig}; use reth_etl::Collector; use reth_provider::test_utils::create_test_provider_factory; -use std::{future::Future, str::FromStr}; +use std::str::FromStr; use tempfile::tempdir; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_history_imports_from_fresh_state_successfully() { // URL where the ERA1 files are hosted - let url = Url::from_str("https://era.ithaca.xyz/era1/index.html").unwrap(); + let url = Url::from_str(ITHACA_ERA_INDEX_URL).unwrap(); // Directory where the ERA1 files will be downloaded to let folder = tempdir().unwrap(); @@ -35,38 +34,3 @@ async fn test_history_imports_from_fresh_state_successfully() { assert_eq!(actual_block_number, expected_block_number); } - -/// An HTTP client pre-programmed with canned answer to index. -/// -/// Passes any other calls to a real HTTP client! -#[derive(Debug, Clone)] -struct ClientWithFakeIndex(Client); - -impl HttpClient for ClientWithFakeIndex { - fn get( - &self, - url: U, - ) -> impl Future< - Output = eyre::Result> + Send + Sync + Unpin>, - > + Send - + Sync { - let url = url.into_url().unwrap(); - - async move { - match url.to_string().as_str() { - "https://era.ithaca.xyz/era1/index.html" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from_static(b"mainnet-00000-5ec1ffb8.era1")) - }))) - as Box> + Send + Sync + Unpin>) - } - _ => { - let response = Client::get(&self.0, url).send().await?; - - Ok(Box::new(response.bytes_stream().map_err(|e| eyre::Error::new(e))) - as Box> + Send + Sync + Unpin>) - } - } - } - } -} diff --git a/crates/era-utils/tests/it/main.rs b/crates/era-utils/tests/it/main.rs index 9a035cdf7da..79e84418dc4 100644 --- a/crates/era-utils/tests/it/main.rs +++ b/crates/era-utils/tests/it/main.rs @@ -3,3 +3,44 @@ mod history; const fn main() {} + +use alloy_primitives::bytes::Bytes; +use futures_util::{Stream, TryStreamExt}; +use reqwest::{Client, IntoUrl}; +use reth_era_downloader::HttpClient; + +// Url where the ERA1 files are hosted +const ITHACA_ERA_INDEX_URL: &str = "https://era.ithaca.xyz/era1/index.html"; + +// The response containing one file that the fake client will return when the index Url is requested +const GENESIS_ITHACA_INDEX_RESPONSE: &[u8] = b"mainnet-00000-5ec1ffb8.era1"; + +/// An HTTP client that fakes the file list to always show one known file +/// +/// but passes all other calls including actual downloads to a real HTTP client +/// +/// In that way, only one file is used but downloads are still performed from the original source. +#[derive(Debug, Clone)] +struct ClientWithFakeIndex(Client); + +impl HttpClient for ClientWithFakeIndex { + async fn get( + &self, + url: U, + ) -> eyre::Result> + Send + Sync + Unpin> { + let url = url.into_url().unwrap(); + + match url.to_string().as_str() { + ITHACA_ERA_INDEX_URL => Ok(Box::new(futures::stream::once(Box::pin(async move { + Ok(bytes::Bytes::from_static(GENESIS_ITHACA_INDEX_RESPONSE)) + }))) + as Box> + Send + Sync + Unpin>), + _ => { + let response = Client::get(&self.0, url).send().await?; + + Ok(Box::new(response.bytes_stream().map_err(|e| eyre::Error::new(e))) + as Box> + Send + Sync + Unpin>) + } + } + } +} From 6ee5006ac0eef43bbb3bdf8802701533434e4de3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 21 Jun 2025 15:19:01 +0200 Subject: [PATCH 162/274] chore: relax localpending block bounds (#16979) --- crates/optimism/rpc/src/eth/pending_block.rs | 16 +++++--------- .../rpc/rpc/src/eth/helpers/pending_block.rs | 22 ++++++++----------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs index 684207fde8a..916a4787066 100644 --- a/crates/optimism/rpc/src/eth/pending_block.rs +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -9,7 +9,6 @@ use reth_evm::ConfigureEvm; use reth_node_api::NodePrimitives; use reth_optimism_evm::OpNextBlockEnvAttributes; use reth_optimism_forks::OpHardforks; -use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned}; use reth_primitives_traits::{RecoveredBlock, SealedHeader}; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, @@ -33,17 +32,13 @@ where Error: FromEvmError, >, N: RpcNodeCore< - Provider: BlockReaderIdExt< - Transaction = OpTransactionSigned, - Block = OpBlock, - Receipt = OpReceipt, - Header = alloy_consensus::Header, - > + ChainSpecProvider + Provider: BlockReaderIdExt + + ChainSpecProvider + StateProviderFactory, Pool: TransactionPool>>, Evm: ConfigureEvm< Primitives = ::Primitives, - NextBlockEnvCtx = OpNextBlockEnvAttributes, + NextBlockEnvCtx: From, >, Primitives: NodePrimitives< BlockHeader = ProviderHeader, @@ -72,8 +67,9 @@ where prev_randao: B256::random(), gas_limit: parent.gas_limit(), parent_beacon_block_root: parent.parent_beacon_block_root(), - extra_data: parent.extra_data.clone(), - }) + extra_data: parent.extra_data().clone(), + } + .into()) } /// Returns the locally built pending block diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index 46fb7e7b0ef..3308cac7984 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -24,22 +24,20 @@ impl LoadPendingBlock for EthApi where Self: SpawnBlocking< - NetworkTypes: RpcTypes
, + NetworkTypes: RpcTypes< + Header = alloy_rpc_types_eth::Header>, + >, Error: FromEvmError, > + RpcNodeCore< - Provider: BlockReaderIdExt< - Transaction = reth_ethereum_primitives::TransactionSigned, - Block = reth_ethereum_primitives::Block, - Receipt = reth_ethereum_primitives::Receipt, - Header = alloy_consensus::Header, - > + ChainSpecProvider + Provider: BlockReaderIdExt + + ChainSpecProvider + StateProviderFactory, Pool: TransactionPool< Transaction: PoolTransaction>, >, Evm: ConfigureEvm< Primitives = ::Primitives, - NextBlockEnvCtx = NextBlockEnvAttributes, + NextBlockEnvCtx: From, >, Primitives: NodePrimitives< BlockHeader = ProviderHeader, @@ -48,10 +46,7 @@ where Block = ProviderBlock, >, >, - Provider: BlockReader< - Block = reth_ethereum_primitives::Block, - Receipt = reth_ethereum_primitives::Receipt, - >, + Provider: BlockReader, { #[inline] fn pending_block( @@ -73,6 +68,7 @@ where gas_limit: parent.gas_limit(), parent_beacon_block_root: parent.parent_beacon_block_root().map(|_| B256::ZERO), withdrawals: None, - }) + } + .into()) } } From 02bbcc83679d6ca7c21629724a9986ea5471bd8a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 21 Jun 2025 16:03:44 +0200 Subject: [PATCH 163/274] fix: use empty withdrawals if parent has withdrawals root (#16980) --- crates/rpc/rpc/src/eth/helpers/pending_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index 3308cac7984..f86170768cc 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -67,7 +67,7 @@ where prev_randao: B256::random(), gas_limit: parent.gas_limit(), parent_beacon_block_root: parent.parent_beacon_block_root().map(|_| B256::ZERO), - withdrawals: None, + withdrawals: parent.withdrawals_root().map(|_| Default::default()), } .into()) } From 7e9f1416047f1dc803a366661a561c1e1b9abd2d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 21 Jun 2025 16:26:13 +0200 Subject: [PATCH 164/274] chore: simplify test HttpClient implementations (#16985) --- Cargo.lock | 1 + crates/era-downloader/tests/it/checksums.rs | 103 ++++--------------- crates/era-downloader/tests/it/main.rs | 104 ++++---------------- crates/era-utils/Cargo.toml | 1 + crates/era-utils/tests/it/main.rs | 20 ++-- 5 files changed, 55 insertions(+), 174 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20e79650468..e4d99327f8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8058,6 +8058,7 @@ dependencies = [ "reth-storage-api", "tempfile", "tokio", + "tokio-util", "tracing", ] diff --git a/crates/era-downloader/tests/it/checksums.rs b/crates/era-downloader/tests/it/checksums.rs index 46f889adf7c..630cbece5d4 100644 --- a/crates/era-downloader/tests/it/checksums.rs +++ b/crates/era-downloader/tests/it/checksums.rs @@ -3,7 +3,7 @@ use futures::Stream; use futures_util::StreamExt; use reqwest::{IntoUrl, Url}; use reth_era_downloader::{EraClient, EraStream, EraStreamConfig, HttpClient}; -use std::{future::Future, str::FromStr}; +use std::str::FromStr; use tempfile::tempdir; use test_case::test_case; @@ -54,91 +54,30 @@ const CHECKSUMS: &[u8] = b"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa struct FailingClient; impl HttpClient for FailingClient { - fn get( + async fn get( &self, url: U, - ) -> impl Future< - Output = eyre::Result> + Send + Sync + Unpin>, - > + Send - + Sync { + ) -> eyre::Result> + Send + Sync + Unpin> { let url = url.into_url().unwrap(); - async move { - match url.to_string().as_str() { - "https://mainnet.era1.nimbus.team/" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::NIMBUS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::ETH_PORTAL)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/index.html" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::ITHACA)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://mainnet.era1.nimbus.team/checksums.txt" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(CHECKSUMS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/checksums.txt" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(CHECKSUMS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/checksums.txt" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(CHECKSUMS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/mainnet-00000-5ec1ffb8.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::MAINNET_0)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://mainnet.era1.nimbus.team/mainnet-00000-5ec1ffb8.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::MAINNET_0)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/mainnet-00000-5ec1ffb8.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::MAINNET_0)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/mainnet-00001-a5364e9a.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::MAINNET_1)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://mainnet.era1.nimbus.team/mainnet-00001-a5364e9a.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::MAINNET_1)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/mainnet-00001-a5364e9a.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(crate::MAINNET_1)) - }))) - as Box> + Send + Sync + Unpin>) - } - v => unimplemented!("Unexpected URL \"{v}\""), + Ok(futures::stream::iter(vec![Ok(match url.to_string().as_str() { + "https://mainnet.era1.nimbus.team/" => Bytes::from_static(crate::NIMBUS), + "https://era1.ethportal.net/" => Bytes::from_static(crate::ETH_PORTAL), + "https://era.ithaca.xyz/era1/index.html" => Bytes::from_static(crate::ITHACA), + "https://mainnet.era1.nimbus.team/checksums.txt" | + "https://era1.ethportal.net/checksums.txt" | + "https://era.ithaca.xyz/era1/checksums.txt" => Bytes::from_static(CHECKSUMS), + "https://era1.ethportal.net/mainnet-00000-5ec1ffb8.era1" | + "https://mainnet.era1.nimbus.team/mainnet-00000-5ec1ffb8.era1" | + "https://era.ithaca.xyz/era1/mainnet-00000-5ec1ffb8.era1" => { + Bytes::from_static(crate::MAINNET_0) } - } + "https://era1.ethportal.net/mainnet-00001-a5364e9a.era1" | + "https://mainnet.era1.nimbus.team/mainnet-00001-a5364e9a.era1" | + "https://era.ithaca.xyz/era1/mainnet-00001-a5364e9a.era1" => { + Bytes::from_static(crate::MAINNET_1) + } + v => unimplemented!("Unexpected URL \"{v}\""), + })])) } } diff --git a/crates/era-downloader/tests/it/main.rs b/crates/era-downloader/tests/it/main.rs index 26ba4e6143e..526d3885bff 100644 --- a/crates/era-downloader/tests/it/main.rs +++ b/crates/era-downloader/tests/it/main.rs @@ -9,10 +9,9 @@ mod stream; const fn main() {} use bytes::Bytes; -use futures_util::Stream; +use futures::Stream; use reqwest::IntoUrl; use reth_era_downloader::HttpClient; -use std::future::Future; pub(crate) const NIMBUS: &[u8] = include_bytes!("../res/nimbus.html"); pub(crate) const ETH_PORTAL: &[u8] = include_bytes!("../res/ethportal.html"); @@ -27,91 +26,30 @@ pub(crate) const MAINNET_1: &[u8] = include_bytes!("../res/mainnet-00001-a5364e9 struct StubClient; impl HttpClient for StubClient { - fn get( + async fn get( &self, url: U, - ) -> impl Future< - Output = eyre::Result> + Send + Sync + Unpin>, - > + Send - + Sync { + ) -> eyre::Result> + Send + Sync + Unpin> { let url = url.into_url().unwrap(); - async move { - match url.to_string().as_str() { - "https://mainnet.era1.nimbus.team/" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(NIMBUS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(ETH_PORTAL)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/index.html" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(ITHACA)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://mainnet.era1.nimbus.team/checksums.txt" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(CHECKSUMS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/checksums.txt" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(CHECKSUMS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/checksums.txt" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(CHECKSUMS)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/mainnet-00000-5ec1ffb8.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(MAINNET_0)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://mainnet.era1.nimbus.team/mainnet-00000-5ec1ffb8.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(MAINNET_0)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/mainnet-00000-5ec1ffb8.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(MAINNET_0)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era1.ethportal.net/mainnet-00001-a5364e9a.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(MAINNET_1)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://mainnet.era1.nimbus.team/mainnet-00001-a5364e9a.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(MAINNET_1)) - }))) - as Box> + Send + Sync + Unpin>) - } - "https://era.ithaca.xyz/era1/mainnet-00001-a5364e9a.era1" => { - Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from(MAINNET_1)) - }))) - as Box> + Send + Sync + Unpin>) - } - v => unimplemented!("Unexpected URL \"{v}\""), + Ok(futures::stream::iter(vec![Ok(match url.to_string().as_str() { + "https://mainnet.era1.nimbus.team/" => Bytes::from_static(NIMBUS), + "https://era1.ethportal.net/" => Bytes::from_static(ETH_PORTAL), + "https://era.ithaca.xyz/era1/index.html" => Bytes::from_static(ITHACA), + "https://mainnet.era1.nimbus.team/checksums.txt" | + "https://era1.ethportal.net/checksums.txt" | + "https://era.ithaca.xyz/era1/checksums.txt" => Bytes::from_static(CHECKSUMS), + "https://era1.ethportal.net/mainnet-00000-5ec1ffb8.era1" | + "https://mainnet.era1.nimbus.team/mainnet-00000-5ec1ffb8.era1" | + "https://era.ithaca.xyz/era1/mainnet-00000-5ec1ffb8.era1" => { + Bytes::from_static(MAINNET_0) } - } + "https://era1.ethportal.net/mainnet-00001-a5364e9a.era1" | + "https://mainnet.era1.nimbus.team/mainnet-00001-a5364e9a.era1" | + "https://era.ithaca.xyz/era1/mainnet-00001-a5364e9a.era1" => { + Bytes::from_static(MAINNET_1) + } + v => unimplemented!("Unexpected URL \"{v}\""), + })])) } } diff --git a/crates/era-utils/Cargo.toml b/crates/era-utils/Cargo.toml index c24843c7a0d..006e084aec8 100644 --- a/crates/era-utils/Cargo.toml +++ b/crates/era-utils/Cargo.toml @@ -42,6 +42,7 @@ reth-db-common.workspace = true # async tokio.workspace = true tokio.features = ["fs", "io-util", "macros", "rt-multi-thread"] +tokio-util.workspace = true futures.workspace = true bytes.workspace = true diff --git a/crates/era-utils/tests/it/main.rs b/crates/era-utils/tests/it/main.rs index 79e84418dc4..bd49aca6879 100644 --- a/crates/era-utils/tests/it/main.rs +++ b/crates/era-utils/tests/it/main.rs @@ -5,9 +5,10 @@ mod history; const fn main() {} use alloy_primitives::bytes::Bytes; -use futures_util::{Stream, TryStreamExt}; +use futures_util::{stream, Stream, TryStreamExt}; use reqwest::{Client, IntoUrl}; use reth_era_downloader::HttpClient; +use tokio_util::either::Either; // Url where the ERA1 files are hosted const ITHACA_ERA_INDEX_URL: &str = "https://era.ithaca.xyz/era1/index.html"; @@ -28,18 +29,19 @@ impl HttpClient for ClientWithFakeIndex { &self, url: U, ) -> eyre::Result> + Send + Sync + Unpin> { - let url = url.into_url().unwrap(); + let url = url.into_url()?; match url.to_string().as_str() { - ITHACA_ERA_INDEX_URL => Ok(Box::new(futures::stream::once(Box::pin(async move { - Ok(bytes::Bytes::from_static(GENESIS_ITHACA_INDEX_RESPONSE)) - }))) - as Box> + Send + Sync + Unpin>), + ITHACA_ERA_INDEX_URL => { + // Create a static stream without boxing + let stream = + stream::iter(vec![Ok(Bytes::from_static(GENESIS_ITHACA_INDEX_RESPONSE))]); + Ok(Either::Left(stream)) + } _ => { let response = Client::get(&self.0, url).send().await?; - - Ok(Box::new(response.bytes_stream().map_err(|e| eyre::Error::new(e))) - as Box> + Send + Sync + Unpin>) + let stream = response.bytes_stream().map_err(|e| eyre::Error::new(e)); + Ok(Either::Right(stream)) } } } From 0131267e3f95bbad66dbc881e9688dcd24a8e928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Sun, 22 Jun 2025 09:32:15 +0200 Subject: [PATCH 165/274] feat(rpc): Replace associated type `Transaction` with `Network` in `TransactionCompat` (#16973) --- Cargo.lock | 2 +- crates/optimism/rpc/src/eth/pending_block.rs | 3 +- .../rpc-eth-api/src/helpers/pending_block.rs | 2 ++ crates/rpc/rpc-eth-api/src/types.rs | 34 +++---------------- crates/rpc/rpc-eth-types/src/simulate.rs | 4 +-- crates/rpc/rpc-eth-types/src/transaction.rs | 4 +-- crates/rpc/rpc-types-compat/Cargo.toml | 2 +- crates/rpc/rpc-types-compat/src/lib.rs | 3 ++ crates/rpc/rpc-types-compat/src/rpc.rs | 26 ++++++++++++++ .../rpc/rpc-types-compat/src/transaction.rs | 32 ++++++++--------- crates/rpc/rpc/src/eth/filter.rs | 10 +++--- crates/rpc/rpc/src/eth/helpers/block.rs | 2 ++ .../rpc/rpc/src/eth/helpers/pending_block.rs | 5 +-- crates/rpc/rpc/src/txpool.rs | 16 +++++---- 14 files changed, 81 insertions(+), 64 deletions(-) create mode 100644 crates/rpc/rpc-types-compat/src/rpc.rs diff --git a/Cargo.lock b/Cargo.lock index e4d99327f8f..bbec2bcc96f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10082,6 +10082,7 @@ name = "reth-rpc-types-compat" version = "1.4.8" dependencies = [ "alloy-consensus", + "alloy-json-rpc", "alloy-network", "alloy-primitives", "alloy-rpc-types-eth", @@ -10094,7 +10095,6 @@ dependencies = [ "reth-primitives-traits", "reth-storage-api", "revm-context", - "serde", "thiserror 2.0.12", ] diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs index 916a4787066..2b0a7362ed2 100644 --- a/crates/optimism/rpc/src/eth/pending_block.rs +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -13,7 +13,7 @@ use reth_primitives_traits::{RecoveredBlock, SealedHeader}; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, types::RpcTypes, - EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore, + EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore, TransactionCompat, }; use reth_rpc_eth_types::{EthApiError, PendingBlock}; use reth_storage_api::{ @@ -30,6 +30,7 @@ where Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, + TransactionCompat: TransactionCompat, >, N: RpcNodeCore< Provider: BlockReaderIdExt diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index 62202c5b664..25cafe92b7c 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -19,6 +19,7 @@ use reth_primitives_traits::{ }; use reth_revm::{database::StateProviderDatabase, db::State}; use reth_rpc_eth_types::{EthApiError, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{ BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, @@ -41,6 +42,7 @@ pub trait LoadPendingBlock: Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, + TransactionCompat: TransactionCompat, > + RpcNodeCore< Provider: BlockReaderIdExt + ChainSpecProvider diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index 5eafcdd0b01..de13ce68421 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -1,8 +1,6 @@ //! Trait for specifying `eth` network dependent API types. use crate::{AsEthApiError, FromEthApiError, RpcNodeCore}; -use alloy_json_rpc::RpcObject; -use alloy_network::{Network, ReceiptResponse, TransactionResponse}; use alloy_rpc_types_eth::Block; use reth_chain_state::CanonStateSubscriptions; use reth_rpc_types_compat::TransactionCompat; @@ -13,32 +11,13 @@ use std::{ fmt::{self}, }; -/// RPC types used by the `eth_` RPC API. -/// -/// This is a subset of [`alloy_network::Network`] trait with only RPC response types kept. -pub trait RpcTypes { - /// Header response type. - type Header: RpcObject; - /// Receipt response type. - type Receipt: RpcObject + ReceiptResponse; - /// Transaction response type. - type Transaction: RpcObject + TransactionResponse; -} - -impl RpcTypes for T -where - T: Network, -{ - type Header = T::HeaderResponse; - type Receipt = T::ReceiptResponse; - type Transaction = T::TransactionResponse; -} +pub use reth_rpc_types_compat::{RpcTransaction, RpcTypes}; /// Network specific `eth` API types. /// /// This trait defines the network specific rpc types and helpers required for the `eth_` and -/// adjacent endpoints. `NetworkTypes` is [`Network`] as defined by the alloy crate, see also -/// [`alloy_network::Ethereum`]. +/// adjacent endpoints. `NetworkTypes` is [`alloy_network::Network`] as defined by the alloy crate, +/// see also [`alloy_network::Ethereum`]. /// /// This type is stateful so that it can provide additional context if necessary, e.g. populating /// receipts with additional data. @@ -59,9 +38,6 @@ pub trait EthApiTypes: Send + Sync + Clone { fn tx_resp_builder(&self) -> &Self::TransactionCompat; } -/// Adapter for network specific transaction type. -pub type RpcTransaction = ::Transaction; - /// Adapter for network specific block type. pub type RpcBlock = Block, RpcHeader>; @@ -85,7 +61,7 @@ where > + EthApiTypes< TransactionCompat: TransactionCompat< Primitives = ::Primitives, - Transaction = RpcTransaction, + Network = Self::NetworkTypes, Error = RpcError, >, >, @@ -101,7 +77,7 @@ impl FullEthApiTypes for T where > + EthApiTypes< TransactionCompat: TransactionCompat< Primitives = ::Primitives, - Transaction = RpcTransaction, + Network = Self::NetworkTypes, Error = RpcError, >, > diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 51a30e861b7..42c0b618c90 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -23,7 +23,7 @@ use reth_primitives_traits::{ block::BlockTx, BlockBody as _, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction, }; use reth_rpc_server_types::result::rpc_err; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::{RpcTransaction, TransactionCompat}; use reth_storage_api::noop::NoopProvider; use revm::{ context_interface::result::ExecutionResult, @@ -191,7 +191,7 @@ pub fn build_simulated_block( results: Vec>, full_transactions: bool, tx_resp_builder: &T, -) -> Result>>, T::Error> +) -> Result, Header>>, T::Error> where T: TransactionCompat< Primitives: NodePrimitives>, diff --git a/crates/rpc/rpc-eth-types/src/transaction.rs b/crates/rpc/rpc-eth-types/src/transaction.rs index de11acc8dc8..ddc5dbe0b4d 100644 --- a/crates/rpc/rpc-eth-types/src/transaction.rs +++ b/crates/rpc/rpc-eth-types/src/transaction.rs @@ -6,7 +6,7 @@ use alloy_primitives::B256; use alloy_rpc_types_eth::TransactionInfo; use reth_ethereum_primitives::TransactionSigned; use reth_primitives_traits::{NodePrimitives, Recovered, SignedTransaction}; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::{RpcTransaction, TransactionCompat}; /// Represents from where a transaction was fetched. #[derive(Debug, Clone, Eq, PartialEq)] @@ -42,7 +42,7 @@ impl TransactionSource { pub fn into_transaction( self, resp_builder: &Builder, - ) -> Result + ) -> Result, Builder::Error> where Builder: TransactionCompat>, { diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-types-compat/Cargo.toml index 135fe9c07f5..f78cdbff673 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-types-compat/Cargo.toml @@ -22,6 +22,7 @@ alloy-primitives.workspace = true alloy-rpc-types-eth = { workspace = true, features = ["serde"] } alloy-consensus.workspace = true alloy-network.workspace = true +alloy-json-rpc.workspace = true # optimism op-alloy-consensus = { workspace = true, optional = true } @@ -33,7 +34,6 @@ op-revm = { workspace = true, optional = true } revm-context.workspace = true # io -serde.workspace = true jsonrpsee-types.workspace = true # error diff --git a/crates/rpc/rpc-types-compat/src/lib.rs b/crates/rpc/rpc-types-compat/src/lib.rs index a7e2ad99515..d08da214b53 100644 --- a/crates/rpc/rpc-types-compat/src/lib.rs +++ b/crates/rpc/rpc-types-compat/src/lib.rs @@ -11,8 +11,11 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod fees; +mod rpc; pub mod transaction; + pub use fees::{CallFees, CallFeesError}; +pub use rpc::*; pub use transaction::{ EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, TransactionConversionError, TryIntoSimTx, TxInfoMapper, diff --git a/crates/rpc/rpc-types-compat/src/rpc.rs b/crates/rpc/rpc-types-compat/src/rpc.rs new file mode 100644 index 00000000000..f9a80815560 --- /dev/null +++ b/crates/rpc/rpc-types-compat/src/rpc.rs @@ -0,0 +1,26 @@ +use alloy_json_rpc::RpcObject; +use alloy_network::{Network, ReceiptResponse, TransactionResponse}; + +/// RPC types used by the `eth_` RPC API. +/// +/// This is a subset of [`Network`] trait with only RPC response types kept. +pub trait RpcTypes { + /// Header response type. + type Header: RpcObject; + /// Receipt response type. + type Receipt: RpcObject + ReceiptResponse; + /// Transaction response type. + type Transaction: RpcObject + TransactionResponse; +} + +impl RpcTypes for T +where + T: Network, +{ + type Header = T::HeaderResponse; + type Receipt = T::ReceiptResponse; + type Transaction = T::TransactionResponse; +} + +/// Adapter for network specific transaction type. +pub type RpcTransaction = ::Transaction; diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index fc5afd3cf8e..88a0d1eb7a7 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -1,8 +1,10 @@ //! Compatibility functions for rpc `Transaction` type. -use crate::fees::{CallFees, CallFeesError}; +use crate::{ + fees::{CallFees, CallFeesError}, + RpcTransaction, RpcTypes, +}; use alloy_consensus::{error::ValueError, transaction::Recovered, EthereumTxEnvelope, TxEip4844}; -use alloy_network::Network; use alloy_primitives::{Address, TxKind, U256}; use alloy_rpc_types_eth::{ request::{TransactionInputError, TransactionRequest}, @@ -15,17 +17,17 @@ use reth_evm::{ }; use reth_primitives_traits::{NodePrimitives, TxTy}; use revm_context::{BlockEnv, CfgEnv, TxEnv}; -use serde::{Deserialize, Serialize}; use std::{convert::Infallible, error::Error, fmt::Debug, marker::PhantomData}; use thiserror::Error; /// Builds RPC transaction w.r.t. network. pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { - /// The lower layer consensus types to convert from. + /// Associated lower layer consensus types to convert from and into types of [`Self::Network`]. type Primitives: NodePrimitives; - /// RPC transaction response type. - type Transaction: Serialize + for<'de> Deserialize<'de> + Send + Sync + Unpin + Clone + Debug; + /// Associated upper layer JSON-RPC API network requests and responses to convert from and into + /// types of [`Self::Primitives`]. + type Network: RpcTypes + Send + Sync + Unpin + Clone + Debug; /// A set of variables for executing a transaction. type TxEnv; @@ -39,7 +41,7 @@ pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { fn fill_pending( &self, tx: Recovered>, - ) -> Result { + ) -> Result, Self::Error> { self.fill(tx, TransactionInfo::default()) } @@ -52,7 +54,7 @@ pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { &self, tx: Recovered>, tx_inf: TransactionInfo, - ) -> Result; + ) -> Result, Self::Error>; /// Builds a fake transaction from a transaction request for inclusion into block built in /// `eth_simulateV1`. @@ -353,9 +355,9 @@ impl Default for RpcConverter { impl TransactionCompat for RpcConverter where N: NodePrimitives, - E: Network + Unpin, + E: RpcTypes + Send + Sync + Unpin + Clone + Debug, Evm: ConfigureEvm, - TxTy: IntoRpcTx<::TransactionResponse> + Clone + Debug, + TxTy: IntoRpcTx + Clone + Debug, TransactionRequest: TryIntoSimTx> + TryIntoTxEnv>, Err: From + From<>>::Err> @@ -365,17 +367,15 @@ where + Sync + Send + Into>, - Map: for<'a> TxInfoMapper< - &'a TxTy, - Out = as IntoRpcTx<::TransactionResponse>>::TxInfo, - > + Clone + Map: for<'a> TxInfoMapper<&'a TxTy, Out = as IntoRpcTx>::TxInfo> + + Clone + Debug + Unpin + Send + Sync, { type Primitives = N; - type Transaction = ::TransactionResponse; + type Network = E; type TxEnv = TxEnvFor; type Error = Err; @@ -383,7 +383,7 @@ where &self, tx: Recovered>, tx_info: TransactionInfo, - ) -> Result { + ) -> Result { let (tx, signer) = tx.into_parts(); let tx_info = self.mapper.try_map(&tx, tx_info)?; diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index f44d5b7c572..25c9f472669 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -415,7 +415,9 @@ struct EthFilterInner { impl EthFilterInner where - Eth: RpcNodeCoreExt + EthApiTypes + 'static, + Eth: RpcNodeCoreExt + + EthApiTypes + + 'static, { /// Access the underlying provider. fn provider(&self) -> &Eth::Provider { @@ -692,7 +694,7 @@ where } /// Returns all new pending transactions received since the last poll. - async fn drain(&self) -> FilterChanges { + async fn drain(&self) -> FilterChanges> { let mut pending_txs = Vec::new(); let mut prepared_stream = self.txs_stream.lock().await; @@ -718,13 +720,13 @@ trait FullTransactionsFilter: fmt::Debug + Send + Sync + Unpin + 'static { } #[async_trait] -impl FullTransactionsFilter +impl FullTransactionsFilter> for FullTransactionsReceiver where T: PoolTransaction + 'static, TxCompat: TransactionCompat> + 'static, { - async fn drain(&self) -> FilterChanges { + async fn drain(&self) -> FilterChanges> { Self::drain(self).await } } diff --git a/crates/rpc/rpc/src/eth/helpers/block.rs b/crates/rpc/rpc/src/eth/helpers/block.rs index 0cb0b57a423..508db22619b 100644 --- a/crates/rpc/rpc/src/eth/helpers/block.rs +++ b/crates/rpc/rpc/src/eth/helpers/block.rs @@ -11,6 +11,7 @@ use reth_rpc_eth_api::{ RpcNodeCore, RpcNodeCoreExt, RpcReceipt, }; use reth_rpc_eth_types::{EthApiError, EthReceiptBuilder}; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{BlockReader, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; @@ -21,6 +22,7 @@ where Self: LoadBlock< Error = EthApiError, NetworkTypes: RpcTypes, + TransactionCompat: TransactionCompat, Provider: BlockReader< Transaction = reth_ethereum_primitives::TransactionSigned, Receipt = reth_ethereum_primitives::Receipt, diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index f86170768cc..8e94f8ab2ab 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -1,5 +1,6 @@ //! Support for building a pending block with transactions from local view of mempool. +use crate::EthApi; use alloy_consensus::BlockHeader; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_evm::{ConfigureEvm, NextBlockEnvAttributes}; @@ -11,6 +12,7 @@ use reth_rpc_eth_api::{ FromEvmError, RpcNodeCore, }; use reth_rpc_eth_types::PendingBlock; +use reth_rpc_types_compat::TransactionCompat; use reth_storage_api::{ BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, StateProviderFactory, @@ -18,8 +20,6 @@ use reth_storage_api::{ use reth_transaction_pool::{PoolTransaction, TransactionPool}; use revm_primitives::B256; -use crate::EthApi; - impl LoadPendingBlock for EthApi where @@ -28,6 +28,7 @@ where Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, + TransactionCompat: TransactionCompat, > + RpcNodeCore< Provider: BlockReaderIdExt + ChainSpecProvider diff --git a/crates/rpc/rpc/src/txpool.rs b/crates/rpc/rpc/src/txpool.rs index 8c69aaf7e0b..ae9df3535a6 100644 --- a/crates/rpc/rpc/src/txpool.rs +++ b/crates/rpc/rpc/src/txpool.rs @@ -10,7 +10,8 @@ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_primitives_traits::NodePrimitives; use reth_rpc_api::TxPoolApiServer; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_eth_api::RpcTransaction; +use reth_rpc_types_compat::{RpcTypes, TransactionCompat}; use reth_transaction_pool::{ AllPoolTransactions, PoolConsensusTx, PoolTransaction, TransactionPool, }; @@ -38,11 +39,14 @@ where Pool: TransactionPool> + 'static, Eth: TransactionCompat>>, { - fn content(&self) -> Result, Eth::Error> { + fn content(&self) -> Result>, Eth::Error> { #[inline] fn insert( tx: &Tx, - content: &mut BTreeMap>, + content: &mut BTreeMap< + Address, + BTreeMap::Transaction>, + >, resp_builder: &RpcTxB, ) -> Result<(), RpcTxB::Error> where @@ -72,7 +76,7 @@ where } #[async_trait] -impl TxPoolApiServer for TxPoolApi +impl TxPoolApiServer> for TxPoolApi where Pool: TransactionPool> + 'static, Eth: TransactionCompat>> + 'static, @@ -129,7 +133,7 @@ where async fn txpool_content_from( &self, from: Address, - ) -> RpcResult> { + ) -> RpcResult>> { trace!(target: "rpc::eth", ?from, "Serving txpool_contentFrom"); Ok(self.content().map_err(Into::into)?.remove_from(&from)) } @@ -139,7 +143,7 @@ where /// /// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) for more details /// Handler for `txpool_content` - async fn txpool_content(&self) -> RpcResult> { + async fn txpool_content(&self) -> RpcResult>> { trace!(target: "rpc::eth", "Serving txpool_content"); Ok(self.content().map_err(Into::into)?) } From a0c3bbf92083ca54bdab4841b754c7b5370b112e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 22 Jun 2025 11:34:06 +0200 Subject: [PATCH 166/274] feat: add rpc header compat (#16988) --- crates/primitives-traits/src/header/sealed.rs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/primitives-traits/src/header/sealed.rs b/crates/primitives-traits/src/header/sealed.rs index b84a7fa622f..bcf69813f97 100644 --- a/crates/primitives-traits/src/header/sealed.rs +++ b/crates/primitives-traits/src/header/sealed.rs @@ -239,6 +239,34 @@ impl SealedHeader { } } +#[cfg(feature = "rpc-compat")] +mod rpc_compat { + use super::*; + + impl SealedHeader { + /// Converts this header into `alloy_rpc_types_eth::Header`. + /// + /// Note: This does not set the total difficulty or size of the block. + pub fn into_rpc_header(self) -> alloy_rpc_types_eth::Header + where + H: Sealable, + { + alloy_rpc_types_eth::Header::from_sealed(self.into()) + } + + /// Converts an `alloy_rpc_types_eth::Header` into a `SealedHeader`. + pub fn from_rpc_header(header: alloy_rpc_types_eth::Header) -> Self { + Self::new(header.inner, header.hash) + } + } + + impl From> for SealedHeader { + fn from(value: alloy_rpc_types_eth::Header) -> Self { + Self::from_rpc_header(value) + } + } +} + /// Bincode-compatible [`SealedHeader`] serde implementation. #[cfg(feature = "serde-bincode-compat")] pub(super) mod serde_bincode_compat { From 09f740d9308aae1d257658d4fcee1c8656d0bb69 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 22 Jun 2025 12:49:22 +0200 Subject: [PATCH 167/274] chore: use from conversion for txkind (#16990) --- crates/rpc/rpc-eth-api/src/helpers/call.rs | 2 +- crates/rpc/rpc-eth-types/src/simulate.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index e41c8262a03..e5ceb7e523c 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -190,7 +190,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let block = simulate::build_simulated_block( result.block, results, - return_full_transactions, + return_full_transactions.into(), this.tx_resp_builder(), )?; diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 42c0b618c90..1c82633b5b9 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -189,7 +189,7 @@ where pub fn build_simulated_block( block: RecoveredBlock, results: Vec>, - full_transactions: bool, + txs_kind: BlockTransactionsKind, tx_resp_builder: &T, ) -> Result, Header>>, T::Error> where @@ -256,9 +256,6 @@ where calls.push(call); } - let txs_kind = - if full_transactions { BlockTransactionsKind::Full } else { BlockTransactionsKind::Hashes }; - let block = block.into_rpc_block(txs_kind, |tx, tx_info| tx_resp_builder.fill(tx, tx_info))?; Ok(SimulatedBlock { inner: block, calls }) } From 45a63c615a0d019932275d3a2d3f1c034ca278ae Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:09:29 +0200 Subject: [PATCH 168/274] docs: fix error in HARDFORK-CHECKLIST.md (#16989) --- HARDFORK-CHECKLIST.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HARDFORK-CHECKLIST.md b/HARDFORK-CHECKLIST.md index fa69107a2c1..3e3628f0b4c 100644 --- a/HARDFORK-CHECKLIST.md +++ b/HARDFORK-CHECKLIST.md @@ -35,7 +35,7 @@ Adding a new versioned endpoint requires the same changes as for L1 just for the ### Hardforks -Opstack has dedicated hardkfors (e.g. Isthmus), that can be entirely opstack specific (e.g. Holocene) or can be an L1 +Opstack has dedicated hardforks (e.g. Isthmus), that can be entirely opstack specific (e.g. Holocene) or can be an L1 equivalent hardfork. Since opstack sticks to the L1 header primitive, a new L1 equivalent hardfork also requires new equivalent consensus checks. For this reason these `OpHardfork` must be mapped to L1 `EthereumHardfork`, for example: -`OpHardfork::Isthmus` corresponds to `EthereumHardfork::Prague`. These mappings must be defined in the `ChainSpec`. \ No newline at end of file +`OpHardfork::Isthmus` corresponds to `EthereumHardfork::Prague`. These mappings must be defined in the `ChainSpec`. From 0c862caa9129c6672d42449da4ed4abf3f4fe399 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:38:16 +0200 Subject: [PATCH 169/274] chore(deps): weekly `cargo update` (#16987) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- Cargo.lock | 292 ++++++++++++++++++++++++++--------------------------- 1 file changed, 146 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbec2bcc96f..19c301df09e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2492d3548408746e0d3fddcaeb033b1ee9041aa449dcf7df35fb18008fe921a2" +checksum = "3ab669be40024565acb719daf1b2a050e6dc065fc0bec6050d97a81cdb860bd7" dependencies = [ "alloy-consensus", "alloy-eips", @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9135eb501feccf7f4cb8a183afd406a65483fdad7bbd7332d0470e5d725c92f" +checksum = "7b95b3deca680efc7e9cba781f1a1db352fa1ea50e6384a514944dcf4419e652" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b26fdd571915bafe857fccba4ee1a4f352965800e46a53e4a5f50187b7776fa" +checksum = "15516116086325c157c18261d768a20677f0f699348000ed391d4ad0dcb82530" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -399,9 +399,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a326d47106039f38b811057215a92139f46eef7983a4b77b10930a0ea5685b1e" +checksum = "6177ed26655d4e84e00b65cb494d4e0b8830e7cae7ef5d63087d445a2600fb55" dependencies = [ "alloy-rlp", "arbitrary", @@ -512,7 +512,7 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -582,9 +582,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193dcf549a9383bc855eb1f209eb3aa0d2b1bcbc45998b92f905565f243cf130" +checksum = "5958f2310d69f4806e6f6b90ceb4f2b781cc5a843517a7afe2e7cfec6de3cfb9" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -749,23 +749,23 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4be1ce1274ddd7fdfac86e5ece1b225e9bba1f2327e20fbb30ee6b9cc1423fe" +checksum = "a14f21d053aea4c6630687c2f4ad614bed4c81e14737a9b904798b24f30ea849" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e92f3708ea4e0d9139001c86c051c538af0146944a2a9c7181753bd944bf57" +checksum = "34d99282e7c9ef14eb62727981a985a01869e586d1dec729d3bb33679094c100" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -774,16 +774,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afe1bd348a41f8c9b4b54dfb314886786d6201235b0b3f47198b9d910c86bb2" +checksum = "eda029f955b78e493360ee1d7bd11e1ab9f2a220a5715449babc79d6d0a01105" dependencies = [ "const-hex", "dunce", @@ -791,15 +791,15 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6195df2acd42df92a380a8db6205a5c7b41282d0ce3f4c665ecf7911ac292f1" +checksum = "10db1bd7baa35bc8d4a1b07efbf734e73e5ba09f2580fb8cee3483a36087ceb2" dependencies = [ "serde", "winnow", @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6185e98a79cf19010722f48a74b5a65d153631d2f038cabd250f4b9e9813b8ad" +checksum = "58377025a47d8b8426b3e4846a251f2c1991033b27f517aade368146f6ab1dfe" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -923,7 +923,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1014,7 +1014,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1156,7 +1156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1194,7 +1194,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1283,7 +1283,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.24" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d615619615a650c571269c00dca41db04b9210037fa76ed8239f70404ab56985" +checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" dependencies = [ "brotli", "flate2", @@ -1403,7 +1403,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1414,7 +1414,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1452,14 +1452,14 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backon" @@ -1578,7 +1578,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1790,7 +1790,7 @@ checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -1918,7 +1918,7 @@ checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2150,7 +2150,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2287,7 +2287,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -2625,7 +2625,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2649,7 +2649,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2660,7 +2660,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2713,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2772,7 +2772,7 @@ checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2783,7 +2783,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2804,7 +2804,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2814,7 +2814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2835,7 +2835,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "unicode-xid", ] @@ -2949,7 +2949,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3019,7 +3019,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3121,7 +3121,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3141,7 +3141,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3152,12 +3152,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -3217,7 +3217,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3846,7 +3846,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4365,7 +4365,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] @@ -4617,7 +4617,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4674,7 +4674,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4777,7 +4777,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4849,7 +4849,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5037,7 +5037,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5182,15 +5182,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.173" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libgit2-sys" -version = "0.18.1+1.9.0" +version = "0.18.2+1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dcb20f84ffcdd825c7a311ae347cce604a6f084a767dec4a4929829645290e" +checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" dependencies = [ "cc", "libc", @@ -5428,9 +5428,9 @@ dependencies = [ [[package]] name = "lz4_flex" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c592ad9fbc1b7838633b3ae55ce69b17d01150c72fcef229fbb819d39ee51ee" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" [[package]] name = "mach2" @@ -5449,7 +5449,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5504,7 +5504,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5883,7 +5883,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6227,7 +6227,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6343,7 +6343,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6372,7 +6372,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6500,12 +6500,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6556,7 +6556,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6631,7 +6631,7 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6654,7 +6654,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -6741,16 +6741,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -6764,9 +6764,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -6999,7 +6999,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -7102,7 +7102,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] @@ -7451,7 +7451,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -10934,7 +10934,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.103", + "syn 2.0.104", "unicode-ident", ] @@ -11027,7 +11027,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11040,7 +11040,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11098,7 +11098,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11325,7 +11325,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11401,7 +11401,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11722,7 +11722,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11735,7 +11735,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11757,9 +11757,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -11768,14 +11768,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c8c8f496c33dc6343dac05b4be8d9e0bca180a4caa81d7b8416b10cc2273cd" +checksum = "b9ac494e7266fcdd2ad80bf4375d55d27a117ea5c866c26d0e97fe5b3caeeb75" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11795,7 +11795,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11856,7 +11856,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11877,7 +11877,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11888,7 +11888,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "test-case-core", ] @@ -11928,7 +11928,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11976,7 +11976,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -11987,7 +11987,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12153,7 +12153,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12360,13 +12360,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12502,7 +12502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c032d68a49d25d9012a864fef1c64ac17aee43c87e0477bf7301d8ae8bfea7b7" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -12527,7 +12527,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12808,7 +12808,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -12888,7 +12888,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -12923,7 +12923,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12952,9 +12952,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" dependencies = [ "futures", "js-sys", @@ -12990,14 +12990,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.0", + "webpki-root-certs 1.0.1", ] [[package]] name = "webpki-root-certs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a83f7e1a9f8712695c03eabe9ed3fbca0feff0152f33f12593e5a6303cb1a4" +checksum = "86138b15b2b7d561bc4469e77027b8dd005a43dc502e9031d1f5afc8ce1f280e" dependencies = [ "rustls-pki-types", ] @@ -13008,14 +13008,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.0", + "webpki-roots 1.0.1", ] [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -13048,7 +13048,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -13156,7 +13156,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13167,7 +13167,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13178,7 +13178,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13189,7 +13189,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13200,7 +13200,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13211,7 +13211,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13694,7 +13694,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -13706,28 +13706,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13747,7 +13747,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -13768,7 +13768,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13812,7 +13812,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -13823,7 +13823,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] From 18cd06f306582d3d8a3dc0303d776f0bbd0e0b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Narzis?= <78718413+lean-apple@users.noreply.github.com> Date: Sun, 22 Jun 2025 16:35:19 +0200 Subject: [PATCH 170/274] docs: add `reth_fs_util` suggestion instead of `std::fs` to claude doc helper (#16992) --- CLAUDE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 80c3e7af032..5d53439d235 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ Reth is a high-performance Ethereum execution client written in Rust, focusing o 2. **Linting**: Run clippy with all features ```bash - RUSTFLAGS="-D warnings" cargo clippy --workspace --lib --examples --tests --benches --all-features --locked + RUSTFLAGS="-D warnings" cargo +nightly clippy --workspace --lib --examples --tests --benches --all-features --locked ``` 3. **Testing**: Use nextest for faster test execution @@ -147,6 +147,7 @@ mod tests { 1. **Avoid Allocations in Hot Paths**: Use references and borrowing 2. **Parallel Processing**: Use rayon for CPU-bound parallel work 3. **Async/Await**: Use tokio for I/O-bound operations +4. **File Operations**: Use `reth_fs_util` instead of `std::fs` for better error handling ### Common Pitfalls @@ -292,7 +293,7 @@ Let's say you want to fix a bug where external IP resolution fails on startup: cargo +nightly fmt --all # Run lints -RUSTFLAGS="-D warnings" cargo clippy --workspace --all-features --locked +RUSTFLAGS="-D warnings" cargo +nightly clippy --workspace --all-features --locked # Run tests cargo nextest run --workspace From 55fdebdc0efb75686a277bcc4c37dbca5ae0ef62 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra <160333583+Soubhik-10@users.noreply.github.com> Date: Sun, 22 Jun 2025 20:58:39 +0530 Subject: [PATCH 171/274] chore: changed example command (#16993) --- examples/beacon-api-sidecar-fetcher/src/main.rs | 2 +- examples/beacon-api-sse/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/beacon-api-sidecar-fetcher/src/main.rs b/examples/beacon-api-sidecar-fetcher/src/main.rs index 61cf1ce3410..382261a39d2 100644 --- a/examples/beacon-api-sidecar-fetcher/src/main.rs +++ b/examples/beacon-api-sidecar-fetcher/src/main.rs @@ -1,7 +1,7 @@ //! Run with //! //! ```sh -//! cargo run -p beacon-api-beacon-sidecar-fetcher --node --full +//! cargo run -p example-beacon-api-sidecar-fetcher -- node --full //! ``` //! //! This launches a regular reth instance and subscribes to payload attributes event stream. diff --git a/examples/beacon-api-sse/src/main.rs b/examples/beacon-api-sse/src/main.rs index 46bb0ddd444..fee20e09b1f 100644 --- a/examples/beacon-api-sse/src/main.rs +++ b/examples/beacon-api-sse/src/main.rs @@ -5,7 +5,7 @@ //! Run with //! //! ```sh -//! cargo run -p beacon-api-sse -- node +//! cargo run -p example-beacon-api-sse -- node //! ``` //! //! This launches a regular reth instance and subscribes to payload attributes event stream. From 9f710adee0af35d1968ef98bdc029432fc2a4278 Mon Sep 17 00:00:00 2001 From: Skylar Ray <137945430+sky-coderay@users.noreply.github.com> Date: Mon, 23 Jun 2025 11:36:47 +0300 Subject: [PATCH 172/274] chore: fix typo bootnode.rs (#16995) --- crates/cli/commands/src/p2p/bootnode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/commands/src/p2p/bootnode.rs b/crates/cli/commands/src/p2p/bootnode.rs index 9be60aca658..c27586b243f 100644 --- a/crates/cli/commands/src/p2p/bootnode.rs +++ b/crates/cli/commands/src/p2p/bootnode.rs @@ -10,7 +10,7 @@ use tokio::select; use tokio_stream::StreamExt; use tracing::info; -/// Satrt a discovery only bootnode. +/// Start a discovery only bootnode. #[derive(Parser, Debug)] pub struct Command { /// Listen address for the bootnode (default: ":30301"). From 2ba3d134a91223c61df450c5ddb8b0202bec7299 Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Mon, 23 Jun 2025 10:37:13 +0200 Subject: [PATCH 173/274] feat(test): rewrite test_engine_tree_live_sync_transition_eventually_canonical using e2e framework (#16972) --- .../src/testsuite/actions/mod.rs | 4 +- .../src/testsuite/actions/node_ops.rs | 115 ++++++++++ crates/engine/tree/src/tree/e2e_tests.rs | 65 +++++- crates/engine/tree/src/tree/tests.rs | 202 +----------------- 4 files changed, 183 insertions(+), 203 deletions(-) diff --git a/crates/e2e-test-utils/src/testsuite/actions/mod.rs b/crates/e2e-test-utils/src/testsuite/actions/mod.rs index a5ed75ed441..205eb9ac48e 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/mod.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/mod.rs @@ -17,7 +17,9 @@ pub mod reorg; pub use engine_api::{ExpectedPayloadStatus, SendNewPayload, SendNewPayloads}; pub use fork::{CreateFork, ForkBase, SetForkBase, SetForkBaseFromBlockInfo, ValidateFork}; -pub use node_ops::{CaptureBlockOnNode, CompareNodeChainTips, SelectActiveNode, ValidateBlockTag}; +pub use node_ops::{ + CaptureBlockOnNode, CompareNodeChainTips, SelectActiveNode, ValidateBlockTag, WaitForSync, +}; pub use produce_blocks::{ AssertMineBlock, BroadcastLatestForkchoice, BroadcastNextNewPayload, CheckPayloadAccepted, ExpectFcuStatus, GenerateNextPayload, GeneratePayloadAttributes, PickNextBlockProducer, diff --git a/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs b/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs index 3a240d8f644..022e3eb2d78 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs @@ -6,6 +6,8 @@ use eyre::Result; use futures_util::future::BoxFuture; use reth_node_api::EngineTypes; use reth_rpc_api::clients::EthApiClient; +use std::time::Duration; +use tokio::time::{sleep, timeout}; use tracing::debug; /// Action to select which node should be active for subsequent single-node operations. @@ -213,3 +215,116 @@ where }) } } + +/// Action that waits for two nodes to sync and have the same chain tip. +#[derive(Debug)] +pub struct WaitForSync { + /// First node index + pub node_a: usize, + /// Second node index + pub node_b: usize, + /// Maximum time to wait for sync (default: 30 seconds) + pub timeout_secs: u64, + /// Polling interval (default: 1 second) + pub poll_interval_secs: u64, +} + +impl WaitForSync { + /// Create a new `WaitForSync` action with default timeouts + pub const fn new(node_a: usize, node_b: usize) -> Self { + Self { node_a, node_b, timeout_secs: 30, poll_interval_secs: 1 } + } + + /// Set custom timeout + pub const fn with_timeout(mut self, timeout_secs: u64) -> Self { + self.timeout_secs = timeout_secs; + self + } + + /// Set custom poll interval + pub const fn with_poll_interval(mut self, poll_interval_secs: u64) -> Self { + self.poll_interval_secs = poll_interval_secs; + self + } +} + +impl Action for WaitForSync +where + Engine: EngineTypes, +{ + fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { + Box::pin(async move { + if self.node_a >= env.node_count() || self.node_b >= env.node_count() { + return Err(eyre::eyre!("Node index out of bounds")); + } + + let timeout_duration = Duration::from_secs(self.timeout_secs); + let poll_interval = Duration::from_secs(self.poll_interval_secs); + + debug!( + "Waiting for nodes {} and {} to sync (timeout: {}s, poll interval: {}s)", + self.node_a, self.node_b, self.timeout_secs, self.poll_interval_secs + ); + + let sync_check = async { + loop { + let node_a_client = &env.node_clients[self.node_a]; + let node_b_client = &env.node_clients[self.node_b]; + + // Get latest block from each node + let block_a = + EthApiClient::::block_by_number( + &node_a_client.rpc, + alloy_eips::BlockNumberOrTag::Latest, + false, + ) + .await? + .ok_or_else(|| { + eyre::eyre!("Failed to get latest block from node {}", self.node_a) + })?; + + let block_b = + EthApiClient::::block_by_number( + &node_b_client.rpc, + alloy_eips::BlockNumberOrTag::Latest, + false, + ) + .await? + .ok_or_else(|| { + eyre::eyre!("Failed to get latest block from node {}", self.node_b) + })?; + + debug!( + "Sync check: Node {} tip: {} (block {}), Node {} tip: {} (block {})", + self.node_a, + block_a.header.hash, + block_a.header.number, + self.node_b, + block_b.header.hash, + block_b.header.number + ); + + if block_a.header.hash == block_b.header.hash { + debug!( + "Nodes {} and {} successfully synced to block {} (hash: {})", + self.node_a, self.node_b, block_a.header.number, block_a.header.hash + ); + return Ok(()); + } + + sleep(poll_interval).await; + } + }; + + match timeout(timeout_duration, sync_check).await { + Ok(result) => result, + Err(_) => Err(eyre::eyre!( + "Timeout waiting for nodes {} and {} to sync after {}s", + self.node_a, + self.node_b, + self.timeout_secs + )), + } + }) + } +} diff --git a/crates/engine/tree/src/tree/e2e_tests.rs b/crates/engine/tree/src/tree/e2e_tests.rs index a2754d61adf..9eb6a64c885 100644 --- a/crates/engine/tree/src/tree/e2e_tests.rs +++ b/crates/engine/tree/src/tree/e2e_tests.rs @@ -7,7 +7,7 @@ use reth_e2e_test_utils::testsuite::{ actions::{ CaptureBlock, CompareNodeChainTips, CreateFork, ExpectFcuStatus, MakeCanonical, ProduceBlocks, ProduceBlocksLocally, ProduceInvalidBlocks, ReorgTo, SelectActiveNode, - SendNewPayloads, UpdateBlockInfo, ValidateCanonicalTag, + SendNewPayloads, UpdateBlockInfo, ValidateCanonicalTag, WaitForSync, }, setup::{NetworkSetup, Setup}, TestBuilder, @@ -269,3 +269,66 @@ async fn test_engine_tree_fcu_extends_canon_chain_e2e() -> Result<()> { Ok(()) } + +/// Test that verifies live sync transition where a long chain eventually becomes canonical. +/// +/// This test simulates a scenario where: +/// 1. Both nodes start with the same short base chain +/// 2. Node 0 builds a long chain locally (no broadcast, becomes its canonical tip) +/// 3. Node 1 still has only the short base chain as its canonical tip +/// 4. Node 1 receives FCU pointing to Node 0's long chain tip and must sync +/// 5. Both nodes end up with the same canonical chain through real P2P sync +#[tokio::test] +async fn test_engine_tree_live_sync_transition_eventually_canonical_e2e() -> Result<()> { + reth_tracing::init_test_tracing(); + + const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = 32; // EPOCH_SLOTS from alloy-eips + + let test = TestBuilder::new() + .with_setup( + Setup::default() + .with_chain_spec(Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis( + serde_json::from_str(include_str!( + "../../../../e2e-test-utils/src/testsuite/assets/genesis.json" + )) + .unwrap(), + ) + .cancun_activated() + .build(), + )) + .with_network(NetworkSetup::multi_node(2)) // Two connected nodes + .with_tree_config( + TreeConfig::default() + .with_legacy_state_root(false) + .with_has_enough_parallelism(true), + ), + ) + // Both nodes start with the same base chain (1 block) + .with_action(SelectActiveNode::new(0)) + .with_action(ProduceBlocks::::new(1)) + .with_action(MakeCanonical::new()) // Both nodes have the same base chain + .with_action(CaptureBlock::new("base_chain_tip")) + // Node 0: Build a much longer chain but don't broadcast it yet + .with_action(ProduceBlocksLocally::::new(MIN_BLOCKS_FOR_PIPELINE_RUN + 10)) + .with_action(MakeCanonical::with_active_node()) // Only make it canonical on Node 0 + .with_action(CaptureBlock::new("long_chain_tip")) + // Verify Node 0's canonical tip is the long chain tip + .with_action(ValidateCanonicalTag::new("long_chain_tip")) + // Verify Node 1's canonical tip is still the base chain tip + .with_action(SelectActiveNode::new(1)) + .with_action(ValidateCanonicalTag::new("base_chain_tip")) + // Node 1: Send FCU pointing to Node 0's long chain tip + // This should trigger Node 1 to sync the missing blocks from Node 0 + .with_action(ReorgTo::::new_from_tag("long_chain_tip")) + // Wait for Node 1 to sync with Node 0 + .with_action(WaitForSync::new(0, 1).with_timeout(60)) + // Verify both nodes end up with the same canonical chain + .with_action(CompareNodeChainTips::expect_same(0, 1)); + + test.run::().await?; + + Ok(()) +} diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index a00d4a56bdb..5c7e63ab634 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -90,7 +90,6 @@ struct TestHarness { from_tree_rx: UnboundedReceiver, blocks: Vec, action_rx: Receiver, - evm_config: MockEvmConfig, block_builder: TestBlockBuilder, provider: MockEthProvider, } @@ -147,7 +146,7 @@ impl TestHarness { // always assume enough parallelism for tests TreeConfig::default().with_legacy_state_root(true).with_has_enough_parallelism(true), EngineApiKind::Ethereum, - evm_config.clone(), + evm_config, ); let block_builder = TestBlockBuilder::default().with_chain_spec((*chain_spec).clone()); @@ -157,7 +156,6 @@ impl TestHarness { from_tree_rx, blocks: vec![], action_rx, - evm_config, block_builder, provider, } @@ -212,13 +210,6 @@ impl TestHarness { self } - fn extend_execution_outcome( - &self, - execution_outcomes: impl IntoIterator>, - ) { - self.evm_config.extend(execution_outcomes); - } - async fn fcu_to(&mut self, block_hash: B256, fcu_status: impl Into) { let fcu_status = fcu_status.into(); @@ -276,40 +267,6 @@ impl TestHarness { } } - async fn check_canon_commit(&mut self, hash: B256) { - let event = self.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::BeaconConsensus( - BeaconConsensusEngineEvent::CanonicalChainCommitted(header, _), - ) => { - assert_eq!(header.hash(), hash); - } - _ => panic!("Unexpected event: {event:#?}"), - } - } - - async fn check_canon_chain_insertion( - &mut self, - chain: impl IntoIterator> + Clone, - ) { - for block in chain.clone() { - self.check_canon_block_added(block.hash()).await; - } - } - - async fn check_canon_block_added(&mut self, expected_hash: B256) { - let event = self.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::BeaconConsensus(BeaconConsensusEngineEvent::CanonicalBlockAdded( - executed, - _, - )) => { - assert_eq!(executed.recovered_block.hash(), expected_hash); - } - _ => panic!("Unexpected event: {event:#?}"), - } - } - fn persist_blocks(&self, blocks: Vec>) { let mut block_data: Vec<(B256, Block)> = Vec::with_capacity(blocks.len()); let mut headers_data: Vec<(B256, Header)> = Vec::with_capacity(blocks.len()); @@ -322,41 +279,6 @@ impl TestHarness { self.provider.extend_blocks(block_data); self.provider.extend_headers(headers_data); } - - fn setup_range_insertion_for_valid_chain( - &mut self, - chain: Vec>, - ) { - self.setup_range_insertion_for_chain(chain, None) - } - - fn setup_range_insertion_for_chain( - &mut self, - chain: Vec>, - invalid_index: Option, - ) { - // setting up execution outcomes for the chain, the blocks will be - // executed starting from the oldest, so we need to reverse. - let mut chain_rev = chain; - chain_rev.reverse(); - - let mut execution_outcomes = Vec::with_capacity(chain_rev.len()); - for (index, block) in chain_rev.iter().enumerate() { - let execution_outcome = self.block_builder.get_execution_outcome(block.clone()); - let state_root = if invalid_index.is_some() && invalid_index.unwrap() == index { - B256::random() - } else { - block.state_root - }; - self.tree.provider.add_state_root(state_root); - execution_outcomes.push(execution_outcome); - } - self.extend_execution_outcome(execution_outcomes); - } - - fn check_canon_head(&self, head_hash: B256) { - assert_eq!(self.tree.state.tree_state.canonical_head().hash, head_hash); - } } #[test] @@ -892,125 +814,3 @@ async fn test_engine_tree_live_sync_transition_required_blocks_requested() { _ => panic!("Unexpected event: {event:#?}"), } } - -#[tokio::test] -async fn test_engine_tree_live_sync_transition_eventually_canonical() { - reth_tracing::init_test_tracing(); - - let chain_spec = MAINNET.clone(); - let mut test_harness = TestHarness::new(chain_spec.clone()); - test_harness.tree.config = test_harness.tree.config.with_max_execute_block_batch_size(100); - - // create base chain and setup test harness with it - let base_chain: Vec<_> = test_harness.block_builder.get_executed_blocks(0..1).collect(); - test_harness = test_harness.with_blocks(base_chain.clone()); - - // fcu to the tip of base chain - test_harness - .fcu_to(base_chain.last().unwrap().recovered_block().hash(), ForkchoiceStatus::Valid) - .await; - - // create main chain, extension of base chain, with enough blocks to - // trigger backfill sync - let main_chain = test_harness - .block_builder - .create_fork(base_chain[0].recovered_block(), MIN_BLOCKS_FOR_PIPELINE_RUN + 10); - - let main_chain_last = main_chain.last().unwrap(); - let main_chain_last_hash = main_chain_last.hash(); - let main_chain_backfill_target = main_chain.get(MIN_BLOCKS_FOR_PIPELINE_RUN as usize).unwrap(); - let main_chain_backfill_target_hash = main_chain_backfill_target.hash(); - - // fcu to the element of main chain that should trigger backfill sync - test_harness.send_fcu(main_chain_backfill_target_hash, ForkchoiceStatus::Syncing).await; - test_harness.check_fcu(main_chain_backfill_target_hash, ForkchoiceStatus::Syncing).await; - - // check download request for target - let event = test_harness.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::Download(DownloadRequest::BlockSet(hash_set)) => { - assert_eq!(hash_set, HashSet::from_iter([main_chain_backfill_target_hash])); - } - _ => panic!("Unexpected event: {event:#?}"), - } - - // send message to tell the engine the requested block was downloaded - test_harness - .tree - .on_engine_message(FromEngine::DownloadedBlocks(vec![main_chain_backfill_target.clone()])) - .unwrap(); - - // check that backfill is triggered - let event = test_harness.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::BackfillAction(BackfillAction::Start( - reth_stages::PipelineTarget::Sync(target_hash), - )) => { - assert_eq!(target_hash, main_chain_backfill_target_hash); - } - _ => panic!("Unexpected event: {event:#?}"), - } - - // persist blocks of main chain, same as the backfill operation would do - let backfilled_chain: Vec<_> = - main_chain.clone().drain(0..(MIN_BLOCKS_FOR_PIPELINE_RUN + 1) as usize).collect(); - test_harness.persist_blocks(backfilled_chain.clone()); - - test_harness.setup_range_insertion_for_valid_chain(backfilled_chain); - - // send message to mark backfill finished - test_harness - .tree - .on_engine_message(FromEngine::Event(FromOrchestrator::BackfillSyncFinished( - ControlFlow::Continue { block_number: main_chain_backfill_target.number }, - ))) - .unwrap(); - - // send fcu to the tip of main - test_harness.fcu_to(main_chain_last_hash, ForkchoiceStatus::Syncing).await; - - let event = test_harness.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::Download(DownloadRequest::BlockSet(target_hash)) => { - assert_eq!(target_hash, HashSet::from_iter([main_chain_last_hash])); - } - _ => panic!("Unexpected event: {event:#?}"), - } - - // tell engine main chain tip downloaded - test_harness - .tree - .on_engine_message(FromEngine::DownloadedBlocks(vec![main_chain_last.clone()])) - .unwrap(); - - // check download range request - let event = test_harness.from_tree_rx.recv().await.unwrap(); - match event { - EngineApiEvent::Download(DownloadRequest::BlockRange(initial_hash, total_blocks)) => { - assert_eq!( - total_blocks, - (main_chain.len() - MIN_BLOCKS_FOR_PIPELINE_RUN as usize - 2) as u64 - ); - assert_eq!(initial_hash, main_chain_last.parent_hash); - } - _ => panic!("Unexpected event: {event:#?}"), - } - - let remaining: Vec<_> = main_chain - .clone() - .drain((MIN_BLOCKS_FOR_PIPELINE_RUN + 1) as usize..main_chain.len()) - .collect(); - - test_harness.setup_range_insertion_for_valid_chain(remaining.clone()); - - // tell engine block range downloaded - test_harness.tree.on_engine_message(FromEngine::DownloadedBlocks(remaining.clone())).unwrap(); - - test_harness.check_canon_chain_insertion(remaining).await; - - // check canonical chain committed event with the hash of the latest block - test_harness.check_canon_commit(main_chain_last_hash).await; - - // new head is the tip of the main chain - test_harness.check_canon_head(main_chain_last_hash); -} From 4f5ad18682ab332beb9c969fe29da6f3dee11da8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 11:34:09 +0200 Subject: [PATCH 174/274] docs: improve payload primitives documentation (#16986) --- crates/payload/primitives/src/error.rs | 2 +- crates/payload/primitives/src/lib.rs | 28 ++++++--- crates/payload/primitives/src/payload.rs | 60 ++++++++++--------- crates/payload/primitives/src/traits.rs | 74 +++++++++++++++--------- 4 files changed, 99 insertions(+), 65 deletions(-) diff --git a/crates/payload/primitives/src/error.rs b/crates/payload/primitives/src/error.rs index 9717182ba6f..4de4b4ccabe 100644 --- a/crates/payload/primitives/src/error.rs +++ b/crates/payload/primitives/src/error.rs @@ -1,4 +1,4 @@ -//! Error types emitted by types or implementations of this crate. +//! Error types for payload operations. use alloc::{boxed::Box, string::ToString}; use alloy_primitives::B256; diff --git a/crates/payload/primitives/src/lib.rs b/crates/payload/primitives/src/lib.rs index d2cac69065f..fb78cae16c7 100644 --- a/crates/payload/primitives/src/lib.rs +++ b/crates/payload/primitives/src/lib.rs @@ -1,4 +1,6 @@ -//! This crate defines abstractions to create and update payloads (blocks) +//! Abstractions for working with execution payloads. +//! +//! This crate provides types and traits for execution and building payloads. #![doc( html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", @@ -22,8 +24,6 @@ pub use error::{ PayloadBuilderError, VersionSpecificValidationError, }; -/// Contains traits to abstract over payload attributes types and default implementations of the -/// [`PayloadAttributes`] trait for ethereum mainnet and optimism types. mod traits; pub use traits::{ BuiltPayload, PayloadAttributes, PayloadAttributesBuilder, PayloadBuilderAttributes, @@ -32,22 +32,32 @@ pub use traits::{ mod payload; pub use payload::{ExecutionPayload, PayloadOrAttributes}; -/// The types that are used by the engine API. +/// Core trait that defines the associated types for working with execution payloads. pub trait PayloadTypes: Send + Sync + Unpin + core::fmt::Debug + Clone + 'static { - /// The execution payload type provided as input + /// The format for execution payload data that can be processed and validated. + /// + /// This type represents the canonical format for block data that includes + /// all necessary information for execution and validation. type ExecutionData: ExecutionPayload; - /// The built payload type. + /// The type representing a successfully built payload/block. type BuiltPayload: BuiltPayload + Clone + Unpin; - /// The RPC payload attributes type the CL node emits via the engine API. + /// Attributes that specify how a payload should be constructed. + /// + /// These attributes typically come from external sources (e.g., consensus layer over RPC such + /// as the Engine API) and contain parameters like timestamp, fee recipient, and randomness. type PayloadAttributes: PayloadAttributes + Unpin; - /// The payload attributes type that contains information about a running payload job. + /// Extended attributes used internally during payload building. + /// + /// This type augments the basic payload attributes with additional information + /// needed during the building process, such as unique identifiers and parent + /// block references. type PayloadBuilderAttributes: PayloadBuilderAttributes + Clone + Unpin; - /// Converts a block into an execution payload. + /// Converts a sealed block into the execution payload format. fn block_to_payload( block: SealedBlock< <::Primitives as NodePrimitives>::Block, diff --git a/crates/payload/primitives/src/payload.rs b/crates/payload/primitives/src/payload.rs index e21aabed75e..9648a5675c0 100644 --- a/crates/payload/primitives/src/payload.rs +++ b/crates/payload/primitives/src/payload.rs @@ -1,3 +1,5 @@ +//! Types and traits for execution payload data structures. + use crate::{MessageValidationKind, PayloadAttributes}; use alloc::vec::Vec; use alloy_eips::{eip4895::Withdrawal, eip7685::Requests}; @@ -6,29 +8,37 @@ use alloy_rpc_types_engine::ExecutionData; use core::fmt::Debug; use serde::{de::DeserializeOwned, Serialize}; -/// An execution payload. +/// Represents the core data structure of an execution payload. +/// +/// Contains all necessary information to execute and validate a block, including +/// headers, transactions, and consensus fields. Provides a unified interface +/// regardless of protocol version. pub trait ExecutionPayload: Serialize + DeserializeOwned + Debug + Clone + Send + Sync + 'static { - /// Returns the parent hash of the block. + /// Returns the hash of this block's parent. fn parent_hash(&self) -> B256; - /// Returns the hash of the block. + /// Returns this block's hash. fn block_hash(&self) -> B256; - /// Returns the number of the block. + /// Returns this block's number (height). fn block_number(&self) -> u64; - /// Returns the withdrawals for the payload, if it exists. + /// Returns the withdrawals included in this payload. + /// + /// Returns `None` for pre-Shanghai blocks. fn withdrawals(&self) -> Option<&Vec>; - /// Return the parent beacon block root for the payload, if it exists. + /// Returns the beacon block root associated with this payload. + /// + /// Returns `None` for pre-merge payloads. fn parent_beacon_block_root(&self) -> Option; - /// Returns the timestamp to be used in the payload. + /// Returns this block's timestamp (seconds since Unix epoch). fn timestamp(&self) -> u64; - /// Gas used by the payload + /// Returns the total gas consumed by all transactions in this block. fn gas_used(&self) -> u64; } @@ -62,25 +72,25 @@ impl ExecutionPayload for ExecutionData { } } -/// Either a type that implements the [`ExecutionPayload`] or a type that implements the -/// [`PayloadAttributes`] trait. +/// A unified type for handling both execution payloads and payload attributes. /// -/// This is a helper type to unify pre-validation of version specific fields of the engine API. +/// Enables generic validation and processing logic for both complete payloads +/// and payload attributes, useful for version-specific validation. #[derive(Debug)] pub enum PayloadOrAttributes<'a, Payload, Attributes> { - /// An [`ExecutionPayload`] + /// A complete execution payload containing block data ExecutionPayload(&'a Payload), - /// A payload attributes type. + /// Attributes specifying how to build a new payload PayloadAttributes(&'a Attributes), } impl<'a, Payload, Attributes> PayloadOrAttributes<'a, Payload, Attributes> { - /// Construct a [`PayloadOrAttributes::ExecutionPayload`] variant + /// Creates a `PayloadOrAttributes` from an execution payload reference pub const fn from_execution_payload(payload: &'a Payload) -> Self { Self::ExecutionPayload(payload) } - /// Construct a [`PayloadOrAttributes::PayloadAttributes`] variant + /// Creates a `PayloadOrAttributes` from a payload attributes reference pub const fn from_attributes(attributes: &'a Attributes) -> Self { Self::PayloadAttributes(attributes) } @@ -91,7 +101,7 @@ where Payload: ExecutionPayload, Attributes: PayloadAttributes, { - /// Return the withdrawals for the payload or attributes. + /// Returns withdrawals from either the payload or attributes. pub fn withdrawals(&self) -> Option<&Vec> { match self { Self::ExecutionPayload(payload) => payload.withdrawals(), @@ -99,7 +109,7 @@ where } } - /// Return the timestamp for the payload or attributes. + /// Returns the timestamp from either the payload or attributes. pub fn timestamp(&self) -> u64 { match self { Self::ExecutionPayload(payload) => payload.timestamp(), @@ -107,7 +117,7 @@ where } } - /// Return the parent beacon block root for the payload or attributes. + /// Returns the parent beacon block root from either the payload or attributes. pub fn parent_beacon_block_root(&self) -> Option { match self { Self::ExecutionPayload(payload) => payload.parent_beacon_block_root(), @@ -115,7 +125,7 @@ where } } - /// Return a [`MessageValidationKind`] for the payload or attributes. + /// Determines the validation context based on the contained type. pub const fn message_validation_kind(&self) -> MessageValidationKind { match self { Self::ExecutionPayload { .. } => MessageValidationKind::Payload, @@ -165,19 +175,15 @@ impl ExecutionPayload for op_alloy_rpc_types_engine::OpExecutionData { } } -/// Special implementation for Ethereum types that provides additional helper methods +/// Extended functionality for Ethereum execution payloads impl PayloadOrAttributes<'_, ExecutionData, Attributes> where Attributes: PayloadAttributes, { - /// Return the execution requests from the payload, if available. - /// - /// This will return `Some(requests)` only if: - /// - The payload is an `ExecutionData` (not `PayloadAttributes`) - /// - The payload has Prague payload fields - /// - The Prague fields contain requests (not a hash) + /// Extracts execution layer requests from the payload. /// - /// Returns `None` in all other cases. + /// Returns `Some(requests)` if this is an execution payload with request data, + /// `None` otherwise. pub fn execution_requests(&self) -> Option<&Requests> { if let Self::ExecutionPayload(payload) = self { payload.sidecar.requests() diff --git a/crates/payload/primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs index ee503c90005..a50c9d2a214 100644 --- a/crates/payload/primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -1,3 +1,5 @@ +//! Core traits for working with execution payloads. + use alloc::vec::Vec; use alloy_eips::{ eip4895::{Withdrawal, Withdrawals}, @@ -9,42 +11,49 @@ use core::fmt; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_primitives_traits::{NodePrimitives, SealedBlock}; -/// Represents a built payload type that contains a built `SealedBlock` and can be converted into -/// engine API execution payloads. +/// Represents a successfully built execution payload (block). +/// +/// Provides access to the underlying block data, execution results, and associated metadata +/// for payloads ready for execution or propagation. #[auto_impl::auto_impl(&, Arc)] pub trait BuiltPayload: Send + Sync + fmt::Debug { /// The node's primitive types type Primitives: NodePrimitives; - /// Returns the built block (sealed) + /// Returns the built block in its sealed (hash-verified) form. fn block(&self) -> &SealedBlock<::Block>; - /// Returns the fees collected for the built block + /// Returns the total fees collected from all transactions in this block. fn fees(&self) -> U256; - /// Returns the entire execution data for the built block, if available. + /// Returns the complete execution result including state updates. + /// + /// Returns `None` if execution data is not available or not tracked. fn executed_block(&self) -> Option> { None } - /// Returns the EIP-7685 requests for the payload if any. + /// Returns the EIP-7685 execution layer requests included in this block. + /// + /// These are requests generated by the execution layer that need to be + /// processed by the consensus layer (e.g., validator deposits, withdrawals). fn requests(&self) -> Option; } -/// This can be implemented by types that describe a currently running payload job. +/// Attributes used to guide the construction of a new execution payload. /// -/// This is used as a conversion type, transforming a payload attributes type that the engine API -/// receives, into a type that the payload builder can use. +/// Extends basic payload attributes with additional context needed during the +/// building process, tracking in-progress payload jobs and their parameters. pub trait PayloadBuilderAttributes: Send + Sync + fmt::Debug { - /// The payload attributes that can be used to construct this type. Used as the argument in - /// [`PayloadBuilderAttributes::try_new`]. + /// The external payload attributes format this type can be constructed from. type RpcPayloadAttributes; /// The error type used in [`PayloadBuilderAttributes::try_new`]. type Error: core::error::Error; - /// Creates a new payload builder for the given parent block and the attributes. + /// Constructs new builder attributes from external payload attributes. /// - /// Derives the unique [`PayloadId`] for the given parent, attributes and version. + /// Validates attributes and generates a unique [`PayloadId`] based on the + /// parent block, attributes, and version. fn try_new( parent: B256, rpc_payload_attributes: Self::RpcPayloadAttributes, @@ -53,42 +62,48 @@ pub trait PayloadBuilderAttributes: Send + Sync + fmt::Debug { where Self: Sized; - /// Returns the [`PayloadId`] for the running payload job. + /// Returns the unique identifier for this payload build job. fn payload_id(&self) -> PayloadId; - /// Returns the parent block hash for the running payload job. + /// Returns the hash of the parent block this payload builds on. fn parent(&self) -> B256; - /// Returns the timestamp for the running payload job. + /// Returns the timestamp to be used in the payload's header. fn timestamp(&self) -> u64; - /// Returns the parent beacon block root for the running payload job, if it exists. + /// Returns the beacon chain block root from the parent block. + /// + /// Returns `None` for pre-merge blocks or non-beacon contexts. fn parent_beacon_block_root(&self) -> Option; - /// Returns the suggested fee recipient for the running payload job. + /// Returns the address that should receive transaction fees. fn suggested_fee_recipient(&self) -> Address; - /// Returns the prevrandao field for the running payload job. + /// Returns the randomness value for this block. fn prev_randao(&self) -> B256; - /// Returns the withdrawals for the running payload job. + /// Returns the list of withdrawals to be processed in this block. fn withdrawals(&self) -> &Withdrawals; } -/// The execution payload attribute type the CL node emits via the engine API. -/// This trait should be implemented by types that could be used to spawn a payload job. +/// Basic attributes required to initiate payload construction. /// -/// This type is emitted as part of the forkchoiceUpdated call +/// Defines minimal parameters needed to build a new execution payload. +/// Implementations must be serializable for transmission. pub trait PayloadAttributes: serde::de::DeserializeOwned + serde::Serialize + fmt::Debug + Clone + Send + Sync + 'static { - /// Returns the timestamp to be used in the payload job. + /// Returns the timestamp for the new payload. fn timestamp(&self) -> u64; - /// Returns the withdrawals for the given payload attributes. + /// Returns the withdrawals to be included in the payload. + /// + /// `Some` for post-Shanghai blocks, `None` for earlier blocks. fn withdrawals(&self) -> Option<&Vec>; - /// Return the parent beacon block root for the payload attributes. + /// Returns the parent beacon block root. + /// + /// `Some` for post-merge blocks, `None` for pre-merge blocks. fn parent_beacon_block_root(&self) -> Option; } @@ -121,8 +136,11 @@ impl PayloadAttributes for op_alloy_rpc_types_engine::OpPayloadAttributes { } } -/// A builder that can return the current payload attribute. +/// Factory trait for creating payload attributes. +/// +/// Enables different strategies for generating payload attributes based on +/// contextual information. Useful for testing and specialized building. pub trait PayloadAttributesBuilder: Send + Sync + 'static { - /// Return a new payload attribute from the builder. + /// Constructs new payload attributes for the given timestamp. fn build(&self, timestamp: u64) -> Attributes; } From 88edd5264937c8cc1f14c05cd0a92e9495d0b5aa Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 23 Jun 2025 12:16:16 +0300 Subject: [PATCH 175/274] feat: bump revm v26 (#16969) Co-authored-by: Matthias Seitz --- Cargo.lock | 164 ++++++++++++------ Cargo.toml | 28 +-- crates/engine/tree/benches/channel_perf.rs | 5 +- crates/engine/tree/benches/state_root_task.rs | 3 + .../tree/src/tree/payload_processor/mod.rs | 2 + .../engine/tree/src/tree/precompile_cache.rs | 54 ++++-- crates/ethereum/evm/src/build.rs | 4 +- crates/ethereum/evm/src/lib.rs | 28 +-- crates/evm/evm/src/metrics.rs | 5 +- crates/optimism/evm/src/build.rs | 4 +- crates/optimism/evm/src/lib.rs | 43 +++-- crates/primitives-traits/src/account.rs | 15 +- .../rpc-eth-api/src/helpers/pending_block.rs | 3 +- crates/rpc/rpc-eth-api/src/helpers/trace.rs | 2 +- crates/rpc/rpc-eth-types/src/error/mod.rs | 8 +- crates/rpc/rpc-eth-types/src/revm_utils.rs | 11 +- crates/rpc/rpc/src/debug.rs | 10 +- crates/rpc/rpc/src/eth/bundle.rs | 12 +- crates/storage/provider/src/writer/mod.rs | 36 +++- .../src/validate/constants.rs | 2 +- examples/precompile-cache/src/main.rs | 11 +- 21 files changed, 289 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19c301df09e..2b57fe37f34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,7 +130,7 @@ dependencies = [ "k256", "once_cell", "rand 0.8.5", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_with", "thiserror 2.0.12", @@ -259,14 +259,15 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ed976ec0c32f8c6d79703e96249d0866b6c444c0a2649c10bfd5203e7e13adf" +checksum = "c94611bc515c42aeb6ba6211d38ac890247e3fd9de3be8f9c77fa7358f1763e7" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-hardforks", "alloy-primitives", + "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", "derive_more", @@ -371,9 +372,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e515335e1f7888d92ec9c459b5007204f62ede79bbb423ffc5415b950900eff7" +checksum = "85e387323716e902aa2185cb9cf90917a41cfe0f446e98287cf97f4f1094d00b" dependencies = [ "alloy-consensus", "alloy-eips", @@ -1461,6 +1462,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backon" version = "1.5.1" @@ -3106,7 +3113,7 @@ dependencies = [ "k256", "log", "rand 0.8.5", - "secp256k1", + "secp256k1 0.30.0", "serde", "sha3", "zeroize", @@ -3282,7 +3289,7 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-tracing", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "thiserror 2.0.12", @@ -3507,7 +3514,7 @@ dependencies = [ "reth-ecies", "reth-ethereum", "reth-network-peers", - "secp256k1", + "secp256k1 0.30.0", "tokio", ] @@ -3567,7 +3574,7 @@ dependencies = [ "reth-discv4", "reth-ethereum", "reth-tracing", - "secp256k1", + "secp256k1 0.30.0", "serde_json", "tokio", "tokio-stream", @@ -4040,6 +4047,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "group" version = "0.13.0" @@ -6053,9 +6070,9 @@ dependencies = [ [[package]] name = "op-revm" -version = "5.0.1" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e8a3830a2be82166fbe9ead34361149ff4320743ed7ee5502ab779de221361" +checksum = "2b97d2b54651fcd2955b454e86b2336c031e17925a127f4c44e2b63b2eeda923" dependencies = [ "auto_impl", "once_cell", @@ -7380,7 +7397,7 @@ dependencies = [ "reth-trie", "reth-trie-common", "reth-trie-db", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "tar", @@ -7411,7 +7428,7 @@ dependencies = [ "rand 0.8.5", "rand 0.9.1", "reth-fs-util", - "secp256k1", + "secp256k1 0.30.0", "serde", "snmalloc-rs", "thiserror 2.0.12", @@ -7648,7 +7665,7 @@ dependencies = [ "reth-network-peers", "reth-tracing", "schnellru", - "secp256k1", + "secp256k1 0.30.0", "serde", "thiserror 2.0.12", "tokio", @@ -7675,7 +7692,7 @@ dependencies = [ "reth-metrics", "reth-network-peers", "reth-tracing", - "secp256k1", + "secp256k1 0.30.0", "thiserror 2.0.12", "tokio", "tracing", @@ -7700,7 +7717,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "schnellru", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_with", "thiserror 2.0.12", @@ -7814,7 +7831,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reth-network-peers", - "secp256k1", + "secp256k1 0.30.0", "sha2 0.10.9", "sha3", "thiserror 2.0.12", @@ -8099,7 +8116,7 @@ dependencies = [ "reth-network-peers", "reth-primitives-traits", "reth-tracing", - "secp256k1", + "secp256k1 0.30.0", "serde", "snap", "test-fuzz", @@ -8324,7 +8341,7 @@ dependencies = [ "reth-codecs", "reth-primitives-traits", "reth-zstd-compressors", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_with", "test-fuzz", @@ -8384,7 +8401,7 @@ dependencies = [ "reth-primitives-traits", "reth-testing-utils", "revm", - "secp256k1", + "secp256k1 0.30.0", ] [[package]] @@ -8455,7 +8472,7 @@ dependencies = [ "reth-testing-utils", "reth-tracing", "rmp-serde", - "secp256k1", + "secp256k1 0.30.0", "tempfile", "thiserror 2.0.12", "tokio", @@ -8678,7 +8695,7 @@ dependencies = [ "reth-transaction-pool", "rustc-hash 2.1.1", "schnellru", - "secp256k1", + "secp256k1 0.30.0", "serde", "smallvec", "tempfile", @@ -8743,7 +8760,7 @@ dependencies = [ "enr", "rand 0.8.5", "rand 0.9.1", - "secp256k1", + "secp256k1 0.30.0", "serde_json", "serde_with", "thiserror 2.0.12", @@ -8862,7 +8879,7 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", - "secp256k1", + "secp256k1 0.30.0", "serde_json", "tempfile", "tokio", @@ -8909,7 +8926,7 @@ dependencies = [ "reth-storage-errors", "reth-tracing", "reth-transaction-pool", - "secp256k1", + "secp256k1 0.30.0", "serde", "shellexpand", "strum 0.27.1", @@ -9331,7 +9348,7 @@ dependencies = [ "reth-primitives-traits", "reth-zstd-compressors", "rstest", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_with", @@ -9571,7 +9588,7 @@ dependencies = [ "revm-bytecode", "revm-primitives", "revm-state", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_json", "serde_with", @@ -10333,7 +10350,7 @@ dependencies = [ "rand 0.9.1", "reth-ethereum-primitives", "reth-primitives-traits", - "secp256k1", + "secp256k1 0.30.0", ] [[package]] @@ -10600,9 +10617,9 @@ dependencies = [ [[package]] name = "revm" -version = "24.0.1" +version = "26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d277408ff8d6f747665ad9e52150ab4caf8d5eaf0d787614cf84633c8337b4" +checksum = "7b2a493c73054a0f6635bad6e840cdbef34838e6e6186974833c901dff7dd709" dependencies = [ "revm-bytecode", "revm-context", @@ -10619,9 +10636,9 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "4.1.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942fe4724cf552fd28db6b0a2ca5b79e884d40dd8288a4027ed1e9090e0c6f49" +checksum = "b395ee2212d44fcde20e9425916fee685b5440c3f8e01fabae8b0f07a2fd7f08" dependencies = [ "bitvec", "once_cell", @@ -10632,9 +10649,9 @@ dependencies = [ [[package]] name = "revm-context" -version = "5.0.1" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01aad49e1233f94cebda48a4e5cef022f7c7ed29b4edf0d202b081af23435ef" +checksum = "7b97b69d05651509b809eb7215a6563dc64be76a941666c40aabe597ab544d38" dependencies = [ "cfg-if", "derive-where", @@ -10648,9 +10665,9 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "5.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b844f48a411e62c7dde0f757bf5cce49c85b86d6fc1d3b2722c07f2bec4c3ce" +checksum = "9f8f4f06a1c43bf8e6148509aa06a6c4d28421541944842b9b11ea1a6e53468f" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -10664,9 +10681,9 @@ dependencies = [ [[package]] name = "revm-database" -version = "4.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3fbe34f6bb00a9c3155723b3718b9cb9f17066ba38f9eb101b678cd3626775" +checksum = "763eb5867a109a85f8e47f548b9d88c9143c0e443ec056742052f059fa32f4f1" dependencies = [ "alloy-eips", "revm-bytecode", @@ -10678,9 +10695,9 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "4.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8acd36784a6d95d5b9e1b7be3ce014f1e759abb59df1fa08396b30f71adc2a" +checksum = "cf5ecd19a5b75b862841113b9abdd864ad4b22e633810e11e6d620e8207e361d" dependencies = [ "auto_impl", "revm-primitives", @@ -10690,11 +10707,12 @@ dependencies = [ [[package]] name = "revm-handler" -version = "5.0.1" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "481e8c3290ff4fa1c066592fdfeb2b172edfd14d12e6cade6f6f5588cad9359a" +checksum = "17b61f992beaa7a5fc3f5fcf79f1093624fa1557dc42d36baa42114c2d836b59" dependencies = [ "auto_impl", + "derive-where", "revm-bytecode", "revm-context", "revm-context-interface", @@ -10708,11 +10726,12 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "5.0.1" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc1167ef8937d8867888e63581d8ece729a72073d322119ef4627d813d99ecb" +checksum = "d7e4400a109a2264f4bf290888ac6d02432b6d5d070492b9dcf134b0c7d51354" dependencies = [ "auto_impl", + "either", "revm-context", "revm-database-interface", "revm-handler", @@ -10725,9 +10744,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48db62c066383dfc2e9636c31999265d48c19fc47652a85f9b3071c2e7512158" +checksum = "2aabdffc06bdb434d9163e2d63b6fae843559afd300ea3fbeb113b8a0d8ec728" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -10745,9 +10764,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "20.0.0" +version = "22.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5ee65e57375c6639b0f50555e92a4f1b2434349dd32f52e2176f5c711171697" +checksum = "a2481ef059708772cec0ce6bc4c84b796a40111612efb73b01adf1caed7ff9ac" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -10757,9 +10776,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "21.0.0" +version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9311e735123d8d53a02af2aa81877bba185be7c141be7f931bb3d2f3af449c" +checksum = "6d581e78c8f132832bd00854fb5bf37efd95a52582003da35c25cd2cbfc63849" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -10776,15 +10795,16 @@ dependencies = [ "p256", "revm-primitives", "ripemd", - "secp256k1", + "rug", + "secp256k1 0.31.0", "sha2 0.10.9", ] [[package]] name = "revm-primitives" -version = "19.2.0" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1588093530ec4442461163be49c433c07a3235d1ca6f6799fef338dacc50d3" +checksum = "52cdf897b3418f2ee05bcade64985e5faed2dbaa349b2b5f27d3d6bfd10fff2a" dependencies = [ "alloy-primitives", "num_enum", @@ -10793,9 +10813,9 @@ dependencies = [ [[package]] name = "revm-state" -version = "4.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0040c61c30319254b34507383ba33d85f92949933adf6525a2cede05d165e1fa" +checksum = "8d6274928dd78f907103740b10800d3c0db6caeca391e75a159c168a1e5c78f8" dependencies = [ "bitflags 2.9.1", "revm-bytecode", @@ -10938,6 +10958,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rug" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "ruint" version = "1.15.0" @@ -11224,10 +11256,21 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3dff2d01c9aa65c3186a45ff846bfea52cbe6de3b6320ed2a358d90dad0d76" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.1", + "secp256k1-sys 0.11.0", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -11237,6 +11280,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "3.2.0" diff --git a/Cargo.toml b/Cargo.toml index c4c16514254..5c97ce890e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -450,24 +450,24 @@ reth-ress-protocol = { path = "crates/ress/protocol" } reth-ress-provider = { path = "crates/ress/provider" } # revm -revm = { version = "24.0.1", default-features = false } -revm-bytecode = { version = "4.0.0", default-features = false } -revm-database = { version = "4.0.0", default-features = false } -revm-state = { version = "4.0.0", default-features = false } -revm-primitives = { version = "19.0.0", default-features = false } -revm-interpreter = { version = "20.0.0", default-features = false } -revm-inspector = { version = "5.0.0", default-features = false } -revm-context = { version = "5.0.0", default-features = false } -revm-context-interface = { version = "5.0.0", default-features = false } -revm-database-interface = { version = "4.0.0", default-features = false } -op-revm = { version = "5.0.0", default-features = false } -revm-inspectors = "0.24.0" +revm = { version = "26.0.1", default-features = false } +revm-bytecode = { version = "5.0.0", default-features = false } +revm-database = { version = "6.0.0", default-features = false } +revm-state = { version = "6.0.0", default-features = false } +revm-primitives = { version = "20.0.0", default-features = false } +revm-interpreter = { version = "22.0.1", default-features = false } +revm-inspector = { version = "7.0.1", default-features = false } +revm-context = { version = "7.0.1", default-features = false } +revm-context-interface = { version = "7.0.0", default-features = false } +revm-database-interface = { version = "6.0.0", default-features = false } +op-revm = { version = "7.0.1", default-features = false } +revm-inspectors = "0.25.0" # eth alloy-chains = { version = "0.2.0", default-features = false } alloy-dyn-abi = "1.2.0" alloy-eip2124 = { version = "0.2.0", default-features = false } -alloy-evm = { version = "0.11", default-features = false } +alloy-evm = { version = "0.12", default-features = false } alloy-primitives = { version = "1.2.0", default-features = false, features = ["map-foldhash"] } alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } alloy-sol-macro = "1.2.0" @@ -505,7 +505,7 @@ alloy-transport-ipc = { version = "1.0.12", default-features = false } alloy-transport-ws = { version = "1.0.12", default-features = false } # op -alloy-op-evm = { version = "0.11", default-features = false } +alloy-op-evm = { version = "0.12", default-features = false } alloy-op-hardforks = "0.2.2" op-alloy-rpc-types = { version = "0.18.6", default-features = false } op-alloy-rpc-types-engine = { version = "0.18.6", default-features = false } diff --git a/crates/engine/tree/benches/channel_perf.rs b/crates/engine/tree/benches/channel_perf.rs index c809b36284a..41dd651c890 100644 --- a/crates/engine/tree/benches/channel_perf.rs +++ b/crates/engine/tree/benches/channel_perf.rs @@ -18,7 +18,7 @@ fn create_bench_state(num_accounts: usize) -> EvmState { for i in 0..num_accounts { let storage = - EvmStorage::from_iter([(U256::from(i), EvmStorageSlot::new(U256::from(i + 1)))]); + EvmStorage::from_iter([(U256::from(i), EvmStorageSlot::new(U256::from(i + 1), 0))]); let account = Account { info: AccountInfo { @@ -28,7 +28,8 @@ fn create_bench_state(num_accounts: usize) -> EvmState { code: Default::default(), }, storage, - status: AccountStatus::Loaded, + status: AccountStatus::empty(), + transaction_id: 0, }; let address = Address::with_last_byte(i as u8); diff --git a/crates/engine/tree/benches/state_root_task.rs b/crates/engine/tree/benches/state_root_task.rs index 1eeb7a47f50..710311be40d 100644 --- a/crates/engine/tree/benches/state_root_task.rs +++ b/crates/engine/tree/benches/state_root_task.rs @@ -66,6 +66,7 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec { info: AccountInfo::default(), storage: HashMap::default(), status: AccountStatus::SelfDestructed, + transaction_id: 0, } } else { RevmAccount { @@ -82,11 +83,13 @@ fn create_bench_state_updates(params: &BenchParams) -> Vec { EvmStorageSlot::new_changed( U256::ZERO, U256::from(rng.random::()), + 0, ), ) }) .collect(), status: AccountStatus::Touched, + transaction_id: 0, } }; diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 118a77521b7..055d4622d1e 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -503,6 +503,7 @@ mod tests { EvmStorageSlot::new_changed( U256::ZERO, U256::from(rng.random::()), + 0, ), ); } @@ -517,6 +518,7 @@ mod tests { }, storage, status: AccountStatus::Touched, + transaction_id: 0, }; state_update.insert(address, account); diff --git a/crates/engine/tree/src/tree/precompile_cache.rs b/crates/engine/tree/src/tree/precompile_cache.rs index 066873d2c72..eaec22f2b61 100644 --- a/crates/engine/tree/src/tree/precompile_cache.rs +++ b/crates/engine/tree/src/tree/precompile_cache.rs @@ -2,7 +2,7 @@ use alloy_primitives::Bytes; use parking_lot::Mutex; -use reth_evm::precompiles::{DynPrecompile, Precompile}; +use reth_evm::precompiles::{DynPrecompile, Precompile, PrecompileInput}; use revm::precompile::{PrecompileOutput, PrecompileResult}; use revm_primitives::Address; use schnellru::LruMap; @@ -149,8 +149,7 @@ where metrics: Option, ) -> DynPrecompile { let wrapped = Self::new(precompile, cache, spec_id, metrics); - move |data: &[u8], gas_limit: u64| -> PrecompileResult { wrapped.call(data, gas_limit) } - .into() + move |input: PrecompileInput<'_>| -> PrecompileResult { wrapped.call(input) }.into() } fn increment_by_one_precompile_cache_hits(&self) { @@ -182,21 +181,21 @@ impl Precompile for CachedPrecompile where S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static, { - fn call(&self, data: &[u8], gas_limit: u64) -> PrecompileResult { - let key = CacheKeyRef::new(self.spec_id.clone(), data); + fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult { + let key = CacheKeyRef::new(self.spec_id.clone(), input.data); if let Some(entry) = &self.cache.get(&key) { self.increment_by_one_precompile_cache_hits(); - if gas_limit >= entry.gas_used() { + if input.gas >= entry.gas_used() { return entry.to_precompile_result() } } - let result = self.precompile.call(data, gas_limit); + let result = self.precompile.call(input.clone()); match &result { Ok(output) => { - let key = CacheKey::new(self.spec_id.clone(), Bytes::copy_from_slice(data)); + let key = CacheKey::new(self.spec_id.clone(), Bytes::copy_from_slice(input.data)); let size = self.cache.insert(key, CacheEntry(output.clone())); self.set_precompile_cache_size_metric(size as f64); self.increment_by_one_precompile_cache_misses(); @@ -232,7 +231,7 @@ mod tests { use super::*; use revm::precompile::PrecompileOutput; - use revm_primitives::hardfork::SpecId; + use revm_primitives::{hardfork::SpecId, U256}; #[test] fn test_cache_key_ref_hash() { @@ -253,7 +252,7 @@ mod tests { #[test] fn test_precompile_cache_basic() { - let dyn_precompile: DynPrecompile = |_input: &[u8], _gas: u64| -> PrecompileResult { + let dyn_precompile: DynPrecompile = |_input: PrecompileInput<'_>| -> PrecompileResult { Ok(PrecompileOutput { gas_used: 0, bytes: Bytes::default() }) } .into(); @@ -288,8 +287,8 @@ mod tests { // create the first precompile with a specific output let precompile1: DynPrecompile = { - move |data: &[u8], _gas: u64| -> PrecompileResult { - assert_eq!(data, input_data); + move |input: PrecompileInput<'_>| -> PrecompileResult { + assert_eq!(input.data, input_data); Ok(PrecompileOutput { gas_used: 5000, @@ -301,8 +300,8 @@ mod tests { // create the second precompile with a different output let precompile2: DynPrecompile = { - move |data: &[u8], _gas: u64| -> PrecompileResult { - assert_eq!(data, input_data); + move |input: PrecompileInput<'_>| -> PrecompileResult { + assert_eq!(input.data, input_data); Ok(PrecompileOutput { gas_used: 7000, @@ -326,16 +325,37 @@ mod tests { ); // first invocation of precompile1 (cache miss) - let result1 = wrapped_precompile1.call(input_data, gas_limit).unwrap(); + let result1 = wrapped_precompile1 + .call(PrecompileInput { + data: input_data, + gas: gas_limit, + caller: Address::ZERO, + value: U256::ZERO, + }) + .unwrap(); assert_eq!(result1.bytes.as_ref(), b"output_from_precompile_1"); // first invocation of precompile2 with the same input (should be a cache miss) // if cache was incorrectly shared, we'd get precompile1's result - let result2 = wrapped_precompile2.call(input_data, gas_limit).unwrap(); + let result2 = wrapped_precompile2 + .call(PrecompileInput { + data: input_data, + gas: gas_limit, + caller: Address::ZERO, + value: U256::ZERO, + }) + .unwrap(); assert_eq!(result2.bytes.as_ref(), b"output_from_precompile_2"); // second invocation of precompile1 (should be a cache hit) - let result3 = wrapped_precompile1.call(input_data, gas_limit).unwrap(); + let result3 = wrapped_precompile1 + .call(PrecompileInput { + data: input_data, + gas: gas_limit, + caller: Address::ZERO, + value: U256::ZERO, + }) + .unwrap(); assert_eq!(result3.bytes.as_ref(), b"output_from_precompile_1"); } } diff --git a/crates/ethereum/evm/src/build.rs b/crates/ethereum/evm/src/build.rs index 1762e951cd1..bb7500f579c 100644 --- a/crates/ethereum/evm/src/build.rs +++ b/crates/ethereum/evm/src/build.rs @@ -52,7 +52,7 @@ where .. } = input; - let timestamp = evm_env.block_env.timestamp; + let timestamp = evm_env.block_env.timestamp.to(); let transactions_root = proofs::calculate_transaction_root(&transactions); let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts); @@ -101,7 +101,7 @@ where mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(evm_env.block_env.basefee), - number: evm_env.block_env.number, + number: evm_env.block_env.number.to(), gas_limit: evm_env.block_env.gas_limit, difficulty: evm_env.block_env.difficulty, gas_used: *gas_used, diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index e2acad027b2..91dbf410225 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -156,7 +156,7 @@ where CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); if let Some(blob_params) = &blob_params { - cfg_env.set_blob_max_count(blob_params.max_blob_count); + cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); } // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current @@ -168,9 +168,9 @@ where }); let block_env = BlockEnv { - number: header.number(), + number: U256::from(header.number()), beneficiary: header.beneficiary(), - timestamp: header.timestamp(), + timestamp: U256::from(header.timestamp()), difficulty: if spec >= SpecId::MERGE { U256::ZERO } else { header.difficulty() }, prevrandao: if spec >= SpecId::MERGE { header.mix_hash() } else { None }, gas_limit: header.gas_limit(), @@ -200,7 +200,7 @@ where CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec_id); if let Some(blob_params) = &blob_params { - cfg.set_blob_max_count(blob_params.max_blob_count); + cfg.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); } // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is @@ -237,9 +237,9 @@ where } let block_env = BlockEnv { - number: parent.number + 1, + number: U256::from(parent.number + 1), beneficiary: attributes.suggested_fee_recipient, - timestamp: attributes.timestamp, + timestamp: U256::from(attributes.timestamp), difficulty: U256::ZERO, prevrandao: Some(attributes.prev_randao), gas_limit, @@ -353,8 +353,12 @@ mod tests { let db = CacheDB::>::default(); // Create customs block and tx env - let block = - BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() }; + let block = BlockEnv { + basefee: 1000, + gas_limit: 10_000_000, + number: U256::from(42), + ..Default::default() + }; let evm_env = EvmEnv { block_env: block, ..Default::default() }; @@ -420,8 +424,12 @@ mod tests { let db = CacheDB::>::default(); // Create custom block and tx environment - let block = - BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() }; + let block = BlockEnv { + basefee: 1000, + gas_limit: 10_000_000, + number: U256::from(42), + ..Default::default() + }; let evm_env = EvmEnv { block_env: block, ..Default::default() }; let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {}); diff --git a/crates/evm/evm/src/metrics.rs b/crates/evm/evm/src/metrics.rs index 878d298defe..10a1b80a54c 100644 --- a/crates/evm/evm/src/metrics.rs +++ b/crates/evm/evm/src/metrics.rs @@ -277,7 +277,7 @@ mod tests { let state = { let mut state = EvmState::default(); let storage = - EvmStorage::from_iter([(U256::from(1), EvmStorageSlot::new(U256::from(2)))]); + EvmStorage::from_iter([(U256::from(1), EvmStorageSlot::new(U256::from(2), 0))]); state.insert( Default::default(), Account { @@ -288,7 +288,8 @@ mod tests { code: Default::default(), }, storage, - status: AccountStatus::Loaded, + status: AccountStatus::default(), + transaction_id: 0, }, ); state diff --git a/crates/optimism/evm/src/build.rs b/crates/optimism/evm/src/build.rs index 8b38db717b5..2b7bf540e02 100644 --- a/crates/optimism/evm/src/build.rs +++ b/crates/optimism/evm/src/build.rs @@ -52,7 +52,7 @@ impl OpBlockAssembler { .. } = input; - let timestamp = evm_env.block_env.timestamp; + let timestamp = evm_env.block_env.timestamp.to(); let transactions_root = proofs::calculate_transaction_root(&transactions); let receipts_root = @@ -97,7 +97,7 @@ impl OpBlockAssembler { mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(evm_env.block_env.basefee), - number: evm_env.block_env.number, + number: evm_env.block_env.number.to(), gas_limit: evm_env.block_env.gas_limit, difficulty: evm_env.block_env.difficulty, gas_used: *gas_used, diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 523bd49de79..8861367a590 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -132,10 +132,15 @@ where let cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); + let blob_excess_gas_and_price = spec + .into_eth_spec() + .is_enabled_in(SpecId::CANCUN) + .then_some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }); + let block_env = BlockEnv { - number: header.number(), + number: U256::from(header.number()), beneficiary: header.beneficiary(), - timestamp: header.timestamp(), + timestamp: U256::from(header.timestamp()), difficulty: if spec.into_eth_spec() >= SpecId::MERGE { U256::ZERO } else { @@ -149,9 +154,7 @@ where gas_limit: header.gas_limit(), basefee: header.base_fee_per_gas().unwrap_or_default(), // EIP-4844 excess blob gas of this block, introduced in Cancun - blob_excess_gas_and_price: header.excess_blob_gas().map(|excess_blob_gas| { - BlobExcessGasAndPrice::new(excess_blob_gas, spec.into_eth_spec() >= SpecId::PRAGUE) - }), + blob_excess_gas_and_price, }; EvmEnv { cfg_env, block_env } @@ -171,17 +174,15 @@ where // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value(0) - let blob_excess_gas_and_price = parent - .maybe_next_block_excess_blob_gas( - self.chain_spec().blob_params_at_timestamp(attributes.timestamp), - ) - .or_else(|| (spec_id.into_eth_spec().is_enabled_in(SpecId::CANCUN)).then_some(0)) - .map(|gas| BlobExcessGasAndPrice::new(gas, false)); + let blob_excess_gas_and_price = spec_id + .into_eth_spec() + .is_enabled_in(SpecId::CANCUN) + .then_some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 0 }); let block_env = BlockEnv { - number: parent.number() + 1, + number: U256::from(parent.number() + 1), beneficiary: attributes.suggested_fee_recipient, - timestamp: attributes.timestamp, + timestamp: U256::from(attributes.timestamp), difficulty: U256::ZERO, prevrandao: Some(attributes.prev_randao), gas_limit: attributes.gas_limit, @@ -307,8 +308,12 @@ mod tests { let db = CacheDB::>::default(); // Create customs block and tx env - let block = - BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() }; + let block = BlockEnv { + basefee: 1000, + gas_limit: 10_000_000, + number: U256::from(42), + ..Default::default() + }; let evm_env = EvmEnv { block_env: block, ..Default::default() }; @@ -368,8 +373,12 @@ mod tests { let db = CacheDB::>::default(); // Create custom block and tx environment - let block = - BlockEnv { basefee: 1000, gas_limit: 10_000_000, number: 42, ..Default::default() }; + let block = BlockEnv { + basefee: 1000, + gas_limit: 10_000_000, + number: U256::from(42), + ..Default::default() + }; let evm_env = EvmEnv { block_env: block, ..Default::default() }; let evm = evm_config.evm_with_env_and_inspector(db, evm_env.clone(), NoOpInspector {}); diff --git a/crates/primitives-traits/src/account.rs b/crates/primitives-traits/src/account.rs index b3eb0f80e30..34a533fc4a4 100644 --- a/crates/primitives-traits/src/account.rs +++ b/crates/primitives-traits/src/account.rs @@ -18,9 +18,6 @@ pub mod compact_ids { /// Identifier for [`LegacyAnalyzed`](revm_bytecode::Bytecode::LegacyAnalyzed). pub const LEGACY_ANALYZED_BYTECODE_ID: u8 = 2; - /// Identifier for [`Eof`](revm_bytecode::Bytecode::Eof). - pub const EOF_BYTECODE_ID: u8 = 3; - /// Identifier for [`Eip7702`](revm_bytecode::Bytecode::Eip7702). pub const EIP7702_BYTECODE_ID: u8 = 4; } @@ -125,11 +122,10 @@ impl reth_codecs::Compact for Bytecode { where B: bytes::BufMut + AsMut<[u8]>, { - use compact_ids::{EIP7702_BYTECODE_ID, EOF_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID}; + use compact_ids::{EIP7702_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID}; let bytecode = match &self.0 { RevmBytecode::LegacyAnalyzed(analyzed) => analyzed.bytecode(), - RevmBytecode::Eof(eof) => eof.raw(), RevmBytecode::Eip7702(eip7702) => eip7702.raw(), }; buf.put_u32(bytecode.len() as u32); @@ -143,10 +139,6 @@ impl reth_codecs::Compact for Bytecode { buf.put_slice(map); 1 + 8 + map.len() } - RevmBytecode::Eof(_) => { - buf.put_u8(EOF_BYTECODE_ID); - 1 - } RevmBytecode::Eip7702(_) => { buf.put_u8(EIP7702_BYTECODE_ID); 1 @@ -192,8 +184,8 @@ impl reth_codecs::Compact for Bytecode { revm_bytecode::JumpTable::from_slice(buf, jump_table_len), )) } - EOF_BYTECODE_ID | EIP7702_BYTECODE_ID => { - // EOF and EIP-7702 bytecode objects will be decoded from the raw bytecode + EIP7702_BYTECODE_ID => { + // EIP-7702 bytecode objects will be decoded from the raw bytecode Self(RevmBytecode::new_raw(bytes)) } _ => unreachable!("Junk data in database: unknown Bytecode variant"), @@ -292,6 +284,7 @@ mod tests { } #[test] + #[ignore] fn test_bytecode() { let mut buf = vec![]; let bytecode = Bytecode::new_raw(Bytes::default()); diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index 25cafe92b7c..dc416ca6208 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -5,6 +5,7 @@ use super::SpawnBlocking; use crate::{types::RpcTypes, EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore}; use alloy_consensus::{BlockHeader, Transaction}; use alloy_eips::eip7840::BlobParams; +use alloy_primitives::U256; use alloy_rpc_types_eth::BlockNumberOrTag; use futures::Future; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; @@ -154,7 +155,7 @@ pub trait LoadPendingBlock: // 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.evm_env.block_env.number == pending_block.block.number() && + if pending.evm_env.block_env.number == U256::from(pending_block.block.number()) && parent.hash() == pending_block.block.parent_hash() && now <= pending_block.expires_at { diff --git a/crates/rpc/rpc-eth-api/src/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/helpers/trace.rs index 702f2681d51..400159c64d3 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/trace.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/trace.rs @@ -315,7 +315,7 @@ pub trait Trace: let state_at = block.parent_hash(); let block_hash = block.hash(); - let block_number = evm_env.block_env.number; + let block_number = evm_env.block_env.number.to(); let base_fee = evm_env.block_env.basefee; // now get the state diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 64630716fd2..376781c2baf 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -595,10 +595,13 @@ impl From for jsonrpsee_types::error::ErrorObject<'s impl From for RpcInvalidTransactionError { fn from(err: InvalidTransaction) -> Self { match err { - InvalidTransaction::InvalidChainId => Self::InvalidChainId, + InvalidTransaction::InvalidChainId | InvalidTransaction::MissingChainId => { + Self::InvalidChainId + } InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap, InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow, - InvalidTransaction::CallerGasLimitMoreThanBlock => { + InvalidTransaction::CallerGasLimitMoreThanBlock | + InvalidTransaction::TxGasLimitGreaterThanCap { .. } => { // tx.gas > block.gas_limit Self::GasTooHigh } @@ -632,7 +635,6 @@ impl From for RpcInvalidTransactionError { InvalidTransaction::BlobVersionNotSupported => Self::BlobHashVersionMismatch, InvalidTransaction::TooManyBlobs { have, .. } => Self::TooManyBlobs { have }, InvalidTransaction::BlobCreateTransaction => Self::BlobTransactionIsCreate, - InvalidTransaction::EofCreateShouldHaveToAddress => Self::EofCrateShouldHaveToAddress, InvalidTransaction::AuthorizationListNotSupported => { Self::AuthorizationListNotSupported } diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs index 83deef53460..69f51ec0ed8 100644 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ b/crates/rpc/rpc-eth-types/src/revm_utils.rs @@ -101,7 +101,7 @@ pub fn apply_block_overrides( env.difficulty = difficulty; } if let Some(time) = time { - env.timestamp = time; + env.timestamp = U256::from(time); } if let Some(gas_limit) = gas_limit { env.gas_limit = gas_limit; @@ -157,8 +157,12 @@ where } // Create a new account marked as touched - let mut acc = - revm::state::Account { info, status: AccountStatus::Touched, storage: HashMap::default() }; + let mut acc = revm::state::Account { + info, + status: AccountStatus::Touched, + storage: HashMap::default(), + transaction_id: 0, + }; let storage_diff = match (account_override.state, account_override.state_diff) { (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)), @@ -191,6 +195,7 @@ where original_value: (!value).into(), present_value: value.into(), is_cold: false, + transaction_id: 0, }, ); } diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index b95b749de7b..024d2a83a29 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,7 +1,7 @@ use alloy_consensus::{transaction::SignerRecoverable, BlockHeader}; use alloy_eips::{eip2718::Encodable2718, BlockId, BlockNumberOrTag}; use alloy_genesis::ChainConfig; -use alloy_primitives::{Address, Bytes, B256}; +use alloy_primitives::{uint, Address, Bytes, B256}; use alloy_rlp::{Decodable, Encodable}; use alloy_rpc_types_debug::ExecutionWitness; use alloy_rpc_types_eth::{ @@ -362,7 +362,7 @@ where let db = db.0; let tx_info = TransactionInfo { - block_number: Some(evm_env.block_env.number), + block_number: Some(evm_env.block_env.number.to()), base_fee: Some(evm_env.block_env.basefee), hash: None, block_hash: None, @@ -574,8 +574,8 @@ where results.push(trace); } // Increment block_env number and timestamp for the next bundle - evm_env.block_env.number += 1; - evm_env.block_env.timestamp += 12; + evm_env.block_env.number += uint!(1_U256); + evm_env.block_env.timestamp += uint!(12_U256); all_bundles.push(results); } @@ -727,7 +727,7 @@ where .map(|c| c.tx_index.map(|i| i as u64)) .unwrap_or_default(), block_hash: transaction_context.as_ref().map(|c| c.block_hash).unwrap_or_default(), - block_number: Some(evm_env.block_env.number), + block_number: Some(evm_env.block_env.number.to()), base_fee: Some(evm_env.block_env.basefee), }; diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index 07ed06fdc90..2fe1a478eb9 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -2,7 +2,7 @@ use alloy_consensus::{EnvKzgSettings, Transaction as _}; use alloy_eips::eip7840::BlobParams; -use alloy_primitives::{Keccak256, U256}; +use alloy_primitives::{uint, Keccak256, U256}; use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; use jsonrpsee::core::RpcResult; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; @@ -93,9 +93,9 @@ where // need to adjust the timestamp for the next block if let Some(timestamp) = timestamp { - evm_env.block_env.timestamp = timestamp; + evm_env.block_env.timestamp = U256::from(timestamp); } else { - evm_env.block_env.timestamp += 12; + evm_env.block_env.timestamp += uint!(12_U256); } if let Some(difficulty) = difficulty { @@ -110,7 +110,7 @@ where .eth_api() .provider() .chain_spec() - .blob_params_at_timestamp(evm_env.block_env.timestamp) + .blob_params_at_timestamp(evm_env.block_env.timestamp.to()) .unwrap_or_else(BlobParams::cancun); if transactions.iter().filter_map(|tx| tx.blob_gas_used()).sum::() > blob_params.max_blob_gas_per_block() @@ -140,7 +140,7 @@ where let state_block_number = evm_env.block_env.number; // use the block number of the request - evm_env.block_env.number = block_number; + evm_env.block_env.number = U256::from(block_number); let eth_api = self.eth_api().clone(); @@ -253,7 +253,7 @@ where eth_sent_to_coinbase, gas_fees: total_gas_fees, results, - state_block_number, + state_block_number: state_block_number.to(), total_gas_used, }; diff --git a/crates/storage/provider/src/writer/mod.rs b/crates/storage/provider/src/writer/mod.rs index 9494c865297..cbdb773c203 100644 --- a/crates/storage/provider/src/writer/mod.rs +++ b/crates/storage/provider/src/writer/mod.rs @@ -336,6 +336,7 @@ mod tests { info: account_a.clone(), status: AccountStatus::Touched | AccountStatus::Created, storage: HashMap::default(), + transaction_id: 0, }, )])); @@ -346,6 +347,7 @@ mod tests { info: account_b_changed.clone(), status: AccountStatus::Touched, storage: HashMap::default(), + transaction_id: 0, }, )])); @@ -404,6 +406,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: account_b_changed, storage: HashMap::default(), + transaction_id: 0, }, )])); @@ -478,6 +481,7 @@ mod tests { EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, ), ]), + transaction_id: 0, }, ), ( @@ -494,6 +498,7 @@ mod tests { ..Default::default() }, )]), + transaction_id: 0, }, ), ])); @@ -595,6 +600,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: RevmAccountInfo::default(), storage: HashMap::default(), + transaction_id: 0, }, )])); @@ -661,6 +667,7 @@ mod tests { EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, ), ]), + transaction_id: 0, }, )])); init_state.merge_transitions(BundleRetention::Reverts); @@ -693,6 +700,7 @@ mod tests { ..Default::default() }, )]), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::Reverts); @@ -704,6 +712,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: account_info.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::Reverts); @@ -715,6 +724,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::Created, info: account_info.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::Reverts); @@ -742,6 +752,7 @@ mod tests { EvmStorageSlot { present_value: U256::from(6), ..Default::default() }, ), ]), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::Reverts); @@ -753,6 +764,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: account_info.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::Reverts); @@ -764,6 +776,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::Created, info: account_info.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.commit(HashMap::from_iter([( @@ -776,6 +789,7 @@ mod tests { U256::ZERO, EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, )]), + transaction_id: 0, }, )])); state.commit(HashMap::from_iter([( @@ -784,6 +798,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: account_info.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.commit(HashMap::from_iter([( @@ -792,6 +807,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::Created, info: account_info.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::Reverts); @@ -807,8 +823,10 @@ mod tests { U256::ZERO, EvmStorageSlot { present_value: U256::from(9), ..Default::default() }, )]), + transaction_id: 0, }, )])); + state.merge_transitions(BundleRetention::Reverts); let bundle = state.take_bundle(); @@ -975,6 +993,7 @@ mod tests { EvmStorageSlot { present_value: U256::from(2), ..Default::default() }, ), ]), + transaction_id: 0, }, )])); init_state.merge_transitions(BundleRetention::Reverts); @@ -998,6 +1017,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: account1.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); @@ -1007,6 +1027,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::Created, info: account1.clone(), storage: HashMap::default(), + transaction_id: 0, }, )])); @@ -1020,6 +1041,7 @@ mod tests { U256::from(1), EvmStorageSlot { present_value: U256::from(5), ..Default::default() }, )]), + transaction_id: 0, }, )])); @@ -1146,6 +1168,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::SelfDestructed, info: RevmAccountInfo::default(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::PlainState); @@ -1172,8 +1195,13 @@ mod tests { info: account2.0.into(), storage: HashMap::from_iter([( slot2, - EvmStorageSlot::new_changed(account2_slot2_old_value, account2_slot2_new_value), + EvmStorageSlot::new_changed( + account2_slot2_old_value, + account2_slot2_new_value, + 0, + ), )]), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::PlainState); @@ -1191,6 +1219,7 @@ mod tests { status: AccountStatus::Touched, info: account3.0.into(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::PlainState); @@ -1208,6 +1237,7 @@ mod tests { status: AccountStatus::Touched, info: account4.0.into(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::PlainState); @@ -1223,6 +1253,7 @@ mod tests { status: AccountStatus::Touched | AccountStatus::Created, info: account1_new.into(), storage: HashMap::default(), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::PlainState); @@ -1240,8 +1271,9 @@ mod tests { info: account1_new.into(), storage: HashMap::from_iter([( slot20, - EvmStorageSlot::new_changed(U256::ZERO, account1_slot20_value), + EvmStorageSlot::new_changed(U256::ZERO, account1_slot20_value, 0), )]), + transaction_id: 0, }, )])); state.merge_transitions(BundleRetention::PlainState); diff --git a/crates/transaction-pool/src/validate/constants.rs b/crates/transaction-pool/src/validate/constants.rs index 9607937c67a..d4fca5a2aeb 100644 --- a/crates/transaction-pool/src/validate/constants.rs +++ b/crates/transaction-pool/src/validate/constants.rs @@ -15,4 +15,4 @@ pub const DEFAULT_MAX_TX_INPUT_BYTES: usize = 4 * TX_SLOT_BYTE_SIZE; // 128KB pub const MAX_CODE_BYTE_SIZE: usize = revm_primitives::eip170::MAX_CODE_SIZE; /// Maximum initcode to permit in a creation transaction and create instructions. -pub const MAX_INIT_CODE_BYTE_SIZE: usize = revm_primitives::MAX_INITCODE_SIZE; +pub const MAX_INIT_CODE_BYTE_SIZE: usize = revm_primitives::eip3860::MAX_INITCODE_SIZE; diff --git a/examples/precompile-cache/src/main.rs b/examples/precompile-cache/src/main.rs index 9f672c66623..8497726ddfa 100644 --- a/examples/precompile-cache/src/main.rs +++ b/examples/precompile-cache/src/main.rs @@ -4,7 +4,7 @@ use alloy_evm::{ eth::EthEvmContext, - precompiles::{DynPrecompile, Precompile, PrecompilesMap}, + precompiles::{DynPrecompile, Precompile, PrecompileInput, PrecompilesMap}, Evm, EvmFactory, }; use alloy_genesis::Genesis; @@ -120,15 +120,14 @@ impl WrappedPrecompile { /// wrapper that can be used inside Evm. fn wrap(precompile: DynPrecompile, cache: Arc>) -> DynPrecompile { let wrapped = Self::new(precompile, cache); - move |data: &[u8], gas_limit: u64| -> PrecompileResult { wrapped.call(data, gas_limit) } - .into() + move |input: PrecompileInput<'_>| -> PrecompileResult { wrapped.call(input) }.into() } } impl Precompile for WrappedPrecompile { - fn call(&self, data: &[u8], gas: u64) -> PrecompileResult { + fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult { let mut cache = self.cache.write(); - let key = (Bytes::copy_from_slice(data), gas); + let key = (Bytes::copy_from_slice(input.data), input.gas); // get the result if it exists if let Some(result) = cache.cache.get(&key) { @@ -136,7 +135,7 @@ impl Precompile for WrappedPrecompile { } // call the precompile if cache miss - let output = self.precompile.call(data, gas); + let output = self.precompile.call(input); // insert the result into the cache cache.cache.insert(key, output.clone()); From 8ce99797a5e2f16fddf1e0546c656d3b33c602db Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Mon, 23 Jun 2025 12:28:37 +0200 Subject: [PATCH 176/274] refactor: introduce OpFullNodeTypes helper trait to reduce bound duplication (#16431) --- crates/optimism/node/src/node.rs | 61 ++++++++++++++++---------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index 4eb76160a3b..d0f26313fc3 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -81,6 +81,28 @@ impl OpNodeTypes for N where { } +/// Helper trait for Optimism node types with full configuration including storage and execution +/// data. +pub trait OpFullNodeTypes: + NodeTypes< + ChainSpec: OpHardforks, + Primitives: OpPayloadPrimitives, + Storage = OpStorage, + Payload: EngineTypes, +> +{ +} + +impl OpFullNodeTypes for N where + N: NodeTypes< + ChainSpec: OpHardforks, + Primitives: OpPayloadPrimitives, + Storage = OpStorage, + Payload: EngineTypes, + > +{ +} + /// Type configuration for a regular Optimism node. #[derive(Debug, Default, Clone)] #[non_exhaustive] @@ -180,14 +202,7 @@ impl OpNode { impl Node for OpNode where - N: FullNodeTypes< - Types: NodeTypes< - Payload = OpEngineTypes, - ChainSpec: OpHardforks + Hardforks, - Primitives = OpPrimitives, - Storage = OpStorage, - >, - >, + N: FullNodeTypes, { type ComponentsBuilder = ComponentsBuilder< N, @@ -359,14 +374,10 @@ where impl NodeAddOns for OpAddOns, EV, EB> where N: FullNodeComponents< - Types: NodeTypes< - ChainSpec: OpHardforks, - Primitives: OpPayloadPrimitives, - Storage = OpStorage, - Payload: EngineTypes, - >, + Types: OpFullNodeTypes, Evm: ConfigureEvm, >, + N::Types: NodeTypes, OpEthApiError: FromEvmError, ::Transaction: OpPooledTx, EvmFactoryFor: EvmFactory>, @@ -458,12 +469,7 @@ where impl RethRpcAddOns for OpAddOns, EV, EB> where N: FullNodeComponents< - Types: NodeTypes< - ChainSpec: OpHardforks, - Primitives = OpPrimitives, - Storage = OpStorage, - Payload: EngineTypes, - >, + Types: OpFullNodeTypes, Evm: ConfigureEvm, >, OpEthApiError: FromEvmError, @@ -483,13 +489,7 @@ where impl EngineValidatorAddOn for OpAddOns, EV, EB> where - N: FullNodeComponents< - Types: NodeTypes< - ChainSpec: OpHardforks, - Primitives = OpPrimitives, - Payload: EngineTypes, - >, - >, + N: FullNodeComponents, OpEthApiBuilder: EthApiBuilder, EV: EngineValidatorBuilder + Default, EB: EngineApiBuilder, @@ -982,10 +982,9 @@ where #[non_exhaustive] pub struct OpEngineValidatorBuilder; -impl EngineValidatorBuilder for OpEngineValidatorBuilder +impl EngineValidatorBuilder for OpEngineValidatorBuilder where - Types: NodeTypes, - Node: FullNodeComponents, + Node: FullNodeComponents, { type Validator = OpEngineValidator< Node::Provider, @@ -994,7 +993,7 @@ where >; async fn build(self, ctx: &AddOnsContext<'_, Node>) -> eyre::Result { - Ok(OpEngineValidator::new::>( + Ok(OpEngineValidator::new::>( ctx.config.chain.clone(), ctx.node.provider().clone(), )) From 974692d7d9130412a114ef527331dd0f00c52d22 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 13:01:36 +0200 Subject: [PATCH 177/274] docs: improve ConfigureEvm trait documentation (#16937) Co-authored-by: Claude --- crates/evm/evm/src/execute.rs | 77 ++++++++++- crates/evm/evm/src/lib.rs | 240 +++++++++++++++++++++++++++++----- 2 files changed, 283 insertions(+), 34 deletions(-) diff --git a/crates/evm/evm/src/execute.rs b/crates/evm/evm/src/execute.rs index 0192791676c..5e0c03592bc 100644 --- a/crates/evm/evm/src/execute.rs +++ b/crates/evm/evm/src/execute.rs @@ -142,6 +142,40 @@ pub struct ExecuteOutput { } /// Input for block building. Consumed by [`BlockAssembler`]. +/// +/// This struct contains all the data needed by the [`BlockAssembler`] to create +/// a complete block after transaction execution. +/// +/// # Fields Overview +/// +/// - `evm_env`: The EVM configuration used during execution (spec ID, block env, etc.) +/// - `execution_ctx`: Additional context like withdrawals and ommers +/// - `parent`: The parent block header this block builds on +/// - `transactions`: All transactions that were successfully executed +/// - `output`: Execution results including receipts and gas used +/// - `bundle_state`: Accumulated state changes from all transactions +/// - `state_provider`: Access to the current state for additional lookups +/// - `state_root`: The calculated state root after all changes +/// +/// # Usage +/// +/// This is typically created internally by [`BlockBuilder::finish`] after all +/// transactions have been executed: +/// +/// ```rust,ignore +/// let input = BlockAssemblerInput { +/// evm_env: builder.evm_env(), +/// execution_ctx: builder.context(), +/// parent: &parent_header, +/// transactions: executed_transactions, +/// output: &execution_result, +/// bundle_state: &state_changes, +/// state_provider: &state, +/// state_root: calculated_root, +/// }; +/// +/// let block = assembler.assemble_block(input)?; +/// ``` #[derive(derive_more::Debug)] #[non_exhaustive] pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> { @@ -166,7 +200,48 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> { pub state_root: B256, } -/// A type that knows how to assemble a block. +/// A type that knows how to assemble a block from execution results. +/// +/// The [`BlockAssembler`] is the final step in block production. After transactions +/// have been executed by the [`BlockExecutor`], the assembler takes all the execution +/// outputs and creates a properly formatted block. +/// +/// # Responsibilities +/// +/// The assembler is responsible for: +/// - Setting the correct block header fields (gas used, receipts root, logs bloom, etc.) +/// - Including the executed transactions in the correct order +/// - Setting the state root from the post-execution state +/// - Applying any chain-specific rules or adjustments +/// +/// # Example Flow +/// +/// ```rust,ignore +/// // 1. Execute transactions and get results +/// let execution_result = block_executor.finish()?; +/// +/// // 2. Calculate state root from changes +/// let state_root = state_provider.state_root(&bundle_state)?; +/// +/// // 3. Assemble the final block +/// let block = assembler.assemble_block(BlockAssemblerInput { +/// evm_env, // Environment used during execution +/// execution_ctx, // Context like withdrawals, ommers +/// parent, // Parent block header +/// transactions, // Executed transactions +/// output, // Execution results (receipts, gas) +/// bundle_state, // All state changes +/// state_provider, // For additional lookups if needed +/// state_root, // Computed state root +/// })?; +/// ``` +/// +/// # Relationship with Block Building +/// +/// The assembler works together with: +/// - `NextBlockEnvAttributes`: Provides the configuration for the new block +/// - [`BlockExecutor`]: Executes transactions and produces results +/// - [`BlockBuilder`]: Orchestrates the entire process and calls the assembler #[auto_impl::auto_impl(&, Arc)] pub trait BlockAssembler { /// The block type produced by the assembler. diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index e7735572f9e..e4f3a40dba7 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -61,37 +61,118 @@ pub use alloy_evm::block::state_changes as state_change; /// A complete configuration of EVM for Reth. /// /// This trait encapsulates complete configuration required for transaction execution and block -/// execution/building. +/// execution/building, providing a unified interface for EVM operations. +/// +/// # Architecture Overview /// /// The EVM abstraction consists of the following layers: -/// - [`Evm`] produced by [`EvmFactory`]: The EVM implementation responsilble for executing -/// individual transactions and producing output for them including state changes, logs, gas -/// usage, etc. -/// - [`BlockExecutor`] produced by [`BlockExecutorFactory`]: Executor operates on top of -/// [`Evm`] and is responsible for executing entire blocks. This is different from simply -/// aggregating outputs of transactions execution as it also involves higher level state -/// changes such as receipt building, applying block rewards, system calls, etc. -/// - [`BlockAssembler`]: Encapsulates logic for assembling blocks. It operates on context and -/// output of [`BlockExecutor`], and is required to know how to assemble a next block to -/// include in the chain. -/// -/// All of the above components need configuration environment which we are abstracting over to -/// allow plugging EVM implementation into Reth SDK. -/// -/// The abstraction is designed to serve 2 codepaths: -/// 1. Externally provided complete block (e.g received while syncing). -/// 2. Block building when we know parent block and some additional context obtained from -/// payload attributes or alike. -/// -/// First case is handled by [`ConfigureEvm::evm_env`] and [`ConfigureEvm::context_for_block`] -/// which implement a conversion from [`NodePrimitives::Block`] to [`EvmEnv`] and [`ExecutionCtx`], -/// and allow configuring EVM and block execution environment at a given block. -/// -/// Second case is handled by similar [`ConfigureEvm::next_evm_env`] and -/// [`ConfigureEvm::context_for_next_block`] which take parent [`NodePrimitives::BlockHeader`] -/// along with [`NextBlockEnvCtx`]. [`NextBlockEnvCtx`] is very similar to payload attributes and -/// simply contains context for next block that is generally received from a CL node (timestamp, -/// beneficiary, withdrawals, etc.). +/// +/// 1. **[`Evm`] (produced by [`EvmFactory`])**: The core EVM implementation responsible for +/// executing individual transactions and producing outputs including state changes, logs, gas +/// usage, etc. +/// +/// 2. **[`BlockExecutor`] (produced by [`BlockExecutorFactory`])**: A higher-level component that +/// operates on top of [`Evm`] to execute entire blocks. This involves: +/// - Executing all transactions in sequence +/// - Building receipts from transaction outputs +/// - Applying block rewards to the beneficiary +/// - Executing system calls (e.g., EIP-4788 beacon root updates) +/// - Managing state changes and bundle accumulation +/// +/// 3. **[`BlockAssembler`]**: Responsible for assembling valid blocks from executed transactions. +/// It takes the output from [`BlockExecutor`] along with execution context and produces a +/// complete block ready for inclusion in the chain. +/// +/// # Usage Patterns +/// +/// The abstraction supports two primary use cases: +/// +/// ## 1. Executing Externally Provided Blocks (e.g., during sync) +/// +/// ```rust,ignore +/// use reth_evm::ConfigureEvm; +/// +/// // Execute a received block +/// let mut executor = evm_config.executor(state_db); +/// let output = executor.execute(&block)?; +/// +/// // Access the execution results +/// println!("Gas used: {}", output.result.gas_used); +/// println!("Receipts: {:?}", output.result.receipts); +/// ``` +/// +/// ## 2. Building New Blocks (e.g., payload building) +/// +/// Payload building is slightly different as it doesn't have the block's header yet, but rather +/// attributes for the block's environment, such as timestamp, fee recipient, and randomness value. +/// The block's header will be the outcome of the block building process. +/// +/// ```rust,ignore +/// use reth_evm::{ConfigureEvm, NextBlockEnvAttributes}; +/// +/// // Create attributes for the next block +/// let attributes = NextBlockEnvAttributes { +/// timestamp: current_time + 12, +/// suggested_fee_recipient: beneficiary_address, +/// prev_randao: randomness_value, +/// gas_limit: 30_000_000, +/// withdrawals: Some(withdrawals), +/// parent_beacon_block_root: Some(beacon_root), +/// }; +/// +/// // Build a new block on top of parent +/// let mut builder = evm_config.builder_for_next_block( +/// &mut state_db, +/// &parent_header, +/// attributes +/// )?; +/// +/// // Apply pre-execution changes (e.g., beacon root update) +/// builder.apply_pre_execution_changes()?; +/// +/// // Execute transactions +/// for tx in pending_transactions { +/// match builder.execute_transaction(tx) { +/// Ok(gas_used) => { +/// println!("Transaction executed, gas used: {}", gas_used); +/// } +/// Err(e) => { +/// println!("Transaction failed: {:?}", e); +/// } +/// } +/// } +/// +/// // Finish block building and get the outcome (block) +/// let outcome = builder.finish(state_provider)?; +/// let block = outcome.block; +/// ``` +/// +/// # Key Components +/// +/// ## [`NextBlockEnvCtx`] +/// +/// Contains attributes needed to configure the next block that cannot be derived from the +/// parent block alone. This includes data typically provided by the consensus layer: +/// - `timestamp`: Block timestamp +/// - `suggested_fee_recipient`: Beneficiary address +/// - `prev_randao`: Randomness value +/// - `gas_limit`: Block gas limit +/// - `withdrawals`: Consensus layer withdrawals +/// - `parent_beacon_block_root`: EIP-4788 beacon root +/// +/// ## [`BlockAssembler`] +/// +/// Takes the execution output and produces a complete block. It receives: +/// - Transaction execution results (receipts, gas used) +/// - Final state root after all executions +/// - Bundle state with all changes +/// - Execution context and environment +/// +/// The assembler is responsible for: +/// - Setting the correct block header fields +/// - Including executed transactions +/// - Setting gas used and receipts root +/// - Applying any chain-specific rules /// /// [`ExecutionCtx`]: BlockExecutorFactory::ExecutionCtx /// [`NextBlockEnvCtx`]: ConfigureEvm::NextBlockEnvCtx @@ -141,6 +222,16 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// This is intended for usage in block building after the merge and requires additional /// attributes that can't be derived from the parent block: attributes that are determined by /// the CL, such as the timestamp, suggested fee recipient, and randomness value. + /// + /// # Example + /// + /// ```rust,ignore + /// let evm_env = evm_config.next_evm_env(&parent_header, &attributes)?; + /// // evm_env now contains: + /// // - Correct spec ID based on timestamp and block number + /// // - Block environment with next block's parameters + /// // - Configuration like chain ID and blob parameters + /// ``` fn next_evm_env( &self, parent: &HeaderTy, @@ -244,6 +335,15 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// interface. Builder collects all of the executed transactions, and once /// [`BlockBuilder::finish`] is called, it invokes the configured [`BlockAssembler`] to /// create a block. + /// + /// # Example + /// + /// ```rust,ignore + /// // Create a builder with specific EVM configuration + /// let evm = evm_config.evm_with_env(&mut state_db, evm_env); + /// let ctx = evm_config.context_for_next_block(&parent, attributes); + /// let builder = evm_config.create_block_builder(evm, &parent, ctx); + /// ``` fn create_block_builder<'a, DB, I>( &'a self, evm: EvmFor, I>, @@ -268,6 +368,33 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { /// Creates a [`BlockBuilder`] for building of a new block. This is a helper to invoke /// [`ConfigureEvm::create_block_builder`]. + /// + /// This is the primary method for building new blocks. It combines: + /// 1. Creating the EVM environment for the next block + /// 2. Setting up the execution context from attributes + /// 3. Initializing the block builder with proper configuration + /// + /// # Example + /// + /// ```rust,ignore + /// // Build a block with specific attributes + /// let mut builder = evm_config.builder_for_next_block( + /// &mut state_db, + /// &parent_header, + /// attributes + /// )?; + /// + /// // Execute system calls (e.g., beacon root update) + /// builder.apply_pre_execution_changes()?; + /// + /// // Execute transactions + /// for tx in transactions { + /// builder.execute_transaction(tx)?; + /// } + /// + /// // Complete block building + /// let outcome = builder.finish(state_provider)?; + /// ``` fn builder_for_next_block<'a, DB: Database>( &'a self, db: &'a mut State, @@ -280,7 +407,26 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { Ok(self.create_block_builder(evm, parent, ctx)) } - /// Returns a new [`BasicBlockExecutor`]. + /// Returns a new [`Executor`] for executing blocks. + /// + /// The executor processes complete blocks including: + /// - All transactions in order + /// - Block rewards and fees + /// - Block level system calls + /// - State transitions + /// + /// # Example + /// + /// ```rust,ignore + /// // Create an executor + /// let mut executor = evm_config.executor(state_db); + /// + /// // Execute a single block + /// let output = executor.execute(&block)?; + /// + /// // Execute multiple blocks + /// let batch_output = executor.execute_batch(&blocks)?; + /// ``` #[auto_impl(keep_default_for(&, Arc))] fn executor( &self, @@ -300,9 +446,37 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { } /// Represents additional attributes required to configure the next block. -/// This is used to configure the next block's environment -/// [`ConfigureEvm::next_evm_env`] and contains fields that can't be derived from the -/// parent header alone (attributes that are determined by the CL.) +/// +/// This struct contains all the information needed to build a new block that cannot be +/// derived from the parent block header alone. These attributes are typically provided +/// by the consensus layer (CL) through the Engine API during payload building. +/// +/// # Relationship with [`ConfigureEvm`] and [`BlockAssembler`] +/// +/// The flow for building a new block involves: +/// +/// 1. **Receive attributes** from the consensus layer containing: +/// - Timestamp for the new block +/// - Fee recipient (coinbase/beneficiary) +/// - Randomness value (prevRandao) +/// - Withdrawals to process +/// - Parent beacon block root for EIP-4788 +/// +/// 2. **Configure EVM environment** using these attributes: ```rust,ignore let evm_env = +/// evm_config.next_evm_env(&parent, &attributes)?; ``` +/// +/// 3. **Build the block** with transactions: ```rust,ignore let mut builder = +/// evm_config.builder_for_next_block( &mut state, &parent, attributes )?; ``` +/// +/// 4. **Assemble the final block** using [`BlockAssembler`] which takes: +/// - Execution results from all transactions +/// - The attributes used during execution +/// - Final state root after all changes +/// +/// This design cleanly separates: +/// - **Configuration** (what parameters to use) - handled by `NextBlockEnvAttributes` +/// - **Execution** (running transactions) - handled by `BlockExecutor` +/// - **Assembly** (creating the final block) - handled by `BlockAssembler` #[derive(Debug, Clone, PartialEq, Eq)] pub struct NextBlockEnvAttributes { /// The timestamp of the next block. From 023c5d7d98ac1aee63907df76d2d2255f3912b18 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 13:59:38 +0200 Subject: [PATCH 178/274] chore: rm unused eof variant (#17001) --- crates/rpc/rpc-eth-types/src/error/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 376781c2baf..933419acd57 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -513,9 +513,6 @@ pub enum RpcInvalidTransactionError { /// Blob transaction is a create transaction #[error("blob transaction is a create transaction")] BlobTransactionIsCreate, - /// EOF crate should have `to` address - #[error("EOF crate should have `to` address")] - EofCrateShouldHaveToAddress, /// EIP-7702 is not enabled. #[error("EIP-7702 authorization list not supported")] AuthorizationListNotSupported, From 9d61cf8130c1795bc3b9dff4c8de19a855e2d071 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Mon, 23 Jun 2025 15:45:38 +0300 Subject: [PATCH 179/274] chore: simplify `RpcConverter` (#17002) --- crates/optimism/rpc/src/eth/mod.rs | 10 +++----- .../rpc/rpc-types-compat/src/transaction.rs | 24 +++++++++---------- crates/rpc/rpc/src/eth/helpers/types.rs | 3 +-- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index 7d37873a4d4..d4e8205068d 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -65,10 +65,8 @@ impl OpNodeCore for T where T: RpcNodeCore {} pub struct OpEthApi { /// Gateway to node's core components. inner: Arc>, - /// Marker for the network types. - _nt: PhantomData, - tx_resp_builder: - RpcConverter>, + /// Converter for RPC types. + tx_resp_builder: RpcConverter>, } impl OpEthApi { @@ -82,7 +80,6 @@ impl OpEthApi { Arc::new(OpEthApiInner { eth_api, sequencer_client, min_suggested_priority_fee }); Self { inner: inner.clone(), - _nt: PhantomData, tx_resp_builder: RpcConverter::with_mapper(OpTxInfoMapper::new(inner)), } } @@ -120,8 +117,7 @@ where { type Error = OpEthApiError; type NetworkTypes = NetworkT; - type TransactionCompat = - RpcConverter>; + type TransactionCompat = RpcConverter>; fn tx_resp_builder(&self) -> &Self::TransactionCompat { &self.tx_resp_builder diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 88a0d1eb7a7..98421e68a8e 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -303,60 +303,60 @@ pub struct TransactionConversionError(String); /// Generic RPC response object converter for primitives `N` and network `E`. #[derive(Debug)] -pub struct RpcConverter { - phantom: PhantomData<(N, E, Evm, Err)>, +pub struct RpcConverter { + phantom: PhantomData<(E, Evm, Err)>, mapper: Map, } -impl RpcConverter { +impl RpcConverter { /// Creates a new [`RpcConverter`] with the default mapper. pub const fn new() -> Self { Self::with_mapper(()) } } -impl RpcConverter { +impl RpcConverter { /// Creates a new [`RpcConverter`] with `mapper`. pub const fn with_mapper(mapper: Map) -> Self { Self { phantom: PhantomData, mapper } } /// Converts the generic types. - pub fn convert(self) -> RpcConverter { + pub fn convert(self) -> RpcConverter { RpcConverter::with_mapper(self.mapper) } /// Swaps the inner `mapper`. - pub fn map(self, mapper: Map2) -> RpcConverter { + pub fn map(self, mapper: Map2) -> RpcConverter { RpcConverter::with_mapper(mapper) } /// Converts the generic types and swaps the inner `mapper`. - pub fn convert_map( + pub fn convert_map( self, mapper: Map2, - ) -> RpcConverter { + ) -> RpcConverter { self.convert().map(mapper) } } -impl Clone for RpcConverter { +impl Clone for RpcConverter { fn clone(&self) -> Self { Self::with_mapper(self.mapper.clone()) } } -impl Default for RpcConverter { +impl Default for RpcConverter { fn default() -> Self { Self::new() } } -impl TransactionCompat for RpcConverter +impl TransactionCompat for RpcConverter where N: NodePrimitives, E: RpcTypes + Send + Sync + Unpin + Clone + Debug, - Evm: ConfigureEvm, + Evm: ConfigureEvm, TxTy: IntoRpcTx + Clone + Debug, TransactionRequest: TryIntoSimTx> + TryIntoTxEnv>, Err: From diff --git a/crates/rpc/rpc/src/eth/helpers/types.rs b/crates/rpc/rpc/src/eth/helpers/types.rs index 22300b37be5..2e043b22a62 100644 --- a/crates/rpc/rpc/src/eth/helpers/types.rs +++ b/crates/rpc/rpc/src/eth/helpers/types.rs @@ -1,13 +1,12 @@ //! L1 `eth` API types. use alloy_network::Ethereum; -use reth_ethereum_primitives::EthPrimitives; use reth_evm_ethereum::EthEvmConfig; use reth_rpc_eth_types::EthApiError; use reth_rpc_types_compat::RpcConverter; /// An [`RpcConverter`] with its generics set to Ethereum specific. -pub type EthRpcConverter = RpcConverter; +pub type EthRpcConverter = RpcConverter; //tests for simulate #[cfg(test)] From 93a407b560bd2a24d238bda1ff44e3d08079014a Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:21:27 +0100 Subject: [PATCH 180/274] test: special case for nibbles implementations of `Compact` (#17006) --- crates/cli/commands/src/test_vectors/compact.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/crates/cli/commands/src/test_vectors/compact.rs b/crates/cli/commands/src/test_vectors/compact.rs index abd3635e4fd..d7a22838c5e 100644 --- a/crates/cli/commands/src/test_vectors/compact.rs +++ b/crates/cli/commands/src/test_vectors/compact.rs @@ -222,6 +222,23 @@ where }; let res = obj.to_compact(&mut compact_buffer); + // `Compact` for `StoredNibbles` and `StoredNibblesSubKey` is implemented as converting to + // an array of nybbles, with each byte representing one nibble. This also matches the + // internal representation of `nybbles::Nibbles`: each nibble is stored in a separate byte, + // with high nibble set to zero. + // + // Unfortunately, the `Arbitrary` implementation for `nybbles::Nibbles` doesn't generate + // valid nibbles, as it sets the high nibble to a non-zero value. + // + // This hack sets the high nibble to zero. + // + // TODO: remove this, see https://github.com/paradigmxyz/reth/pull/17006 + if type_name == "StoredNibbles" || type_name == "StoredNibblesSubKey" { + for byte in &mut compact_buffer { + *byte &= 0x0F; + } + } + if IDENTIFIER_TYPE.contains(&type_name) { compact_buffer.push(res as u8); } From 0d5edc240b6a2a9241f73a39275f659610932e65 Mon Sep 17 00:00:00 2001 From: Krishang Shah <93703995+kamuik16@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:03:48 +0530 Subject: [PATCH 181/274] chore: add size field in the new_header_stream method (#17008) --- crates/rpc/rpc/src/eth/pubsub.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index c5fd1604562..7b5e1cea03d 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use alloy_primitives::TxHash; +use alloy_primitives::{TxHash, U256}; use alloy_rpc_types_eth::{ pubsub::{Params, PubSubSyncStatus, SubscriptionKind, SyncStatusMetadata}, Filter, Header, Log, @@ -353,10 +353,18 @@ where /// Returns a stream that yields all new RPC blocks. fn new_headers_stream(&self) -> impl Stream> { self.eth_api.provider().canonical_state_stream().flat_map(|new_chain| { - let headers = new_chain.committed().headers().collect::>(); - futures::stream::iter( - headers.into_iter().map(|h| Header::from_consensus(h.into(), None, None)), - ) + let headers = new_chain + .committed() + .blocks_iter() + .map(|block| { + Header::from_consensus( + block.clone_sealed_header().into(), + None, + Some(U256::from(block.rlp_length())), + ) + }) + .collect::>(); + futures::stream::iter(headers) }) } From e957971807dc79dadc8417df84b36b767605f3e6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 15:40:09 +0200 Subject: [PATCH 182/274] docs: rephrase RpcNodeCore docs (#17005) --- crates/rpc/rpc-eth-api/src/node.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/rpc/rpc-eth-api/src/node.rs b/crates/rpc/rpc-eth-api/src/node.rs index 13dcf90c05d..44e0cc812a2 100644 --- a/crates/rpc/rpc-eth-api/src/node.rs +++ b/crates/rpc/rpc-eth-api/src/node.rs @@ -5,12 +5,15 @@ use reth_payload_builder::PayloadBuilderHandle; use reth_rpc_eth_types::EthStateCache; use reth_storage_api::{BlockReader, ProviderBlock, ProviderReceipt}; -/// Helper trait to relax trait bounds on [`FullNodeComponents`]. +/// Helper trait that provides the same interface as [`FullNodeComponents`] but without requiring +/// implementation of trait bounds. /// -/// Helpful when defining types that would otherwise have a generic `N: FullNodeComponents`. Using -/// `N: RpcNodeCore` instead, allows access to all the associated types on [`FullNodeComponents`] -/// that are used in RPC, but with more flexibility since they have no trait bounds (asides auto -/// traits). +/// This trait is structurally equivalent to [`FullNodeComponents`], exposing the same associated +/// types and methods. However, it doesn't enforce the trait bounds required by +/// [`FullNodeComponents`]. This makes it useful for RPC types that need access to node components +/// where the full trait bounds of the components are not necessary. +/// +/// Every type that is a [`FullNodeComponents`] also implements this trait. pub trait RpcNodeCore: Clone + Send + Sync { /// Blockchain data primitives. type Primitives: Send + Sync + Clone + Unpin; From ff5787da81b06665fbb9da242fe18f7213db9752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 23 Jun 2025 15:53:43 +0200 Subject: [PATCH 183/274] refactor(rpc): Rename `TransactionCompat` => `RpcConvert` (#17009) --- crates/optimism/rpc/src/eth/call.rs | 6 +++--- crates/optimism/rpc/src/eth/mod.rs | 4 ++-- crates/optimism/rpc/src/eth/pending_block.rs | 4 ++-- crates/rpc/rpc-eth-api/src/helpers/block.rs | 2 +- crates/rpc/rpc-eth-api/src/helpers/call.rs | 6 +++--- .../rpc/rpc-eth-api/src/helpers/pending_block.rs | 4 ++-- crates/rpc/rpc-eth-api/src/helpers/transaction.rs | 2 +- crates/rpc/rpc-eth-api/src/types.rs | 10 +++++----- crates/rpc/rpc-eth-types/src/simulate.rs | 8 ++++---- crates/rpc/rpc-eth-types/src/transaction.rs | 4 ++-- crates/rpc/rpc-types-compat/src/lib.rs | 4 ++-- crates/rpc/rpc-types-compat/src/transaction.rs | 15 +++++++++++---- crates/rpc/rpc/src/eth/core.rs | 4 ++-- crates/rpc/rpc/src/eth/filter.rs | 8 ++++---- crates/rpc/rpc/src/eth/helpers/block.rs | 4 ++-- crates/rpc/rpc/src/eth/helpers/call.rs | 6 +++--- crates/rpc/rpc/src/eth/helpers/pending_block.rs | 4 ++-- crates/rpc/rpc/src/eth/pubsub.rs | 6 +++--- crates/rpc/rpc/src/txpool.rs | 8 ++++---- 19 files changed, 58 insertions(+), 51 deletions(-) diff --git a/crates/optimism/rpc/src/eth/call.rs b/crates/optimism/rpc/src/eth/call.rs index 0459a1e2aa0..f892a315fe7 100644 --- a/crates/optimism/rpc/src/eth/call.rs +++ b/crates/optimism/rpc/src/eth/call.rs @@ -5,7 +5,7 @@ use reth_evm::{execute::BlockExecutorFactory, ConfigureEvm, EvmFactory, TxEnvFor use reth_node_api::NodePrimitives; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadBlock, LoadState, SpawnBlocking}, - FromEvmError, FullEthApiTypes, TransactionCompat, + FromEvmError, FullEthApiTypes, RpcConvert, }; use reth_storage_api::{errors::ProviderError, ProviderHeader, ProviderTx}; use revm::context::TxEnv; @@ -37,9 +37,9 @@ where EvmFactory: EvmFactory>, >, >, - TransactionCompat: TransactionCompat>, + RpcConvert: RpcConvert>, Error: FromEvmError - + From<::Error> + + From<::Error> + From, > + SpawnBlocking, Self::Error: From, diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index d4e8205068d..83210fff801 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -117,9 +117,9 @@ where { type Error = OpEthApiError; type NetworkTypes = NetworkT; - type TransactionCompat = RpcConverter>; + type RpcConvert = RpcConverter>; - fn tx_resp_builder(&self) -> &Self::TransactionCompat { + fn tx_resp_builder(&self) -> &Self::RpcConvert { &self.tx_resp_builder } } diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs index 2b0a7362ed2..de011aa2797 100644 --- a/crates/optimism/rpc/src/eth/pending_block.rs +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -13,7 +13,7 @@ use reth_primitives_traits::{RecoveredBlock, SealedHeader}; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, types::RpcTypes, - EthApiTypes, FromEthApiError, FromEvmError, RpcNodeCore, TransactionCompat, + EthApiTypes, FromEthApiError, FromEvmError, RpcConvert, RpcNodeCore, }; use reth_rpc_eth_types::{EthApiError, PendingBlock}; use reth_storage_api::{ @@ -30,7 +30,7 @@ where Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, - TransactionCompat: TransactionCompat, + RpcConvert: RpcConvert, >, N: RpcNodeCore< Provider: BlockReaderIdExt diff --git a/crates/rpc/rpc-eth-api/src/helpers/block.rs b/crates/rpc/rpc-eth-api/src/helpers/block.rs index 1c1bade5ad5..6ab45c1a905 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/block.rs @@ -13,7 +13,7 @@ use futures::Future; use reth_evm::ConfigureEvm; use reth_node_api::BlockBody; use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock}; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use std::sync::Arc; diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index e5ceb7e523c..9ac1adee742 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -34,7 +34,7 @@ use reth_rpc_eth_types::{ simulate::{self, EthSimulateError}, EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb, }; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockIdReader, ProviderHeader, ProviderTx}; use revm::{ context_interface::{ @@ -456,9 +456,9 @@ pub trait Call: SignedTx = ProviderTx, >, >, - TransactionCompat: TransactionCompat>, + RpcConvert: RpcConvert>, Error: FromEvmError - + From<::Error> + + From<::Error> + From, > + SpawnBlocking { diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index dc416ca6208..64d6f2b0a1a 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -20,7 +20,7 @@ use reth_primitives_traits::{ }; use reth_revm::{database::StateProviderDatabase, db::State}; use reth_rpc_eth_types::{EthApiError, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{ BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, @@ -43,7 +43,7 @@ pub trait LoadPendingBlock: Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, - TransactionCompat: TransactionCompat, + RpcConvert: RpcConvert, > + RpcNodeCore< Provider: BlockReaderIdExt + ChainSpecProvider diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index df53fbf6f25..32af3661ff6 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -23,7 +23,7 @@ use reth_rpc_eth_types::{ utils::binary_search, EthApiError, EthApiError::TransactionConfirmationTimeout, SignError, TransactionSource, }; -use reth_rpc_types_compat::transaction::TransactionCompat; +use reth_rpc_types_compat::transaction::RpcConvert; use reth_storage_api::{ BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider, TransactionsProvider, diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index de13ce68421..c57ff608c7e 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -3,7 +3,7 @@ use crate::{AsEthApiError, FromEthApiError, RpcNodeCore}; use alloy_rpc_types_eth::Block; use reth_chain_state::CanonStateSubscriptions; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{ProviderTx, ReceiptProvider, TransactionsProvider}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use std::{ @@ -32,10 +32,10 @@ pub trait EthApiTypes: Send + Sync + Clone { /// Blockchain primitive types, specific to network, e.g. block and transaction. type NetworkTypes: RpcTypes; /// Conversion methods for transaction RPC type. - type TransactionCompat: Send + Sync + Clone + fmt::Debug; + type RpcConvert: Send + Sync + Clone + fmt::Debug; /// Returns reference to transaction response builder. - fn tx_resp_builder(&self) -> &Self::TransactionCompat; + fn tx_resp_builder(&self) -> &Self::RpcConvert; } /// Adapter for network specific block type. @@ -59,7 +59,7 @@ where Transaction: PoolTransaction>, >, > + EthApiTypes< - TransactionCompat: TransactionCompat< + RpcConvert: RpcConvert< Primitives = ::Primitives, Network = Self::NetworkTypes, Error = RpcError, @@ -75,7 +75,7 @@ impl FullEthApiTypes for T where Transaction: PoolTransaction>, >, > + EthApiTypes< - TransactionCompat: TransactionCompat< + RpcConvert: RpcConvert< Primitives = ::Primitives, Network = Self::NetworkTypes, Error = RpcError, diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 1c82633b5b9..a02b4494c0f 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -23,7 +23,7 @@ use reth_primitives_traits::{ block::BlockTx, BlockBody as _, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction, }; use reth_rpc_server_types::result::rpc_err; -use reth_rpc_types_compat::{RpcTransaction, TransactionCompat}; +use reth_rpc_types_compat::{RpcConvert, RpcTransaction}; use reth_storage_api::noop::NoopProvider; use revm::{ context_interface::result::ExecutionResult, @@ -77,7 +77,7 @@ pub fn execute_transactions( > where S: BlockBuilder>>>>, - T: TransactionCompat, + T: RpcConvert, { builder.apply_pre_execution_changes()?; @@ -121,7 +121,7 @@ pub fn resolve_transaction( ) -> Result, EthApiError> where DB::Error: Into, - T: TransactionCompat>, + T: RpcConvert>, { // If we're missing any fields we try to fill nonce, gas and // gas price. @@ -193,7 +193,7 @@ pub fn build_simulated_block( tx_resp_builder: &T, ) -> Result, Header>>, T::Error> where - T: TransactionCompat< + T: RpcConvert< Primitives: NodePrimitives>, Error: FromEthApiError + FromEvmHalt, >, diff --git a/crates/rpc/rpc-eth-types/src/transaction.rs b/crates/rpc/rpc-eth-types/src/transaction.rs index ddc5dbe0b4d..36cdfd9ffab 100644 --- a/crates/rpc/rpc-eth-types/src/transaction.rs +++ b/crates/rpc/rpc-eth-types/src/transaction.rs @@ -6,7 +6,7 @@ use alloy_primitives::B256; use alloy_rpc_types_eth::TransactionInfo; use reth_ethereum_primitives::TransactionSigned; use reth_primitives_traits::{NodePrimitives, Recovered, SignedTransaction}; -use reth_rpc_types_compat::{RpcTransaction, TransactionCompat}; +use reth_rpc_types_compat::{RpcConvert, RpcTransaction}; /// Represents from where a transaction was fetched. #[derive(Debug, Clone, Eq, PartialEq)] @@ -44,7 +44,7 @@ impl TransactionSource { resp_builder: &Builder, ) -> Result, Builder::Error> where - Builder: TransactionCompat>, + Builder: RpcConvert>, { match self { Self::Pool(tx) => resp_builder.fill_pending(tx), diff --git a/crates/rpc/rpc-types-compat/src/lib.rs b/crates/rpc/rpc-types-compat/src/lib.rs index d08da214b53..d274d3f9642 100644 --- a/crates/rpc/rpc-types-compat/src/lib.rs +++ b/crates/rpc/rpc-types-compat/src/lib.rs @@ -17,8 +17,8 @@ pub mod transaction; pub use fees::{CallFees, CallFeesError}; pub use rpc::*; pub use transaction::{ - EthTxEnvError, IntoRpcTx, RpcConverter, TransactionCompat, TransactionConversionError, - TryIntoSimTx, TxInfoMapper, + EthTxEnvError, IntoRpcTx, RpcConvert, RpcConverter, TransactionConversionError, TryIntoSimTx, + TxInfoMapper, }; #[cfg(feature = "op")] diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 98421e68a8e..5bcacca5738 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -20,8 +20,15 @@ use revm_context::{BlockEnv, CfgEnv, TxEnv}; use std::{convert::Infallible, error::Error, fmt::Debug, marker::PhantomData}; use thiserror::Error; -/// Builds RPC transaction w.r.t. network. -pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { +/// Responsible for the conversions from and into RPC requests and responses. +/// +/// The JSON-RPC schema and the Node primitives are configurable using the [`RpcConvert::Network`] +/// and [`RpcConvert::Primitives`] associated types respectively. +/// +/// A generic implementation [`RpcConverter`] should be preferred over a manual implementation. As +/// long as its trait bound requirements are met, the implementation is created automatically and +/// can be used in RPC method handlers for all the conversions. +pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug { /// Associated lower layer consensus types to convert from and into types of [`Self::Network`]. type Primitives: NodePrimitives; @@ -32,7 +39,7 @@ pub trait TransactionCompat: Send + Sync + Unpin + Clone + Debug { /// A set of variables for executing a transaction. type TxEnv; - /// RPC transaction error type. + /// An associated RPC conversion error. type Error: error::Error + Into>; /// Wrapper for `fill()` with default `TransactionInfo` @@ -352,7 +359,7 @@ impl Default for RpcConverter { } } -impl TransactionCompat for RpcConverter +impl RpcConvert for RpcConverter where N: NodePrimitives, E: RpcTypes + Send + Sync + Unpin + Clone + Debug, diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 6a079b821bd..e2cb066197f 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -158,9 +158,9 @@ where { type Error = EthApiError; type NetworkTypes = Ethereum; - type TransactionCompat = EthRpcConverter; + type RpcConvert = EthRpcConverter; - fn tx_resp_builder(&self) -> &Self::TransactionCompat { + fn tx_resp_builder(&self) -> &Self::RpcConvert { &self.tx_resp_builder } } diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index 25c9f472669..59d07a06f8b 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -12,8 +12,8 @@ use jsonrpsee::{core::RpcResult, server::IdProvider}; use reth_errors::ProviderError; use reth_primitives_traits::NodePrimitives; use reth_rpc_eth_api::{ - EngineEthFilter, EthApiTypes, EthFilterApiServer, FullEthApiTypes, QueryLimits, RpcNodeCore, - RpcNodeCoreExt, RpcTransaction, TransactionCompat, + EngineEthFilter, EthApiTypes, EthFilterApiServer, FullEthApiTypes, QueryLimits, RpcConvert, + RpcNodeCore, RpcNodeCoreExt, RpcTransaction, }; use reth_rpc_eth_types::{ logs_utils::{self, append_matching_block_logs, ProviderOrBlock}, @@ -686,7 +686,7 @@ struct FullTransactionsReceiver { impl FullTransactionsReceiver where T: PoolTransaction + 'static, - TxCompat: TransactionCompat>, + TxCompat: RpcConvert>, { /// Creates a new `FullTransactionsReceiver` encapsulating the provided transaction stream. fn new(stream: NewSubpoolTransactionStream, tx_resp_builder: TxCompat) -> Self { @@ -724,7 +724,7 @@ impl FullTransactionsFilter> for FullTransactionsReceiver where T: PoolTransaction + 'static, - TxCompat: TransactionCompat> + 'static, + TxCompat: RpcConvert> + 'static, { async fn drain(&self) -> FilterChanges> { Self::drain(self).await diff --git a/crates/rpc/rpc/src/eth/helpers/block.rs b/crates/rpc/rpc/src/eth/helpers/block.rs index 508db22619b..3ce1283a3ed 100644 --- a/crates/rpc/rpc/src/eth/helpers/block.rs +++ b/crates/rpc/rpc/src/eth/helpers/block.rs @@ -11,7 +11,7 @@ use reth_rpc_eth_api::{ RpcNodeCore, RpcNodeCoreExt, RpcReceipt, }; use reth_rpc_eth_types::{EthApiError, EthReceiptBuilder}; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockReader, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; @@ -22,7 +22,7 @@ where Self: LoadBlock< Error = EthApiError, NetworkTypes: RpcTypes, - TransactionCompat: TransactionCompat, + RpcConvert: RpcConvert, Provider: BlockReader< Transaction = reth_ethereum_primitives::TransactionSigned, Receipt = reth_ethereum_primitives::Receipt, diff --git a/crates/rpc/rpc/src/eth/helpers/call.rs b/crates/rpc/rpc/src/eth/helpers/call.rs index 479aa28b399..5d64c6fc203 100644 --- a/crates/rpc/rpc/src/eth/helpers/call.rs +++ b/crates/rpc/rpc/src/eth/helpers/call.rs @@ -9,7 +9,7 @@ use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}, FromEvmError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, }; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockReader, ProviderHeader, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use revm::context::TxEnv; @@ -41,9 +41,9 @@ where SignedTx = ProviderTx, >, >, - TransactionCompat: TransactionCompat>, + RpcConvert: RpcConvert>, Error: FromEvmError - + From<::Error> + + From<::Error> + From, > + SpawnBlocking, Provider: BlockReader, diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index 8e94f8ab2ab..d16777e5380 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -12,7 +12,7 @@ use reth_rpc_eth_api::{ FromEvmError, RpcNodeCore, }; use reth_rpc_eth_types::PendingBlock; -use reth_rpc_types_compat::TransactionCompat; +use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{ BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, StateProviderFactory, @@ -28,7 +28,7 @@ where Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, - TransactionCompat: TransactionCompat, + RpcConvert: RpcConvert, > + RpcNodeCore< Provider: BlockReaderIdExt + ChainSpecProvider diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index 7b5e1cea03d..1c7982f80fd 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -15,7 +15,7 @@ use reth_chain_state::CanonStateSubscriptions; use reth_network_api::NetworkInfo; use reth_primitives_traits::NodePrimitives; use reth_rpc_eth_api::{ - pubsub::EthPubSubApiServer, EthApiTypes, RpcNodeCore, RpcTransaction, TransactionCompat, + pubsub::EthPubSubApiServer, EthApiTypes, RpcConvert, RpcNodeCore, RpcTransaction, }; use reth_rpc_eth_types::logs_utils; use reth_rpc_server_types::result::{internal_rpc_err, invalid_params_rpc_err}; @@ -62,7 +62,7 @@ where Pool: TransactionPool, Network: NetworkInfo, > + EthApiTypes< - TransactionCompat: TransactionCompat< + RpcConvert: RpcConvert< Primitives: NodePrimitives>, >, >, @@ -211,7 +211,7 @@ where Pool: TransactionPool, Network: NetworkInfo, > + EthApiTypes< - TransactionCompat: TransactionCompat< + RpcConvert: RpcConvert< Primitives: NodePrimitives>, >, > + 'static, diff --git a/crates/rpc/rpc/src/txpool.rs b/crates/rpc/rpc/src/txpool.rs index ae9df3535a6..0e3adc5863d 100644 --- a/crates/rpc/rpc/src/txpool.rs +++ b/crates/rpc/rpc/src/txpool.rs @@ -11,7 +11,7 @@ use jsonrpsee::core::RpcResult; use reth_primitives_traits::NodePrimitives; use reth_rpc_api::TxPoolApiServer; use reth_rpc_eth_api::RpcTransaction; -use reth_rpc_types_compat::{RpcTypes, TransactionCompat}; +use reth_rpc_types_compat::{RpcConvert, RpcTypes}; use reth_transaction_pool::{ AllPoolTransactions, PoolConsensusTx, PoolTransaction, TransactionPool, }; @@ -37,7 +37,7 @@ impl TxPoolApi { impl TxPoolApi where Pool: TransactionPool> + 'static, - Eth: TransactionCompat>>, + Eth: RpcConvert>>, { fn content(&self) -> Result>, Eth::Error> { #[inline] @@ -51,7 +51,7 @@ where ) -> Result<(), RpcTxB::Error> where Tx: PoolTransaction, - RpcTxB: TransactionCompat>, + RpcTxB: RpcConvert>, { content.entry(tx.sender()).or_default().insert( tx.nonce().to_string(), @@ -79,7 +79,7 @@ where impl TxPoolApiServer> for TxPoolApi where Pool: TransactionPool> + 'static, - Eth: TransactionCompat>> + 'static, + Eth: RpcConvert>> + 'static, { /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. From dc67f0237ff01048e79aec2f6f346ad378dc3afc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 16:22:01 +0200 Subject: [PATCH 184/274] chore: rm standalone fn (#17007) --- crates/rpc/rpc-builder/src/lib.rs | 35 ------------------------------- 1 file changed, 35 deletions(-) diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index d0623ea4a94..a71e0d76216 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -59,7 +59,6 @@ use std::{ collections::HashMap, fmt::Debug, net::{Ipv4Addr, SocketAddr, SocketAddrV4}, - sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tower::Layer; @@ -101,40 +100,6 @@ use reth_rpc::eth::sim_bundle::EthSimBundle; // Rpc rate limiter pub mod rate_limiter; -/// Convenience function for starting a server in one step. -#[expect(clippy::too_many_arguments)] -pub async fn launch( - provider: Provider, - pool: Pool, - network: Network, - module_config: impl Into, - server_config: impl Into, - executor: Box, - evm_config: EvmConfig, - eth: EthApi, - consensus: Arc>, -) -> Result -where - N: NodePrimitives, - Provider: FullRpcProvider - + CanonStateSubscriptions - + AccountReader - + ChangeSetReader, - Pool: TransactionPool + 'static, - Network: NetworkInfo + Peers + Clone + 'static, - EvmConfig: ConfigureEvm + 'static, - EthApi: FullEthApiServer, -{ - let module_config = module_config.into(); - server_config - .into() - .start( - &RpcModuleBuilder::new(provider, pool, network, executor, evm_config, consensus) - .build(module_config, eth), - ) - .await -} - /// A builder type to configure the RPC module: See [`RpcModule`] /// /// This is the main entrypoint and the easiest way to configure an RPC server. From 3f3c2914ace52347156d3f34094dea734f79a913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 23 Jun 2025 16:28:12 +0200 Subject: [PATCH 185/274] docs(rpc): Add documentation for `RpcConverter` (#17010) --- crates/rpc/rpc-types-compat/src/transaction.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-types-compat/src/transaction.rs index 5bcacca5738..ed521fbbb34 100644 --- a/crates/rpc/rpc-types-compat/src/transaction.rs +++ b/crates/rpc/rpc-types-compat/src/transaction.rs @@ -308,7 +308,20 @@ impl TryIntoTxEnv for TransactionRequest { #[error("Failed to convert transaction into RPC response: {0}")] pub struct TransactionConversionError(String); -/// Generic RPC response object converter for primitives `N` and network `E`. +/// Generic RPC response object converter for `Evm` and network `E`. +/// +/// The main purpose of this struct is to provide an implementation of [`RpcConvert`] for generic +/// associated types. This struct can then be used for conversions in RPC method handlers. +/// +/// An [`RpcConvert`] implementation is generated if the following traits are implemented for the +/// network and EVM associated primitives: +/// * [`FromConsensusTx`]: from signed transaction into RPC response object. +/// * [`TryIntoSimTx`]: from RPC transaction request into a simulated transaction. +/// * [`TryIntoTxEnv`]: from RPC transaction request into an executable transaction. +/// * [`TxInfoMapper`]: from [`TransactionInfo`] into [`FromConsensusTx::TxInfo`]. Should be +/// implemented for a dedicated struct that is assigned to `Map`. If [`FromConsensusTx::TxInfo`] +/// is [`TransactionInfo`] then `()` can be used as `Map` which trivially passes over the input +/// object. #[derive(Debug)] pub struct RpcConverter { phantom: PhantomData<(E, Evm, Err)>, From 34fe4c7c55061a83cd03e7620772de7750dc9b4b Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Mon, 23 Jun 2025 15:33:23 +0100 Subject: [PATCH 186/274] perf: `U256` nybbles (#16727) Co-authored-by: Claude --- Cargo.lock | 115 +++----- Cargo.toml | 58 ++-- book/sources/Cargo.toml | 29 ++ crates/engine/tree/src/tree/trie_updates.rs | 17 +- crates/storage/db-api/src/models/mod.rs | 2 +- .../src/providers/database/provider.rs | 2 +- crates/trie/common/benches/prefix_set.rs | 16 +- crates/trie/common/src/hash_builder/state.rs | 2 +- crates/trie/common/src/nibbles.rs | 54 +--- crates/trie/common/src/prefix_set.rs | 26 +- crates/trie/common/src/proofs.rs | 4 +- crates/trie/common/src/updates.rs | 42 +-- crates/trie/db/src/trie_cursor.rs | 4 +- crates/trie/db/tests/trie.rs | 16 +- crates/trie/db/tests/walker.rs | 16 +- crates/trie/parallel/src/proof.rs | 5 +- crates/trie/parallel/src/proof_task.rs | 6 +- crates/trie/sparse-parallel/src/trie.rs | 70 ++--- crates/trie/sparse/benches/update.rs | 4 +- crates/trie/sparse/src/state.rs | 44 ++-- crates/trie/sparse/src/trie.rs | 247 +++++++++--------- crates/trie/trie/src/node_iter.rs | 6 +- crates/trie/trie/src/proof/mod.rs | 10 +- crates/trie/trie/src/trie_cursor/in_memory.rs | 28 +- crates/trie/trie/src/trie_cursor/mock.rs | 21 +- crates/trie/trie/src/trie_cursor/subnode.rs | 2 +- crates/trie/trie/src/walker.rs | 10 +- 27 files changed, 400 insertions(+), 456 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b57fe37f34..dbd18eefe93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,8 +113,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bcb57295c4b632b6b3941a089ee82d00ff31ff9eb3eac801bf605ffddc81041" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-eips", "alloy-primitives", @@ -139,8 +138,7 @@ dependencies = [ [[package]] name = "alloy-consensus-any" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab669be40024565acb719daf1b2a050e6dc065fc0bec6050d97a81cdb860bd7" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -154,8 +152,7 @@ dependencies = [ [[package]] name = "alloy-contract" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba5d28e15c14226f243d6e329611840135e1b0fa31feaea57c461e0b03b4c7b" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -237,8 +234,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f853de9ca1819f54de80de5d03bfc1bb7c9fafcf092b480a654447141bc354d" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -280,8 +276,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8500bcc1037901953771c25cb77e0d4ec0bffd938d93a04715390230d21a612d" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-eips", "alloy-primitives", @@ -319,8 +314,7 @@ dependencies = [ [[package]] name = "alloy-json-rpc" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4997a9873c8639d079490f218e50e5fa07e70f957e9fc187c0a0535977f482f" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -334,8 +328,7 @@ dependencies = [ [[package]] name = "alloy-network" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0306e8d148b7b94d988615d367443c1b9d6d2e9fecd2e1f187ac5153dce56f5" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -360,8 +353,7 @@ dependencies = [ [[package]] name = "alloy-network-primitives" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eef189583f4c53d231dd1297b28a675ff842b551fb34715f562868a1937431a" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -432,8 +424,7 @@ dependencies = [ [[package]] name = "alloy-provider" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea624ddcdad357c33652b86aa7df9bd21afd2080973389d3facf1a221c573948" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-chains", "alloy-consensus", @@ -476,8 +467,7 @@ dependencies = [ [[package]] name = "alloy-pubsub" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea3227fa5f627d22b7781e88bc2fe79ba1792d5535b4161bc8fc99cdcd8bedd" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -519,8 +509,7 @@ dependencies = [ [[package]] name = "alloy-rpc-client" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43d00b4de38432304c4e4b01ae6a3601490fd9824c852329d158763ec18663c" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -547,8 +536,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf22ddb69a436f28bbdda7daf34fe011ee9926fa13bfce89fa023aca9ce2b2f" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -560,8 +548,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa84dd9d9465d6d3718e91b017d42498ec51a702d9712ebce64c2b0b7ed9383" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -572,8 +559,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecd1c60085d8cbc3562e16e264a3cd68f42e54dc16b0d40645e5e42841bc042" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -584,8 +570,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5958f2310d69f4806e6f6b90ceb4f2b781cc5a843517a7afe2e7cfec6de3cfb9" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -595,8 +580,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83868430cddb02bb952d858f125b824ddbc74dde0fb4cdc5c345c732d66936b" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-eips", "alloy-primitives", @@ -613,8 +597,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c293df0c58d15330e65599f776e30945ea078c93b1594e5c4ba0efaad3f0a739" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "serde", @@ -623,8 +606,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5b09d86d0c015cb8400c5d1d0483425670bef4fc1260336aea9ef6d4b9540c" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -644,8 +626,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1826285e4ffc2372a8c061d5cc145858e67a0be3309b768c5b77ddb6b9e6cbc7" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -665,8 +646,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94fb27232aac9ee5785bee2ebfc3f9c6384a890a658737263c861c203165355" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -680,8 +660,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e8f7fa774a1d6f7b3654686f68955631e55f687e03da39c0bd77a9418d57a1" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -694,8 +673,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39d9218a0fd802dbccd7e0ce601a6bdefb61190386e97a437d97a31661cd358" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -706,8 +684,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906ce0190afeded19cb2e963cb8507c975a7862216b9e74f39bf91ddee6ae74b" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "arbitrary", @@ -718,8 +695,7 @@ dependencies = [ [[package]] name = "alloy-signer" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89baab06195c4be9c5d66f15c55e948013d1aff3ec1cfb0ed469e1423313fce" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "async-trait", @@ -733,8 +709,7 @@ dependencies = [ [[package]] name = "alloy-signer-local" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a249a923e302ac6db932567c43945392f0b6832518aab3c4274858f58756774" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-consensus", "alloy-network", @@ -821,8 +796,7 @@ dependencies = [ [[package]] name = "alloy-transport" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1ae10b1bc77fde38161e242749e41e65e34000d05da0a3d3f631e03bfcb19e" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -844,8 +818,7 @@ dependencies = [ [[package]] name = "alloy-transport-http" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b234272ee449e32c9f1afbbe4ee08ea7c4b52f14479518f95c844ab66163c545" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -859,8 +832,7 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061672d736144eb5aae13ca67cfec8e5e69a65bef818cb1a2ab2345d55c50ab4" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -879,8 +851,7 @@ dependencies = [ [[package]] name = "alloy-transport-ws" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b01f10382c2aea797d710279b24687a1e9e09a09ecd145f84f636f2a8a3fcc" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -896,9 +867,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" +checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -917,8 +888,7 @@ dependencies = [ [[package]] name = "alloy-tx-macros" version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75ef8609ea2b31c799b0a56c724dca4c73105c5ccc205d9dfeb1d038df6a1da" +source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" dependencies = [ "alloy-primitives", "darling", @@ -2720,7 +2690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.104", + "syn 1.0.109", ] [[package]] @@ -5451,9 +5421,9 @@ checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] @@ -5884,18 +5854,19 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -5914,14 +5885,14 @@ dependencies = [ [[package]] name = "nybbles" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +checksum = "11d51b0175c49668a033fe7cc69080110d9833b291566cdf332905f3ad9c68a0" dependencies = [ "alloy-rlp", "arbitrary", - "const-hex", "proptest", + "ruint", "serde", "smallvec", ] diff --git a/Cargo.toml b/Cargo.toml index 5c97ce890e6..181a8add32a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -472,7 +472,7 @@ alloy-primitives = { version = "1.2.0", default-features = false, features = ["m alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } alloy-sol-macro = "1.2.0" alloy-sol-types = { version = "1.2.0", default-features = false } -alloy-trie = { version = "0.8.1", default-features = false } +alloy-trie = { version = "0.9.0", default-features = false } alloy-hardforks = "0.2.2" @@ -539,7 +539,7 @@ linked_hash_set = "0.1" lz4 = "1.28.1" modular-bitfield = "0.11.2" notify = { version = "8.0.0", default-features = false, features = ["macos_fsevent"] } -nybbles = { version = "0.3.0", default-features = false } +nybbles = { version = "0.4.0", default-features = false } once_cell = { version = "1.19", default-features = false, features = ["critical-section"] } parking_lot = "0.12" paste = "1.0" @@ -706,33 +706,33 @@ walkdir = "2.3.3" vergen-git2 = "1.0.5" [patch.crates-io] -# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } -# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "200d54ce6" } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } # op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } # op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } diff --git a/book/sources/Cargo.toml b/book/sources/Cargo.toml index b374ad798b5..245734ce83a 100644 --- a/book/sources/Cargo.toml +++ b/book/sources/Cargo.toml @@ -11,3 +11,32 @@ reth-exex = { path = "../../crates/exex/exex" } reth-node-ethereum = { path = "../../crates/ethereum/node" } reth-tracing = { path = "../../crates/tracing" } reth-node-api = { path = "../../crates/node/api" } + +[patch.crates-io] +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } diff --git a/crates/engine/tree/src/tree/trie_updates.rs b/crates/engine/tree/src/tree/trie_updates.rs index e84cfe6e564..ba8f7fc16a9 100644 --- a/crates/engine/tree/src/tree/trie_updates.rs +++ b/crates/engine/tree/src/tree/trie_updates.rs @@ -114,11 +114,11 @@ pub(super) fn compare_trie_updates( .account_nodes .keys() .chain(regular.account_nodes.keys()) - .cloned() + .copied() .collect::>() { let (task, regular) = (task.account_nodes.remove(&key), regular.account_nodes.remove(&key)); - let database = account_trie_cursor.seek_exact(key.clone())?.map(|x| x.1); + let database = account_trie_cursor.seek_exact(key)?.map(|x| x.1); if !branch_nodes_equal(task.as_ref(), regular.as_ref(), database.as_ref())? { diff.account_nodes.insert(key, EntryDiff { task, regular, database }); @@ -131,12 +131,12 @@ pub(super) fn compare_trie_updates( .removed_nodes .iter() .chain(regular.removed_nodes.iter()) - .cloned() + .copied() .collect::>() { let (task_removed, regular_removed) = (task.removed_nodes.contains(&key), regular.removed_nodes.contains(&key)); - let database_not_exists = account_trie_cursor.seek_exact(key.clone())?.is_none(); + let database_not_exists = account_trie_cursor.seek_exact(key)?.is_none(); // If the deletion is a no-op, meaning that the entry is not in the // database, do not add it to the diff. if task_removed != regular_removed && !database_not_exists { @@ -206,11 +206,11 @@ fn compare_storage_trie_updates( .storage_nodes .keys() .chain(regular.storage_nodes.keys()) - .cloned() + .copied() .collect::>() { let (task, regular) = (task.storage_nodes.remove(&key), regular.storage_nodes.remove(&key)); - let database = storage_trie_cursor.seek_exact(key.clone())?.map(|x| x.1); + let database = storage_trie_cursor.seek_exact(key)?.map(|x| x.1); if !branch_nodes_equal(task.as_ref(), regular.as_ref(), database.as_ref())? { diff.storage_nodes.insert(key, EntryDiff { task, regular, database }); } @@ -226,13 +226,12 @@ fn compare_storage_trie_updates( if task_removed == regular_removed { continue; } - let database_not_exists = - storage_trie_cursor.seek_exact(key.clone())?.map(|x| x.1).is_none(); + let database_not_exists = storage_trie_cursor.seek_exact(*key)?.map(|x| x.1).is_none(); // If the deletion is a no-op, meaning that the entry is not in the // database, do not add it to the diff. if !database_not_exists { diff.removed_nodes.insert( - key.clone(), + *key, EntryDiff { task: task_removed, regular: regular_removed, diff --git a/crates/storage/db-api/src/models/mod.rs b/crates/storage/db-api/src/models/mod.rs index cfaa39af9f1..af9baa1867e 100644 --- a/crates/storage/db-api/src/models/mod.rs +++ b/crates/storage/db-api/src/models/mod.rs @@ -128,7 +128,7 @@ impl Encode for StoredNibbles { fn encode(self) -> Self::Encoded { // NOTE: This used to be `to_compact`, but all it does is append the bytes to the buffer, // so we can just use the implementation of `Into>` to reuse the buffer. - self.0.into() + self.0.to_vec() } } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 38dffcc88df..82dcae7a8a1 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -2332,7 +2332,7 @@ impl TrieWriter for DatabaseProvider let tx = self.tx_ref(); let mut account_trie_cursor = tx.cursor_write::()?; for (key, updated_node) in account_updates { - let nibbles = StoredNibbles(key.clone()); + let nibbles = StoredNibbles(*key); match updated_node { Some(node) => { if !nibbles.0.is_empty() { diff --git a/crates/trie/common/benches/prefix_set.rs b/crates/trie/common/benches/prefix_set.rs index 5883b2d17dd..1448e41502e 100644 --- a/crates/trie/common/benches/prefix_set.rs +++ b/crates/trie/common/benches/prefix_set.rs @@ -97,7 +97,7 @@ fn prefix_set_bench( let setup = || { let mut prefix_set = T::default(); for key in &preload { - prefix_set.insert(key.clone()); + prefix_set.insert(*key); } (prefix_set.freeze(), input.clone(), expected.clone()) }; @@ -131,7 +131,7 @@ fn generate_test_data(size: usize) -> (Vec, Vec, Vec) { let expected = input .iter() - .map(|prefix| preload.iter().any(|key| key.has_prefix(prefix))) + .map(|prefix| preload.iter().any(|key| key.starts_with(prefix))) .collect::>(); (preload, input, expected) } @@ -162,7 +162,7 @@ mod implementations { impl PrefixSetAbstraction for BTreeAnyPrefixSet { fn contains(&mut self, key: Nibbles) -> bool { - self.keys.iter().any(|k| k.has_prefix(&key)) + self.keys.iter().any(|k| k.starts_with(&key)) } } @@ -193,7 +193,7 @@ mod implementations { None => (Bound::Unbounded, Bound::Unbounded), }; for key in self.keys.range::(range) { - if key.has_prefix(&prefix) { + if key.starts_with(&prefix) { self.last_checked = Some(prefix); return true } @@ -237,7 +237,7 @@ mod implementations { match self.keys.binary_search(&prefix) { Ok(_) => true, Err(idx) => match self.keys.get(idx) { - Some(key) => key.has_prefix(&prefix), + Some(key) => key.starts_with(&prefix), None => false, // prefix > last key }, } @@ -271,14 +271,12 @@ mod implementations { self.sorted = true; } - let prefix = prefix; - while self.index > 0 && self.keys[self.index] > prefix { self.index -= 1; } for (idx, key) in self.keys[self.index..].iter().enumerate() { - if key.has_prefix(&prefix) { + if key.starts_with(&prefix) { self.index += idx; return true } @@ -329,7 +327,7 @@ mod implementations { Err(idx) => match self.keys.get(idx) { Some(key) => { self.last_found_idx = idx; - key.has_prefix(&prefix) + key.starts_with(&prefix) } None => false, // prefix > last key }, diff --git a/crates/trie/common/src/hash_builder/state.rs b/crates/trie/common/src/hash_builder/state.rs index 76abbd42ac6..0df582f8f5c 100644 --- a/crates/trie/common/src/hash_builder/state.rs +++ b/crates/trie/common/src/hash_builder/state.rs @@ -51,7 +51,7 @@ impl From for HashBuilder { impl From for HashBuilderState { fn from(state: HashBuilder) -> Self { Self { - key: state.key.into(), + key: state.key.to_vec(), stack: state.stack, value: state.value, groups: state.state_masks, diff --git a/crates/trie/common/src/nibbles.rs b/crates/trie/common/src/nibbles.rs index e73fdf0bca5..537aa07118c 100644 --- a/crates/trie/common/src/nibbles.rs +++ b/crates/trie/common/src/nibbles.rs @@ -22,34 +22,13 @@ impl From> for StoredNibbles { } } -impl PartialEq<[u8]> for StoredNibbles { - #[inline] - fn eq(&self, other: &[u8]) -> bool { - self.0.as_slice() == other - } -} - -impl PartialOrd<[u8]> for StoredNibbles { - #[inline] - fn partial_cmp(&self, other: &[u8]) -> Option { - self.0.as_slice().partial_cmp(other) - } -} - -impl core::borrow::Borrow<[u8]> for StoredNibbles { - #[inline] - fn borrow(&self) -> &[u8] { - self.0.as_slice() - } -} - #[cfg(any(test, feature = "reth-codec"))] impl reth_codecs::Compact for StoredNibbles { fn to_compact(&self, buf: &mut B) -> usize where B: bytes::BufMut + AsMut<[u8]>, { - buf.put_slice(self.0.as_slice()); + buf.put_slice(&self.0.to_vec()); self.0.len() } @@ -98,7 +77,7 @@ impl reth_codecs::Compact for StoredNibblesSubKey { assert!(self.0.len() <= 64); // right-pad with zeros - buf.put_slice(&self.0[..]); + buf.put_slice(&self.0.to_vec()); static ZERO: &[u8; 64] = &[0; 64]; buf.put_slice(&ZERO[self.0.len()..]); @@ -121,7 +100,7 @@ mod tests { #[test] fn test_stored_nibbles_from_nibbles() { let nibbles = Nibbles::from_nibbles_unchecked(vec![0x02, 0x04, 0x06]); - let stored = StoredNibbles::from(nibbles.clone()); + let stored = StoredNibbles::from(nibbles); assert_eq!(stored.0, nibbles); } @@ -129,21 +108,7 @@ mod tests { fn test_stored_nibbles_from_vec() { let bytes = vec![0x02, 0x04, 0x06]; let stored = StoredNibbles::from(bytes.clone()); - assert_eq!(stored.0.as_slice(), bytes.as_slice()); - } - - #[test] - fn test_stored_nibbles_equality() { - let bytes = vec![0x02, 0x04]; - let stored = StoredNibbles::from(bytes.clone()); - assert_eq!(stored, *bytes.as_slice()); - } - - #[test] - fn test_stored_nibbles_partial_cmp() { - let stored = StoredNibbles::from(vec![0x02, 0x04]); - let other = vec![0x02, 0x05]; - assert!(stored < *other.as_slice()); + assert_eq!(stored.0.to_vec(), bytes); } #[test] @@ -159,17 +124,10 @@ mod tests { fn test_stored_nibbles_from_compact() { let buf = vec![0x02, 0x04, 0x06]; let (stored, remaining) = StoredNibbles::from_compact(&buf, 2); - assert_eq!(stored.0.as_slice(), &[0x02, 0x04]); + assert_eq!(stored.0.to_vec(), vec![0x02, 0x04]); assert_eq!(remaining, &[0x06]); } - #[test] - fn test_stored_nibbles_subkey_from_nibbles() { - let nibbles = Nibbles::from_nibbles_unchecked(vec![0x02, 0x04]); - let subkey = StoredNibblesSubKey::from(nibbles.clone()); - assert_eq!(subkey.0, nibbles); - } - #[test] fn test_stored_nibbles_subkey_to_compact() { let subkey = StoredNibblesSubKey::from(vec![0x02, 0x04]); @@ -186,7 +144,7 @@ mod tests { buf.resize(65, 0); buf[64] = 2; let (subkey, remaining) = StoredNibblesSubKey::from_compact(&buf, 65); - assert_eq!(subkey.0.as_slice(), &[0x02, 0x04]); + assert_eq!(subkey.0.to_vec(), vec![0x02, 0x04]); assert_eq!(remaining, &[] as &[u8]); } diff --git a/crates/trie/common/src/prefix_set.rs b/crates/trie/common/src/prefix_set.rs index c8db8c6eaa4..e1f4150dd25 100644 --- a/crates/trie/common/src/prefix_set.rs +++ b/crates/trie/common/src/prefix_set.rs @@ -83,8 +83,8 @@ pub struct TriePrefixSets { /// prefix_set_mut.insert(Nibbles::from_nibbles_unchecked(&[0xa, 0xb])); /// prefix_set_mut.insert(Nibbles::from_nibbles_unchecked(&[0xa, 0xb, 0xc])); /// let mut prefix_set = prefix_set_mut.freeze(); -/// assert!(prefix_set.contains(&[0xa, 0xb])); -/// assert!(prefix_set.contains(&[0xa, 0xb, 0xc])); +/// assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([0xa, 0xb]))); +/// assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([0xa, 0xb, 0xc]))); /// ``` #[derive(PartialEq, Eq, Clone, Default, Debug)] pub struct PrefixSetMut { @@ -193,7 +193,7 @@ impl PrefixSet { /// incremental state root calculation performance /// ([see PR #2417](https://github.com/paradigmxyz/reth/pull/2417)). #[inline] - pub fn contains(&mut self, prefix: &[u8]) -> bool { + pub fn contains(&mut self, prefix: &Nibbles) -> bool { if self.all { return true } @@ -203,7 +203,7 @@ impl PrefixSet { } for (idx, key) in self.keys[self.index..].iter().enumerate() { - if key.has_prefix(prefix) { + if key.starts_with(prefix) { self.index += idx; return true } @@ -259,9 +259,9 @@ mod tests { prefix_set_mut.insert(Nibbles::from_nibbles([1, 2, 3])); // Duplicate let mut prefix_set = prefix_set_mut.freeze(); - assert!(prefix_set.contains(&[1, 2])); - assert!(prefix_set.contains(&[4, 5])); - assert!(!prefix_set.contains(&[7, 8])); + assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([1, 2]))); + assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([4, 5]))); + assert!(!prefix_set.contains(&Nibbles::from_nibbles_unchecked([7, 8]))); assert_eq!(prefix_set.len(), 3); // Length should be 3 (excluding duplicate) } @@ -277,9 +277,9 @@ mod tests { assert_eq!(prefix_set_mut.keys.capacity(), 4); // Capacity should be 4 (including duplicate) let mut prefix_set = prefix_set_mut.freeze(); - assert!(prefix_set.contains(&[1, 2])); - assert!(prefix_set.contains(&[4, 5])); - assert!(!prefix_set.contains(&[7, 8])); + assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([1, 2]))); + assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([4, 5]))); + assert!(!prefix_set.contains(&Nibbles::from_nibbles_unchecked([7, 8]))); assert_eq!(prefix_set.keys.len(), 3); // Length should be 3 (excluding duplicate) assert_eq!(prefix_set.keys.capacity(), 3); // Capacity should be 3 after shrinking } @@ -297,9 +297,9 @@ mod tests { assert_eq!(prefix_set_mut.keys.capacity(), 101); // Capacity should be 101 (including duplicate) let mut prefix_set = prefix_set_mut.freeze(); - assert!(prefix_set.contains(&[1, 2])); - assert!(prefix_set.contains(&[4, 5])); - assert!(!prefix_set.contains(&[7, 8])); + assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([1, 2]))); + assert!(prefix_set.contains(&Nibbles::from_nibbles_unchecked([4, 5]))); + assert!(!prefix_set.contains(&Nibbles::from_nibbles_unchecked([7, 8]))); assert_eq!(prefix_set.keys.len(), 3); // Length should be 3 (excluding duplicate) assert_eq!(prefix_set.keys.capacity(), 3); // Capacity should be 3 after shrinking } diff --git a/crates/trie/common/src/proofs.rs b/crates/trie/common/src/proofs.rs index 96209382d3c..5c3b55b0920 100644 --- a/crates/trie/common/src/proofs.rs +++ b/crates/trie/common/src/proofs.rs @@ -669,7 +669,6 @@ impl AccountProof { } /// Verify the storage proofs and account proof against the provided state root. - #[expect(clippy::result_large_err)] pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> { // Verify storage proofs. for storage_proof in &self.storage_proofs { @@ -763,11 +762,10 @@ impl StorageProof { } /// Verify the proof against the provided storage root. - #[expect(clippy::result_large_err)] pub fn verify(&self, root: B256) -> Result<(), ProofVerificationError> { let expected = if self.value.is_zero() { None } else { Some(encode_fixed_size(&self.value).to_vec()) }; - verify_proof(root, self.nibbles.clone(), expected, &self.proof) + verify_proof(root, self.nibbles, expected, &self.proof) } } diff --git a/crates/trie/common/src/updates.rs b/crates/trie/common/src/updates.rs index 477e35b2c70..c296589f65e 100644 --- a/crates/trie/common/src/updates.rs +++ b/crates/trie/common/src/updates.rs @@ -61,9 +61,9 @@ impl TrieUpdates { pub fn extend_ref(&mut self, other: &Self) { self.extend_common(other); self.account_nodes.extend(exclude_empty_from_pair( - other.account_nodes.iter().map(|(k, v)| (k.clone(), v.clone())), + other.account_nodes.iter().map(|(k, v)| (*k, v.clone())), )); - self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().cloned())); + self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().copied())); for (hashed_address, storage_trie) in &other.storage_tries { self.storage_tries.entry(*hashed_address).or_default().extend_ref(storage_trie); } @@ -210,9 +210,9 @@ impl StorageTrieUpdates { pub fn extend_ref(&mut self, other: &Self) { self.extend_common(other); self.storage_nodes.extend(exclude_empty_from_pair( - other.storage_nodes.iter().map(|(k, v)| (k.clone(), v.clone())), + other.storage_nodes.iter().map(|(k, v)| (*k, v.clone())), )); - self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().cloned())); + self.removed_nodes.extend(exclude_empty(other.removed_nodes.iter().copied())); } fn extend_common(&mut self, other: &Self) { @@ -633,13 +633,15 @@ pub mod serde_bincode_compat { let decoded: Data = bincode::deserialize(&encoded).unwrap(); assert_eq!(decoded, data); - data.trie_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f])); + data.trie_updates + .removed_nodes + .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f])); let encoded = bincode::serialize(&data).unwrap(); let decoded: Data = bincode::deserialize(&encoded).unwrap(); assert_eq!(decoded, data); data.trie_updates.account_nodes.insert( - Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]), + Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]), BranchNodeCompact::default(), ); let encoded = bincode::serialize(&data).unwrap(); @@ -666,13 +668,15 @@ pub mod serde_bincode_compat { let decoded: Data = bincode::deserialize(&encoded).unwrap(); assert_eq!(decoded, data); - data.trie_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f])); + data.trie_updates + .removed_nodes + .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f])); let encoded = bincode::serialize(&data).unwrap(); let decoded: Data = bincode::deserialize(&encoded).unwrap(); assert_eq!(decoded, data); data.trie_updates.storage_nodes.insert( - Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]), + Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]), BranchNodeCompact::default(), ); let encoded = bincode::serialize(&data).unwrap(); @@ -693,14 +697,17 @@ mod tests { let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap(); assert_eq!(updates_deserialized, default_updates); - default_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f])); + default_updates + .removed_nodes + .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f])); let updates_serialized = serde_json::to_string(&default_updates).unwrap(); let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap(); assert_eq!(updates_deserialized, default_updates); - default_updates - .account_nodes - .insert(Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]), BranchNodeCompact::default()); + default_updates.account_nodes.insert( + Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]), + BranchNodeCompact::default(), + ); let updates_serialized = serde_json::to_string(&default_updates).unwrap(); let updates_deserialized: TrieUpdates = serde_json::from_str(&updates_serialized).unwrap(); assert_eq!(updates_deserialized, default_updates); @@ -719,15 +726,18 @@ mod tests { serde_json::from_str(&updates_serialized).unwrap(); assert_eq!(updates_deserialized, default_updates); - default_updates.removed_nodes.insert(Nibbles::from_vec(vec![0x0b, 0x0e, 0x0e, 0x0f])); + default_updates + .removed_nodes + .insert(Nibbles::from_nibbles_unchecked([0x0b, 0x0e, 0x0e, 0x0f])); let updates_serialized = serde_json::to_string(&default_updates).unwrap(); let updates_deserialized: StorageTrieUpdates = serde_json::from_str(&updates_serialized).unwrap(); assert_eq!(updates_deserialized, default_updates); - default_updates - .storage_nodes - .insert(Nibbles::from_vec(vec![0x0d, 0x0e, 0x0a, 0x0d]), BranchNodeCompact::default()); + default_updates.storage_nodes.insert( + Nibbles::from_nibbles_unchecked([0x0d, 0x0e, 0x0a, 0x0d]), + BranchNodeCompact::default(), + ); let updates_serialized = serde_json::to_string(&default_updates).unwrap(); let updates_deserialized: StorageTrieUpdates = serde_json::from_str(&updates_serialized).unwrap(); diff --git a/crates/trie/db/src/trie_cursor.rs b/crates/trie/db/src/trie_cursor.rs index ad6b8eac171..d4cfa22f309 100644 --- a/crates/trie/db/src/trie_cursor.rs +++ b/crates/trie/db/src/trie_cursor.rs @@ -140,7 +140,7 @@ where let mut num_entries = 0; for (nibbles, maybe_updated) in storage_updates.into_iter().filter(|(n, _)| !n.is_empty()) { num_entries += 1; - let nibbles = StoredNibblesSubKey(nibbles.clone()); + let nibbles = StoredNibblesSubKey(*nibbles); // Delete the old entry if it exists. if self .cursor @@ -175,7 +175,7 @@ where ) -> Result, DatabaseError> { Ok(self .cursor - .seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key.clone()))? + .seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))? .filter(|e| e.nibbles == StoredNibblesSubKey(key)) .map(|value| (value.nibbles.0, value.node))) } diff --git a/crates/trie/db/tests/trie.rs b/crates/trie/db/tests/trie.rs index 232d36e66e6..4b56911b518 100644 --- a/crates/trie/db/tests/trie.rs +++ b/crates/trie/db/tests/trie.rs @@ -431,7 +431,7 @@ fn account_and_storage_trie() { assert_eq!(account_updates.len(), 2); let (nibbles1a, node1a) = account_updates.first().unwrap(); - assert_eq!(nibbles1a[..], [0xB]); + assert_eq!(nibbles1a.to_vec(), vec![0xB]); assert_eq!(node1a.state_mask, TrieMask::new(0b1011)); assert_eq!(node1a.tree_mask, TrieMask::new(0b0001)); assert_eq!(node1a.hash_mask, TrieMask::new(0b1001)); @@ -439,7 +439,7 @@ fn account_and_storage_trie() { assert_eq!(node1a.hashes.len(), 2); let (nibbles2a, node2a) = account_updates.last().unwrap(); - assert_eq!(nibbles2a[..], [0xB, 0x0]); + assert_eq!(nibbles2a.to_vec(), vec![0xB, 0x0]); assert_eq!(node2a.state_mask, TrieMask::new(0b10001)); assert_eq!(node2a.tree_mask, TrieMask::new(0b00000)); assert_eq!(node2a.hash_mask, TrieMask::new(0b10000)); @@ -474,7 +474,7 @@ fn account_and_storage_trie() { assert_eq!(account_updates.len(), 2); let (nibbles1b, node1b) = account_updates.first().unwrap(); - assert_eq!(nibbles1b[..], [0xB]); + assert_eq!(nibbles1b.to_vec(), vec![0xB]); assert_eq!(node1b.state_mask, TrieMask::new(0b1011)); assert_eq!(node1b.tree_mask, TrieMask::new(0b0001)); assert_eq!(node1b.hash_mask, TrieMask::new(0b1011)); @@ -484,7 +484,7 @@ fn account_and_storage_trie() { assert_eq!(node1a.hashes[1], node1b.hashes[2]); let (nibbles2b, node2b) = account_updates.last().unwrap(); - assert_eq!(nibbles2b[..], [0xB, 0x0]); + assert_eq!(nibbles2b.to_vec(), vec![0xB, 0x0]); assert_eq!(node2a, node2b); tx.commit().unwrap(); @@ -525,7 +525,7 @@ fn account_and_storage_trie() { assert_eq!(trie_updates.account_nodes_ref().len(), 1); let (nibbles1c, node1c) = trie_updates.account_nodes_ref().iter().next().unwrap(); - assert_eq!(nibbles1c[..], [0xB]); + assert_eq!(nibbles1c.to_vec(), vec![0xB]); assert_eq!(node1c.state_mask, TrieMask::new(0b1011)); assert_eq!(node1c.tree_mask, TrieMask::new(0b0000)); @@ -583,7 +583,7 @@ fn account_and_storage_trie() { assert_eq!(trie_updates.account_nodes_ref().len(), 1); let (nibbles1d, node1d) = trie_updates.account_nodes_ref().iter().next().unwrap(); - assert_eq!(nibbles1d[..], [0xB]); + assert_eq!(nibbles1d.to_vec(), vec![0xB]); assert_eq!(node1d.state_mask, TrieMask::new(0b1011)); assert_eq!(node1d.tree_mask, TrieMask::new(0b0000)); @@ -742,11 +742,11 @@ fn extension_node_trie( fn assert_trie_updates(account_updates: &HashMap) { assert_eq!(account_updates.len(), 2); - let node = account_updates.get(&[0x3][..]).unwrap(); + let node = account_updates.get(&Nibbles::from_nibbles_unchecked([0x3])).unwrap(); let expected = BranchNodeCompact::new(0b0011, 0b0001, 0b0000, vec![], None); assert_eq!(node, &expected); - let node = account_updates.get(&[0x3, 0x0, 0xA, 0xF][..]).unwrap(); + let node = account_updates.get(&Nibbles::from_nibbles_unchecked([0x3, 0x0, 0xA, 0xF])).unwrap(); assert_eq!(node.state_mask, TrieMask::new(0b101100000)); assert_eq!(node.tree_mask, TrieMask::new(0b000000000)); assert_eq!(node.hash_mask, TrieMask::new(0b001000000)); diff --git a/crates/trie/db/tests/walker.rs b/crates/trie/db/tests/walker.rs index 5fd7538cd47..22316cd5ad4 100644 --- a/crates/trie/db/tests/walker.rs +++ b/crates/trie/db/tests/walker.rs @@ -66,7 +66,7 @@ where // We're traversing the path in lexicographical order. for expected in expected { walker.advance().unwrap(); - let got = walker.key().cloned(); + let got = walker.key().copied(); assert_eq!(got.unwrap(), Nibbles::from_nibbles_unchecked(expected.clone())); } @@ -115,10 +115,10 @@ fn cursor_rootnode_with_changesets() { // No changes let mut cursor = TrieWalker::state_trie(&mut trie, Default::default()); - assert_eq!(cursor.key().cloned(), Some(Nibbles::new())); // root + assert_eq!(cursor.key().copied(), Some(Nibbles::new())); // root assert!(cursor.can_skip_current_node); // due to root_hash cursor.advance().unwrap(); // skips to the end of trie - assert_eq!(cursor.key().cloned(), None); + assert_eq!(cursor.key().copied(), None); // We insert something that's not part of the existing trie/prefix. let mut changed = PrefixSetMut::default(); @@ -126,16 +126,16 @@ fn cursor_rootnode_with_changesets() { let mut cursor = TrieWalker::state_trie(&mut trie, changed.freeze()); // Root node - assert_eq!(cursor.key().cloned(), Some(Nibbles::new())); + assert_eq!(cursor.key().copied(), Some(Nibbles::new())); // Should not be able to skip state due to the changed values assert!(!cursor.can_skip_current_node); cursor.advance().unwrap(); - assert_eq!(cursor.key().cloned(), Some(Nibbles::from_nibbles([0x2]))); + assert_eq!(cursor.key().copied(), Some(Nibbles::from_nibbles([0x2]))); cursor.advance().unwrap(); - assert_eq!(cursor.key().cloned(), Some(Nibbles::from_nibbles([0x2, 0x1]))); + assert_eq!(cursor.key().copied(), Some(Nibbles::from_nibbles([0x2, 0x1]))); cursor.advance().unwrap(); - assert_eq!(cursor.key().cloned(), Some(Nibbles::from_nibbles([0x4]))); + assert_eq!(cursor.key().copied(), Some(Nibbles::from_nibbles([0x4]))); cursor.advance().unwrap(); - assert_eq!(cursor.key().cloned(), None); // the end of trie + assert_eq!(cursor.key().copied(), None); // the end of trie } diff --git a/crates/trie/parallel/src/proof.rs b/crates/trie/parallel/src/proof.rs index e183f566948..940a51a924e 100644 --- a/crates/trie/parallel/src/proof.rs +++ b/crates/trie/parallel/src/proof.rs @@ -309,10 +309,7 @@ where let (branch_node_hash_masks, branch_node_tree_masks) = if self.collect_branch_node_masks { let updated_branch_nodes = hash_builder.updated_branch_nodes.unwrap_or_default(); ( - updated_branch_nodes - .iter() - .map(|(path, node)| (path.clone(), node.hash_mask)) - .collect(), + updated_branch_nodes.iter().map(|(path, node)| (*path, node.hash_mask)).collect(), updated_branch_nodes .into_iter() .map(|(path, node)| (path, node.tree_mask)) diff --git a/crates/trie/parallel/src/proof_task.rs b/crates/trie/parallel/src/proof_task.rs index 70dea2cf22f..253624d71fc 100644 --- a/crates/trie/parallel/src/proof_task.rs +++ b/crates/trie/parallel/src/proof_task.rs @@ -249,7 +249,7 @@ where hashed_cursor_factory, input.hashed_address, ) - .with_prefix_set_mut(PrefixSetMut::from(input.prefix_set.iter().cloned())) + .with_prefix_set_mut(PrefixSetMut::from(input.prefix_set.iter().copied())) .with_branch_node_masks(input.with_branch_node_masks) .storage_multiproof(input.target_slots) .map_err(|e| ParallelStateRootError::Other(e.to_string())); @@ -534,12 +534,12 @@ impl BlindedProvider for ProofTaskBlindedNodeProvider { match self { Self::AccountNode { sender } => { let _ = sender.send(ProofTaskMessage::QueueTask( - ProofTaskKind::BlindedAccountNode(path.clone(), tx), + ProofTaskKind::BlindedAccountNode(*path, tx), )); } Self::StorageNode { sender, account } => { let _ = sender.send(ProofTaskMessage::QueueTask( - ProofTaskKind::BlindedStorageNode(*account, path.clone(), tx), + ProofTaskKind::BlindedStorageNode(*account, *path, tx), )); } } diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index daf65d8c077..fd1b326b193 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -110,7 +110,7 @@ impl ParallelSparseTrie { // If there is no subtrie for the path it means the path is UPPER_TRIE_MAX_DEPTH or less // nibbles, and so belongs to the upper trie. - self.upper_subtrie.reveal_node(path.clone(), &node, masks)?; + self.upper_subtrie.reveal_node(path, &node, masks)?; // The previous upper_trie.reveal_node call will not have revealed any child nodes via // reveal_node_or_hash if the child node would be found on a lower subtrie. We handle that @@ -124,7 +124,7 @@ impl ParallelSparseTrie { let mut stack_ptr = branch.as_ref().first_child_index(); for idx in CHILD_INDEX_RANGE { if branch.state_mask.is_bit_set(idx) { - let mut child_path = path.clone(); + let mut child_path = path; child_path.push_unchecked(idx); self.lower_subtrie_for_path(&child_path) .expect("child_path must have a lower subtrie") @@ -135,8 +135,8 @@ impl ParallelSparseTrie { } } TrieNode::Extension(ext) => { - let mut child_path = path.clone(); - child_path.extend_from_slice_unchecked(&ext.key); + let mut child_path = path; + child_path.extend(&ext.key); if child_path.len() > UPPER_TRIE_MAX_DEPTH { self.lower_subtrie_for_path(&child_path) .expect("child_path must have a lower subtrie") @@ -276,7 +276,7 @@ impl ParallelSparseTrie { ) -> (Vec, PrefixSetMut) { // Clone the prefix set to iterate over its keys. Cloning is cheap, it's just an Arc. let prefix_set_clone = prefix_set.clone(); - let mut prefix_set_iter = prefix_set_clone.into_iter().cloned().peekable(); + let mut prefix_set_iter = prefix_set_clone.into_iter().copied().peekable(); let mut changed_subtries = Vec::new(); let mut unchanged_prefix_set = PrefixSetMut::default(); @@ -292,7 +292,7 @@ impl ParallelSparseTrie { // prefix set iterator. let mut new_prefix_set = Vec::new(); while let Some(key) = prefix_set_iter.peek() { - if key.has_prefix(&subtrie.path) { + if key.starts_with(&subtrie.path) { // If the key starts with the subtrie path, add it to the new prefix set new_prefix_set.push(prefix_set_iter.next().unwrap()); } else if new_prefix_set.is_empty() && key < &subtrie.path { @@ -382,10 +382,10 @@ impl SparseSubtrie { } if let Some(tree_mask) = masks.tree_mask { - self.branch_node_tree_masks.insert(path.clone(), tree_mask); + self.branch_node_tree_masks.insert(path, tree_mask); } if let Some(hash_mask) = masks.hash_mask { - self.branch_node_hash_masks.insert(path.clone(), hash_mask); + self.branch_node_hash_masks.insert(path, hash_mask); } match node { @@ -400,7 +400,7 @@ impl SparseSubtrie { let mut stack_ptr = branch.as_ref().first_child_index(); for idx in CHILD_INDEX_RANGE { if branch.state_mask.is_bit_set(idx) { - let mut child_path = path.clone(); + let mut child_path = path; child_path.push_unchecked(idx); if Self::is_child_same_level(&path, &child_path) { // Reveal each child node or hash it has, but only if the child is on @@ -433,7 +433,7 @@ impl SparseSubtrie { // All other node types can't be handled. node @ (SparseNode::Empty | SparseNode::Leaf { .. }) => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(node.clone()), } .into()) @@ -444,14 +444,14 @@ impl SparseSubtrie { } } } - TrieNode::Extension(ext) => match self.nodes.entry(path.clone()) { + TrieNode::Extension(ext) => match self.nodes.entry(path) { Entry::Occupied(mut entry) => match entry.get() { // Replace a hash node with a revealed extension node. SparseNode::Hash(hash) => { - let mut child_path = entry.key().clone(); - child_path.extend_from_slice_unchecked(&ext.key); + let mut child_path = *entry.key(); + child_path.extend(&ext.key); entry.insert(SparseNode::Extension { - key: ext.key.clone(), + key: ext.key, // Memoize the hash of a previously blinded node in a new extension // node. hash: Some(*hash), @@ -467,16 +467,16 @@ impl SparseSubtrie { // All other node types can't be handled. node @ (SparseNode::Empty | SparseNode::Leaf { .. }) => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(node.clone()), } .into()) } }, Entry::Vacant(entry) => { - let mut child_path = entry.key().clone(); - child_path.extend_from_slice_unchecked(&ext.key); - entry.insert(SparseNode::new_ext(ext.key.clone())); + let mut child_path = *entry.key(); + child_path.extend(&ext.key); + entry.insert(SparseNode::new_ext(ext.key)); if Self::is_child_same_level(&path, &child_path) { self.reveal_node_or_hash(child_path, &ext.child)?; } @@ -486,11 +486,11 @@ impl SparseSubtrie { Entry::Occupied(mut entry) => match entry.get() { // Replace a hash node with a revealed leaf node and store leaf node value. SparseNode::Hash(hash) => { - let mut full = entry.key().clone(); - full.extend_from_slice_unchecked(&leaf.key); + let mut full = *entry.key(); + full.extend(&leaf.key); self.values.insert(full, leaf.value.clone()); entry.insert(SparseNode::Leaf { - key: leaf.key.clone(), + key: leaf.key, // Memoize the hash of a previously blinded node in a new leaf // node. hash: Some(*hash), @@ -503,16 +503,16 @@ impl SparseSubtrie { SparseNode::Extension { .. } | SparseNode::Branch { .. }) => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(node.clone()), } .into()) } }, Entry::Vacant(entry) => { - let mut full = entry.key().clone(); - full.extend_from_slice_unchecked(&leaf.key); - entry.insert(SparseNode::new_leaf(leaf.key.clone())); + let mut full = *entry.key(); + full.extend(&leaf.key); + entry.insert(SparseNode::new_leaf(leaf.key)); self.values.insert(full, leaf.value.clone()); } }, @@ -547,7 +547,7 @@ impl SparseSubtrie { // Hash node with a different hash can't be handled. SparseNode::Hash(previous_hash) if previous_hash != &hash => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(SparseNode::Hash(hash)), } .into()) @@ -653,7 +653,7 @@ struct ChangedSubtrie { /// If the path is shorter than [`UPPER_TRIE_MAX_DEPTH`] nibbles. fn path_subtrie_index_unchecked(path: &Nibbles) -> usize { debug_assert_eq!(UPPER_TRIE_MAX_DEPTH, 2); - (path[0] << 4 | path[1]) as usize + path.get_byte_unchecked(0) as usize } #[cfg(test)] @@ -718,7 +718,7 @@ mod tests { let (subtries, unchanged_prefix_set) = trie.take_changed_lower_subtries(&mut prefix_set); assert!(subtries.is_empty()); - assert_eq!(unchanged_prefix_set, PrefixSetMut::from(prefix_set.iter().cloned())); + assert_eq!(unchanged_prefix_set, PrefixSetMut::from(prefix_set.iter().copied())); } #[test] @@ -756,7 +756,7 @@ mod tests { subtries .into_iter() .map(|ChangedSubtrie { index, subtrie, prefix_set }| { - (index, subtrie, prefix_set.iter().cloned().collect::>()) + (index, subtrie, prefix_set.iter().copied().collect::>()) }) .collect::>(), vec![( @@ -860,7 +860,7 @@ mod tests { let node = create_leaf_node(&[0x3, 0x4], 42); let masks = TrieMasks::none(); - trie.reveal_node(path.clone(), node, masks).unwrap(); + trie.reveal_node(path, node, masks).unwrap(); assert_matches!( trie.upper_subtrie.nodes.get(&path), @@ -878,7 +878,7 @@ mod tests { let node = create_leaf_node(&[0x4, 0x5], 42); let masks = TrieMasks::none(); - trie.reveal_node(path.clone(), node, masks).unwrap(); + trie.reveal_node(path, node, masks).unwrap(); // Check that the lower subtrie was created let idx = path_subtrie_index_unchecked(&path); @@ -901,7 +901,7 @@ mod tests { let node = create_extension_node(&[0x2], child_hash); let masks = TrieMasks::none(); - trie.reveal_node(path.clone(), node, masks).unwrap(); + trie.reveal_node(path, node, masks).unwrap(); assert_matches!( trie.upper_subtrie.nodes.get(&path), @@ -922,7 +922,7 @@ mod tests { let node = create_extension_node(&[0x3], child_hash); let masks = TrieMasks::none(); - trie.reveal_node(path.clone(), node, masks).unwrap(); + trie.reveal_node(path, node, masks).unwrap(); // Extension node should be in upper trie assert_matches!( @@ -948,7 +948,7 @@ mod tests { let node = create_branch_node_with_children(&[0x0, 0x5], &child_hashes); let masks = TrieMasks::none(); - trie.reveal_node(path.clone(), node, masks).unwrap(); + trie.reveal_node(path, node, masks).unwrap(); // Branch node should be in upper trie assert_matches!( @@ -979,7 +979,7 @@ mod tests { let node = create_branch_node_with_children(&[0x0, 0x7, 0xf], &child_hashes); let masks = TrieMasks::none(); - trie.reveal_node(path.clone(), node, masks).unwrap(); + trie.reveal_node(path, node, masks).unwrap(); // Branch node should be in upper trie assert_matches!( diff --git a/crates/trie/sparse/benches/update.rs b/crates/trie/sparse/benches/update.rs index 1b3ce121105..4b2971c1e05 100644 --- a/crates/trie/sparse/benches/update.rs +++ b/crates/trie/sparse/benches/update.rs @@ -31,7 +31,7 @@ fn update_leaf(c: &mut Criterion) { .into_iter() .map(|(path, _)| { ( - path.clone(), + path, alloy_rlp::encode_fixed_size(&U256::from(path.len() * 2)).to_vec(), ) }) @@ -41,7 +41,7 @@ fn update_leaf(c: &mut Criterion) { }, |(mut trie, new_leaves)| { for (path, new_value) in new_leaves { - trie.update_leaf(path, new_value).unwrap(); + trie.update_leaf(*path, new_value).unwrap(); } trie }, diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index 84a5a03c12c..66c3596363c 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -234,7 +234,7 @@ impl SparseStateTrie { continue } let node = TrieNode::decode(&mut &bytes[..])?; - trie.reveal_node(path.clone(), node, TrieMasks::none())?; + trie.reveal_node(path, node, TrieMasks::none())?; // Track the revealed path. self.revealed_account_paths.insert(path); @@ -281,7 +281,7 @@ impl SparseStateTrie { continue } let node = TrieNode::decode(&mut &bytes[..])?; - trie.reveal_node(path.clone(), node, TrieMasks::none())?; + trie.reveal_node(path, node, TrieMasks::none())?; // Track the revealed path. revealed_nodes.insert(path); @@ -388,7 +388,7 @@ impl SparseStateTrie { }; trace!(target: "trie::sparse", ?path, ?node, ?hash_mask, ?tree_mask, "Revealing account node"); - trie.reveal_node(path.clone(), node, TrieMasks { hash_mask, tree_mask })?; + trie.reveal_node(path, node, TrieMasks { hash_mask, tree_mask })?; // Track the revealed path. self.revealed_account_paths.insert(path); @@ -463,7 +463,7 @@ impl SparseStateTrie { }; trace!(target: "trie::sparse", ?account, ?path, ?node, ?hash_mask, ?tree_mask, "Revealing storage node"); - trie.reveal_node(path.clone(), node, TrieMasks { hash_mask, tree_mask })?; + trie.reveal_node(path, node, TrieMasks { hash_mask, tree_mask })?; // Track the revealed path. revealed_nodes.insert(path); @@ -495,7 +495,7 @@ impl SparseStateTrie { TrieNode::Branch(branch) => { for (idx, maybe_child) in branch.as_ref().children() { if let Some(child_hash) = maybe_child.and_then(RlpNode::as_hash) { - let mut child_path = path.clone(); + let mut child_path = path; child_path.push_unchecked(idx); queue.push_back((child_hash, child_path, maybe_account)); } @@ -503,14 +503,14 @@ impl SparseStateTrie { } TrieNode::Extension(ext) => { if let Some(child_hash) = ext.child.as_hash() { - let mut child_path = path.clone(); - child_path.extend_from_slice_unchecked(&ext.key); + let mut child_path = path; + child_path.extend(&ext.key); queue.push_back((child_hash, child_path, maybe_account)); } } TrieNode::Leaf(leaf) => { - let mut full_path = path.clone(); - full_path.extend_from_slice_unchecked(&leaf.key); + let mut full_path = path; + full_path.extend(&leaf.key); if maybe_account.is_none() { let hashed_address = B256::from_slice(&full_path.pack()); let account = TrieAccount::decode(&mut &leaf.value[..])?; @@ -548,7 +548,7 @@ impl SparseStateTrie { storage_trie_entry .as_revealed_mut() .ok_or(SparseTrieErrorKind::Blind)? - .reveal_node(path.clone(), trie_node, TrieMasks::none())?; + .reveal_node(path, trie_node, TrieMasks::none())?; } // Track the revealed path. @@ -568,7 +568,7 @@ impl SparseStateTrie { } else { // Reveal non-root state trie node. self.state.as_revealed_mut().ok_or(SparseTrieErrorKind::Blind)?.reveal_node( - path.clone(), + path, trie_node, TrieMasks::none(), )?; @@ -752,7 +752,7 @@ impl SparseStateTrie { value: Vec, ) -> SparseStateTrieResult<()> { if !self.revealed_account_paths.contains(&path) { - self.revealed_account_paths.insert(path.clone()); + self.revealed_account_paths.insert(path); } self.state.update_leaf(path, value)?; @@ -767,7 +767,7 @@ impl SparseStateTrie { value: Vec, ) -> SparseStateTrieResult<()> { if !self.revealed_storage_paths.get(&address).is_some_and(|slots| slots.contains(&slot)) { - self.revealed_storage_paths.entry(address).or_default().insert(slot.clone()); + self.revealed_storage_paths.entry(address).or_default().insert(slot); } let storage_trie = self.storages.get_mut(&address).ok_or(SparseTrieErrorKind::Blind)?; @@ -1182,11 +1182,8 @@ mod tests { let slot_path_3 = Nibbles::unpack(slot_3); let value_3 = U256::from(rng.random::()); - let mut storage_hash_builder = - HashBuilder::default().with_proof_retainer(ProofRetainer::from_iter([ - slot_path_1.clone(), - slot_path_2.clone(), - ])); + let mut storage_hash_builder = HashBuilder::default() + .with_proof_retainer(ProofRetainer::from_iter([slot_path_1, slot_path_2])); storage_hash_builder.add_leaf(slot_path_1, &alloy_rlp::encode_fixed_size(&value_1)); storage_hash_builder.add_leaf(slot_path_2, &alloy_rlp::encode_fixed_size(&value_2)); @@ -1206,13 +1203,10 @@ mod tests { let account_2 = Account::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); let mut trie_account_2 = account_2.into_trie_account(EMPTY_ROOT_HASH); - let mut hash_builder = - HashBuilder::default().with_proof_retainer(ProofRetainer::from_iter([ - address_path_1.clone(), - address_path_2.clone(), - ])); - hash_builder.add_leaf(address_path_1.clone(), &alloy_rlp::encode(trie_account_1)); - hash_builder.add_leaf(address_path_2.clone(), &alloy_rlp::encode(trie_account_2)); + let mut hash_builder = HashBuilder::default() + .with_proof_retainer(ProofRetainer::from_iter([address_path_1, address_path_2])); + hash_builder.add_leaf(address_path_1, &alloy_rlp::encode(trie_account_1)); + hash_builder.add_leaf(address_path_2, &alloy_rlp::encode(trie_account_2)); let root = hash_builder.root(); let proof_nodes = hash_builder.take_proof_nodes(); diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 45f1a266e47..ed84592e4b7 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -384,7 +384,7 @@ impl fmt::Display for RevealedSparseTrie

{ stack.push((Nibbles::default(), self.nodes_ref().get(&Nibbles::default()).unwrap(), 0)); while let Some((path, node, depth)) = stack.pop() { - if !visited.insert(path.clone()) { + if !visited.insert(path) { continue; } @@ -401,8 +401,8 @@ impl fmt::Display for RevealedSparseTrie

{ } SparseNode::Leaf { key, .. } => { // we want to append the key to the path - let mut full_path = path.clone(); - full_path.extend_from_slice_unchecked(key); + let mut full_path = path; + full_path.extend(key); let packed_path = encode_nibbles(&full_path); writeln!(f, "{packed_path} -> {node:?}")?; @@ -411,8 +411,8 @@ impl fmt::Display for RevealedSparseTrie

{ writeln!(f, "{packed_path} -> {node:?}")?; // push the child node onto the stack with increased depth - let mut child_path = path.clone(); - child_path.extend_from_slice_unchecked(key); + let mut child_path = path; + child_path.extend(key); if let Some(child_node) = self.nodes_ref().get(&child_path) { stack.push((child_path, child_node, depth + 1)); } @@ -422,7 +422,7 @@ impl fmt::Display for RevealedSparseTrie

{ for i in CHILD_INDEX_RANGE.rev() { if state_mask.is_bit_set(i) { - let mut child_path = path.clone(); + let mut child_path = path; child_path.push_unchecked(i); if let Some(child_node) = self.nodes_ref().get(&child_path) { stack.push((child_path, child_node, depth + 1)); @@ -638,10 +638,10 @@ impl

RevealedSparseTrie

{ } if let Some(tree_mask) = masks.tree_mask { - self.branch_node_tree_masks.insert(path.clone(), tree_mask); + self.branch_node_tree_masks.insert(path, tree_mask); } if let Some(hash_mask) = masks.hash_mask { - self.branch_node_hash_masks.insert(path.clone(), hash_mask); + self.branch_node_hash_masks.insert(path, hash_mask); } match node { @@ -655,7 +655,7 @@ impl

RevealedSparseTrie

{ let mut stack_ptr = branch.as_ref().first_child_index(); for idx in CHILD_INDEX_RANGE { if branch.state_mask.is_bit_set(idx) { - let mut child_path = path.clone(); + let mut child_path = path; child_path.push_unchecked(idx); // Reveal each child node or hash it has self.reveal_node_or_hash(child_path, &branch.stack[stack_ptr])?; @@ -685,7 +685,7 @@ impl

RevealedSparseTrie

{ // All other node types can't be handled. node @ (SparseNode::Empty | SparseNode::Leaf { .. }) => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(node.clone()), } .into()) @@ -700,8 +700,8 @@ impl

RevealedSparseTrie

{ Entry::Occupied(mut entry) => match entry.get() { // Replace a hash node with a revealed extension node. SparseNode::Hash(hash) => { - let mut child_path = entry.key().clone(); - child_path.extend_from_slice_unchecked(&ext.key); + let mut child_path = *entry.key(); + child_path.extend(&ext.key); entry.insert(SparseNode::Extension { key: ext.key, // Memoize the hash of a previously blinded node in a new extension @@ -717,15 +717,15 @@ impl

RevealedSparseTrie

{ // All other node types can't be handled. node @ (SparseNode::Empty | SparseNode::Leaf { .. }) => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(node.clone()), } .into()) } }, Entry::Vacant(entry) => { - let mut child_path = entry.key().clone(); - child_path.extend_from_slice_unchecked(&ext.key); + let mut child_path = *entry.key(); + child_path.extend(&ext.key); entry.insert(SparseNode::new_ext(ext.key)); self.reveal_node_or_hash(child_path, &ext.child)?; } @@ -734,8 +734,8 @@ impl

RevealedSparseTrie

{ Entry::Occupied(mut entry) => match entry.get() { // Replace a hash node with a revealed leaf node and store leaf node value. SparseNode::Hash(hash) => { - let mut full = entry.key().clone(); - full.extend_from_slice_unchecked(&leaf.key); + let mut full = *entry.key(); + full.extend(&leaf.key); self.values.insert(full, leaf.value); entry.insert(SparseNode::Leaf { key: leaf.key, @@ -751,15 +751,15 @@ impl

RevealedSparseTrie

{ SparseNode::Extension { .. } | SparseNode::Branch { .. }) => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(node.clone()), } .into()) } }, Entry::Vacant(entry) => { - let mut full = entry.key().clone(); - full.extend_from_slice_unchecked(&leaf.key); + let mut full = *entry.key(); + full.extend(&leaf.key); entry.insert(SparseNode::new_leaf(leaf.key)); self.values.insert(full, leaf.value); } @@ -795,7 +795,7 @@ impl

RevealedSparseTrie

{ // Hash node with a different hash can't be handled. SparseNode::Hash(previous_hash) if previous_hash != &hash => { return Err(SparseTrieErrorKind::Reveal { - path: entry.key().clone(), + path: *entry.key(), node: Box::new(SparseNode::Hash(hash)), } .into()) @@ -844,13 +844,13 @@ impl

RevealedSparseTrie

{ #[cfg(debug_assertions)] { - let mut current = current.clone(); - current.extend_from_slice_unchecked(_key); + let mut current = current; + current.extend(_key); assert_eq!(¤t, path); } nodes.push(RemovedSparseNode { - path: current.clone(), + path: current, node, unset_branch_nibble: None, }); @@ -859,20 +859,20 @@ impl

RevealedSparseTrie

{ SparseNode::Extension { key, .. } => { #[cfg(debug_assertions)] { - let mut current = current.clone(); - current.extend_from_slice_unchecked(key); + let mut current = current; + current.extend(key); assert!( path.starts_with(¤t), "path: {path:?}, current: {current:?}, key: {key:?}", ); } - let path = current.clone(); - current.extend_from_slice_unchecked(key); + let path = current; + current.extend(key); nodes.push(RemovedSparseNode { path, node, unset_branch_nibble: None }); } SparseNode::Branch { state_mask, .. } => { - let nibble = path[current.len()]; + let nibble = path.get_unchecked(current.len()); debug_assert!( state_mask.is_bit_set(nibble), "current: {current:?}, path: {path:?}, nibble: {nibble:?}, state_mask: {state_mask:?}", @@ -883,26 +883,22 @@ impl

RevealedSparseTrie

{ // Any other branch nodes will not require unsetting the nibble, because // deleting one leaf node can not remove the whole path // where the branch node is located. - let mut child_path = - Nibbles::from_nibbles([current.as_slice(), &[nibble]].concat()); + let mut child_path = current; + child_path.push_unchecked(nibble); let unset_branch_nibble = self .nodes .get(&child_path) .is_some_and(move |node| match node { SparseNode::Leaf { key, .. } => { // Get full path of the leaf node - child_path.extend_from_slice_unchecked(key); + child_path.extend(key); &child_path == path } _ => false, }) .then_some(nibble); - nodes.push(RemovedSparseNode { - path: current.clone(), - node, - unset_branch_nibble, - }); + nodes.push(RemovedSparseNode { path: current, node, unset_branch_nibble }); current.push_unchecked(nibble); } @@ -1047,9 +1043,9 @@ impl

RevealedSparseTrie

{ if level >= depth { targets.push((level, path)); } else { - unchanged_prefix_set.insert(path.clone()); + unchanged_prefix_set.insert(path); - path.extend_from_slice_unchecked(key); + path.extend(key); paths.push((path, level + 1)); } } @@ -1061,11 +1057,11 @@ impl

RevealedSparseTrie

{ if level >= depth { targets.push((level, path)); } else { - unchanged_prefix_set.insert(path.clone()); + unchanged_prefix_set.insert(path); for bit in CHILD_INDEX_RANGE.rev() { if state_mask.is_bit_set(bit) { - let mut child_path = path.clone(); + let mut child_path = path; child_path.push_unchecked(bit); paths.push((child_path, level + 1)); } @@ -1112,7 +1108,7 @@ impl

RevealedSparseTrie

{ buffers: &mut RlpNodeBuffers, rlp_buf: &mut Vec, ) -> RlpNode { - let _starting_path = buffers.path_stack.last().map(|item| item.path.clone()); + let _starting_path = buffers.path_stack.last().map(|item| item.path); 'main: while let Some(RlpNodePathStackItem { level, path, mut is_in_prefix_set }) = buffers.path_stack.pop() @@ -1138,8 +1134,8 @@ impl

RevealedSparseTrie

{ SparseNode::Empty => (RlpNode::word_rlp(&EMPTY_ROOT_HASH), SparseNodeType::Empty), SparseNode::Hash(hash) => (RlpNode::word_rlp(hash), SparseNodeType::Hash), SparseNode::Leaf { key, hash } => { - let mut path = path.clone(); - path.extend_from_slice_unchecked(key); + let mut path = path; + path.extend(key); if let Some(hash) = hash.filter(|_| !prefix_set_contains(&path)) { (RlpNode::word_rlp(&hash), SparseNodeType::Leaf) } else { @@ -1151,8 +1147,8 @@ impl

RevealedSparseTrie

{ } } SparseNode::Extension { key, hash, store_in_db_trie } => { - let mut child_path = path.clone(); - child_path.extend_from_slice_unchecked(key); + let mut child_path = path; + child_path.extend(key); if let Some((hash, store_in_db_trie)) = hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) { @@ -1223,7 +1219,7 @@ impl

RevealedSparseTrie

{ // from the stack and keep walking in the sorted order. for bit in CHILD_INDEX_RANGE.rev() { if state_mask.is_bit_set(bit) { - let mut child = path.clone(); + let mut child = path; child.push_unchecked(bit); buffers.branch_child_buf.push(child); } @@ -1341,7 +1337,7 @@ impl

RevealedSparseTrie

{ hashes, hash.filter(|_| path.is_empty()), ); - updates.updated_nodes.insert(path.clone(), branch_node); + updates.updated_nodes.insert(path, branch_node); } else if self .branch_node_tree_masks .get(&path) @@ -1354,7 +1350,7 @@ impl

RevealedSparseTrie

{ // need to remove the node update and add the node itself to the list of // removed nodes. updates.updated_nodes.remove(&path); - updates.removed_nodes.insert(path.clone()); + updates.removed_nodes.insert(path); } else if self .branch_node_hash_masks .get(&path) @@ -1468,7 +1464,7 @@ impl RevealedSparseTrie

{ if let Some(expected) = expected_value { if actual_value != expected { return Err(LeafLookupError::ValueMismatch { - path: path.clone(), + path: *path, expected: Some(expected.clone()), actual: actual_value.clone(), }); @@ -1505,14 +1501,14 @@ impl RevealedSparseTrie

{ } Some(&SparseNode::Hash(hash)) => { // We hit a blinded node - cannot determine if leaf exists - return Err(LeafLookupError::BlindedNode { path: current.clone(), hash }); + return Err(LeafLookupError::BlindedNode { path: current, hash }); } Some(SparseNode::Leaf { key, .. }) => { // We found a leaf node before reaching our target depth // Temporarily append the leaf key to `current` let saved_len = current.len(); - current.extend_from_slice_unchecked(key); + current.extend(key); if ¤t == path { // This should have been handled by our initial values map check @@ -1531,7 +1527,7 @@ impl RevealedSparseTrie

{ Some(SparseNode::Extension { key, .. }) => { // Temporarily append the extension key to `current` let saved_len = current.len(); - current.extend_from_slice_unchecked(key); + current.extend(key); if path.len() < current.len() || !path.starts_with(¤t) { let diverged_at = current.slice(..saved_len); @@ -1542,7 +1538,7 @@ impl RevealedSparseTrie

{ } Some(SparseNode::Branch { state_mask, .. }) => { // Check if branch has a child at the next nibble in our path - let nibble = path[current.len()]; + let nibble = path.get_unchecked(current.len()); if !state_mask.is_bit_set(nibble) { // No child at this nibble - exclusion proof return Ok(LeafLookup::NonExistent { diverged_at: current }); @@ -1566,7 +1562,7 @@ impl RevealedSparseTrie

{ } } Some(&SparseNode::Hash(hash)) => { - return Err(LeafLookupError::BlindedNode { path: path.clone(), hash }); + return Err(LeafLookupError::BlindedNode { path: *path, hash }); } _ => { // No leaf at exactly the target path @@ -1597,8 +1593,8 @@ impl RevealedSparseTrie

{ /// Note: If an update requires revealing a blinded node, an error is returned if the blinded /// provider returns an error. pub fn update_leaf(&mut self, path: Nibbles, value: Vec) -> SparseTrieResult<()> { - self.prefix_set.insert(path.clone()); - let existing = self.values.insert(path.clone(), value); + self.prefix_set.insert(path); + let existing = self.values.insert(path, value); if existing.is_some() { // trie structure unchanged, return immediately return Ok(()) @@ -1615,7 +1611,7 @@ impl RevealedSparseTrie

{ return Err(SparseTrieErrorKind::BlindedNode { path: current, hash }.into()) } SparseNode::Leaf { key: current_key, .. } => { - current.extend_from_slice_unchecked(current_key); + current.extend(current_key); // this leaf is being updated if current == path { @@ -1633,7 +1629,10 @@ impl RevealedSparseTrie

{ self.nodes.reserve(3); self.nodes.insert( current.slice(..common), - SparseNode::new_split_branch(current[common], path[common]), + SparseNode::new_split_branch( + current.get_unchecked(common), + path.get_unchecked(common), + ), ); self.nodes.insert( path.slice(..=common), @@ -1647,7 +1646,7 @@ impl RevealedSparseTrie

{ break; } SparseNode::Extension { key, .. } => { - current.extend_from_slice(key); + current.extend(key); if !path.starts_with(¤t) { // find the common prefix @@ -1673,7 +1672,7 @@ impl RevealedSparseTrie

{ "Revealing extension node child", ); self.reveal_node( - current.clone(), + current, decoded, TrieMasks { hash_mask, tree_mask }, )?; @@ -1684,7 +1683,10 @@ impl RevealedSparseTrie

{ // create state mask for new branch node // NOTE: this might overwrite the current extension node self.nodes.reserve(3); - let branch = SparseNode::new_split_branch(current[common], path[common]); + let branch = SparseNode::new_split_branch( + current.get_unchecked(common), + path.get_unchecked(common), + ); self.nodes.insert(current.slice(..common), branch); // create new leaf @@ -1701,7 +1703,7 @@ impl RevealedSparseTrie

{ } } SparseNode::Branch { state_mask, .. } => { - let nibble = path[current.len()]; + let nibble = path.get_unchecked(current.len()); current.push_unchecked(nibble); if !state_mask.is_bit_set(nibble) { state_mask.set_bit(nibble); @@ -1729,28 +1731,27 @@ impl RevealedSparseTrie

{ if self.values.remove(path).is_none() { if let Some(&SparseNode::Hash(hash)) = self.nodes.get(path) { // Leaf is present in the trie, but it's blinded. - return Err(SparseTrieErrorKind::BlindedNode { path: path.clone(), hash }.into()) + return Err(SparseTrieErrorKind::BlindedNode { path: *path, hash }.into()) } trace!(target: "trie::sparse", ?path, "Leaf node is not present in the trie"); // Leaf is not present in the trie. return Ok(()) } - self.prefix_set.insert(path.clone()); + self.prefix_set.insert(*path); // If the path wasn't present in `values`, we still need to walk the trie and ensure that // there is no node at the path. When a leaf node is a blinded `Hash`, it will have an entry // in `nodes`, but not in the `values`. let mut removed_nodes = self.take_nodes_for_path(path)?; - trace!(target: "trie::sparse", ?path, ?removed_nodes, "Removed nodes for path"); // Pop the first node from the stack which is the leaf node we want to remove. let mut child = removed_nodes.pop().expect("leaf exists"); #[cfg(debug_assertions)] { - let mut child_path = child.path.clone(); + let mut child_path = child.path; let SparseNode::Leaf { key, .. } = &child.node else { panic!("expected leaf node") }; - child_path.extend_from_slice_unchecked(key); + child_path.extend(key); assert_eq!(&child_path, path); } @@ -1793,8 +1794,8 @@ impl RevealedSparseTrie

{ SparseNode::Leaf { key: leaf_key, .. } => { self.nodes.remove(&child.path); - let mut new_key = key.clone(); - new_key.extend_from_slice_unchecked(leaf_key); + let mut new_key = *key; + new_key.extend(leaf_key); SparseNode::new_leaf(new_key) } // For an extension node, we collapse them into one extension node, @@ -1802,8 +1803,8 @@ impl RevealedSparseTrie

{ SparseNode::Extension { key: extension_key, .. } => { self.nodes.remove(&child.path); - let mut new_key = key.clone(); - new_key.extend_from_slice_unchecked(extension_key); + let mut new_key = *key; + new_key.extend(extension_key); SparseNode::new_ext(new_key) } // For a branch node, we just leave the extension node as-is. @@ -1824,7 +1825,7 @@ impl RevealedSparseTrie

{ state_mask.first_set_bit_index().expect("state mask is not empty"); // Get full path of the only child node left. - let mut child_path = removed_path.clone(); + let mut child_path = removed_path; child_path.push_unchecked(child_nibble); trace!(target: "trie::sparse", ?removed_path, ?child_path, "Branch node has only one child"); @@ -1844,7 +1845,7 @@ impl RevealedSparseTrie

{ "Revealing remaining blinded branch child" ); self.reveal_node( - child_path.clone(), + child_path, decoded, TrieMasks { hash_mask, tree_mask }, )?; @@ -1871,7 +1872,7 @@ impl RevealedSparseTrie

{ delete_child = true; let mut new_key = Nibbles::from_nibbles_unchecked([child_nibble]); - new_key.extend_from_slice_unchecked(key); + new_key.extend(key); SparseNode::new_leaf(new_key) } // If the only child node is an extension node, we downgrade the branch @@ -1881,7 +1882,7 @@ impl RevealedSparseTrie

{ delete_child = true; let mut new_key = Nibbles::from_nibbles_unchecked([child_nibble]); - new_key.extend_from_slice_unchecked(key); + new_key.extend(key); SparseNode::new_ext(new_key) } // If the only child is a branch node, we downgrade the current branch @@ -1897,7 +1898,7 @@ impl RevealedSparseTrie

{ if let Some(updates) = self.updates.as_mut() { updates.updated_nodes.remove(&removed_path); - updates.removed_nodes.insert(removed_path.clone()); + updates.removed_nodes.insert(removed_path); } new_node @@ -1910,7 +1911,7 @@ impl RevealedSparseTrie

{ }; child = RemovedSparseNode { - path: removed_path.clone(), + path: removed_path, node: new_node.clone(), unset_branch_nibble: None, }; @@ -2186,7 +2187,7 @@ mod find_leaf_tests { let path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); let value = b"test_value".to_vec(); - sparse.update_leaf(path.clone(), value.clone()).unwrap(); + sparse.update_leaf(path, value.clone()).unwrap(); // Check that the leaf exists let result = sparse.find_leaf(&path, None); @@ -2205,7 +2206,7 @@ mod find_leaf_tests { let value = b"test_value".to_vec(); let wrong_value = b"wrong_value".to_vec(); - sparse.update_leaf(path.clone(), value).unwrap(); + sparse.update_leaf(path, value).unwrap(); // Check with wrong expected value let result = sparse.find_leaf(&path, Some(&wrong_value)); @@ -2244,7 +2245,7 @@ mod find_leaf_tests { fn find_leaf_exists_no_value_check() { let mut sparse = RevealedSparseTrie::::default(); let path = Nibbles::from_nibbles_unchecked([0x1, 0x2, 0x3, 0x4]); - sparse.update_leaf(path.clone(), VALUE_A()).unwrap(); + sparse.update_leaf(path, VALUE_A()).unwrap(); let result = sparse.find_leaf(&path, None); assert_matches!(result, Ok(LeafLookup::Exists)); @@ -2255,7 +2256,7 @@ mod find_leaf_tests { let mut sparse = RevealedSparseTrie::::default(); let path = Nibbles::from_nibbles_unchecked([0x1, 0x2, 0x3, 0x4]); let value = VALUE_A(); - sparse.update_leaf(path.clone(), value.clone()).unwrap(); + sparse.update_leaf(path, value.clone()).unwrap(); let result = sparse.find_leaf(&path, Some(&value)); assert_matches!(result, Ok(LeafLookup::Exists)); @@ -2350,7 +2351,7 @@ mod find_leaf_tests { Nibbles::from_nibbles_unchecked([0x1, 0x2, 0x3]), SparseNode::new_branch(TrieMask::new(0b10000)), ); // Branch at 0x123, child 4 - nodes.insert(leaf_path.clone(), SparseNode::Hash(blinded_hash)); // Blinded node at 0x1234 + nodes.insert(leaf_path, SparseNode::Hash(blinded_hash)); // Blinded node at 0x1234 let sparse = RevealedSparseTrie { provider: DefaultBlindedProvider, @@ -2385,7 +2386,7 @@ mod find_leaf_tests { let state_mask = TrieMask::new(0b100010); nodes.insert(Nibbles::default(), SparseNode::new_branch(state_mask)); - nodes.insert(path_to_blind.clone(), SparseNode::Hash(blinded_hash)); + nodes.insert(path_to_blind, SparseNode::Hash(blinded_hash)); let path_revealed = Nibbles::from_nibbles_unchecked([0x5]); let path_revealed_leaf = Nibbles::from_nibbles_unchecked([0x5, 0x6, 0x7, 0x8]); nodes.insert( @@ -2429,7 +2430,7 @@ mod find_leaf_tests { // 1. Construct the RLP representation of the children for the root branch let rlp_node_child1 = RlpNode::word_rlp(&blinded_hash); // Blinded node - let leaf_node_child5 = LeafNode::new(revealed_leaf_suffix.clone(), revealed_value.clone()); + let leaf_node_child5 = LeafNode::new(revealed_leaf_suffix, revealed_value.clone()); let leaf_node_child5_rlp_buf = alloy_rlp::encode(&leaf_node_child5); let hash_of_child5 = keccak256(&leaf_node_child5_rlp_buf); let rlp_node_child5 = RlpNode::word_rlp(&hash_of_child5); @@ -2455,11 +2456,7 @@ mod find_leaf_tests { // 4. Explicitly reveal the leaf node for child 5 sparse - .reveal_node( - revealed_leaf_prefix.clone(), - TrieNode::Leaf(leaf_node_child5), - TrieMasks::none(), - ) + .reveal_node(revealed_leaf_prefix, TrieNode::Leaf(leaf_node_child5), TrieMasks::none()) .expect("Failed to reveal leaf node"); // Assertions after we reveal child 5 @@ -2509,13 +2506,16 @@ mod tests { fn pad_nibbles_left(nibbles: Nibbles) -> Nibbles { let mut base = Nibbles::from_nibbles_unchecked(vec![0; B256::len_bytes() * 2 - nibbles.len()]); - base.extend_from_slice_unchecked(&nibbles); + base.extend(&nibbles); base } /// Pad nibbles to the length of a B256 hash with zeros on the right. fn pad_nibbles_right(mut nibbles: Nibbles) -> Nibbles { - nibbles.extend_from_slice_unchecked(&vec![0; B256::len_bytes() * 2 - nibbles.len()]); + nibbles.extend(&Nibbles::from_nibbles_unchecked(vec![ + 0; + B256::len_bytes() * 2 - nibbles.len() + ])); nibbles } @@ -2575,14 +2575,14 @@ mod tests { .clone() .unwrap_or_default() .iter() - .map(|(path, node)| (path.clone(), node.hash_mask)) + .map(|(path, node)| (*path, node.hash_mask)) .collect(); let branch_node_tree_masks = hash_builder .updated_branch_nodes .clone() .unwrap_or_default() .iter() - .map(|(path, node)| (path.clone(), node.tree_mask)) + .map(|(path, node)| (*path, node.tree_mask)) .collect(); let mut trie_updates = TrieUpdates::default(); @@ -2656,10 +2656,10 @@ mod tests { let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) = run_hash_builder( - [(key.clone(), value())], + [(key, value())], NoopAccountTrieCursor::default(), Default::default(), - [key.clone()], + [key], ); let mut sparse = RevealedSparseTrie::default().with_updates(true); @@ -2686,7 +2686,7 @@ mod tests { let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) = run_hash_builder( - paths.iter().cloned().zip(std::iter::repeat_with(value)), + paths.iter().copied().zip(std::iter::repeat_with(value)), NoopAccountTrieCursor::default(), Default::default(), paths.clone(), @@ -2694,7 +2694,7 @@ mod tests { let mut sparse = RevealedSparseTrie::default().with_updates(true); for path in &paths { - sparse.update_leaf(path.clone(), value_encoded()).unwrap(); + sparse.update_leaf(*path, value_encoded()).unwrap(); } let sparse_root = sparse.root(); let sparse_updates = sparse.take_updates(); @@ -2716,7 +2716,7 @@ mod tests { let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) = run_hash_builder( - paths.iter().cloned().zip(std::iter::repeat_with(value)), + paths.iter().copied().zip(std::iter::repeat_with(value)), NoopAccountTrieCursor::default(), Default::default(), paths.clone(), @@ -2724,7 +2724,7 @@ mod tests { let mut sparse = RevealedSparseTrie::default().with_updates(true); for path in &paths { - sparse.update_leaf(path.clone(), value_encoded()).unwrap(); + sparse.update_leaf(*path, value_encoded()).unwrap(); } let sparse_root = sparse.root(); let sparse_updates = sparse.take_updates(); @@ -2754,7 +2754,7 @@ mod tests { let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) = run_hash_builder( - paths.iter().sorted_unstable().cloned().zip(std::iter::repeat_with(value)), + paths.iter().sorted_unstable().copied().zip(std::iter::repeat_with(value)), NoopAccountTrieCursor::default(), Default::default(), paths.clone(), @@ -2762,7 +2762,7 @@ mod tests { let mut sparse = RevealedSparseTrie::default().with_updates(true); for path in &paths { - sparse.update_leaf(path.clone(), value_encoded()).unwrap(); + sparse.update_leaf(*path, value_encoded()).unwrap(); } let sparse_root = sparse.root(); let sparse_updates = sparse.take_updates(); @@ -2793,7 +2793,7 @@ mod tests { let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) = run_hash_builder( - paths.iter().cloned().zip(std::iter::repeat_with(|| old_value)), + paths.iter().copied().zip(std::iter::repeat_with(|| old_value)), NoopAccountTrieCursor::default(), Default::default(), paths.clone(), @@ -2801,7 +2801,7 @@ mod tests { let mut sparse = RevealedSparseTrie::default().with_updates(true); for path in &paths { - sparse.update_leaf(path.clone(), old_value_encoded.clone()).unwrap(); + sparse.update_leaf(*path, old_value_encoded.clone()).unwrap(); } let sparse_root = sparse.root(); let sparse_updates = sparse.updates_ref(); @@ -2812,14 +2812,14 @@ mod tests { let (hash_builder_root, hash_builder_updates, hash_builder_proof_nodes, _, _) = run_hash_builder( - paths.iter().cloned().zip(std::iter::repeat_with(|| new_value)), + paths.iter().copied().zip(std::iter::repeat_with(|| new_value)), NoopAccountTrieCursor::default(), Default::default(), paths.clone(), ); for path in &paths { - sparse.update_leaf(path.clone(), new_value_encoded.clone()).unwrap(); + sparse.update_leaf(*path, new_value_encoded.clone()).unwrap(); } let sparse_root = sparse.root(); let sparse_updates = sparse.take_updates(); @@ -3194,7 +3194,7 @@ mod tests { state.clone(), trie_cursor.account_trie_cursor().unwrap(), Default::default(), - state.keys().cloned().collect::>(), + state.keys().copied().collect::>(), ); // Write trie updates to the database @@ -3236,7 +3236,7 @@ mod tests { .iter() .map(|nibbles| B256::from_slice(&nibbles.pack())) .collect(), - state.keys().cloned().collect::>(), + state.keys().copied().collect::>(), ); // Write trie updates to the database @@ -3265,14 +3265,13 @@ mod tests { updates .into_iter() .map(|update| { - keys.extend(update.keys().cloned()); + keys.extend(update.keys().copied()); let keys_to_delete_len = update.len() / 2; let keys_to_delete = (0..keys_to_delete_len) .map(|_| { - let key = rand::seq::IteratorRandom::choose(keys.iter(), &mut rng) - .unwrap() - .clone(); + let key = + *rand::seq::IteratorRandom::choose(keys.iter(), &mut rng).unwrap(); keys.take(&key).unwrap() }) .collect(); @@ -3817,35 +3816,35 @@ mod tests { let normal_printed = format!("{sparse}"); let expected = "\ -Root -> Extension { key: Nibbles(0x05), hash: None, store_in_db_trie: None } +Root -> Extension { key: Nibbles(0x5), hash: None, store_in_db_trie: None } 5 -> Branch { state_mask: TrieMask(0000000000001101), hash: None, store_in_db_trie: None } -50 -> Extension { key: Nibbles(0x0203), hash: None, store_in_db_trie: None } +50 -> Extension { key: Nibbles(0x23), hash: None, store_in_db_trie: None } 5023 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None } 50231 -> Leaf { key: Nibbles(0x), hash: None } 50233 -> Leaf { key: Nibbles(0x), hash: None } -52013 -> Leaf { key: Nibbles(0x000103), hash: None } +52013 -> Leaf { key: Nibbles(0x013), hash: None } 53 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None } -53102 -> Leaf { key: Nibbles(0x0002), hash: None } +53102 -> Leaf { key: Nibbles(0x02), hash: None } 533 -> Branch { state_mask: TrieMask(0000000000000101), hash: None, store_in_db_trie: None } -53302 -> Leaf { key: Nibbles(0x02), hash: None } -53320 -> Leaf { key: Nibbles(0x00), hash: None } +53302 -> Leaf { key: Nibbles(0x2), hash: None } +53320 -> Leaf { key: Nibbles(0x0), hash: None } "; assert_eq!(normal_printed, expected); let alternate_printed = format!("{sparse:#}"); let expected = "\ -Root -> Extension { key: Nibbles(0x05), hash: None, store_in_db_trie: None } +Root -> Extension { key: Nibbles(0x5), hash: None, store_in_db_trie: None } 5 -> Branch { state_mask: TrieMask(0000000000001101), hash: None, store_in_db_trie: None } - 50 -> Extension { key: Nibbles(0x0203), hash: None, store_in_db_trie: None } + 50 -> Extension { key: Nibbles(0x23), hash: None, store_in_db_trie: None } 5023 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None } 50231 -> Leaf { key: Nibbles(0x), hash: None } 50233 -> Leaf { key: Nibbles(0x), hash: None } - 52013 -> Leaf { key: Nibbles(0x000103), hash: None } + 52013 -> Leaf { key: Nibbles(0x013), hash: None } 53 -> Branch { state_mask: TrieMask(0000000000001010), hash: None, store_in_db_trie: None } - 53102 -> Leaf { key: Nibbles(0x0002), hash: None } + 53102 -> Leaf { key: Nibbles(0x02), hash: None } 533 -> Branch { state_mask: TrieMask(0000000000000101), hash: None, store_in_db_trie: None } - 53302 -> Leaf { key: Nibbles(0x02), hash: None } - 53320 -> Leaf { key: Nibbles(0x00), hash: None } + 53302 -> Leaf { key: Nibbles(0x2), hash: None } + 53320 -> Leaf { key: Nibbles(0x0), hash: None } "; assert_eq!(alternate_printed, expected); diff --git a/crates/trie/trie/src/node_iter.rs b/crates/trie/trie/src/node_iter.rs index 17895d1d38e..dfb140fdf98 100644 --- a/crates/trie/trie/src/node_iter.rs +++ b/crates/trie/trie/src/node_iter.rs @@ -208,7 +208,7 @@ where #[cfg(feature = "metrics")] self.metrics.inc_branch_nodes_returned(); return Ok(Some(TrieElement::Branch(TrieBranchNode::new( - key.clone(), + *key, self.walker.hash().unwrap(), self.walker.children_are_in_trie(), )))) @@ -275,7 +275,7 @@ where // of this, we need to check that the current walker key has a prefix of the key // that we seeked to. if can_skip_node && - self.walker.key().is_some_and(|key| key.has_prefix(&seek_prefix)) && + self.walker.key().is_some_and(|key| key.starts_with(&seek_prefix)) && self.walker.children_are_in_trie() { trace!( @@ -500,7 +500,7 @@ mod tests { visited_key: Some(branch_node_0.0) }, KeyVisit { - visit_type: KeyVisitType::SeekNonExact(branch_node_2.0.clone()), + visit_type: KeyVisitType::SeekNonExact(branch_node_2.0), visited_key: Some(branch_node_2.0) }, KeyVisit { diff --git a/crates/trie/trie/src/proof/mod.rs b/crates/trie/trie/src/proof/mod.rs index 64a8f4d3b93..266aac19a39 100644 --- a/crates/trie/trie/src/proof/mod.rs +++ b/crates/trie/trie/src/proof/mod.rs @@ -167,10 +167,7 @@ where let (branch_node_hash_masks, branch_node_tree_masks) = if self.collect_branch_node_masks { let updated_branch_nodes = hash_builder.updated_branch_nodes.unwrap_or_default(); ( - updated_branch_nodes - .iter() - .map(|(path, node)| (path.clone(), node.hash_mask)) - .collect(), + updated_branch_nodes.iter().map(|(path, node)| (*path, node.hash_mask)).collect(), updated_branch_nodes .into_iter() .map(|(path, node)| (path, node.tree_mask)) @@ -308,10 +305,7 @@ where let (branch_node_hash_masks, branch_node_tree_masks) = if self.collect_branch_node_masks { let updated_branch_nodes = hash_builder.updated_branch_nodes.unwrap_or_default(); ( - updated_branch_nodes - .iter() - .map(|(path, node)| (path.clone(), node.hash_mask)) - .collect(), + updated_branch_nodes.iter().map(|(path, node)| (*path, node.hash_mask)).collect(), updated_branch_nodes .into_iter() .map(|(path, node)| (path, node.tree_mask)) diff --git a/crates/trie/trie/src/trie_cursor/in_memory.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs index 40f4447daa6..4925dc8a666 100644 --- a/crates/trie/trie/src/trie_cursor/in_memory.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -83,7 +83,7 @@ impl<'a, C: TrieCursor> InMemoryAccountTrieCursor<'a, C> { } // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(key.clone())?; + let mut db_entry = self.cursor.seek(key)?; while db_entry.as_ref().is_some_and(|entry| self.removed_nodes.contains(&entry.0)) { db_entry = self.cursor.next()?; } @@ -101,7 +101,7 @@ impl<'a, C: TrieCursor> InMemoryAccountTrieCursor<'a, C> { let in_memory = self.in_memory_cursor.first_after(&last); // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(last.clone())?; + let mut db_entry = self.cursor.seek(last)?; while db_entry .as_ref() .is_some_and(|entry| entry.0 < last || self.removed_nodes.contains(&entry.0)) @@ -120,7 +120,7 @@ impl TrieCursor for InMemoryAccountTrieCursor<'_, C> { key: Nibbles, ) -> Result, DatabaseError> { let entry = self.seek_inner(key, true)?; - self.last_key = entry.as_ref().map(|(nibbles, _)| nibbles.clone()); + self.last_key = entry.as_ref().map(|(nibbles, _)| *nibbles); Ok(entry) } @@ -129,15 +129,15 @@ impl TrieCursor for InMemoryAccountTrieCursor<'_, C> { key: Nibbles, ) -> Result, DatabaseError> { let entry = self.seek_inner(key, false)?; - self.last_key = entry.as_ref().map(|(nibbles, _)| nibbles.clone()); + self.last_key = entry.as_ref().map(|(nibbles, _)| *nibbles); Ok(entry) } fn next(&mut self) -> Result, DatabaseError> { let next = match &self.last_key { Some(last) => { - let entry = self.next_inner(last.clone())?; - self.last_key = entry.as_ref().map(|entry| entry.0.clone()); + let entry = self.next_inner(*last)?; + self.last_key = entry.as_ref().map(|entry| entry.0); entry } // no previous entry was found @@ -148,7 +148,7 @@ impl TrieCursor for InMemoryAccountTrieCursor<'_, C> { fn current(&mut self) -> Result, DatabaseError> { match &self.last_key { - Some(key) => Ok(Some(key.clone())), + Some(key) => Ok(Some(*key)), None => self.cursor.current(), } } @@ -207,7 +207,7 @@ impl InMemoryStorageTrieCursor<'_, C> { } // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(key.clone())?; + let mut db_entry = self.cursor.seek(key)?; while db_entry .as_ref() .is_some_and(|entry| self.removed_nodes.as_ref().is_some_and(|r| r.contains(&entry.0))) @@ -231,7 +231,7 @@ impl InMemoryStorageTrieCursor<'_, C> { } // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(last.clone())?; + let mut db_entry = self.cursor.seek(last)?; while db_entry.as_ref().is_some_and(|entry| { entry.0 < last || self.removed_nodes.as_ref().is_some_and(|r| r.contains(&entry.0)) }) { @@ -249,7 +249,7 @@ impl TrieCursor for InMemoryStorageTrieCursor<'_, C> { key: Nibbles, ) -> Result, DatabaseError> { let entry = self.seek_inner(key, true)?; - self.last_key = entry.as_ref().map(|(nibbles, _)| nibbles.clone()); + self.last_key = entry.as_ref().map(|(nibbles, _)| *nibbles); Ok(entry) } @@ -258,15 +258,15 @@ impl TrieCursor for InMemoryStorageTrieCursor<'_, C> { key: Nibbles, ) -> Result, DatabaseError> { let entry = self.seek_inner(key, false)?; - self.last_key = entry.as_ref().map(|(nibbles, _)| nibbles.clone()); + self.last_key = entry.as_ref().map(|(nibbles, _)| *nibbles); Ok(entry) } fn next(&mut self) -> Result, DatabaseError> { let next = match &self.last_key { Some(last) => { - let entry = self.next_inner(last.clone())?; - self.last_key = entry.as_ref().map(|entry| entry.0.clone()); + let entry = self.next_inner(*last)?; + self.last_key = entry.as_ref().map(|entry| entry.0); entry } // no previous entry was found @@ -277,7 +277,7 @@ impl TrieCursor for InMemoryStorageTrieCursor<'_, C> { fn current(&mut self) -> Result, DatabaseError> { match &self.last_key { - Some(key) => Ok(Some(key.clone())), + Some(key) => Ok(Some(*key)), None => self.cursor.current(), } } diff --git a/crates/trie/trie/src/trie_cursor/mock.rs b/crates/trie/trie/src/trie_cursor/mock.rs index 4c7d20defb0..feda1c72a85 100644 --- a/crates/trie/trie/src/trie_cursor/mock.rs +++ b/crates/trie/trie/src/trie_cursor/mock.rs @@ -107,13 +107,13 @@ impl TrieCursor for MockTrieCursor { &mut self, key: Nibbles, ) -> Result, DatabaseError> { - let entry = self.trie_nodes.get(&key).cloned().map(|value| (key.clone(), value)); + let entry = self.trie_nodes.get(&key).cloned().map(|value| (key, value)); if let Some((key, _)) = &entry { - self.current_key = Some(key.clone()); + self.current_key = Some(*key); } self.visited_keys.lock().push(KeyVisit { visit_type: KeyVisitType::SeekExact(key), - visited_key: entry.as_ref().map(|(k, _)| k.clone()), + visited_key: entry.as_ref().map(|(k, _)| *k), }); Ok(entry) } @@ -124,14 +124,13 @@ impl TrieCursor for MockTrieCursor { key: Nibbles, ) -> Result, DatabaseError> { // Find the first key that is greater than or equal to the given key. - let entry = - self.trie_nodes.iter().find_map(|(k, v)| (k >= &key).then(|| (k.clone(), v.clone()))); + let entry = self.trie_nodes.iter().find_map(|(k, v)| (k >= &key).then(|| (*k, v.clone()))); if let Some((key, _)) = &entry { - self.current_key = Some(key.clone()); + self.current_key = Some(*key); } self.visited_keys.lock().push(KeyVisit { visit_type: KeyVisitType::SeekNonExact(key), - visited_key: entry.as_ref().map(|(k, _)| k.clone()), + visited_key: entry.as_ref().map(|(k, _)| *k), }); Ok(entry) } @@ -144,19 +143,19 @@ impl TrieCursor for MockTrieCursor { iter.find(|(k, _)| self.current_key.as_ref().is_none_or(|current| k.starts_with(current))) .expect("current key should exist in trie nodes"); // Get the next key-value pair. - let entry = iter.next().map(|(k, v)| (k.clone(), v.clone())); + let entry = iter.next().map(|(k, v)| (*k, v.clone())); if let Some((key, _)) = &entry { - self.current_key = Some(key.clone()); + self.current_key = Some(*key); } self.visited_keys.lock().push(KeyVisit { visit_type: KeyVisitType::Next, - visited_key: entry.as_ref().map(|(k, _)| k.clone()), + visited_key: entry.as_ref().map(|(k, _)| *k), }); Ok(entry) } #[instrument(level = "trace", skip(self), ret)] fn current(&mut self) -> Result, DatabaseError> { - Ok(self.current_key.clone()) + Ok(self.current_key) } } diff --git a/crates/trie/trie/src/trie_cursor/subnode.rs b/crates/trie/trie/src/trie_cursor/subnode.rs index 8443934ee6f..82a5d5e670a 100644 --- a/crates/trie/trie/src/trie_cursor/subnode.rs +++ b/crates/trie/trie/src/trie_cursor/subnode.rs @@ -74,7 +74,7 @@ impl CursorSubNode { node: Option, position: SubNodePosition, ) -> Self { - let mut full_key = key.clone(); + let mut full_key = key; if let Some(nibble) = position.as_child() { full_key.push(nibble); } diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index b3c30a81ef2..5bbedb23535 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -139,9 +139,7 @@ impl TrieWalker { #[instrument(level = "trace", skip(self), ret)] pub fn next_unprocessed_key(&self) -> Option<(B256, Nibbles)> { self.key() - .and_then( - |key| if self.can_skip_current_node { key.increment() } else { Some(key.clone()) }, - ) + .and_then(|key| if self.can_skip_current_node { key.increment() } else { Some(*key) }) .map(|key| { let mut packed = key.pack(); packed.resize(32, 0); @@ -249,8 +247,8 @@ impl TrieWalker { /// Retrieves the current root node from the DB, seeking either the exact node or the next one. fn node(&mut self, exact: bool) -> Result, DatabaseError> { - let key = self.key().expect("key must exist").clone(); - let entry = if exact { self.cursor.seek_exact(key)? } else { self.cursor.seek(key)? }; + let key = self.key().expect("key must exist"); + let entry = if exact { self.cursor.seek_exact(*key)? } else { self.cursor.seek(*key)? }; #[cfg(feature = "metrics")] self.metrics.inc_branch_nodes_seeked(); @@ -274,7 +272,7 @@ impl TrieWalker { // We need to sync the stack with the trie structure when consuming a new node. This is // necessary for proper traversal and accurately representing the trie in the stack. if !key.is_empty() && !self.stack.is_empty() { - self.stack[0].set_nibble(key[0]); + self.stack[0].set_nibble(key.get_unchecked(0)); } // The current tree mask might have been set incorrectly. From fd101ea955f7bf95b48ac6281f30b78dc1eb613e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 23 Jun 2025 17:14:25 +0200 Subject: [PATCH 187/274] refactor(rpc): Rename crate `reth_rpc_types_compat` => `reth_rpc_convert` (#17013) --- Cargo.lock | 56 +++++++++---------- Cargo.toml | 4 +- bin/reth/Cargo.toml | 2 +- bin/reth/src/lib.rs | 4 +- crates/engine/tree/Cargo.toml | 2 +- crates/node/core/Cargo.toml | 2 +- crates/node/core/src/lib.rs | 2 +- crates/rpc/rpc-builder/Cargo.toml | 2 +- .../Cargo.toml | 2 +- .../src/fees.rs | 0 .../src/lib.rs | 0 .../src/rpc.rs | 0 .../src/transaction.rs | 0 crates/rpc/rpc-eth-api/Cargo.toml | 4 +- crates/rpc/rpc-eth-api/src/helpers/block.rs | 2 +- crates/rpc/rpc-eth-api/src/helpers/call.rs | 2 +- .../rpc-eth-api/src/helpers/pending_block.rs | 2 +- .../rpc-eth-api/src/helpers/transaction.rs | 2 +- crates/rpc/rpc-eth-api/src/lib.rs | 2 +- crates/rpc/rpc-eth-api/src/types.rs | 4 +- crates/rpc/rpc-eth-types/Cargo.toml | 2 +- crates/rpc/rpc-eth-types/src/error/mod.rs | 2 +- crates/rpc/rpc-eth-types/src/simulate.rs | 2 +- crates/rpc/rpc-eth-types/src/transaction.rs | 2 +- crates/rpc/rpc/Cargo.toml | 2 +- crates/rpc/rpc/src/eth/helpers/block.rs | 2 +- crates/rpc/rpc/src/eth/helpers/call.rs | 2 +- .../rpc/rpc/src/eth/helpers/pending_block.rs | 2 +- crates/rpc/rpc/src/eth/helpers/types.rs | 2 +- crates/rpc/rpc/src/txpool.rs | 2 +- 30 files changed, 57 insertions(+), 57 deletions(-) rename crates/rpc/{rpc-types-compat => rpc-convert}/Cargo.toml (97%) rename crates/rpc/{rpc-types-compat => rpc-convert}/src/fees.rs (100%) rename crates/rpc/{rpc-types-compat => rpc-convert}/src/lib.rs (100%) rename crates/rpc/{rpc-types-compat => rpc-convert}/src/rpc.rs (100%) rename crates/rpc/{rpc-types-compat => rpc-convert}/src/transaction.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index dbd18eefe93..63edeb4d94a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7135,9 +7135,9 @@ dependencies = [ "reth-rpc", "reth-rpc-api", "reth-rpc-builder", + "reth-rpc-convert", "reth-rpc-eth-types", "reth-rpc-server-types", - "reth-rpc-types-compat", "reth-tasks", "reth-tokio-util", "reth-transaction-pool", @@ -7938,7 +7938,7 @@ dependencies = [ "reth-prune", "reth-prune-types", "reth-revm", - "reth-rpc-types-compat", + "reth-rpc-convert", "reth-stages", "reth-stages-api", "reth-static-file", @@ -8889,9 +8889,9 @@ dependencies = [ "reth-network-peers", "reth-primitives-traits", "reth-prune-types", + "reth-rpc-convert", "reth-rpc-eth-types", "reth-rpc-server-types", - "reth-rpc-types-compat", "reth-stages-types", "reth-storage-api", "reth-storage-errors", @@ -9787,11 +9787,11 @@ dependencies = [ "reth-provider", "reth-revm", "reth-rpc-api", + "reth-rpc-convert", "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-server-types", - "reth-rpc-types-compat", "reth-storage-api", "reth-tasks", "reth-testing-utils", @@ -9892,12 +9892,12 @@ dependencies = [ "reth-provider", "reth-rpc", "reth-rpc-api", + "reth-rpc-convert", "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-rpc-layer", "reth-rpc-server-types", - "reth-rpc-types-compat", "reth-storage-api", "reth-tasks", "reth-tracing", @@ -9912,6 +9912,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-rpc-convert" +version = "1.4.8" +dependencies = [ + "alloy-consensus", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "jsonrpsee-types", + "op-alloy-consensus", + "op-alloy-rpc-types", + "op-revm", + "reth-evm", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-storage-api", + "revm-context", + "thiserror 2.0.12", +] + [[package]] name = "reth-rpc-engine-api" version = "1.4.8" @@ -9978,9 +9999,9 @@ dependencies = [ "reth-payload-builder", "reth-primitives-traits", "reth-revm", + "reth-rpc-convert", "reth-rpc-eth-types", "reth-rpc-server-types", - "reth-rpc-types-compat", "reth-storage-api", "reth-tasks", "reth-transaction-pool", @@ -10016,8 +10037,8 @@ dependencies = [ "reth-metrics", "reth-primitives-traits", "reth-revm", + "reth-rpc-convert", "reth-rpc-server-types", - "reth-rpc-types-compat", "reth-storage-api", "reth-tasks", "reth-transaction-pool", @@ -10065,27 +10086,6 @@ dependencies = [ "strum 0.27.1", ] -[[package]] -name = "reth-rpc-types-compat" -version = "1.4.8" -dependencies = [ - "alloy-consensus", - "alloy-json-rpc", - "alloy-network", - "alloy-primitives", - "alloy-rpc-types-eth", - "jsonrpsee-types", - "op-alloy-consensus", - "op-alloy-rpc-types", - "op-revm", - "reth-evm", - "reth-optimism-primitives", - "reth-primitives-traits", - "reth-storage-api", - "revm-context", - "thiserror 2.0.12", -] - [[package]] name = "reth-stages" version = "1.4.8" diff --git a/Cargo.toml b/Cargo.toml index 181a8add32a..ab1580388c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,7 +105,7 @@ members = [ "crates/rpc/rpc-layer", "crates/rpc/rpc-server-types/", "crates/rpc/rpc-testing-util/", - "crates/rpc/rpc-types-compat/", + "crates/rpc/rpc-convert/", "crates/rpc/rpc/", "crates/stages/api/", "crates/stages/stages/", @@ -425,7 +425,7 @@ reth-rpc-eth-api = { path = "crates/rpc/rpc-eth-api" } reth-rpc-eth-types = { path = "crates/rpc/rpc-eth-types", default-features = false } reth-rpc-layer = { path = "crates/rpc/rpc-layer" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } -reth-rpc-types-compat = { path = "crates/rpc/rpc-types-compat" } +reth-rpc-convert = { path = "crates/rpc/rpc-convert" } reth-stages = { path = "crates/stages/stages" } reth-stages-api = { path = "crates/stages/api" } reth-stages-types = { path = "crates/stages/types", default-features = false } diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 4d93ca5d73c..fb940250033 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -27,7 +27,7 @@ reth-cli-util.workspace = true reth-consensus-common.workspace = true reth-rpc-builder.workspace = true reth-rpc.workspace = true -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true reth-rpc-api = { workspace = true, features = ["client"] } reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 11a50acd3a7..ae07f9f3567 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -175,9 +175,9 @@ pub mod rpc { pub use reth_rpc_server_types::result::*; } - /// Re-exported from `reth_rpc_types_compat`. + /// Re-exported from `reth_rpc_convert`. pub mod compat { - pub use reth_rpc_types_compat::*; + pub use reth_rpc_convert::*; } } diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index 8b17a4a8a75..b5515142cad 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -82,7 +82,7 @@ reth-exex-types.workspace = true reth-network-p2p = { workspace = true, features = ["test-utils"] } reth-prune-types.workspace = true reth-prune.workspace = true -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true reth-stages = { workspace = true, features = ["test-utils"] } reth-static-file.workspace = true reth-testing-utils.workspace = true diff --git a/crates/node/core/Cargo.toml b/crates/node/core/Cargo.toml index 3aff3175717..1a36c9af5ef 100644 --- a/crates/node/core/Cargo.toml +++ b/crates/node/core/Cargo.toml @@ -23,7 +23,7 @@ reth-network = { workspace = true, features = ["serde"] } reth-network-p2p.workspace = true reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true reth-transaction-pool.workspace = true reth-tracing.workspace = true reth-config = { workspace = true, features = ["serde"] } diff --git a/crates/node/core/src/lib.rs b/crates/node/core/src/lib.rs index aa4f72bd6a4..b999121c5e9 100644 --- a/crates/node/core/src/lib.rs +++ b/crates/node/core/src/lib.rs @@ -31,6 +31,6 @@ pub mod rpc { /// Re-exported from `reth_rpc::eth`. pub mod compat { - pub use reth_rpc_types_compat::*; + pub use reth_rpc_convert::*; } } diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 92ef9cfbc12..281b32ef568 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -65,7 +65,7 @@ reth-rpc-api = { workspace = true, features = ["client"] } reth-rpc-engine-api.workspace = true reth-tracing.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true reth-engine-primitives.workspace = true reth-node-ethereum.workspace = true diff --git a/crates/rpc/rpc-types-compat/Cargo.toml b/crates/rpc/rpc-convert/Cargo.toml similarity index 97% rename from crates/rpc/rpc-types-compat/Cargo.toml rename to crates/rpc/rpc-convert/Cargo.toml index f78cdbff673..0ccf2107ad2 100644 --- a/crates/rpc/rpc-types-compat/Cargo.toml +++ b/crates/rpc/rpc-convert/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "reth-rpc-types-compat" +name = "reth-rpc-convert" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/rpc/rpc-types-compat/src/fees.rs b/crates/rpc/rpc-convert/src/fees.rs similarity index 100% rename from crates/rpc/rpc-types-compat/src/fees.rs rename to crates/rpc/rpc-convert/src/fees.rs diff --git a/crates/rpc/rpc-types-compat/src/lib.rs b/crates/rpc/rpc-convert/src/lib.rs similarity index 100% rename from crates/rpc/rpc-types-compat/src/lib.rs rename to crates/rpc/rpc-convert/src/lib.rs diff --git a/crates/rpc/rpc-types-compat/src/rpc.rs b/crates/rpc/rpc-convert/src/rpc.rs similarity index 100% rename from crates/rpc/rpc-types-compat/src/rpc.rs rename to crates/rpc/rpc-convert/src/rpc.rs diff --git a/crates/rpc/rpc-types-compat/src/transaction.rs b/crates/rpc/rpc-convert/src/transaction.rs similarity index 100% rename from crates/rpc/rpc-types-compat/src/transaction.rs rename to crates/rpc/rpc-convert/src/transaction.rs diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index 25ff3b7c1a2..69dd83026d8 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -21,7 +21,7 @@ reth-errors.workspace = true reth-evm.workspace = true reth-storage-api.workspace = true reth-revm.workspace = true -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true reth-tasks = { workspace = true, features = ["rayon"] } reth-transaction-pool.workspace = true reth-chainspec.workspace = true @@ -65,5 +65,5 @@ client = ["jsonrpsee/client", "jsonrpsee/async-client"] op = [ "reth-evm/op", "reth-primitives-traits/op", - "reth-rpc-types-compat/op", + "reth-rpc-convert/op", ] diff --git a/crates/rpc/rpc-eth-api/src/helpers/block.rs b/crates/rpc/rpc-eth-api/src/helpers/block.rs index 6ab45c1a905..91a6739b8b3 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/block.rs @@ -13,7 +13,7 @@ use futures::Future; use reth_evm::ConfigureEvm; use reth_node_api::BlockBody; use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock}; -use reth_rpc_types_compat::RpcConvert; +use reth_rpc_convert::RpcConvert; use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use std::sync::Arc; diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 9ac1adee742..a5b07a42aa4 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -27,6 +27,7 @@ use reth_revm::{ db::{CacheDB, State}, DatabaseRef, }; +use reth_rpc_convert::RpcConvert; use reth_rpc_eth_types::{ cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, error::{api::FromEvmHalt, ensure_success, FromEthApiError}, @@ -34,7 +35,6 @@ use reth_rpc_eth_types::{ simulate::{self, EthSimulateError}, EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb, }; -use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockIdReader, ProviderHeader, ProviderTx}; use revm::{ context_interface::{ diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index 64d6f2b0a1a..272f3c18f1f 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -19,8 +19,8 @@ use reth_primitives_traits::{ transaction::error::InvalidTransactionError, Receipt, RecoveredBlock, SealedHeader, }; use reth_revm::{database::StateProviderDatabase, db::State}; +use reth_rpc_convert::RpcConvert; use reth_rpc_eth_types::{EthApiError, PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; -use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{ BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderFactory, diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 32af3661ff6..c7849011d25 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -19,11 +19,11 @@ use futures::{Future, StreamExt}; use reth_chain_state::CanonStateSubscriptions; use reth_node_api::BlockBody; use reth_primitives_traits::{RecoveredBlock, SignedTransaction}; +use reth_rpc_convert::transaction::RpcConvert; use reth_rpc_eth_types::{ utils::binary_search, EthApiError, EthApiError::TransactionConfirmationTimeout, SignError, TransactionSource, }; -use reth_rpc_types_compat::transaction::RpcConvert; use reth_storage_api::{ BlockNumReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ProviderTx, ReceiptProvider, TransactionsProvider, diff --git a/crates/rpc/rpc-eth-api/src/lib.rs b/crates/rpc/rpc-eth-api/src/lib.rs index e6d02a1644f..a44c7600b9d 100644 --- a/crates/rpc/rpc-eth-api/src/lib.rs +++ b/crates/rpc/rpc-eth-api/src/lib.rs @@ -27,10 +27,10 @@ pub use ext::L2EthApiExtServer; pub use filter::{EngineEthFilter, EthFilterApiServer, QueryLimits}; pub use node::{RpcNodeCore, RpcNodeCoreExt}; pub use pubsub::EthPubSubApiServer; +pub use reth_rpc_convert::*; pub use reth_rpc_eth_types::error::{ AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError, }; -pub use reth_rpc_types_compat::*; pub use types::{EthApiTypes, FullEthApiTypes, RpcBlock, RpcHeader, RpcReceipt, RpcTransaction}; #[cfg(feature = "client")] diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index c57ff608c7e..4bfae089b29 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -3,7 +3,7 @@ use crate::{AsEthApiError, FromEthApiError, RpcNodeCore}; use alloy_rpc_types_eth::Block; use reth_chain_state::CanonStateSubscriptions; -use reth_rpc_types_compat::RpcConvert; +use reth_rpc_convert::RpcConvert; use reth_storage_api::{ProviderTx, ReceiptProvider, TransactionsProvider}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use std::{ @@ -11,7 +11,7 @@ use std::{ fmt::{self}, }; -pub use reth_rpc_types_compat::{RpcTransaction, RpcTypes}; +pub use reth_rpc_convert::{RpcTransaction, RpcTypes}; /// Network specific `eth` API types. /// diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml index 5ba7e438f87..f969a9a4891 100644 --- a/crates/rpc/rpc-eth-types/Cargo.toml +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -23,7 +23,7 @@ reth-primitives-traits = { workspace = true, features = ["rpc-compat"] } reth-storage-api.workspace = true reth-revm.workspace = true reth-rpc-server-types.workspace = true -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true reth-tasks.workspace = true reth-transaction-pool.workspace = true reth-trie.workspace = true diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 933419acd57..1d936fe87a3 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -10,10 +10,10 @@ pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError}; use core::time::Duration; use reth_errors::{BlockExecutionError, RethError}; use reth_primitives_traits::transaction::{error::InvalidTransactionError, signed::RecoveryError}; +use reth_rpc_convert::{CallFeesError, EthTxEnvError, TransactionConversionError}; use reth_rpc_server_types::result::{ block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code, }; -use reth_rpc_types_compat::{CallFeesError, EthTxEnvError, TransactionConversionError}; use reth_transaction_pool::error::{ Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError, PoolError, PoolErrorKind, PoolTransactionError, diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index a02b4494c0f..dead42af004 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -22,8 +22,8 @@ use reth_evm::{ use reth_primitives_traits::{ block::BlockTx, BlockBody as _, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction, }; +use reth_rpc_convert::{RpcConvert, RpcTransaction}; use reth_rpc_server_types::result::rpc_err; -use reth_rpc_types_compat::{RpcConvert, RpcTransaction}; use reth_storage_api::noop::NoopProvider; use revm::{ context_interface::result::ExecutionResult, diff --git a/crates/rpc/rpc-eth-types/src/transaction.rs b/crates/rpc/rpc-eth-types/src/transaction.rs index 36cdfd9ffab..de3323d61e6 100644 --- a/crates/rpc/rpc-eth-types/src/transaction.rs +++ b/crates/rpc/rpc-eth-types/src/transaction.rs @@ -6,7 +6,7 @@ use alloy_primitives::B256; use alloy_rpc_types_eth::TransactionInfo; use reth_ethereum_primitives::TransactionSigned; use reth_primitives_traits::{NodePrimitives, Recovered, SignedTransaction}; -use reth_rpc_types_compat::{RpcConvert, RpcTransaction}; +use reth_rpc_convert::{RpcConvert, RpcTransaction}; /// Represents from where a transaction was fetched. #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index f8b264fb2b1..5c83527e1b5 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -29,7 +29,7 @@ reth-network-api.workspace = true reth-rpc-engine-api.workspace = true reth-revm = { workspace = true, features = ["witness"] } reth-tasks = { workspace = true, features = ["rayon"] } -reth-rpc-types-compat.workspace = true +reth-rpc-convert.workspace = true revm-inspectors.workspace = true reth-network-peers = { workspace = true, features = ["secp256k1"] } reth-evm.workspace = true diff --git a/crates/rpc/rpc/src/eth/helpers/block.rs b/crates/rpc/rpc/src/eth/helpers/block.rs index 3ce1283a3ed..724b3a5c965 100644 --- a/crates/rpc/rpc/src/eth/helpers/block.rs +++ b/crates/rpc/rpc/src/eth/helpers/block.rs @@ -5,13 +5,13 @@ use alloy_rpc_types_eth::{BlockId, TransactionReceipt}; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::ConfigureEvm; use reth_primitives_traits::{BlockBody, NodePrimitives}; +use reth_rpc_convert::RpcConvert; use reth_rpc_eth_api::{ helpers::{EthBlocks, LoadBlock, LoadPendingBlock, LoadReceipt, SpawnBlocking}, types::RpcTypes, RpcNodeCore, RpcNodeCoreExt, RpcReceipt, }; use reth_rpc_eth_types::{EthApiError, EthReceiptBuilder}; -use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockReader, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; diff --git a/crates/rpc/rpc/src/eth/helpers/call.rs b/crates/rpc/rpc/src/eth/helpers/call.rs index 5d64c6fc203..fdaab2a6d21 100644 --- a/crates/rpc/rpc/src/eth/helpers/call.rs +++ b/crates/rpc/rpc/src/eth/helpers/call.rs @@ -5,11 +5,11 @@ use alloy_evm::block::BlockExecutorFactory; use reth_errors::ProviderError; use reth_evm::{ConfigureEvm, EvmFactory, TxEnvFor}; use reth_node_api::NodePrimitives; +use reth_rpc_convert::RpcConvert; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}, FromEvmError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, }; -use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{BlockReader, ProviderHeader, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; use revm::context::TxEnv; diff --git a/crates/rpc/rpc/src/eth/helpers/pending_block.rs b/crates/rpc/rpc/src/eth/helpers/pending_block.rs index d16777e5380..dd65fd53ca9 100644 --- a/crates/rpc/rpc/src/eth/helpers/pending_block.rs +++ b/crates/rpc/rpc/src/eth/helpers/pending_block.rs @@ -6,13 +6,13 @@ use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_evm::{ConfigureEvm, NextBlockEnvAttributes}; use reth_node_api::NodePrimitives; use reth_primitives_traits::SealedHeader; +use reth_rpc_convert::RpcConvert; use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, types::RpcTypes, FromEvmError, RpcNodeCore, }; use reth_rpc_eth_types::PendingBlock; -use reth_rpc_types_compat::RpcConvert; use reth_storage_api::{ BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, StateProviderFactory, diff --git a/crates/rpc/rpc/src/eth/helpers/types.rs b/crates/rpc/rpc/src/eth/helpers/types.rs index 2e043b22a62..2425c15fc0b 100644 --- a/crates/rpc/rpc/src/eth/helpers/types.rs +++ b/crates/rpc/rpc/src/eth/helpers/types.rs @@ -2,8 +2,8 @@ use alloy_network::Ethereum; use reth_evm_ethereum::EthEvmConfig; +use reth_rpc_convert::RpcConverter; use reth_rpc_eth_types::EthApiError; -use reth_rpc_types_compat::RpcConverter; /// An [`RpcConverter`] with its generics set to Ethereum specific. pub type EthRpcConverter = RpcConverter; diff --git a/crates/rpc/rpc/src/txpool.rs b/crates/rpc/rpc/src/txpool.rs index 0e3adc5863d..d0feffb683b 100644 --- a/crates/rpc/rpc/src/txpool.rs +++ b/crates/rpc/rpc/src/txpool.rs @@ -10,8 +10,8 @@ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_primitives_traits::NodePrimitives; use reth_rpc_api::TxPoolApiServer; +use reth_rpc_convert::{RpcConvert, RpcTypes}; use reth_rpc_eth_api::RpcTransaction; -use reth_rpc_types_compat::{RpcConvert, RpcTypes}; use reth_transaction_pool::{ AllPoolTransactions, PoolConsensusTx, PoolTransaction, TransactionPool, }; From 5eed5c6d73e767b3966e705366911db56ee0048a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Mon, 23 Jun 2025 17:57:15 +0200 Subject: [PATCH 188/274] feat(rpc): Add `TransactionRequest` into `RpcTypes` (#17017) --- crates/rpc/rpc-convert/src/rpc.rs | 9 ++++++--- crates/rpc/rpc-convert/src/transaction.rs | 10 ++++++---- crates/rpc/rpc/src/txpool.rs | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/rpc/rpc-convert/src/rpc.rs b/crates/rpc/rpc-convert/src/rpc.rs index f9a80815560..0e71419ccfb 100644 --- a/crates/rpc/rpc-convert/src/rpc.rs +++ b/crates/rpc/rpc-convert/src/rpc.rs @@ -10,7 +10,9 @@ pub trait RpcTypes { /// Receipt response type. type Receipt: RpcObject + ReceiptResponse; /// Transaction response type. - type Transaction: RpcObject + TransactionResponse; + type TransactionResponse: RpcObject + TransactionResponse; + /// Transaction response type. + type TransactionRequest: RpcObject; } impl RpcTypes for T @@ -19,8 +21,9 @@ where { type Header = T::HeaderResponse; type Receipt = T::ReceiptResponse; - type Transaction = T::TransactionResponse; + type TransactionResponse = T::TransactionResponse; + type TransactionRequest = T::TransactionRequest; } /// Adapter for network specific transaction type. -pub type RpcTransaction = ::Transaction; +pub type RpcTransaction = ::TransactionResponse; diff --git a/crates/rpc/rpc-convert/src/transaction.rs b/crates/rpc/rpc-convert/src/transaction.rs index ed521fbbb34..4f9df7cd13e 100644 --- a/crates/rpc/rpc-convert/src/transaction.rs +++ b/crates/rpc/rpc-convert/src/transaction.rs @@ -377,7 +377,7 @@ where N: NodePrimitives, E: RpcTypes + Send + Sync + Unpin + Clone + Debug, Evm: ConfigureEvm, - TxTy: IntoRpcTx + Clone + Debug, + TxTy: IntoRpcTx + Clone + Debug, TransactionRequest: TryIntoSimTx> + TryIntoTxEnv>, Err: From + From<>>::Err> @@ -387,8 +387,10 @@ where + Sync + Send + Into>, - Map: for<'a> TxInfoMapper<&'a TxTy, Out = as IntoRpcTx>::TxInfo> - + Clone + Map: for<'a> TxInfoMapper< + &'a TxTy, + Out = as IntoRpcTx>::TxInfo, + > + Clone + Debug + Unpin + Send @@ -403,7 +405,7 @@ where &self, tx: Recovered>, tx_info: TransactionInfo, - ) -> Result { + ) -> Result { let (tx, signer) = tx.into_parts(); let tx_info = self.mapper.try_map(&tx, tx_info)?; diff --git a/crates/rpc/rpc/src/txpool.rs b/crates/rpc/rpc/src/txpool.rs index d0feffb683b..e910e6a101e 100644 --- a/crates/rpc/rpc/src/txpool.rs +++ b/crates/rpc/rpc/src/txpool.rs @@ -45,7 +45,7 @@ where tx: &Tx, content: &mut BTreeMap< Address, - BTreeMap::Transaction>, + BTreeMap::TransactionResponse>, >, resp_builder: &RpcTxB, ) -> Result<(), RpcTxB::Error> From 3916c8571c9d07b8bdf7f513b2dbd10233b3f2b7 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:26:06 +0100 Subject: [PATCH 189/274] revert: test: special case for nibbles implementations of `Compact` (#17006) (#17012) --- crates/cli/commands/src/test_vectors/compact.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/cli/commands/src/test_vectors/compact.rs b/crates/cli/commands/src/test_vectors/compact.rs index d7a22838c5e..abd3635e4fd 100644 --- a/crates/cli/commands/src/test_vectors/compact.rs +++ b/crates/cli/commands/src/test_vectors/compact.rs @@ -222,23 +222,6 @@ where }; let res = obj.to_compact(&mut compact_buffer); - // `Compact` for `StoredNibbles` and `StoredNibblesSubKey` is implemented as converting to - // an array of nybbles, with each byte representing one nibble. This also matches the - // internal representation of `nybbles::Nibbles`: each nibble is stored in a separate byte, - // with high nibble set to zero. - // - // Unfortunately, the `Arbitrary` implementation for `nybbles::Nibbles` doesn't generate - // valid nibbles, as it sets the high nibble to a non-zero value. - // - // This hack sets the high nibble to zero. - // - // TODO: remove this, see https://github.com/paradigmxyz/reth/pull/17006 - if type_name == "StoredNibbles" || type_name == "StoredNibblesSubKey" { - for byte in &mut compact_buffer { - *byte &= 0x0F; - } - } - if IDENTIFIER_TYPE.contains(&type_name) { compact_buffer.push(res as u8); } From 2462eb2f6a75535b29613346fbc925f74b753a1f Mon Sep 17 00:00:00 2001 From: "fuder.eth" <139509124+vtjl10@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:26:50 +0300 Subject: [PATCH 190/274] =?UTF-8?q?refactor(rpc):=20rename=20crate=20reth?= =?UTF-8?q?=5Frpc=5Ftypes=5Fcompat=20=E2=86=92=20reth=5Frpc=5Fconvert=20(#?= =?UTF-8?q?17019)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/repo/layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 525405216e1..c7102c0e5b1 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -135,7 +135,7 @@ The IPC transport lives in [`rpc/ipc`](../../crates/rpc/ipc). #### Utilities Crates -- [`rpc/rpc-types-compat`](../../crates/rpc/rpc-types-compat): This crate various helper functions to convert between reth primitive types and rpc types. +- [`rpc/rpc-convert`](../../crates/rpc/rpc-convert): This crate various helper functions to convert between reth primitive types and rpc types. - [`rpc/layer`](../../crates/rpc/rpc-layer/): Some RPC middleware layers (e.g. `AuthValidator`, `JwtAuthValidator`) - [`rpc/rpc-testing-util`](../../crates/rpc/rpc-testing-util/): Reth RPC testing helpers From dd5501336c75c140aa75a93bd5ab2ab84abaa95f Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Mon, 23 Jun 2025 18:48:39 +0200 Subject: [PATCH 191/274] perf(trie): Place the root nodes of the lower SparseSubtries in those tries (#17011) Co-authored-by: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> --- crates/trie/sparse-parallel/src/trie.rs | 134 ++++++++++++++++-------- 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index fd1b326b193..ef982af3e08 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -119,8 +119,9 @@ impl ParallelSparseTrie { match node { TrieNode::Branch(branch) => { // If a branch is at the cutoff level of the trie then it will be in the upper trie, - // but all of its children will be in a lower trie. - if path.len() == UPPER_TRIE_MAX_DEPTH { + // but all of its children will be in a lower trie. Check if a child node would be + // in the lower subtrie, and reveal accordingly. + if !SparseSubtrieType::path_len_is_upper(path.len() + 1) { let mut stack_ptr = branch.as_ref().first_child_index(); for idx in CHILD_INDEX_RANGE { if branch.state_mask.is_bit_set(idx) { @@ -137,10 +138,8 @@ impl ParallelSparseTrie { TrieNode::Extension(ext) => { let mut child_path = path; child_path.extend(&ext.key); - if child_path.len() > UPPER_TRIE_MAX_DEPTH { - self.lower_subtrie_for_path(&child_path) - .expect("child_path must have a lower subtrie") - .reveal_node_or_hash(child_path, &ext.child)?; + if let Some(subtrie) = self.lower_subtrie_for_path(&child_path) { + subtrie.reveal_node_or_hash(child_path, &ext.child)?; } } TrieNode::EmptyRoot | TrieNode::Leaf(_) => (), @@ -583,24 +582,32 @@ impl SparseSubtrie { /// Sparse Subtrie Type. /// /// Used to determine the type of subtrie a certain path belongs to: -/// - Paths in the range `0x..=0xff` belong to the upper subtrie. -/// - Paths in the range `0x000..` belong to one of the lower subtries. The index of the lower -/// subtrie is determined by the path first nibbles of the path. +/// - Paths in the range `0x..=0xf` belong to the upper subtrie. +/// - Paths in the range `0x00..` belong to one of the lower subtries. The index of the lower +/// subtrie is determined by the first [`UPPER_TRIE_MAX_DEPTH`] nibbles of the path. /// /// There can be at most [`NUM_LOWER_SUBTRIES`] lower subtries. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum SparseSubtrieType { - /// Upper subtrie with paths in the range `0x..=0xff` + /// Upper subtrie with paths in the range `0x..=0xf` Upper, - /// Lower subtrie with paths in the range `0x000..`. Includes the index of the subtrie, + /// Lower subtrie with paths in the range `0x00..`. Includes the index of the subtrie, /// according to the path prefix. Lower(usize), } impl SparseSubtrieType { + /// Returns true if a node at a path of the given length would be placed in the upper subtrie. + /// + /// Nodes with paths shorter than [`UPPER_TRIE_MAX_DEPTH`] nibbles belong to the upper subtrie, + /// while longer paths belong to the lower subtries. + pub const fn path_len_is_upper(len: usize) -> bool { + len < UPPER_TRIE_MAX_DEPTH + } + /// Returns the type of subtrie based on the given path. pub fn from_path(path: &Nibbles) -> Self { - if path.len() <= UPPER_TRIE_MAX_DEPTH { + if Self::path_len_is_upper(path.len()) { Self::Upper } else { Self::Lower(path_subtrie_index_unchecked(path)) @@ -815,49 +822,62 @@ mod tests { } #[test] - fn sparse_subtrie_type() { + fn test_sparse_subtrie_type() { + assert_eq!(SparseSubtrieType::from_path(&Nibbles::new()), SparseSubtrieType::Upper); assert_eq!( - SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 0])), + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0])), SparseSubtrieType::Upper ); assert_eq!( - SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 15])), + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15])), SparseSubtrieType::Upper ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 0])), + SparseSubtrieType::Lower(0) + ); assert_eq!( SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 0, 0])), SparseSubtrieType::Lower(0) ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 1])), + SparseSubtrieType::Lower(1) + ); assert_eq!( SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 1, 0])), SparseSubtrieType::Lower(1) ); assert_eq!( - SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 15, 0])), + SparseSubtrieType::from_path(&Nibbles::from_nibbles([0, 15])), SparseSubtrieType::Lower(15) ); assert_eq!( - SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 0, 0])), + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 0])), SparseSubtrieType::Lower(240) ); assert_eq!( - SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 1, 0])), + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 1])), SparseSubtrieType::Lower(241) ); assert_eq!( - SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 15, 0])), + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 15])), + SparseSubtrieType::Lower(255) + ); + assert_eq!( + SparseSubtrieType::from_path(&Nibbles::from_nibbles([15, 15, 15])), SparseSubtrieType::Lower(255) ); } #[test] - fn reveal_node_leaves() { + fn test_reveal_node_leaves() { let mut trie = ParallelSparseTrie::default(); // Reveal leaf in the upper trie { - let path = Nibbles::from_nibbles([0x1, 0x2]); - let node = create_leaf_node(&[0x3, 0x4], 42); + let path = Nibbles::from_nibbles([0x1]); + let node = create_leaf_node(&[0x2, 0x3], 42); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -865,17 +885,17 @@ mod tests { assert_matches!( trie.upper_subtrie.nodes.get(&path), Some(SparseNode::Leaf { key, hash: None }) - if key == &Nibbles::from_nibbles([0x3, 0x4]) + if key == &Nibbles::from_nibbles([0x2, 0x3]) ); - let full_path = Nibbles::from_nibbles([0x1, 0x2, 0x3, 0x4]); + let full_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); assert_eq!(trie.upper_subtrie.values.get(&full_path), Some(&encode_account_value(42))); } // Reveal leaf in a lower trie { - let path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); - let node = create_leaf_node(&[0x4, 0x5], 42); + let path = Nibbles::from_nibbles([0x1, 0x2]); + let node = create_leaf_node(&[0x3, 0x4], 42); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -888,17 +908,17 @@ mod tests { assert_matches!( lower_subtrie.nodes.get(&path), Some(SparseNode::Leaf { key, hash: None }) - if key == &Nibbles::from_nibbles([0x4, 0x5]) + if key == &Nibbles::from_nibbles([0x3, 0x4]) ); } } #[test] - fn reveal_node_extension_all_upper() { + fn test_reveal_node_extension_all_upper() { let mut trie = ParallelSparseTrie::default(); - let path = Nibbles::from_nibbles([0x1]); + let path = Nibbles::new(); let child_hash = B256::repeat_byte(0xab); - let node = create_extension_node(&[0x2], child_hash); + let node = create_extension_node(&[0x1], child_hash); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -906,20 +926,20 @@ mod tests { assert_matches!( trie.upper_subtrie.nodes.get(&path), Some(SparseNode::Extension { key, hash: None, .. }) - if key == &Nibbles::from_nibbles([0x2]) + if key == &Nibbles::from_nibbles([0x1]) ); // Child path should be in upper trie - let child_path = Nibbles::from_nibbles([0x1, 0x2]); + let child_path = Nibbles::from_nibbles([0x1]); assert_eq!(trie.upper_subtrie.nodes.get(&child_path), Some(&SparseNode::Hash(child_hash))); } #[test] - fn reveal_node_extension_cross_level() { + fn test_reveal_node_extension_cross_level() { let mut trie = ParallelSparseTrie::default(); - let path = Nibbles::from_nibbles([0x1, 0x2]); + let path = Nibbles::new(); let child_hash = B256::repeat_byte(0xcd); - let node = create_extension_node(&[0x3], child_hash); + let node = create_extension_node(&[0x1, 0x2, 0x3], child_hash); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -928,7 +948,7 @@ mod tests { assert_matches!( trie.upper_subtrie.nodes.get(&path), Some(SparseNode::Extension { key, hash: None, .. }) - if key == &Nibbles::from_nibbles([0x3]) + if key == &Nibbles::from_nibbles([0x1, 0x2, 0x3]) ); // Child path (0x1, 0x2, 0x3) should be in lower trie @@ -941,9 +961,35 @@ mod tests { } #[test] - fn reveal_node_branch_all_upper() { + fn test_reveal_node_extension_cross_level_boundary() { let mut trie = ParallelSparseTrie::default(); let path = Nibbles::from_nibbles([0x1]); + let child_hash = B256::repeat_byte(0xcd); + let node = create_extension_node(&[0x2], child_hash); + let masks = TrieMasks::none(); + + trie.reveal_node(path, node, masks).unwrap(); + + // Extension node should be in upper trie + assert_matches!( + trie.upper_subtrie.nodes.get(&path), + Some(SparseNode::Extension { key, hash: None, .. }) + if key == &Nibbles::from_nibbles([0x2]) + ); + + // Child path (0x1, 0x2) should be in lower trie + let child_path = Nibbles::from_nibbles([0x1, 0x2]); + let idx = path_subtrie_index_unchecked(&child_path); + assert!(trie.lower_subtries[idx].is_some()); + + let lower_subtrie = trie.lower_subtries[idx].as_ref().unwrap(); + assert_eq!(lower_subtrie.nodes.get(&child_path), Some(&SparseNode::Hash(child_hash))); + } + + #[test] + fn test_reveal_node_branch_all_upper() { + let mut trie = ParallelSparseTrie::default(); + let path = Nibbles::new(); let child_hashes = [B256::repeat_byte(0x11), B256::repeat_byte(0x22)]; let node = create_branch_node_with_children(&[0x0, 0x5], &child_hashes); let masks = TrieMasks::none(); @@ -958,8 +1004,8 @@ mod tests { ); // Children should be in upper trie (paths of length 2) - let child_path_0 = Nibbles::from_nibbles([0x1, 0x0]); - let child_path_5 = Nibbles::from_nibbles([0x1, 0x5]); + let child_path_0 = Nibbles::from_nibbles([0x0]); + let child_path_5 = Nibbles::from_nibbles([0x5]); assert_eq!( trie.upper_subtrie.nodes.get(&child_path_0), Some(&SparseNode::Hash(child_hashes[0])) @@ -971,9 +1017,9 @@ mod tests { } #[test] - fn reveal_node_branch_cross_level() { + fn test_reveal_node_branch_cross_level() { let mut trie = ParallelSparseTrie::default(); - let path = Nibbles::from_nibbles([0x1, 0x2]); // Exactly 2 nibbles - boundary case + let path = Nibbles::from_nibbles([0x1]); // Exactly 1 nibbles - boundary case let child_hashes = [B256::repeat_byte(0x33), B256::repeat_byte(0x44), B256::repeat_byte(0x55)]; let node = create_branch_node_with_children(&[0x0, 0x7, 0xf], &child_hashes); @@ -990,9 +1036,9 @@ mod tests { // All children should be in lower tries since they have paths of length 3 let child_paths = [ - Nibbles::from_nibbles([0x1, 0x2, 0x0]), - Nibbles::from_nibbles([0x1, 0x2, 0x7]), - Nibbles::from_nibbles([0x1, 0x2, 0xf]), + Nibbles::from_nibbles([0x1, 0x0]), + Nibbles::from_nibbles([0x1, 0x7]), + Nibbles::from_nibbles([0x1, 0xf]), ]; for (i, child_path) in child_paths.iter().enumerate() { From eefbc953a0988a2dfc46b2f31558037f935fb301 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 19:11:14 +0200 Subject: [PATCH 192/274] feat: allow access to db via NodeBuilder (#17021) --- crates/ethereum/node/tests/it/builder.rs | 7 +- crates/node/builder/src/builder/mod.rs | 84 ++++++++++++++++++++---- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/crates/ethereum/node/tests/it/builder.rs b/crates/ethereum/node/tests/it/builder.rs index e3d78182ed5..91dfd683efe 100644 --- a/crates/ethereum/node/tests/it/builder.rs +++ b/crates/ethereum/node/tests/it/builder.rs @@ -50,15 +50,20 @@ async fn test_eth_launcher() { let _builder = NodeBuilder::new(config) .with_database(db) + .with_launch_context(tasks.executor()) .with_types_and_provider::>>, >>() .with_components(EthereumNode::components()) .with_add_ons(EthereumAddOns::default()) + .apply(|builder| { + let _ = builder.db(); + builder + }) .launch_with_fn(|builder| { let launcher = EngineNodeLauncher::new( tasks.executor(), - builder.config.datadir(), + builder.config().datadir(), Default::default(), ); builder.launch_with(launcher) diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 73baae37c62..acbca2b7324 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -160,6 +160,48 @@ impl NodeBuilder<(), ChainSpec> { pub const fn new(config: NodeConfig) -> Self { Self { config, database: () } } +} + +impl NodeBuilder { + /// Returns a reference to the node builder's config. + pub const fn config(&self) -> &NodeConfig { + &self.config + } + + /// Returns a mutable reference to the node builder's config. + pub const fn config_mut(&mut self) -> &mut NodeConfig { + &mut self.config + } + + /// Returns a reference to the node's database + pub const fn db(&self) -> &DB { + &self.database + } + + /// Returns a mutable reference to the node's database + pub const fn db_mut(&mut self) -> &mut DB { + &mut self.database + } + + /// Applies a fallible function to the builder. + pub fn try_apply(self, f: F) -> Result + where + F: FnOnce(Self) -> Result, + { + f(self) + } + + /// Applies a fallible function to the builder, if the condition is `true`. + pub fn try_apply_if(self, cond: bool, f: F) -> Result + where + F: FnOnce(Self) -> Result, + { + if cond { + f(self) + } else { + Ok(self) + } + } /// Apply a function to the builder pub fn apply(self, f: F) -> Self @@ -182,18 +224,6 @@ impl NodeBuilder<(), ChainSpec> { } } -impl NodeBuilder { - /// Returns a reference to the node builder's config. - pub const fn config(&self) -> &NodeConfig { - &self.config - } - - /// Returns a mutable reference to the node builder's config. - pub const fn config_mut(&mut self) -> &mut NodeConfig { - &mut self.config - } -} - impl NodeBuilder { /// Configures the underlying database that the node will use. pub fn with_database(self, database: D) -> NodeBuilder { @@ -413,6 +443,36 @@ where &self.builder.config } + /// Returns a reference to node's database. + pub const fn db(&self) -> &T::DB { + &self.builder.adapter.database + } + + /// Returns a mutable reference to node's database. + pub const fn db_mut(&mut self) -> &mut T::DB { + &mut self.builder.adapter.database + } + + /// Applies a fallible function to the builder. + pub fn try_apply(self, f: F) -> Result + where + F: FnOnce(Self) -> Result, + { + f(self) + } + + /// Applies a fallible function to the builder, if the condition is `true`. + pub fn try_apply_if(self, cond: bool, f: F) -> Result + where + F: FnOnce(Self) -> Result, + { + if cond { + f(self) + } else { + Ok(self) + } + } + /// Apply a function to the builder pub fn apply(self, f: F) -> Self where From 474096146ab4d434ee9ab893bdcbd7d18d5a0f9b Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Mon, 23 Jun 2025 19:38:25 +0100 Subject: [PATCH 193/274] perf(trie): `SparseSubtrie::update_hashes` (#16943) --- Cargo.lock | 1 + crates/trie/sparse-parallel/Cargo.toml | 1 + crates/trie/sparse-parallel/src/trie.rs | 627 ++++++++++++++++++++++-- crates/trie/sparse/src/trie.rs | 29 +- 4 files changed, 606 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63edeb4d94a..6d49012d09b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10573,6 +10573,7 @@ dependencies = [ "rand 0.9.1", "reth-execution-errors", "reth-primitives-traits", + "reth-trie", "reth-trie-common", "reth-trie-sparse", "smallvec", diff --git a/crates/trie/sparse-parallel/Cargo.toml b/crates/trie/sparse-parallel/Cargo.toml index 4b6571f80e5..9f944382db6 100644 --- a/crates/trie/sparse-parallel/Cargo.toml +++ b/crates/trie/sparse-parallel/Cargo.toml @@ -30,6 +30,7 @@ smallvec.workspace = true # reth reth-primitives-traits.workspace = true reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } +reth-trie.workspace = true arbitrary.workspace = true assert_matches.workspace = true diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index ef982af3e08..6dafcec3f98 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -5,14 +5,14 @@ use alloy_primitives::{ B256, }; use alloy_rlp::Decodable; -use alloy_trie::TrieMask; +use alloy_trie::{BranchNodeCompact, TrieMask, EMPTY_ROOT_HASH}; use reth_execution_errors::{SparseTrieErrorKind, SparseTrieResult}; use reth_trie_common::{ prefix_set::{PrefixSet, PrefixSetMut}, - Nibbles, RlpNode, TrieNode, CHILD_INDEX_RANGE, + BranchNodeRef, ExtensionNodeRef, LeafNodeRef, Nibbles, RlpNode, TrieNode, CHILD_INDEX_RANGE, }; use reth_trie_sparse::{ - blinded::BlindedProvider, RlpNodePathStackItem, RlpNodeStackItem, SparseNode, + blinded::BlindedProvider, RlpNodePathStackItem, RlpNodeStackItem, SparseNode, SparseNodeType, SparseTrieUpdates, TrieMasks, }; use smallvec::SmallVec; @@ -330,6 +330,8 @@ pub struct SparseSubtrie { /// This is the _full_ path to this subtrie, meaning it includes the first /// [`UPPER_TRIE_MAX_DEPTH`] nibbles that we also use for indexing subtries in the /// [`ParallelSparseTrie`]. + /// + /// There should be a node for this path in `nodes` map. path: Nibbles, /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, @@ -375,6 +377,8 @@ impl SparseSubtrie { node: &TrieNode, masks: TrieMasks, ) -> SparseTrieResult<()> { + debug_assert!(path.starts_with(&self.path)); + // If the node is already revealed and it's not a hash node, do nothing. if self.nodes.get(&path).is_some_and(|node| !node.is_hash()) { return Ok(()) @@ -564,10 +568,331 @@ impl SparseSubtrie { } /// Recalculates and updates the RLP hashes for the changed nodes in this subtrie. + /// + /// The function starts from the subtrie root, traverses down to leaves, and then calculates + /// the hashes from leaves back up to the root. It uses a stack from [`SparseSubtrieBuffers`] to + /// track the traversal and accumulate RLP encodings. + /// + /// # Parameters + /// + /// - `prefix_set`: The set of trie paths whose nodes have changed. + /// + /// # Returns + /// + /// A tuple containing the root node of the updated subtrie and an optional set of updates. + /// Updates are [`Some`] if [`Self::with_updates`] was set to `true`. + /// + /// # Panics + /// + /// If the node at the root path does not exist. #[allow(unused)] - fn update_hashes(&self, _prefix_set: &mut PrefixSet) -> RlpNode { - trace!(target: "trie::parallel_sparse", path=?self.path, "Updating subtrie hashes"); - todo!() + pub fn update_hashes(&mut self, prefix_set: &mut PrefixSet) -> RlpNode { + trace!(target: "trie::parallel_sparse", root=?self.path, "Updating subtrie hashes"); + + debug_assert!(prefix_set.iter().all(|path| path.starts_with(&self.path))); + + debug_assert!(self.buffers.path_stack.is_empty()); + self.buffers.path_stack.push(RlpNodePathStackItem { + level: 0, + path: self.path, + is_in_prefix_set: None, + }); + + 'main: while let Some(RlpNodePathStackItem { level, path, mut is_in_prefix_set }) = + self.buffers.path_stack.pop() + { + let node = self + .nodes + .get_mut(&path) + .unwrap_or_else(|| panic!("node at path {path:?} does not exist")); + trace!( + target: "trie::parallel_sparse", + root = ?self.path, + ?level, + ?path, + ?is_in_prefix_set, + ?node, + "Popped node from path stack" + ); + + // Check if the path is in the prefix set. + // First, check the cached value. If it's `None`, then check the prefix set, and update + // the cached value. + let mut prefix_set_contains = + |path: &Nibbles| *is_in_prefix_set.get_or_insert_with(|| prefix_set.contains(path)); + + let (rlp_node, node_type) = match node { + SparseNode::Empty => (RlpNode::word_rlp(&EMPTY_ROOT_HASH), SparseNodeType::Empty), + SparseNode::Hash(hash) => (RlpNode::word_rlp(hash), SparseNodeType::Hash), + SparseNode::Leaf { key, hash } => { + let mut path = path; + path.extend(key); + if let Some(hash) = hash.filter(|_| !prefix_set_contains(&path)) { + (RlpNode::word_rlp(&hash), SparseNodeType::Leaf) + } else { + let value = self.values.get(&path).unwrap(); + self.buffers.rlp_buf.clear(); + let rlp_node = LeafNodeRef { key, value }.rlp(&mut self.buffers.rlp_buf); + *hash = rlp_node.as_hash(); + (rlp_node, SparseNodeType::Leaf) + } + } + SparseNode::Extension { key, hash, store_in_db_trie } => { + let mut child_path = path; + child_path.extend(key); + if let Some((hash, store_in_db_trie)) = + hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) + { + ( + RlpNode::word_rlp(&hash), + SparseNodeType::Extension { store_in_db_trie: Some(store_in_db_trie) }, + ) + } else if self + .buffers + .rlp_node_stack + .last() + .is_some_and(|e| e.path == child_path) + { + let RlpNodeStackItem { + path: _, + rlp_node: child, + node_type: child_node_type, + } = self.buffers.rlp_node_stack.pop().unwrap(); + self.buffers.rlp_buf.clear(); + let rlp_node = + ExtensionNodeRef::new(key, &child).rlp(&mut self.buffers.rlp_buf); + *hash = rlp_node.as_hash(); + + let store_in_db_trie_value = child_node_type.store_in_db_trie(); + + trace!( + target: "trie::parallel_sparse", + ?path, + ?child_path, + ?child_node_type, + "Extension node" + ); + + *store_in_db_trie = store_in_db_trie_value; + + ( + rlp_node, + SparseNodeType::Extension { + // Inherit the `store_in_db_trie` flag from the child node, which is + // always the branch node + store_in_db_trie: store_in_db_trie_value, + }, + ) + } else { + // need to get rlp node for child first + self.buffers.path_stack.extend([ + RlpNodePathStackItem { level, path, is_in_prefix_set }, + RlpNodePathStackItem { + level: level + 1, + path: child_path, + is_in_prefix_set: None, + }, + ]); + continue + } + } + SparseNode::Branch { state_mask, hash, store_in_db_trie } => { + if let Some((hash, store_in_db_trie)) = + hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) + { + self.buffers.rlp_node_stack.push(RlpNodeStackItem { + path, + rlp_node: RlpNode::word_rlp(&hash), + node_type: SparseNodeType::Branch { + store_in_db_trie: Some(store_in_db_trie), + }, + }); + continue + } + let retain_updates = self.updates.is_some() && prefix_set_contains(&path); + + self.buffers.branch_child_buf.clear(); + // Walk children in a reverse order from `f` to `0`, so we pop the `0` first + // from the stack and keep walking in the sorted order. + for bit in CHILD_INDEX_RANGE.rev() { + if state_mask.is_bit_set(bit) { + let mut child = path; + child.push_unchecked(bit); + self.buffers.branch_child_buf.push(child); + } + } + + self.buffers + .branch_value_stack_buf + .resize(self.buffers.branch_child_buf.len(), Default::default()); + let mut added_children = false; + + let mut tree_mask = TrieMask::default(); + let mut hash_mask = TrieMask::default(); + let mut hashes = Vec::new(); + for (i, child_path) in self.buffers.branch_child_buf.iter().enumerate() { + if self.buffers.rlp_node_stack.last().is_some_and(|e| &e.path == child_path) + { + let RlpNodeStackItem { + path: _, + rlp_node: child, + node_type: child_node_type, + } = self.buffers.rlp_node_stack.pop().unwrap(); + + // Update the masks only if we need to retain trie updates + if retain_updates { + // SAFETY: it's a child, so it's never empty + let last_child_nibble = child_path.last().unwrap(); + + // Determine whether we need to set trie mask bit. + let should_set_tree_mask_bit = if let Some(store_in_db_trie) = + child_node_type.store_in_db_trie() + { + // A branch or an extension node explicitly set the + // `store_in_db_trie` flag + store_in_db_trie + } else { + // A blinded node has the tree mask bit set + child_node_type.is_hash() && + self.branch_node_tree_masks.get(&path).is_some_and( + |mask| mask.is_bit_set(last_child_nibble), + ) + }; + if should_set_tree_mask_bit { + tree_mask.set_bit(last_child_nibble); + } + + // Set the hash mask. If a child node is a revealed branch node OR + // is a blinded node that has its hash mask bit set according to the + // database, set the hash mask bit and save the hash. + let hash = child.as_hash().filter(|_| { + child_node_type.is_branch() || + (child_node_type.is_hash() && + self.branch_node_hash_masks + .get(&path) + .is_some_and(|mask| { + mask.is_bit_set(last_child_nibble) + })) + }); + if let Some(hash) = hash { + hash_mask.set_bit(last_child_nibble); + hashes.push(hash); + } + } + + // Insert children in the resulting buffer in a normal order, + // because initially we iterated in reverse. + // SAFETY: i < len and len is never 0 + let original_idx = self.buffers.branch_child_buf.len() - i - 1; + self.buffers.branch_value_stack_buf[original_idx] = child; + added_children = true; + } else { + debug_assert!(!added_children); + self.buffers.path_stack.push(RlpNodePathStackItem { + level, + path, + is_in_prefix_set, + }); + self.buffers.path_stack.extend( + self.buffers.branch_child_buf.drain(..).map(|path| { + RlpNodePathStackItem { + level: level + 1, + path, + is_in_prefix_set: None, + } + }), + ); + continue 'main + } + } + + trace!( + target: "trie::parallel_sparse", + ?path, + ?tree_mask, + ?hash_mask, + "Branch node masks" + ); + + self.buffers.rlp_buf.clear(); + let branch_node_ref = + BranchNodeRef::new(&self.buffers.branch_value_stack_buf, *state_mask); + let rlp_node = branch_node_ref.rlp(&mut self.buffers.rlp_buf); + *hash = rlp_node.as_hash(); + + // Save a branch node update only if it's not a root node, and we need to + // persist updates. + let store_in_db_trie_value = if let Some(updates) = + self.updates.as_mut().filter(|_| retain_updates && !path.is_empty()) + { + let store_in_db_trie = !tree_mask.is_empty() || !hash_mask.is_empty(); + if store_in_db_trie { + // Store in DB trie if there are either any children that are stored in + // the DB trie, or any children represent hashed values + hashes.reverse(); + let branch_node = BranchNodeCompact::new( + *state_mask, + tree_mask, + hash_mask, + hashes, + hash.filter(|_| path.is_empty()), + ); + updates.updated_nodes.insert(path, branch_node); + } else if self + .branch_node_tree_masks + .get(&path) + .is_some_and(|mask| !mask.is_empty()) || + self.branch_node_hash_masks + .get(&path) + .is_some_and(|mask| !mask.is_empty()) + { + // If new tree and hash masks are empty, but previously they weren't, we + // need to remove the node update and add the node itself to the list of + // removed nodes. + updates.updated_nodes.remove(&path); + updates.removed_nodes.insert(path); + } else if self + .branch_node_hash_masks + .get(&path) + .is_none_or(|mask| mask.is_empty()) && + self.branch_node_hash_masks + .get(&path) + .is_none_or(|mask| mask.is_empty()) + { + // If new tree and hash masks are empty, and they were previously empty + // as well, we need to remove the node update. + updates.updated_nodes.remove(&path); + } + + store_in_db_trie + } else { + false + }; + *store_in_db_trie = Some(store_in_db_trie_value); + + ( + rlp_node, + SparseNodeType::Branch { store_in_db_trie: Some(store_in_db_trie_value) }, + ) + } + }; + + trace!( + target: "trie::parallel_sparse", + root = ?self.path, + ?level, + ?path, + ?node, + ?node_type, + ?is_in_prefix_set, + "Added node to rlp node stack" + ); + + self.buffers.rlp_node_stack.push(RlpNodeStackItem { path, rlp_node, node_type }); + } + + debug_assert_eq!(self.buffers.rlp_node_stack.len(), 1); + self.buffers.rlp_node_stack.pop().unwrap().rlp_node } /// Consumes and returns the currently accumulated trie updates. @@ -669,18 +994,33 @@ mod tests { path_subtrie_index_unchecked, ParallelSparseTrie, SparseSubtrie, SparseSubtrieType, }; use crate::trie::ChangedSubtrie; - use alloy_primitives::B256; + use alloy_primitives::{ + map::{B256Set, HashMap}, + B256, + }; use alloy_rlp::Encodable; use alloy_trie::Nibbles; use assert_matches::assert_matches; use reth_primitives_traits::Account; + use reth_trie::{ + hashed_cursor::{noop::NoopHashedAccountCursor, HashedPostStateAccountCursor}, + node_iter::{TrieElement, TrieNodeIter}, + trie_cursor::{noop::NoopAccountTrieCursor, TrieCursor}, + walker::TrieWalker, + }; use reth_trie_common::{ - prefix_set::PrefixSetMut, BranchNode, ExtensionNode, LeafNode, RlpNode, TrieMask, TrieNode, - EMPTY_ROOT_HASH, + prefix_set::PrefixSetMut, + proof::{ProofNodes, ProofRetainer}, + updates::TrieUpdates, + BranchNode, ExtensionNode, HashBuilder, HashedPostState, LeafNode, RlpNode, TrieMask, + TrieNode, EMPTY_ROOT_HASH, }; use reth_trie_sparse::{SparseNode, TrieMasks}; - // Test helpers + fn create_account(nonce: u64) -> Account { + Account { nonce, ..Default::default() } + } + fn encode_account_value(nonce: u64) -> Vec { let account = Account { nonce, ..Default::default() }; let trie_account = account.into_trie_account(EMPTY_ROOT_HASH); @@ -689,33 +1029,103 @@ mod tests { buf } - fn create_leaf_node(key: &[u8], value_nonce: u64) -> TrieNode { - TrieNode::Leaf(LeafNode::new( - Nibbles::from_nibbles_unchecked(key), - encode_account_value(value_nonce), - )) + fn create_leaf_node(key: impl AsRef<[u8]>, value_nonce: u64) -> TrieNode { + TrieNode::Leaf(LeafNode::new(Nibbles::from_nibbles(key), encode_account_value(value_nonce))) } - fn create_extension_node(key: &[u8], child_hash: B256) -> TrieNode { + fn create_extension_node(key: impl AsRef<[u8]>, child_hash: B256) -> TrieNode { TrieNode::Extension(ExtensionNode::new( - Nibbles::from_nibbles_unchecked(key), + Nibbles::from_nibbles(key), RlpNode::word_rlp(&child_hash), )) } fn create_branch_node_with_children( children_indices: &[u8], - child_hashes: &[B256], + child_hashes: impl IntoIterator, ) -> TrieNode { let mut stack = Vec::new(); - let mut state_mask = 0u16; + let mut state_mask = TrieMask::default(); - for (&idx, &hash) in children_indices.iter().zip(child_hashes.iter()) { - state_mask |= 1 << idx; - stack.push(RlpNode::word_rlp(&hash)); + for (&idx, hash) in children_indices.iter().zip(child_hashes.into_iter()) { + state_mask.set_bit(idx); + stack.push(hash); } - TrieNode::Branch(BranchNode::new(stack, TrieMask::new(state_mask))) + TrieNode::Branch(BranchNode::new(stack, state_mask)) + } + + /// Calculate the state root by feeding the provided state to the hash builder and retaining the + /// proofs for the provided targets. + /// + /// Returns the state root and the retained proof nodes. + fn run_hash_builder( + state: impl IntoIterator + Clone, + trie_cursor: impl TrieCursor, + destroyed_accounts: B256Set, + proof_targets: impl IntoIterator, + ) -> (B256, TrieUpdates, ProofNodes, HashMap, HashMap) + { + let mut account_rlp = Vec::new(); + + let mut hash_builder = HashBuilder::default() + .with_updates(true) + .with_proof_retainer(ProofRetainer::from_iter(proof_targets)); + + let mut prefix_set = PrefixSetMut::default(); + prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles)); + prefix_set.extend_keys(destroyed_accounts.iter().map(Nibbles::unpack)); + let walker = + TrieWalker::state_trie(trie_cursor, prefix_set.freeze()).with_deletions_retained(true); + let hashed_post_state = HashedPostState::default() + .with_accounts(state.into_iter().map(|(nibbles, account)| { + (nibbles.pack().into_inner().unwrap().into(), Some(account)) + })) + .into_sorted(); + let mut node_iter = TrieNodeIter::state_trie( + walker, + HashedPostStateAccountCursor::new( + NoopHashedAccountCursor::default(), + hashed_post_state.accounts(), + ), + ); + + while let Some(node) = node_iter.try_next().unwrap() { + match node { + TrieElement::Branch(branch) => { + hash_builder.add_branch(branch.key, branch.value, branch.children_are_in_trie); + } + TrieElement::Leaf(key, account) => { + let account = account.into_trie_account(EMPTY_ROOT_HASH); + account.encode(&mut account_rlp); + + hash_builder.add_leaf(Nibbles::unpack(key), &account_rlp); + account_rlp.clear(); + } + } + } + let root = hash_builder.root(); + let proof_nodes = hash_builder.take_proof_nodes(); + let branch_node_hash_masks = hash_builder + .updated_branch_nodes + .clone() + .unwrap_or_default() + .iter() + .map(|(path, node)| (*path, node.hash_mask)) + .collect(); + let branch_node_tree_masks = hash_builder + .updated_branch_nodes + .clone() + .unwrap_or_default() + .iter() + .map(|(path, node)| (*path, node.tree_mask)) + .collect(); + + let mut trie_updates = TrieUpdates::default(); + let removed_keys = node_iter.walker.take_removed_keys(); + trie_updates.finalize(hash_builder, removed_keys, destroyed_accounts); + + (root, trie_updates, proof_nodes, branch_node_hash_masks, branch_node_tree_masks) } #[test] @@ -745,14 +1155,14 @@ mod tests { trie.lower_subtries[subtrie_3_index] = Some(subtrie_3); let unchanged_prefix_set = PrefixSetMut::from([ - Nibbles::from_nibbles_unchecked([0x0]), - Nibbles::from_nibbles_unchecked([0x2, 0x0, 0x0]), + Nibbles::from_nibbles([0x0]), + Nibbles::from_nibbles([0x2, 0x0, 0x0]), ]); // Create a prefix set with the keys that match only the second subtrie let mut prefix_set = PrefixSetMut::from([ // Match second subtrie - Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), - Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]), + Nibbles::from_nibbles([0x1, 0x0, 0x0]), + Nibbles::from_nibbles([0x1, 0x0, 0x1, 0x0]), ]); prefix_set.extend(unchanged_prefix_set); let mut prefix_set = prefix_set.freeze(); @@ -770,8 +1180,8 @@ mod tests { subtrie_2_index, subtrie_2, vec![ - Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), - Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]) + Nibbles::from_nibbles([0x1, 0x0, 0x0]), + Nibbles::from_nibbles([0x1, 0x0, 0x1, 0x0]) ] )] ); @@ -877,7 +1287,7 @@ mod tests { // Reveal leaf in the upper trie { let path = Nibbles::from_nibbles([0x1]); - let node = create_leaf_node(&[0x2, 0x3], 42); + let node = create_leaf_node([0x2, 0x3], 42); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -895,7 +1305,7 @@ mod tests { // Reveal leaf in a lower trie { let path = Nibbles::from_nibbles([0x1, 0x2]); - let node = create_leaf_node(&[0x3, 0x4], 42); + let node = create_leaf_node([0x3, 0x4], 42); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -918,7 +1328,7 @@ mod tests { let mut trie = ParallelSparseTrie::default(); let path = Nibbles::new(); let child_hash = B256::repeat_byte(0xab); - let node = create_extension_node(&[0x1], child_hash); + let node = create_extension_node([0x1], child_hash); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -939,7 +1349,7 @@ mod tests { let mut trie = ParallelSparseTrie::default(); let path = Nibbles::new(); let child_hash = B256::repeat_byte(0xcd); - let node = create_extension_node(&[0x1, 0x2, 0x3], child_hash); + let node = create_extension_node([0x1, 0x2, 0x3], child_hash); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -965,7 +1375,7 @@ mod tests { let mut trie = ParallelSparseTrie::default(); let path = Nibbles::from_nibbles([0x1]); let child_hash = B256::repeat_byte(0xcd); - let node = create_extension_node(&[0x2], child_hash); + let node = create_extension_node([0x2], child_hash); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -990,8 +1400,11 @@ mod tests { fn test_reveal_node_branch_all_upper() { let mut trie = ParallelSparseTrie::default(); let path = Nibbles::new(); - let child_hashes = [B256::repeat_byte(0x11), B256::repeat_byte(0x22)]; - let node = create_branch_node_with_children(&[0x0, 0x5], &child_hashes); + let child_hashes = [ + RlpNode::word_rlp(&B256::repeat_byte(0x11)), + RlpNode::word_rlp(&B256::repeat_byte(0x22)), + ]; + let node = create_branch_node_with_children(&[0x0, 0x5], child_hashes.clone()); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -1008,11 +1421,11 @@ mod tests { let child_path_5 = Nibbles::from_nibbles([0x5]); assert_eq!( trie.upper_subtrie.nodes.get(&child_path_0), - Some(&SparseNode::Hash(child_hashes[0])) + Some(&SparseNode::Hash(child_hashes[0].as_hash().unwrap())) ); assert_eq!( trie.upper_subtrie.nodes.get(&child_path_5), - Some(&SparseNode::Hash(child_hashes[1])) + Some(&SparseNode::Hash(child_hashes[1].as_hash().unwrap())) ); } @@ -1020,9 +1433,12 @@ mod tests { fn test_reveal_node_branch_cross_level() { let mut trie = ParallelSparseTrie::default(); let path = Nibbles::from_nibbles([0x1]); // Exactly 1 nibbles - boundary case - let child_hashes = - [B256::repeat_byte(0x33), B256::repeat_byte(0x44), B256::repeat_byte(0x55)]; - let node = create_branch_node_with_children(&[0x0, 0x7, 0xf], &child_hashes); + let child_hashes = [ + RlpNode::word_rlp(&B256::repeat_byte(0x33)), + RlpNode::word_rlp(&B256::repeat_byte(0x44)), + RlpNode::word_rlp(&B256::repeat_byte(0x55)), + ]; + let node = create_branch_node_with_children(&[0x0, 0x7, 0xf], child_hashes.clone()); let masks = TrieMasks::none(); trie.reveal_node(path, node, masks).unwrap(); @@ -1046,7 +1462,7 @@ mod tests { let lower_subtrie = trie.lower_subtries[idx].as_ref().unwrap(); assert_eq!( lower_subtrie.nodes.get(child_path), - Some(&SparseNode::Hash(child_hashes[i])), + Some(&SparseNode::Hash(child_hashes[i].as_hash().unwrap())), ); } } @@ -1068,14 +1484,14 @@ mod tests { trie.lower_subtries[subtrie_3_index] = Some(subtrie_3); let unchanged_prefix_set = PrefixSetMut::from([ - Nibbles::from_nibbles_unchecked([0x0]), - Nibbles::from_nibbles_unchecked([0x2, 0x0, 0x0]), + Nibbles::from_nibbles([0x0]), + Nibbles::from_nibbles([0x2, 0x0, 0x0]), ]); // Create a prefix set with the keys that match only the second subtrie let mut prefix_set = PrefixSetMut::from([ // Match second subtrie - Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x0]), - Nibbles::from_nibbles_unchecked([0x1, 0x0, 0x1, 0x0]), + Nibbles::from_nibbles([0x1, 0x0, 0x0]), + Nibbles::from_nibbles([0x1, 0x0, 0x1, 0x0]), ]); prefix_set.extend(unchanged_prefix_set.clone()); trie.prefix_set = prefix_set; @@ -1090,4 +1506,123 @@ mod tests { assert!(trie.lower_subtries[subtrie_2_index].is_some()); assert!(trie.lower_subtries[subtrie_3_index].is_some()); } + + #[test] + fn test_subtrie_update_hashes() { + let mut subtrie = + Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0])).with_updates(true)); + + // Create leaf nodes with paths 0x0...0, 0x00001...0, 0x0010...0 + let leaf_1_full_path = Nibbles::from_nibbles([0; 64]); + let leaf_1_path = leaf_1_full_path.slice(..5); + let leaf_1_key = leaf_1_full_path.slice(5..); + let leaf_2_full_path = Nibbles::from_nibbles([vec![0, 0, 0, 0, 1], vec![0; 59]].concat()); + let leaf_2_path = leaf_2_full_path.slice(..5); + let leaf_2_key = leaf_2_full_path.slice(5..); + let leaf_3_full_path = Nibbles::from_nibbles([vec![0, 0, 1], vec![0; 61]].concat()); + let leaf_3_path = leaf_3_full_path.slice(..3); + let leaf_3_key = leaf_3_full_path.slice(3..); + + let account_1 = create_account(1); + let account_2 = create_account(2); + let account_3 = create_account(3); + let leaf_1 = create_leaf_node(leaf_1_key.to_vec(), account_1.nonce); + let leaf_2 = create_leaf_node(leaf_2_key.to_vec(), account_2.nonce); + let leaf_3 = create_leaf_node(leaf_3_key.to_vec(), account_3.nonce); + + // Create bottom branch node + let branch_1_path = Nibbles::from_nibbles([0, 0, 0, 0]); + let branch_1 = create_branch_node_with_children( + &[0, 1], + vec![ + RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1)), + RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2)), + ], + ); + + // Create an extension node + let extension_path = Nibbles::from_nibbles([0, 0, 0]); + let extension_key = Nibbles::from_nibbles([0]); + let extension = create_extension_node( + extension_key.to_vec(), + RlpNode::from_rlp(&alloy_rlp::encode(&branch_1)).as_hash().unwrap(), + ); + + // Create top branch node + let branch_2_path = Nibbles::from_nibbles([0, 0]); + let branch_2 = create_branch_node_with_children( + &[0, 1], + vec![ + RlpNode::from_rlp(&alloy_rlp::encode(&extension)), + RlpNode::from_rlp(&alloy_rlp::encode(&leaf_3)), + ], + ); + + // Reveal nodes + subtrie.reveal_node(branch_2_path, &branch_2, TrieMasks::none()).unwrap(); + subtrie.reveal_node(leaf_1_path, &leaf_1, TrieMasks::none()).unwrap(); + subtrie.reveal_node(extension_path, &extension, TrieMasks::none()).unwrap(); + subtrie.reveal_node(branch_1_path, &branch_1, TrieMasks::none()).unwrap(); + subtrie.reveal_node(leaf_2_path, &leaf_2, TrieMasks::none()).unwrap(); + subtrie.reveal_node(leaf_3_path, &leaf_3, TrieMasks::none()).unwrap(); + + // Run hash builder for two leaf nodes + let (_, _, proof_nodes, _, _) = run_hash_builder( + [ + (leaf_1_full_path, account_1), + (leaf_2_full_path, account_2), + (leaf_3_full_path, account_3), + ], + NoopAccountTrieCursor::default(), + Default::default(), + [ + branch_1_path, + extension_path, + branch_2_path, + leaf_1_full_path, + leaf_2_full_path, + leaf_3_full_path, + ], + ); + + // Update hashes for the subtrie + subtrie.update_hashes( + &mut PrefixSetMut::from([leaf_1_full_path, leaf_2_full_path, leaf_3_full_path]) + .freeze(), + ); + + // Compare hashes between hash builder and subtrie + + let hash_builder_branch_1_hash = + RlpNode::from_rlp(proof_nodes.get(&branch_1_path).unwrap().as_ref()).as_hash().unwrap(); + let subtrie_branch_1_hash = subtrie.nodes.get(&branch_1_path).unwrap().hash().unwrap(); + assert_eq!(hash_builder_branch_1_hash, subtrie_branch_1_hash); + + let hash_builder_extension_hash = + RlpNode::from_rlp(proof_nodes.get(&extension_path).unwrap().as_ref()) + .as_hash() + .unwrap(); + let subtrie_extension_hash = subtrie.nodes.get(&extension_path).unwrap().hash().unwrap(); + assert_eq!(hash_builder_extension_hash, subtrie_extension_hash); + + let hash_builder_branch_2_hash = + RlpNode::from_rlp(proof_nodes.get(&branch_2_path).unwrap().as_ref()).as_hash().unwrap(); + let subtrie_branch_2_hash = subtrie.nodes.get(&branch_2_path).unwrap().hash().unwrap(); + assert_eq!(hash_builder_branch_2_hash, subtrie_branch_2_hash); + + let subtrie_leaf_1_hash = subtrie.nodes.get(&leaf_1_path).unwrap().hash().unwrap(); + let hash_builder_leaf_1_hash = + RlpNode::from_rlp(proof_nodes.get(&leaf_1_path).unwrap().as_ref()).as_hash().unwrap(); + assert_eq!(hash_builder_leaf_1_hash, subtrie_leaf_1_hash); + + let hash_builder_leaf_2_hash = + RlpNode::from_rlp(proof_nodes.get(&leaf_2_path).unwrap().as_ref()).as_hash().unwrap(); + let subtrie_leaf_2_hash = subtrie.nodes.get(&leaf_2_path).unwrap().hash().unwrap(); + assert_eq!(hash_builder_leaf_2_hash, subtrie_leaf_2_hash); + + let hash_builder_leaf_3_hash = + RlpNode::from_rlp(proof_nodes.get(&leaf_3_path).unwrap().as_ref()).as_hash().unwrap(); + let subtrie_leaf_3_hash = subtrie.nodes.get(&leaf_3_path).unwrap().hash().unwrap(); + assert_eq!(hash_builder_leaf_3_hash, subtrie_leaf_3_hash); + } } diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index ed84592e4b7..619cadac8f5 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -1945,15 +1945,18 @@ pub enum SparseNodeType { } impl SparseNodeType { - const fn is_hash(&self) -> bool { + /// Returns true if the node is a hash node. + pub const fn is_hash(&self) -> bool { matches!(self, Self::Hash) } - const fn is_branch(&self) -> bool { + /// Returns true if the node is a branch node. + pub const fn is_branch(&self) -> bool { matches!(self, Self::Branch { .. }) } - const fn store_in_db_trie(&self) -> Option { + /// Returns true if the node should be stored in the database. + pub const fn store_in_db_trie(&self) -> Option { match *self { Self::Extension { store_in_db_trie } | Self::Branch { store_in_db_trie } => { store_in_db_trie @@ -2049,6 +2052,17 @@ impl SparseNode { pub const fn is_hash(&self) -> bool { matches!(self, Self::Hash(_)) } + + /// Returns the hash of the node if it exists. + pub const fn hash(&self) -> Option { + match self { + Self::Empty => None, + Self::Hash(hash) => Some(*hash), + Self::Leaf { hash, .. } | Self::Extension { hash, .. } | Self::Branch { hash, .. } => { + *hash + } + } + } } /// A helper struct used to store information about a node that has been removed @@ -2129,9 +2143,12 @@ pub struct RlpNodeStackItem { /// one to make batch updates to a persistent database. #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct SparseTrieUpdates { - pub(crate) updated_nodes: HashMap, - pub(crate) removed_nodes: HashSet, - pub(crate) wiped: bool, + /// Collection of updated intermediate nodes indexed by full path. + pub updated_nodes: HashMap, + /// Collection of removed intermediate nodes indexed by full path. + pub removed_nodes: HashSet, + /// Flag indicating whether the trie was wiped. + pub wiped: bool, } impl SparseTrieUpdates { From 2563a168eed9e6e311bc9d10fec0127514e9db2c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 22:29:04 +0200 Subject: [PATCH 194/274] chore: re-export op hardforks from op chainspec (#17018) --- crates/optimism/chainspec/src/lib.rs | 8 +++++--- crates/optimism/reth/src/lib.rs | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index e0d94bcd367..a53a3b4c8d3 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -45,10 +45,15 @@ mod superchain; #[cfg(feature = "superchain-configs")] pub use superchain::*; +pub use base::BASE_MAINNET; +pub use base_sepolia::BASE_SEPOLIA; pub use dev::OP_DEV; pub use op::OP_MAINNET; pub use op_sepolia::OP_SEPOLIA; +/// Re-export for convenience +pub use reth_optimism_forks::*; + use alloc::{boxed::Box, vec, vec::Vec}; use alloy_chains::Chain; use alloy_consensus::{proofs::storage_root_unhashed, Header}; @@ -56,8 +61,6 @@ use alloy_eips::eip7840::BlobParams; use alloy_genesis::Genesis; use alloy_hardforks::Hardfork; use alloy_primitives::{B256, U256}; -pub use base::BASE_MAINNET; -pub use base_sepolia::BASE_SEPOLIA; use derive_more::{Constructor, Deref, From, Into}; use reth_chainspec::{ BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, @@ -65,7 +68,6 @@ use reth_chainspec::{ }; use reth_ethereum_forks::{ChainHardforks, EthereumHardfork, ForkCondition}; use reth_network_peers::NodeRecord; -use reth_optimism_forks::{OpHardfork, OpHardforks, OP_MAINNET_HARDFORKS}; use reth_optimism_primitives::ADDRESS_L2_TO_L1_MESSAGE_PASSER; use reth_primitives_traits::{sync::LazyLock, SealedHeader}; diff --git a/crates/optimism/reth/src/lib.rs b/crates/optimism/reth/src/lib.rs index 5f029ce67da..f87d90829cf 100644 --- a/crates/optimism/reth/src/lib.rs +++ b/crates/optimism/reth/src/lib.rs @@ -48,6 +48,7 @@ pub mod consensus { } /// Re-exported from `reth_chainspec` +#[allow(ambiguous_glob_reexports)] pub mod chainspec { #[doc(inline)] pub use reth_chainspec::*; From cf8ff9829cca8a16bbed99a2de9e3d85b8dfe92b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 22:29:25 +0200 Subject: [PATCH 195/274] feat: add codec re-exports to reth-op and reth-ethereum (#17020) --- Cargo.lock | 2 ++ crates/ethereum/reth/Cargo.toml | 5 ++++- crates/ethereum/reth/src/lib.rs | 4 ++++ crates/optimism/reth/Cargo.toml | 5 ++++- crates/optimism/reth/src/lib.rs | 4 ++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d49012d09b..78c1d70476f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8131,6 +8131,7 @@ dependencies = [ "alloy-rpc-types-eth", "reth-chainspec", "reth-cli-util", + "reth-codecs", "reth-consensus", "reth-consensus-common", "reth-db", @@ -9026,6 +9027,7 @@ version = "1.4.8" dependencies = [ "reth-chainspec", "reth-cli-util", + "reth-codecs", "reth-consensus", "reth-consensus-common", "reth-db", diff --git a/crates/ethereum/reth/Cargo.toml b/crates/ethereum/reth/Cargo.toml index 45e046b89ef..0522e6f84dc 100644 --- a/crates/ethereum/reth/Cargo.toml +++ b/crates/ethereum/reth/Cargo.toml @@ -19,6 +19,7 @@ reth-network-api = { workspace = true, optional = true } reth-eth-wire = { workspace = true, optional = true } reth-provider = { workspace = true, optional = true } reth-db = { workspace = true, optional = true, features = ["mdbx"] } +reth-codecs = { workspace = true, optional = true } reth-storage-api = { workspace = true, optional = true } reth-node-api = { workspace = true, optional = true } reth-node-core = { workspace = true, optional = true } @@ -75,6 +76,7 @@ arbitrary = [ "reth-transaction-pool?/arbitrary", "reth-eth-wire?/arbitrary", "alloy-rpc-types-engine?/arbitrary", + "reth-codecs?/arbitrary", ] test-utils = [ @@ -93,6 +95,7 @@ test-utils = [ "reth-evm-ethereum?/test-utils", "reth-node-builder?/test-utils", "reth-trie-db?/test-utils", + "reth-codecs?/test-utils", ] full = [ @@ -139,7 +142,7 @@ rpc = [ tasks = ["dep:reth-tasks"] js-tracer = ["rpc", "reth-rpc/js-tracer"] network = ["dep:reth-network", "tasks", "dep:reth-network-api", "dep:reth-eth-wire"] -provider = ["storage-api", "tasks", "dep:reth-provider", "dep:reth-db"] +provider = ["storage-api", "tasks", "dep:reth-provider", "dep:reth-db", "dep:reth-codecs"] storage-api = ["dep:reth-storage-api"] trie = ["dep:reth-trie"] trie-db = ["trie", "dep:reth-trie-db"] diff --git a/crates/ethereum/reth/src/lib.rs b/crates/ethereum/reth/src/lib.rs index b72e504e217..2a3a6135495 100644 --- a/crates/ethereum/reth/src/lib.rs +++ b/crates/ethereum/reth/src/lib.rs @@ -91,6 +91,10 @@ pub mod provider { pub use reth_db as db; } +/// Re-exported codec crate +#[cfg(feature = "provider")] +pub use reth_codecs as codec; + /// Re-exported reth storage api types #[cfg(feature = "storage-api")] pub mod storage { diff --git a/crates/optimism/reth/Cargo.toml b/crates/optimism/reth/Cargo.toml index f199aed7e78..150a50fc84d 100644 --- a/crates/optimism/reth/Cargo.toml +++ b/crates/optimism/reth/Cargo.toml @@ -19,6 +19,7 @@ reth-network-api = { workspace = true, optional = true } reth-eth-wire = { workspace = true, optional = true } reth-provider = { workspace = true, optional = true } reth-db = { workspace = true, optional = true, features = ["mdbx", "op"] } +reth-codecs = { workspace = true, optional = true } reth-storage-api = { workspace = true, optional = true } reth-node-api = { workspace = true, optional = true } reth-node-core = { workspace = true, optional = true } @@ -70,6 +71,7 @@ arbitrary = [ "reth-db?/arbitrary", "reth-transaction-pool?/arbitrary", "reth-eth-wire?/arbitrary", + "reth-codecs?/arbitrary", ] test-utils = [ @@ -86,6 +88,7 @@ test-utils = [ "reth-transaction-pool?/test-utils", "reth-node-builder?/test-utils", "reth-trie-db?/test-utils", + "reth-codecs?/test-utils", ] full = ["consensus", "evm", "node", "provider", "rpc", "trie", "pool", "network"] @@ -121,7 +124,7 @@ rpc = [ tasks = ["dep:reth-tasks"] js-tracer = ["rpc", "reth-rpc/js-tracer"] network = ["dep:reth-network", "tasks", "dep:reth-network-api", "dep:reth-eth-wire"] -provider = ["storage-api", "tasks", "dep:reth-provider", "dep:reth-db"] +provider = ["storage-api", "tasks", "dep:reth-provider", "dep:reth-db", "dep:reth-codecs"] pool = ["dep:reth-transaction-pool"] storage-api = ["dep:reth-storage-api"] trie = ["dep:reth-trie"] diff --git a/crates/optimism/reth/src/lib.rs b/crates/optimism/reth/src/lib.rs index f87d90829cf..3028b07b237 100644 --- a/crates/optimism/reth/src/lib.rs +++ b/crates/optimism/reth/src/lib.rs @@ -100,6 +100,10 @@ pub mod provider { pub use reth_db as db; } +/// Re-exported codec crate +#[cfg(feature = "provider")] +pub use reth_codecs as codec; + /// Re-exported reth storage api types #[cfg(feature = "storage-api")] pub mod storage { From faa9d3756b0dc9e7cd2ef8eaff191f9c152e161e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 23 Jun 2025 23:47:16 +0200 Subject: [PATCH 196/274] chore: remove unused for<'a> (#17026) --- crates/rpc/rpc-builder/src/lib.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index a71e0d76216..ff952016f49 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1255,15 +1255,14 @@ impl RpcServerConfig { pub async fn start(self, modules: &TransportRpcModules) -> Result where RpcMiddleware: Layer> + Clone + Send + 'static, - for<'a> >>::Service: - Send - + Sync - + 'static - + RpcServiceT< - MethodResponse = MethodResponse, - BatchResponse = MethodResponse, - NotificationResponse = MethodResponse, - >, + >>::Service: Send + + Sync + + 'static + + RpcServiceT< + MethodResponse = MethodResponse, + BatchResponse = MethodResponse, + NotificationResponse = MethodResponse, + >, { let mut http_handle = None; let mut ws_handle = None; From b719bb7d56286d3996b0dbcc3802dbcfc9bddc33 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 24 Jun 2025 10:16:38 +0200 Subject: [PATCH 197/274] docs: update outdated validtor docs (#17027) --- crates/transaction-pool/src/validate/eth.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 5044e2490cc..93c6474397a 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -720,12 +720,13 @@ pub struct EthTransactionValidatorBuilder { impl EthTransactionValidatorBuilder { /// Creates a new builder for the given client /// - /// By default this assumes the network is on the `Cancun` hardfork and the following + /// By default this assumes the network is on the `Prague` hardfork and the following /// transactions are allowed: /// - Legacy /// - EIP-2718 /// - EIP-1559 /// - EIP-4844 + /// - EIP-7702 pub fn new(client: Client) -> Self { Self { block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M.into(), From 3f5486d9c60a71f67ce3d75154673aee42af3795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Narzis?= <78718413+lean-apple@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:16:09 +0200 Subject: [PATCH 198/274] feat(tx-pool): add getter methods for `EthTransactionValidator` internal fields (#17022) Co-authored-by: Matthias Seitz --- crates/transaction-pool/src/validate/eth.rs | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 93c6474397a..2c5803a735a 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -61,6 +61,57 @@ impl EthTransactionValidator { pub fn client(&self) -> &Client { &self.inner.client } + + /// Returns the tracks activated forks relevant for transaction validation + pub fn fork_tracker(&self) -> &ForkTracker { + &self.inner.fork_tracker + } + + /// Returns if there are EIP-2718 type transactions + pub fn eip2718(&self) -> bool { + self.inner.eip2718 + } + + /// Returns if there are EIP-1559 type transactions + pub fn eip1559(&self) -> bool { + self.inner.eip1559 + } + + /// Returns if there are EIP-4844 blob transactions + pub fn eip4844(&self) -> bool { + self.inner.eip4844 + } + + /// Returns if there are EIP-7702 type transactions + pub fn eip7702(&self) -> bool { + self.inner.eip7702 + } + + /// Returns the current tx fee cap limit in wei locally submitted into the pool + pub fn tx_fee_cap(&self) -> &Option { + &self.inner.tx_fee_cap + } + + /// Returns the minimum priority fee to enforce for acceptance into the pool + pub fn minimum_priority_fee(&self) -> &Option { + &self.inner.minimum_priority_fee + } + + /// Returns the setup and parameters needed for validating KZG proofs. + pub fn kzg_settings(&self) -> &EnvKzgSettings { + &self.inner.kzg_settings + } + + /// Returns the config to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.. + pub fn local_transactions_config(&self) -> &LocalTransactionConfig { + &self.inner.local_transactions_config + } + + /// Returns the maximum size in bytes a single transaction can have in order to be accepted into + /// the pool. + pub fn max_tx_input_bytes(&self) -> usize { + self.inner.max_tx_input_bytes + } } impl EthTransactionValidator @@ -68,6 +119,11 @@ where Client: ChainSpecProvider + StateProviderFactory, Tx: EthPoolTransaction, { + /// Returns the current max gas limit + pub fn block_gas_limit(&self) -> u64 { + self.inner.max_gas_limit() + } + /// Validates a single transaction. /// /// See also [`TransactionValidator::validate_transaction`] From 71b33f12cc01b39a651aa017c970ca9606a2b57f Mon Sep 17 00:00:00 2001 From: Federico Gimenez Date: Tue, 24 Jun 2025 10:30:08 +0200 Subject: [PATCH 199/274] chore: enable state root task in engine tree unit tests (#17023) --- crates/engine/tree/src/tree/tests.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index 5c7e63ab634..9922d29ff1d 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -142,9 +142,8 @@ impl TestHarness { persistence_handle, PersistenceState::default(), payload_builder, - // TODO: fix tests for state root task https://github.com/paradigmxyz/reth/issues/14376 // always assume enough parallelism for tests - TreeConfig::default().with_legacy_state_root(true).with_has_enough_parallelism(true), + TreeConfig::default().with_legacy_state_root(false).with_has_enough_parallelism(true), EngineApiKind::Ethereum, evm_config, ); From 265700cf2fc96c7d6c78a802e937e328a0cc1ade Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 24 Jun 2025 10:35:19 +0200 Subject: [PATCH 200/274] feat: add configurable RPC middleware to RpcAddOns (#17024) --- Cargo.lock | 1 + crates/ethereum/node/src/node.rs | 3 +- crates/node/builder/Cargo.toml | 3 + crates/node/builder/src/rpc.rs | 152 +++++++++++++++++++++++++++---- crates/optimism/node/src/node.rs | 6 +- 5 files changed, 145 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 78c1d70476f..df1ec6100bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8856,6 +8856,7 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", + "tower", "tracing", ] diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index c0f33e21b38..1bbed9c5859 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -25,7 +25,7 @@ use reth_node_builder::{ node::{FullNodeTypes, NodeTypes}, rpc::{ BasicEngineApiBuilder, EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, - EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle, + EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle, RpcServiceBuilder, }, BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder, PayloadBuilderConfig, PayloadTypes, @@ -174,6 +174,7 @@ where EthereumEthApiBuilder, EthereumEngineValidatorBuilder::default(), BasicEngineApiBuilder::default(), + RpcServiceBuilder::new(), ), } } diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index d08c62d38ce..a21c9ef5bc6 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -82,6 +82,9 @@ serde_json.workspace = true # tracing tracing.workspace = true +# tower +tower.workspace = true + [dev-dependencies] tempfile.workspace = true diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index e965dfb2fbd..2883d511140 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1,5 +1,8 @@ //! Builder support for rpc components. +pub use jsonrpsee::server::middleware::rpc::{RpcService, RpcServiceBuilder}; +pub use reth_rpc_builder::{Identity, RpcRequestMetricsService}; + use crate::{BeaconConsensusEngineEvent, BeaconConsensusEngineHandle}; use alloy_rpc_types::engine::ClientVersionV1; use alloy_rpc_types_engine::ExecutionData; @@ -31,6 +34,7 @@ use std::{ future::Future, ops::{Deref, DerefMut}, }; +use tower::Layer; /// Contains the handles to the spawned RPC servers. /// @@ -417,6 +421,7 @@ pub struct RpcAddOns< EthB: EthApiBuilder, EV, EB = BasicEngineApiBuilder, + RpcMiddleware = Identity, > { /// Additional RPC add-ons. pub hooks: RpcHooks, @@ -426,9 +431,14 @@ pub struct RpcAddOns< engine_validator_builder: EV, /// Builder for `EngineApi` engine_api_builder: EB, + /// Configurable RPC middleware stack. + /// + /// This middleware is applied to all RPC requests across all transports (HTTP, WS, IPC). + /// See [`RpcAddOns::with_rpc_middleware`] for more details. + rpc_middleware: RpcServiceBuilder, } -impl Debug for RpcAddOns +impl Debug for RpcAddOns where Node: FullNodeComponents, EthB: EthApiBuilder, @@ -441,11 +451,12 @@ where .field("eth_api_builder", &"...") .field("engine_validator_builder", &self.engine_validator_builder) .field("engine_api_builder", &self.engine_api_builder) + .field("rpc_middleware", &"...") .finish() } } -impl RpcAddOns +impl RpcAddOns where Node: FullNodeComponents, EthB: EthApiBuilder, @@ -455,28 +466,98 @@ where eth_api_builder: EthB, engine_validator_builder: EV, engine_api_builder: EB, + rpc_middleware: RpcServiceBuilder, ) -> Self { Self { hooks: RpcHooks::default(), eth_api_builder, engine_validator_builder, engine_api_builder, + rpc_middleware, } } /// Maps the [`EngineApiBuilder`] builder type. - pub fn with_engine_api(self, engine_api_builder: T) -> RpcAddOns { - let Self { hooks, eth_api_builder, engine_validator_builder, .. } = self; - RpcAddOns { hooks, eth_api_builder, engine_validator_builder, engine_api_builder } + pub fn with_engine_api( + self, + engine_api_builder: T, + ) -> RpcAddOns { + let Self { hooks, eth_api_builder, engine_validator_builder, rpc_middleware, .. } = self; + RpcAddOns { + hooks, + eth_api_builder, + engine_validator_builder, + engine_api_builder, + rpc_middleware, + } } /// Maps the [`EngineValidatorBuilder`] builder type. pub fn with_engine_validator( self, engine_validator_builder: T, - ) -> RpcAddOns { - let Self { hooks, eth_api_builder, engine_api_builder, .. } = self; - RpcAddOns { hooks, eth_api_builder, engine_validator_builder, engine_api_builder } + ) -> RpcAddOns { + let Self { hooks, eth_api_builder, engine_api_builder, rpc_middleware, .. } = self; + RpcAddOns { + hooks, + eth_api_builder, + engine_validator_builder, + engine_api_builder, + rpc_middleware, + } + } + + /// Sets the RPC middleware stack for processing RPC requests. + /// + /// This method configures a custom middleware stack that will be applied to all RPC requests + /// across HTTP, `WebSocket`, and IPC transports. The middleware is applied to the RPC service + /// layer, allowing you to intercept, modify, or enhance RPC request processing. + /// + /// + /// # How It Works + /// + /// The middleware uses the Tower ecosystem's `Layer` pattern. When an RPC server is started, + /// the configured middleware stack is applied to create a layered service that processes + /// requests in the order the layers were added. + /// + /// # Examples + /// + /// ```ignore + /// use reth_rpc_builder::{RpcServiceBuilder, RpcRequestMetrics}; + /// use tower::Layer; + /// + /// // Simple example with metrics + /// let metrics_layer = RpcRequestMetrics::new(metrics_recorder); + /// let with_metrics = rpc_addons.with_rpc_middleware( + /// RpcServiceBuilder::new().layer(metrics_layer) + /// ); + /// + /// // Composing multiple middleware layers + /// let middleware_stack = RpcServiceBuilder::new() + /// .layer(rate_limit_layer) + /// .layer(logging_layer) + /// .layer(metrics_layer); + /// let with_full_stack = rpc_addons.with_rpc_middleware(middleware_stack); + /// ``` + /// + /// # Notes + /// + /// - Middleware is applied to the RPC service layer, not the HTTP transport layer + /// - The default middleware is `Identity` (no-op), which passes through requests unchanged + /// - Middleware layers are applied in the order they are added via `.layer()` + pub fn with_rpc_middleware( + self, + rpc_middleware: RpcServiceBuilder, + ) -> RpcAddOns { + let Self { hooks, eth_api_builder, engine_validator_builder, engine_api_builder, .. } = + self; + RpcAddOns { + hooks, + eth_api_builder, + engine_validator_builder, + engine_api_builder, + rpc_middleware, + } } /// Sets the hook that is run once the rpc server is started. @@ -500,7 +581,7 @@ where } } -impl Default for RpcAddOns +impl Default for RpcAddOns where Node: FullNodeComponents, EthB: EthApiBuilder, @@ -508,17 +589,27 @@ where EB: Default, { fn default() -> Self { - Self::new(EthB::default(), EV::default(), EB::default()) + Self::new(EthB::default(), EV::default(), EB::default(), RpcServiceBuilder::new()) } } -impl RpcAddOns +impl RpcAddOns where N: FullNodeComponents, N::Provider: ChainSpecProvider, EthB: EthApiBuilder, EV: EngineValidatorBuilder, EB: EngineApiBuilder, + RpcMiddleware: Layer> + Clone + Send + 'static, + >>::Service: + Send + + Sync + + 'static + + jsonrpsee::server::middleware::rpc::RpcServiceT< + MethodResponse = jsonrpsee::MethodResponse, + BatchResponse = jsonrpsee::MethodResponse, + NotificationResponse = jsonrpsee::MethodResponse, + >, { /// Launches only the regular RPC server (HTTP/WS/IPC), without the authenticated Engine API /// server. @@ -533,6 +624,7 @@ where where F: FnOnce(RpcModuleContainer<'_, N, EthB::EthApi>) -> eyre::Result<()>, { + let rpc_middleware = self.rpc_middleware.clone(); let setup_ctx = self.setup_rpc_components(ctx, ext).await?; let RpcSetupContext { node, @@ -546,7 +638,7 @@ where engine_handle, } = setup_ctx; - let server_config = config.rpc.rpc_server_config(); + let server_config = config.rpc.rpc_server_config().set_rpc_middleware(rpc_middleware); let rpc_server_handle = Self::launch_rpc_server_internal(server_config, &modules).await?; let handles = @@ -579,6 +671,7 @@ where where F: FnOnce(RpcModuleContainer<'_, N, EthB::EthApi>) -> eyre::Result<()>, { + let rpc_middleware = self.rpc_middleware.clone(); let setup_ctx = self.setup_rpc_components(ctx, ext).await?; let RpcSetupContext { node, @@ -592,7 +685,7 @@ where engine_handle, } = setup_ctx; - let server_config = config.rpc.rpc_server_config(); + let server_config = config.rpc.rpc_server_config().set_rpc_middleware(rpc_middleware); let auth_module_clone = auth_module.clone(); // launch servers concurrently @@ -706,10 +799,22 @@ where } /// Helper to launch the RPC server - async fn launch_rpc_server_internal( - server_config: RpcServerConfig, + async fn launch_rpc_server_internal( + server_config: RpcServerConfig, modules: &TransportRpcModules, - ) -> eyre::Result { + ) -> eyre::Result + where + M: Layer> + Clone + Send + 'static, + for<'a> >>::Service: + Send + + Sync + + 'static + + jsonrpsee::server::middleware::rpc::RpcServiceT< + MethodResponse = jsonrpsee::MethodResponse, + BatchResponse = jsonrpsee::MethodResponse, + NotificationResponse = jsonrpsee::MethodResponse, + >, + { let handle = server_config.start(modules).await?; if let Some(path) = handle.ipc_endpoint() { @@ -760,13 +865,23 @@ where } } -impl NodeAddOns for RpcAddOns +impl NodeAddOns for RpcAddOns where N: FullNodeComponents, ::Provider: ChainSpecProvider, EthB: EthApiBuilder, EV: EngineValidatorBuilder, EB: EngineApiBuilder, + RpcMiddleware: Layer> + Clone + Send + 'static, + >>::Service: + Send + + Sync + + 'static + + jsonrpsee::server::middleware::rpc::RpcServiceT< + MethodResponse = jsonrpsee::MethodResponse, + BatchResponse = jsonrpsee::MethodResponse, + NotificationResponse = jsonrpsee::MethodResponse, + >, { type Handle = RpcHandle; @@ -787,7 +902,8 @@ pub trait RethRpcAddOns: fn hooks_mut(&mut self) -> &mut RpcHooks; } -impl RethRpcAddOns for RpcAddOns +impl RethRpcAddOns + for RpcAddOns where Self: NodeAddOns>, EthB: EthApiBuilder, diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index d0f26313fc3..27440be5710 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -27,7 +27,7 @@ use reth_node_builder::{ node::{FullNodeTypes, NodeTypes}, rpc::{ EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder, - RethRpcAddOns, RethRpcServerHandles, RpcAddOns, RpcContext, RpcHandle, + RethRpcAddOns, RethRpcServerHandles, RpcAddOns, RpcContext, RpcHandle, RpcServiceBuilder, }, BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder, }; @@ -255,6 +255,9 @@ impl NodeTypes for OpNode { } /// Add-ons w.r.t. optimism. +/// +/// This type provides optimism-specific addons to the node and exposes the RPC server and engine +/// API. #[derive(Debug)] pub struct OpAddOns, EV, EB> { /// Rpc add-ons responsible for launching the RPC servers and instantiating the RPC handlers @@ -591,6 +594,7 @@ impl OpAddOnsBuilder { .with_min_suggested_priority_fee(min_suggested_priority_fee), EV::default(), EB::default(), + RpcServiceBuilder::new(), ), da_config: da_config.unwrap_or_default(), sequencer_url, From b011ad0d8d63baad0ae211c822a61ab17006014f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Tue, 24 Jun 2025 11:22:08 +0200 Subject: [PATCH 201/274] feat(rpc): Propagate the RPC transaction request from `Network` and `RpcTypes` (#17025) --- .../src/testsuite/actions/engine_api.rs | 4 +- .../src/testsuite/actions/fork.rs | 25 +-- .../src/testsuite/actions/node_ops.rs | 58 ++++--- .../src/testsuite/actions/produce_blocks.rs | 75 +++++---- crates/e2e-test-utils/src/testsuite/setup.rs | 23 +-- crates/node/builder/src/launch/common.rs | 1 + crates/optimism/rpc/src/eth/call.rs | 6 +- crates/rpc/rpc-builder/src/lib.rs | 2 + crates/rpc/rpc-builder/tests/it/http.rs | 150 ++++++++++++------ crates/rpc/rpc-builder/tests/it/middleware.rs | 8 +- crates/rpc/rpc-convert/src/rpc.rs | 5 +- crates/rpc/rpc-convert/src/transaction.rs | 30 ++-- crates/rpc/rpc-eth-api/src/core.rs | 18 ++- crates/rpc/rpc-eth-api/src/helpers/call.rs | 9 +- crates/rpc/rpc-eth-api/src/types.rs | 6 +- crates/rpc/rpc-eth-types/src/simulate.rs | 14 +- crates/rpc/rpc-testing-util/src/debug.rs | 4 +- crates/rpc/rpc-testing-util/tests/it/trace.rs | 36 +++-- crates/rpc/rpc/src/engine.rs | 2 + crates/rpc/rpc/src/eth/core.rs | 8 +- crates/rpc/rpc/src/eth/helpers/call.rs | 6 +- crates/rpc/rpc/src/otterscan.rs | 2 + 22 files changed, 304 insertions(+), 188 deletions(-) diff --git a/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs b/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs index 655a6d723a0..6548fc951c6 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/engine_api.rs @@ -5,7 +5,7 @@ use alloy_primitives::B256; use alloy_rpc_types_engine::{ ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, PayloadStatusEnum, }; -use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction}; +use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest}; use eyre::Result; use futures_util::future::BoxFuture; use reth_node_api::{EngineTypes, PayloadTypes}; @@ -85,7 +85,7 @@ where const MAX_RETRIES: u32 = 5; while retries < MAX_RETRIES { - match EthApiClient::::block_by_number( + match EthApiClient::::block_by_number( source_rpc, alloy_eips::BlockNumberOrTag::Number(self.block_number), true, // include transactions diff --git a/crates/e2e-test-utils/src/testsuite/actions/fork.rs b/crates/e2e-test-utils/src/testsuite/actions/fork.rs index a0be6bdd8d0..1511d90fa59 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/fork.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/fork.rs @@ -5,7 +5,7 @@ use crate::testsuite::{ Action, BlockInfo, Environment, }; use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes}; -use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction}; +use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest}; use eyre::Result; use futures_util::future::BoxFuture; use reth_node_api::{EngineTypes, PayloadTypes}; @@ -130,14 +130,19 @@ where // get the block at the fork base number to establish the fork point let rpc_client = &env.node_clients[0].rpc; - let fork_base_block = - EthApiClient::::block_by_number( - rpc_client, - alloy_eips::BlockNumberOrTag::Number(self.fork_base_block), - false, - ) - .await? - .ok_or_else(|| eyre::eyre!("Fork base block {} not found", self.fork_base_block))?; + let fork_base_block = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::block_by_number( + rpc_client, + alloy_eips::BlockNumberOrTag::Number(self.fork_base_block), + false, + ) + .await? + .ok_or_else(|| eyre::eyre!("Fork base block {} not found", self.fork_base_block))?; // update active node state to point to the fork base block let active_node_state = env.active_node_state_mut()?; @@ -243,7 +248,7 @@ where // walk backwards through the chain until we reach the fork base while current_number > self.fork_base_number { - let block = EthApiClient::::block_by_hash( + let block = EthApiClient::::block_by_hash( rpc_client, current_hash, false, diff --git a/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs b/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs index 022e3eb2d78..2b3914339f8 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs @@ -1,7 +1,7 @@ //! Node-specific operations for multi-node testing. use crate::testsuite::{Action, Environment}; -use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction}; +use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest}; use eyre::Result; use futures_util::future::BoxFuture; use reth_node_api::EngineTypes; @@ -74,7 +74,7 @@ where let node_b_client = &env.node_clients[self.node_b]; // Get latest block from each node - let block_a = EthApiClient::::block_by_number( + let block_a = EthApiClient::::block_by_number( &node_a_client.rpc, alloy_eips::BlockNumberOrTag::Latest, false, @@ -82,7 +82,7 @@ where .await? .ok_or_else(|| eyre::eyre!("Failed to get latest block from node {}", self.node_a))?; - let block_b = EthApiClient::::block_by_number( + let block_b = EthApiClient::::block_by_number( &node_b_client.rpc, alloy_eips::BlockNumberOrTag::Latest, false, @@ -272,27 +272,37 @@ where let node_b_client = &env.node_clients[self.node_b]; // Get latest block from each node - let block_a = - EthApiClient::::block_by_number( - &node_a_client.rpc, - alloy_eips::BlockNumberOrTag::Latest, - false, - ) - .await? - .ok_or_else(|| { - eyre::eyre!("Failed to get latest block from node {}", self.node_a) - })?; - - let block_b = - EthApiClient::::block_by_number( - &node_b_client.rpc, - alloy_eips::BlockNumberOrTag::Latest, - false, - ) - .await? - .ok_or_else(|| { - eyre::eyre!("Failed to get latest block from node {}", self.node_b) - })?; + let block_a = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::block_by_number( + &node_a_client.rpc, + alloy_eips::BlockNumberOrTag::Latest, + false, + ) + .await? + .ok_or_else(|| { + eyre::eyre!("Failed to get latest block from node {}", self.node_a) + })?; + + let block_b = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::block_by_number( + &node_b_client.rpc, + alloy_eips::BlockNumberOrTag::Latest, + false, + ) + .await? + .ok_or_else(|| { + eyre::eyre!("Failed to get latest block from node {}", self.node_b) + })?; debug!( "Sync check: Node {} tip: {} (block {}), Node {} tip: {} (block {})", diff --git a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs index a6ceec2eee7..c20b79d9ae4 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs @@ -8,7 +8,7 @@ use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_engine::{ payload::ExecutionPayloadEnvelopeV3, ForkchoiceState, PayloadAttributes, PayloadStatusEnum, }; -use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction}; +use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest}; use eyre::Result; use futures_util::future::BoxFuture; use reth_node_api::{EngineTypes, PayloadTypes}; @@ -73,13 +73,16 @@ where let engine_client = node_client.engine.http_client(); // get the latest block to use as parent - let latest_block = - EthApiClient::::block_by_number( - rpc_client, - alloy_eips::BlockNumberOrTag::Latest, - false, - ) - .await?; + let latest_block = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::block_by_number( + rpc_client, alloy_eips::BlockNumberOrTag::Latest, false + ) + .await?; let latest_block = latest_block.ok_or_else(|| eyre::eyre!("Latest block not found"))?; let parent_hash = latest_block.header.hash; @@ -339,14 +342,17 @@ where } else { // fallback to RPC query let rpc_client = &env.node_clients[0].rpc; - let current_head_block = - EthApiClient::::block_by_number( - rpc_client, - alloy_eips::BlockNumberOrTag::Latest, - false, - ) - .await? - .ok_or_else(|| eyre::eyre!("No latest block found from RPC"))?; + let current_head_block = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::block_by_number( + rpc_client, alloy_eips::BlockNumberOrTag::Latest, false + ) + .await? + .ok_or_else(|| eyre::eyre!("No latest block found from RPC"))?; debug!("Using RPC latest block hash as head: {}", current_head_block.header.hash); current_head_block.header.hash }; @@ -409,14 +415,17 @@ where Box::pin(async move { // get the latest block from the first client to update environment state let rpc_client = &env.node_clients[0].rpc; - let latest_block = - EthApiClient::::block_by_number( - rpc_client, - alloy_eips::BlockNumberOrTag::Latest, - false, - ) - .await? - .ok_or_else(|| eyre::eyre!("No latest block found from RPC"))?; + let latest_block = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::block_by_number( + rpc_client, alloy_eips::BlockNumberOrTag::Latest, false + ) + .await? + .ok_or_else(|| eyre::eyre!("No latest block found from RPC"))?; // update environment with the new block information env.set_current_block_info(BlockInfo { @@ -516,13 +525,17 @@ where let rpc_client = &client.rpc; // get the last header by number using latest_head_number - let rpc_latest_header = - EthApiClient::::header_by_number( - rpc_client, - alloy_eips::BlockNumberOrTag::Latest, - ) - .await? - .ok_or_else(|| eyre::eyre!("No latest header found from rpc"))?; + let rpc_latest_header = EthApiClient::< + TransactionRequest, + Transaction, + Block, + Receipt, + Header, + >::header_by_number( + rpc_client, alloy_eips::BlockNumberOrTag::Latest + ) + .await? + .ok_or_else(|| eyre::eyre!("No latest header found from rpc"))?; // perform several checks let next_new_payload = env diff --git a/crates/e2e-test-utils/src/testsuite/setup.rs b/crates/e2e-test-utils/src/testsuite/setup.rs index c51fd3c0bfe..1c1f0297064 100644 --- a/crates/e2e-test-utils/src/testsuite/setup.rs +++ b/crates/e2e-test-utils/src/testsuite/setup.rs @@ -7,7 +7,7 @@ use crate::{ use alloy_eips::BlockNumberOrTag; use alloy_primitives::B256; use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes}; -use alloy_rpc_types_eth::{Block as RpcBlock, Header, Receipt, Transaction}; +use alloy_rpc_types_eth::{Block as RpcBlock, Header, Receipt, Transaction, TransactionRequest}; use eyre::{eyre, Result}; use reth_chainspec::ChainSpec; use reth_engine_local::LocalPayloadAttributesBuilder; @@ -210,7 +210,7 @@ where let mut last_error = None; while retry_count < MAX_RETRIES { - match EthApiClient::::block_by_number( + match EthApiClient::::block_by_number( &client.rpc, BlockNumberOrTag::Latest, false, @@ -244,14 +244,17 @@ where // Initialize each node's state with genesis block information let genesis_block_info = { let first_client = &env.node_clients[0]; - let genesis_block = - EthApiClient::::block_by_number( - &first_client.rpc, - BlockNumberOrTag::Number(0), - false, - ) - .await? - .ok_or_else(|| eyre!("Genesis block not found"))?; + let genesis_block = EthApiClient::< + TransactionRequest, + Transaction, + RpcBlock, + Receipt, + Header, + >::block_by_number( + &first_client.rpc, BlockNumberOrTag::Number(0), false + ) + .await? + .ok_or_else(|| eyre!("Genesis block not found"))?; crate::testsuite::BlockInfo { hash: genesis_block.header.hash, diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index c5c4bf2c4eb..7a7521ea012 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -941,6 +941,7 @@ where // Verify that the healthy node is running the same chain as the current node. let chain_id = futures::executor::block_on(async { EthApiClient::< + alloy_rpc_types::TransactionRequest, alloy_rpc_types::Transaction, alloy_rpc_types::Block, alloy_rpc_types::Receipt, diff --git a/crates/optimism/rpc/src/eth/call.rs b/crates/optimism/rpc/src/eth/call.rs index f892a315fe7..d886b201bdf 100644 --- a/crates/optimism/rpc/src/eth/call.rs +++ b/crates/optimism/rpc/src/eth/call.rs @@ -1,11 +1,12 @@ use super::OpNodeCore; use crate::{OpEthApi, OpEthApiError}; +use alloy_rpc_types_eth::TransactionRequest; use op_revm::OpTransaction; use reth_evm::{execute::BlockExecutorFactory, ConfigureEvm, EvmFactory, TxEnvFor}; use reth_node_api::NodePrimitives; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadBlock, LoadState, SpawnBlocking}, - FromEvmError, FullEthApiTypes, RpcConvert, + FromEvmError, FullEthApiTypes, RpcConvert, RpcTypes, }; use reth_storage_api::{errors::ProviderError, ProviderHeader, ProviderTx}; use revm::context::TxEnv; @@ -37,7 +38,8 @@ where EvmFactory: EvmFactory>, >, >, - RpcConvert: RpcConvert>, + RpcConvert: RpcConvert, Network = Self::NetworkTypes>, + NetworkTypes: RpcTypes>, Error: FromEvmError + From<::Error> + From, diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index ff952016f49..9a16c079461 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -45,6 +45,7 @@ use reth_rpc_api::servers::*; use reth_rpc_eth_api::{ helpers::{Call, EthApiSpec, EthTransactions, LoadPendingBlock, TraceExt}, EthApiServer, EthApiTypes, FullEthApiServer, RpcBlock, RpcHeader, RpcReceipt, RpcTransaction, + RpcTxReq, }; use reth_rpc_eth_types::{EthConfig, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret}; @@ -663,6 +664,7 @@ where + CanonStateSubscriptions, Network: NetworkInfo + Peers + Clone + 'static, EthApi: EthApiServer< + RpcTxReq, RpcTransaction, RpcBlock, RpcReceipt, diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index a32b208d939..d21d6f915a9 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -176,24 +176,38 @@ where .unwrap(); // Implemented - EthApiClient::::protocol_version(client).await.unwrap(); - EthApiClient::::chain_id(client).await.unwrap(); - EthApiClient::::accounts(client).await.unwrap(); - EthApiClient::::get_account( + EthApiClient::::protocol_version( client, - address, - block_number.into(), ) .await .unwrap(); - EthApiClient::::block_number(client).await.unwrap(); - EthApiClient::::get_code(client, address, None) + EthApiClient::::chain_id(client) + .await + .unwrap(); + EthApiClient::::accounts(client) .await .unwrap(); - EthApiClient::::send_raw_transaction(client, tx) + EthApiClient::::get_account( + client, + address, + block_number.into(), + ) + .await + .unwrap(); + EthApiClient::::block_number(client) .await .unwrap(); - EthApiClient::::fee_history( + EthApiClient::::get_code( + client, address, None, + ) + .await + .unwrap(); + EthApiClient::::send_raw_transaction( + client, tx, + ) + .await + .unwrap(); + EthApiClient::::fee_history( client, U64::from(0), block_number, @@ -201,13 +215,17 @@ where ) .await .unwrap(); - EthApiClient::::balance(client, address, None) - .await - .unwrap(); - EthApiClient::::transaction_count(client, address, None) - .await - .unwrap(); - EthApiClient::::storage_at( + EthApiClient::::balance( + client, address, None, + ) + .await + .unwrap(); + EthApiClient::::transaction_count( + client, address, None, + ) + .await + .unwrap(); + EthApiClient::::storage_at( client, address, U256::default().into(), @@ -215,72 +233,80 @@ where ) .await .unwrap(); - EthApiClient::::block_by_hash(client, hash, false) - .await - .unwrap(); - EthApiClient::::block_by_number( + EthApiClient::::block_by_hash( + client, hash, false, + ) + .await + .unwrap(); + EthApiClient::::block_by_number( client, block_number, false, ) .await .unwrap(); - EthApiClient::::block_transaction_count_by_number( + EthApiClient::::block_transaction_count_by_number( client, block_number, ) .await .unwrap(); - EthApiClient::::block_transaction_count_by_hash( + EthApiClient::::block_transaction_count_by_hash( client, hash, ) .await .unwrap(); - EthApiClient::::block_uncles_count_by_hash(client, hash) + EthApiClient::::block_uncles_count_by_hash(client, hash) .await .unwrap(); - EthApiClient::::block_uncles_count_by_number( + EthApiClient::::block_uncles_count_by_number( client, block_number, ) .await .unwrap(); - EthApiClient::::uncle_by_block_hash_and_index( + EthApiClient::::uncle_by_block_hash_and_index( client, hash, index, ) .await .unwrap(); - EthApiClient::::uncle_by_block_number_and_index( + EthApiClient::::uncle_by_block_number_and_index( client, block_number, index, ) .await .unwrap(); - EthApiClient::::sign(client, address, bytes.clone()) - .await - .unwrap_err(); - EthApiClient::::sign_typed_data( + EthApiClient::::sign( + client, + address, + bytes.clone(), + ) + .await + .unwrap_err(); + EthApiClient::::sign_typed_data( client, address, typed_data, ) .await .unwrap_err(); - EthApiClient::::transaction_by_hash(client, tx_hash) - .await - .unwrap(); - EthApiClient::::transaction_by_block_hash_and_index( + EthApiClient::::transaction_by_hash( + client, tx_hash, + ) + .await + .unwrap(); + EthApiClient::::transaction_by_block_hash_and_index( client, hash, index, ) .await .unwrap(); - EthApiClient::::transaction_by_block_number_and_index( + EthApiClient::::transaction_by_block_number_and_index( client, block_number, index, ) .await .unwrap(); - EthApiClient::::create_access_list( + EthApiClient::::create_access_list( client, call_request.clone(), Some(block_number.into()), @@ -288,7 +314,7 @@ where ) .await .unwrap_err(); - EthApiClient::::estimate_gas( + EthApiClient::::estimate_gas( client, call_request.clone(), Some(block_number.into()), @@ -296,7 +322,7 @@ where ) .await .unwrap_err(); - EthApiClient::::call( + EthApiClient::::call( client, call_request.clone(), Some(block_number.into()), @@ -305,47 +331,67 @@ where ) .await .unwrap_err(); - EthApiClient::::syncing(client).await.unwrap(); - EthApiClient::::send_transaction( + EthApiClient::::syncing(client) + .await + .unwrap(); + EthApiClient::::send_transaction( client, transaction_request.clone(), ) .await .unwrap_err(); - EthApiClient::::sign_transaction( + EthApiClient::::sign_transaction( client, transaction_request, ) .await .unwrap_err(); - EthApiClient::::hashrate(client).await.unwrap(); - EthApiClient::::submit_hashrate( + EthApiClient::::hashrate(client) + .await + .unwrap(); + EthApiClient::::submit_hashrate( client, U256::default(), B256::default(), ) .await .unwrap(); - EthApiClient::::gas_price(client).await.unwrap_err(); - EthApiClient::::max_priority_fee_per_gas(client) + EthApiClient::::gas_price(client) .await .unwrap_err(); - EthApiClient::::get_proof(client, address, vec![], None) + EthApiClient::::max_priority_fee_per_gas(client) .await - .unwrap(); + .unwrap_err(); + EthApiClient::::get_proof( + client, + address, + vec![], + None, + ) + .await + .unwrap(); // Unimplemented assert!(is_unimplemented( - EthApiClient::::author(client).await.err().unwrap() + EthApiClient::::author(client) + .await + .err() + .unwrap() )); assert!(is_unimplemented( - EthApiClient::::is_mining(client).await.err().unwrap() + EthApiClient::::is_mining(client) + .await + .err() + .unwrap() )); assert!(is_unimplemented( - EthApiClient::::get_work(client).await.err().unwrap() + EthApiClient::::get_work(client) + .await + .err() + .unwrap() )); assert!(is_unimplemented( - EthApiClient::::submit_work( + EthApiClient::::submit_work( client, B64::default(), B256::default(), diff --git a/crates/rpc/rpc-builder/tests/it/middleware.rs b/crates/rpc/rpc-builder/tests/it/middleware.rs index 5e89867c8c6..80c94110a74 100644 --- a/crates/rpc/rpc-builder/tests/it/middleware.rs +++ b/crates/rpc/rpc-builder/tests/it/middleware.rs @@ -1,5 +1,5 @@ use crate::utils::{test_address, test_rpc_builder}; -use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction}; +use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest}; use jsonrpsee::{ core::middleware::{Batch, Notification}, server::middleware::rpc::{RpcServiceBuilder, RpcServiceT}, @@ -85,7 +85,11 @@ async fn test_rpc_middleware() { .unwrap(); let client = handle.http_client().unwrap(); - EthApiClient::::protocol_version(&client).await.unwrap(); + EthApiClient::::protocol_version( + &client, + ) + .await + .unwrap(); let count = mylayer.count.load(Ordering::Relaxed); assert_eq!(count, 1); } diff --git a/crates/rpc/rpc-convert/src/rpc.rs b/crates/rpc/rpc-convert/src/rpc.rs index 0e71419ccfb..7b5c457419c 100644 --- a/crates/rpc/rpc-convert/src/rpc.rs +++ b/crates/rpc/rpc-convert/src/rpc.rs @@ -25,5 +25,8 @@ where type TransactionRequest = T::TransactionRequest; } -/// Adapter for network specific transaction type. +/// Adapter for network specific transaction response. pub type RpcTransaction = ::TransactionResponse; + +/// Adapter for network specific transaction request. +pub type RpcTxReq = ::TransactionRequest; diff --git a/crates/rpc/rpc-convert/src/transaction.rs b/crates/rpc/rpc-convert/src/transaction.rs index 4f9df7cd13e..edb16d341ad 100644 --- a/crates/rpc/rpc-convert/src/transaction.rs +++ b/crates/rpc/rpc-convert/src/transaction.rs @@ -2,7 +2,7 @@ use crate::{ fees::{CallFees, CallFeesError}, - RpcTransaction, RpcTypes, + RpcTransaction, RpcTxReq, RpcTypes, }; use alloy_consensus::{error::ValueError, transaction::Recovered, EthereumTxEnvelope, TxEip4844}; use alloy_primitives::{Address, TxKind, U256}; @@ -67,14 +67,14 @@ pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug { /// `eth_simulateV1`. fn build_simulate_v1_transaction( &self, - request: TransactionRequest, + request: RpcTxReq, ) -> Result, Self::Error>; /// Creates a transaction environment for execution based on `request` with corresponding /// `cfg_env` and `block_env`. fn tx_env( &self, - request: TransactionRequest, + request: RpcTxReq, cfg_env: &CfgEnv, block_env: &BlockEnv, ) -> Result; @@ -378,9 +378,9 @@ where E: RpcTypes + Send + Sync + Unpin + Clone + Debug, Evm: ConfigureEvm, TxTy: IntoRpcTx + Clone + Debug, - TransactionRequest: TryIntoSimTx> + TryIntoTxEnv>, + RpcTxReq: TryIntoSimTx> + TryIntoTxEnv>, Err: From - + From<>>::Err> + + From< as TryIntoTxEnv>>::Err> + for<'a> From<>>::Err> + Error + Unpin @@ -412,16 +412,13 @@ where Ok(tx.into_rpc_tx(signer, tx_info)) } - fn build_simulate_v1_transaction( - &self, - request: TransactionRequest, - ) -> Result, Self::Error> { + fn build_simulate_v1_transaction(&self, request: RpcTxReq) -> Result, Self::Error> { Ok(request.try_into_sim_tx().map_err(|e| TransactionConversionError(e.to_string()))?) } fn tx_env( &self, - request: TransactionRequest, + request: RpcTxReq, cfg_env: &CfgEnv, block_env: &BlockEnv, ) -> Result { @@ -476,12 +473,11 @@ pub mod op { } } - impl TryIntoSimTx for TransactionRequest { + impl TryIntoSimTx for OpTransactionRequest { fn try_into_sim_tx(self) -> Result> { - let request: OpTransactionRequest = self.into(); - let tx = request.build_typed_tx().map_err(|request| { - ValueError::new(request.as_ref().clone(), "Required fields missing") - })?; + let tx = self + .build_typed_tx() + .map_err(|request| ValueError::new(request, "Required fields missing"))?; // Create an empty signature for the transaction. let signature = Signature::new(Default::default(), Default::default(), false); @@ -490,7 +486,7 @@ pub mod op { } } - impl TryIntoTxEnv> for TransactionRequest { + impl TryIntoTxEnv> for OpTransactionRequest { type Err = EthTxEnvError; fn try_into_tx_env( @@ -499,7 +495,7 @@ pub mod op { block_env: &BlockEnv, ) -> Result, Self::Err> { Ok(OpTransaction { - base: self.try_into_tx_env(cfg_env, block_env)?, + base: self.as_ref().clone().try_into_tx_env(cfg_env, block_env)?, enveloped_tx: Some(Bytes::new()), deposit: Default::default(), }) diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index 2a3e361729c..f7185a73c11 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -1,5 +1,9 @@ //! Implementation of the [`jsonrpsee`] generated [`EthApiServer`] trait. Handles RPC requests for //! the `eth_` namespace. +use crate::{ + helpers::{EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, FullEthApi}, + RpcBlock, RpcHeader, RpcReceipt, RpcTransaction, +}; use alloy_dyn_abi::TypedData; use alloy_eips::{eip2930::AccessListResult, BlockId, BlockNumberOrTag}; use alloy_json_rpc::RpcObject; @@ -7,24 +11,20 @@ use alloy_primitives::{Address, Bytes, B256, B64, U256, U64}; use alloy_rpc_types_eth::{ simulate::{SimulatePayload, SimulatedBlock}, state::{EvmOverrides, StateOverride}, - transaction::TransactionRequest, BlockOverrides, Bundle, EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Index, - StateContext, SyncStatus, Work, + StateContext, SyncStatus, TransactionRequest, Work, }; use alloy_serde::JsonStorageKey; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use reth_rpc_convert::RpcTxReq; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use tracing::trace; -use crate::{ - helpers::{EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, FullEthApi}, - RpcBlock, RpcHeader, RpcReceipt, RpcTransaction, -}; - /// Helper trait, unifies functionality that must be supported to implement all RPC methods for /// server. pub trait FullEthApiServer: EthApiServer< + RpcTxReq, RpcTransaction, RpcBlock, RpcReceipt, @@ -36,6 +36,7 @@ pub trait FullEthApiServer: impl FullEthApiServer for T where T: EthApiServer< + RpcTxReq, RpcTransaction, RpcBlock, RpcReceipt, @@ -48,7 +49,7 @@ impl FullEthApiServer for T where /// Eth rpc interface: #[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] -pub trait EthApi { +pub trait EthApi { /// Returns the protocol version encoded as a string. #[method(name = "protocolVersion")] async fn protocol_version(&self) -> RpcResult; @@ -376,6 +377,7 @@ pub trait EthApi { #[async_trait::async_trait] impl EthApiServer< + RpcTxReq, RpcTransaction, RpcBlock, RpcReceipt, diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index a5b07a42aa4..ba8122017f4 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -27,7 +27,7 @@ use reth_revm::{ db::{CacheDB, State}, DatabaseRef, }; -use reth_rpc_convert::RpcConvert; +use reth_rpc_convert::{RpcConvert, RpcTypes}; use reth_rpc_eth_types::{ cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, error::{api::FromEvmHalt, ensure_success, FromEthApiError}, @@ -456,7 +456,10 @@ pub trait Call: SignedTx = ProviderTx, >, >, - RpcConvert: RpcConvert>, + RpcConvert: RpcConvert< + TxEnv = TxEnvFor, + Network: RpcTypes>, + >, Error: FromEvmError + From<::Error> + From, @@ -705,7 +708,7 @@ pub trait Call: ); } - Ok(self.tx_resp_builder().tx_env(request, &evm_env.cfg_env, &evm_env.block_env)?) + Ok(self.tx_resp_builder().tx_env(request.into(), &evm_env.cfg_env, &evm_env.block_env)?) } /// Prepares the [`EvmEnv`] for execution of calls. diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index 4bfae089b29..7bb91af8258 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -1,7 +1,7 @@ //! Trait for specifying `eth` network dependent API types. use crate::{AsEthApiError, FromEthApiError, RpcNodeCore}; -use alloy_rpc_types_eth::Block; +use alloy_rpc_types_eth::{Block, TransactionRequest}; use reth_chain_state::CanonStateSubscriptions; use reth_rpc_convert::RpcConvert; use reth_storage_api::{ProviderTx, ReceiptProvider, TransactionsProvider}; @@ -11,7 +11,7 @@ use std::{ fmt::{self}, }; -pub use reth_rpc_convert::{RpcTransaction, RpcTypes}; +pub use reth_rpc_convert::{RpcTransaction, RpcTxReq, RpcTypes}; /// Network specific `eth` API types. /// @@ -64,6 +64,7 @@ where Network = Self::NetworkTypes, Error = RpcError, >, + NetworkTypes: RpcTypes>, >, { } @@ -80,6 +81,7 @@ impl FullEthApiTypes for T where Network = Self::NetworkTypes, Error = RpcError, >, + NetworkTypes: RpcTypes>, > { } diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index dead42af004..988261b8179 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -22,7 +22,7 @@ use reth_evm::{ use reth_primitives_traits::{ block::BlockTx, BlockBody as _, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction, }; -use reth_rpc_convert::{RpcConvert, RpcTransaction}; +use reth_rpc_convert::{RpcConvert, RpcTransaction, RpcTypes}; use reth_rpc_server_types::result::rpc_err; use reth_storage_api::noop::NoopProvider; use revm::{ @@ -77,7 +77,10 @@ pub fn execute_transactions( > where S: BlockBuilder>>>>, - T: RpcConvert, + T: RpcConvert< + Primitives = S::Primitives, + Network: RpcTypes>, + >, { builder.apply_pre_execution_changes()?; @@ -121,7 +124,10 @@ pub fn resolve_transaction( ) -> Result, EthApiError> where DB::Error: Into, - T: RpcConvert>, + T: RpcConvert< + Primitives: NodePrimitives, + Network: RpcTypes>, + >, { // If we're missing any fields we try to fill nonce, gas and // gas price. @@ -178,7 +184,7 @@ where } let tx = tx_resp_builder - .build_simulate_v1_transaction(tx) + .build_simulate_v1_transaction(tx.into()) .map_err(|e| EthApiError::other(e.into()))?; Ok(Recovered::new_unchecked(tx, from)) diff --git a/crates/rpc/rpc-testing-util/src/debug.rs b/crates/rpc/rpc-testing-util/src/debug.rs index 73e3c0471c5..85b1bc4208c 100644 --- a/crates/rpc/rpc-testing-util/src/debug.rs +++ b/crates/rpc/rpc-testing-util/src/debug.rs @@ -77,7 +77,9 @@ pub trait DebugApiExt { impl DebugApiExt for T where - T: EthApiClient + DebugApiClient + Sync, + T: EthApiClient + + DebugApiClient + + Sync, { type Provider = T; diff --git a/crates/rpc/rpc-testing-util/tests/it/trace.rs b/crates/rpc/rpc-testing-util/tests/it/trace.rs index c733e6bde67..301d65a820b 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 @@ //! Integration tests for the trace API. use alloy_primitives::map::HashSet; -use alloy_rpc_types_eth::{Block, Header, Transaction}; +use alloy_rpc_types_eth::{Block, Header, Transaction, TransactionRequest}; use alloy_rpc_types_trace::{ filter::TraceFilter, parity::TraceType, tracerequest::TraceCallRequest, }; @@ -112,12 +112,17 @@ async fn debug_trace_block_entire_chain() { let url = url.unwrap(); let client = HttpClientBuilder::default().build(url).unwrap(); - let current_block: u64 = - >::block_number(&client) - .await - .unwrap() - .try_into() - .unwrap(); + let current_block: u64 = >::block_number(&client) + .await + .unwrap() + .try_into() + .unwrap(); let range = 0..=current_block; let mut stream = client.debug_trace_block_buffered_unordered(range, None, 20); let now = Instant::now(); @@ -141,12 +146,17 @@ async fn debug_trace_block_opcodes_entire_chain() { let url = url.unwrap(); let client = HttpClientBuilder::default().build(url).unwrap(); - let current_block: u64 = - >::block_number(&client) - .await - .unwrap() - .try_into() - .unwrap(); + let current_block: u64 = >::block_number(&client) + .await + .unwrap() + .try_into() + .unwrap(); let range = 0..=current_block; println!("Tracing blocks {range:?} for opcodes"); let mut stream = client.trace_block_opcode_gas_unordered(range, 2).enumerate(); diff --git a/crates/rpc/rpc/src/engine.rs b/crates/rpc/rpc/src/engine.rs index 824e5fb40d7..33ef2b3e5fe 100644 --- a/crates/rpc/rpc/src/engine.rs +++ b/crates/rpc/rpc/src/engine.rs @@ -7,6 +7,7 @@ use alloy_rpc_types_eth::{ use alloy_serde::JsonStorageKey; use jsonrpsee::core::RpcResult as Result; use reth_rpc_api::{EngineEthApiServer, EthApiServer}; +use reth_rpc_convert::RpcTxReq; /// Re-export for convenience pub use reth_rpc_engine_api::EngineApi; use reth_rpc_eth_api::{ @@ -40,6 +41,7 @@ impl EngineEthApiServer, RpcReceipt< for EngineEthApi where Eth: EthApiServer< + RpcTxReq, RpcTransaction, RpcBlock, RpcReceipt, diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index e2cb066197f..f5ff036b73f 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -594,7 +594,7 @@ mod tests { /// Invalid block range #[tokio::test] async fn test_fee_history_empty() { - let response = as EthApiServer<_, _, _, _>>::fee_history( + let response = as EthApiServer<_, _, _, _, _>>::fee_history( &build_test_eth_api(NoopProvider::default()), U64::from(1), BlockNumberOrTag::Latest, @@ -616,7 +616,7 @@ mod tests { let (eth_api, _, _) = prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - let response = as EthApiServer<_, _, _, _>>::fee_history( + let response = as EthApiServer<_, _, _, _, _>>::fee_history( ð_api, U64::from(newest_block + 1), newest_block.into(), @@ -639,7 +639,7 @@ mod tests { let (eth_api, _, _) = prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - let response = as EthApiServer<_, _, _, _>>::fee_history( + let response = as EthApiServer<_, _, _, _, _>>::fee_history( ð_api, U64::from(1), (newest_block + 1000).into(), @@ -662,7 +662,7 @@ mod tests { let (eth_api, _, _) = prepare_eth_api(newest_block, oldest_block, block_count, MockEthProvider::default()); - let response = as EthApiServer<_, _, _, _>>::fee_history( + let response = as EthApiServer<_, _, _, _, _>>::fee_history( ð_api, U64::from(0), newest_block.into(), diff --git a/crates/rpc/rpc/src/eth/helpers/call.rs b/crates/rpc/rpc/src/eth/helpers/call.rs index fdaab2a6d21..1a41b8d5768 100644 --- a/crates/rpc/rpc/src/eth/helpers/call.rs +++ b/crates/rpc/rpc/src/eth/helpers/call.rs @@ -2,10 +2,11 @@ use crate::EthApi; use alloy_evm::block::BlockExecutorFactory; +use alloy_rpc_types_eth::TransactionRequest; use reth_errors::ProviderError; use reth_evm::{ConfigureEvm, EvmFactory, TxEnvFor}; use reth_node_api::NodePrimitives; -use reth_rpc_convert::RpcConvert; +use reth_rpc_convert::{RpcConvert, RpcTypes}; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadPendingBlock, LoadState, SpawnBlocking}, FromEvmError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, @@ -41,7 +42,8 @@ where SignedTx = ProviderTx, >, >, - RpcConvert: RpcConvert>, + RpcConvert: RpcConvert, Network = Self::NetworkTypes>, + NetworkTypes: RpcTypes>, Error: FromEvmError + From<::Error> + From, diff --git a/crates/rpc/rpc/src/otterscan.rs b/crates/rpc/rpc/src/otterscan.rs index 3ca257346e7..bafbf0730bd 100644 --- a/crates/rpc/rpc/src/otterscan.rs +++ b/crates/rpc/rpc/src/otterscan.rs @@ -13,6 +13,7 @@ use alloy_rpc_types_trace::{ use async_trait::async_trait; use jsonrpsee::{core::RpcResult, types::ErrorObjectOwned}; use reth_rpc_api::{EthApiServer, OtterscanServer}; +use reth_rpc_convert::RpcTxReq; use reth_rpc_eth_api::{ helpers::{EthTransactions, TraceExt}, FullEthApiTypes, RpcBlock, RpcHeader, RpcReceipt, RpcTransaction, @@ -67,6 +68,7 @@ impl OtterscanServer, RpcHeader where Eth: EthApiServer< + RpcTxReq, RpcTransaction, RpcBlock, RpcReceipt, From e4281b345d04497487a32dcc08a64fad53835a93 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Tue, 24 Jun 2025 11:53:05 +0100 Subject: [PATCH 202/274] refactor(trie): introduce `SparseSubtrieInner::rlp_node` method (#17031) --- crates/trie/sparse-parallel/src/trie.rs | 586 ++++++++++++------------ 1 file changed, 297 insertions(+), 289 deletions(-) diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 6dafcec3f98..ae3e8da1fad 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -12,8 +12,8 @@ use reth_trie_common::{ BranchNodeRef, ExtensionNodeRef, LeafNodeRef, Nibbles, RlpNode, TrieNode, CHILD_INDEX_RANGE, }; use reth_trie_sparse::{ - blinded::BlindedProvider, RlpNodePathStackItem, RlpNodeStackItem, SparseNode, SparseNodeType, - SparseTrieUpdates, TrieMasks, + blinded::BlindedProvider, RlpNodeStackItem, SparseNode, SparseNodeType, SparseTrieUpdates, + TrieMasks, }; use smallvec::SmallVec; use tracing::trace; @@ -335,17 +335,8 @@ pub struct SparseSubtrie { path: Nibbles, /// The map from paths to sparse trie nodes within this subtrie. nodes: HashMap, - /// When a branch is set, the corresponding child subtree is stored in the database. - branch_node_tree_masks: HashMap, - /// When a bit is set, the corresponding child is stored as a hash in the database. - branch_node_hash_masks: HashMap, - /// Map from leaf key paths to their values. - /// All values are stored here instead of directly in leaf nodes. - values: HashMap>, - /// Optional tracking of trie updates for later use. - updates: Option, - /// Reusable buffers for [`SparseSubtrie::update_hashes`]. - buffers: SparseSubtrieBuffers, + /// Subset of fields for mutable access while `nodes` field is also being mutably borrowed. + inner: SparseSubtrieInner, } impl SparseSubtrie { @@ -358,7 +349,7 @@ impl SparseSubtrie { /// If `retain_updates` is true, the trie will record branch node updates and deletions. /// This information can then be used to efficiently update an external database. pub fn with_updates(mut self, retain_updates: bool) -> Self { - self.updates = retain_updates.then_some(SparseTrieUpdates::default()); + self.inner.updates = retain_updates.then_some(SparseTrieUpdates::default()); self } @@ -385,10 +376,10 @@ impl SparseSubtrie { } if let Some(tree_mask) = masks.tree_mask { - self.branch_node_tree_masks.insert(path, tree_mask); + self.inner.branch_node_tree_masks.insert(path, tree_mask); } if let Some(hash_mask) = masks.hash_mask { - self.branch_node_hash_masks.insert(path, hash_mask); + self.inner.branch_node_hash_masks.insert(path, hash_mask); } match node { @@ -491,7 +482,7 @@ impl SparseSubtrie { SparseNode::Hash(hash) => { let mut full = *entry.key(); full.extend(&leaf.key); - self.values.insert(full, leaf.value.clone()); + self.inner.values.insert(full, leaf.value.clone()); entry.insert(SparseNode::Leaf { key: leaf.key, // Memoize the hash of a previously blinded node in a new leaf @@ -516,7 +507,7 @@ impl SparseSubtrie { let mut full = *entry.key(); full.extend(&leaf.key); entry.insert(SparseNode::new_leaf(leaf.key)); - self.values.insert(full, leaf.value.clone()); + self.inner.values.insert(full, leaf.value.clone()); } }, } @@ -591,15 +582,14 @@ impl SparseSubtrie { debug_assert!(prefix_set.iter().all(|path| path.starts_with(&self.path))); - debug_assert!(self.buffers.path_stack.is_empty()); - self.buffers.path_stack.push(RlpNodePathStackItem { - level: 0, - path: self.path, - is_in_prefix_set: None, - }); + debug_assert!(self.inner.buffers.path_stack.is_empty()); + self.inner + .buffers + .path_stack + .push(RlpNodePathStackItem { path: self.path, is_in_prefix_set: None }); - 'main: while let Some(RlpNodePathStackItem { level, path, mut is_in_prefix_set }) = - self.buffers.path_stack.pop() + while let Some(RlpNodePathStackItem { path, mut is_in_prefix_set }) = + self.inner.buffers.path_stack.pop() { let node = self .nodes @@ -608,7 +598,6 @@ impl SparseSubtrie { trace!( target: "trie::parallel_sparse", root = ?self.path, - ?level, ?path, ?is_in_prefix_set, ?node, @@ -621,286 +610,293 @@ impl SparseSubtrie { let mut prefix_set_contains = |path: &Nibbles| *is_in_prefix_set.get_or_insert_with(|| prefix_set.contains(path)); - let (rlp_node, node_type) = match node { - SparseNode::Empty => (RlpNode::word_rlp(&EMPTY_ROOT_HASH), SparseNodeType::Empty), - SparseNode::Hash(hash) => (RlpNode::word_rlp(hash), SparseNodeType::Hash), - SparseNode::Leaf { key, hash } => { - let mut path = path; - path.extend(key); - if let Some(hash) = hash.filter(|_| !prefix_set_contains(&path)) { - (RlpNode::word_rlp(&hash), SparseNodeType::Leaf) - } else { - let value = self.values.get(&path).unwrap(); - self.buffers.rlp_buf.clear(); - let rlp_node = LeafNodeRef { key, value }.rlp(&mut self.buffers.rlp_buf); - *hash = rlp_node.as_hash(); - (rlp_node, SparseNodeType::Leaf) - } - } - SparseNode::Extension { key, hash, store_in_db_trie } => { - let mut child_path = path; - child_path.extend(key); - if let Some((hash, store_in_db_trie)) = - hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) - { - ( - RlpNode::word_rlp(&hash), - SparseNodeType::Extension { store_in_db_trie: Some(store_in_db_trie) }, - ) - } else if self - .buffers - .rlp_node_stack - .last() - .is_some_and(|e| e.path == child_path) - { - let RlpNodeStackItem { - path: _, - rlp_node: child, - node_type: child_node_type, - } = self.buffers.rlp_node_stack.pop().unwrap(); - self.buffers.rlp_buf.clear(); - let rlp_node = - ExtensionNodeRef::new(key, &child).rlp(&mut self.buffers.rlp_buf); - *hash = rlp_node.as_hash(); - - let store_in_db_trie_value = child_node_type.store_in_db_trie(); - - trace!( - target: "trie::parallel_sparse", - ?path, - ?child_path, - ?child_node_type, - "Extension node" - ); + self.inner.rlp_node(prefix_set_contains, path, node); + } - *store_in_db_trie = store_in_db_trie_value; + debug_assert_eq!(self.inner.buffers.rlp_node_stack.len(), 1); + self.inner.buffers.rlp_node_stack.pop().unwrap().rlp_node + } - ( - rlp_node, - SparseNodeType::Extension { - // Inherit the `store_in_db_trie` flag from the child node, which is - // always the branch node - store_in_db_trie: store_in_db_trie_value, - }, - ) - } else { - // need to get rlp node for child first - self.buffers.path_stack.extend([ - RlpNodePathStackItem { level, path, is_in_prefix_set }, - RlpNodePathStackItem { - level: level + 1, - path: child_path, - is_in_prefix_set: None, - }, - ]); - continue - } - } - SparseNode::Branch { state_mask, hash, store_in_db_trie } => { - if let Some((hash, store_in_db_trie)) = - hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) - { - self.buffers.rlp_node_stack.push(RlpNodeStackItem { - path, - rlp_node: RlpNode::word_rlp(&hash), - node_type: SparseNodeType::Branch { - store_in_db_trie: Some(store_in_db_trie), - }, - }); - continue - } - let retain_updates = self.updates.is_some() && prefix_set_contains(&path); - - self.buffers.branch_child_buf.clear(); - // Walk children in a reverse order from `f` to `0`, so we pop the `0` first - // from the stack and keep walking in the sorted order. - for bit in CHILD_INDEX_RANGE.rev() { - if state_mask.is_bit_set(bit) { - let mut child = path; - child.push_unchecked(bit); - self.buffers.branch_child_buf.push(child); - } - } + /// Consumes and returns the currently accumulated trie updates. + /// + /// This is useful when you want to apply the updates to an external database, + /// and then start tracking a new set of updates. + fn take_updates(&mut self) -> SparseTrieUpdates { + self.inner.updates.take().unwrap_or_default() + } +} - self.buffers - .branch_value_stack_buf - .resize(self.buffers.branch_child_buf.len(), Default::default()); - let mut added_children = false; - - let mut tree_mask = TrieMask::default(); - let mut hash_mask = TrieMask::default(); - let mut hashes = Vec::new(); - for (i, child_path) in self.buffers.branch_child_buf.iter().enumerate() { - if self.buffers.rlp_node_stack.last().is_some_and(|e| &e.path == child_path) - { - let RlpNodeStackItem { - path: _, - rlp_node: child, - node_type: child_node_type, - } = self.buffers.rlp_node_stack.pop().unwrap(); - - // Update the masks only if we need to retain trie updates - if retain_updates { - // SAFETY: it's a child, so it's never empty - let last_child_nibble = child_path.last().unwrap(); - - // Determine whether we need to set trie mask bit. - let should_set_tree_mask_bit = if let Some(store_in_db_trie) = - child_node_type.store_in_db_trie() - { - // A branch or an extension node explicitly set the - // `store_in_db_trie` flag - store_in_db_trie - } else { - // A blinded node has the tree mask bit set - child_node_type.is_hash() && - self.branch_node_tree_masks.get(&path).is_some_and( - |mask| mask.is_bit_set(last_child_nibble), - ) - }; - if should_set_tree_mask_bit { - tree_mask.set_bit(last_child_nibble); - } - - // Set the hash mask. If a child node is a revealed branch node OR - // is a blinded node that has its hash mask bit set according to the - // database, set the hash mask bit and save the hash. - let hash = child.as_hash().filter(|_| { - child_node_type.is_branch() || - (child_node_type.is_hash() && - self.branch_node_hash_masks - .get(&path) - .is_some_and(|mask| { - mask.is_bit_set(last_child_nibble) - })) - }); - if let Some(hash) = hash { - hash_mask.set_bit(last_child_nibble); - hashes.push(hash); - } - } +/// Helper type for [`SparseSubtrie`] to mutably access only a subset of fields from the original +/// struct. +#[derive(Clone, PartialEq, Eq, Debug, Default)] +struct SparseSubtrieInner { + /// When a branch is set, the corresponding child subtree is stored in the database. + branch_node_tree_masks: HashMap, + /// When a bit is set, the corresponding child is stored as a hash in the database. + branch_node_hash_masks: HashMap, + /// Map from leaf key paths to their values. + /// All values are stored here instead of directly in leaf nodes. + values: HashMap>, + /// Optional tracking of trie updates for later use. + updates: Option, + /// Reusable buffers for [`SparseSubtrie::update_hashes`]. + buffers: SparseSubtrieBuffers, +} - // Insert children in the resulting buffer in a normal order, - // because initially we iterated in reverse. - // SAFETY: i < len and len is never 0 - let original_idx = self.buffers.branch_child_buf.len() - i - 1; - self.buffers.branch_value_stack_buf[original_idx] = child; - added_children = true; - } else { - debug_assert!(!added_children); - self.buffers.path_stack.push(RlpNodePathStackItem { - level, - path, - is_in_prefix_set, - }); - self.buffers.path_stack.extend( - self.buffers.branch_child_buf.drain(..).map(|path| { - RlpNodePathStackItem { - level: level + 1, - path, - is_in_prefix_set: None, - } - }), - ); - continue 'main - } - } +impl SparseSubtrieInner { + fn rlp_node( + &mut self, + mut prefix_set_contains: impl FnMut(&Nibbles) -> bool, + path: Nibbles, + node: &mut SparseNode, + ) { + let (rlp_node, node_type) = match node { + SparseNode::Empty => (RlpNode::word_rlp(&EMPTY_ROOT_HASH), SparseNodeType::Empty), + SparseNode::Hash(hash) => (RlpNode::word_rlp(hash), SparseNodeType::Hash), + SparseNode::Leaf { key, hash } => { + let mut path = path; + path.extend(key); + if let Some(hash) = hash.filter(|_| !prefix_set_contains(&path)) { + (RlpNode::word_rlp(&hash), SparseNodeType::Leaf) + } else { + let value = self.values.get(&path).unwrap(); + self.buffers.rlp_buf.clear(); + let rlp_node = LeafNodeRef { key, value }.rlp(&mut self.buffers.rlp_buf); + *hash = rlp_node.as_hash(); + (rlp_node, SparseNodeType::Leaf) + } + } + SparseNode::Extension { key, hash, store_in_db_trie } => { + let mut child_path = path; + child_path.extend(key); + if let Some((hash, store_in_db_trie)) = + hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) + { + ( + RlpNode::word_rlp(&hash), + SparseNodeType::Extension { store_in_db_trie: Some(store_in_db_trie) }, + ) + } else if self.buffers.rlp_node_stack.last().is_some_and(|e| e.path == child_path) { + let RlpNodeStackItem { path: _, rlp_node: child, node_type: child_node_type } = + self.buffers.rlp_node_stack.pop().unwrap(); + self.buffers.rlp_buf.clear(); + let rlp_node = + ExtensionNodeRef::new(key, &child).rlp(&mut self.buffers.rlp_buf); + *hash = rlp_node.as_hash(); + + let store_in_db_trie_value = child_node_type.store_in_db_trie(); trace!( target: "trie::parallel_sparse", ?path, - ?tree_mask, - ?hash_mask, - "Branch node masks" + ?child_path, + ?child_node_type, + "Extension node" ); - self.buffers.rlp_buf.clear(); - let branch_node_ref = - BranchNodeRef::new(&self.buffers.branch_value_stack_buf, *state_mask); - let rlp_node = branch_node_ref.rlp(&mut self.buffers.rlp_buf); - *hash = rlp_node.as_hash(); - - // Save a branch node update only if it's not a root node, and we need to - // persist updates. - let store_in_db_trie_value = if let Some(updates) = - self.updates.as_mut().filter(|_| retain_updates && !path.is_empty()) - { - let store_in_db_trie = !tree_mask.is_empty() || !hash_mask.is_empty(); - if store_in_db_trie { - // Store in DB trie if there are either any children that are stored in - // the DB trie, or any children represent hashed values - hashes.reverse(); - let branch_node = BranchNodeCompact::new( - *state_mask, - tree_mask, - hash_mask, - hashes, - hash.filter(|_| path.is_empty()), - ); - updates.updated_nodes.insert(path, branch_node); - } else if self - .branch_node_tree_masks - .get(&path) - .is_some_and(|mask| !mask.is_empty()) || - self.branch_node_hash_masks - .get(&path) - .is_some_and(|mask| !mask.is_empty()) - { - // If new tree and hash masks are empty, but previously they weren't, we - // need to remove the node update and add the node itself to the list of - // removed nodes. - updates.updated_nodes.remove(&path); - updates.removed_nodes.insert(path); - } else if self - .branch_node_hash_masks - .get(&path) - .is_none_or(|mask| mask.is_empty()) && - self.branch_node_hash_masks - .get(&path) - .is_none_or(|mask| mask.is_empty()) - { - // If new tree and hash masks are empty, and they were previously empty - // as well, we need to remove the node update. - updates.updated_nodes.remove(&path); - } - - store_in_db_trie - } else { - false - }; - *store_in_db_trie = Some(store_in_db_trie_value); + *store_in_db_trie = store_in_db_trie_value; ( rlp_node, - SparseNodeType::Branch { store_in_db_trie: Some(store_in_db_trie_value) }, + SparseNodeType::Extension { + // Inherit the `store_in_db_trie` flag from the child node, which is + // always the branch node + store_in_db_trie: store_in_db_trie_value, + }, ) + } else { + // need to get rlp node for child first + self.buffers.path_stack.extend([ + RlpNodePathStackItem { + path, + is_in_prefix_set: Some(prefix_set_contains(&path)), + }, + RlpNodePathStackItem { path: child_path, is_in_prefix_set: None }, + ]); + return + } + } + SparseNode::Branch { state_mask, hash, store_in_db_trie } => { + if let Some((hash, store_in_db_trie)) = + hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) + { + self.buffers.rlp_node_stack.push(RlpNodeStackItem { + path, + rlp_node: RlpNode::word_rlp(&hash), + node_type: SparseNodeType::Branch { + store_in_db_trie: Some(store_in_db_trie), + }, + }); + return + } + let retain_updates = self.updates.is_some() && prefix_set_contains(&path); + + self.buffers.branch_child_buf.clear(); + // Walk children in a reverse order from `f` to `0`, so we pop the `0` first + // from the stack and keep walking in the sorted order. + for bit in CHILD_INDEX_RANGE.rev() { + if state_mask.is_bit_set(bit) { + let mut child = path; + child.push_unchecked(bit); + self.buffers.branch_child_buf.push(child); + } } - }; - trace!( - target: "trie::parallel_sparse", - root = ?self.path, - ?level, - ?path, - ?node, - ?node_type, - ?is_in_prefix_set, - "Added node to rlp node stack" - ); + self.buffers + .branch_value_stack_buf + .resize(self.buffers.branch_child_buf.len(), Default::default()); + let mut added_children = false; - self.buffers.rlp_node_stack.push(RlpNodeStackItem { path, rlp_node, node_type }); - } + let mut tree_mask = TrieMask::default(); + let mut hash_mask = TrieMask::default(); + let mut hashes = Vec::new(); + for (i, child_path) in self.buffers.branch_child_buf.iter().enumerate() { + if self.buffers.rlp_node_stack.last().is_some_and(|e| &e.path == child_path) { + let RlpNodeStackItem { + path: _, + rlp_node: child, + node_type: child_node_type, + } = self.buffers.rlp_node_stack.pop().unwrap(); - debug_assert_eq!(self.buffers.rlp_node_stack.len(), 1); - self.buffers.rlp_node_stack.pop().unwrap().rlp_node - } + // Update the masks only if we need to retain trie updates + if retain_updates { + // SAFETY: it's a child, so it's never empty + let last_child_nibble = child_path.last().unwrap(); + + // Determine whether we need to set trie mask bit. + let should_set_tree_mask_bit = if let Some(store_in_db_trie) = + child_node_type.store_in_db_trie() + { + // A branch or an extension node explicitly set the + // `store_in_db_trie` flag + store_in_db_trie + } else { + // A blinded node has the tree mask bit set + child_node_type.is_hash() && + self.branch_node_tree_masks + .get(&path) + .is_some_and(|mask| mask.is_bit_set(last_child_nibble)) + }; + if should_set_tree_mask_bit { + tree_mask.set_bit(last_child_nibble); + } - /// Consumes and returns the currently accumulated trie updates. - /// - /// This is useful when you want to apply the updates to an external database, - /// and then start tracking a new set of updates. - fn take_updates(&mut self) -> SparseTrieUpdates { - self.updates.take().unwrap_or_default() + // Set the hash mask. If a child node is a revealed branch node OR + // is a blinded node that has its hash mask bit set according to the + // database, set the hash mask bit and save the hash. + let hash = child.as_hash().filter(|_| { + child_node_type.is_branch() || + (child_node_type.is_hash() && + self.branch_node_hash_masks.get(&path).is_some_and( + |mask| mask.is_bit_set(last_child_nibble), + )) + }); + if let Some(hash) = hash { + hash_mask.set_bit(last_child_nibble); + hashes.push(hash); + } + } + + // Insert children in the resulting buffer in a normal order, + // because initially we iterated in reverse. + // SAFETY: i < len and len is never 0 + let original_idx = self.buffers.branch_child_buf.len() - i - 1; + self.buffers.branch_value_stack_buf[original_idx] = child; + added_children = true; + } else { + debug_assert!(!added_children); + self.buffers.path_stack.push(RlpNodePathStackItem { + path, + is_in_prefix_set: Some(prefix_set_contains(&path)), + }); + self.buffers.path_stack.extend( + self.buffers + .branch_child_buf + .drain(..) + .map(|path| RlpNodePathStackItem { path, is_in_prefix_set: None }), + ); + return + } + } + + trace!( + target: "trie::parallel_sparse", + ?path, + ?tree_mask, + ?hash_mask, + "Branch node masks" + ); + + self.buffers.rlp_buf.clear(); + let branch_node_ref = + BranchNodeRef::new(&self.buffers.branch_value_stack_buf, *state_mask); + let rlp_node = branch_node_ref.rlp(&mut self.buffers.rlp_buf); + *hash = rlp_node.as_hash(); + + // Save a branch node update only if it's not a root node, and we need to + // persist updates. + let store_in_db_trie_value = if let Some(updates) = + self.updates.as_mut().filter(|_| retain_updates && !path.is_empty()) + { + let store_in_db_trie = !tree_mask.is_empty() || !hash_mask.is_empty(); + if store_in_db_trie { + // Store in DB trie if there are either any children that are stored in + // the DB trie, or any children represent hashed values + hashes.reverse(); + let branch_node = BranchNodeCompact::new( + *state_mask, + tree_mask, + hash_mask, + hashes, + hash.filter(|_| path.is_empty()), + ); + updates.updated_nodes.insert(path, branch_node); + } else if self + .branch_node_tree_masks + .get(&path) + .is_some_and(|mask| !mask.is_empty()) || + self.branch_node_hash_masks + .get(&path) + .is_some_and(|mask| !mask.is_empty()) + { + // If new tree and hash masks are empty, but previously they weren't, we + // need to remove the node update and add the node itself to the list of + // removed nodes. + updates.updated_nodes.remove(&path); + updates.removed_nodes.insert(path); + } else if self + .branch_node_hash_masks + .get(&path) + .is_none_or(|mask| mask.is_empty()) && + self.branch_node_hash_masks.get(&path).is_none_or(|mask| mask.is_empty()) + { + // If new tree and hash masks are empty, and they were previously empty + // as well, we need to remove the node update. + updates.updated_nodes.remove(&path); + } + + store_in_db_trie + } else { + false + }; + *store_in_db_trie = Some(store_in_db_trie_value); + + ( + rlp_node, + SparseNodeType::Branch { store_in_db_trie: Some(store_in_db_trie_value) }, + ) + } + }; + + trace!( + target: "trie::parallel_sparse", + ?path, + ?node, + ?node_type, + "Added node to rlp node stack" + ); + + self.buffers.rlp_node_stack.push(RlpNodeStackItem { path, rlp_node, node_type }); } } @@ -965,6 +961,15 @@ pub struct SparseSubtrieBuffers { rlp_buf: Vec, } +/// RLP node path stack item. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct RlpNodePathStackItem { + /// Path to the node. + pub path: Nibbles, + /// Whether the path is in the prefix set. If [`None`], then unknown yet. + pub is_in_prefix_set: Option, +} + /// Changed subtrie. #[derive(Debug)] struct ChangedSubtrie { @@ -1299,7 +1304,10 @@ mod tests { ); let full_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); - assert_eq!(trie.upper_subtrie.values.get(&full_path), Some(&encode_account_value(42))); + assert_eq!( + trie.upper_subtrie.inner.values.get(&full_path), + Some(&encode_account_value(42)) + ); } // Reveal leaf in a lower trie From b8e3f673dd330d77496570b84a826f423337f5e3 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Tue, 24 Jun 2025 12:16:32 +0100 Subject: [PATCH 203/274] chore(trie): rephrase the log about storage proof task result sending (#17032) --- crates/trie/parallel/src/proof_task.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/trie/parallel/src/proof_task.rs b/crates/trie/parallel/src/proof_task.rs index 253624d71fc..4dc78106963 100644 --- a/crates/trie/parallel/src/proof_task.rs +++ b/crates/trie/parallel/src/proof_task.rs @@ -279,7 +279,7 @@ where hashed_address = ?input.hashed_address, ?error, task_time = ?proof_start.elapsed(), - "Failed to send proof result" + "Storage proof receiver is dropped, discarding the result" ); } From f5680e74d519840e80654d34032989eb8e3b9202 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 24 Jun 2025 14:41:22 +0200 Subject: [PATCH 204/274] feat: prune pre merge transaction files (#16702) --- Cargo.toml | 2 +- book/cli/reth/node.md | 9 ++ crates/config/src/config.rs | 4 + crates/node/builder/src/launch/common.rs | 77 +++++++++-- crates/node/builder/src/launch/engine.rs | 3 + crates/node/core/src/args/pruning.rs | 42 +++++- crates/node/core/src/node_config.rs | 9 +- crates/prune/prune/src/segments/set.rs | 1 + .../src/segments/static_file/transactions.rs | 1 + crates/prune/types/src/target.rs | 10 ++ crates/storage/nippy-jar/src/lib.rs | 1 + .../src/providers/static_file/manager.rs | 129 ++++++++++++++++-- 12 files changed, 257 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab1580388c9..b6b64130603 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -474,7 +474,7 @@ alloy-sol-macro = "1.2.0" alloy-sol-types = { version = "1.2.0", default-features = false } alloy-trie = { version = "0.9.0", default-features = false } -alloy-hardforks = "0.2.2" +alloy-hardforks = "0.2.7" alloy-consensus = { version = "1.0.12", default-features = false } alloy-contract = { version = "1.0.12", default-features = false } diff --git a/book/cli/reth/node.md b/book/cli/reth/node.md index 6dcd98c684b..9556c3256c3 100644 --- a/book/cli/reth/node.md +++ b/book/cli/reth/node.md @@ -721,6 +721,15 @@ Pruning: --prune.storagehistory.before Prune storage history before the specified block number. The specified block number is not pruned + --prune.bodies.pre-merge + Prune bodies before the merge block + + --prune.bodies.distance + Prune bodies before the `head-N` block number. In other words, keep last N + 1 blocks + + --prune.bodies.before + Prune storage history before the specified block number. The specified block number is not pruned + Engine: --engine.persistence-threshold Configure persistence threshold for engine experimental diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 5c187074000..c1c5ef96075 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -463,6 +463,7 @@ impl PruneConfig { receipts, account_history, storage_history, + bodies_history, receipts_log_filter, }, } = other; @@ -478,6 +479,7 @@ impl PruneConfig { self.segments.receipts = self.segments.receipts.or(receipts); self.segments.account_history = self.segments.account_history.or(account_history); self.segments.storage_history = self.segments.storage_history.or(storage_history); + self.segments.bodies_history = self.segments.bodies_history.or(bodies_history); if self.segments.receipts_log_filter.0.is_empty() && !receipts_log_filter.0.is_empty() { self.segments.receipts_log_filter = receipts_log_filter; @@ -998,6 +1000,7 @@ receipts = 'full' receipts: Some(PruneMode::Distance(1000)), account_history: None, storage_history: Some(PruneMode::Before(5000)), + bodies_history: None, receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([( Address::random(), PruneMode::Full, @@ -1013,6 +1016,7 @@ receipts = 'full' receipts: Some(PruneMode::Full), account_history: Some(PruneMode::Distance(2000)), storage_history: Some(PruneMode::Distance(3000)), + bodies_history: None, receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([ (Address::random(), PruneMode::Distance(1000)), (Address::random(), PruneMode::Before(2000)), diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 7a7521ea012..3c4583466a2 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -5,11 +5,12 @@ use crate::{ hooks::OnComponentInitializedHook, BuilderContext, NodeAdapter, }; +use alloy_consensus::BlockHeader as _; use alloy_eips::eip2124::Head; use alloy_primitives::{BlockNumber, B256}; use eyre::{Context, OptionExt}; use rayon::ThreadPoolBuilder; -use reth_chainspec::{Chain, EthChainSpec, EthereumHardforks}; +use reth_chainspec::{Chain, EthChainSpec, EthereumHardfork, EthereumHardforks}; use reth_config::{config::EtlConfig, PruneConfig}; use reth_consensus::noop::NoopConsensus; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; @@ -41,8 +42,9 @@ use reth_node_metrics::{ }; use reth_provider::{ providers::{NodeTypesForProvider, ProviderNodeTypes, StaticFileProvider}, - BlockHashReader, BlockNumReader, ChainSpecProvider, ProviderError, ProviderFactory, - ProviderResult, StageCheckpointReader, StateProviderFactory, StaticFileProviderFactory, + BlockHashReader, BlockNumReader, BlockReaderIdExt, ChainSpecProvider, ProviderError, + ProviderFactory, ProviderResult, StageCheckpointReader, StateProviderFactory, + StaticFileProviderFactory, }; use reth_prune::{PruneModes, PrunerBuilder}; use reth_rpc_api::clients::EthApiClient; @@ -85,10 +87,13 @@ impl LaunchContext { /// `config`. /// /// Attaches both the `NodeConfig` and the loaded `reth.toml` config to the launch context. - pub fn with_loaded_toml_config( + pub fn with_loaded_toml_config( self, config: NodeConfig, - ) -> eyre::Result>> { + ) -> eyre::Result>> + where + ChainSpec: EthChainSpec + reth_chainspec::EthereumHardforks, + { let toml_config = self.load_toml_config(&config)?; Ok(self.with(WithConfigs { config, toml_config })) } @@ -97,10 +102,13 @@ impl LaunchContext { /// `config`. /// /// This is async because the trusted peers may have to be resolved. - pub fn load_toml_config( + pub fn load_toml_config( &self, config: &NodeConfig, - ) -> eyre::Result { + ) -> eyre::Result + where + ChainSpec: EthChainSpec + reth_chainspec::EthereumHardforks, + { let config_path = config.config.clone().unwrap_or_else(|| self.data_dir.config()); let mut toml_config = reth_config::Config::from_path(&config_path) @@ -117,11 +125,14 @@ impl LaunchContext { } /// Save prune config to the toml file if node is a full node. - fn save_pruning_config_if_full_node( + fn save_pruning_config_if_full_node( reth_config: &mut reth_config::Config, config: &NodeConfig, config_path: impl AsRef, - ) -> eyre::Result<()> { + ) -> eyre::Result<()> + where + ChainSpec: EthChainSpec + reth_chainspec::EthereumHardforks, + { if reth_config.prune.is_none() { if let Some(prune_config) = config.prune_config() { reth_config.update_prune_config(prune_config); @@ -340,7 +351,10 @@ impl LaunchContextWith Option { + pub fn prune_config(&self) -> Option + where + ChainSpec: reth_chainspec::EthereumHardforks, + { let Some(mut node_prune_config) = self.node_config().prune_config() else { // No CLI config is set, use the toml config. return self.toml_config().prune.clone(); @@ -352,12 +366,18 @@ impl LaunchContextWith PruneModes { + pub fn prune_modes(&self) -> PruneModes + where + ChainSpec: reth_chainspec::EthereumHardforks, + { self.prune_config().map(|config| config.segments).unwrap_or_default() } /// Returns an initialized [`PrunerBuilder`] based on the configured [`PruneConfig`] - pub fn pruner_builder(&self) -> PrunerBuilder { + pub fn pruner_builder(&self) -> PrunerBuilder + where + ChainSpec: reth_chainspec::EthereumHardforks, + { PrunerBuilder::new(self.prune_config().unwrap_or_default()) .delete_limit(self.chain_spec().prune_delete_limit()) .timeout(PrunerBuilder::DEFAULT_TIMEOUT) @@ -873,6 +893,36 @@ where Ok(None) } + /// Expire the pre-merge transactions if the node is configured to do so and the chain has a + /// merge block. + /// + /// If the node is configured to prune pre-merge transactions and it has synced past the merge + /// block, it will delete the pre-merge transaction static files if they still exist. + pub fn expire_pre_merge_transactions(&self) -> eyre::Result<()> + where + T: FullNodeTypes, + { + if self.node_config().pruning.bodies_pre_merge { + if let Some(merge_block) = + self.chain_spec().ethereum_fork_activation(EthereumHardfork::Paris).block_number() + { + // Ensure we only expire transactions after we synced past the merge block. + let Some(latest) = self.blockchain_db().latest_header()? else { return Ok(()) }; + if latest.number() > merge_block { + let provider = self.blockchain_db().static_file_provider(); + if provider.get_lowest_transaction_static_file_block() < Some(merge_block) { + info!(target: "reth::cli", merge_block, "Expiring pre-merge transactions"); + provider.delete_transactions_below(merge_block)?; + } else { + debug!(target: "reth::cli", merge_block, "No pre-merge transactions to expire"); + } + } + } + } + + Ok(()) + } + /// Returns the metrics sender. pub fn sync_metrics_tx(&self) -> UnboundedSender { self.right().db_provider_container.metrics_sender.clone() @@ -1095,7 +1145,10 @@ mod tests { storage_history_full: false, storage_history_distance: None, storage_history_before: None, + bodies_pre_merge: false, + bodies_distance: None, receipts_log_filter: None, + bodies_before: None, }, ..NodeConfig::test() }; diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 433f34bfb1e..796a47b3db9 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -130,6 +130,9 @@ where })? .with_components(components_builder, on_component_initialized).await?; + // Try to expire pre-merge transaction history if configured + ctx.expire_pre_merge_transactions()?; + // spawn exexs let exex_manager_handle = ExExLauncher::new( ctx.head(), diff --git a/crates/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index b523191eeca..3f493a900a9 100644 --- a/crates/node/core/src/args/pruning.rs +++ b/crates/node/core/src/args/pruning.rs @@ -1,8 +1,9 @@ //! Pruning and full node arguments -use crate::args::error::ReceiptsLogError; +use crate::{args::error::ReceiptsLogError, primitives::EthereumHardfork}; use alloy_primitives::{Address, BlockNumber}; use clap::{builder::RangedU64ValueParser, Args}; +use reth_chainspec::EthereumHardforks; use reth_config::config::PruneConfig; use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE}; use std::collections::BTreeMap; @@ -86,11 +87,27 @@ pub struct PruningArgs { /// pruned. #[arg(long = "prune.storagehistory.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["storage_history_full", "storage_history_distance"])] pub storage_history_before: Option, + + // Bodies + /// Prune bodies before the merge block. + #[arg(long = "prune.bodies.pre-merge", value_name = "BLOCKS", conflicts_with_all = &["bodies_distance", "bodies_before"])] + pub bodies_pre_merge: bool, + /// Prune bodies before the `head-N` block number. In other words, keep last N + 1 + /// blocks. + #[arg(long = "prune.bodies.distance", value_name = "BLOCKS", conflicts_with_all = &["bodies_pre_merge", "bodies_before"])] + pub bodies_distance: Option, + /// Prune storage history before the specified block number. The specified block number is not + /// pruned. + #[arg(long = "prune.bodies.before", value_name = "BLOCK_NUMBER", conflicts_with_all = &["bodies_distance", "bodies_pre_merge"])] + pub bodies_before: Option, } impl PruningArgs { /// Returns pruning configuration. - pub fn prune_config(&self) -> Option { + pub fn prune_config(&self, chain_spec: &ChainSpec) -> Option + where + ChainSpec: EthereumHardforks, + { // Initialise with a default prune configuration. let mut config = PruneConfig::default(); @@ -104,6 +121,8 @@ impl PruningArgs { receipts: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), account_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)), + // TODO: set default to pre-merge block if available + bodies_history: None, receipts_log_filter: Default::default(), }, } @@ -125,6 +144,9 @@ impl PruningArgs { if let Some(mode) = self.account_history_prune_mode() { config.segments.account_history = Some(mode); } + if let Some(mode) = self.bodies_prune_mode(chain_spec) { + config.segments.bodies_history = Some(mode); + } if let Some(mode) = self.storage_history_prune_mode() { config.segments.storage_history = Some(mode); } @@ -140,6 +162,22 @@ impl PruningArgs { Some(config) } + fn bodies_prune_mode(&self, chain_spec: &ChainSpec) -> Option + where + ChainSpec: EthereumHardforks, + { + if self.bodies_pre_merge { + chain_spec + .ethereum_fork_activation(EthereumHardfork::Paris) + .block_number() + .map(PruneMode::Before) + } else if let Some(distance) = self.bodies_distance { + Some(PruneMode::Distance(distance)) + } else { + self.bodies_before.map(PruneMode::Before) + } + } + const fn sender_recovery_prune_mode(&self) -> Option { if self.sender_recovery_full { Some(PruneMode::Full) diff --git a/crates/node/core/src/node_config.rs b/crates/node/core/src/node_config.rs index e94256556cf..b1998110a33 100644 --- a/crates/node/core/src/node_config.rs +++ b/crates/node/core/src/node_config.rs @@ -14,7 +14,7 @@ use alloy_primitives::{BlockNumber, B256}; use eyre::eyre; use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET}; use reth_config::config::PruneConfig; -use reth_ethereum_forks::Head; +use reth_ethereum_forks::{EthereumHardforks, Head}; use reth_network_p2p::headers::client::HeadersClient; use reth_primitives_traits::SealedHeader; use reth_stages_types::StageId; @@ -288,8 +288,11 @@ impl NodeConfig { } /// Returns pruning configuration. - pub fn prune_config(&self) -> Option { - self.pruning.prune_config() + pub fn prune_config(&self) -> Option + where + ChainSpec: EthereumHardforks, + { + self.pruning.prune_config(&self.chain) } /// Returns the max block that the node should run to, looking it up from the network if diff --git a/crates/prune/prune/src/segments/set.rs b/crates/prune/prune/src/segments/set.rs index c99defe0841..52e6ee75442 100644 --- a/crates/prune/prune/src/segments/set.rs +++ b/crates/prune/prune/src/segments/set.rs @@ -64,6 +64,7 @@ where receipts, account_history, storage_history, + bodies_history: _, receipts_log_filter, } = prune_modes; diff --git a/crates/prune/prune/src/segments/static_file/transactions.rs b/crates/prune/prune/src/segments/static_file/transactions.rs index 7005ae15e7d..409e7f9b3d3 100644 --- a/crates/prune/prune/src/segments/static_file/transactions.rs +++ b/crates/prune/prune/src/segments/static_file/transactions.rs @@ -15,6 +15,7 @@ use reth_prune_types::{ use reth_static_file_types::StaticFileSegment; use tracing::trace; +/// The type responsible for pruning transactions in the database and history expiry. #[derive(Debug)] pub struct Transactions { static_file_provider: StaticFileProvider, diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index c0f9515fa60..d91faea0a11 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -75,6 +75,15 @@ pub struct PruneModes { ) )] pub storage_history: Option, + /// Bodies History pruning configuration. + #[cfg_attr( + any(test, feature = "serde"), + serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::" + ) + )] + pub bodies_history: Option, /// Receipts pruning configuration by retaining only those receipts that contain logs emitted /// by the specified addresses, discarding others. This setting is overridden by `receipts`. /// @@ -97,6 +106,7 @@ impl PruneModes { receipts: Some(PruneMode::Full), account_history: Some(PruneMode::Full), storage_history: Some(PruneMode::Full), + bodies_history: Some(PruneMode::Full), receipts_log_filter: Default::default(), } } diff --git a/crates/storage/nippy-jar/src/lib.rs b/crates/storage/nippy-jar/src/lib.rs index b4e39d709d8..f3d1944d3b4 100644 --- a/crates/storage/nippy-jar/src/lib.rs +++ b/crates/storage/nippy-jar/src/lib.rs @@ -240,6 +240,7 @@ impl NippyJar { [self.data_path().into(), self.index_path(), self.offsets_path(), self.config_path()] { if path.exists() { + debug!(target: "nippy-jar", ?path, "Removing file."); reth_fs_util::remove_file(path)?; } } diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 8c4f76bccba..1594941e903 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -50,9 +50,9 @@ use std::{ marker::PhantomData, ops::{Deref, Range, RangeBounds, RangeInclusive}, path::{Path, PathBuf}, - sync::{mpsc, Arc}, + sync::{atomic::AtomicU64, mpsc, Arc}, }; -use tracing::{info, trace, warn}; +use tracing::{debug, info, trace, warn}; /// Alias type for a map that can be queried for block ranges from a transaction /// segment respectively. It uses `TxNumber` to represent the transaction end of a static file @@ -229,6 +229,26 @@ pub struct StaticFileProviderInner { /// Maintains a map which allows for concurrent access to different `NippyJars`, over different /// segments and ranges. map: DashMap<(BlockNumber, StaticFileSegment), LoadedJar>, + /// Min static file block for each segment. + /// This index is initialized on launch to keep track of the lowest, non-expired static files. + /// + /// This tracks the lowest static file per segment together with the highest block in that + /// file. E.g. static file is batched in 500k block intervals then the lowest static file + /// is [0..499K], and the highest block is 499K. + /// This index is mainly used to History expiry, which targets transactions, e.g. pre-merge + /// history expiry would lead to removing all static files below the merge height. + static_files_min_block: RwLock>, + /// This is an additional index that tracks the expired height, this will track the highest + /// block number that has been expired (missing). The first, non expired block is + /// `expired_history_height + 1`. + /// + /// This is effecitvely the transaction range that has been expired: + /// [`StaticFileProvider::delete_transactions_below`] and mirrors + /// `static_files_min_block[transactions] - blocks_per_file`. + /// + /// This additional tracker exists for more efficient lookups because the node must be aware of + /// the expired height. + expired_history_height: AtomicU64, /// Max static file block for each segment static_files_max_block: RwLock>, /// Available static file block ranges on disk indexed by max transactions. @@ -261,6 +281,8 @@ impl StaticFileProviderInner { let provider = Self { map: Default::default(), writers: Default::default(), + static_files_min_block: Default::default(), + expired_history_height: Default::default(), static_files_max_block: Default::default(), static_files_tx_index: Default::default(), path: path.as_ref().to_path_buf(), @@ -422,26 +444,71 @@ impl StaticFileProvider { self.map.remove(&(fixed_block_range_end, segment)); } + /// This handles history expiry by deleting all transaction static files below the given block. + /// + /// For example if block is 1M and the blocks per file are 500K this will delete all individual + /// files below 1M, so 0-499K and 500K-999K. + /// + /// This will not delete the file that contains the block itself, because files can only be + /// removed entirely. + pub fn delete_transactions_below(&self, block: BlockNumber) -> ProviderResult<()> { + // Nothing to delete if block is 0. + if block == 0 { + return Ok(()) + } + + loop { + let Some(block_height) = + self.get_lowest_static_file_block(StaticFileSegment::Transactions) + else { + return Ok(()) + }; + + if block_height >= block { + return Ok(()) + } + + debug!( + target: "provider::static_file", + ?block_height, + "Deleting transaction static file below block" + ); + + // now we need to wipe the static file, this will take care of updating the index and + // advance the lowest tracked block height for the transactions segment. + self.delete_jar(StaticFileSegment::Transactions, block_height) + .inspect_err(|err| { + warn!( target: "provider::static_file", %block_height, ?err, "Failed to delete transaction static file below block") + }) + ?; + } + } + /// Given a segment and block, it deletes the jar and all files from the respective block range. /// /// CAUTION: destructive. Deletes files on disk. + /// + /// This will re-initialize the index after deletion, so all files are tracked. pub fn delete_jar(&self, segment: StaticFileSegment, block: BlockNumber) -> ProviderResult<()> { let fixed_block_range = self.find_fixed_range(block); let key = (fixed_block_range.end(), segment); let jar = if let Some((_, jar)) = self.map.remove(&key) { jar.jar } else { - NippyJar::::load(&self.path.join(segment.filename(&fixed_block_range))) - .map_err(ProviderError::other)? + let file = self.path.join(segment.filename(&fixed_block_range)); + debug!( + target: "provider::static_file", + ?file, + ?fixed_block_range, + ?block, + "Loading static file jar for deletion" + ); + NippyJar::::load(&file).map_err(ProviderError::other)? }; jar.delete().map_err(ProviderError::other)?; - let mut segment_max_block = None; - if fixed_block_range.start() > 0 { - segment_max_block = Some(fixed_block_range.start() - 1) - }; - self.update_index(segment, segment_max_block)?; + self.initialize_index()?; Ok(()) } @@ -597,16 +664,21 @@ impl StaticFileProvider { /// Initializes the inner transaction and block index pub fn initialize_index(&self) -> ProviderResult<()> { + let mut min_block = self.static_files_min_block.write(); let mut max_block = self.static_files_max_block.write(); let mut tx_index = self.static_files_tx_index.write(); + min_block.clear(); max_block.clear(); tx_index.clear(); for (segment, ranges) in iter_static_files(&self.path).map_err(ProviderError::other)? { - // Update last block for each segment - if let Some((block_range, _)) = ranges.last() { - max_block.insert(segment, block_range.end()); + // Update first and last block for each segment + if let Some((first_block_range, _)) = ranges.first() { + min_block.insert(segment, first_block_range.end()); + } + if let Some((last_block_range, _)) = ranges.last() { + max_block.insert(segment, last_block_range.end()); } // Update tx -> block_range index @@ -629,6 +701,11 @@ impl StaticFileProvider { // If this is a re-initialization, we need to clear this as well self.map.clear(); + // initialize the expired history height to the lowest static file block + if let Some(lowest_block) = min_block.get(&StaticFileSegment::Transactions) { + self.expired_history_height.store(*lowest_block, std::sync::atomic::Ordering::Relaxed); + } + Ok(()) } @@ -938,7 +1015,33 @@ impl StaticFileProvider { Ok(None) } - /// Gets the highest static file block if it exists for a static file segment. + /// Returns the highest block number that has been expired from the history (missing). + /// + /// The earliest block that is still available in the static files is `expired_history_height + + /// 1`. + pub fn expired_history_height(&self) -> BlockNumber { + self.expired_history_height.load(std::sync::atomic::Ordering::Relaxed) + } + + /// Gets the lowest transaction static file block if it exists. + /// + /// For example if the transactions static file has blocks 0-499, this will return 499.. + /// + /// If there is nothing on disk for the given segment, this will return [`None`]. + pub fn get_lowest_transaction_static_file_block(&self) -> Option { + self.get_lowest_static_file_block(StaticFileSegment::Transactions) + } + + /// Gets the lowest static file's block height if it exists for a static file segment. + /// + /// For example if the static file has blocks 0-499, this will return 499.. + /// + /// If there is nothing on disk for the given segment, this will return [`None`]. + pub fn get_lowest_static_file_block(&self, segment: StaticFileSegment) -> Option { + self.static_files_min_block.read().get(&segment).copied() + } + + /// Gets the highest static file's block height if it exists for a static file segment. /// /// If there is nothing on disk for the given segment, this will return [`None`]. pub fn get_highest_static_file_block(&self, segment: StaticFileSegment) -> Option { From f7b26ade339ec9379e3098785d1ee550ccb966f4 Mon Sep 17 00:00:00 2001 From: otc group Date: Tue, 24 Jun 2025 14:59:24 +0200 Subject: [PATCH 205/274] =?UTF-8?q?fix:=20correct=20typo=20=E2=80=9Creseip?= =?UTF-8?q?t=E2=80=9D=20=E2=86=92=20=E2=80=9Creceipt=E2=80=9D=20in=20serde?= =?UTF-8?q?=5Fbincode=5Fcompat=20tests=20(#17034)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/ethereum/primitives/src/receipt.rs | 4 ++-- crates/optimism/primitives/src/receipt.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/ethereum/primitives/src/receipt.rs b/crates/ethereum/primitives/src/receipt.rs index 4d947661f5c..2893c36159e 100644 --- a/crates/ethereum/primitives/src/receipt.rs +++ b/crates/ethereum/primitives/src/receipt.rs @@ -438,13 +438,13 @@ pub(super) mod serde_bincode_compat { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] struct Data { #[serde_as(as = "serde_bincode_compat::Receipt<'_>")] - reseipt: Receipt, + receipt: Receipt, } let mut bytes = [0u8; 1024]; rand::rng().fill(bytes.as_mut_slice()); let data = Data { - reseipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(), + receipt: Receipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(), }; let encoded = bincode::serialize(&data).unwrap(); let decoded: Data = bincode::deserialize(&encoded).unwrap(); diff --git a/crates/optimism/primitives/src/receipt.rs b/crates/optimism/primitives/src/receipt.rs index de9a777cb1d..f5f960a0034 100644 --- a/crates/optimism/primitives/src/receipt.rs +++ b/crates/optimism/primitives/src/receipt.rs @@ -602,17 +602,17 @@ pub(super) mod serde_bincode_compat { #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] struct Data { #[serde_as(as = "serde_bincode_compat::OpReceipt<'_>")] - reseipt: OpReceipt, + receipt: OpReceipt, } let mut bytes = [0u8; 1024]; rand::rng().fill(bytes.as_mut_slice()); let mut data = Data { - reseipt: OpReceipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(), + receipt: OpReceipt::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(), }; - let success = data.reseipt.as_receipt_mut().status.coerce_status(); + let success = data.receipt.as_receipt_mut().status.coerce_status(); // // ensure we don't have an invalid poststate variant - data.reseipt.as_receipt_mut().status = success.into(); + data.receipt.as_receipt_mut().status = success.into(); let encoded = bincode::serialize(&data).unwrap(); let decoded: Data = bincode::deserialize(&encoded).unwrap(); From 599de19fb391afe5257c5f7d1275396f6c639f18 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:30:56 +0530 Subject: [PATCH 206/274] chore(`book`): migrate to vocs (#16605) Co-authored-by: Claude Co-authored-by: Matthias Seitz --- .github/workflows/book.yml | 113 +--- .github/workflows/lint.yml | 5 - .github/workflows/unit.yml | 4 - .gitignore | 3 + book/SUMMARY.md | 84 --- book/cli/SUMMARY.md | 47 -- book/cli/help.rs | 104 ++-- book/cli/reth/debug/replay-engine.md | 332 ----------- book/cli/reth/import-op.md | 134 ----- book/cli/reth/import-receipts-op.md | 133 ----- book/cli/reth/test-vectors.md | 113 ---- book/cli/update.sh | 7 +- book/developers/contribute.md | 9 - book/developers/developers.md | 3 - book/installation/priorities.md | 18 - book/run/ports.md | 38 -- book/run/run-a-node.md | 15 - book/templates/source_and_github.md | 4 - book/theme/head.hbs | 5 - book/vocs/CLAUDE.md | 103 ++++ book/vocs/README.md | 1 + book/vocs/bun.lockb | Bin 0 -> 302056 bytes book/vocs/check-links.ts | 316 +++++++++++ book/vocs/docs/components/SdkShowcase.tsx | 88 +++ book/vocs/docs/components/TrustedBy.tsx | 49 ++ book/vocs/docs/pages/cli/SUMMARY.mdx | 47 ++ .../cli.md => vocs/docs/pages/cli/cli.mdx} | 4 +- book/{ => vocs/docs/pages}/cli/op-reth.md | 0 .../reth.md => vocs/docs/pages/cli/reth.mdx} | 0 .../docs/pages/cli/reth/config.mdx} | 0 .../db.md => vocs/docs/pages/cli/reth/db.mdx} | 0 .../docs/pages/cli/reth/db/checksum.mdx} | 0 .../docs/pages/cli/reth/db/clear.mdx} | 0 .../docs/pages/cli/reth/db/clear/mdbx.mdx} | 0 .../pages/cli/reth/db/clear/static-file.mdx} | 0 .../docs/pages/cli/reth/db/diff.mdx} | 0 .../docs/pages/cli/reth/db/drop.mdx} | 0 .../docs/pages/cli/reth/db/get.mdx} | 0 .../docs/pages/cli/reth/db/get/mdbx.mdx} | 0 .../pages/cli/reth/db/get/static-file.mdx} | 0 .../docs/pages/cli/reth/db/list.mdx} | 0 .../docs/pages/cli/reth/db/path.mdx} | 0 .../docs/pages/cli/reth/db/stats.mdx} | 0 .../docs/pages/cli/reth/db/version.mdx} | 0 .../docs/pages/cli/reth/debug.mdx} | 0 .../pages/cli/reth/debug/build-block.mdx} | 0 .../docs/pages/cli/reth/debug/execution.mdx} | 0 .../cli/reth/debug/in-memory-merkle.mdx} | 0 .../docs/pages/cli/reth/debug/merkle.mdx} | 0 .../docs/pages/cli/reth/download.mdx} | 0 .../docs/pages/cli/reth/dump-genesis.mdx} | 0 .../docs/pages/cli/reth/import-era.mdx} | 0 .../docs/pages/cli/reth/import.mdx} | 0 .../docs/pages/cli/reth/init-state.mdx} | 0 .../docs/pages/cli/reth/init.mdx} | 0 .../docs/pages/cli/reth/node.mdx} | 0 .../docs/pages/cli/reth/p2p.mdx} | 0 .../docs/pages/cli/reth/p2p/body.mdx} | 0 .../docs/pages/cli/reth/p2p/header.mdx} | 0 .../docs/pages/cli/reth/p2p/rlpx.mdx} | 0 .../docs/pages/cli/reth/p2p/rlpx/ping.mdx} | 0 .../docs/pages/cli/reth/prune.mdx} | 0 .../docs/pages/cli/reth/recover.mdx} | 0 .../pages/cli/reth/recover/storage-tries.mdx} | 0 .../docs/pages/cli/reth/stage.mdx} | 0 .../docs/pages/cli/reth/stage/drop.mdx} | 0 .../docs/pages/cli/reth/stage/dump.mdx} | 0 .../cli/reth/stage/dump/account-hashing.mdx} | 0 .../pages/cli/reth/stage/dump/execution.mdx} | 0 .../pages/cli/reth/stage/dump/merkle.mdx} | 0 .../cli/reth/stage/dump/storage-hashing.mdx} | 0 .../docs/pages/cli/reth/stage/run.mdx} | 0 .../docs/pages/cli/reth/stage/unwind.mdx} | 0 .../cli/reth/stage/unwind/num-blocks.mdx} | 0 .../pages/cli/reth/stage/unwind/to-block.mdx} | 0 .../pages/cli/reth/test-vectors/tables.mdx} | 0 .../docs/pages/exex/hello-world.mdx} | 30 +- .../docs/pages/exex/how-it-works.mdx} | 17 +- .../docs/pages/exex/overview.mdx} | 20 +- .../docs/pages/exex/remote.mdx} | 62 ++- .../docs/pages/exex/tracking-state.mdx} | 36 +- book/vocs/docs/pages/index.mdx | 162 ++++++ .../docs/pages/installation/binaries.mdx} | 6 +- .../installation/build-for-arm-devices.mdx} | 26 +- .../docs/pages/installation/docker.mdx} | 43 +- .../vocs/docs/pages/installation/overview.mdx | 18 + .../docs/pages/installation/priorities.mdx | 22 + .../docs/pages/installation/source.mdx} | 54 +- .../docs/pages/introduction/contributing.mdx | 258 +++++++++ .../vocs/docs/pages/introduction/why-reth.mdx | 50 ++ .../docs/pages/jsonrpc/admin.mdx} | 55 +- .../docs/pages/jsonrpc/debug.mdx} | 29 +- .../docs/pages/jsonrpc/eth.mdx} | 4 + .../docs/pages/jsonrpc/intro.mdx} | 33 +- .../docs/pages/jsonrpc/net.mdx} | 12 +- .../docs/pages/jsonrpc/rpc.mdx} | 6 +- .../docs/pages/jsonrpc/trace.mdx} | 68 +-- .../docs/pages/jsonrpc/txpool.mdx} | 14 +- .../docs/pages/jsonrpc/web3.mdx} | 11 +- .../docs/pages/overview.mdx} | 48 +- .../docs/pages/run/configuration.mdx} | 58 +- .../docs/pages/run/ethereum.mdx} | 44 +- .../docs/pages/run/ethereum/snapshots.mdx | 1 + book/vocs/docs/pages/run/faq.mdx | 11 + book/vocs/docs/pages/run/faq/ports.mdx | 42 ++ .../docs/pages/run/faq/profiling.mdx} | 66 ++- .../docs/pages/run/faq/pruning.mdx} | 56 +- .../docs/pages/run/faq/sync-op-mainnet.mdx} | 25 +- .../docs/pages/run/faq/transactions.mdx} | 4 + .../docs/pages/run/faq/troubleshooting.mdx} | 206 +++---- .../docs/pages/run/monitoring.mdx} | 24 +- book/vocs/docs/pages/run/networks.mdx | 1 + .../docs/pages/run/opstack.mdx} | 22 +- .../pages/run/opstack/op-mainnet-caveats.mdx | 1 + book/vocs/docs/pages/run/overview.mdx | 47 ++ .../docs/pages/run/private-testnets.mdx} | 44 +- .../docs/pages/run/system-requirements.mdx} | 55 +- .../pages/sdk/custom-node/modifications.mdx | 1 + .../pages/sdk/custom-node/prerequisites.mdx | 1 + .../docs/pages/sdk/examples/modify-node.mdx | 16 + .../sdk/examples/standalone-components.mdx | 12 + book/vocs/docs/pages/sdk/node-components.mdx | 112 ++++ .../pages/sdk/node-components/consensus.mdx | 45 ++ .../docs/pages/sdk/node-components/evm.mdx | 45 ++ .../pages/sdk/node-components/network.mdx | 55 ++ .../docs/pages/sdk/node-components/pool.mdx | 80 +++ .../docs/pages/sdk/node-components/rpc.mdx | 20 + book/vocs/docs/pages/sdk/overview.mdx | 127 +++++ book/vocs/docs/pages/sdk/typesystem/block.mdx | 26 + .../sdk/typesystem/transaction-types.mdx | 92 ++++ book/vocs/docs/public/alchemy.png | Bin 0 -> 27206 bytes book/vocs/docs/public/coinbase.png | Bin 0 -> 31453 bytes book/vocs/docs/public/flashbots.png | Bin 0 -> 38646 bytes book/vocs/docs/public/logo.png | Bin 0 -> 100250 bytes .../docs/public}/remote_exex.png | Bin book/vocs/docs/public/reth-prod.png | Bin 0 -> 324203 bytes book/vocs/docs/public/succinct.png | Bin 0 -> 2588 bytes .../docs/snippets}/sources/Cargo.toml | 0 .../sources/exex/hello-world/Cargo.toml | 0 .../sources/exex/hello-world/src/bin/1.rs | 0 .../sources/exex/hello-world/src/bin/2.rs | 0 .../sources/exex/hello-world/src/bin/3.rs | 0 .../snippets}/sources/exex/remote/Cargo.toml | 0 .../snippets}/sources/exex/remote/build.rs | 0 .../sources/exex/remote/proto/exex.proto | 0 .../sources/exex/remote/src/consumer.rs | 0 .../snippets}/sources/exex/remote/src/exex.rs | 0 .../sources/exex/remote/src/exex_1.rs | 0 .../sources/exex/remote/src/exex_2.rs | 0 .../sources/exex/remote/src/exex_3.rs | 0 .../sources/exex/remote/src/exex_4.rs | 0 .../snippets}/sources/exex/remote/src/lib.rs | 0 .../sources/exex/tracking-state/Cargo.toml | 0 .../sources/exex/tracking-state/src/bin/1.rs | 0 .../sources/exex/tracking-state/src/bin/2.rs | 0 book/vocs/docs/styles.css | 31 ++ book/vocs/generate-redirects.ts | 54 ++ book/vocs/links-report.json | 17 + book/vocs/package.json | 22 + book/vocs/redirects.config.ts | 27 + book/vocs/sidebar.ts | 514 ++++++++++++++++++ book/vocs/tsconfig.json | 24 + book/vocs/vocs.config.ts | 69 +++ 163 files changed, 3384 insertions(+), 1576 deletions(-) delete mode 100644 book/SUMMARY.md delete mode 100644 book/cli/SUMMARY.md delete mode 100644 book/cli/reth/debug/replay-engine.md delete mode 100644 book/cli/reth/import-op.md delete mode 100644 book/cli/reth/import-receipts-op.md delete mode 100644 book/cli/reth/test-vectors.md delete mode 100644 book/developers/contribute.md delete mode 100644 book/developers/developers.md delete mode 100644 book/installation/priorities.md delete mode 100644 book/run/ports.md delete mode 100644 book/run/run-a-node.md delete mode 100644 book/templates/source_and_github.md delete mode 100644 book/theme/head.hbs create mode 100644 book/vocs/CLAUDE.md create mode 100644 book/vocs/README.md create mode 100755 book/vocs/bun.lockb create mode 100644 book/vocs/check-links.ts create mode 100644 book/vocs/docs/components/SdkShowcase.tsx create mode 100644 book/vocs/docs/components/TrustedBy.tsx create mode 100644 book/vocs/docs/pages/cli/SUMMARY.mdx rename book/{cli/cli.md => vocs/docs/pages/cli/cli.mdx} (83%) rename book/{ => vocs/docs/pages}/cli/op-reth.md (100%) rename book/{cli/reth.md => vocs/docs/pages/cli/reth.mdx} (100%) rename book/{cli/reth/config.md => vocs/docs/pages/cli/reth/config.mdx} (100%) rename book/{cli/reth/db.md => vocs/docs/pages/cli/reth/db.mdx} (100%) rename book/{cli/reth/db/checksum.md => vocs/docs/pages/cli/reth/db/checksum.mdx} (100%) rename book/{cli/reth/db/clear.md => vocs/docs/pages/cli/reth/db/clear.mdx} (100%) rename book/{cli/reth/db/clear/mdbx.md => vocs/docs/pages/cli/reth/db/clear/mdbx.mdx} (100%) rename book/{cli/reth/db/clear/static-file.md => vocs/docs/pages/cli/reth/db/clear/static-file.mdx} (100%) rename book/{cli/reth/db/diff.md => vocs/docs/pages/cli/reth/db/diff.mdx} (100%) rename book/{cli/reth/db/drop.md => vocs/docs/pages/cli/reth/db/drop.mdx} (100%) rename book/{cli/reth/db/get.md => vocs/docs/pages/cli/reth/db/get.mdx} (100%) rename book/{cli/reth/db/get/mdbx.md => vocs/docs/pages/cli/reth/db/get/mdbx.mdx} (100%) rename book/{cli/reth/db/get/static-file.md => vocs/docs/pages/cli/reth/db/get/static-file.mdx} (100%) rename book/{cli/reth/db/list.md => vocs/docs/pages/cli/reth/db/list.mdx} (100%) rename book/{cli/reth/db/path.md => vocs/docs/pages/cli/reth/db/path.mdx} (100%) rename book/{cli/reth/db/stats.md => vocs/docs/pages/cli/reth/db/stats.mdx} (100%) rename book/{cli/reth/db/version.md => vocs/docs/pages/cli/reth/db/version.mdx} (100%) rename book/{cli/reth/debug.md => vocs/docs/pages/cli/reth/debug.mdx} (100%) rename book/{cli/reth/debug/build-block.md => vocs/docs/pages/cli/reth/debug/build-block.mdx} (100%) rename book/{cli/reth/debug/execution.md => vocs/docs/pages/cli/reth/debug/execution.mdx} (100%) rename book/{cli/reth/debug/in-memory-merkle.md => vocs/docs/pages/cli/reth/debug/in-memory-merkle.mdx} (100%) rename book/{cli/reth/debug/merkle.md => vocs/docs/pages/cli/reth/debug/merkle.mdx} (100%) rename book/{cli/reth/download.md => vocs/docs/pages/cli/reth/download.mdx} (100%) rename book/{cli/reth/dump-genesis.md => vocs/docs/pages/cli/reth/dump-genesis.mdx} (100%) rename book/{cli/reth/import-era.md => vocs/docs/pages/cli/reth/import-era.mdx} (100%) rename book/{cli/reth/import.md => vocs/docs/pages/cli/reth/import.mdx} (100%) rename book/{cli/reth/init-state.md => vocs/docs/pages/cli/reth/init-state.mdx} (100%) rename book/{cli/reth/init.md => vocs/docs/pages/cli/reth/init.mdx} (100%) rename book/{cli/reth/node.md => vocs/docs/pages/cli/reth/node.mdx} (100%) rename book/{cli/reth/p2p.md => vocs/docs/pages/cli/reth/p2p.mdx} (100%) rename book/{cli/reth/p2p/body.md => vocs/docs/pages/cli/reth/p2p/body.mdx} (100%) rename book/{cli/reth/p2p/header.md => vocs/docs/pages/cli/reth/p2p/header.mdx} (100%) rename book/{cli/reth/p2p/rlpx.md => vocs/docs/pages/cli/reth/p2p/rlpx.mdx} (100%) rename book/{cli/reth/p2p/rlpx/ping.md => vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx} (100%) rename book/{cli/reth/prune.md => vocs/docs/pages/cli/reth/prune.mdx} (100%) rename book/{cli/reth/recover.md => vocs/docs/pages/cli/reth/recover.mdx} (100%) rename book/{cli/reth/recover/storage-tries.md => vocs/docs/pages/cli/reth/recover/storage-tries.mdx} (100%) rename book/{cli/reth/stage.md => vocs/docs/pages/cli/reth/stage.mdx} (100%) rename book/{cli/reth/stage/drop.md => vocs/docs/pages/cli/reth/stage/drop.mdx} (100%) rename book/{cli/reth/stage/dump.md => vocs/docs/pages/cli/reth/stage/dump.mdx} (100%) rename book/{cli/reth/stage/dump/account-hashing.md => vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx} (100%) rename book/{cli/reth/stage/dump/execution.md => vocs/docs/pages/cli/reth/stage/dump/execution.mdx} (100%) rename book/{cli/reth/stage/dump/merkle.md => vocs/docs/pages/cli/reth/stage/dump/merkle.mdx} (100%) rename book/{cli/reth/stage/dump/storage-hashing.md => vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx} (100%) rename book/{cli/reth/stage/run.md => vocs/docs/pages/cli/reth/stage/run.mdx} (100%) rename book/{cli/reth/stage/unwind.md => vocs/docs/pages/cli/reth/stage/unwind.mdx} (100%) rename book/{cli/reth/stage/unwind/num-blocks.md => vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx} (100%) rename book/{cli/reth/stage/unwind/to-block.md => vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx} (100%) rename book/{cli/reth/test-vectors/tables.md => vocs/docs/pages/cli/reth/test-vectors/tables.mdx} (100%) rename book/{developers/exex/hello-world.md => vocs/docs/pages/exex/hello-world.mdx} (70%) rename book/{developers/exex/how-it-works.md => vocs/docs/pages/exex/how-it-works.mdx} (67%) rename book/{developers/exex/exex.md => vocs/docs/pages/exex/overview.mdx} (62%) rename book/{developers/exex/remote.md => vocs/docs/pages/exex/remote.mdx} (76%) rename book/{developers/exex/tracking-state.md => vocs/docs/pages/exex/tracking-state.mdx} (63%) create mode 100644 book/vocs/docs/pages/index.mdx rename book/{installation/binaries.md => vocs/docs/pages/installation/binaries.mdx} (90%) rename book/{installation/build-for-arm-devices.md => vocs/docs/pages/installation/build-for-arm-devices.mdx} (82%) rename book/{installation/docker.md => vocs/docs/pages/installation/docker.mdx} (80%) create mode 100644 book/vocs/docs/pages/installation/overview.mdx create mode 100644 book/vocs/docs/pages/installation/priorities.mdx rename book/{installation/source.md => vocs/docs/pages/installation/source.mdx} (72%) create mode 100644 book/vocs/docs/pages/introduction/contributing.mdx create mode 100644 book/vocs/docs/pages/introduction/why-reth.mdx rename book/{jsonrpc/admin.md => vocs/docs/pages/jsonrpc/admin.mdx} (79%) rename book/{jsonrpc/debug.md => vocs/docs/pages/jsonrpc/debug.mdx} (80%) rename book/{jsonrpc/eth.md => vocs/docs/pages/jsonrpc/eth.mdx} (72%) rename book/{jsonrpc/intro.md => vocs/docs/pages/jsonrpc/intro.mdx} (70%) rename book/{jsonrpc/net.md => vocs/docs/pages/jsonrpc/net.mdx} (82%) rename book/{jsonrpc/rpc.md => vocs/docs/pages/jsonrpc/rpc.mdx} (91%) rename book/{jsonrpc/trace.md => vocs/docs/pages/jsonrpc/trace.mdx} (86%) rename book/{jsonrpc/txpool.md => vocs/docs/pages/jsonrpc/txpool.mdx} (81%) rename book/{jsonrpc/web3.md => vocs/docs/pages/jsonrpc/web3.mdx} (83%) rename book/{intro.md => vocs/docs/pages/overview.mdx} (72%) rename book/{run/config.md => vocs/docs/pages/run/configuration.mdx} (90%) rename book/{run/mainnet.md => vocs/docs/pages/run/ethereum.mdx} (73%) create mode 100644 book/vocs/docs/pages/run/ethereum/snapshots.mdx create mode 100644 book/vocs/docs/pages/run/faq.mdx create mode 100644 book/vocs/docs/pages/run/faq/ports.mdx rename book/{developers/profiling.md => vocs/docs/pages/run/faq/profiling.mdx} (84%) rename book/{run/pruning.md => vocs/docs/pages/run/faq/pruning.mdx} (92%) rename book/{run/sync-op-mainnet.md => vocs/docs/pages/run/faq/sync-op-mainnet.mdx} (70%) rename book/{run/transactions.md => vocs/docs/pages/run/faq/transactions.mdx} (97%) rename book/{run/troubleshooting.md => vocs/docs/pages/run/faq/troubleshooting.mdx} (52%) rename book/{run/observability.md => vocs/docs/pages/run/monitoring.mdx} (92%) create mode 100644 book/vocs/docs/pages/run/networks.mdx rename book/{run/optimism.md => vocs/docs/pages/run/opstack.mdx} (95%) create mode 100644 book/vocs/docs/pages/run/opstack/op-mainnet-caveats.mdx create mode 100644 book/vocs/docs/pages/run/overview.mdx rename book/{run/private-testnet.md => vocs/docs/pages/run/private-testnets.mdx} (90%) rename book/{installation/installation.md => vocs/docs/pages/run/system-requirements.mdx} (68%) create mode 100644 book/vocs/docs/pages/sdk/custom-node/modifications.mdx create mode 100644 book/vocs/docs/pages/sdk/custom-node/prerequisites.mdx create mode 100644 book/vocs/docs/pages/sdk/examples/modify-node.mdx create mode 100644 book/vocs/docs/pages/sdk/examples/standalone-components.mdx create mode 100644 book/vocs/docs/pages/sdk/node-components.mdx create mode 100644 book/vocs/docs/pages/sdk/node-components/consensus.mdx create mode 100644 book/vocs/docs/pages/sdk/node-components/evm.mdx create mode 100644 book/vocs/docs/pages/sdk/node-components/network.mdx create mode 100644 book/vocs/docs/pages/sdk/node-components/pool.mdx create mode 100644 book/vocs/docs/pages/sdk/node-components/rpc.mdx create mode 100644 book/vocs/docs/pages/sdk/overview.mdx create mode 100644 book/vocs/docs/pages/sdk/typesystem/block.mdx create mode 100644 book/vocs/docs/pages/sdk/typesystem/transaction-types.mdx create mode 100644 book/vocs/docs/public/alchemy.png create mode 100644 book/vocs/docs/public/coinbase.png create mode 100644 book/vocs/docs/public/flashbots.png create mode 100644 book/vocs/docs/public/logo.png rename book/{developers/exex/assets => vocs/docs/public}/remote_exex.png (100%) create mode 100644 book/vocs/docs/public/reth-prod.png create mode 100644 book/vocs/docs/public/succinct.png rename book/{ => vocs/docs/snippets}/sources/Cargo.toml (100%) rename book/{ => vocs/docs/snippets}/sources/exex/hello-world/Cargo.toml (100%) rename book/{ => vocs/docs/snippets}/sources/exex/hello-world/src/bin/1.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/hello-world/src/bin/2.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/hello-world/src/bin/3.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/Cargo.toml (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/build.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/proto/exex.proto (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/consumer.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/exex.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/exex_1.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/exex_2.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/exex_3.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/exex_4.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/remote/src/lib.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/tracking-state/Cargo.toml (100%) rename book/{ => vocs/docs/snippets}/sources/exex/tracking-state/src/bin/1.rs (100%) rename book/{ => vocs/docs/snippets}/sources/exex/tracking-state/src/bin/2.rs (100%) create mode 100644 book/vocs/docs/styles.css create mode 100644 book/vocs/generate-redirects.ts create mode 100644 book/vocs/links-report.json create mode 100644 book/vocs/package.json create mode 100644 book/vocs/redirects.config.ts create mode 100644 book/vocs/sidebar.ts create mode 100644 book/vocs/tsconfig.json create mode 100644 book/vocs/vocs.config.ts diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 837d47e9f84..abc93f85c2b 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -7,115 +7,50 @@ on: branches: [main] pull_request: branches: [main] + types: [opened, reopened, synchronize, closed] merge_group: -jobs: - test: - runs-on: ubuntu-latest - name: test - timeout-minutes: 60 - - steps: - - uses: actions/checkout@v4 - - - name: Install mdbook - run: | - mkdir mdbook - curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook - echo $(pwd)/mdbook >> $GITHUB_PATH - - - name: Install mdbook-template - run: | - mkdir mdbook-template - curl -sSL https://github.com/sgoudham/mdbook-template/releases/latest/download/mdbook-template-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook-template - echo $(pwd)/mdbook-template >> $GITHUB_PATH - - - name: Run tests - run: mdbook test - - lint: - runs-on: ubuntu-latest - name: lint - timeout-minutes: 60 - - steps: - - uses: actions/checkout@v4 - - - name: Install mdbook-linkcheck - run: | - mkdir mdbook-linkcheck - curl -sSL -o mdbook-linkcheck.zip https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip - unzip mdbook-linkcheck.zip -d ./mdbook-linkcheck - chmod +x $(pwd)/mdbook-linkcheck/mdbook-linkcheck - echo $(pwd)/mdbook-linkcheck >> $GITHUB_PATH - - - name: Run linkcheck - run: mdbook-linkcheck --standalone +# Add concurrency to prevent conflicts when multiple PR previews are being deployed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} +jobs: build: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@nightly - - name: Install mdbook - run: | - mkdir mdbook - curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook - echo $(pwd)/mdbook >> $GITHUB_PATH + - name: Checkout + uses: actions/checkout@v4 - - name: Install mdbook-template - run: | - mkdir mdbook-template - curl -sSL https://github.com/sgoudham/mdbook-template/releases/latest/download/mdbook-template-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook-template - echo $(pwd)/mdbook-template >> $GITHUB_PATH - - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true + - name: Install bun + uses: oven-sh/setup-bun@v2 - - name: Build book - run: mdbook build - - - name: Build docs - run: cargo docs --exclude "example-*" - env: - # Keep in sync with ./ci.yml:jobs.docs - RUSTDOCFLAGS: --cfg docsrs --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options - - - name: Move docs to book folder + - name: Install Playwright browsers + # Required for rehype-mermaid to render Mermaid diagrams during build run: | - mv target/doc target/book/docs + cd book/vocs/ + bun i + npx playwright install --with-deps chromium - - name: Archive artifact - shell: sh + - name: Build Vocs run: | - chmod -c -R +rX "target/book" | - while read line; do - echo "::warning title=Invalid file permissions automatically fixed::$line" - done - tar \ - --dereference --hard-dereference \ - --directory "target/book" \ - -cvf "$RUNNER_TEMP/artifact.tar" \ - --exclude=.git \ - --exclude=.github \ - . + cd book/vocs/ && bun run build + echo "Vocs Build Complete" + + - name: Setup Pages + uses: actions/configure-pages@v5 - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-pages-artifact@v3 with: - name: github-pages - path: ${{ runner.temp }}/artifact.tar - retention-days: 1 - if-no-files-found: error + path: "./book/vocs/docs/dist" deploy: # Only deploy if a push to main if: github.ref_name == 'main' && github.event_name == 'push' runs-on: ubuntu-latest - needs: [test, lint, build] + needs: [build] # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ffa9f8edc30..7a167da8b19 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,9 +20,6 @@ jobs: - type: ethereum args: --workspace --lib --examples --tests --benches --locked features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs" - - type: book - args: --manifest-path book/sources/Cargo.toml --workspace --bins - features: "" steps: - uses: actions/checkout@v4 - uses: rui314/setup-mold@v1 @@ -158,8 +155,6 @@ jobs: components: rustfmt - name: Run fmt run: cargo fmt --all --check - - name: Run fmt on book sources - run: cargo fmt --manifest-path book/sources/Cargo.toml --all --check udeps: name: udeps diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 767a3e5c0ad..a46bf5bc3ca 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -42,10 +42,6 @@ jobs: args: --features "asm-keccak" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" partition: 2 total_partitions: 2 - - type: book - args: --manifest-path book/sources/Cargo.toml - partition: 1 - total_partitions: 1 timeout-minutes: 30 steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 1072d75dfaa..e4ca0420bad 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,8 @@ rustc-ice-* # Book sources should be able to build with the latest version book/sources/Cargo.lock +# vocs node_modules +book/vocs/node_modules + # Cargo chef recipe file recipe.json diff --git a/book/SUMMARY.md b/book/SUMMARY.md deleted file mode 100644 index 310eebb0285..00000000000 --- a/book/SUMMARY.md +++ /dev/null @@ -1,84 +0,0 @@ -# Reth Book - -- [Introduction](./intro.md) -- [Installation](./installation/installation.md) - - [Pre-Built Binaries](./installation/binaries.md) - - [Docker](./installation/docker.md) - - [Build from Source](./installation/source.md) - - [Build for ARM devices](./installation/build-for-arm-devices.md) - - [Update Priorities](./installation/priorities.md) -- [Run a Node](./run/run-a-node.md) - - [Mainnet or official testnets](./run/mainnet.md) - - [OP Stack](./run/optimism.md) - - [Run an OP Mainnet Node](./run/sync-op-mainnet.md) - - [Private testnet](./run/private-testnet.md) - - [Metrics](./run/observability.md) - - [Configuring Reth](./run/config.md) - - [Transaction types](./run/transactions.md) - - [Pruning & Full Node](./run/pruning.md) - - [Ports](./run/ports.md) - - [Troubleshooting](./run/troubleshooting.md) -- [Interacting with Reth over JSON-RPC](./jsonrpc/intro.md) - - [eth](./jsonrpc/eth.md) - - [web3](./jsonrpc/web3.md) - - [net](./jsonrpc/net.md) - - [txpool](./jsonrpc/txpool.md) - - [debug](./jsonrpc/debug.md) - - [trace](./jsonrpc/trace.md) - - [admin](./jsonrpc/admin.md) - - [rpc](./jsonrpc/rpc.md) -- [CLI Reference](./cli/cli.md) - - [`reth`](./cli/reth.md) - - [`reth node`](./cli/reth/node.md) - - [`reth init`](./cli/reth/init.md) - - [`reth init-state`](./cli/reth/init-state.md) - - [`reth import`](./cli/reth/import.md) - - [`reth import-era`](./cli/reth/import-era.md) - - [`reth dump-genesis`](./cli/reth/dump-genesis.md) - - [`reth db`](./cli/reth/db.md) - - [`reth db stats`](./cli/reth/db/stats.md) - - [`reth db list`](./cli/reth/db/list.md) - - [`reth db checksum`](./cli/reth/db/checksum.md) - - [`reth db diff`](./cli/reth/db/diff.md) - - [`reth db get`](./cli/reth/db/get.md) - - [`reth db get mdbx`](./cli/reth/db/get/mdbx.md) - - [`reth db get static-file`](./cli/reth/db/get/static-file.md) - - [`reth db drop`](./cli/reth/db/drop.md) - - [`reth db clear`](./cli/reth/db/clear.md) - - [`reth db clear mdbx`](./cli/reth/db/clear/mdbx.md) - - [`reth db clear static-file`](./cli/reth/db/clear/static-file.md) - - [`reth db version`](./cli/reth/db/version.md) - - [`reth db path`](./cli/reth/db/path.md) - - [`reth download`](./cli/reth/download.md) - - [`reth stage`](./cli/reth/stage.md) - - [`reth stage run`](./cli/reth/stage/run.md) - - [`reth stage drop`](./cli/reth/stage/drop.md) - - [`reth stage dump`](./cli/reth/stage/dump.md) - - [`reth stage dump execution`](./cli/reth/stage/dump/execution.md) - - [`reth stage dump storage-hashing`](./cli/reth/stage/dump/storage-hashing.md) - - [`reth stage dump account-hashing`](./cli/reth/stage/dump/account-hashing.md) - - [`reth stage dump merkle`](./cli/reth/stage/dump/merkle.md) - - [`reth stage unwind`](./cli/reth/stage/unwind.md) - - [`reth stage unwind to-block`](./cli/reth/stage/unwind/to-block.md) - - [`reth stage unwind num-blocks`](./cli/reth/stage/unwind/num-blocks.md) - - [`reth p2p`](./cli/reth/p2p.md) - - [`reth p2p header`](./cli/reth/p2p/header.md) - - [`reth p2p body`](./cli/reth/p2p/body.md) - - [`reth p2p rlpx`](./cli/reth/p2p/rlpx.md) - - [`reth p2p rlpx ping`](./cli/reth/p2p/rlpx/ping.md) - - [`reth config`](./cli/reth/config.md) - - [`reth debug`](./cli/reth/debug.md) - - [`reth debug execution`](./cli/reth/debug/execution.md) - - [`reth debug merkle`](./cli/reth/debug/merkle.md) - - [`reth debug in-memory-merkle`](./cli/reth/debug/in-memory-merkle.md) - - [`reth debug build-block`](./cli/reth/debug/build-block.md) - - [`reth recover`](./cli/reth/recover.md) - - [`reth recover storage-tries`](./cli/reth/recover/storage-tries.md) - - [`reth prune`](./cli/reth/prune.md) -- [Developers](./developers/developers.md) - - [Execution Extensions](./developers/exex/exex.md) - - [How do ExExes work?](./developers/exex/how-it-works.md) - - [Hello World](./developers/exex/hello-world.md) - - [Tracking State](./developers/exex/tracking-state.md) - - [Remote](./developers/exex/remote.md) - - [Contribute](./developers/contribute.md) diff --git a/book/cli/SUMMARY.md b/book/cli/SUMMARY.md deleted file mode 100644 index aa625298590..00000000000 --- a/book/cli/SUMMARY.md +++ /dev/null @@ -1,47 +0,0 @@ -- [`reth`](./reth.md) - - [`reth node`](./reth/node.md) - - [`reth init`](./reth/init.md) - - [`reth init-state`](./reth/init-state.md) - - [`reth import`](./reth/import.md) - - [`reth import-era`](./reth/import-era.md) - - [`reth dump-genesis`](./reth/dump-genesis.md) - - [`reth db`](./reth/db.md) - - [`reth db stats`](./reth/db/stats.md) - - [`reth db list`](./reth/db/list.md) - - [`reth db checksum`](./reth/db/checksum.md) - - [`reth db diff`](./reth/db/diff.md) - - [`reth db get`](./reth/db/get.md) - - [`reth db get mdbx`](./reth/db/get/mdbx.md) - - [`reth db get static-file`](./reth/db/get/static-file.md) - - [`reth db drop`](./reth/db/drop.md) - - [`reth db clear`](./reth/db/clear.md) - - [`reth db clear mdbx`](./reth/db/clear/mdbx.md) - - [`reth db clear static-file`](./reth/db/clear/static-file.md) - - [`reth db version`](./reth/db/version.md) - - [`reth db path`](./reth/db/path.md) - - [`reth download`](./reth/download.md) - - [`reth stage`](./reth/stage.md) - - [`reth stage run`](./reth/stage/run.md) - - [`reth stage drop`](./reth/stage/drop.md) - - [`reth stage dump`](./reth/stage/dump.md) - - [`reth stage dump execution`](./reth/stage/dump/execution.md) - - [`reth stage dump storage-hashing`](./reth/stage/dump/storage-hashing.md) - - [`reth stage dump account-hashing`](./reth/stage/dump/account-hashing.md) - - [`reth stage dump merkle`](./reth/stage/dump/merkle.md) - - [`reth stage unwind`](./reth/stage/unwind.md) - - [`reth stage unwind to-block`](./reth/stage/unwind/to-block.md) - - [`reth stage unwind num-blocks`](./reth/stage/unwind/num-blocks.md) - - [`reth p2p`](./reth/p2p.md) - - [`reth p2p header`](./reth/p2p/header.md) - - [`reth p2p body`](./reth/p2p/body.md) - - [`reth p2p rlpx`](./reth/p2p/rlpx.md) - - [`reth p2p rlpx ping`](./reth/p2p/rlpx/ping.md) - - [`reth config`](./reth/config.md) - - [`reth debug`](./reth/debug.md) - - [`reth debug execution`](./reth/debug/execution.md) - - [`reth debug merkle`](./reth/debug/merkle.md) - - [`reth debug in-memory-merkle`](./reth/debug/in-memory-merkle.md) - - [`reth debug build-block`](./reth/debug/build-block.md) - - [`reth recover`](./reth/recover.md) - - [`reth recover storage-tries`](./reth/recover/storage-tries.md) - - [`reth prune`](./reth/prune.md) diff --git a/book/cli/help.rs b/book/cli/help.rs index 963f53deb0a..e97d0bbfc46 100755 --- a/book/cli/help.rs +++ b/book/cli/help.rs @@ -10,25 +10,28 @@ regex = "1" --- use clap::Parser; use regex::Regex; -use std::borrow::Cow; -use std::fs::{self, File}; -use std::io::{self, Write}; -use std::iter::once; -use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; -use std::str; -use std::sync::LazyLock; -use std::{fmt, process}; - -const SECTION_START: &str = ""; -const SECTION_END: &str = ""; -const README: &str = r#"# CLI Reference - - +use std::{ + borrow::Cow, + fmt, + fs::{self, File}, + io::{self, Write}, + iter::once, + path::{Path, PathBuf}, + process, + process::{Command, Stdio}, + str, + sync::LazyLock, +}; + +const SECTION_START: &str = "{/* CLI_REFERENCE START */}"; +const SECTION_END: &str = "{/* CLI_REFERENCE END */"; +const README: &str = r#"import Summary from './SUMMARY.mdx'; + +# CLI Reference Automatically-generated CLI reference from `--help` output. -{{#include ./SUMMARY.md}} +

"#; const TRIM_LINE_END_MARKDOWN: bool = true; @@ -49,7 +52,7 @@ struct Args { #[arg(long, default_value_t = String::from("."))] root_dir: String, - /// Indentation for the root SUMMARY.md file + /// Indentation for the root SUMMARY.mdx file #[arg(long, default_value_t = 2)] root_indentation: usize, @@ -61,7 +64,7 @@ struct Args { #[arg(long)] readme: bool, - /// Whether to update the root SUMMARY.md file + /// Whether to update the root SUMMARY.mdx file #[arg(long)] root_summary: bool, @@ -76,11 +79,7 @@ struct Args { fn write_file(file_path: &Path, content: &str) -> io::Result<()> { let content = if TRIM_LINE_END_MARKDOWN { - content - .lines() - .map(|line| line.trim_end()) - .collect::>() - .join("\n") + content.lines().map(|line| line.trim_end()).collect::>().join("\n") } else { content.to_string() }; @@ -106,25 +105,13 @@ fn main() -> io::Result<()> { while let Some(cmd) = todo_iter.pop() { let (new_subcmds, stdout) = get_entry(&cmd)?; if args.verbose && !new_subcmds.is_empty() { - println!( - "Found subcommands for \"{}\": {:?}", - cmd.command_name(), - new_subcmds - ); + println!("Found subcommands for \"{}\": {:?}", cmd.command_name(), new_subcmds); } // Add new subcommands to todo_iter (so that they are processed in the correct order). for subcmd in new_subcmds.into_iter().rev() { - let new_subcmds: Vec<_> = cmd - .subcommands - .iter() - .cloned() - .chain(once(subcmd)) - .collect(); - - todo_iter.push(Cmd { - cmd: cmd.cmd, - subcommands: new_subcmds, - }); + let new_subcmds: Vec<_> = cmd.subcommands.iter().cloned().chain(once(subcmd)).collect(); + + todo_iter.push(Cmd { cmd: cmd.cmd, subcommands: new_subcmds }); } output.push((cmd, stdout)); } @@ -134,25 +121,25 @@ fn main() -> io::Result<()> { cmd_markdown(&out_dir, cmd, stdout)?; } - // Generate SUMMARY.md. + // Generate SUMMARY.mdx. let summary: String = output .iter() .map(|(cmd, _)| cmd_summary(None, cmd, 0)) .chain(once("\n".to_string())) .collect(); - write_file(&out_dir.clone().join("SUMMARY.md"), &summary)?; + write_file(&out_dir.clone().join("SUMMARY.mdx"), &summary)?; // Generate README.md. if args.readme { - let path = &out_dir.join("README.md"); + let path = &out_dir.join("README.mdx"); if args.verbose { - println!("Writing README.md to \"{}\"", path.to_string_lossy()); + println!("Writing README.mdx to \"{}\"", path.to_string_lossy()); } write_file(path, README)?; } - // Generate root SUMMARY.md. + // Generate root SUMMARY.mdx. if args.root_summary { let root_summary: String = output .iter() @@ -166,7 +153,8 @@ fn main() -> io::Result<()> { if args.verbose { println!("Updating root summary in \"{}\"", path.to_string_lossy()); } - update_root_summary(path, &root_summary)?; + // TODO: This is where we update the cli reference sidebar.ts + // update_root_summary(path, &root_summary)?; } Ok(()) @@ -213,8 +201,7 @@ fn parse_sub_commands(s: &str) -> Vec { .lines() .take_while(|line| !line.starts_with("Options:") && !line.starts_with("Arguments:")) .filter_map(|line| { - re.captures(line) - .and_then(|cap| cap.get(1).map(|m| m.as_str().to_string())) + re.captures(line).and_then(|cap| cap.get(1).map(|m| m.as_str().to_string())) }) .filter(|cmd| cmd != "help") .map(String::from) @@ -229,7 +216,7 @@ fn cmd_markdown(out_dir: &Path, cmd: &Cmd, stdout: &str) -> io::Result<()> { let out_path = out_dir.join(cmd.to_string().replace(" ", "/")); fs::create_dir_all(out_path.parent().unwrap())?; - write_file(&out_path.with_extension("md"), &out)?; + write_file(&out_path.with_extension("mdx"), &out)?; Ok(()) } @@ -265,12 +252,12 @@ fn cmd_summary(md_root: Option, cmd: &Cmd, indent: usize) -> String { Some(md_root) => format!("{}/{}", md_root.to_string_lossy(), cmd_path), }; let indent_string = " ".repeat(indent + (cmd.subcommands.len() * 2)); - format!("{}- [`{}`](./{}.md)\n", indent_string, cmd_s, full_cmd_path) + format!("{}- [`{}`](/cli/{})\n", indent_string, cmd_s, full_cmd_path) } -/// Replaces the CLI_REFERENCE section in the root SUMMARY.md file. +/// Replaces the CLI_REFERENCE section in the root SUMMARY.mdx file. fn update_root_summary(root_dir: &Path, root_summary: &str) -> io::Result<()> { - let summary_file = root_dir.join("SUMMARY.md"); + let summary_file = root_dir.join("SUMMARY.mdx"); let original_summary_content = fs::read_to_string(&summary_file)?; let section_re = regex!(&format!(r"(?s)\s*{SECTION_START}.*?{SECTION_END}")); @@ -293,9 +280,8 @@ fn update_root_summary(root_dir: &Path, root_summary: &str) -> io::Result<()> { let root_summary_s = root_summary.trim_end().replace("\n\n", "\n"); let replace_with = format!(" {}\n{}\n{}", SECTION_START, root_summary_s, last_line); - let new_root_summary = section_re - .replace(&original_summary_content, replace_with.as_str()) - .to_string(); + let new_root_summary = + section_re.replace(&original_summary_content, replace_with.as_str()).to_string(); let mut root_summary_file = File::create(&summary_file)?; root_summary_file.write_all(new_root_summary.as_bytes()) @@ -349,17 +335,11 @@ struct Cmd<'a> { impl<'a> Cmd<'a> { fn command_name(&self) -> &str { - self.cmd - .file_name() - .and_then(|os_str| os_str.to_str()) - .expect("Expect valid command") + self.cmd.file_name().and_then(|os_str| os_str.to_str()).expect("Expect valid command") } fn new(cmd: &'a PathBuf) -> Self { - Self { - cmd, - subcommands: Vec::new(), - } + Self { cmd, subcommands: Vec::new() } } } diff --git a/book/cli/reth/debug/replay-engine.md b/book/cli/reth/debug/replay-engine.md deleted file mode 100644 index da36f11cc0e..00000000000 --- a/book/cli/reth/debug/replay-engine.md +++ /dev/null @@ -1,332 +0,0 @@ -# reth debug replay-engine - -Debug engine API by replaying stored messages - -```bash -$ reth debug replay-engine --help -``` -```txt -Usage: reth debug replay-engine [OPTIONS] --engine-api-store - -Options: - --instance - Add a new instance of a node. - - Configures the ports of the node to avoid conflicts with the defaults. This is useful for running multiple nodes on the same machine. - - Max number of instances is 200. It is chosen in a way so that it's not possible to have port numbers that conflict with each other. - - Changes to the following port numbers: - `DISCOVERY_PORT`: default + `instance` - 1 - `AUTH_PORT`: default + `instance` * 100 - 100 - `HTTP_RPC_PORT`: default - `instance` + 1 - `WS_RPC_PORT`: default + `instance` * 2 - 2 - - [default: 1] - - -h, --help - Print help (see a summary with '-h') - -Datadir: - --datadir - The path to the data dir for all reth files and subdirectories. - - Defaults to the OS-specific data directory: - - - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` - - Windows: `{FOLDERID_RoamingAppData}/reth/` - - macOS: `$HOME/Library/Application Support/reth/` - - [default: default] - - --datadir.static-files - The absolute path to store static files in. - - --config - The path to the configuration file to use - - --chain - The chain this node is running. - Possible values are either a built-in chain or the path to a chain specification file. - - Built-in chains: - mainnet, sepolia, holesky, dev - - [default: mainnet] - -Database: - --db.log-level - Database logging level. Levels higher than "notice" require a debug build - - Possible values: - - fatal: Enables logging for critical conditions, i.e. assertion failures - - error: Enables logging for error conditions - - warn: Enables logging for warning conditions - - notice: Enables logging for normal but significant condition - - verbose: Enables logging for verbose informational - - debug: Enables logging for debug-level messages - - trace: Enables logging for trace debug-level messages - - extra: Enables logging for extra debug-level messages - - --db.exclusive - Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an NFS volume - - [possible values: true, false] - - --db.max-size - Maximum database size (e.g., 4TB, 8MB) - - --db.growth-step - Database growth step (e.g., 4GB, 4KB) - - --db.read-transaction-timeout - Read transaction timeout in seconds, 0 means no timeout - -Networking: - -d, --disable-discovery - Disable the discovery service - - --disable-dns-discovery - Disable the DNS discovery - - --disable-discv4-discovery - Disable Discv4 discovery - - --enable-discv5-discovery - Enable Discv5 discovery - - --disable-nat - Disable Nat discovery - - --discovery.addr - The UDP address to use for devp2p peer discovery version 4 - - [default: 0.0.0.0] - - --discovery.port - The UDP port to use for devp2p peer discovery version 4 - - [default: 30303] - - --discovery.v5.addr - The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx` address, if it's also IPv4 - - --discovery.v5.addr.ipv6 - The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx` address, if it's also IPv6 - - --discovery.v5.port - The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv4, or `--discovery.v5.addr` is set - - [default: 9200] - - --discovery.v5.port.ipv6 - The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set - - [default: 9200] - - --discovery.v5.lookup-interval - The interval in seconds at which to carry out periodic lookup queries, for the whole run of the program - - [default: 20] - - --discovery.v5.bootstrap.lookup-interval - The interval in seconds at which to carry out boost lookup queries, for a fixed number of times, at bootstrap - - [default: 5] - - --discovery.v5.bootstrap.lookup-countdown - The number of times to carry out boost lookup queries at bootstrap - - [default: 200] - - --trusted-peers - Comma separated enode URLs of trusted peers for P2P connections. - - --trusted-peers enode://abcd@192.168.0.1:30303 - - --trusted-only - Connect to or accept from trusted peers only - - --bootnodes - Comma separated enode URLs for P2P discovery bootstrap. - - Will fall back to a network-specific default if not specified. - - --dns-retries - Amount of DNS resolution requests retries to perform when peering - - [default: 0] - - --peers-file - The path to the known peers file. Connected peers are dumped to this file on nodes - shutdown, and read on startup. Cannot be used with `--no-persist-peers`. - - --identity - Custom node identity - - [default: reth/-/] - - --p2p-secret-key - Secret key to use for this node. - - This will also deterministically set the peer ID. If not specified, it will be set in the data dir for the chain being used. - - --no-persist-peers - Do not persist peers. - - --nat - NAT resolution method (any|none|upnp|publicip|extip:\) - - [default: any] - - --addr - Network listening address - - [default: 0.0.0.0] - - --port - Network listening port - - [default: 30303] - - --max-outbound-peers - Maximum number of outbound requests. default: 100 - - --max-inbound-peers - Maximum number of inbound requests. default: 30 - - --max-tx-reqs - Max concurrent `GetPooledTransactions` requests. - - [default: 130] - - --max-tx-reqs-peer - Max concurrent `GetPooledTransactions` requests per peer. - - [default: 1] - - --max-seen-tx-history - Max number of seen transactions to remember per peer. - - Default is 320 transaction hashes. - - [default: 320] - - --max-pending-imports - Max number of transactions to import concurrently. - - [default: 4096] - - --pooled-tx-response-soft-limit - Experimental, for usage in research. Sets the max accumulated byte size of transactions - to pack in one response. - Spec'd at 2MiB. - - [default: 2097152] - - --pooled-tx-pack-soft-limit - Experimental, for usage in research. Sets the max accumulated byte size of transactions to - request in one request. - - Since `RLPx` protocol version 68, the byte size of a transaction is shared as metadata in a - transaction announcement (see `RLPx` specs). This allows a node to request a specific size - response. - - By default, nodes request only 128 KiB worth of transactions, but should a peer request - more, up to 2 MiB, a node will answer with more than 128 KiB. - - Default is 128 KiB. - - [default: 131072] - - --max-tx-pending-fetch - Max capacity of cache of hashes for transactions pending fetch. - - [default: 25600] - - --net-if.experimental - Name of network interface used to communicate with peers. - - If flag is set, but no value is passed, the default interface for docker `eth0` is tried. - - --engine-api-store - The path to read engine API messages from - - --interval - The number of milliseconds between Engine API messages - - [default: 1000] - -Logging: - --log.stdout.format - The format to use for logs written to stdout - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.stdout.filter - The filter to use for logs written to stdout - - [default: ] - - --log.file.format - The format to use for logs written to the log file - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.file.filter - The filter to use for logs written to the log file - - [default: debug] - - --log.file.directory - The path to put log files in - - [default: /logs] - - --log.file.max-size - The maximum size (in MB) of one log file - - [default: 200] - - --log.file.max-files - The maximum amount of log files that will be stored. If set to 0, background file logging is disabled - - [default: 5] - - --log.journald - Write logs to journald - - --log.journald.filter - The filter to use for logs written to journald - - [default: error] - - --color - Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting - - [default: always] - - Possible values: - - always: Colors on - - auto: Colors on - - never: Colors off - -Display: - -v, --verbosity... - Set the minimum log level. - - -v Errors - -vv Warnings - -vvv Info - -vvvv Debug - -vvvvv Traces (warning: very verbose!) - - -q, --quiet - Silence all log output -``` \ No newline at end of file diff --git a/book/cli/reth/import-op.md b/book/cli/reth/import-op.md deleted file mode 100644 index d2d81980ce3..00000000000 --- a/book/cli/reth/import-op.md +++ /dev/null @@ -1,134 +0,0 @@ -# op-reth import - -This syncs RLP encoded blocks from a file. Supports import of OVM blocks -from the Bedrock datadir. Requires blocks, up to same height as receipts -file, to already be imported. - -```bash -$ op-reth import-op --help -Usage: op-reth import-op [OPTIONS] - -Options: - --config - The path to the configuration file to use. - - --datadir - The path to the data dir for all reth files and subdirectories. - - Defaults to the OS-specific data directory: - - - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` - - Windows: `{FOLDERID_RoamingAppData}/reth/` - - macOS: `$HOME/Library/Application Support/reth/` - - [default: default] - - --chunk-len - Chunk byte length to read from file. - - [default: 1GB] - - -h, --help - Print help (see a summary with '-h') - -Database: - --db.log-level - Database logging level. Levels higher than "notice" require a debug build - - Possible values: - - fatal: Enables logging for critical conditions, i.e. assertion failures - - error: Enables logging for error conditions - - warn: Enables logging for warning conditions - - notice: Enables logging for normal but significant condition - - verbose: Enables logging for verbose informational - - debug: Enables logging for debug-level messages - - trace: Enables logging for trace debug-level messages - - extra: Enables logging for extra debug-level messages - - --db.exclusive - Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an NFS volume - - [possible values: true, false] - - - The path to a `.rlp` block file for import. - - The online sync pipeline stages (headers and bodies) are replaced by a file import. Skips block execution since blocks below Bedrock are built on OVM. - -Logging: - --log.stdout.format - The format to use for logs written to stdout - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.stdout.filter - The filter to use for logs written to stdout - - [default: ] - - --log.file.format - The format to use for logs written to the log file - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.file.filter - The filter to use for logs written to the log file - - [default: debug] - - --log.file.directory - The path to put log files in - - [default: /logs] - - --log.file.max-size - The maximum size (in MB) of one log file - - [default: 200] - - --log.file.max-files - The maximum amount of log files that will be stored. If set to 0, background file logging is disabled - - [default: 5] - - --log.journald - Write logs to journald - - --log.journald.filter - The filter to use for logs written to journald - - [default: error] - - --color - Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting - - [default: always] - - Possible values: - - always: Colors on - - auto: Colors on - - never: Colors off - -Display: - -v, --verbosity... - Set the minimum log level. - - -v Errors - -vv Warnings - -vvv Info - -vvvv Debug - -vvvvv Traces (warning: very verbose!) - - -q, --quiet - Silence all log output -``` \ No newline at end of file diff --git a/book/cli/reth/import-receipts-op.md b/book/cli/reth/import-receipts-op.md deleted file mode 100644 index 0b7135e1d7a..00000000000 --- a/book/cli/reth/import-receipts-op.md +++ /dev/null @@ -1,133 +0,0 @@ -# op-reth import-receipts-op - -This imports non-standard RLP encoded receipts from a file. -The supported RLP encoding, is the non-standard encoding used -for receipt export in . -Supports import of OVM receipts from the Bedrock datadir. - -```bash -$ op-reth import-receipts-op --help -Usage: op-reth import-receipts-op [OPTIONS] - -Options: - --datadir - The path to the data dir for all reth files and subdirectories. - - Defaults to the OS-specific data directory: - - - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` - - Windows: `{FOLDERID_RoamingAppData}/reth/` - - macOS: `$HOME/Library/Application Support/reth/` - - [default: default] - - --chunk-len - Chunk byte length to read from file. - - [default: 1GB] - - -h, --help - Print help (see a summary with '-h') - -Database: - --db.log-level - Database logging level. Levels higher than "notice" require a debug build - - Possible values: - - fatal: Enables logging for critical conditions, i.e. assertion failures - - error: Enables logging for error conditions - - warn: Enables logging for warning conditions - - notice: Enables logging for normal but significant condition - - verbose: Enables logging for verbose informational - - debug: Enables logging for debug-level messages - - trace: Enables logging for trace debug-level messages - - extra: Enables logging for extra debug-level messages - - --db.exclusive - Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an NFS volume - - [possible values: true, false] - - - The path to a receipts file for import. File must use `OpGethReceiptFileCodec` (used for - exporting OP chain segment below Bedrock block via testinprod/op-geth). - - - -Logging: - --log.stdout.format - The format to use for logs written to stdout - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.stdout.filter - The filter to use for logs written to stdout - - [default: ] - - --log.file.format - The format to use for logs written to the log file - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.file.filter - The filter to use for logs written to the log file - - [default: debug] - - --log.file.directory - The path to put log files in - - [default: /logs] - - --log.file.max-size - The maximum size (in MB) of one log file - - [default: 200] - - --log.file.max-files - The maximum amount of log files that will be stored. If set to 0, background file logging is disabled - - [default: 5] - - --log.journald - Write logs to journald - - --log.journald.filter - The filter to use for logs written to journald - - [default: error] - - --color - Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting - - [default: always] - - Possible values: - - always: Colors on - - auto: Colors on - - never: Colors off - -Display: - -v, --verbosity... - Set the minimum log level. - - -v Errors - -vv Warnings - -vvv Info - -vvvv Debug - -vvvvv Traces (warning: very verbose!) - - -q, --quiet - Silence all log output -``` \ No newline at end of file diff --git a/book/cli/reth/test-vectors.md b/book/cli/reth/test-vectors.md deleted file mode 100644 index 844c5ed8455..00000000000 --- a/book/cli/reth/test-vectors.md +++ /dev/null @@ -1,113 +0,0 @@ -# reth test-vectors - -Generate Test Vectors - -```bash -$ reth test-vectors --help -Usage: reth test-vectors [OPTIONS] - -Commands: - tables Generates test vectors for specified tables. If no table is specified, generate for all - help Print this message or the help of the given subcommand(s) - -Options: - --chain - The chain this node is running. - Possible values are either a built-in chain or the path to a chain specification file. - - Built-in chains: - mainnet, sepolia, holesky, dev - - [default: mainnet] - - --instance - Add a new instance of a node. - - Configures the ports of the node to avoid conflicts with the defaults. This is useful for running multiple nodes on the same machine. - - Max number of instances is 200. It is chosen in a way so that it's not possible to have port numbers that conflict with each other. - - Changes to the following port numbers: - `DISCOVERY_PORT`: default + `instance` - 1 - `AUTH_PORT`: default + `instance` * 100 - 100 - `HTTP_RPC_PORT`: default - `instance` + 1 - `WS_RPC_PORT`: default + `instance` * 2 - 2 - - [default: 1] - - -h, --help - Print help (see a summary with '-h') - -Logging: - --log.stdout.format - The format to use for logs written to stdout - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.stdout.filter - The filter to use for logs written to stdout - - [default: ] - - --log.file.format - The format to use for logs written to the log file - - [default: terminal] - - Possible values: - - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging - - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications - - terminal: Represents terminal-friendly formatting for logs - - --log.file.filter - The filter to use for logs written to the log file - - [default: debug] - - --log.file.directory - The path to put log files in - - [default: /logs] - - --log.file.max-size - The maximum size (in MB) of one log file - - [default: 200] - - --log.file.max-files - The maximum amount of log files that will be stored. If set to 0, background file logging is disabled - - [default: 5] - - --log.journald - Write logs to journald - - --log.journald.filter - The filter to use for logs written to journald - - [default: error] - - --color - Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting - - [default: always] - - Possible values: - - always: Colors on - - auto: Colors on - - never: Colors off - -Display: - -v, --verbosity... - Set the minimum log level. - - -v Errors - -vv Warnings - -vvv Info - -vvvv Debug - -vvvvv Traces (warning: very verbose!) - - -q, --quiet - Silence all log output -``` \ No newline at end of file diff --git a/book/cli/update.sh b/book/cli/update.sh index 6e792df0f2b..01593bfb794 100755 --- a/book/cli/update.sh +++ b/book/cli/update.sh @@ -3,13 +3,18 @@ set -eo pipefail BOOK_ROOT="$(dirname "$(dirname "$0")")" RETH=${1:-"$(dirname "$BOOK_ROOT")/target/debug/reth"} +VOCS_PAGES_ROOT="$BOOK_ROOT/vocs/docs/pages" +echo "Generating CLI documentation for reth at $RETH" +echo "Using book root: $BOOK_ROOT" +echo "Using vocs pages root: $VOCS_PAGES_ROOT" cmd=( "$(dirname "$0")/help.rs" --root-dir "$BOOK_ROOT/" --root-indentation 2 --root-summary - --out-dir "$BOOK_ROOT/cli/" + --verbose + --out-dir "$VOCS_PAGES_ROOT/cli/" "$RETH" ) echo "Running: $" "${cmd[*]}" diff --git a/book/developers/contribute.md b/book/developers/contribute.md deleted file mode 100644 index 74f00e69a1a..00000000000 --- a/book/developers/contribute.md +++ /dev/null @@ -1,9 +0,0 @@ -# Contribute - - - -Reth has docs specifically geared for developers and contributors, including documentation on the structure and architecture of reth, the general workflow we employ, and other useful tips. - -You can find these docs [here](https://github.com/paradigmxyz/reth/tree/main/docs). - -Check out our contributing guidelines [here](https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md). diff --git a/book/developers/developers.md b/book/developers/developers.md deleted file mode 100644 index 9d8c5a9c673..00000000000 --- a/book/developers/developers.md +++ /dev/null @@ -1,3 +0,0 @@ -# Developers - -Reth is composed of several crates that can be used in standalone projects. If you are interested in using one or more of the crates, you can get an overview of them in the [developer docs](https://github.com/paradigmxyz/reth/tree/main/docs), or take a look at the [crate docs](https://paradigmxyz.github.io/reth/docs). diff --git a/book/installation/priorities.md b/book/installation/priorities.md deleted file mode 100644 index f7444e79d63..00000000000 --- a/book/installation/priorities.md +++ /dev/null @@ -1,18 +0,0 @@ -# Update Priorities - -When publishing releases, reth will include an "Update Priority" section in the release notes, in the same manner Lighthouse does. - -The "Update Priority" section will include a table which may appear like so: - -| User Class | Priority | -|----------------------|-----------------| -| Payload Builders | Medium Priority | -| Non-Payload Builders | Low Priority | - -To understand this table, the following terms are important: - -- *Payload builders* are those who use reth to build and validate payloads. -- *Non-payload builders* are those who run reth for other purposes (e.g., data analysis, RPC or applications). -- *High priority* updates should be completed as soon as possible (e.g., hours or days). -- *Medium priority* updates should be completed at the next convenience (e.g., days or a week). -- *Low priority* updates should be completed in the next routine update cycle (e.g., two weeks). diff --git a/book/run/ports.md b/book/run/ports.md deleted file mode 100644 index 5239a5262c4..00000000000 --- a/book/run/ports.md +++ /dev/null @@ -1,38 +0,0 @@ -# Ports - -This section provides essential information about the ports used by the system, their primary purposes, and recommendations for exposure settings. - -## Peering Ports - -- **Port:** 30303 -- **Protocol:** TCP and UDP -- **Purpose:** Peering with other nodes for synchronization of blockchain data. Nodes communicate through this port to maintain network consensus and share updated information. -- **Exposure Recommendation:** This port should be exposed to enable seamless interaction and synchronization with other nodes in the network. - -## Metrics Port - -- **Port:** 9001 -- **Protocol:** TCP -- **Purpose:** This port is designated for serving metrics related to the system's performance and operation. It allows internal monitoring and data collection for analysis. -- **Exposure Recommendation:** By default, this port should not be exposed to the public. It is intended for internal monitoring and analysis purposes. - -## HTTP RPC Port - -- **Port:** 8545 -- **Protocol:** TCP -- **Purpose:** Port 8545 provides an HTTP-based Remote Procedure Call (RPC) interface. It enables external applications to interact with the blockchain by sending requests over HTTP. -- **Exposure Recommendation:** Similar to the metrics port, exposing this port to the public is not recommended by default due to security considerations. - -## WS RPC Port - -- **Port:** 8546 -- **Protocol:** TCP -- **Purpose:** Port 8546 offers a WebSocket-based Remote Procedure Call (RPC) interface. It allows real-time communication between external applications and the blockchain. -- **Exposure Recommendation:** As with the HTTP RPC port, the WS RPC port should not be exposed by default for security reasons. - -## Engine API Port - -- **Port:** 8551 -- **Protocol:** TCP -- **Purpose:** Port 8551 facilitates communication between specific components, such as "reth" and "CL" (assuming their definitions are understood within the context of the system). It enables essential internal processes. -- **Exposure Recommendation:** This port is not meant to be exposed to the public by default. It should be reserved for internal communication between vital components of the system. diff --git a/book/run/run-a-node.md b/book/run/run-a-node.md deleted file mode 100644 index d8981e15522..00000000000 --- a/book/run/run-a-node.md +++ /dev/null @@ -1,15 +0,0 @@ -# Run a Node - -Congratulations, now that you have installed Reth, it's time to run it! - -In this chapter we'll go through a few different topics you'll encounter when running Reth, including: -1. [Running on mainnet or official testnets](./mainnet.md) -1. [Running on OP Stack chains](./optimism.md) -1. [Logs and Observability](./observability.md) -1. [Configuring reth.toml](./config.md) -1. [Transaction types](./transactions.md) -1. [Pruning & Full Node](./pruning.md) -1. [Ports](./ports.md) -1. [Troubleshooting](./troubleshooting.md) - -In the future, we also intend to support the [OP Stack](https://docs.optimism.io/get-started/superchain), which will allow you to run Reth as a Layer 2 client. More there soon! diff --git a/book/templates/source_and_github.md b/book/templates/source_and_github.md deleted file mode 100644 index c4abbaa3894..00000000000 --- a/book/templates/source_and_github.md +++ /dev/null @@ -1,4 +0,0 @@ -[File: [[ #path ]]](https://github.com/paradigmxyz/reth/blob/main/[[ #path ]]) -```rust,no_run,noplayground -{{#include [[ #path_to_root ]][[ #path ]]:[[ #anchor ]]}} -``` \ No newline at end of file diff --git a/book/theme/head.hbs b/book/theme/head.hbs deleted file mode 100644 index 37667d80f6e..00000000000 --- a/book/theme/head.hbs +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/book/vocs/CLAUDE.md b/book/vocs/CLAUDE.md new file mode 100644 index 00000000000..98b57a5791f --- /dev/null +++ b/book/vocs/CLAUDE.md @@ -0,0 +1,103 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is the **Reth documentation website** built with [Vocs](https://vocs.dev), a modern documentation framework. The site contains comprehensive documentation for Reth, the Ethereum execution client, including installation guides, CLI references, SDK documentation, and tutorials. + +## Repository Structure + +- **`docs/pages/`**: All documentation content in MDX format + - `cli/`: Command-line interface documentation and references + - `exex/`: Execution Extensions (ExEx) guides and examples + - `installation/`: Installation and setup guides + - `introduction/`: Introduction, benchmarks, and why-reth content + - `jsonrpc/`: JSON-RPC API documentation + - `run/`: Node running guides and configuration + - `sdk/`: SDK documentation and examples +- **`docs/snippets/`**: Code examples and snippets used in documentation +- **`sidebar.ts`**: Navigation configuration +- **`vocs.config.ts`**: Vocs configuration file + +## Essential Commands + +```bash +# Install dependencies +bun install + +# Start development server +bun run dev + +# Build for production +bun run build + +# Preview production build +bun run preview +``` + +## Development Workflow + +### Content Organization + +1. **MDX Files**: All content is written in MDX (Markdown + React components) +2. **Navigation**: Update `sidebar.ts` when adding new pages +3. **Code Examples**: Place reusable code snippets in `docs/snippets/` +4. **Assets**: Place images and static assets in `docs/public/` + +### Adding New Documentation + +1. Create new `.mdx` files in appropriate subdirectories under `docs/pages/` +2. Update `sidebar.ts` to include new pages in navigation +3. Use consistent heading structure and markdown formatting +4. Reference code examples from `docs/snippets/` when possible + +### Code Examples and Snippets + +- **Live Examples**: Use the snippets system to include actual runnable code +- **Rust Code**: Include cargo project examples in `docs/snippets/sources/` +- **CLI Examples**: Show actual command usage with expected outputs + +### Configuration + +- **Base Path**: Site deploys to `/reth` path (configured in `vocs.config.ts`) +- **Theme**: Custom accent colors for light/dark themes +- **Vite**: Uses Vite as the underlying build tool + +### Content Guidelines + +1. **Be Practical**: Focus on actionable guides and real-world examples +2. **Code First**: Show working code examples before explaining concepts +3. **Consistent Structure**: Follow existing page structures for consistency +4. **Cross-References**: Link between related pages and sections +5. **Keep Current**: Ensure documentation matches latest Reth features + +### File Naming Conventions + +- Use kebab-case for file and directory names +- Match URL structure to file structure +- Use descriptive names that reflect content purpose + +### Common Tasks + +**Adding a new CLI command documentation:** +1. Create `.mdx` file in `docs/pages/cli/reth/` +2. Add to sidebar navigation +3. Include usage examples and parameter descriptions + +**Adding a new guide:** +1. Create `.mdx` file in appropriate category +2. Update sidebar with new entry +3. Include practical examples and next steps + +**Updating code examples:** +1. Modify files in `docs/snippets/sources/` +2. Ensure examples compile and run correctly +3. Test that documentation references work properly + +## Development Notes + +- This is a TypeScript/React project using Vocs framework +- Content is primarily MDX with some TypeScript configuration +- Focus on clear, practical documentation that helps users succeed with Reth +- Maintain consistency with existing documentation style and structure \ No newline at end of file diff --git a/book/vocs/README.md b/book/vocs/README.md new file mode 100644 index 00000000000..3bb11a44a0a --- /dev/null +++ b/book/vocs/README.md @@ -0,0 +1 @@ +This is a [Vocs](https://vocs.dev) project bootstrapped with the Vocs CLI. diff --git a/book/vocs/bun.lockb b/book/vocs/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..0f15cd4dc666167798797dafba290536329e65f4 GIT binary patch literal 302056 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

V<#{?GOQ$CU@VX=Ha*`CD6*hdaTaC+Ok)2P9CJ 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

5u(B-`{=RYrowO$UwKM+WIch&`%U1 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 + summary: { + brokenCount: number; + validCount: number; + }; +} + +main(); + +async function main() { + try { + const report = await checkLinks(); + await saveReport(report); + displayResults(report); + + process.exit(report.summary.brokenCount > 0 ? 1 : 0); + } catch (error) { + console.error("\n❌ Fatal error during link checking:"); + + if (error instanceof Error) { + console.error(` ${error.message}`); + if (error.stack) { + [console.error("\nStack trace:"), console.error(error.stack)]; + } + } else console.error(error); + + process.exit(2); + } +} + +async function checkLinks(): Promise { + console.log("🔍 Finding markdown files..."); + const files = await getAllMarkdownFiles(); + console.log(`📄 Found ${files.length} markdown files`); + + console.log("🔍 Finding public assets..."); + const publicAssets = await getAllPublicAssets(); + console.log(`🖼️ Found ${publicAssets.length} public assets`); + + console.log("🗺️ Building file path map..."); + const pathMap = buildFilePathMap(files, publicAssets); + console.log(`📍 Mapped ${pathMap.size} possible paths`); + + const brokenLinks: BrokenLink[] = []; + let totalLinks = 0; + + console.log("🔗 Checking links in files..."); + + for (let index = 0; index < files.length; index++) { + const file = files[index]; + + try { + const content = readFileSync(file, "utf-8"); + const links = extractLinksFromMarkdown(content); + + for (const { link, line } of links) { + totalLinks++; + const error = validateLink(link, file, pathMap); + + if (error) { + brokenLinks.push({ + file: relative(process.cwd(), file), + link, + line, + reason: error, + }); + } + } + } catch (error) { + console.error(`\nError reading ${file}:`, error); + } + } + + console.log("\n✅ Link checking complete!"); + + return { + timestamp: new Date().toISOString(), + totalFiles: files.length, + totalLinks, + brokenLinks, + summary: { + brokenCount: brokenLinks.length, + validCount: totalLinks - brokenLinks.length, + }, + }; +} + +async function getAllMarkdownFiles(): Promise { + const glob = new Glob(CONFIG.FILE_PATTERNS); + const files = await Array.fromAsync(glob.scan({ cwd: CONFIG.DOCS_DIR })); + return files.map((file) => join(CONFIG.DOCS_DIR, file)); +} + +async function getAllPublicAssets(): Promise { + const glob = new Glob("**/*"); + const files = await Array.fromAsync(glob.scan({ cwd: CONFIG.PUBLIC_DIR })); + return files; +} + +function buildFilePathMap( + files: Array, + publicAssets: Array, +): Set { + const pathMap = new Set(); + + const addPath = (path: string) => { + if (path && typeof path === "string") pathMap.add(path); + }; + + for (const file of files) { + const relativePath = relative(CONFIG.DOCS_DIR, file); + + addPath(relativePath); + + const withoutExt = relativePath.replace(CONFIG.MARKDOWN_EXTENSIONS, ""); + addPath(withoutExt); + + if (withoutExt.endsWith("/index")) + addPath(withoutExt.replace("/index", "")); + + addPath(`/${withoutExt}`); + if (withoutExt.endsWith("/index")) + addPath(`/${withoutExt.replace("/index", "")}`); + } + + for (const asset of publicAssets) addPath(`/${asset}`); + + return pathMap; +} + +function extractLinksFromMarkdown( + content: string, +): Array<{ link: string; line: number }> { + const lines = content.split("\n"); + const links: Array<{ link: string; line: number }> = []; + let inCodeBlock = false; + + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]; + const lineNumber = lineIndex + 1; + + // Toggle code block state + if (line.trim().startsWith("```")) { + inCodeBlock = !inCodeBlock; + continue; + } + + if (inCodeBlock) continue; + + const processedLine = line + .split("`") + .filter((_, index) => index % 2 === 0) + .join(""); + + links.push(...extractMarkdownLinks(processedLine, lineNumber)); + links.push(...extractHtmlLinks(processedLine, lineNumber)); + } + + return links; +} + +function extractMarkdownLinks( + line: string, + lineNumber: number, +): Array<{ link: string; line: number }> { + const regex = /\[([^\]]*)\]\(([^)]+)\)/g; + return [...line.matchAll(regex)] + .map(([, , url]) => ({ link: url, line: lineNumber })) + .filter(({ link }) => isInternalLink(link)); +} + +function extractHtmlLinks( + line: string, + lineNumber: number, +): Array<{ link: string; line: number }> { + const regex = /]+href=["']([^"']+)["'][^>]*>/g; + return [...line.matchAll(regex)] + .map(([, url]) => ({ link: url, line: lineNumber })) + .filter(({ link }) => isInternalLink(link)); +} + +function isInternalLink(url: string): boolean { + return ( + !url.startsWith("http") && + !url.startsWith("mailto:") && + !url.startsWith("#") + ); +} + +function validateLink( + link: string, + sourceFile: string, + pathMap: Set, +): string | null { + const [linkPath] = link.split("#"); + if (!linkPath) return null; // Pure anchor link + + if (linkPath.startsWith("/")) return validateAbsolutePath(linkPath, pathMap); + return validateRelativePath(linkPath, sourceFile, pathMap); +} + +function validateAbsolutePath( + linkPath: string, + pathMap: Set, +): string | null { + const variations = [ + linkPath, + linkPath.slice(1), // Remove leading slash + linkPath.replace(/\/$/, ""), // Remove trailing slash + linkPath + .slice(1) + .replace(/\/$/, ""), // Remove both + ]; + + return variations.some((path) => pathMap.has(path)) + ? null + : `Absolute path not found: ${linkPath}`; +} + +function validateRelativePath( + linkPath: string, + sourceFile: string, + pathMap: Set, +): string | null { + const sourceDir = dirname(relative(CONFIG.DOCS_DIR, sourceFile)); + const resolvedPath = resolve(sourceDir, linkPath); + const normalizedPath = relative(".", resolvedPath); + + const variations = [ + linkPath, + normalizedPath, + `/${normalizedPath}`, + normalizedPath.replace(CONFIG.MARKDOWN_EXTENSIONS, ""), + `/${normalizedPath.replace(CONFIG.MARKDOWN_EXTENSIONS, "")}`, + ]; + + return variations.some((path) => pathMap.has(path)) + ? null + : `Relative path not found: ${linkPath} (resolved to: ${normalizedPath})`; +} + +async function saveReport(report: LinkCheckReport) { + try { + await Bun.write(CONFIG.REPORT_PATH, JSON.stringify(report, null, 2)); + console.log(`\n📝 Report saved to: ${CONFIG.REPORT_PATH}`); + } catch (error) { + console.error( + `\n⚠️ Warning: Failed to save report to ${CONFIG.REPORT_PATH}`, + ); + console.error(error); + } +} + +function displayResults(report: LinkCheckReport) { + LinkCheckReporter.printSummary(report); + + if (report.brokenLinks.length > 0) + LinkCheckReporter.printBrokenLinks(report.brokenLinks); + else console.log("\n✅ All links are valid!"); +} + +const LinkCheckReporter = { + printSummary: (report: LinkCheckReport) => { + console.log("\n📊 Link Check Summary:"); + console.log(` 📄 Files checked: ${report.totalFiles}`); + console.log(` 🔗 Total links: ${report.totalLinks}`); + console.log(` ✅ Valid links: ${report.summary.validCount}`); + console.log(` ❌ Broken links: ${report.summary.brokenCount}`); + }, + printBrokenLinks: (brokenLinks: Array) => { + if (brokenLinks.length === 0) return; + + console.log("\n❌ Broken Links Found:\n"); + + const byFile = brokenLinks.reduce( + (acc, broken) => { + if (!acc[broken.file]) acc[broken.file] = []; + acc[broken.file].push(broken); + return acc; + }, + {} as Record, + ); + + for (const [file, links] of Object.entries(byFile)) { + console.log(`📄 ${file}:`); + for (const broken of links) { + console.log(` Line ${broken.line}: ${broken.link}`); + console.log(` └─ ${broken.reason}\n`); + } + } + }, +}; \ No newline at end of file diff --git a/book/vocs/docs/components/SdkShowcase.tsx b/book/vocs/docs/components/SdkShowcase.tsx new file mode 100644 index 00000000000..14a1f491b81 --- /dev/null +++ b/book/vocs/docs/components/SdkShowcase.tsx @@ -0,0 +1,88 @@ +import React from 'react' + +interface SdkProject { + name: string + description: string + loc: string + githubUrl: string + logoUrl?: string + company: string +} + +const projects: SdkProject[] = [ + { + name: 'Base Node', + description: "Coinbase's L2 scaling solution node implementation", + loc: '~3K', + githubUrl: 'https://github.com/base/node-reth', + company: 'Coinbase' + }, + { + name: 'Bera Reth', + description: "Berachain's high-performance EVM node with custom features", + loc: '~1K', + githubUrl: 'https://github.com/berachain/bera-reth', + company: 'Berachain' + }, + { + name: 'Reth Gnosis', + description: "Gnosis Chain's xDai-compatible execution client", + loc: '~5K', + githubUrl: 'https://github.com/gnosischain/reth_gnosis', + company: 'Gnosis' + }, + { + name: 'Reth BSC', + description: "BNB Smart Chain execution client implementation", + loc: '~6K', + githubUrl: 'https://github.com/loocapro/reth-bsc', + company: 'LooCa Protocol' + } +] + +export function SdkShowcase() { + return ( +

+ ) +} \ No newline at end of file diff --git a/book/vocs/docs/components/TrustedBy.tsx b/book/vocs/docs/components/TrustedBy.tsx new file mode 100644 index 00000000000..fdda21d0a0e --- /dev/null +++ b/book/vocs/docs/components/TrustedBy.tsx @@ -0,0 +1,49 @@ +import React from 'react' + +interface TrustedCompany { + name: string + logoUrl: string +} + +const companies: TrustedCompany[] = [ + { + name: 'Flashbots', + logoUrl: '/reth/flashbots.png' + }, + { + name: 'Coinbase', + logoUrl: '/reth/coinbase.png' + }, + { + name: 'Alchemy', + logoUrl: '/reth/alchemy.png' + }, + { + name: 'Succinct Labs', + logoUrl: '/reth/succinct.png' + } +] + +export function TrustedBy() { + return ( +
+ {companies.map((company, index) => ( +
+ {/* Company Logo */} +
+ {`${company.name} +
+
+ ))} +
+ ) +} \ No newline at end of file diff --git a/book/vocs/docs/pages/cli/SUMMARY.mdx b/book/vocs/docs/pages/cli/SUMMARY.mdx new file mode 100644 index 00000000000..330f32b3fd2 --- /dev/null +++ b/book/vocs/docs/pages/cli/SUMMARY.mdx @@ -0,0 +1,47 @@ +- [`reth`](/cli/reth) + - [`reth node`](/cli/reth/node) + - [`reth init`](/cli/reth/init) + - [`reth init-state`](/cli/reth/init-state) + - [`reth import`](/cli/reth/import) + - [`reth import-era`](/cli/reth/import-era) + - [`reth dump-genesis`](/cli/reth/dump-genesis) + - [`reth db`](/cli/reth/db) + - [`reth db stats`](/cli/reth/db/stats) + - [`reth db list`](/cli/reth/db/list) + - [`reth db checksum`](/cli/reth/db/checksum) + - [`reth db diff`](/cli/reth/db/diff) + - [`reth db get`](/cli/reth/db/get) + - [`reth db get mdbx`](/cli/reth/db/get/mdbx) + - [`reth db get static-file`](/cli/reth/db/get/static-file) + - [`reth db drop`](/cli/reth/db/drop) + - [`reth db clear`](/cli/reth/db/clear) + - [`reth db clear mdbx`](/cli/reth/db/clear/mdbx) + - [`reth db clear static-file`](/cli/reth/db/clear/static-file) + - [`reth db version`](/cli/reth/db/version) + - [`reth db path`](/cli/reth/db/path) + - [`reth download`](/cli/reth/download) + - [`reth stage`](/cli/reth/stage) + - [`reth stage run`](/cli/reth/stage/run) + - [`reth stage drop`](/cli/reth/stage/drop) + - [`reth stage dump`](/cli/reth/stage/dump) + - [`reth stage dump execution`](/cli/reth/stage/dump/execution) + - [`reth stage dump storage-hashing`](/cli/reth/stage/dump/storage-hashing) + - [`reth stage dump account-hashing`](/cli/reth/stage/dump/account-hashing) + - [`reth stage dump merkle`](/cli/reth/stage/dump/merkle) + - [`reth stage unwind`](/cli/reth/stage/unwind) + - [`reth stage unwind to-block`](/cli/reth/stage/unwind/to-block) + - [`reth stage unwind num-blocks`](/cli/reth/stage/unwind/num-blocks) + - [`reth p2p`](/cli/reth/p2p) + - [`reth p2p header`](/cli/reth/p2p/header) + - [`reth p2p body`](/cli/reth/p2p/body) + - [`reth p2p rlpx`](/cli/reth/p2p/rlpx) + - [`reth p2p rlpx ping`](/cli/reth/p2p/rlpx/ping) + - [`reth config`](/cli/reth/config) + - [`reth debug`](/cli/reth/debug) + - [`reth debug execution`](/cli/reth/debug/execution) + - [`reth debug merkle`](/cli/reth/debug/merkle) + - [`reth debug in-memory-merkle`](/cli/reth/debug/in-memory-merkle) + - [`reth debug build-block`](/cli/reth/debug/build-block) + - [`reth recover`](/cli/reth/recover) + - [`reth recover storage-tries`](/cli/reth/recover/storage-tries) + - [`reth prune`](/cli/reth/prune) diff --git a/book/cli/cli.md b/book/vocs/docs/pages/cli/cli.mdx similarity index 83% rename from book/cli/cli.md rename to book/vocs/docs/pages/cli/cli.mdx index ef1a98af525..20046ce9e77 100644 --- a/book/cli/cli.md +++ b/book/vocs/docs/pages/cli/cli.mdx @@ -1,7 +1,9 @@ +import Summary from './SUMMARY.mdx'; + # CLI Reference The Reth node is operated via the CLI by running the `reth node` command. To stop it, press `ctrl-c`. You may need to wait a bit as Reth tears down existing p2p connections or other cleanup tasks. However, Reth has more commands: -{{#include ./SUMMARY.md}} + diff --git a/book/cli/op-reth.md b/book/vocs/docs/pages/cli/op-reth.md similarity index 100% rename from book/cli/op-reth.md rename to book/vocs/docs/pages/cli/op-reth.md diff --git a/book/cli/reth.md b/book/vocs/docs/pages/cli/reth.mdx similarity index 100% rename from book/cli/reth.md rename to book/vocs/docs/pages/cli/reth.mdx diff --git a/book/cli/reth/config.md b/book/vocs/docs/pages/cli/reth/config.mdx similarity index 100% rename from book/cli/reth/config.md rename to book/vocs/docs/pages/cli/reth/config.mdx diff --git a/book/cli/reth/db.md b/book/vocs/docs/pages/cli/reth/db.mdx similarity index 100% rename from book/cli/reth/db.md rename to book/vocs/docs/pages/cli/reth/db.mdx diff --git a/book/cli/reth/db/checksum.md b/book/vocs/docs/pages/cli/reth/db/checksum.mdx similarity index 100% rename from book/cli/reth/db/checksum.md rename to book/vocs/docs/pages/cli/reth/db/checksum.mdx diff --git a/book/cli/reth/db/clear.md b/book/vocs/docs/pages/cli/reth/db/clear.mdx similarity index 100% rename from book/cli/reth/db/clear.md rename to book/vocs/docs/pages/cli/reth/db/clear.mdx diff --git a/book/cli/reth/db/clear/mdbx.md b/book/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx similarity index 100% rename from book/cli/reth/db/clear/mdbx.md rename to book/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx diff --git a/book/cli/reth/db/clear/static-file.md b/book/vocs/docs/pages/cli/reth/db/clear/static-file.mdx similarity index 100% rename from book/cli/reth/db/clear/static-file.md rename to book/vocs/docs/pages/cli/reth/db/clear/static-file.mdx diff --git a/book/cli/reth/db/diff.md b/book/vocs/docs/pages/cli/reth/db/diff.mdx similarity index 100% rename from book/cli/reth/db/diff.md rename to book/vocs/docs/pages/cli/reth/db/diff.mdx diff --git a/book/cli/reth/db/drop.md b/book/vocs/docs/pages/cli/reth/db/drop.mdx similarity index 100% rename from book/cli/reth/db/drop.md rename to book/vocs/docs/pages/cli/reth/db/drop.mdx diff --git a/book/cli/reth/db/get.md b/book/vocs/docs/pages/cli/reth/db/get.mdx similarity index 100% rename from book/cli/reth/db/get.md rename to book/vocs/docs/pages/cli/reth/db/get.mdx diff --git a/book/cli/reth/db/get/mdbx.md b/book/vocs/docs/pages/cli/reth/db/get/mdbx.mdx similarity index 100% rename from book/cli/reth/db/get/mdbx.md rename to book/vocs/docs/pages/cli/reth/db/get/mdbx.mdx diff --git a/book/cli/reth/db/get/static-file.md b/book/vocs/docs/pages/cli/reth/db/get/static-file.mdx similarity index 100% rename from book/cli/reth/db/get/static-file.md rename to book/vocs/docs/pages/cli/reth/db/get/static-file.mdx diff --git a/book/cli/reth/db/list.md b/book/vocs/docs/pages/cli/reth/db/list.mdx similarity index 100% rename from book/cli/reth/db/list.md rename to book/vocs/docs/pages/cli/reth/db/list.mdx diff --git a/book/cli/reth/db/path.md b/book/vocs/docs/pages/cli/reth/db/path.mdx similarity index 100% rename from book/cli/reth/db/path.md rename to book/vocs/docs/pages/cli/reth/db/path.mdx diff --git a/book/cli/reth/db/stats.md b/book/vocs/docs/pages/cli/reth/db/stats.mdx similarity index 100% rename from book/cli/reth/db/stats.md rename to book/vocs/docs/pages/cli/reth/db/stats.mdx diff --git a/book/cli/reth/db/version.md b/book/vocs/docs/pages/cli/reth/db/version.mdx similarity index 100% rename from book/cli/reth/db/version.md rename to book/vocs/docs/pages/cli/reth/db/version.mdx diff --git a/book/cli/reth/debug.md b/book/vocs/docs/pages/cli/reth/debug.mdx similarity index 100% rename from book/cli/reth/debug.md rename to book/vocs/docs/pages/cli/reth/debug.mdx diff --git a/book/cli/reth/debug/build-block.md b/book/vocs/docs/pages/cli/reth/debug/build-block.mdx similarity index 100% rename from book/cli/reth/debug/build-block.md rename to book/vocs/docs/pages/cli/reth/debug/build-block.mdx diff --git a/book/cli/reth/debug/execution.md b/book/vocs/docs/pages/cli/reth/debug/execution.mdx similarity index 100% rename from book/cli/reth/debug/execution.md rename to book/vocs/docs/pages/cli/reth/debug/execution.mdx diff --git a/book/cli/reth/debug/in-memory-merkle.md b/book/vocs/docs/pages/cli/reth/debug/in-memory-merkle.mdx similarity index 100% rename from book/cli/reth/debug/in-memory-merkle.md rename to book/vocs/docs/pages/cli/reth/debug/in-memory-merkle.mdx diff --git a/book/cli/reth/debug/merkle.md b/book/vocs/docs/pages/cli/reth/debug/merkle.mdx similarity index 100% rename from book/cli/reth/debug/merkle.md rename to book/vocs/docs/pages/cli/reth/debug/merkle.mdx diff --git a/book/cli/reth/download.md b/book/vocs/docs/pages/cli/reth/download.mdx similarity index 100% rename from book/cli/reth/download.md rename to book/vocs/docs/pages/cli/reth/download.mdx diff --git a/book/cli/reth/dump-genesis.md b/book/vocs/docs/pages/cli/reth/dump-genesis.mdx similarity index 100% rename from book/cli/reth/dump-genesis.md rename to book/vocs/docs/pages/cli/reth/dump-genesis.mdx diff --git a/book/cli/reth/import-era.md b/book/vocs/docs/pages/cli/reth/import-era.mdx similarity index 100% rename from book/cli/reth/import-era.md rename to book/vocs/docs/pages/cli/reth/import-era.mdx diff --git a/book/cli/reth/import.md b/book/vocs/docs/pages/cli/reth/import.mdx similarity index 100% rename from book/cli/reth/import.md rename to book/vocs/docs/pages/cli/reth/import.mdx diff --git a/book/cli/reth/init-state.md b/book/vocs/docs/pages/cli/reth/init-state.mdx similarity index 100% rename from book/cli/reth/init-state.md rename to book/vocs/docs/pages/cli/reth/init-state.mdx diff --git a/book/cli/reth/init.md b/book/vocs/docs/pages/cli/reth/init.mdx similarity index 100% rename from book/cli/reth/init.md rename to book/vocs/docs/pages/cli/reth/init.mdx diff --git a/book/cli/reth/node.md b/book/vocs/docs/pages/cli/reth/node.mdx similarity index 100% rename from book/cli/reth/node.md rename to book/vocs/docs/pages/cli/reth/node.mdx diff --git a/book/cli/reth/p2p.md b/book/vocs/docs/pages/cli/reth/p2p.mdx similarity index 100% rename from book/cli/reth/p2p.md rename to book/vocs/docs/pages/cli/reth/p2p.mdx diff --git a/book/cli/reth/p2p/body.md b/book/vocs/docs/pages/cli/reth/p2p/body.mdx similarity index 100% rename from book/cli/reth/p2p/body.md rename to book/vocs/docs/pages/cli/reth/p2p/body.mdx diff --git a/book/cli/reth/p2p/header.md b/book/vocs/docs/pages/cli/reth/p2p/header.mdx similarity index 100% rename from book/cli/reth/p2p/header.md rename to book/vocs/docs/pages/cli/reth/p2p/header.mdx diff --git a/book/cli/reth/p2p/rlpx.md b/book/vocs/docs/pages/cli/reth/p2p/rlpx.mdx similarity index 100% rename from book/cli/reth/p2p/rlpx.md rename to book/vocs/docs/pages/cli/reth/p2p/rlpx.mdx diff --git a/book/cli/reth/p2p/rlpx/ping.md b/book/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx similarity index 100% rename from book/cli/reth/p2p/rlpx/ping.md rename to book/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx diff --git a/book/cli/reth/prune.md b/book/vocs/docs/pages/cli/reth/prune.mdx similarity index 100% rename from book/cli/reth/prune.md rename to book/vocs/docs/pages/cli/reth/prune.mdx diff --git a/book/cli/reth/recover.md b/book/vocs/docs/pages/cli/reth/recover.mdx similarity index 100% rename from book/cli/reth/recover.md rename to book/vocs/docs/pages/cli/reth/recover.mdx diff --git a/book/cli/reth/recover/storage-tries.md b/book/vocs/docs/pages/cli/reth/recover/storage-tries.mdx similarity index 100% rename from book/cli/reth/recover/storage-tries.md rename to book/vocs/docs/pages/cli/reth/recover/storage-tries.mdx diff --git a/book/cli/reth/stage.md b/book/vocs/docs/pages/cli/reth/stage.mdx similarity index 100% rename from book/cli/reth/stage.md rename to book/vocs/docs/pages/cli/reth/stage.mdx diff --git a/book/cli/reth/stage/drop.md b/book/vocs/docs/pages/cli/reth/stage/drop.mdx similarity index 100% rename from book/cli/reth/stage/drop.md rename to book/vocs/docs/pages/cli/reth/stage/drop.mdx diff --git a/book/cli/reth/stage/dump.md b/book/vocs/docs/pages/cli/reth/stage/dump.mdx similarity index 100% rename from book/cli/reth/stage/dump.md rename to book/vocs/docs/pages/cli/reth/stage/dump.mdx diff --git a/book/cli/reth/stage/dump/account-hashing.md b/book/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx similarity index 100% rename from book/cli/reth/stage/dump/account-hashing.md rename to book/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx diff --git a/book/cli/reth/stage/dump/execution.md b/book/vocs/docs/pages/cli/reth/stage/dump/execution.mdx similarity index 100% rename from book/cli/reth/stage/dump/execution.md rename to book/vocs/docs/pages/cli/reth/stage/dump/execution.mdx diff --git a/book/cli/reth/stage/dump/merkle.md b/book/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx similarity index 100% rename from book/cli/reth/stage/dump/merkle.md rename to book/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx diff --git a/book/cli/reth/stage/dump/storage-hashing.md b/book/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx similarity index 100% rename from book/cli/reth/stage/dump/storage-hashing.md rename to book/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx diff --git a/book/cli/reth/stage/run.md b/book/vocs/docs/pages/cli/reth/stage/run.mdx similarity index 100% rename from book/cli/reth/stage/run.md rename to book/vocs/docs/pages/cli/reth/stage/run.mdx diff --git a/book/cli/reth/stage/unwind.md b/book/vocs/docs/pages/cli/reth/stage/unwind.mdx similarity index 100% rename from book/cli/reth/stage/unwind.md rename to book/vocs/docs/pages/cli/reth/stage/unwind.mdx diff --git a/book/cli/reth/stage/unwind/num-blocks.md b/book/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx similarity index 100% rename from book/cli/reth/stage/unwind/num-blocks.md rename to book/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx diff --git a/book/cli/reth/stage/unwind/to-block.md b/book/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx similarity index 100% rename from book/cli/reth/stage/unwind/to-block.md rename to book/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx diff --git a/book/cli/reth/test-vectors/tables.md b/book/vocs/docs/pages/cli/reth/test-vectors/tables.mdx similarity index 100% rename from book/cli/reth/test-vectors/tables.md rename to book/vocs/docs/pages/cli/reth/test-vectors/tables.mdx diff --git a/book/developers/exex/hello-world.md b/book/vocs/docs/pages/exex/hello-world.mdx similarity index 70% rename from book/developers/exex/hello-world.md rename to book/vocs/docs/pages/exex/hello-world.mdx index c1f3e5af944..547f6e4e31d 100644 --- a/book/developers/exex/hello-world.md +++ b/book/vocs/docs/pages/exex/hello-world.mdx @@ -1,3 +1,7 @@ +--- +description: Example of a minimal Hello World ExEx in Reth. +--- + # Hello World Let's write a simple "Hello World" ExEx that emits a log every time a new chain of blocks is committed, reverted, or reorged. @@ -14,15 +18,15 @@ cd my-exex And add Reth as a dependency in `Cargo.toml` ```toml -{{#include ../../sources/exex/hello-world/Cargo.toml}} +// [!include ~/snippets/sources/exex/hello-world/Cargo.toml] ``` ### Default Reth node Now, let's jump to our `main.rs` and start by initializing and launching a default Reth node -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/hello-world/src/bin/1.rs}} +```rust +// [!include ~/snippets/sources/exex/hello-world/src/bin/1.rs] ``` You can already test that it works by running the binary and initializing the Holesky node in a custom datadir @@ -42,8 +46,8 @@ $ cargo run -- init --chain holesky --datadir data The simplest ExEx is just an async function that never returns. We need to install it into our node -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/hello-world/src/bin/2.rs}} +```rust +// [!include ~/snippets/sources/exex/hello-world/src/bin/2.rs] ``` See that unused `_ctx`? That's the context that we'll use to listen to new notifications coming from the main node, @@ -63,17 +67,17 @@ If you try running a node with an ExEx that exits, the node will exit as well. Now, let's extend our simplest ExEx and start actually listening to new notifications, log them, and send events back to the main node -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/hello-world/src/bin/3.rs}} +```rust +// [!include ~/snippets/sources/exex/hello-world/src/bin/3.rs] ``` Woah, there's a lot of new stuff here! Let's go through it step by step: -- First, we've added a `while let Some(notification) = ctx.notifications.recv().await` loop that waits for new notifications to come in. - - The main node is responsible for sending notifications to the ExEx, so we're waiting for them to come in. -- Next, we've added a `match ¬ification { ... }` block that matches on the type of the notification. - - In each case, we're logging the notification and the corresponding block range, be it a chain commit, revert, or reorg. -- Finally, we're checking if the notification contains a committed chain, and if it does, we're sending a `ExExEvent::FinishedHeight` event back to the main node using the `ctx.events.send` method. +- First, we've added a `while let Some(notification) = ctx.notifications.recv().await` loop that waits for new notifications to come in. + - The main node is responsible for sending notifications to the ExEx, so we're waiting for them to come in. +- Next, we've added a `match ¬ification { ... }` block that matches on the type of the notification. + - In each case, we're logging the notification and the corresponding block range, be it a chain commit, revert, or reorg. +- Finally, we're checking if the notification contains a committed chain, and if it does, we're sending a `ExExEvent::FinishedHeight` event back to the main node using the `ctx.events.send` method.
@@ -88,4 +92,4 @@ What we've arrived at is the [minimal ExEx example](https://github.com/paradigmx ## What's next? -Let's do something a bit more interesting, and see how you can [keep track of some state](./tracking-state.md) inside your ExEx. +Let's do something a bit more interesting, and see how you can [keep track of some state](./tracking-state) inside your ExEx. diff --git a/book/developers/exex/how-it-works.md b/book/vocs/docs/pages/exex/how-it-works.mdx similarity index 67% rename from book/developers/exex/how-it-works.md rename to book/vocs/docs/pages/exex/how-it-works.mdx index 7f80d71cbff..21162a75620 100644 --- a/book/developers/exex/how-it-works.md +++ b/book/vocs/docs/pages/exex/how-it-works.mdx @@ -1,3 +1,7 @@ +--- +description: How Execution Extensions (ExExes) work in Reth. +--- + # How do ExExes work? ExExes are just [Futures](https://doc.rust-lang.org/std/future/trait.Future.html) that run indefinitely alongside Reth @@ -7,12 +11,13 @@ An ExEx is usually driven by and acts on new notifications about chain commits, They are installed into the node by using the [node builder](https://reth.rs/docs/reth/builder/struct.NodeBuilder.html). Reth manages the lifecycle of all ExExes, including: -- Polling ExEx futures -- Sending [notifications](https://reth.rs/docs/reth_exex/enum.ExExNotification.html) about new chain, reverts, - and reorgs from historical and live sync -- Processing [events](https://reth.rs/docs/reth_exex/enum.ExExEvent.html) emitted by ExExes -- Pruning (in case of a full or pruned node) only the data that has been processed by all ExExes -- Shutting ExExes down when the node is shut down + +- Polling ExEx futures +- Sending [notifications](https://reth.rs/docs/reth_exex/enum.ExExNotification.html) about new chain, reverts, + and reorgs from historical and live sync +- Processing [events](https://reth.rs/docs/reth_exex/enum.ExExEvent.html) emitted by ExExes +- Pruning (in case of a full or pruned node) only the data that has been processed by all ExExes +- Shutting ExExes down when the node is shut down ## Pruning diff --git a/book/developers/exex/exex.md b/book/vocs/docs/pages/exex/overview.mdx similarity index 62% rename from book/developers/exex/exex.md rename to book/vocs/docs/pages/exex/overview.mdx index 25372a7c922..abfcc8f3b82 100644 --- a/book/developers/exex/exex.md +++ b/book/vocs/docs/pages/exex/overview.mdx @@ -1,9 +1,13 @@ +--- +description: Introduction to Execution Extensions (ExEx) in Reth. +--- + # Execution Extensions (ExEx) ## What are Execution Extensions? Execution Extensions (or ExExes, for short) allow developers to build their own infrastructure that relies on Reth -as a base for driving the chain (be it [Ethereum](../../run/mainnet.md) or [OP Stack](../../run/optimism.md)) forward. +as a base for driving the chain (be it [Ethereum](/run/ethereum) or [OP Stack](/run/opstack)) forward. An Execution Extension is a task that derives its state from changes in Reth's state. Some examples of such state derivations are rollups, bridges, and indexers. @@ -18,14 +22,18 @@ Read more about things you can build with Execution Extensions in the [Paradigm Execution Extensions are not separate processes that connect to the main Reth node process. Instead, ExExes are compiled into the same binary as Reth, and run alongside it, using shared memory for communication. -If you want to build an Execution Extension that sends data into a separate process, check out the [Remote](./remote.md) chapter. +If you want to build an Execution Extension that sends data into a separate process, check out the [Remote](/exex/remote) chapter. ## How do I build an Execution Extension? Let's dive into how to build our own ExEx from scratch, add tests for it, and run it on the Holesky testnet. -1. [How do ExExes work?](./how-it-works.md) -1. [Hello World](./hello-world.md) -1. [Tracking State](./tracking-state.md) -1. [Remote](./remote.md) +1. [How do ExExes work?](/exex/how-it-works) +1. [Hello World](/exex/hello-world) +1. [Tracking State](/exex/tracking-state) +1. [Remote](/exex/remote) + +:::tip +For more practical examples and ready-to-use ExEx implementations, check out the [reth-exex-examples](https://github.com/paradigmxyz/reth-exex-examples) repository which contains various ExEx examples including indexers, bridges, and other state derivation patterns. +::: diff --git a/book/developers/exex/remote.md b/book/vocs/docs/pages/exex/remote.mdx similarity index 76% rename from book/developers/exex/remote.md rename to book/vocs/docs/pages/exex/remote.mdx index 0ec704308ff..92da3372089 100644 --- a/book/developers/exex/remote.md +++ b/book/vocs/docs/pages/exex/remote.mdx @@ -1,10 +1,15 @@ +--- +description: Building a remote ExEx that communicates via gRPC. +--- + # Remote Execution Extensions In this chapter, we will learn how to create an ExEx that emits all notifications to an external process. We will use [Tonic](https://github.com/hyperium/tonic) to create a gRPC server and a client. -- The server binary will have the Reth client, our ExEx and the gRPC server. -- The client binary will have the gRPC client that connects to the server. + +- The server binary will have the Reth client, our ExEx and the gRPC server. +- The client binary will have the gRPC client that connects to the server. ## Prerequisites @@ -21,20 +26,21 @@ $ cargo new --lib exex-remote $ cd exex-remote ``` -We will also need a bunch of dependencies. Some of them you know from the [Hello World](./hello-world.md) chapter, +We will also need a bunch of dependencies. Some of them you know from the [Hello World](./hello-world) chapter, but some of specific to what we need now. ```toml -{{#include ../../sources/exex/remote/Cargo.toml}} +// [!include ~/snippets/sources/exex/remote/Cargo.toml] ``` We also added a build dependency for Tonic. We will use it to generate the Rust code for our Protobuf definitions at compile time. Read more about using Tonic in the -[introductory tutorial](https://github.com/hyperium/tonic/blob/6a213e9485965db0628591e30577ed81cdaeaf2b/examples/helloworld-tutorial.md). +[introductory tutorial](https://github.com/hyperium/tonic/blob/6a213e9485965db0628591e30577ed81cdaeaf2b/examples/helloworld-tutorial). Also, we now have two separate binaries: -- `exex` is the server binary that will run the ExEx and the gRPC server. -- `consumer` is the client binary that will connect to the server and receive notifications. + +- `exex` is the server binary that will run the ExEx and the gRPC server. +- `consumer` is the client binary that will connect to the server and receive notifications. ### Create the Protobuf definitions @@ -53,12 +59,13 @@ For an example of a full schema, see the [Remote ExEx](https://github.com/paradi
```protobuf -{{#include ../../sources/exex/remote/proto/exex.proto}} +// [!include ~/snippets/sources/exex/remote/proto/exex.proto] ``` To instruct Tonic to generate the Rust code using this `.proto`, add the following lines to your `lib.rs` file: -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/remote/src/lib.rs}} + +```rust +// [!include ~/snippets/sources/exex/remote/src/lib.rs] ``` ## ExEx and gRPC server @@ -70,8 +77,8 @@ We will now create the ExEx and the gRPC server in our `src/exex.rs` file. Let's create a minimal gRPC server that listens on the port `:10000`, and spawn it using the [NodeBuilder](https://reth.rs/docs/reth/builder/struct.NodeBuilder.html)'s [task executor](https://reth.rs/docs/reth/tasks/struct.TaskExecutor.html). -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/remote/src/exex_1.rs}} +```rust +// [!include ~/snippets/sources/exex/remote/src/exex_1.rs] ``` Currently, it does not send anything on the stream. @@ -81,8 +88,8 @@ to send new `ExExNotification` on it. Let's create this channel in the `main` function where we will have both gRPC server and ExEx initiated, and save the sender part (that way we will be able to create new receivers) of this channel in our gRPC server. -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/remote/src/exex_2.rs}} +```rust +// [!include ~/snippets/sources/exex/remote/src/exex_2.rs] ``` And with that, we're ready to handle incoming notifications, serialize them with [bincode](https://docs.rs/bincode/) @@ -91,8 +98,8 @@ and send back to the client. For each incoming request, we spawn a separate tokio task that will run in the background, and then return the stream receiver to the client. -```rust,norun,noplayground,ignore -{{#rustdoc_include ../../sources/exex/remote/src/exex_3.rs:snippet}} +```rust +// [!include ~/snippets/sources/exex/remote/src/exex_3.rs] ``` That's it for the gRPC server part! It doesn't receive anything on the `notifications` channel yet, @@ -110,25 +117,24 @@ Don't forget to emit `ExExEvent::FinishedHeight` -```rust,norun,noplayground,ignore -{{#rustdoc_include ../../sources/exex/remote/src/exex_4.rs:snippet}} +```rust +// [!include ~/snippets/sources/exex/remote/src/exex_4.rs] ``` All that's left is to connect all pieces together: install our ExEx in the node and pass the sender part of communication channel to it. -```rust,norun,noplayground,ignore -{{#rustdoc_include ../../sources/exex/remote/src/exex.rs:snippet}} +```rust +// [!include ~/snippets/sources/exex/remote/src/exex.rs] ``` ### Full `exex.rs` code
-Click to expand - -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/remote/src/exex.rs}} -``` + Click to expand + ```rust + // [!include ~/snippets/sources/exex/remote/src/exex.rs] + ```
## Consumer @@ -143,8 +149,8 @@ because notifications can get very heavy -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/remote/src/consumer.rs}} +```rust +// [!include ~/snippets/sources/exex/remote/src/consumer.rs] ``` ## Running @@ -162,4 +168,4 @@ And in the other, we will run our consumer: cargo run --bin consumer --release ``` - +![remote_exex](/remote_exex.png) diff --git a/book/developers/exex/tracking-state.md b/book/vocs/docs/pages/exex/tracking-state.mdx similarity index 63% rename from book/developers/exex/tracking-state.md rename to book/vocs/docs/pages/exex/tracking-state.mdx index 92e4ee0f184..cd704c88969 100644 --- a/book/developers/exex/tracking-state.md +++ b/book/vocs/docs/pages/exex/tracking-state.mdx @@ -1,8 +1,12 @@ +--- +description: How to track state in a custom ExEx. +--- + # Tracking State In this chapter, we'll learn how to keep track of some state inside our ExEx. -Let's continue with our Hello World example from the [previous chapter](./hello-world.md). +Let's continue with our Hello World example from the [previous chapter](./hello-world). ### Turning ExEx into a struct @@ -18,8 +22,8 @@ because you can't access variables inside the function to assert the state of yo -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/tracking-state/src/bin/1.rs}} +```rust +// [!include ~/snippets/sources/exex/tracking-state/src/bin/1.rs] ``` For those who are not familiar with how async Rust works on a lower level, that may seem scary, @@ -39,23 +43,25 @@ With all that done, we're now free to add more fields to our `MyExEx` struct, an Our ExEx will count the number of transactions in each block and log it to the console. -```rust,norun,noplayground,ignore -{{#include ../../sources/exex/tracking-state/src/bin/2.rs}} +```rust +// [!include ~/snippets/sources/exex/tracking-state/src/bin/2.rs] ``` As you can see, we added two fields to our ExEx struct: -- `first_block` to keep track of the first block that was committed since the start of the ExEx. -- `transactions` to keep track of the total number of transactions committed, accounting for reorgs and reverts. + +- `first_block` to keep track of the first block that was committed since the start of the ExEx. +- `transactions` to keep track of the total number of transactions committed, accounting for reorgs and reverts. We also changed our `match` block to two `if` clauses: -- First one checks if there's a reverted chain using `notification.reverted_chain()`. If there is: - - We subtract the number of transactions in the reverted chain from the total number of transactions. - - It's important to do the `saturating_sub` here, because if we just started our node and - instantly received a reorg, our `transactions` field will still be zero. -- Second one checks if there's a committed chain using `notification.committed_chain()`. If there is: - - We update the `first_block` field to the first block of the committed chain. - - We add the number of transactions in the committed chain to the total number of transactions. - - We send a `FinishedHeight` event back to the main node. + +- First one checks if there's a reverted chain using `notification.reverted_chain()`. If there is: + - We subtract the number of transactions in the reverted chain from the total number of transactions. + - It's important to do the `saturating_sub` here, because if we just started our node and + instantly received a reorg, our `transactions` field will still be zero. +- Second one checks if there's a committed chain using `notification.committed_chain()`. If there is: + - We update the `first_block` field to the first block of the committed chain. + - We add the number of transactions in the committed chain to the total number of transactions. + - We send a `FinishedHeight` event back to the main node. Finally, on every notification, we log the total number of transactions and the first block that was committed since the start of the ExEx. diff --git a/book/vocs/docs/pages/index.mdx b/book/vocs/docs/pages/index.mdx new file mode 100644 index 00000000000..5e65d0695ce --- /dev/null +++ b/book/vocs/docs/pages/index.mdx @@ -0,0 +1,162 @@ +--- +content: + width: 100% +layout: landing +showLogo: false +title: Reth +description: Secure, performant and modular node implementation that supports both Ethereum and OP-Stack chains. +--- + +import { HomePage, Sponsors } from "vocs/components"; +import { SdkShowcase } from "../components/SdkShowcase"; +import { TrustedBy } from "../components/TrustedBy"; + +
+
+
+
+
+ Reth +
Secure, performant, and modular blockchain SDK and node.
+
+
+ Run a Node + Build a Node + Why Reth? +
+
+
+
+ :::code-group + + ```bash [Run a Node] + # Install the binary + brew install paradigmxyz/brew/reth + + # Run the node with JSON-RPC enabled + reth node --http --http.api eth,trace + ``` + + ```rust [Build a Node] + // .. snip .. + let handle = node_builder + .with_types::() + .with_components(EthereumNode::components()) + .with_add_ons(EthereumAddOns::default()) + .launch() + .await?; + ``` + + ::: +
+
+ +
+
+ stars +
+
+ 4.7K +
+
+
+
+ + +
+
+ contributors +
+
+ 580+ +
+
+
+ +
+
+
+ +
Institutional Security
+
Run reliable staking nodes trusted by Coinbase Staking
+
+
+
+
+
+
+
+ +
Performant
+
Sync faster with optimal transaction processing
+
+
+
+
+
+
+ +
+ +## Trusted by the Best + +Leading infra companies use Reth for MEV applications, staking, RPC services and generating zero-knowledge proofs. + +
+ +
+ +## Built with Reth SDK + +Production chains and networks powered by Reth's modular architecture. These nodes are built using existing components without forking, saving several engineering hours while improving maintainability. + +
+ +
+ +## Supporters + + +
diff --git a/book/installation/binaries.md b/book/vocs/docs/pages/installation/binaries.mdx similarity index 90% rename from book/installation/binaries.md rename to book/vocs/docs/pages/installation/binaries.mdx index fc741805cd9..56c5cf2bacc 100644 --- a/book/installation/binaries.md +++ b/book/vocs/docs/pages/installation/binaries.mdx @@ -1,3 +1,7 @@ +--- +description: Instructions for installing Reth using pre-built binaries for Windows, macOS, and Linux, including Homebrew and Arch Linux AUR options. Explains how to verify binary signatures and provides details about the release signing key. +--- + # Binaries [**Archives of precompiled binaries of reth are available for Windows, macOS and Linux.**](https://github.com/paradigmxyz/reth/releases) They are static executables. Users of platforms not explicitly listed below should download one of these archives. @@ -41,7 +45,7 @@ Replace the filenames by those corresponding to the downloaded Reth release. Releases are signed using the key with ID [`50FB7CC55B2E8AFA59FE03B7AA5ED56A7FBF253E`](https://keyserver.ubuntu.com/pks/lookup?search=50FB7CC55B2E8AFA59FE03B7AA5ED56A7FBF253E&fingerprint=on&op=index). -```none +```text -----BEGIN PGP PUBLIC KEY BLOCK----- mDMEZl4GjhYJKwYBBAHaRw8BAQdAU5gnINBAfIgF9S9GzZ1zHDwZtv/WcJRIQI+h diff --git a/book/installation/build-for-arm-devices.md b/book/vocs/docs/pages/installation/build-for-arm-devices.mdx similarity index 82% rename from book/installation/build-for-arm-devices.md rename to book/vocs/docs/pages/installation/build-for-arm-devices.mdx index 21d32c9e8bd..534fe1c014e 100644 --- a/book/installation/build-for-arm-devices.md +++ b/book/vocs/docs/pages/installation/build-for-arm-devices.mdx @@ -1,3 +1,7 @@ +--- +description: Building and troubleshooting Reth on ARM devices. +--- + # Building for ARM devices Reth can be built for and run on ARM devices, but there are a few things to take into consideration before. @@ -37,8 +41,8 @@ Some newer versions of ARM architecture offer support for Large Virtual Address ### Additional Resources -- [ARM developer documentation](https://developer.arm.com/documentation/ddi0406/cb/Appendixes/ARMv4-and-ARMv5-Differences/System-level-memory-model/Virtual-memory-support) -- [ARM Community Forums](https://community.arm.com) +- [ARM developer documentation](https://developer.arm.com/documentation/ddi0406/cb/Appendixes/ARMv4-and-ARMv5-Differences/System-level-memory-model/Virtual-memory-support) +- [ARM Community Forums](https://community.arm.com) ## Build Reth @@ -57,16 +61,21 @@ This error is raised whenever MDBX can not open a database due to the limitation You will need to recompile the Linux Kernel to fix the issue. A simple and safe approach to achieve this is to use the Armbian build framework to create a new image of the OS that will be flashed to a storage device of your choice - an SD card for example - with the following kernel feature values: -- **Page Size**: 64 KB -- **Virtual Address Space Size**: 48 Bits + +- **Page Size**: 64 KB +- **Virtual Address Space Size**: 48 Bits To be able to build an Armbian image and set those values, you will need to: -- Clone the Armbian build framework repository + +- Clone the Armbian build framework repository + ```bash git clone https://github.com/armbian/build cd build ``` -- Run the compile script with the following parameters: + +- Run the compile script with the following parameters: + ```bash ./compile.sh \ BUILD_MINIMAL=yes \ @@ -74,5 +83,6 @@ BUILD_DESKTOP=no \ KERNEL_CONFIGURE=yes \ CARD_DEVICE="/dev/sdX" # Replace sdX with your own storage device ``` -- From there, you will be able to select the target board, the OS release and branch. Then, once you get in the **Kernel Configuration** screen, select the **Kernel Features options** and set the previous values accordingly. -- Wait for the process to finish, plug your storage device into your board and start it. You can now download or install Reth and it should work properly. + +- From there, you will be able to select the target board, the OS release and branch. Then, once you get in the **Kernel Configuration** screen, select the **Kernel Features options** and set the previous values accordingly. +- Wait for the process to finish, plug your storage device into your board and start it. You can now download or install Reth and it should work properly. diff --git a/book/installation/docker.md b/book/vocs/docs/pages/installation/docker.mdx similarity index 80% rename from book/installation/docker.md rename to book/vocs/docs/pages/installation/docker.mdx index 6ce2ae50a5b..8774d549a5c 100644 --- a/book/installation/docker.md +++ b/book/vocs/docs/pages/installation/docker.mdx @@ -1,3 +1,7 @@ +--- +description: Guide to running Reth using Docker, including obtaining images from GitHub or building locally, using Docker Compose. +--- + # Docker There are two ways to obtain a Reth Docker image: @@ -8,9 +12,10 @@ There are two ways to obtain a Reth Docker image: Once you have obtained the Docker image, proceed to [Using the Docker image](#using-the-docker-image). -> **Note** -> -> Reth requires Docker Engine version 20.10.10 or higher due to [missing support](https://docs.docker.com/engine/release-notes/20.10/#201010) for the `clone3` syscall in previous versions. +:::note +Reth requires Docker Engine version 20.10.10 or higher due to [missing support](https://docs.docker.com/engine/release-notes/20.10/#201010) for the `clone3` syscall in previous versions. +::: + ## GitHub Reth docker images for both x86_64 and ARM64 machines are published with every release of reth on GitHub Container Registry. @@ -52,6 +57,7 @@ docker run reth:local --version ## Using the Docker image There are two ways to use the Docker image: + 1. [Using Docker](#using-plain-docker) 2. [Using Docker Compose](#using-docker-compose) @@ -86,12 +92,12 @@ To run Reth with Docker Compose, run the following command from a shell inside t docker compose -f etc/docker-compose.yml -f etc/lighthouse.yml up -d ``` -> **Note** -> -> If you want to run Reth with a CL that is not Lighthouse: -> -> - The JWT for the consensus client can be found at `etc/jwttoken/jwt.hex` in this repository, after the `etc/generate-jwt.sh` script is run -> - The Reth Engine API is accessible on `localhost:8551` +:::note +If you want to run Reth with a CL that is not Lighthouse: + +- The JWT for the consensus client can be found at `etc/jwttoken/jwt.hex` in this repository, after the `etc/generate-jwt.sh` script is run +- The Reth Engine API is accessible on `localhost:8551` + ::: To check if Reth is running correctly, run: @@ -101,18 +107,19 @@ docker compose -f etc/docker-compose.yml -f etc/lighthouse.yml logs -f reth The default `docker-compose.yml` file will create three containers: -- Reth -- Prometheus -- Grafana +- Reth +- Prometheus +- Grafana The optional `lighthouse.yml` file will create two containers: -- Lighthouse -- [`ethereum-metrics-exporter`](https://github.com/ethpandaops/ethereum-metrics-exporter) +- Lighthouse +- [`ethereum-metrics-exporter`](https://github.com/ethpandaops/ethereum-metrics-exporter) Grafana will be exposed on `localhost:3000` and accessible via default credentials (username and password is `admin`), with two available dashboards: -- reth -- Ethereum Metrics Exporter (works only if Lighthouse is also running) + +- reth +- Ethereum Metrics Exporter (works only if Lighthouse is also running) ## Interacting with Reth inside Docker @@ -124,7 +131,7 @@ docker exec -it reth bash **If Reth is running with Docker Compose, replace `reth` with `reth-reth-1` in the above command** -Refer to the [CLI docs](../cli/cli.md) to interact with Reth once inside the Reth container. +Refer to the [CLI docs](#TODO) to interact with Reth once inside the Reth container. ## Run only Grafana in Docker @@ -134,4 +141,4 @@ This allows importing existing Grafana dashboards, without running Reth in Docke docker compose -f etc/docker-compose.yml up -d --no-deps grafana ``` -After login with `admin:admin` credentials, Prometheus should be listed under [`Grafana datasources`](http://localhost:3000/connections/datasources). Replace its `Prometheus server URL` so it points to locally running one. On Mac or Windows, use `http://host.docker.internal:9090`. On Linux, try `http://172.17.0.1:9090`. \ No newline at end of file +After login with `admin:admin` credentials, Prometheus should be listed under [`Grafana datasources`](http://localhost:3000/connections/datasources). Replace its `Prometheus server URL` so it points to locally running one. On Mac or Windows, use `http://host.docker.internal:9090`. On Linux, try `http://172.17.0.1:9090`. diff --git a/book/vocs/docs/pages/installation/overview.mdx b/book/vocs/docs/pages/installation/overview.mdx new file mode 100644 index 00000000000..8101c509cdd --- /dev/null +++ b/book/vocs/docs/pages/installation/overview.mdx @@ -0,0 +1,18 @@ +--- +description: Installation instructions for Reth and hardware recommendations. +--- + +# Installation + +Reth runs on Linux and macOS (Windows tracked). + +There are three core methods to obtain Reth: + +- [Pre-built binaries](./binaries) +- [Docker images](./docker) +- [Building from source.](./source) + +:::note +If you have Docker installed, we recommend using the [Docker Compose](./docker#using-docker-compose) configuration +that will get you Reth, Lighthouse (Consensus Client), Prometheus and Grafana running and syncing with just one command. +::: diff --git a/book/vocs/docs/pages/installation/priorities.mdx b/book/vocs/docs/pages/installation/priorities.mdx new file mode 100644 index 00000000000..4494083e399 --- /dev/null +++ b/book/vocs/docs/pages/installation/priorities.mdx @@ -0,0 +1,22 @@ +--- +description: Explains Reth update priorities for user classes such as payload builders and non-payload builders. +--- + +# Update Priorities + +When publishing releases, reth will include an "Update Priority" section in the release notes, in the same manner Lighthouse does. + +The "Update Priority" section will include a table which may appear like so: + +| User Class | Priority | +| -------------------- | --------------- | +| Payload Builders | Medium Priority | +| Non-Payload Builders | Low Priority | + +To understand this table, the following terms are important: + +- _Payload builders_ are those who use reth to build and validate payloads. +- _Non-payload builders_ are those who run reth for other purposes (e.g., data analysis, RPC or applications). +- _High priority_ updates should be completed as soon as possible (e.g., hours or days). +- _Medium priority_ updates should be completed at the next convenience (e.g., days or a week). +- _Low priority_ updates should be completed in the next routine update cycle (e.g., two weeks). diff --git a/book/installation/source.md b/book/vocs/docs/pages/installation/source.mdx similarity index 72% rename from book/installation/source.md rename to book/vocs/docs/pages/installation/source.mdx index d9642c4bc48..d3d412a58f3 100644 --- a/book/installation/source.md +++ b/book/vocs/docs/pages/installation/source.mdx @@ -1,14 +1,18 @@ +--- +description: How to build, update, and troubleshoot Reth from source. +--- + # Build from Source You can build Reth on Linux, macOS, Windows, and Windows WSL2. -> **Note** -> -> Reth does **not** work on Windows WSL1. +:::note +Reth does **not** work on Windows WSL1. +::: ## Dependencies -First, **install Rust** using [rustup](https://rustup.rs/): +First, **install Rust** using [rustup](https://rustup.rs/): ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -16,19 +20,20 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh The rustup installer provides an easy way to update the Rust compiler, and works on all platforms. -> **Tips** -> -> - During installation, when prompted, enter `1` for the default installation. -> - After Rust installation completes, try running `cargo version` . If it cannot -> be found, run `source $HOME/.cargo/env`. After that, running `cargo version` should return the version, for example `cargo 1.68.2`. -> - It's generally advisable to append `source $HOME/.cargo/env` to `~/.bashrc`. +:::tip + +- During installation, when prompted, enter `1` for the default installation. +- After Rust installation completes, try running `cargo version` . If it cannot + be found, run `source $HOME/.cargo/env`. After that, running `cargo version` should return the version, for example `cargo 1.68.2`. +- It's generally advisable to append `source $HOME/.cargo/env` to `~/.bashrc`. + ::: With Rust installed, follow the instructions below to install dependencies relevant to your operating system: -- **Ubuntu**: `apt-get install libclang-dev pkg-config build-essential` -- **macOS**: `brew install llvm pkg-config` -- **Windows**: `choco install llvm` or `winget install LLVM.LLVM` +- **Ubuntu**: `apt-get install libclang-dev pkg-config build-essential` +- **macOS**: `brew install llvm pkg-config` +- **Windows**: `choco install llvm` or `winget install LLVM.LLVM` These are needed to build bindings for Reth's database. @@ -60,7 +65,7 @@ cargo build --release This will place the reth binary under `./target/release/reth`, and you can copy it to your directory of preference after that. -Compilation may take around 10 minutes. Installation was successful if `reth --help` displays the [command-line documentation](../cli/cli.md). +Compilation may take around 10 minutes. Installation was successful if `reth --help` displays the [command-line documentation](#TODO). If you run into any issues, please check the [Troubleshooting](#troubleshooting) section, or reach out to us on [Telegram](https://t.me/paradigm_reth). @@ -88,11 +93,11 @@ You can customise the compiler settings used to compile Reth via Reth includes several profiles which can be selected via the Cargo flag `--profile`. -* `release`: default for source builds, enables most optimisations while not taking too long to - compile. -* `maxperf`: default for binary releases, enables aggressive optimisations including full LTO. - Although compiling with this profile improves some benchmarks by around 20% compared to `release`, - it imposes a _significant_ cost at compile time and is only recommended if you have a fast CPU. +- `release`: default for source builds, enables most optimisations while not taking too long to + compile. +- `maxperf`: default for binary releases, enables aggressive optimisations including full LTO. + Although compiling with this profile improves some benchmarks by around 20% compared to `release`, + it imposes a _significant_ cost at compile time and is only recommended if you have a fast CPU. **Rust compiler flags** @@ -107,9 +112,10 @@ RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf Finally, some optional features are present that may improve performance, but may not very portable, and as such might not compile on your particular system. These are currently: -- `jemalloc`: replaces the default system memory allocator with [`jemalloc`](https://jemalloc.net/); this feature is unstable on Windows -- `asm-keccak`: replaces the default, pure-Rust implementation of Keccak256 with one implemented in assembly; see [the `keccak-asm` crate](https://github.com/DaniPopes/keccak-asm) for more details and supported targets -- `min-LEVEL-logs`, where `LEVEL` is one of `error`, `warn`, `info`, `debug`, `trace`: disables compilation of logs of lower level than the given one; this in general isn't that significant, and is not recommended due to the loss of debugging that the logs would provide + +- `jemalloc`: replaces the default system memory allocator with [`jemalloc`](https://jemalloc.net/); this feature is unstable on Windows +- `asm-keccak`: replaces the default, pure-Rust implementation of Keccak256 with one implemented in assembly; see [the `keccak-asm` crate](https://github.com/DaniPopes/keccak-asm) for more details and supported targets +- `min-LEVEL-logs`, where `LEVEL` is one of `error`, `warn`, `info`, `debug`, `trace`: disables compilation of logs of lower level than the given one; this in general isn't that significant, and is not recommended due to the loss of debugging that the logs would provide You can activate features by passing them to the `--features` or `-F` Cargo flag; multiple features can be activated with a space- or comma-separated list to the flag: @@ -136,7 +142,7 @@ Rust Version (MSRV) which is listed under the `rust-version` key in Reth's If compilation fails with `(signal: 9, SIGKILL: kill)`, this could mean your machine ran out of memory during compilation. If you are on Docker, consider increasing the memory of the container, or use a [pre-built -binary](../installation/binaries.md). +binary](/installation/binaries). If compilation fails in either the `keccak-asm` or `sha3-asm` crates, it is likely that your current system configuration is not supported. See the [`keccak-asm` target table](https://github.com/DaniPopes/keccak-asm?tab=readme-ov-file#support) for supported targets. @@ -147,7 +153,7 @@ _(Thanks to Sigma Prime for this section from [their Lighthouse book](https://li ### Bus error (WSL2) -In WSL 2 on Windows, the default virtual disk size is set to 1TB. +In WSL 2 on Windows, the default virtual disk size is set to 1TB. You must increase the allocated disk size for your WSL2 instance before syncing reth. diff --git a/book/vocs/docs/pages/introduction/contributing.mdx b/book/vocs/docs/pages/introduction/contributing.mdx new file mode 100644 index 00000000000..63fc5987153 --- /dev/null +++ b/book/vocs/docs/pages/introduction/contributing.mdx @@ -0,0 +1,258 @@ +# Contributing to Reth + +Reth has docs specifically geared for developers and contributors, including documentation on the structure and architecture of reth, the general workflow we employ, and other useful tips. + +## Getting Help + +Need support or have questions? Open a github issue and/or join the TG chat: + +- **GitHub Issues**: [Open an issue](https://github.com/paradigmxyz/reth/issues/new) for bugs or feature requests +- **Telegram Chat**: [Join our Telegram](https://t.me/paradigm_reth) for real-time support and discussions + +## Repository and Project Structure + +Reth is organized as a modular codebase with clear separation and a contributor friendly architecture, you can read about it in detail [here](https://github.com/paradigmxyz/reth/tree/main/docs). Here's the TL;DR: + +### Design + +Reth follows a modular architecture where each component can be used independently: + +- **Consensus**: Block validation and consensus rules +- **Storage**: Hybrid database with MDBX + static files +- **Networking**: P2P networking stack +- **RPC**: JSON-RPC server implementation +- **Engine**: Consensus layer integration +- **EVM**: Transaction execution +- **Node Builder**: High-level orchestration + +### Crates + +The repository is organized into focused crates under `/crates/`: + +``` +crates/ +├── consensus/ # Consensus and validation logic +├── storage/ # Database and storage implementations +├── net/ # Networking components +├── rpc/ # JSON-RPC server and APIs +├── engine/ # Engine API and consensus integration +├── evm/ # EVM execution +├── node/ # Node building and orchestration +├── ethereum/ # Ethereum-specific implementations +├── optimism/ # Optimism L2 support +└── ... +``` + +## Workflow: The Lifecycle of PRs + +### 1. Before You Start + +- Check existing issues to avoid duplicate work +- For large features, open an issue first to discuss the approach +- Fork the repository and create a feature branch + +### 2. Development Process + +#### Setting Up Your Environment + +```bash +# Clone your fork +git clone https://github.com/YOUR_USERNAME/reth.git +cd reth + +# Install dependencies and tools +# Use nightly Rust for formatting +rustup install nightly +rustup component add rustfmt --toolchain nightly + +# Run the validation suite +make pr +``` + +#### Code Style and Standards + +- **Formatting**: Use nightly rustfmt (`cargo +nightly fmt`) +- **Linting**: All clippy warnings must be addressed +- **Documentation**: Add doc comments for public APIs +- **Testing**: Include appropriate tests for your changes + +#### Recommended VS Code Settings + +Install the `rust-analyzer` extension and use these settings for the best development experience: + +```json +{ + "rust-analyzer.rustfmt.overrideCommand": ["rustfmt", "+nightly"], + "rust-analyzer.check.overrideCommand": [ + "cargo", + "clippy", + "--workspace", + "--message-format=json", + "--all-targets", + "--all-features" + ] +} +``` + +### 3. Testing Your Changes + +Reth uses comprehensive testing at multiple levels: + +#### Unit Tests + +Test specific functions and components: + +```bash +cargo test --package reth-ethereum-consensus +``` + +#### Integration Tests + +Test component interactions: + +```bash +cargo test --test integration_tests +``` + +#### Full Test Suite + +Run all tests including Ethereum Foundation tests: + +```bash +make test +``` + +#### Validation Suite + +Before submitting, always run: + +```bash +make pr +``` + +This runs: + +- Code formatting checks +- Clippy linting +- Documentation generation +- Full test suite + +### 4. Submitting Your PR + +#### Draft PRs for Large Features + +For substantial changes, open a draft PR early to get feedback on the approach. + +#### PR Requirements + +- [ ] Clear, descriptive title and description +- [ ] Tests for new functionality +- [ ] Documentation updates if needed +- [ ] All CI checks passing +- [ ] Commit messages follow conventional format + +#### Commit Message Format + +``` +type: brief description + +More detailed explanation if needed. + +- feat: new feature +- fix: bug fix +- docs: documentation changes +- refactor: code refactoring +- test: adding tests +- chore: maintenance tasks +``` + +### 5. Review Process + +#### Who Can Review + +Any community member can review PRs. We encourage participation from all skill levels. + +#### What Reviewers Look For + +- **Does the change improve Reth?** +- **Are there clear bugs or issues?** +- **Are commit messages clear and descriptive?** +- **Is the code well-tested?** +- **Is documentation updated appropriately?** + +#### Review Guidelines + +- Be constructive and respectful +- Provide specific, actionable feedback +- Focus on significant issues first +- Acknowledge good work and improvements + +## Releases: How Reth is Released + +### Release Schedule + +- **Regular releases**: Following semantic versioning +- **Security releases**: As needed for critical vulnerabilities +- **Pre-releases**: For testing major changes + +### Release Process + +1. **Version bump**: Update version numbers across crates +2. **Changelog**: Update `CHANGELOG.md` with notable changes +3. **Testing**: Final validation on testnet and mainnet +4. **Tagging**: Create release tags and GitHub releases +5. **Distribution**: Update package registries and Docker images + +### Release Criteria + +- All CI checks passing +- No known critical bugs +- Documentation up to date +- Backwards compatibility considerations addressed + +## Ways to Contribute + +### 💡 Feature Requests + +For feature requests, please include: + +- **Detailed explanation**: What should the feature do? +- **Context and motivation**: Why is this feature needed? +- **Examples**: How would it be used? +- **Similar tools**: References to similar functionality elsewhere + +### 📝 Documentation + +Documentation improvements are always welcome: + +- Add missing documentation +- Improve code examples +- Create tutorials or guides + +### 🔧 Code Contributions + +Contributing code changes: + +- Fix bugs identified in issues +- Implement requested features +- Improve performance +- Refactor for better maintainability + +## Code of Conduct + +Reth follows the [Rust Code of Conduct](https://www.rust-lang.org/conduct.html). We are committed to providing a welcoming and inclusive environment for all contributors. + +### Our Standards + +- Be respectful and constructive +- Focus on what's best for the community +- Show empathy towards other contributors +- Accept constructive criticism gracefully + +### Reporting Issues + +If you experience or witness behavior that violates our code of conduct, please report it to [georgios@paradigm.xyz](mailto:georgios@paradigm.xyz). + +:::note +Also read [CONTRIBUTING.md](https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md) for in depth guidelines. +::: diff --git a/book/vocs/docs/pages/introduction/why-reth.mdx b/book/vocs/docs/pages/introduction/why-reth.mdx new file mode 100644 index 00000000000..f140c0e3128 --- /dev/null +++ b/book/vocs/docs/pages/introduction/why-reth.mdx @@ -0,0 +1,50 @@ +--- +description: Why Reth is the future of Ethereum infrastructure - powering everything from production staking to cutting-edge L2s and ZK applications. +--- + +# Why Reth? + +Reth is more than just another Ethereum client—it's the foundation upon which the next generation of blockchain infrastructure is being built. From powering production staking environments at institutions like Coinbase to enabling cutting-edge L2 sequencers and ZK applications, Reth represents the convergence of security, performance, and extensibility that the ecosystem demands. + +Every piece of crypto infrastructure will be touching Reth one way or another. Here's why the world's leading developers and institutions are choosing Reth as their node of choice. + +## Institutional-Grade Security + +Reth secures real value on Ethereum mainnet today, trusted by institutions like [Coinbase](https://x.com/CoinbasePltfrm/status/1933546893742579890) for production staking infrastructure. It powers RPC providers such as Alchemy. + +## Future Proof Performance + +Reth pushes the performance frontier across every dimension, from L2 sequencers to MEV block building. + +- **L2 Sequencer Performance**: Used by [Base](https://www.base.org/), other production L2s and also rollup-as-a-service providers such as [Conduit](https://conduit.xyz) which require high throughput and fast block times. +- **MEV & Block Building**: [rbuilder](https://github.com/flashbots/rbuilder) is an open-source implementation of a block builder built on Reth due to developer friendless and blazing fast performance. + +## Infinitely Customizable + +Reth's modular architecture means you are not locked into someone else's design decisions—build exactly the chain you need. + +- **Component-Based Design**: Swap out consensus, execution, mempool, or networking modules independently +- **Custom Transaction Types**: Build specialized DeFi chains, and unique economic models +- **Rapid Development**: Reth SDK accelerates custom blockchain development with pre-built components + +## ZK & Stateless Ready + +Reth is designed from the ground up to excel in the zero-knowledge future with stateless execution and modular architecture. + +[SP1](https://github.com/succinctlabs/sp1), a zkVM for proving arbitrary Rust programs, and [Ress](https://www.paradigm.xyz/2025/03/stateless-reth-nodes), an experimental stateless node, demonstrate how Reth enables scalable zero-knowledge applications for Ethereum. + +## Thriving Open Source Ecosystem + +The most important factor in Reth's success is our vibrant open source community building the future together. + +500+ geo-distributed developers from leading companies and academia have played a role to build Reth into what it is today. + +## Join the community + +Reth isn't just a tool—it's a movement toward better blockchain infrastructure. Whether you're running a validator, building the next generation of L2s, or creating cutting-edge ZK applications, Reth provides the foundation you need to succeed. + +**Ready to build the future?** + +- [Get Started](/run/ethereum) with running your first Reth node +- [Explore the SDK](/sdk/overview) to build custom blockchain infrastructure +- [Join the Community](https://github.com/paradigmxyz/reth) and contribute to the future of Ethereum diff --git a/book/jsonrpc/admin.md b/book/vocs/docs/pages/jsonrpc/admin.mdx similarity index 79% rename from book/jsonrpc/admin.md rename to book/vocs/docs/pages/jsonrpc/admin.mdx index b85cd194b6d..cf1ef29c05b 100644 --- a/book/jsonrpc/admin.md +++ b/book/vocs/docs/pages/jsonrpc/admin.mdx @@ -1,10 +1,13 @@ +--- +description: Admin API for node configuration and peer management. +--- # `admin` Namespace The `admin` API allows you to configure your node, including adding and removing peers. -> **Note** -> -> As this namespace can configure your node at runtime, it is generally **not advised** to expose it publicly. +:::note +As this namespace can configure your node at runtime, it is generally **not advised** to expose it publicly. +::: ## `admin_addPeer` @@ -13,7 +16,7 @@ Add the given peer to the current peer set of the node. The method accepts a single argument, the [`enode`][enode] URL of the remote peer to connect to, and returns a `bool` indicating whether the peer was accepted or not. | Client | Method invocation | -|--------|------------------------------------------------| +| ------ | ---------------------------------------------- | | RPC | `{"method": "admin_addPeer", "params": [url]}` | ### Example @@ -27,9 +30,9 @@ The method accepts a single argument, the [`enode`][enode] URL of the remote pee Disconnects from a peer if the connection exists. Returns a `bool` indicating whether the peer was successfully removed or not. -| Client | Method invocation | -|--------|----------------------------------------------------| -| RPC | `{"method": "admin_removePeer", "params": [url]}` | +| Client | Method invocation | +| ------ | ------------------------------------------------- | +| RPC | `{"method": "admin_removePeer", "params": [url]}` | ### Example @@ -45,7 +48,7 @@ Adds the given peer to a list of trusted peers, which allows the peer to always It returns a `bool` indicating whether the peer was added to the list or not. | Client | Method invocation | -|--------|-------------------------------------------------------| +| ------ | ----------------------------------------------------- | | RPC | `{"method": "admin_addTrustedPeer", "params": [url]}` | ### Example @@ -62,7 +65,7 @@ Removes a remote node from the trusted peer set, but it does not disconnect it a Returns true if the peer was successfully removed. | Client | Method invocation | -|--------|----------------------------------------------------------| +| ------ | -------------------------------------------------------- | | RPC | `{"method": "admin_removeTrustedPeer", "params": [url]}` | ### Example @@ -79,7 +82,7 @@ Returns all information known about the running node. These include general information about the node itself, as well as what protocols it participates in, its IP and ports. | Client | Method invocation | -|--------|--------------------------------| +| ------ | ------------------------------ | | RPC | `{"method": "admin_nodeInfo"}` | ### Example @@ -121,9 +124,9 @@ Like other subscription methods, this returns the ID of the subscription, which To unsubscribe from peer events, call `admin_peerEvents_unsubscribe` with the subscription ID. -| Client | Method invocation | -|--------|-------------------------------------------------------| -| RPC | `{"method": "admin_peerEvents", "params": []}` | +| Client | Method invocation | +| ------ | ------------------------------------------------------------ | +| RPC | `{"method": "admin_peerEvents", "params": []}` | | RPC | `{"method": "admin_peerEvents_unsubscribe", "params": [id]}` | ### Event Types @@ -132,20 +135,20 @@ The subscription emits events with the following structure: ```json { - "jsonrpc": "2.0", - "method": "admin_subscription", - "params": { - "subscription": "0xcd0c3e8af590364c09d0fa6a1210faf5", - "result": { - "type": "add", // or "drop", "error" - "peer": { - "id": "44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d", - "enode": "enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@192.168.1.1:30303", - "addr": "192.168.1.1:30303" - }, - "error": "reason for disconnect or error" // only present for "drop" and "error" events + "jsonrpc": "2.0", + "method": "admin_subscription", + "params": { + "subscription": "0xcd0c3e8af590364c09d0fa6a1210faf5", + "result": { + "type": "add", // or "drop", "error" + "peer": { + "id": "44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d", + "enode": "enode://44826a5d6a55f88a18298bca4773fca5749cdc3a5c9f308aa7d810e9b31123f3e7c5fba0b1d70aac5308426f47df2a128a6747040a3815cc7dd7167d03be320d@192.168.1.1:30303", + "addr": "192.168.1.1:30303" + }, + "error": "reason for disconnect or error" // only present for "drop" and "error" events + } } - } } ``` diff --git a/book/jsonrpc/debug.md b/book/vocs/docs/pages/jsonrpc/debug.mdx similarity index 80% rename from book/jsonrpc/debug.md rename to book/vocs/docs/pages/jsonrpc/debug.mdx index 7965e2e0d50..aa3a47685c6 100644 --- a/book/jsonrpc/debug.md +++ b/book/vocs/docs/pages/jsonrpc/debug.mdx @@ -1,3 +1,6 @@ +--- +description: Debug API for inspecting Ethereum state and traces. +--- # `debug` Namespace The `debug` API provides several methods to inspect the Ethereum state, including Geth-style traces. @@ -7,7 +10,7 @@ The `debug` API provides several methods to inspect the Ethereum state, includin Returns an RLP-encoded header. | Client | Method invocation | -|--------|-------------------------------------------------------| +| ------ | ----------------------------------------------------- | | RPC | `{"method": "debug_getRawHeader", "params": [block]}` | ## `debug_getRawBlock` @@ -15,7 +18,7 @@ Returns an RLP-encoded header. Retrieves and returns the RLP encoded block by number, hash or tag. | Client | Method invocation | -|--------|------------------------------------------------------| +| ------ | ---------------------------------------------------- | | RPC | `{"method": "debug_getRawBlock", "params": [block]}` | ## `debug_getRawTransaction` @@ -23,7 +26,7 @@ Retrieves and returns the RLP encoded block by number, hash or tag. Returns an EIP-2718 binary-encoded transaction. | Client | Method invocation | -|--------|--------------------------------------------------------------| +| ------ | ------------------------------------------------------------ | | RPC | `{"method": "debug_getRawTransaction", "params": [tx_hash]}` | ## `debug_getRawReceipts` @@ -31,7 +34,7 @@ Returns an EIP-2718 binary-encoded transaction. Returns an array of EIP-2718 binary-encoded receipts. | Client | Method invocation | -|--------|---------------------------------------------------------| +| ------ | ------------------------------------------------------- | | RPC | `{"method": "debug_getRawReceipts", "params": [block]}` | ## `debug_getBadBlocks` @@ -39,7 +42,7 @@ Returns an array of EIP-2718 binary-encoded receipts. Returns an array of recent bad blocks that the client has seen on the network. | Client | Method invocation | -|--------|--------------------------------------------------| +| ------ | ------------------------------------------------ | | RPC | `{"method": "debug_getBadBlocks", "params": []}` | ## `debug_traceChain` @@ -47,7 +50,7 @@ Returns an array of recent bad blocks that the client has seen on the network. Returns the structured logs created during the execution of EVM between two blocks (excluding start) as a JSON object. | Client | Method invocation | -|--------|----------------------------------------------------------------------| +| ------ | -------------------------------------------------------------------- | | RPC | `{"method": "debug_traceChain", "params": [start_block, end_block]}` | ## `debug_traceBlock` @@ -57,11 +60,11 @@ The `debug_traceBlock` method will return a full stack trace of all invoked opco This expects an RLP-encoded block. > **Note** -> +> > The parent of this block must be present, or it will fail. | Client | Method invocation | -|--------|---------------------------------------------------------| +| ------ | ------------------------------------------------------- | | RPC | `{"method": "debug_traceBlock", "params": [rlp, opts]}` | ## `debug_traceBlockByHash` @@ -69,7 +72,7 @@ This expects an RLP-encoded block. Similar to [`debug_traceBlock`](#debug_traceblock), `debug_traceBlockByHash` accepts a block hash and will replay the block that is already present in the database. | Client | Method invocation | -|--------|----------------------------------------------------------------------| +| ------ | -------------------------------------------------------------------- | | RPC | `{"method": "debug_traceBlockByHash", "params": [block_hash, opts]}` | ## `debug_traceBlockByNumber` @@ -77,15 +80,15 @@ Similar to [`debug_traceBlock`](#debug_traceblock), `debug_traceBlockByHash` acc Similar to [`debug_traceBlockByHash`](#debug_traceblockbyhash), `debug_traceBlockByNumber` accepts a block number and will replay the block that is already present in the database. | Client | Method invocation | -|--------|--------------------------------------------------------------------------| +| ------ | ------------------------------------------------------------------------ | | RPC | `{"method": "debug_traceBlockByNumber", "params": [block_number, opts]}` | ## `debug_traceTransaction` The `debug_traceTransaction` debugging method will attempt to run the transaction in the exact same manner as it was executed on the network. It will replay any transaction that may have been executed prior to this one before it will finally attempt to execute the transaction that corresponds to the given hash. -| Client | Method invocation | -|--------|-------------------------------------------------------------| +| Client | Method invocation | +| ------ | ----------------------------------------------------------------- | | RPC | `{"method": "debug_traceTransaction", "params": [tx_hash, opts]}` | ## `debug_traceCall` @@ -97,5 +100,5 @@ The first argument (just as in `eth_call`) is a transaction request. The block can optionally be specified either by hash or by number as the second argument. | Client | Method invocation | -|--------|-----------------------------------------------------------------------| +| ------ | --------------------------------------------------------------------- | | RPC | `{"method": "debug_traceCall", "params": [call, block_number, opts]}` | diff --git a/book/jsonrpc/eth.md b/book/vocs/docs/pages/jsonrpc/eth.mdx similarity index 72% rename from book/jsonrpc/eth.md rename to book/vocs/docs/pages/jsonrpc/eth.mdx index 0a3003c4052..052beb4c7b9 100644 --- a/book/jsonrpc/eth.md +++ b/book/vocs/docs/pages/jsonrpc/eth.mdx @@ -1,3 +1,7 @@ +--- +description: Standard Ethereum JSON-RPC API methods. +--- + # `eth` Namespace Documentation for the API methods in the `eth` namespace can be found on [ethereum.org](https://ethereum.org/en/developers/docs/apis/json-rpc/). diff --git a/book/jsonrpc/intro.md b/book/vocs/docs/pages/jsonrpc/intro.mdx similarity index 70% rename from book/jsonrpc/intro.md rename to book/vocs/docs/pages/jsonrpc/intro.mdx index 6f9b894988d..dac173142a6 100644 --- a/book/jsonrpc/intro.md +++ b/book/vocs/docs/pages/jsonrpc/intro.mdx @@ -1,3 +1,7 @@ +--- +description: Overview of Reth's JSON-RPC API and namespaces. +--- + # JSON-RPC You can interact with Reth over JSON-RPC. Reth supports all standard Ethereum JSON-RPC API methods. @@ -12,22 +16,21 @@ Each namespace must be explicitly enabled. The methods are grouped into namespaces, which are listed below: -| Namespace | Description | Sensitive | -|-------------------------|--------------------------------------------------------------------------------------------------------|-----------| -| [`eth`](./eth.md) | The `eth` API allows you to interact with Ethereum. | Maybe | -| [`web3`](./web3.md) | The `web3` API provides utility functions for the web3 client. | No | -| [`net`](./net.md) | The `net` API provides access to network information of the node. | No | -| [`txpool`](./txpool.md) | The `txpool` API allows you to inspect the transaction pool. | No | -| [`debug`](./debug.md) | The `debug` API provides several methods to inspect the Ethereum state, including Geth-style traces. | No | -| [`trace`](./trace.md) | The `trace` API provides several methods to inspect the Ethereum state, including Parity-style traces. | No | -| [`admin`](./admin.md) | The `admin` API allows you to configure your node. | **Yes** | -| [`rpc`](./rpc.md) | The `rpc` API provides information about the RPC server and its modules. | No | +| Namespace | Description | Sensitive | +| -------------------- | ------------------------------------------------------------------------------------------------------ | --------- | +| [`eth`](./eth) | The `eth` API allows you to interact with Ethereum. | Maybe | +| [`web3`](./web3) | The `web3` API provides utility functions for the web3 client. | No | +| [`net`](./net) | The `net` API provides access to network information of the node. | No | +| [`txpool`](./txpool) | The `txpool` API allows you to inspect the transaction pool. | No | +| [`debug`](./debug) | The `debug` API provides several methods to inspect the Ethereum state, including Geth-style traces. | No | +| [`trace`](./trace) | The `trace` API provides several methods to inspect the Ethereum state, including Parity-style traces. | No | +| [`admin`](./admin) | The `admin` API allows you to configure your node. | **Yes** | +| [`rpc`](./rpc) | The `rpc` API provides information about the RPC server and its modules. | No | Note that some APIs are sensitive, since they can be used to configure your node (`admin`), or access accounts stored on the node (`eth`). Generally, it is advisable to not expose any JSONRPC namespace publicly, unless you know what you are doing. - ## Transports Reth supports HTTP, WebSockets and IPC. @@ -90,10 +93,10 @@ Because WebSockets are bidirectional, nodes can push events to clients, which en The configuration of the WebSocket server follows the same pattern as the HTTP server: -- Enable it using `--ws` -- Configure the server address by passing `--ws.addr` and `--ws.port` (default `8546`) -- Configure cross-origin requests using `--ws.origins` -- Enable APIs using `--ws.api` +- Enable it using `--ws` +- Configure the server address by passing `--ws.addr` and `--ws.port` (default `8546`) +- Configure cross-origin requests using `--ws.origins` +- Enable APIs using `--ws.api` ### IPC diff --git a/book/jsonrpc/net.md b/book/vocs/docs/pages/jsonrpc/net.mdx similarity index 82% rename from book/jsonrpc/net.md rename to book/vocs/docs/pages/jsonrpc/net.mdx index ac40c75b2ab..145b9c27676 100644 --- a/book/jsonrpc/net.md +++ b/book/vocs/docs/pages/jsonrpc/net.mdx @@ -1,3 +1,7 @@ +--- +description: net_ namespace for Ethereum nodes. +--- + # `net` Namespace The `net` API provides information about the networking component of the node. @@ -7,7 +11,7 @@ The `net` API provides information about the networking component of the node. Returns a `bool` indicating whether or not the node is listening for network connections. | Client | Method invocation | -|--------|---------------------------------------------| +| ------ | ------------------------------------------- | | RPC | `{"method": "net_listening", "params": []}` | ### Example @@ -22,7 +26,7 @@ Returns a `bool` indicating whether or not the node is listening for network con Returns the number of peers connected to the node. | Client | Method invocation | -|--------|---------------------------------------------| +| ------ | ------------------------------------------- | | RPC | `{"method": "net_peerCount", "params": []}` | ### Example @@ -37,7 +41,7 @@ Returns the number of peers connected to the node. Returns the network ID (e.g. 1 for mainnet) | Client | Method invocation | -|--------|-------------------------------------------| +| ------ | ----------------------------------------- | | RPC | `{"method": "net_version", "params": []}` | ### Example @@ -45,4 +49,4 @@ Returns the network ID (e.g. 1 for mainnet) ```js // > {"jsonrpc":"2.0","id":1,"method":"net_version","params":[]} {"jsonrpc":"2.0","id":1,"result":1} -``` \ No newline at end of file +``` diff --git a/book/jsonrpc/rpc.md b/book/vocs/docs/pages/jsonrpc/rpc.mdx similarity index 91% rename from book/jsonrpc/rpc.md rename to book/vocs/docs/pages/jsonrpc/rpc.mdx index 0a4739718be..c85babcfe3c 100644 --- a/book/jsonrpc/rpc.md +++ b/book/vocs/docs/pages/jsonrpc/rpc.mdx @@ -1,3 +1,7 @@ +--- +description: rpc_ namespace for retrieving server information such as enabled namespaces +--- + # `rpc` Namespace The `rpc` API provides methods to get information about the RPC server itself, such as the enabled namespaces. @@ -7,7 +11,7 @@ The `rpc` API provides methods to get information about the RPC server itself, s Lists the enabled RPC namespaces and the versions of each. | Client | Method invocation | -|--------|-------------------------------------------| +| ------ | ----------------------------------------- | | RPC | `{"method": "rpc_modules", "params": []}` | ### Example diff --git a/book/jsonrpc/trace.md b/book/vocs/docs/pages/jsonrpc/trace.mdx similarity index 86% rename from book/jsonrpc/trace.md rename to book/vocs/docs/pages/jsonrpc/trace.mdx index ba0f2490b57..38157e44230 100644 --- a/book/jsonrpc/trace.md +++ b/book/vocs/docs/pages/jsonrpc/trace.mdx @@ -1,33 +1,37 @@ +--- +description: Trace API for inspecting Ethereum state and transactions. +--- + # `trace` Namespace - +{/* TODO: We should probably document the format of the traces themselves, OE does not do that */} The `trace` API provides several methods to inspect the Ethereum state, including Parity-style traces. -A similar module exists (with other debug functions) with Geth-style traces ([`debug`](./debug.md)). +A similar module exists (with other debug functions) with Geth-style traces ([`debug`](./debug)). The `trace` API gives deeper insight into transaction processing. There are two types of methods in this API: -- **Ad-hoc tracing APIs** for performing diagnostics on calls or transactions (historical or hypothetical). -- **Transaction-trace filtering APIs** for getting full externality traces on any transaction executed by reth. +- **Ad-hoc tracing APIs** for performing diagnostics on calls or transactions (historical or hypothetical). +- **Transaction-trace filtering APIs** for getting full externality traces on any transaction executed by reth. ## Ad-hoc tracing APIs Ad-hoc tracing APIs allow you to perform diagnostics on calls or transactions (historical or hypothetical), including: -- Transaction traces (`trace`) -- VM traces (`vmTrace`) -- State difference traces (`stateDiff`) +- Transaction traces (`trace`) +- VM traces (`vmTrace`) +- State difference traces (`stateDiff`) The ad-hoc tracing APIs are: -- [`trace_call`](#trace_call) -- [`trace_callMany`](#trace_callmany) -- [`trace_rawTransaction`](#trace_rawtransaction) -- [`trace_replayBlockTransactions`](#trace_replayblocktransactions) -- [`trace_replayTransaction`](#trace_replaytransaction) +- [`trace_call`](#trace_call) +- [`trace_callMany`](#trace_callmany) +- [`trace_rawTransaction`](#trace_rawtransaction) +- [`trace_replayBlockTransactions`](#trace_replayblocktransactions) +- [`trace_replayTransaction`](#trace_replaytransaction) ## Transaction-trace filtering APIs @@ -37,10 +41,10 @@ Information returned includes the execution of all contract creations, destructi The transaction trace filtering APIs are: -- [`trace_block`](#trace_block) -- [`trace_filter`](#trace_filter) -- [`trace_get`](#trace_get) -- [`trace_transaction`](#trace_transaction) +- [`trace_block`](#trace_block) +- [`trace_filter`](#trace_filter) +- [`trace_get`](#trace_get) +- [`trace_transaction`](#trace_transaction) ## `trace_call` @@ -53,7 +57,7 @@ The second parameter is an array of one or more trace types (`vmTrace`, `trace`, The third and optional parameter is a block number, block hash, or a block tag (`latest`, `finalized`, `safe`, `earliest`, `pending`). | Client | Method invocation | -|--------|-----------------------------------------------------------| +| ------ | --------------------------------------------------------- | | RPC | `{"method": "trace_call", "params": [tx, type[], block]}` | ### Example @@ -90,7 +94,7 @@ The first parameter is a list of call traces, where each call trace is of the fo The second and optional parameter is a block number, block hash, or a block tag (`latest`, `finalized`, `safe`, `earliest`, `pending`). | Client | Method invocation | -|--------|--------------------------------------------------------| +| ------ | ------------------------------------------------------ | | RPC | `{"method": "trace_call", "params": [trace[], block]}` | ### Example @@ -154,7 +158,7 @@ The second and optional parameter is a block number, block hash, or a block tag Traces a call to `eth_sendRawTransaction` without making the call, returning the traces. | Client | Method invocation | -|--------|--------------------------------------------------------| +| ------ | ------------------------------------------------------ | | RPC | `{"method": "trace_call", "params": [raw_tx, type[]]}` | ### Example @@ -187,7 +191,7 @@ Traces a call to `eth_sendRawTransaction` without making the call, returning the Replays all transactions in a block returning the requested traces for each transaction. | Client | Method invocation | -|--------|--------------------------------------------------------------------------| +| ------ | ------------------------------------------------------------------------ | | RPC | `{"method": "trace_replayBlockTransactions", "params": [block, type[]]}` | ### Example @@ -224,7 +228,7 @@ Replays all transactions in a block returning the requested traces for each tran Replays a transaction, returning the traces. | Client | Method invocation | -|--------|----------------------------------------------------------------------| +| ------ | -------------------------------------------------------------------- | | RPC | `{"method": "trace_replayTransaction", "params": [tx_hash, type[]]}` | ### Example @@ -257,7 +261,7 @@ Replays a transaction, returning the traces. Returns traces created at given block. | Client | Method invocation | -|--------|------------------------------------------------| +| ------ | ---------------------------------------------- | | RPC | `{"method": "trace_block", "params": [block]}` | ### Example @@ -300,17 +304,17 @@ Returns traces matching given filter. Filters are objects with the following properties: -- `fromBlock`: Returns traces from the given block (a number, hash, or a tag like `latest`). -- `toBlock`: Returns traces to the given block. -- `fromAddress`: Sent from these addresses -- `toAddress`: Sent to these addresses -- `after`: The offset trace number -- `count`: The number of traces to display in a batch +- `fromBlock`: Returns traces from the given block (a number, hash, or a tag like `latest`). +- `toBlock`: Returns traces to the given block. +- `fromAddress`: Sent from these addresses +- `toAddress`: Sent to these addresses +- `after`: The offset trace number +- `count`: The number of traces to display in a batch All properties are optional. | Client | Method invocation | -|--------|--------------------------------------------------| +| ------ | ------------------------------------------------ | | RPC | `{"method": "trace_filter", "params": [filter]}` | ### Example @@ -352,7 +356,7 @@ All properties are optional. Returns trace at given position. | Client | Method invocation | -|--------|----------------------------------------------------------| +| ------ | -------------------------------------------------------- | | RPC | `{"method": "trace_get", "params": [tx_hash,indices[]]}` | ### Example @@ -393,7 +397,7 @@ Returns trace at given position. Returns all traces of given transaction | Client | Method invocation | -|--------|--------------------------------------------------------| +| ------ | ------------------------------------------------------ | | RPC | `{"method": "trace_transaction", "params": [tx_hash]}` | ### Example @@ -430,4 +434,4 @@ Returns all traces of given transaction ... ] } -``` \ No newline at end of file +``` diff --git a/book/jsonrpc/txpool.md b/book/vocs/docs/pages/jsonrpc/txpool.mdx similarity index 81% rename from book/jsonrpc/txpool.md rename to book/vocs/docs/pages/jsonrpc/txpool.mdx index cb9e9c0e69d..57f89c643c6 100644 --- a/book/jsonrpc/txpool.md +++ b/book/vocs/docs/pages/jsonrpc/txpool.mdx @@ -1,3 +1,7 @@ +--- +description: API for inspecting the transaction pool. +--- + # `txpool` Namespace The `txpool` API allows you to inspect the transaction pool. @@ -9,7 +13,7 @@ Returns the details of all transactions currently pending for inclusion in the n See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool-content) for more details | Client | Method invocation | -|--------|----------------------------------------------| +| ------ | -------------------------------------------- | | RPC | `{"method": "txpool_content", "params": []}` | ## `txpool_contentFrom` @@ -19,7 +23,7 @@ Retrieves the transactions contained within the txpool, returning pending as wel See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool-contentfrom) for more details | Client | Method invocation | -|--------|---------------------------------------------------------| +| ------ | ------------------------------------------------------- | | RPC | `{"method": "txpool_contentFrom", "params": [address]}` | ## `txpool_inspect` @@ -29,7 +33,7 @@ Returns a summary of all the transactions currently pending for inclusion in the See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool-inspect) for more details | Client | Method invocation | -|--------|----------------------------------------------| +| ------ | -------------------------------------------- | | RPC | `{"method": "txpool_inspect", "params": []}` | ## `txpool_status` @@ -39,5 +43,5 @@ Returns the number of transactions currently pending for inclusion in the next b See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool-status) for more details | Client | Method invocation | -|--------|---------------------------------------------| -| RPC | `{"method": "txpool_status", "params": []}` | \ No newline at end of file +| ------ | ------------------------------------------- | +| RPC | `{"method": "txpool_status", "params": []}` | diff --git a/book/jsonrpc/web3.md b/book/vocs/docs/pages/jsonrpc/web3.mdx similarity index 83% rename from book/jsonrpc/web3.md rename to book/vocs/docs/pages/jsonrpc/web3.mdx index 8221e5c2507..f1eb68bcafe 100644 --- a/book/jsonrpc/web3.md +++ b/book/vocs/docs/pages/jsonrpc/web3.mdx @@ -1,3 +1,7 @@ +--- +description: Web3 API utility methods for Ethereum clients. +--- + # `web3` Namespace The `web3` API provides utility functions for the web3 client. @@ -6,9 +10,8 @@ The `web3` API provides utility functions for the web3 client. Get the web3 client version. - | Client | Method invocation | -|--------|------------------------------------| +| ------ | ---------------------------------- | | RPC | `{"method": "web3_clientVersion"}` | ### Example @@ -23,7 +26,7 @@ Get the web3 client version. Get the Keccak-256 hash of the given data. | Client | Method invocation | -|--------|----------------------------------------------| +| ------ | -------------------------------------------- | | RPC | `{"method": "web3_sha3", "params": [bytes]}` | ### Example @@ -36,4 +39,4 @@ Get the Keccak-256 hash of the given data. ```js // > {"jsonrpc":"2.0","id":1,"method":"web3_sha3","params":["0x7275737420697320617765736f6d65"]} {"jsonrpc":"2.0","id":1,"result":"0xe421b3428564a5c509ac118bad93a3b84485ec3f927e214b0c4c23076d4bc4e0"} -``` \ No newline at end of file +``` diff --git a/book/intro.md b/book/vocs/docs/pages/overview.mdx similarity index 72% rename from book/intro.md rename to book/vocs/docs/pages/overview.mdx index 6abd3da7acf..e41ca3ad83a 100644 --- a/book/intro.md +++ b/book/vocs/docs/pages/overview.mdx @@ -1,15 +1,14 @@ -# Reth Book -_Documentation for Reth users and developers._ +--- +description: Reth - A secure, performant, and modular blockchain SDK and Ethereum node. +--- -[![Telegram Chat][tg-badge]][tg-url] +# Reth [Documentation for Reth users and developers] Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is an **Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient.** Reth is production ready, and suitable for usage in mission-critical environments such as staking or high-uptime services. We also actively recommend professional node operators to switch to Reth in production for performance and cost reasons in use cases where high performance with great margins is required such as RPC, MEV, Indexing, Simulations, and P2P activities. - - - +![Reth](https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-prod.png) ## What is this about? @@ -60,8 +59,9 @@ We envision that Reth will be configurable enough for the tradeoffs that each te ## Who is this for? Reth is a new Ethereum full node that allows users to sync and interact with the entire blockchain, including its historical state if in archive mode. -- Full node: It can be used as a full node, which stores and processes the entire blockchain, validates blocks and transactions, and participates in the consensus process. -- Archive node: It can also be used as an archive node, which stores the entire history of the blockchain and is useful for applications that need access to historical data. + +- Full node: It can be used as a full node, which stores and processes the entire blockchain, validates blocks and transactions, and participates in the consensus process. +- Archive node: It can also be used as an archive node, which stores the entire history of the blockchain and is useful for applications that need access to historical data. As a data engineer/analyst, or as a data indexer, you'll want to use Archive mode. For all other use cases where historical access is not needed, you can use Full mode. @@ -79,21 +79,35 @@ We have completed an audit of the [Reth v1.0.0-rc.2](https://github.com/paradigm [Revm](https://github.com/bluealloy/revm) (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. +## Reth Metrics + +We operate several public Reth nodes across different networks. You can monitor their performance metrics through our public Grafana dashboards: + +| Name | Chain ID | Type | Grafana | +| -------- | -------- | ------- | ---------------------------------------------------------------------------------- | +| Ethereum | 1 | Full | [View](https://reth.ithaca.xyz/public-dashboards/23ceb3bd26594e349aaaf2bcf336d0d4) | +| Ethereum | 1 | Archive | [View](https://reth.ithaca.xyz/public-dashboards/a49fa110dc9149298fa6763d5c89c8c0) | +| Base | 8453 | Archive | [View](https://reth.ithaca.xyz/public-dashboards/b3e9f2e668ee4b86960b7fac691b5e64) | +| OP | 10 | Archive | [View](https://reth.ithaca.xyz/public-dashboards/aa32f6c39a664f9aa371399b59622527) | + +:::tip +Want to set up metrics for your own Reth node? Check out our [monitoring guide](/run/monitoring) to learn how to configure Prometheus metrics and build your own dashboards. +::: ## Sections Here are some useful sections to jump to: -- Install Reth by following the [guide](./installation/installation.md). -- Sync your node on any [official network](./run/run-a-node.md). -- View [statistics and metrics](./run/observability.md) about your node. -- Query the [JSON-RPC](./jsonrpc/intro.md) using Foundry's `cast` or `curl`. -- Set up your [development environment and contribute](./developers/contribute.md)! +- Install Reth by following the [guide](/installation/overview). +- Sync your node on any [official network](/run/overview). +- View [statistics and metrics](/run/monitoring) about your node. +- Query the [JSON-RPC](/jsonrpc/intro) using Foundry's `cast` or `curl`. +- Set up your [development environment and contribute](/introduction/contributing)! -> 📖 **About this book** -> -> The book is continuously rendered [here](https://paradigmxyz.github.io/reth/)! -> You can contribute to this book on [GitHub][gh-book]. +:::note +The book is continuously rendered [here](https://reth.rs)! +You can contribute to the docs on [GitHub][gh-book]. +::: [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fparadigm%5Freth [tg-url]: https://t.me/paradigm_reth diff --git a/book/run/config.md b/book/vocs/docs/pages/run/configuration.mdx similarity index 90% rename from book/run/config.md rename to book/vocs/docs/pages/run/configuration.mdx index bb28d855de8..8f34cfc691f 100644 --- a/book/run/config.md +++ b/book/vocs/docs/pages/run/configuration.mdx @@ -1,32 +1,36 @@ +--- +description: How to configure Reth using reth.toml and its options. +--- + # Configuring Reth Reth places a configuration file named `reth.toml` in the data directory specified when starting the node. It is written in the [TOML] format. The default data directory is platform dependent: -- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` -- Windows: `{FOLDERID_RoamingAppData}/reth/` -- macOS: `$HOME/Library/Application Support/reth/` +- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` +- Windows: `{FOLDERID_RoamingAppData}/reth/` +- macOS: `$HOME/Library/Application Support/reth/` The configuration file contains the following sections: -- [`[stages]`](#the-stages-section) -- Configuration of the individual sync stages - - [`headers`](#headers) - - [`bodies`](#bodies) - - [`sender_recovery`](#sender_recovery) - - [`execution`](#execution) - - [`account_hashing`](#account_hashing) - - [`storage_hashing`](#storage_hashing) - - [`merkle`](#merkle) - - [`transaction_lookup`](#transaction_lookup) - - [`index_account_history`](#index_account_history) - - [`index_storage_history`](#index_storage_history) -- [`[peers]`](#the-peers-section) - - [`connection_info`](#connection_info) - - [`reputation_weights`](#reputation_weights) - - [`backoff_durations`](#backoff_durations) -- [`[sessions]`](#the-sessions-section) -- [`[prune]`](#the-prune-section) +- [`[stages]`](#the-stages-section) -- Configuration of the individual sync stages + - [`headers`](#headers) + - [`bodies`](#bodies) + - [`sender_recovery`](#sender_recovery) + - [`execution`](#execution) + - [`account_hashing`](#account_hashing) + - [`storage_hashing`](#storage_hashing) + - [`merkle`](#merkle) + - [`transaction_lookup`](#transaction_lookup) + - [`index_account_history`](#index_account_history) + - [`index_storage_history`](#index_storage_history) +- [`[peers]`](#the-peers-section) + - [`connection_info`](#connection_info) + - [`reputation_weights`](#reputation_weights) + - [`backoff_durations`](#backoff_durations) +- [`[sessions]`](#the-sessions-section) +- [`[prune]`](#the-prune-section) ## The `[stages]` section @@ -305,8 +309,8 @@ The sessions section configures the internal behavior of a single peer-to-peer c You can configure the session buffer sizes, which limits the amount of pending events (incoming messages) and commands (outgoing messages) each session can hold before it will start to ignore messages. > **Note** -> -> These buffers are allocated *per peer*, which means that increasing the buffer sizes can have large impact on memory consumption. +> +> These buffers are allocated _per peer_, which means that increasing the buffer sizes can have large impact on memory consumption. ```toml [sessions] @@ -342,10 +346,11 @@ No pruning, run as archive node. ### Example of the custom pruning configuration This configuration will: -- Run pruning every 5 blocks -- Continuously prune all transaction senders, account history and storage history before the block `head-100_000`, -i.e. keep the data for the last `100_000` blocks -- Prune all receipts before the block 1920000, i.e. keep receipts from the block 1920000 + +- Run pruning every 5 blocks +- Continuously prune all transaction senders, account history and storage history before the block `head-100_000`, + i.e. keep the data for the last `100_000` blocks +- Prune all receipts before the block 1920000, i.e. keep receipts from the block 1920000 ```toml [prune] @@ -370,6 +375,7 @@ storage_history = { distance = 100_000 } # Prune all historical storage states b ``` We can also prune receipts more granular, using the logs filtering: + ```toml # Receipts pruning configuration by retaining only those receipts that contain logs emitted # by the specified addresses, discarding all others. This setting is overridden by `receipts`. diff --git a/book/run/mainnet.md b/book/vocs/docs/pages/run/ethereum.mdx similarity index 73% rename from book/run/mainnet.md rename to book/vocs/docs/pages/run/ethereum.mdx index c4908971f69..7e0d01daa1f 100644 --- a/book/run/mainnet.md +++ b/book/vocs/docs/pages/run/ethereum.mdx @@ -1,3 +1,7 @@ +--- +description: How to run Reth on Ethereum mainnet and testnets. +--- + # Running Reth on Ethereum Mainnet or testnets Reth is an [_execution client_](https://ethereum.org/en/developers/docs/nodes-and-clients/#execution-clients). After Ethereum's transition to Proof of Stake (aka the Merge) it became required to run a [_consensus client_](https://ethereum.org/en/developers/docs/nodes-and-clients/#consensus-clients) along your execution client in order to sync into any "post-Merge" network. This is because the Ethereum execution layer now outsources consensus to a separate component, known as the consensus client. @@ -6,12 +10,12 @@ Consensus clients decide what blocks are part of the chain, while execution clie By running both an execution client like Reth and a consensus client, such as Lighthouse 🦀 (which we will assume for this guide), you can effectively contribute to the Ethereum network and participate in the consensus process, even if you don't intend to run validators. -| Client | Role | -|-------------|--------------------------------------------------| -| Execution | Validates transactions and blocks | -| | (checks their validity and global state) | -| Consensus | Determines which blocks are part of the chain | -| | (makes consensus decisions) | +| Client | Role | +| --------- | --------------------------------------------- | +| Execution | Validates transactions and blocks | +| | (checks their validity and global state) | +| Consensus | Determines which blocks are part of the chain | +| | (makes consensus decisions) | ## Running the Reth Node @@ -24,15 +28,22 @@ reth node ``` And to start the full node, run: + ```bash reth node --full ``` -On differences between archive and full nodes, see [Pruning & Full Node](./pruning.md#basic-concepts) section. +On differences between archive and full nodes, see [Pruning & Full Node](/run/faq/pruning#basic-concepts) section. + +:::note +These commands will not open any HTTP/WS ports by default. + +You can change this by adding the `--http`, `--ws` flags, respectively and using the `--http.api` and `--ws.api` flags to enable various [JSON-RPC APIs](/jsonrpc/intro). -> Note that these commands will not open any HTTP/WS ports by default. You can change this by adding the `--http`, `--ws` flags, respectively and using the `--http.api` and `--ws.api` flags to enable various [JSON-RPC APIs](../jsonrpc/intro.md). For more commands, see the [`reth node` CLI reference](../cli/reth/node.md). +For more commands, see the [`reth node` CLI reference](/cli/cli). +::: -The EL <> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth and placed in a file called `jwt.hex` in the data directory, which on Linux by default is `$HOME/.local/share/reth/` (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). +The EL \<> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth and placed in a file called `jwt.hex` in the data directory, which on Linux by default is `$HOME/.local/share/reth/` (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). You can override this path using the `--authrpc.jwtsecret` option. You MUST use the same JWT secret in BOTH Reth and the chosen Consensus Layer. If you want to override the address or port, you can use the `--authrpc.addr` and `--authrpc.port` options, respectively. @@ -62,24 +73,24 @@ lighthouse bn \ If you don't intend on running validators on your node you can add: -``` bash +```bash --disable-deposit-contract-sync ``` -The `--checkpoint-sync-url` argument value can be replaced with any checkpoint sync endpoint from a [community maintained list](https://eth-clients.github.io/checkpoint-sync-endpoints/#mainnet). +The `--checkpoint-sync-url` argument value can be replaced with any checkpoint sync endpoint from a [community maintained list](https://eth-clients.github.io/checkpoint-sync-endpoints/#mainnet). Your Reth node should start receiving "fork choice updated" messages, and begin syncing the chain. ## Verify the chain is growing You can easily verify that by inspecting the logs, and seeing that headers are arriving in Reth. Sit back now and wait for the stages to run! -In the meantime, consider setting up [observability](./observability.md) to monitor your node's health or [test the JSON RPC API](../jsonrpc/intro.md). +In the meantime, consider setting up [observability](/run/monitoring) to monitor your node's health or [test the JSON RPC API](../jsonrpc/intro). - +{/* TODO: Add more logs to help node operators debug any weird CL to EL messages! */} -[installation]: ./../installation/installation.md +[installation]: ./../installation/installation [docs]: https://github.com/paradigmxyz/reth/tree/main/docs -[metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics.md#current-metrics +[metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics#current-metrics ## Running without a Consensus Layer @@ -90,7 +101,8 @@ We provide a method for running Reth without a Consensus Layer via the `--debug. You can use `--debug.etherscan` to run Reth with a fake consensus client that advances the chain using recent blocks on Etherscan. This requires an Etherscan API key (set via `ETHERSCAN_API_KEY` environment variable). Optionally, specify a custom API URL with `--debug.etherscan `. Example: + ```bash export ETHERSCAN_API_KEY=your_api_key_here reth node --debug.etherscan -``` \ No newline at end of file +``` diff --git a/book/vocs/docs/pages/run/ethereum/snapshots.mdx b/book/vocs/docs/pages/run/ethereum/snapshots.mdx new file mode 100644 index 00000000000..116d4359e53 --- /dev/null +++ b/book/vocs/docs/pages/run/ethereum/snapshots.mdx @@ -0,0 +1 @@ +# Snapshots \ No newline at end of file diff --git a/book/vocs/docs/pages/run/faq.mdx b/book/vocs/docs/pages/run/faq.mdx new file mode 100644 index 00000000000..bdd0a9f68e7 --- /dev/null +++ b/book/vocs/docs/pages/run/faq.mdx @@ -0,0 +1,11 @@ +# FAQ + +1. [Transaction Types](/run/faq/transactions) - Learn about the transaction types supported by Reth. + +2. [Pruning & Full Node](/run/faq/pruning) - Understand the differences between archive nodes, full nodes, and pruned nodes. Learn how to configure pruning options and what RPC methods are available for each node type. + +3. [Ports](/run/faq/ports) - Information about the network ports used by Reth for P2P communication, JSON-RPC APIs, and the Engine API for consensus layer communication. + +4. [Profiling](/run/faq/profiling) - Performance profiling techniques and tools for analyzing Reth node performance, including CPU profiling, memory analysis, and bottleneck identification. + +5. [Sync OP Mainnet](/run/faq/sync-op-mainnet) - Detailed guide for syncing a Reth node with OP Mainnet, including specific configuration requirements and considerations for the Optimism ecosystem. diff --git a/book/vocs/docs/pages/run/faq/ports.mdx b/book/vocs/docs/pages/run/faq/ports.mdx new file mode 100644 index 00000000000..f9a3ba9950d --- /dev/null +++ b/book/vocs/docs/pages/run/faq/ports.mdx @@ -0,0 +1,42 @@ +--- +description: Ports used by Reth. +--- + +# Ports + +This section provides essential information about the ports used by the system, their primary purposes, and recommendations for exposure settings. + +## Peering Ports + +- **Port:** `30303` +- **Protocol:** TCP and UDP +- **Purpose:** Peering with other nodes for synchronization of blockchain data. Nodes communicate through this port to maintain network consensus and share updated information. +- **Exposure Recommendation:** This port should be exposed to enable seamless interaction and synchronization with other nodes in the network. + +## Metrics Port + +- **Port:** `9001` +- **Protocol:** TCP +- **Purpose:** This port is designated for serving metrics related to the system's performance and operation. It allows internal monitoring and data collection for analysis. +- **Exposure Recommendation:** By default, this port should not be exposed to the public. It is intended for internal monitoring and analysis purposes. + +## HTTP RPC Port + +- **Port:** `8545` +- **Protocol:** TCP +- **Purpose:** Port 8545 provides an HTTP-based Remote Procedure Call (RPC) interface. It enables external applications to interact with the blockchain by sending requests over HTTP. +- **Exposure Recommendation:** Similar to the metrics port, exposing this port to the public is not recommended by default due to security considerations. + +## WS RPC Port + +- **Port:** `8546` +- **Protocol:** TCP +- **Purpose:** Port 8546 offers a WebSocket-based Remote Procedure Call (RPC) interface. It allows real-time communication between external applications and the blockchain. +- **Exposure Recommendation:** As with the HTTP RPC port, the WS RPC port should not be exposed by default for security reasons. + +## Engine API Port + +- **Port:** `8551` +- **Protocol:** TCP +- **Purpose:** Port 8551 facilitates communication between specific components, such as "reth" and "CL" (assuming their definitions are understood within the context of the system). It enables essential internal processes. +- **Exposure Recommendation:** This port is not meant to be exposed to the public by default. It should be reserved for internal communication between vital components of the system. diff --git a/book/developers/profiling.md b/book/vocs/docs/pages/run/faq/profiling.mdx similarity index 84% rename from book/developers/profiling.md rename to book/vocs/docs/pages/run/faq/profiling.mdx index fdae94e2d4a..123808ad2d3 100644 --- a/book/developers/profiling.md +++ b/book/vocs/docs/pages/run/faq/profiling.mdx @@ -1,11 +1,8 @@ -# Profiling reth +--- +description: Profiling and debugging memory usage in Reth. +--- -#### Table of Contents - - [Memory profiling](#memory-profiling) - - [Jemalloc](#jemalloc) - - [Monitoring memory usage](#monitoring-memory-usage) - - [Limiting process memory](#limiting-process-memory) - - [Understanding allocation with jeprof](#understanding-allocation-with-jeprof) +# Profiling Reth ## Memory profiling @@ -16,10 +13,11 @@ Reth is also a complex program, with many moving pieces, and it can be difficult Understanding how to profile memory usage is an extremely valuable skill when faced with this type of problem, and can quickly help shed light on the root cause of a memory leak. In this tutorial, we will be reviewing: - * How to monitor reth's memory usage, - * How to emulate a low-memory environment to lab-reproduce OOM crashes, - * How to enable `jemalloc` and its built-in memory profiling, and - * How to use `jeprof` to interpret heap profiles and identify potential root causes for a memory leak. + +- How to monitor reth's memory usage, +- How to emulate a low-memory environment to lab-reproduce OOM crashes, +- How to enable `jemalloc` and its built-in memory profiling, and +- How to use `jeprof` to interpret heap profiles and identify potential root causes for a memory leak. ### Jemalloc @@ -27,21 +25,24 @@ In this tutorial, we will be reviewing: We've seen significant performance benefits in reth when using jemalloc, but will be primarily focusing on its profiling capabilities. Jemalloc also provides tools for analyzing and visualizing its allocation profiles it generates, notably `jeprof`. - #### Enabling jemalloc in reth + Reth includes a `jemalloc` feature to explicitly use jemalloc instead of the system allocator: + ``` cargo build --features jemalloc ``` While the `jemalloc` feature does enable jemalloc, reth has an additional feature, `profiling`, that must be used to enable heap profiling. This feature implicitly enables the `jemalloc` feature as well: + ``` cargo build --features jemalloc-prof ``` When performing a longer-running or performance-sensitive task with reth, such as a sync test or load benchmark, it's usually recommended to use the `maxperf` profile. However, the `maxperf` profile does not enable debug symbols, which are required for tools like `perf` and `jemalloc` to produce results that a human can interpret. Reth includes a performance profile with debug symbols called `profiling`. To compile reth with debug symbols, jemalloc, profiling, and a performance profile: + ``` cargo build --features jemalloc-prof --profile profiling @@ -51,19 +52,39 @@ RUSTFLAGS="-C target-cpu=native" cargo build --features jemalloc-prof --profile ### Monitoring memory usage -Reth's dashboard has a few metrics that are important when monitoring memory usage. The **Jemalloc memory** graph shows reth's memory usage. The *allocated* label shows the memory used by the reth process which cannot be reclaimed unless reth frees that memory. This metric exceeding the available system memory would cause reth to be killed by the OOM killer. -Jemalloc memory +Reth's dashboard has a few metrics that are important when monitoring memory usage. The **Jemalloc memory** graph shows reth's memory usage. The _allocated_ label shows the memory used by the reth process which cannot be reclaimed unless reth frees that memory. This metric exceeding the available system memory would cause reth to be killed by the OOM killer. + +Jemalloc memory Some of reth's internal components also have metrics for the memory usage of certain data structures, usually data structures that are likely to contain many elements or may consume a lot of memory at peak load. **The bodies downloader buffer**: -The bodies downloader buffer graph + +The bodies downloader buffer graph **The blockchain tree block buffer**: -The blockchain tree block buffer graph + +The blockchain tree block buffer graph **The transaction pool subpools**: -The transaction pool subpool size graph + +The transaction pool subpool size graph One of these metrics growing beyond, 2GB for example, is likely a bug and could lead to an OOM on a low memory machine. It isn't likely for that to happen frequently, so in the best case these metrics can be used to rule out these components from having a leak, if an OOM is occurring. @@ -81,28 +102,37 @@ See the [canonical documentation for cgroups](https://git.kernel.org/pub/scm/lin In order to use cgroups to limit process memory, sometimes it must be explicitly enabled as a kernel parameter. For example, the following line is sometimes necessary to enable cgroup memory limits on Ubuntu machines that use GRUB: + ``` GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory" ``` + Then, create a named cgroup: + ``` sudo cgcreate -t $USER:$USER -a $USER:$USER -g memory:rethMemory ``` + The memory limit for the named cgroup can be set in `sys/fs/cgroup/memory`. This for example sets an 8 gigabyte memory limit: + ``` echo 8G > /sys/fs/cgroup/memory/rethMemory/memory.limit_in_bytes ``` + If the intention of setting up the cgroup is to strictly limit memory and simulate OOMs, a high amount of swap may prevent those OOMs from happening. To check swap, use `free -m`: + ``` ubuntu@bench-box:~/reth$ free -m total used free shared buff/cache available Mem: 257668 10695 218760 12 28213 244761 Swap: 8191 159 8032 ``` + If this is a problem, it may be worth either adjusting the system swappiness or disabling swap overall. Finally, `cgexec` can be used to run reth under the cgroup: + ``` cgexec -g memory:rethMemory reth node ``` @@ -111,11 +141,13 @@ cgexec -g memory:rethMemory reth node When reth is built with the `jemalloc-prof` feature and debug symbols, the profiling still needs to be configured and enabled at runtime. This is done with the `_RJEM_MALLOC_CONF` environment variable. Take the following command to launch reth with jemalloc profiling enabled: + ``` _RJEM_MALLOC_CONF=prof:true,lg_prof_interval:32,lg_prof_sample:19 reth node ``` If reth is not built properly, you will see this when you try to run reth: + ``` ~/p/reth (dan/managing-memory)> _RJEM_MALLOC_CONF=prof:true,lg_prof_interval:32,lg_prof_sample:19 reth node : Invalid conf pair: prof:true diff --git a/book/run/pruning.md b/book/vocs/docs/pages/run/faq/pruning.mdx similarity index 92% rename from book/run/pruning.md rename to book/vocs/docs/pages/run/faq/pruning.mdx index 25d11b4e46e..2a800b7bae8 100644 --- a/book/run/pruning.md +++ b/book/vocs/docs/pages/run/faq/pruning.mdx @@ -1,8 +1,14 @@ +--- +description: Pruning and full node options in Reth. +--- + # Pruning & Full Node -> Pruning and full node are new features of Reth, -> and we will be happy to hear about your experience using them either -> on [GitHub](https://github.com/paradigmxyz/reth/issues) or in the [Telegram group](https://t.me/paradigm_reth). +:::info +Pruning and full node are new features of Reth, +and we will be happy to hear about your experience using them either +on [GitHub](https://github.com/paradigmxyz/reth/issues) or in the [Telegram group](https://t.me/paradigm_reth). +::: By default, Reth runs as an archive node. Such nodes have all historical blocks and the state at each of these blocks available for querying and tracing. @@ -12,31 +18,31 @@ the steps for running Reth as a full node, what caveats to expect and how to con ## Basic concepts -- Archive node – Reth node that has all historical data from genesis. -- Pruned node – Reth node that has its historical data pruned partially or fully through - a [custom configuration](./config.md#the-prune-section). -- Full Node – Reth node that has the latest state and historical data for only the last 10064 blocks available - for querying in the same way as an archive node. +- Archive node – Reth node that has all historical data from genesis. +- Pruned node – Reth node that has its historical data pruned partially or fully through + a [custom configuration](/run/configuration#the-prune-section). +- Full Node – Reth node that has the latest state and historical data for only the last 10064 blocks available + for querying in the same way as an archive node. -The node type that was chosen when first [running a node](./run-a-node.md) **cannot** be changed after +The node type that was chosen when first [running a node](/run/overview) **cannot** be changed after the initial sync. Turning Archive into Pruned, or Pruned into Full is not supported. ## Modes ### Archive Node -Default mode, follow the steps from the previous chapter on [how to run on mainnet or official testnets](./mainnet.md). +Default mode, follow the steps from the previous chapter on [how to run on mainnet or official testnets](/run/ethereum). ### Pruned Node -To run Reth as a pruned node configured through a [custom configuration](./config.md#the-prune-section), +To run Reth as a pruned node configured through a [custom configuration](/run/configuration#the-prune-section), modify the `reth.toml` file and run Reth in the same way as archive node by following the steps from -the previous chapter on [how to run on mainnet or official testnets](./mainnet.md). +the previous chapter on [how to run on mainnet or official testnets](/run/ethereum). ### Full Node To run Reth as a full node, follow the steps from the previous chapter on -[how to run on mainnet or official testnets](./mainnet.md), and add a `--full` flag. For example: +[how to run on mainnet or official testnets](/run/ethereum), and add a `--full` flag. For example: ```bash reth node \ @@ -95,21 +101,21 @@ storage_history = { distance = 10_064 } Meaning, it prunes: -- Account History and Storage History up to the last 10064 blocks -- All of Sender Recovery data. The caveat is that it's pruned gradually after the initial sync - is completed, so the disk space is reclaimed slowly. -- Receipts up to the last 10064 blocks, preserving all receipts with the logs from Beacon Deposit Contract +- Account History and Storage History up to the last 10064 blocks +- All of Sender Recovery data. The caveat is that it's pruned gradually after the initial sync + is completed, so the disk space is reclaimed slowly. +- Receipts up to the last 10064 blocks, preserving all receipts with the logs from Beacon Deposit Contract ## RPC support -As it was mentioned in the [pruning configuration chapter](./config.md#the-prune-section), there are several segments which can be pruned +As it was mentioned in the [pruning configuration chapter](/run/configuration#the-prune-section), there are several segments which can be pruned independently of each other: -- Sender Recovery -- Transaction Lookup -- Receipts -- Account History -- Storage History +- Sender Recovery +- Transaction Lookup +- Receipts +- Account History +- Storage History Pruning of each of these segments disables different RPC methods, because the historical data or lookup indexes become unavailable. @@ -215,8 +221,8 @@ The following tables describe RPC methods available in the full node. The following tables describe the requirements for prune segments, per RPC method: -- ✅ – if the segment is pruned, the RPC method still works -- ❌ - if the segment is pruned, the RPC method doesn't work anymore +- ✅ – if the segment is pruned, the RPC method still works +- ❌ - if the segment is pruned, the RPC method doesn't work anymore #### `debug` namespace diff --git a/book/run/sync-op-mainnet.md b/book/vocs/docs/pages/run/faq/sync-op-mainnet.mdx similarity index 70% rename from book/run/sync-op-mainnet.md rename to book/vocs/docs/pages/run/faq/sync-op-mainnet.mdx index 0e2090acbcb..e895331288e 100644 --- a/book/run/sync-op-mainnet.md +++ b/book/vocs/docs/pages/run/faq/sync-op-mainnet.mdx @@ -1,13 +1,17 @@ +--- +description: Syncing Reth with OP Mainnet and Bedrock state. +--- + # Sync OP Mainnet To sync OP mainnet, Bedrock state needs to be imported as a starting point. There are currently two ways: -* Minimal bootstrap **(recommended)**: only state snapshot at Bedrock block is imported without any OVM historical data. -* Full bootstrap **(not recommended)**: state, blocks and receipts are imported. *Not recommended for now: [storage consistency issue](https://github.com/paradigmxyz/reth/pull/11099) tldr: sudden crash may break the node +- Minimal bootstrap **(recommended)**: only state snapshot at Bedrock block is imported without any OVM historical data. +- Full bootstrap **(not recommended)**: state, blocks and receipts are imported. \*Not recommended for now: [storage consistency issue](https://github.com/paradigmxyz/reth/pull/11099) tldr: sudden crash may break the node ## Minimal bootstrap (recommended) -**The state snapshot at Bedrock block is required.** It can be exported from [op-geth](https://github.com/testinprod-io/op-erigon/blob/pcw109550/bedrock-db-migration/bedrock-migration.md#export-state) (**.jsonl**) or downloaded directly from [here](https://mega.nz/file/GdZ1xbAT#a9cBv3AqzsTGXYgX7nZc_3fl--tcBmOAIwIA5ND6kwc). +**The state snapshot at Bedrock block is required.** It can be exported from [op-geth](https://github.com/testinprod-io/op-erigon/blob/pcw109550/bedrock-db-migration/bedrock-migration#export-state) (**.jsonl**) or downloaded directly from [here](https://mega.nz/file/GdZ1xbAT#a9cBv3AqzsTGXYgX7nZc_3fl--tcBmOAIwIA5ND6kwc). Import the state snapshot @@ -21,12 +25,11 @@ Sync the node to a recent finalized block (e.g. 125200000) to catch up close to $ op-reth node --chain optimism --datadir op-mainnet --debug.tip 0x098f87b75c8b861c775984f9d5dbe7b70cbbbc30fc15adb03a5044de0144f2d0 # block #125200000 ``` - ## Full bootstrap (not recommended) **Not recommended for now**: [storage consistency issue](https://github.com/paradigmxyz/reth/pull/11099) tldr: sudden crash may break the node. -### Import state +### Import state To sync OP mainnet, the Bedrock datadir needs to be imported to use as starting point. Blocks lower than the OP mainnet Bedrock fork, are built on the OVM and cannot be executed on the EVM. @@ -35,15 +38,15 @@ execution in reth's sync pipeline. Importing OP mainnet Bedrock datadir requires exported data: -- Blocks [and receipts] below Bedrock -- State snapshot at first Bedrock block +- Blocks [and receipts] below Bedrock +- State snapshot at first Bedrock block ### Manual Export Steps -The `op-geth` Bedrock datadir can be downloaded from . +The `op-geth` Bedrock datadir can be downloaded from [https://datadirs.optimism.io](https://datadirs.optimism.io). To export the OVM chain from `op-geth`, clone the `testinprod-io/op-geth` repo and checkout -. Commands to export blocks, receipts and state dump can be +[testinprod-io/op-geth#1](https://github.com/testinprod-io/op-geth/pull/1). Commands to export blocks, receipts and state dump can be found in `op-geth/migrate.sh`. ### Manual Import Steps @@ -64,7 +67,7 @@ This step is optional. To run a full node, skip this step. If however receipts a corresponding transactions must already be imported (see [step 1](#1-import-blocks)). Imports a `.rlp` file of receipts, that has been exported with command specified in - (command for exporting receipts uses custom RLP-encoding). +[testinprod-io/op-geth#1](https://github.com/testinprod-io/op-geth/pull/1) (command for exporting receipts uses custom RLP-encoding). Import of >100 million OVM receipts, from genesis to Bedrock, completes in 30 minutes. @@ -86,7 +89,7 @@ $ op-reth init-state --chain optimism ## Sync from Bedrock to tip Running the node with `--debug.tip `syncs the node without help from CL until a fixed tip. The -block hash can be taken from the latest block on . +block hash can be taken from the latest block on [https://optimistic.etherscan.io](https://optimistic.etherscan.io). Use `op-node` to track the tip. Start `op-node` with `--syncmode=execution-layer` and `--l2.enginekind=reth`. If `op-node`'s RPC connection to L1 is over localhost, `--l1.trustrpc` can be set to improve performance. diff --git a/book/run/transactions.md b/book/vocs/docs/pages/run/faq/transactions.mdx similarity index 97% rename from book/run/transactions.md rename to book/vocs/docs/pages/run/faq/transactions.mdx index edb3a24d76f..a4d19df38d5 100644 --- a/book/run/transactions.md +++ b/book/vocs/docs/pages/run/faq/transactions.mdx @@ -1,3 +1,7 @@ +--- +description: Overview of Ethereum transaction types in Reth. +--- + # Transaction types Over time, the Ethereum network has undergone various upgrades and improvements to enhance transaction efficiency, security, and user experience. Four significant transaction types that have evolved are: diff --git a/book/run/troubleshooting.md b/book/vocs/docs/pages/run/faq/troubleshooting.mdx similarity index 52% rename from book/run/troubleshooting.md rename to book/vocs/docs/pages/run/faq/troubleshooting.mdx index 7b8ec6ba19c..3dafa678ac2 100644 --- a/book/run/troubleshooting.md +++ b/book/vocs/docs/pages/run/faq/troubleshooting.mdx @@ -1,102 +1,107 @@ +--- +description: Troubleshooting common Reth node and database issues. +--- + # Troubleshooting This page tries to answer how to deal with the most popular issues. -- [Troubleshooting](#troubleshooting) - - [Database](#database) - - [Docker](#docker) - - [Error code 13](#error-code-13) - - [Slow database inserts and updates](#slow-database-inserts-and-updates) - - [Compact the database](#compact-the-database) - - [Re-sync from scratch](#re-sync-from-scratch) - - [Database write error](#database-write-error) - - [Concurrent database access error (using containers/Docker)](#concurrent-database-access-error-using-containersdocker) - - [Hardware Performance Testing](#hardware-performance-testing) - - [Disk Speed Testing with IOzone](#disk-speed-testing-with-iozone) - +- [Troubleshooting](#troubleshooting) + - [Database](#database) + - [Docker](#docker) + - [Error code 13](#error-code-13) + - [Slow database inserts and updates](#slow-database-inserts-and-updates) + - [Compact the database](#compact-the-database) + - [Re-sync from scratch](#re-sync-from-scratch) + - [Database write error](#database-write-error) + - [Concurrent database access error (using containers/Docker)](#concurrent-database-access-error-using-containersdocker) + - [Hardware Performance Testing](#hardware-performance-testing) + - [Disk Speed Testing with IOzone](#disk-speed-testing-with-iozone) ## Database -### Docker +### Docker Externally accessing a `datadir` inside a named docker volume will usually come with folder/file ownership/permissions issues. **It is not recommended** to use the path to the named volume as it will trigger an error code 13. `RETH_DB_PATH: /var/lib/docker/volumes/named_volume/_data/eth/db cargo r --examples db-access --path ` is **DISCOURAGED** and a mounted volume with the right permissions should be used instead. -### Error code 13 +### Error code 13 `the environment opened in read-only code: 13` Externally accessing a database in a read-only folder is not supported, **UNLESS** there's no `mdbx.lck` present, and it's called with `exclusive` on calling `open_db_read_only`. Meaning that there's no node syncing concurrently. -If the error persists, ensure that you have the right `rx` permissions on the `datadir` **and its parent** folders. Eg. the following command should succeed: +If the error persists, ensure that you have the right `rx` permissions on the `datadir` **and its parent** folders. Eg. the following command should succeed: ```bash,ignore stat /full/path/datadir ``` - ### Slow database inserts and updates If you're: + 1. Running behind the tip -2. Have slow canonical commit time according to the `Canonical Commit Latency Time` chart on [Grafana dashboard](./observability.md#prometheus--grafana) (more than 2-3 seconds) -3. Seeing warnings in your logs such as - ```console - 2023-11-08T15:17:24.789731Z WARN providers::db: Transaction insertion took too long block_number=18528075 tx_num=2150227643 hash=0xb7de1d6620efbdd3aa8547c47a0ff09a7fd3e48ba3fd2c53ce94c6683ed66e7c elapsed=6.793759034s - ``` +2. Have slow canonical commit time according to the `Canonical Commit Latency Time` chart on [Grafana dashboard](/run/monitoring#prometheus--grafana) (more than 2-3 seconds) +3. Seeing warnings in your logs such as + ```console + 2023-11-08T15:17:24.789731Z WARN providers::db: Transaction insertion took too long block_number=18528075 tx_num=2150227643 hash=0xb7de1d6620efbdd3aa8547c47a0ff09a7fd3e48ba3fd2c53ce94c6683ed66e7c elapsed=6.793759034s + ``` then most likely you're experiencing issues with the [database freelist](https://github.com/paradigmxyz/reth/issues/5228). -To confirm it, check if the values on the `Freelist` chart on [Grafana dashboard](./observability.md#prometheus--grafana) +To confirm it, check if the values on the `Freelist` chart on [Grafana dashboard](/run/monitoring#prometheus--grafana) is greater than 10M. Currently, there are two main ways to fix this issue. - #### Compact the database + It will take around 5-6 hours and require **additional** disk space located on the same or different drive -equal to the [freshly synced node](../installation/installation.md#hardware-requirements). +equal to the [freshly synced node](/installation/overview#hardware-requirements). 1. Clone Reth - ```bash - git clone https://github.com/paradigmxyz/reth - cd reth - ``` + ```bash + git clone https://github.com/paradigmxyz/reth + cd reth + ``` 2. Build database debug tools - ```bash - make db-tools - ``` + ```bash + make db-tools + ``` 3. Run compaction (this step will take 5-6 hours, depending on the I/O speed) - ```bash - ./db-tools/mdbx_copy -c $(reth db path) reth_compact.dat - ``` + ```bash + ./db-tools/mdbx_copy -c $(reth db path) reth_compact.dat + ``` 4. Stop Reth 5. Backup original database - ```bash - mv $(reth db path)/mdbx.dat reth_old.dat - ``` + ```bash + mv $(reth db path)/mdbx.dat reth_old.dat + ``` 6. Move compacted database in place of the original database - ```bash - mv reth_compact.dat $(reth db path)/mdbx.dat - ``` + ```bash + mv reth_compact.dat $(reth db path)/mdbx.dat + ``` 7. Start Reth 8. Confirm that the values on the `Freelist` chart are near zero and the values on the `Canonical Commit Latency Time` chart -is less than 1 second. + is less than 1 second. 9. Delete original database - ```bash - rm reth_old.dat - ``` + ```bash + rm reth_old.dat + ``` #### Re-sync from scratch + It will take the same time as initial sync. 1. Stop Reth -2. Drop the database using [`reth db drop`](../cli/reth/db/drop.md) +2. Drop the database using [`reth db drop`](#TODO) 3. Start reth ### Database write error If you encounter an irrecoverable database-related errors, in most of the cases it's related to the RAM/NVMe/SSD you use. For example: + ```console Error: A stage encountered an irrecoverable error. @@ -132,6 +137,7 @@ If you encounter an error while accessing the database from multiple processes a ```console mdbx:0: panic: Assertion `osal_rdt_unlock() failed: err 1' failed. ``` + or ```console @@ -151,61 +157,71 @@ If your hardware performance is significantly lower than these reference numbers ### Disk Speed Testing with [IOzone](https://linux.die.net/man/1/iozone) 1. Test disk speed: - ```bash - iozone -e -t1 -i0 -i2 -r1k -s1g /tmp - ``` - Reference numbers (on Latitude c3.large.x86): - - ```console - Children see throughput for 1 initial writers = 907733.81 kB/sec - Parent sees throughput for 1 initial writers = 907239.68 kB/sec - Children see throughput for 1 rewriters = 1765222.62 kB/sec - Parent sees throughput for 1 rewriters = 1763433.35 kB/sec - Children see throughput for 1 random readers = 1557497.38 kB/sec - Parent sees throughput for 1 random readers = 1554846.58 kB/sec - Children see throughput for 1 random writers = 984428.69 kB/sec - Parent sees throughput for 1 random writers = 983476.67 kB/sec - ``` + + ```bash + iozone -e -t1 -i0 -i2 -r1k -s1g /tmp + ``` + + Reference numbers (on Latitude c3.large.x86): + + ```console + Children see throughput for 1 initial writers = 907733.81 kB/sec + Parent sees throughput for 1 initial writers = 907239.68 kB/sec + Children see throughput for 1 rewriters = 1765222.62 kB/sec + Parent sees throughput for 1 rewriters = 1763433.35 kB/sec + Children see throughput for 1 random readers = 1557497.38 kB/sec + Parent sees throughput for 1 random readers = 1554846.58 kB/sec + Children see throughput for 1 random writers = 984428.69 kB/sec + Parent sees throughput for 1 random writers = 983476.67 kB/sec + ``` + 2. Test disk speed with memory-mapped files: - ```bash - iozone -B -G -e -t1 -i0 -i2 -r1k -s1g /tmp - ``` - Reference numbers (on Latitude c3.large.x86): - - ```console - Children see throughput for 1 initial writers = 56471.06 kB/sec - Parent sees throughput for 1 initial writers = 56365.14 kB/sec - Children see throughput for 1 rewriters = 241650.69 kB/sec - Parent sees throughput for 1 rewriters = 239067.96 kB/sec - Children see throughput for 1 random readers = 6833161.00 kB/sec - Parent sees throughput for 1 random readers = 5597659.65 kB/sec - Children see throughput for 1 random writers = 220248.53 kB/sec - Parent sees throughput for 1 random writers = 219112.26 kB/sec + + ```bash + iozone -B -G -e -t1 -i0 -i2 -r1k -s1g /tmp + ``` + + Reference numbers (on Latitude c3.large.x86): + + ```console + Children see throughput for 1 initial writers = 56471.06 kB/sec + Parent sees throughput for 1 initial writers = 56365.14 kB/sec + Children see throughput for 1 rewriters = 241650.69 kB/sec + Parent sees throughput for 1 rewriters = 239067.96 kB/sec + Children see throughput for 1 random readers = 6833161.00 kB/sec + Parent sees throughput for 1 random readers = 5597659.65 kB/sec + Children see throughput for 1 random writers = 220248.53 kB/sec + Parent sees throughput for 1 random writers = 219112.26 kB/sec ``` ### RAM Speed and Health Testing 1. Check RAM speed with [lshw](https://linux.die.net/man/1/lshw): - ```bash - sudo lshw -short -C memory - ``` - Look for the frequency in the output. Reference output: - - ```console - H/W path Device Class Description - ================================================================ - /0/24/0 memory 64GiB DIMM DDR4 Synchronous Registered (Buffered) 3200 MHz (0.3 ns) - /0/24/1 memory 64GiB DIMM DDR4 Synchronous Registered (Buffered) 3200 MHz (0.3 ns) - ... - ``` + + ```bash + sudo lshw -short -C memory + ``` + + Look for the frequency in the output. Reference output: + + ```console + H/W path Device Class Description + ================================================================ + /0/24/0 memory 64GiB DIMM DDR4 Synchronous Registered (Buffered) 3200 MHz (0.3 ns) + /0/24/1 memory 64GiB DIMM DDR4 Synchronous Registered (Buffered) 3200 MHz (0.3 ns) + ... + ``` 2. Test RAM health with [memtester](https://linux.die.net/man/8/memtester): - ```bash - sudo memtester 10G - ``` - This will take a while. You can test with a smaller amount first: - - ```bash - sudo memtester 1G 1 - ``` - All checks should report "ok". + + ```bash + sudo memtester 10G + ``` + + This will take a while. You can test with a smaller amount first: + + ```bash + sudo memtester 1G 1 + ``` + + All checks should report "ok". diff --git a/book/run/observability.md b/book/vocs/docs/pages/run/monitoring.mdx similarity index 92% rename from book/run/observability.md rename to book/vocs/docs/pages/run/monitoring.mdx index aa4e9387a0b..d09b795dc4b 100644 --- a/book/run/observability.md +++ b/book/vocs/docs/pages/run/monitoring.mdx @@ -1,3 +1,7 @@ +--- +description: Reth observability and metrics with Prometheus and Grafana. +--- + # Observability with Prometheus & Grafana Reth exposes a number of metrics which can be enabled by adding the `--metrics` flag: @@ -41,6 +45,7 @@ brew install grafana ### Linux #### Debian/Ubuntu + ```bash # Install Prometheus # Visit https://prometheus.io/download/ for the latest version @@ -58,6 +63,7 @@ sudo apt-get install grafana ``` #### Fedora/RHEL/CentOS + ```bash # Install Prometheus # Visit https://prometheus.io/download/ for the latest version @@ -74,16 +80,18 @@ sudo dnf install -y https://dl.grafana.com/oss/release/grafana-latest-1.x86_64.r ### Windows #### Using Chocolatey + ```powershell choco install prometheus choco install grafana ``` #### Manual installation + 1. Download the latest Prometheus from [prometheus.io/download](https://prometheus.io/download/) - - Select the Windows binary (.zip) for your architecture (typically windows-amd64) + - Select the Windows binary (.zip) for your architecture (typically windows-amd64) 2. Download the latest Grafana from [grafana.com/grafana/download](https://grafana.com/grafana/download) - - Choose the Windows installer (.msi) or standalone version + - Choose the Windows installer (.msi) or standalone version 3. Extract Prometheus to a location of your choice (e.g., `C:\prometheus`) 4. Install Grafana by running the installer or extracting the standalone version 5. Configure Prometheus and Grafana to run as services if needed @@ -95,7 +103,7 @@ Then, kick off the Prometheus and Grafana services: brew services start prometheus brew services start grafana -# For Linux (systemd-based distributions) +# For Linux (syst-based distributions) sudo systemctl start prometheus sudo systemctl start grafana-server @@ -110,9 +118,9 @@ You can find an example config for the Prometheus service in the repo here: [`et Depending on your installation you may find the config for your Prometheus service at: -- OSX: `/opt/homebrew/etc/prometheus.yml` -- Linuxbrew: `/home/linuxbrew/.linuxbrew/etc/prometheus.yml` -- Others: `/usr/local/etc/prometheus/prometheus.yml` +- OSX: `/opt/homebrew/etc/prometheus.yml` +- Linuxbrew: `/home/linuxbrew/.linuxbrew/etc/prometheus.yml` +- Others: `/usr/local/etc/prometheus/prometheus.yml` Next, open up "localhost:3000" in your browser, which is the default URL for Grafana. Here, "admin" is the default for both the username and password. @@ -130,7 +138,7 @@ In this runbook, we took you through starting the node, exposing different log l This will all be very useful to you, whether you're simply running a home node and want to keep an eye on its performance, or if you're a contributor and want to see the effect that your (or others') changes have on Reth's operations. -[installation]: ../installation/installation.md +[installation]: ../installation/installation [release-profile]: https://doc.rust-lang.org/cargo/reference/profiles.html#release [docs]: https://github.com/paradigmxyz/reth/tree/main/docs -[metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics.md#current-metrics +[metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics#current-metrics diff --git a/book/vocs/docs/pages/run/networks.mdx b/book/vocs/docs/pages/run/networks.mdx new file mode 100644 index 00000000000..1bb6593b2e4 --- /dev/null +++ b/book/vocs/docs/pages/run/networks.mdx @@ -0,0 +1 @@ +# Networks diff --git a/book/run/optimism.md b/book/vocs/docs/pages/run/opstack.mdx similarity index 95% rename from book/run/optimism.md rename to book/vocs/docs/pages/run/opstack.mdx index a3f747dd1f9..86e9ad72438 100644 --- a/book/run/optimism.md +++ b/book/vocs/docs/pages/run/opstack.mdx @@ -1,7 +1,12 @@ +--- +description: Running Reth on Optimism and OP Stack chains. +--- + # Running Reth on OP Stack chains `reth` ships with the `optimism` feature flag in several crates, including the binary, enabling support for OP Stack chains out of the box. Optimism has a small diff from the [L1 EELS][l1-el-spec], comprising of the following key changes: + 1. A new transaction type, [`0x7E (Deposit)`][deposit-spec], which is used to deposit funds from L1 to L2. 1. Modifications to the `PayloadAttributes` that allow the [sequencer][sequencer] to submit transactions to the EL through the Engine API. Payloads will be built with deposit transactions at the top of the block, with the first deposit transaction always being the "L1 Info Transaction." @@ -19,6 +24,7 @@ Since 1.4.0 op-reth has built in support for all chains in the [superchain regis ## Running on Optimism You will need three things to run `op-reth`: + 1. An archival L1 node, synced to the settlement layer of the OP Stack chain you want to sync (e.g. `reth`, `geth`, `besu`, `nethermind`, etc.) 1. A rollup node (e.g. `op-node`, `magi`, `hildr`, etc.) 1. An instance of `op-reth`. @@ -40,6 +46,7 @@ This will install the `op-reth` binary to `~/.cargo/bin/op-reth`. ### Installing a Rollup Node Next, you'll need to install a [Rollup Node][rollup-node-spec], which is the equivalent to the Consensus Client on the OP Stack. Available options include: + 1. [`op-node`][op-node] 1. [`magi`][magi] 1. [`hildr`][hildr] @@ -49,11 +56,13 @@ For the sake of this tutorial, we'll use the reference implementation of the Rol ### Running `op-reth` op-reth supports additional OP Stack specific CLI arguments: + 1. `--rollup.sequencer-http ` - The sequencer endpoint to connect to. Transactions sent to the `op-reth` EL are also forwarded to this sequencer endpoint for inclusion, as the sequencer is the entity that builds blocks on OP Stack chains. 1. `--rollup.disable-tx-pool-gossip` - Disables gossiping of transactions in the mempool to peers. This can be omitted for personal nodes, though providers should always opt to enable this flag. 1. `--rollup.discovery.v4` - Enables the discovery v4 protocol for peer discovery. By default, op-reth, similar to op-geth, has discovery v5 enabled and discovery v4 disabled, whereas regular reth has discovery v4 enabled and discovery v5 disabled. First, ensure that your L1 archival node is running and synced to tip. Also make sure that the beacon node / consensus layer client is running and has http APIs enabled. Then, start `op-reth` with the `--rollup.sequencer-http` flag set to the `Base Mainnet` sequencer endpoint: + ```sh op-reth node \ --chain base \ @@ -65,6 +74,7 @@ op-reth node \ ``` Then, once `op-reth` has been started, start up the `op-node`: + ```sh op-node \ --network="base-mainnet" \ @@ -81,17 +91,15 @@ op-node \ Consider adding the `--l1.trustrpc` flag to improve performance, if the connection to l1 is over localhost. [l1-el-spec]: https://github.com/ethereum/execution-specs -[rollup-node-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node.md +[rollup-node-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/rollup-node [op-geth-forkdiff]: https://op-geth.optimism.io -[sequencer]: https://github.com/ethereum-optimism/specs/blob/main/specs/background.md#sequencers +[sequencer]: https://github.com/ethereum-optimism/specs/blob/main/specs/background#sequencers [op-stack-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs -[l2-el-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine.md -[deposit-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/deposits.md -[derivation-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/derivation.md +[l2-el-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/exec-engine +[deposit-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/deposits +[derivation-spec]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/derivation [superchain-registry]: https://github.com/ethereum-optimism/superchain-registry - [op-node-docker]: https://console.cloud.google.com/artifacts/docker/oplabs-tools-artifacts/us/images/op-node - [reth]: https://github.com/paradigmxyz/reth [op-node]: https://github.com/ethereum-optimism/optimism/tree/develop/op-node [magi]: https://github.com/a16z/magi diff --git a/book/vocs/docs/pages/run/opstack/op-mainnet-caveats.mdx b/book/vocs/docs/pages/run/opstack/op-mainnet-caveats.mdx new file mode 100644 index 00000000000..94f1024dfca --- /dev/null +++ b/book/vocs/docs/pages/run/opstack/op-mainnet-caveats.mdx @@ -0,0 +1 @@ +# Caveats OP-Mainnet \ No newline at end of file diff --git a/book/vocs/docs/pages/run/overview.mdx b/book/vocs/docs/pages/run/overview.mdx new file mode 100644 index 00000000000..06b595ad482 --- /dev/null +++ b/book/vocs/docs/pages/run/overview.mdx @@ -0,0 +1,47 @@ +--- +description: Guide to running a Reth node. +--- + +# Run a Node + +Congratulations, now that you have installed Reth, it's time to run it! + +In this section, we'll guide you through running a Reth node on various networks and configurations. + +## Networks + +Choose the network you want to run your node on: + +- **[Ethereum](/run/ethereum)** - Run a node on Ethereum mainnet or testnets +- **[OP-stack](/run/opstack)** - Run a node on OP Stack chains like Base, Optimism, and others +- **[Private testnets](/run/private-testnets)** - Set up and run private test networks + +## Configuration & Monitoring + +Learn how to configure and monitor your node: + +- **[Configuration](/run/configuration)** - Configure your node using reth.toml +- **[Monitoring](/run/monitoring)** - Set up logs, metrics, and observability + +## Frequently Asked Questions + +Find answers to common questions and troubleshooting tips: + +- **[Transaction Types](/run/faq/transactions)** - Understanding different transaction types +- **[Pruning & Full Node](/run/faq/pruning)** - Storage management and node types +- **[Ports](/run/faq/ports)** - Network port configuration +- **[Profiling](/run/faq/profiling)** - Performance profiling and optimization +- **[Sync OP Mainnet](/run/faq/sync-op-mainnet)** - Tips for syncing OP Mainnet + +## List of Supported Networks + +| Network | Chain ID | RPC URL | +| --------------- | -------- | ------------------------------------ | +| Ethereum | 1 | https://reth-ethereum.ithaca.xyz/rpc | +| Sepolia Testnet | 11155111 | https://sepolia.drpc.org | +| Base | 8453 | https://base-mainnet.rpc.ithaca.xyz | +| Base Sepolia | 84532 | https://base-sepolia.rpc.ithaca.xyz | + +:::tip +Want to add more networks to this table? Feel free to [contribute](https://github.com/paradigmxyz/reth/edit/main/book/vocs/docs/pages/run/overview.mdx) by submitting a PR with additional networks that Reth supports! +::: diff --git a/book/run/private-testnet.md b/book/vocs/docs/pages/run/private-testnets.mdx similarity index 90% rename from book/run/private-testnet.md rename to book/vocs/docs/pages/run/private-testnets.mdx index 28253ca9f01..af281fc5127 100644 --- a/book/run/private-testnet.md +++ b/book/vocs/docs/pages/run/private-testnets.mdx @@ -1,10 +1,17 @@ +--- +description: Running Reth in a private testnet using Kurtosis. +--- + # Run Reth in a private testnet using Kurtosis + For those who need a private testnet to validate functionality or scale with Reth. ## Using Docker locally + This guide uses [Kurtosis' ethereum-package](https://github.com/ethpandaops/ethereum-package) and assumes you have Kurtosis and Docker installed and have Docker already running on your machine. -* Go [here](https://docs.kurtosis.com/install/) to install Kurtosis -* Go [here](https://docs.docker.com/get-docker/) to install Docker + +- Go [here](https://docs.kurtosis.com/install/) to install Kurtosis +- Go [here](https://docs.docker.com/get-docker/) to install Docker The [`ethereum-package`](https://github.com/ethpandaops/ethereum-package) is a [package](https://docs.kurtosis.com/advanced-concepts/packages) for a general purpose Ethereum testnet definition used for instantiating private testnets at any scale over Docker or Kubernetes, locally or in the cloud. This guide will go through how to spin up a local private testnet with Reth and various CL clients locally. Specifically, you will instantiate a 2-node network over Docker with Reth/Lighthouse and Reth/Teku client combinations. @@ -13,17 +20,19 @@ To see all possible configurations and flags you can use, including metrics and Genesis data will be generated using this [genesis-generator](https://github.com/ethpandaops/ethereum-genesis-generator) to be used to bootstrap the EL and CL clients for each node. The end result will be a private testnet with nodes deployed as Docker containers in an ephemeral, isolated environment on your machine called an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/). Read more about how the `ethereum-package` works by going [here](https://github.com/ethpandaops/ethereum-package/). ### Step 1: Define the parameters and shape of your private network + First, in your home directory, create a file with the name `network_params.yaml` with the following contents: + ```yaml participants: - - el_type: reth - el_image: ghcr.io/paradigmxyz/reth - cl_type: lighthouse - cl_image: sigp/lighthouse:latest - - el_type: reth - el_image: ghcr.io/paradigmxyz/reth - cl_type: teku - cl_image: consensys/teku:latest + - el_type: reth + el_image: ghcr.io/paradigmxyz/reth + cl_type: lighthouse + cl_image: sigp/lighthouse:latest + - el_type: reth + el_image: ghcr.io/paradigmxyz/reth + cl_type: teku + cl_image: consensys/teku:latest ``` > [!TIP] @@ -32,10 +41,13 @@ participants: ### Step 2: Spin up your network Next, run the following command from your command line: + ```bash kurtosis run github.com/ethpandaops/ethereum-package --args-file ~/network_params.yaml --image-download always ``` + Kurtosis will spin up an [enclave](https://docs.kurtosis.com/advanced-concepts/enclaves/) (i.e an ephemeral, isolated environment) and begin to configure and instantiate the nodes in your network. In the end, Kurtosis will print the services running in your enclave that form your private testnet alongside all the container ports and files that were generated & used to start up the private testnet. Here is a sample output: + ```console INFO[2024-07-09T12:01:35+02:00] ======================================================== INFO[2024-07-09T12:01:35+02:00] || Created enclave: silent-mountain || @@ -88,14 +100,18 @@ f0a7d5343346 vc-1-reth-lighthouse metrics: 8080/tc Great! You now have a private network with 2 full Ethereum nodes on your local machine over Docker - one that is a Reth/Lighthouse pair and another that is Reth/Teku. Check out the [Kurtosis docs](https://docs.kurtosis.com/cli) to learn about the various ways you can interact with and inspect your network. ## Using Kurtosis on Kubernetes + Kurtosis packages are portable and reproducible, meaning they will work the same way over Docker or Kubernetes, locally or on remote infrastructure. For use cases that require a larger scale, Kurtosis can be deployed on Kubernetes by following these docs [here](https://docs.kurtosis.com/k8s/). ## Running the network with additional services + The [`ethereum-package`](https://github.com/ethpandaops/ethereum-package) comes with many optional flags and arguments you can enable for your private network. Some include: -- A Grafana + Prometheus instance -- A transaction spammer called [`tx-fuzz`](https://github.com/MariusVanDerWijden/tx-fuzz) -- [A network metrics collector](https://github.com/dapplion/beacon-metrics-gazer) -- Flashbot's `mev-boost` implementation of PBS (to test/simulate MEV workflows) + +- A Grafana + Prometheus instance +- A transaction spammer called [`tx-fuzz`](https://github.com/MariusVanDerWijden/tx-fuzz) +- [A network metrics collector](https://github.com/dapplion/beacon-metrics-gazer) +- Flashbot's `mev-boost` implementation of PBS (to test/simulate MEV workflows) ### Questions? + Please reach out to the [Kurtosis discord](https://discord.com/invite/6Jjp9c89z9) should you have any questions about how to use the `ethereum-package` for your private testnet needs. Thanks! diff --git a/book/installation/installation.md b/book/vocs/docs/pages/run/system-requirements.mdx similarity index 68% rename from book/installation/installation.md rename to book/vocs/docs/pages/run/system-requirements.mdx index 602601b9f30..5db81bc29b8 100644 --- a/book/installation/installation.md +++ b/book/vocs/docs/pages/run/system-requirements.mdx @@ -1,31 +1,38 @@ -# Installation - -Reth runs on Linux and macOS (Windows tracked). - -There are three core methods to obtain Reth: - -* [Pre-built binaries](./binaries.md) -* [Docker images](./docker.md) -* [Building from source.](./source.md) - -> **Note** -> -> If you have Docker installed, we recommend using the [Docker Compose](./docker.md#using-docker-compose) configuration -> that will get you Reth, Lighthouse (Consensus Client), Prometheus and Grafana running and syncing with just one command. - -## Hardware Requirements +# System Requirements The hardware requirements for running Reth depend on the node configuration and can change over time as the network grows or new features are implemented. The most important requirement is by far the disk, whereas CPU and RAM requirements are relatively flexible. +## Ethereum Mainnet Requirements + +Below are the requirements for Ethereum Mainnet: + | | Archive Node | Full Node | -|-----------|---------------------------------------|---------------------------------------| +| --------- | ------------------------------------- | ------------------------------------- | | Disk | At least 2.8TB (TLC NVMe recommended) | At least 1.8TB (TLC NVMe recommended) | | Memory | 16GB+ | 8GB+ | | CPU | Higher clock speed over core count | Higher clock speeds over core count | | Bandwidth | Stable 24Mbps+ | Stable 24Mbps+ | +## Base System Requirements + +Below are the minimum system requirements for running a Base node as of 2025-06-23, block number 31.9M: + +| | Archive Node | Full Node | +| --------- | ------------------------------------- | ------------------------------------- | +| Disk | At least 4.1TB (TLC NVMe recommended) | At least 2TB (TLC NVMe recommended) | +| Memory | 128GB+ | 128GB+ | +| CPU | 6 cores+, Higher clock speed over core count | 6 cores+, Higher clock speed over core count | +| Bandwidth | Stable 24Mbps+ | Stable 24Mbps+ | + +:::note +**On CPU clock speeds**: We've seen >1s payload latency on EPYC GENOA 9254 (2.9 GHz/3.9 GHz), best performance we see on AMD EPYC™ 4004. + +**On CPU cores for Base**: 5+ cores are needed because the state root task splits work into separate threads that run in parallel with each other. The state root task is generally more performant and can scale with the number of CPU cores, while regular state root always uses only one core. This is not a requirement for Mainnet, but for Base you may encounter block processing latencies of more than 2s, which can lead to lagging behind the head of the chain. +::: + + #### QLC and TLC It is crucial to understand the difference between QLC and TLC NVMe drives when considering the disk requirement. @@ -34,29 +41,29 @@ QLC (Quad-Level Cell) NVMe drives utilize four bits of data per cell, allowing f TLC (Triple-Level Cell) NVMe drives, on the other hand, use three bits of data per cell. While they have a slightly lower storage density compared to QLC drives, TLC drives offer faster performance. They typically have higher read and write speeds, making them more suitable for demanding tasks such as data-intensive applications, gaming, and multimedia editing. TLC drives also tend to have a higher endurance, making them more durable and longer-lasting. -Prior to purchasing an NVMe drive, it is advisable to research and determine whether the disk will be based on QLC or TLC technology. An overview of recommended and not-so-recommended NVMe boards can be found at [here]( https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038). +Prior to purchasing an NVMe drive, it is advisable to research and determine whether the disk will be based on QLC or TLC technology. An overview of recommended and not-so-recommended NVMe boards can be found at [here](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038). ### Disk There are multiple types of disks to sync Reth, with varying size requirements, depending on the syncing mode. As of April 2025 at block number 22.1M: -* Archive Node: At least 2.8TB is required -* Full Node: At least 1.8TB is required +- Archive Node: At least 2.8TB is required +- Full Node: At least 1.8TB is required NVMe based SSD drives are recommended for the best performance, with SATA SSDs being a cheaper alternative. HDDs are the cheapest option, but they will take the longest to sync, and are not recommended. As of February 2024, syncing an Ethereum mainnet node to block 19.3M on NVMe drives takes about 50 hours, while on a GCP "Persistent SSD" it takes around 5 days. -> **Note** -> -> It is highly recommended to choose a TLC drive when using an NVMe drive, and not a QLC drive. See [the note](#qlc-and-tlc) above. A list of recommended drives can be found [here]( https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038). +:::tip +It is highly recommended to choose a TLC drive when using an NVMe drive, and not a QLC drive. See [the note](#qlc-and-tlc) above. A list of recommended drives can be found [here](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038). +::: ### CPU Most of the time during syncing is spent executing transactions, which is a single-threaded operation due to potential state dependencies of a transaction on previous ones. -As a result, the number of cores matters less, but in general higher clock speeds are better. More cores are better for parallelizable [stages](https://github.com/paradigmxyz/reth/blob/main/docs/crates/stages.md) (like sender recovery or bodies downloading), but these stages are not the primary bottleneck for syncing. +As a result, the number of cores matters less, but in general higher clock speeds are better. More cores are better for parallelizable [stages](https://github.com/paradigmxyz/reth/blob/main/docs/crates/stages) (like sender recovery or bodies downloading), but these stages are not the primary bottleneck for syncing. ### Memory diff --git a/book/vocs/docs/pages/sdk/custom-node/modifications.mdx b/book/vocs/docs/pages/sdk/custom-node/modifications.mdx new file mode 100644 index 00000000000..b375feb901b --- /dev/null +++ b/book/vocs/docs/pages/sdk/custom-node/modifications.mdx @@ -0,0 +1 @@ +# Modifying Node Components diff --git a/book/vocs/docs/pages/sdk/custom-node/prerequisites.mdx b/book/vocs/docs/pages/sdk/custom-node/prerequisites.mdx new file mode 100644 index 00000000000..8dbf0a1bf48 --- /dev/null +++ b/book/vocs/docs/pages/sdk/custom-node/prerequisites.mdx @@ -0,0 +1 @@ +# Prerequisites and Considerations diff --git a/book/vocs/docs/pages/sdk/examples/modify-node.mdx b/book/vocs/docs/pages/sdk/examples/modify-node.mdx new file mode 100644 index 00000000000..b8f21a06bbf --- /dev/null +++ b/book/vocs/docs/pages/sdk/examples/modify-node.mdx @@ -0,0 +1,16 @@ +# How to Modify an Existing Node + +This guide demonstrates how to extend a Reth node with custom functionality, including adding RPC endpoints, modifying transaction validation, and implementing custom services. + +## Adding Custom RPC Endpoints + +One of the most common modifications is adding custom RPC methods to expose additional functionality. + +### Basic Custom RPC Module + + +## Next Steps + +- Explore [Standalone Components](/sdk/examples/standalone-components) for direct blockchain interaction +- Learn about [Custom Node Building](/sdk/custom-node/prerequisites) for production deployments +- Review [Type System](/sdk/typesystem/block) for working with blockchain data diff --git a/book/vocs/docs/pages/sdk/examples/standalone-components.mdx b/book/vocs/docs/pages/sdk/examples/standalone-components.mdx new file mode 100644 index 00000000000..3c16e1cf123 --- /dev/null +++ b/book/vocs/docs/pages/sdk/examples/standalone-components.mdx @@ -0,0 +1,12 @@ +# Using Standalone Components + +This guide demonstrates how to use Reth components independently without running a full node. This is useful for building tools, analyzers, indexers, or any application that needs direct access to blockchain data. + +## Direct Database Access + + +## Next Steps + +- Learn about [Modifying Nodes](/sdk/examples/modify-node) to add functionality +- Explore the [Type System](/sdk/typesystem/block) for working with data +- Check [Custom Node Building](/sdk/custom-node/prerequisites) for production use diff --git a/book/vocs/docs/pages/sdk/node-components.mdx b/book/vocs/docs/pages/sdk/node-components.mdx new file mode 100644 index 00000000000..cdd4b93650f --- /dev/null +++ b/book/vocs/docs/pages/sdk/node-components.mdx @@ -0,0 +1,112 @@ +# Node Components + +Reth's modular architecture allows developers to customize and extend individual components of the node. Each component serves a specific purpose and can be replaced or modified to suit your needs. + +## Architecture Overview + +A Reth node consists of several key components that work together and can interact with each other: + +```mermaid +graph LR + Network[Network] --> Pool[Transaction Pool] + Network --> Consensus[Consensus] + Pool --> DB[(Database)] + Consensus --> EVM + EVM --> DB[(Database)] + RPC[RPC Server] --> Pool + RPC --> DB + RPC --> EVM +``` + +## Core Components + +### [Network](/sdk/node-components/network) +Handles P2P communication, peer discovery, and block/transaction propagation. The network component is responsible for: +- Peer discovery and management +- Transaction gossip +- State synchronization (downloading blocks) +- Protocol message handling + +### [Transaction Pool](/sdk/node-components/pool) +Manages pending transactions before they're included in blocks: +- Transaction validation +- Ordering and prioritization +- Transaction replacement logic +- Pool size management and eviction + +### [Consensus](/sdk/node-components/consensus) +Validates blocks according to protocol rules: +- Header validation (e.g. gas limit, base fee) +- Block body validation (e.g. transaction root) + +### [EVM](/sdk/node-components/evm) +Executes transactions and manages state transitions: +- Block execution +- Transaction execution +- Block building + +### [RPC](/sdk/node-components/rpc) +Provides external API access to the node: +- Standard Ethereum JSON-RPC methods +- Custom endpoints +- WebSocket subscriptions + +## Component Customization + +Each component can be customized through Reth's builder pattern: + +```rust +use reth_ethereum::node::{EthereumNode, NodeBuilder}; + +let node = NodeBuilder::new(config) + .with_types::() + .with_components(|ctx| { + // Use the ComponentBuilder to customize components + ctx.components_builder() + // Custom network configuration + .network(|network_builder| { + network_builder + .peer_manager(custom_peer_manager) + .build() + }) + // Custom transaction pool + .pool(|pool_builder| { + pool_builder + .validator(custom_validator) + .ordering(custom_ordering) + .build() + }) + // Custom consensus + .consensus(custom_consensus) + // Custom EVM configuration + .evm(|evm_builder| { + evm_builder + .with_precompiles(custom_precompiles) + .build() + }) + // Build all components + .build() + }) + .build() + .await?; +``` + +## Component Lifecycle + +Components follow a specific lifecycle startng from node builder initialization to shutdown: + +1. **Initialization**: Components are created with their dependencies +2. **Configuration**: Settings and parameters are applied +3. **Startup**: Components begin their main operations +4. **Runtime**: Components process requests and events +5. **Shutdown**: Graceful cleanup and resource release + + +## Next Steps + +Explore each component in detail: +- [Network Component](/sdk/node-components/network) - P2P and synchronization +- [Transaction Pool](/sdk/node-components/pool) - Mempool management +- [Consensus](/sdk/node-components/consensus) - Block validation +- [EVM](/sdk/node-components/evm) - Transaction execution +- [RPC](/sdk/node-components/rpc) - External APIs diff --git a/book/vocs/docs/pages/sdk/node-components/consensus.mdx b/book/vocs/docs/pages/sdk/node-components/consensus.mdx new file mode 100644 index 00000000000..1541d351d5f --- /dev/null +++ b/book/vocs/docs/pages/sdk/node-components/consensus.mdx @@ -0,0 +1,45 @@ +# Consensus Component + +The consensus component validates blocks according to Ethereum protocol rules, handles chain reorganizations, and manages the canonical chain state. + +## Overview + +The consensus component is responsible for: +- Validating block headers and bodies +- Verifying state transitions +- Managing fork choice rules +- Handling chain reorganizations +- Tracking finalized and safe blocks +- Validating blob transactions (EIP-4844) + +## Key Concepts + +### Block Validation +The consensus component performs multiple validation steps: +1. **Pre-execution validation**: Header and body checks before running transactions +2. **Post-execution validation**: State root and receipts verification after execution + +### Header Validation +Headers must pass several checks: +- **Timestamp**: Must be greater than parent's timestamp +- **Gas limit**: Changes must be within protocol limits (1/1024 of parent) +- **Extra data**: Size restrictions based on network rules +- **Difficulty/PoS**: Appropriate validation for pre/post-merge + +### Body Validation +Block bodies are validated against headers: +- **Transaction root**: Merkle root must match header +- **Withdrawals root**: For post-Shanghai blocks +- **Blob validation**: For EIP-4844 transactions + +### Fork Choice +The consensus engine determines the canonical chain: +- Tracks multiple chain branches +- Applies fork choice rules (longest chain, most work, etc.) +- Handles reorganizations when better chains are found + +## Next Steps + +- Explore [EVM](/sdk/node-components/evm) execution +- Learn about [RPC](/sdk/node-components/rpc) server integration +- Understand [Transaction Pool](/sdk/node-components/pool) interaction \ No newline at end of file diff --git a/book/vocs/docs/pages/sdk/node-components/evm.mdx b/book/vocs/docs/pages/sdk/node-components/evm.mdx new file mode 100644 index 00000000000..6047f69bd73 --- /dev/null +++ b/book/vocs/docs/pages/sdk/node-components/evm.mdx @@ -0,0 +1,45 @@ +# EVM Component + +The EVM (Ethereum Virtual Machine) component handles transaction execution and state transitionss. It's responsible for processing transactions and updating the blockchain state. + +## Overview + +The EVM component manages: +- Transaction execution +- State transitions and updates +- Gas calculation and metering +- Custom precompiles and opcodes +- Block execution and validation +- State management and caching + +## Architecture + + +## Key Concepts + +### Transaction Execution +The EVM executes transactions in a deterministic way: +1. **Environment Setup**: Configure block and transaction context +2. **State Access**: Load accounts and storage from the database +3. **Execution**: Run EVM bytecode with gas metering +4. **State Updates**: Apply changes to accounts and storage +5. **Receipt Generation**: Create execution receipts with logs + +### Block Execution +Block executors process all transactions in a block: +- Validate pre-state conditions +- Execute transactions sequentially +- Apply block rewards +- Verify post-state (state root, receipts root) + +### Block Building +Block builders construct new blocks for proposal: +- Select transactions (e.g. mempool) +- Order and execute transactions +- Seal the block with a header (state root) + +## Next Steps + +- Learn about [RPC](/sdk/node-components/rpc) server integration +- Explore [Transaction Pool](/sdk/node-components/pool) interaction +- Review [Consensus](/sdk/node-components/consensus) validation \ No newline at end of file diff --git a/book/vocs/docs/pages/sdk/node-components/network.mdx b/book/vocs/docs/pages/sdk/node-components/network.mdx new file mode 100644 index 00000000000..308087305ac --- /dev/null +++ b/book/vocs/docs/pages/sdk/node-components/network.mdx @@ -0,0 +1,55 @@ +# Network Component + +The network component handles all peer-to-peer communication in Reth, including peer discovery, connection management, and protocol message handling. + +## Overview + +The network stack implements the Ethereum Wire Protocol (ETH) and provides: +- Peer discovery via discv4 and discv5 +- Connection management with configurable peer limits +- Transaction propagation +- State synchronization +- Request/response protocols (e.g. GetBHeaders, GetBodies) + +## Architecture + +```mermaid +graph TD + NetworkManager[Network Manager] --> Discovery[Discovery] + NetworkManager --> Sessions[Session Manager] + NetworkManager --> Swarm[Swarm] + + Discovery --> discv4[discv4] + Discovery --> discv5[discv5] + Discovery --> DNS[DNS Discovery] + + Sessions --> ETH[ETH Protocol] +``` + +## Key Concepts + +### Peer Discovery +The network uses multiple discovery mechanisms to find and connect to peers: +- **discv4**: UDP-based discovery protocol for finding peers +- **discv5**: Improved discovery protocol with better security +- **DNS Discovery**: Peer lists published via DNS for bootstrap + +### Connection Management +- Maintains separate limits for inbound and outbound connections +- Implements peer scoring and reputation tracking +- Handles connection lifecycle and graceful disconnections + +### Protocol Support +- **ETH Protocol**: Core Ethereum wire protocol for blocks and transactions + +### Message Broadcasting +The network efficiently propagates new blocks and transactions to peers using: +- Transaction pooling and deduplication +- Block announcement strategies +- Bandwidth management + +## Next Steps + +- Learn about the [Transaction Pool](/sdk/node-components/pool) +- Understand [Consensus](/sdk/node-components/consensus) integration +- Explore [RPC](/sdk/node-components/rpc) server setup \ No newline at end of file diff --git a/book/vocs/docs/pages/sdk/node-components/pool.mdx b/book/vocs/docs/pages/sdk/node-components/pool.mdx new file mode 100644 index 00000000000..301d794b3fd --- /dev/null +++ b/book/vocs/docs/pages/sdk/node-components/pool.mdx @@ -0,0 +1,80 @@ +# Transaction Pool Component + +The transaction pool (mempool) manages pending transactions before they are included in blocks. It handles validation, ordering, replacement, and eviction of transactions. + +## Overview + +The transaction pool is responsible for: +- Validating incoming transactions +- Maintaining transaction ordering (e.g. by fees) +- Handling transaction replacement +- Managing pool size limits +- Broadcasting transactions to peers +- Providing transactions for block building + +## Architecture + +```mermaid +graph TD + API[Pool API] --> Validator[Transaction Validator] + API --> Pool[Transaction Pool] + + Pool --> SubPools[Sub-Pools] + SubPools --> Pending[Pending Pool] + SubPools --> Queued[Queued Pool] + SubPools --> Base[Base Fee Pool] + + Pool --> Ordering[Transaction Ordering] + Pool --> Listeners[Event Listeners] + + Validator --> Checks[Validation Checks] + Checks --> Nonce[Nonce Check] + Checks --> Balance[Balance Check] +``` + +## Key Concepts + +### Transaction Validation +The pool validates transactions before accepting them, checking: +- Sender has sufficient balance for gas and value +- Nonce is correct (either next expected or future) +- Gas price meets minimum requirements +- Transaction size is within limits +- Signature is valid + +### Transaction Ordering +Transactions are ordered by their effective tip per gas to maximize block rewards. Custom ordering strategies can prioritize certain addresses or implement MEV protection. + +### Sub-Pools +- **Pending**: Transactions ready for inclusion (correct nonce) +- **Queued**: Future transactions (nonce gap exists) +- **Base Fee**: Transactions priced below current base fee + +### Pool Maintenance +The pool requires periodic maintenance to: +- Remove stale transactions +- Revalidate after chain reorganizations +- Update base fee thresholds +- Enforce size limits + +## Advanced Features + +### Blob Transaction Support +EIP-4844 introduces blob transactions with separate blob storage and special validation rules. + +### Transaction Filters +Custom filters can block specific addresses, limit gas prices, or implement custom acceptance criteria. + +### Event System +The pool supports an event system that allows other components to listen for transaction lifecycle events such as: +- Transaction added +- Transaction removed +- Transaction replaced +- Transaction promoted to pending state + + +## Next Steps + +- Learn about [Consensus](/sdk/node-components/consensus) validation +- Explore [EVM](/sdk/node-components/evm) execution +- Understand [RPC](/sdk/node-components/rpc) server integration \ No newline at end of file diff --git a/book/vocs/docs/pages/sdk/node-components/rpc.mdx b/book/vocs/docs/pages/sdk/node-components/rpc.mdx new file mode 100644 index 00000000000..4f9fa1e3d7b --- /dev/null +++ b/book/vocs/docs/pages/sdk/node-components/rpc.mdx @@ -0,0 +1,20 @@ +# RPC Component + +The RPC component provides external API access to the node, implementing the Ethereum JSON-RPC specification and allowing custom extensions. + +## Overview + +The RPC component provides: +- Standard Ethereum JSON-RPC methods +- WebSocket subscriptions +- Custom method extensions +- Rate limiting and access control +- Request batching support +- Multiple transport protocols (HTTP, WebSocket, IPC) + + +## Next Steps + +- Explore [Network](/sdk/node-components/network) component integration +- Learn about [Transaction Pool](/sdk/node-components/pool) APIs +- Understand [EVM](/sdk/node-components/evm) execution context \ No newline at end of file diff --git a/book/vocs/docs/pages/sdk/overview.mdx b/book/vocs/docs/pages/sdk/overview.mdx new file mode 100644 index 00000000000..b427ae8834d --- /dev/null +++ b/book/vocs/docs/pages/sdk/overview.mdx @@ -0,0 +1,127 @@ +# Reth for Developers + +Reth can be used as a library to build custom Ethereum nodes, interact with blockchain data, or create specialized tools for blockchain analysis and indexing. + +## What is the Reth SDK? + +The Reth SDK allows developers to: +- Use components of the Reth node as libraries +- Build custom Ethereum execution nodes with modified behavior (e.g. payload building) +- Access blockchain data directly from the database +- Create high-performance indexing solutions +- Extend a new with new RPC endpoints and functionality +- Implement custom consensus mechanisms +- Build specialized tools for blockchain analysis + +## Quick Start + +Add Reth to your project: + +## Ethereum + +```toml +[dependencies] +# Ethereum meta crate +reth-ethereum = { git = "https://github.com/paradigmxyz/reth" } +``` + +## Opstack + +```toml +[dependencies] +reth-op = { git = "https://github.com/paradigmxyz/reth" } +``` + +## Key Concepts + +### Node Architecture + +Reth is built with modularity in mind. The main components include: + +- **Primitives**: Core data type abstractions like `Block` +- **Node Builder**: Constructs and configures node instances +- **Database**: Efficient storage using MDBX and static files +- **Network**: P2P communication and block synchronization +- **Consensus**: Block validation and chain management +- **EVM**: Transaction execution and state transitions +- **RPC**: JSON-RPC server for external communication +- **Transaction Pool**: Pending transaction management + +### Dependency Management +Reth is primarily built on top of the [alloy](https://github.com/alloy-rs/alloy) ecosystem, which provides the necessary abstractions and implementations for core ethereum blockchain data types, transaction handling, and EVM execution. + + +### Type System + +Reth uses its own type system to handle different representations of blockchain data: + +- **Primitives**: Core types like `B256`, `Address`, `U256` +- **Transactions**: Multiple representations for different contexts (pooled, consensus, RPC) +- **Blocks**: Headers, bodies, and sealed blocks with proven properties +- **State**: Accounts, storage, and state transitions + +### Building Custom Nodes + +The node builder pattern allows you to customize every aspect of node behavior: + +```rust +use reth_ethereum::node::{EthereumNode, NodeBuilder}; + +// Build a custom node with modified components +let node = NodeBuilder::new(config) + // install the ethereum specific node primitives + .with_types::() + .with_components(|components| { + // Customize components here + components + }) + .build() + .await?; +``` + +## Architecture Overview + +```mermaid +graph TD + A[Node Builder] --> B[Database] + A --> C[Network] + A --> D[Consensus] + A --> E[EVM] + A --> F[RPC Server] + A --> G[Transaction Pool] + + B --> H[DB Storage] + B --> I[Static Files] + + C --> J[Discovery] + C --> K[ETH Protocol] + + E --> L[State Provider] + E --> M[Block Executor] +``` + +## Nodes Built with Reth + +Several production networks have been built using Reth's node builder pattern: + +### [BSC Reth](https://github.com/loocapro/reth-bsc) +A Binance Smart Chain execution client, implementing BSC-specific consensus rules and features. + +### [Bera Reth](https://github.com/berachain/bera-reth) +Berachain's execution client. + +### [Gnosis Reth](https://github.com/gnosischain/reth_gnosis) +Gnosis Chain's implementation using Reth. + + +## Next Steps + +- **[Node Components](/sdk/node-components)**: Deep dive into each component +- **[Type System](/sdk/typesystem/block)**: Understanding Reth's type system +- **[Custom Nodes](/sdk/custom-node/prerequisites)**: Building production nodes +- **[Examples](/sdk/examples/modify-node)**: Real-world implementations + +## Resources + +- [API Documentation](https://docs.rs/reth/latest/reth/) +- [GitHub Repository](https://github.com/paradigmxyz/reth) diff --git a/book/vocs/docs/pages/sdk/typesystem/block.mdx b/book/vocs/docs/pages/sdk/typesystem/block.mdx new file mode 100644 index 00000000000..450b4f93d1a --- /dev/null +++ b/book/vocs/docs/pages/sdk/typesystem/block.mdx @@ -0,0 +1,26 @@ +# Block Types + +The Reth type system provides a flexible abstraction for blocks through traits, allowing different implementations while maintaining type safety and consistency. + +## Type Relationships + +```mermaid +graph TD + Block[Block Trait] --> Header[BlockHeader Trait] + Block --> Body[BlockBody Trait] + + SealedBlock -.-> Block + SealedBlock --> SealedHeader + RecoveredBlock --> SealedBlock + + SealedHeader --> Header + + Body --> Transaction[Transactions] + Body --> Withdrawals[Withdrawals] +``` + +## Next Steps + +- Learn about [Transaction Types](/sdk/typesystem/transaction-types) +- Understand [Consensus](/sdk/node-components/consensus) validation +- Explore [EVM](/sdk/node-components/evm) execution diff --git a/book/vocs/docs/pages/sdk/typesystem/transaction-types.mdx b/book/vocs/docs/pages/sdk/typesystem/transaction-types.mdx new file mode 100644 index 00000000000..e541727da87 --- /dev/null +++ b/book/vocs/docs/pages/sdk/typesystem/transaction-types.mdx @@ -0,0 +1,92 @@ +# Transaction Types and Representations + +Reth provides multiple transaction representations optimized for different stages of the transaction lifecycle. Understanding these types is crucial for working with the node's transaction handling pipeline. + +## Transaction Lifecycle + +Transactions go through several stages, each with its own optimized representation: + +```mermaid +graph LR + RPC[RPC Transaction] --> Pool[Pooled Transaction] + Pool --> Consensus[Consensus Transaction] + Consensus --> Executed[Executed Transaction] + + Pool -.-> RPC + Consensus -.-> Pool +``` + +## Transaction Representations + +### RPC Transaction + +The RPC representation is designed for JSON-RPC communication with external clients. It uses JSON-compatible types and includes all information clients need to understand transaction status. + +Key characteristics: +- **JSON-compatible types**: Uses U256 for numbers, hex strings for binary data +- **Optional fields**: Supports both legacy and EIP-1559 transactions with appropriate fields +- **Block context**: Includes block hash, number, and index when transaction is mined +- **Human-readable**: Optimized for external consumption and debugging +- **Complete information**: Contains all transaction details including signature components + +Use cases: +- Sending transactions via `eth_sendTransaction` +- Querying transaction details via `eth_getTransactionByHash` +- Transaction receipts and history +- Block explorer displays + +### Pooled Transaction + +The pooled representation is optimized for mempool storage and validation. It pre-computes expensive values and includes additional data needed for pool management. + +Key characteristics: +- **Cached values**: Pre-computed sender address and transaction cost to avoid repeated calculations +- **Validation ready**: Includes all data needed for quick pool validation +- **Blob support**: Handles EIP-4844 blob sidecars separately from the core transaction +- **Memory efficient**: Optimized structure for storing thousands of pending transactions +- **Priority ordering**: Structured for efficient sorting by gas price/priority fee + +Use cases: +- Transaction pool storage and management +- Gas price ordering and replacement logic +- Validation against account state +- Broadcasting to peers + +### Consensus Transaction + +The consensus representation is the canonical format used in blocks and for network propagation. It's the most compact representation and follows Ethereum's wire protocol. + +Key characteristics: +- **Type safety**: Enum variants for different transaction types (Legacy, EIP-2930, EIP-1559, EIP-4844) +- **Compact encoding**: For storage on disk +- **No redundancy**: Minimal data, with values like sender recovered from signature when needed + +Use cases: +- Block construction and validation +- Network propagation between nodes +- Persistent storage in the database +- State transition execution + +## Representation Conversions + +### RPC → Pooled +When transactions arrive via RPC: +1. Validate JSON format and fields +2. Convert to consensus format +3. Recover sender from signature +4. Create pooled representation + +### Pooled → Consensus +When including in a block: +1. Extract core transaction consensus data +2. Remove cached values (sender, cost) + +### Consensus → RPC +When serving RPC requests: +1. Add block context (hash, number, index) + +## Next Steps + +- Learn about [Block Types](/sdk/typesystem/block) and how transactions fit in blocks +- Understand [Transaction Pool](/sdk/node-components/pool) management +- Explore [EVM](/sdk/node-components/evm) transaction execution \ No newline at end of file diff --git a/book/vocs/docs/public/alchemy.png b/book/vocs/docs/public/alchemy.png new file mode 100644 index 0000000000000000000000000000000000000000..422feb032775c227b435557d90ec16ed3223791d GIT binary patch literal 27206 zcmYIv19YU@(spcH6Hjd0wyjPkwv9>BF(;hZ$s`loww;NsiPdrb-0z-q|Fw2=z1_R? zRy|d_DpEyB8X18A0SpWbSyo0u4GavD>+^jz9L(o4^OU^y=L@`(jIJvf7$W9>9^haZ zS$Lmcg1f3oi-A>55uJQ~fU*=-6a@pTjYoV1K!brUDZb6Sb1D_~o0v@wDRArnMg-G6~lZ!NZvlUjM+@#;ZZ)@0Uax z=BgtH#SeTpOI#=A>38xdhLT~DQnER9s?}Q8pEQDaG0F1OQ$ixT3Fk!ch~J;Md)fEi z1bGK)xQ6X(XisZQ3y}Q!Be=1+sQI}ro4ck_!3sH3g$g*k{OejoKcXDDw<+{s`dN_v zZ&^%1;8i-EE}tlpwm`h1l0%UFX40x@guLRz3j_S$VpT_r`NjJd{|i;VhBjWd$qHJ+ zZ}=Hz7{zGQA1B=8pUZ|O%6ifX41z#Ck=p$q(FHrhBSW$4Ahe~Pe>RHwY1(HYr}VM6 zq4{9>Ol~7Fkq4LQL_yB%#+3E!-Uji{Zh#3SM9IwH`Ab!j=^Fizg|zNpbCbV3Lw5p~ zc=avm1lsQz9KQdTFaTbqS==PnHf_s9c`@p2u`@x;;fKY`5VUnjsPenpve?{zSwu3^ zN;jKFbG9v4uc4g-N+L-`%0!e~s9?wSO~+dwwPxd{a{Z4=E!N=@vPBFTZ_Asim}G>6 z2=<%!f&A+W#e)q3|F}~9ph525e>qxW1_N=acUf#a^Lr1?AHln0$k7bxceR-a7Smslpx$SeCRe^_JuO+J5qyELy^z59! zpT}Qwddh$0Y7dB;1b;g7Z5Se~6xL7Ls%a?*PL^AAmW z14NmSHLt|?o{?N{Byg?2U4i*QU||^3q(-qg=xA>tcvQ(lTzGsNcCnt@t-j2AaR1Vl zPT*>X(zDX5=VSp!%DsE%pg@DxL%0AmX^lgIG-rmk||7uwQ2%kB; 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⪚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 XV5Z6i1We5Q00000NkvXXu0mjfxhXde literal 0 HcmV?d00001 diff --git a/book/vocs/docs/public/logo.png b/book/vocs/docs/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..04a889b9d29a37b462ff33adf36f67b5210a75c0 GIT binary patch literal 100250 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 literal 0 HcmV?d00001 diff --git a/book/developers/exex/assets/remote_exex.png b/book/vocs/docs/public/remote_exex.png similarity index 100% rename from book/developers/exex/assets/remote_exex.png rename to book/vocs/docs/public/remote_exex.png diff --git a/book/vocs/docs/public/reth-prod.png b/book/vocs/docs/public/reth-prod.png new file mode 100644 index 0000000000000000000000000000000000000000..d06c4579ccfbf6c8be4b77bd4d9438dd8c80e591 GIT binary patch literal 324203 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%0DOzT1?3!a~KMdsUSn8Tdjc9 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%0AF= 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

4WMX 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 Am;e9( literal 0 HcmV?d00001 diff --git a/book/vocs/docs/public/succinct.png b/book/vocs/docs/public/succinct.png new file mode 100644 index 0000000000000000000000000000000000000000..1261974aa8aae861bfc92499311c9d81c4de6220 GIT binary patch literal 2588 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 + + + + Redirecting... + + + + + +

+ +` +} + +// Generate redirect files +Object.entries(redirects).forEach(([from, to]) => { + // Add base path to target if it doesn't already have it + const finalTarget = to.startsWith(basePath) ? to : `${basePath}${to}` + + // Remove base path if present in from path + const fromPath = from.replace(/^\/reth\//, '') + + // Generate both with and without .html + const paths = [fromPath] + if (!fromPath.endsWith('.html')) { + paths.push(`${fromPath}.html`) + } + + paths.forEach(path => { + const filePath = join('./docs/dist', path) + if (!path.includes('.')) { + // It's a directory path, create index.html + const indexPath = join('./docs/dist', path, 'index.html') + mkdirSync(dirname(indexPath), { recursive: true }) + writeFileSync(indexPath, generateRedirectHtml(finalTarget)) + } else { + // It's a file path + mkdirSync(dirname(filePath), { recursive: true }) + writeFileSync(filePath, generateRedirectHtml(finalTarget)) + } + }) +}) + +console.log('Redirects generated successfully!') \ No newline at end of file diff --git a/book/vocs/links-report.json b/book/vocs/links-report.json new file mode 100644 index 00000000000..830568362a2 --- /dev/null +++ b/book/vocs/links-report.json @@ -0,0 +1,17 @@ +{ + "timestamp": "2025-06-23T11:20:27.303Z", + "totalFiles": 106, + "totalLinks": 150, + "brokenLinks": [ + { + "file": "docs/pages/index.mdx", + "link": "/introduction/benchmarks", + "line": 110, + "reason": "Absolute path not found: /introduction/benchmarks" + } + ], + "summary": { + "brokenCount": 1, + "validCount": 149 + } +} \ No newline at end of file diff --git a/book/vocs/package.json b/book/vocs/package.json new file mode 100644 index 00000000000..912bc136345 --- /dev/null +++ b/book/vocs/package.json @@ -0,0 +1,22 @@ +{ + "name": "vocs", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vocs dev", + "build": "vocs build && bun generate-redirects.ts", + "preview": "vocs preview", + "check-links": "bun check-links.ts", + "generate-redirects": "bun generate-redirects.ts" + }, + "dependencies": { + "react": "latest", + "react-dom": "latest", + "vocs": "latest" + }, + "devDependencies": { + "@types/react": "latest", + "typescript": "latest" + } +} \ No newline at end of file diff --git a/book/vocs/redirects.config.ts b/book/vocs/redirects.config.ts new file mode 100644 index 00000000000..21521d5e86b --- /dev/null +++ b/book/vocs/redirects.config.ts @@ -0,0 +1,27 @@ +export const redirects: Record = { + '/intro': '/overview', + // Installation redirects + '/installation/installation': '/installation/binaries', + // Run a node redirects + '/run/run-a-node': '/run/overview', + '/run/mainnet': '/run/ethereum', + '/run/optimism': '/run/opstack', + '/run/sync-op-mainnet': '/run/faq/sync-op-mainnet', + '/run/private-testnet': '/run/private-testnets', + '/run/observability': '/run/monitoring', + '/run/config': '/run/configuration', + '/run/transactions': '/run/faq/transactions', + '/run/pruning': '/run/faq/pruning', + '/run/ports': '/run/faq/ports', + '/run/troubleshooting': '/run/faq/troubleshooting', + // Exex + '/developers/exex': '/exex/overview', + '/developers/exex/how-it-works': '/exex/how-it-works', + '/developers/exex/hello-world': '/exex/hello-world', + '/developers/exex/tracking-state': '/exex/tracking-state', + '/developers/exex/remote': '/exex/remote', + // Contributing + '/developers/contribute': '/introduction/contributing', +} + +export const basePath = '/'; \ No newline at end of file diff --git a/book/vocs/sidebar.ts b/book/vocs/sidebar.ts new file mode 100644 index 00000000000..65829d8e48c --- /dev/null +++ b/book/vocs/sidebar.ts @@ -0,0 +1,514 @@ +import { SidebarItem } from "vocs"; + +export const sidebar: SidebarItem[] = [ + { + text: "Introduction", + items: [ + { + text: "Overview", + link: "/overview" + }, + { + text: "Why Reth?", + link: "/introduction/why-reth" + }, + { + text: "Contributing", + link: "/introduction/contributing" + } + ] + }, + { + text: "Reth for Node Operators", + items: [ + { + text: "System Requirements", + link: "/run/system-requirements" + }, + { + text: "Installation", + collapsed: true, + items: [ + { + text: "Overview", + link: "/installation/overview" + }, + { + text: "Pre-Built Binaries", + link: "/installation/binaries" + }, + { + text: "Docker", + link: "/installation/docker" + }, + { + text: "Build from Source", + link: "/installation/source" + }, + { + text: "Build for ARM devices", + link: "/installation/build-for-arm-devices" + }, + { + text: "Update Priorities", + link: "/installation/priorities" + } + ] + }, + { + text: "Running a Node", + items: [ + { + text: "Overview", + link: "/run/overview", + }, + { + text: "Networks", + // link: "/run/networks", + items: [ + { + text: "Ethereum", + link: "/run/ethereum", + // items: [ + // { + // text: "Snapshots", + // link: "/run/ethereum/snapshots" + // } + // ] + }, + { + text: "OP-stack", + link: "/run/opstack", + // items: [ + // { + // text: "Caveats OP-Mainnet", + // link: "/run/opstack/op-mainnet-caveats" + // } + // ] + }, + { + text: "Private testnets", + link: "/run/private-testnets" + } + ] + }, + ] + }, + { + text: "Configuration", + link: "/run/configuration" + }, + { + text: "Monitoring", + link: "/run/monitoring" + }, + { + text: "FAQ", + link: "/run/faq", + collapsed: true, + items: [ + { + text: "Transaction Types", + link: "/run/faq/transactions" + }, + { + text: "Pruning & Full Node", + link: "/run/faq/pruning" + }, + { + text: "Ports", + link: "/run/faq/ports" + }, + { + text: "Profiling", + link: "/run/faq/profiling" + }, + { + text: "Sync OP Mainnet", + link: "/run/faq/sync-op-mainnet" + } + ] + } + ] + }, + { + text: "Reth as a library", + items: [ + { + text: "Overview", + link: "/sdk/overview" + }, + { + text: "Typesystem", + items: [ + { + text: "Block", + link: "/sdk/typesystem/block" + }, + { + text: "Transaction types", + link: "/sdk/typesystem/transaction-types" + } + ] + }, + { + text: "What is in a node?", + collapsed: false, + items: [ + { + text: "Network", + link: "/sdk/node-components/network" + }, + { + text: "Pool", + link: "/sdk/node-components/pool" + }, + { + text: "Consensus", + link: "/sdk/node-components/consensus" + }, + { + text: "EVM", + link: "/sdk/node-components/evm" + }, + { + text: "RPC", + link: "/sdk/node-components/rpc" + } + ] + }, + // TODO + // { + // text: "Build a custom node", + // items: [ + // { + // text: "Prerequisites and Considerations", + // link: "/sdk/custom-node/prerequisites" + // }, + // { + // text: "What modifications and how", + // link: "/sdk/custom-node/modifications" + // } + // ] + // }, + // { + // text: "Examples", + // items: [ + // { + // text: "How to modify an existing node", + // items: [ + // { + // text: "Additional features: RPC endpoints, services", + // link: "/sdk/examples/modify-node" + // } + // ] + // }, + // { + // text: "How to use standalone components", + // items: [ + // { + // text: "Interact with the disk directly + caveats", + // link: "/sdk/examples/standalone-components" + // } + // ] + // } + // ] + // } + ] + }, + { + text: "Execution Extensions", + items: [ + { + text: "Overview", + link: "/exex/overview" + }, + { + text: "How do ExExes work?", + link: "/exex/how-it-works" + }, + { + text: "Hello World", + link: "/exex/hello-world" + }, + { + text: "Tracking State", + link: "/exex/tracking-state" + }, + { + text: "Remote", + link: "/exex/remote" + } + ] + }, + { + text: "Interacting with Reth over JSON-RPC", + + items: [ + { + text: "Overview", + link: "/jsonrpc/intro", + }, + { + text: "eth", + link: "/jsonrpc/eth" + }, + { + text: "web3", + link: "/jsonrpc/web3" + }, + { + text: "net", + link: "/jsonrpc/net" + }, + { + text: "txpool", + link: "/jsonrpc/txpool" + }, + { + text: "debug", + link: "/jsonrpc/debug" + }, + { + text: "trace", + link: "/jsonrpc/trace" + }, + { + text: "admin", + link: "/jsonrpc/admin" + }, + { + text: "rpc", + link: "/jsonrpc/rpc" + } + ] + }, + { + text: "CLI Reference", + link: "/cli/cli", + collapsed: false, + items: [ + { + text: "reth", + link: "/cli/reth", + collapsed: false, + items: [ + { + text: "reth node", + link: "/cli/reth/node" + }, + { + text: "reth init", + link: "/cli/reth/init" + }, + { + text: "reth init-state", + link: "/cli/reth/init-state" + }, + { + text: "reth import", + link: "/cli/reth/import" + }, + { + text: "reth import-era", + link: "/cli/reth/import-era" + }, + { + text: "reth dump-genesis", + link: "/cli/reth/dump-genesis" + }, + { + text: "reth db", + link: "/cli/reth/db", + collapsed: true, + items: [ + { + text: "reth db stats", + link: "/cli/reth/db/stats" + }, + { + text: "reth db list", + link: "/cli/reth/db/list" + }, + { + text: "reth db checksum", + link: "/cli/reth/db/checksum" + }, + { + text: "reth db diff", + link: "/cli/reth/db/diff" + }, + { + text: "reth db get", + link: "/cli/reth/db/get", + collapsed: true, + items: [ + { + text: "reth db get mdbx", + link: "/cli/reth/db/get/mdbx" + }, + { + text: "reth db get static-file", + link: "/cli/reth/db/get/static-file" + } + ] + }, + { + text: "reth db drop", + link: "/cli/reth/db/drop" + }, + { + text: "reth db clear", + link: "/cli/reth/db/clear", + collapsed: true, + items: [ + { + text: "reth db clear mdbx", + link: "/cli/reth/db/clear/mdbx" + }, + { + text: "reth db clear static-file", + link: "/cli/reth/db/clear/static-file" + } + ] + }, + { + text: "reth db version", + link: "/cli/reth/db/version" + }, + { + text: "reth db path", + link: "/cli/reth/db/path" + } + ] + }, + { + text: "reth download", + link: "/cli/reth/download" + }, + { + text: "reth stage", + link: "/cli/reth/stage", + collapsed: true, + items: [ + { + text: "reth stage run", + link: "/cli/reth/stage/run" + }, + { + text: "reth stage drop", + link: "/cli/reth/stage/drop" + }, + { + text: "reth stage dump", + link: "/cli/reth/stage/dump", + collapsed: true, + items: [ + { + text: "reth stage dump execution", + link: "/cli/reth/stage/dump/execution" + }, + { + text: "reth stage dump storage-hashing", + link: "/cli/reth/stage/dump/storage-hashing" + }, + { + text: "reth stage dump account-hashing", + link: "/cli/reth/stage/dump/account-hashing" + }, + { + text: "reth stage dump merkle", + link: "/cli/reth/stage/dump/merkle" + } + ] + }, + { + text: "reth stage unwind", + link: "/cli/reth/stage/unwind", + collapsed: true, + items: [ + { + text: "reth stage unwind to-block", + link: "/cli/reth/stage/unwind/to-block" + }, + { + text: "reth stage unwind num-blocks", + link: "/cli/reth/stage/unwind/num-blocks" + } + ] + } + ] + }, + { + text: "reth p2p", + link: "/cli/reth/p2p", + collapsed: true, + items: [ + { + text: "reth p2p header", + link: "/cli/reth/p2p/header" + }, + { + text: "reth p2p body", + link: "/cli/reth/p2p/body" + }, + { + text: "reth p2p rlpx", + link: "/cli/reth/p2p/rlpx", + collapsed: true, + items: [ + { + text: "reth p2p rlpx ping", + link: "/cli/reth/p2p/rlpx/ping" + } + ] + } + ] + }, + { + text: "reth config", + link: "/cli/reth/config" + }, + { + text: "reth debug", + link: "/cli/reth/debug", + collapsed: true, + items: [ + { + text: "reth debug execution", + link: "/cli/reth/debug/execution" + }, + { + text: "reth debug merkle", + link: "/cli/reth/debug/merkle" + }, + { + text: "reth debug in-memory-merkle", + link: "/cli/reth/debug/in-memory-merkle" + }, + { + text: "reth debug build-block", + link: "/cli/reth/debug/build-block" + } + ] + }, + { + text: "reth recover", + link: "/cli/reth/recover", + collapsed: true, + items: [ + { + text: "reth recover storage-tries", + link: "/cli/reth/recover/storage-tries" + } + ] + }, + { + text: "reth prune", + link: "/cli/reth/prune" + } + ] + } + ] + }, +] \ No newline at end of file diff --git a/book/vocs/tsconfig.json b/book/vocs/tsconfig.json new file mode 100644 index 00000000000..d2636aac47e --- /dev/null +++ b/book/vocs/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/book/vocs/vocs.config.ts b/book/vocs/vocs.config.ts new file mode 100644 index 00000000000..7cb376d3cd4 --- /dev/null +++ b/book/vocs/vocs.config.ts @@ -0,0 +1,69 @@ +import { defineConfig } from 'vocs' +import { sidebar } from './sidebar' +import { basePath } from './redirects.config' + +export default defineConfig({ + title: 'Reth', + logoUrl: '/logo.png', + iconUrl: '/logo.png', + ogImageUrl: '/reth-prod.png', + sidebar, + basePath, + topNav: [ + { text: 'Run', link: '/run/ethereum' }, + { text: 'SDK', link: '/sdk/overview' }, + { text: 'GitHub', link: 'https://github.com/paradigmxyz/reth' }, + { + text: 'v1.4.8', + items: [ + { + text: 'Releases', + link: 'https://github.com/paradigmxyz/reth/releases' + }, + { + text: 'Contributing', + link: 'https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md' + } + ] + } + ], + socials: [ + { + icon: 'github', + link: 'https://github.com/paradigmxyz/reth', + }, + { + icon: 'telegram', + link: 'https://t.me/paradigm_reth', + }, + ], + sponsors: [ + { + name: 'Collaborators', + height: 120, + items: [ + [ + { + name: 'Paradigm', + link: 'https://paradigm.xyz', + image: 'https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/paradigm-light.svg', + }, + { + name: 'Ithaca', + link: 'https://ithaca.xyz', + image: 'https://raw.githubusercontent.com/wevm/.github/main/content/sponsors/ithaca-light.svg', + } + ] + ] + } + ], + theme: { + accentColor: { + light: '#1f1f1f', + dark: '#ffffff', + } + }, + editLink: { + pattern: "https://github.com/paradigmxyz/reth/edit/main/book/vocs/docs/pages/:path", + } +}) From 8485d99dfac0d971ef6d2cd622a3e3ee38805961 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 24 Jun 2025 17:10:53 +0200 Subject: [PATCH 207/274] feat: add --rollup.historicalrpc CLI argument for op-reth (#16941) Co-authored-by: Claude Co-authored-by: Arsenii Kulikov --- Cargo.lock | 2 +- crates/ethereum/node/src/node.rs | 4 +- crates/node/builder/Cargo.toml | 3 - crates/node/builder/src/rpc.rs | 83 +++++++------- crates/optimism/node/src/args.rs | 9 ++ crates/optimism/node/src/node.rs | 101 ++++++++++++++++-- crates/optimism/rpc/Cargo.toml | 1 + crates/optimism/rpc/src/historical.rs | 76 ++++++++++--- crates/rpc/rpc-builder/src/lib.rs | 60 +++++------ crates/rpc/rpc-builder/src/middleware.rs | 37 +++++++ crates/rpc/rpc-builder/tests/it/middleware.rs | 4 +- 11 files changed, 276 insertions(+), 104 deletions(-) create mode 100644 crates/rpc/rpc-builder/src/middleware.rs diff --git a/Cargo.lock b/Cargo.lock index df1ec6100bb..ccba6bab4cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8856,7 +8856,6 @@ dependencies = [ "tempfile", "tokio", "tokio-stream", - "tower", "tracing", ] @@ -9384,6 +9383,7 @@ dependencies = [ "serde_json", "thiserror 2.0.12", "tokio", + "tower", "tracing", ] diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 1bbed9c5859..1d6488b62f1 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -25,7 +25,7 @@ use reth_node_builder::{ node::{FullNodeTypes, NodeTypes}, rpc::{ BasicEngineApiBuilder, EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, - EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle, RpcServiceBuilder, + EthApiBuilder, EthApiCtx, RethRpcAddOns, RpcAddOns, RpcHandle, }, BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder, PayloadBuilderConfig, PayloadTypes, @@ -174,7 +174,7 @@ where EthereumEthApiBuilder, EthereumEngineValidatorBuilder::default(), BasicEngineApiBuilder::default(), - RpcServiceBuilder::new(), + Default::default(), ), } } diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index a21c9ef5bc6..d08c62d38ce 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -82,9 +82,6 @@ serde_json.workspace = true # tracing tracing.workspace = true -# tower -tower.workspace = true - [dev-dependencies] tempfile.workspace = true diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 2883d511140..82e94287442 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1,12 +1,12 @@ //! Builder support for rpc components. pub use jsonrpsee::server::middleware::rpc::{RpcService, RpcServiceBuilder}; -pub use reth_rpc_builder::{Identity, RpcRequestMetricsService}; +pub use reth_rpc_builder::{middleware::RethRpcMiddleware, Identity}; use crate::{BeaconConsensusEngineEvent, BeaconConsensusEngineHandle}; use alloy_rpc_types::engine::ClientVersionV1; use alloy_rpc_types_engine::ExecutionData; -use jsonrpsee::RpcModule; +use jsonrpsee::{core::middleware::layer::Either, RpcModule}; use reth_chain_state::CanonStateSubscriptions; use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; use reth_node_api::{ @@ -23,7 +23,8 @@ use reth_rpc_api::{eth::helpers::AddDevSigners, IntoEngineApiRpcModule}; use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerHandle}, config::RethRpcServerConfig, - RpcModuleBuilder, RpcRegistryInner, RpcServerConfig, RpcServerHandle, TransportRpcModules, + RpcModuleBuilder, RpcRegistryInner, RpcServerConfig, RpcServerHandle, Stack, + TransportRpcModules, }; use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; use reth_rpc_eth_types::{cache::cache_new_blocks_task, EthConfig, EthStateCache}; @@ -34,7 +35,6 @@ use std::{ future::Future, ops::{Deref, DerefMut}, }; -use tower::Layer; /// Contains the handles to the spawned RPC servers. /// @@ -435,7 +435,7 @@ pub struct RpcAddOns< /// /// This middleware is applied to all RPC requests across all transports (HTTP, WS, IPC). /// See [`RpcAddOns::with_rpc_middleware`] for more details. - rpc_middleware: RpcServiceBuilder, + rpc_middleware: RpcMiddleware, } impl Debug for RpcAddOns @@ -466,7 +466,7 @@ where eth_api_builder: EthB, engine_validator_builder: EV, engine_api_builder: EB, - rpc_middleware: RpcServiceBuilder, + rpc_middleware: RpcMiddleware, ) -> Self { Self { hooks: RpcHooks::default(), @@ -545,10 +545,7 @@ where /// - Middleware is applied to the RPC service layer, not the HTTP transport layer /// - The default middleware is `Identity` (no-op), which passes through requests unchanged /// - Middleware layers are applied in the order they are added via `.layer()` - pub fn with_rpc_middleware( - self, - rpc_middleware: RpcServiceBuilder, - ) -> RpcAddOns { + pub fn with_rpc_middleware(self, rpc_middleware: T) -> RpcAddOns { let Self { hooks, eth_api_builder, engine_validator_builder, engine_api_builder, .. } = self; RpcAddOns { @@ -560,6 +557,37 @@ where } } + /// Add a new layer `T` to the configured [`RpcServiceBuilder`]. + pub fn layer_rpc_middleware( + self, + layer: T, + ) -> RpcAddOns> { + let Self { + hooks, + eth_api_builder, + engine_validator_builder, + engine_api_builder, + rpc_middleware, + } = self; + let rpc_middleware = Stack::new(rpc_middleware, layer); + RpcAddOns { + hooks, + eth_api_builder, + engine_validator_builder, + engine_api_builder, + rpc_middleware, + } + } + + /// Optionally adds a new layer `T` to the configured [`RpcServiceBuilder`]. + pub fn option_layer_rpc_middleware( + self, + layer: Option, + ) -> RpcAddOns>> { + let layer = layer.map(Either::Left).unwrap_or(Either::Right(Identity::new())); + self.layer_rpc_middleware(layer) + } + /// Sets the hook that is run once the rpc server is started. pub fn on_rpc_started(mut self, hook: F) -> Self where @@ -589,7 +617,7 @@ where EB: Default, { fn default() -> Self { - Self::new(EthB::default(), EV::default(), EB::default(), RpcServiceBuilder::new()) + Self::new(EthB::default(), EV::default(), EB::default(), Default::default()) } } @@ -600,16 +628,7 @@ where EthB: EthApiBuilder, EV: EngineValidatorBuilder, EB: EngineApiBuilder, - RpcMiddleware: Layer> + Clone + Send + 'static, - >>::Service: - Send - + Sync - + 'static - + jsonrpsee::server::middleware::rpc::RpcServiceT< - MethodResponse = jsonrpsee::MethodResponse, - BatchResponse = jsonrpsee::MethodResponse, - NotificationResponse = jsonrpsee::MethodResponse, - >, + RpcMiddleware: RethRpcMiddleware, { /// Launches only the regular RPC server (HTTP/WS/IPC), without the authenticated Engine API /// server. @@ -804,16 +823,7 @@ where modules: &TransportRpcModules, ) -> eyre::Result where - M: Layer> + Clone + Send + 'static, - for<'a> >>::Service: - Send - + Sync - + 'static - + jsonrpsee::server::middleware::rpc::RpcServiceT< - MethodResponse = jsonrpsee::MethodResponse, - BatchResponse = jsonrpsee::MethodResponse, - NotificationResponse = jsonrpsee::MethodResponse, - >, + M: RethRpcMiddleware, { let handle = server_config.start(modules).await?; @@ -872,16 +882,7 @@ where EthB: EthApiBuilder, EV: EngineValidatorBuilder, EB: EngineApiBuilder, - RpcMiddleware: Layer> + Clone + Send + 'static, - >>::Service: - Send - + Sync - + 'static - + jsonrpsee::server::middleware::rpc::RpcServiceT< - MethodResponse = jsonrpsee::MethodResponse, - BatchResponse = jsonrpsee::MethodResponse, - NotificationResponse = jsonrpsee::MethodResponse, - >, + RpcMiddleware: RethRpcMiddleware, { type Handle = RpcHandle; diff --git a/crates/optimism/node/src/args.rs b/crates/optimism/node/src/args.rs index f968a5e2351..9e93f8e63f9 100644 --- a/crates/optimism/node/src/args.rs +++ b/crates/optimism/node/src/args.rs @@ -55,6 +55,14 @@ pub struct RollupArgs { #[arg(long = "rollup.sequencer-headers", requires = "sequencer")] pub sequencer_headers: Vec, + /// RPC endpoint for historical data. + #[arg( + long = "rollup.historicalrpc", + alias = "rollup.historical-rpc", + value_name = "HISTORICAL_HTTP_URL" + )] + pub historical_rpc: Option, + /// Minimum suggested priority fee (tip) in wei, default `1_000_000` #[arg(long, default_value_t = 1_000_000)] pub min_suggested_priority_fee: u64, @@ -71,6 +79,7 @@ impl Default for RollupArgs { supervisor_http: DEFAULT_SUPERVISOR_URL.to_string(), supervisor_safety_level: SafetyLevel::CrossUnsafe, sequencer_headers: Vec::new(), + historical_rpc: None, min_suggested_priority_fee: 1_000_000, } } diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index 27440be5710..d4870ae77a2 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -26,12 +26,12 @@ use reth_node_builder::{ }, node::{FullNodeTypes, NodeTypes}, rpc::{ - EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder, - RethRpcAddOns, RethRpcServerHandles, RpcAddOns, RpcContext, RpcHandle, RpcServiceBuilder, + EngineApiBuilder, EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder, Identity, + RethRpcAddOns, RethRpcMiddleware, RethRpcServerHandles, RpcAddOns, RpcContext, RpcHandle, }, BuilderContext, DebugNode, Node, NodeAdapter, NodeComponentsBuilder, }; -use reth_optimism_chainspec::OpChainSpec; +use reth_optimism_chainspec::{OpChainSpec, OpHardfork}; use reth_optimism_consensus::OpBeaconConsensus; use reth_optimism_evm::{OpEvmConfig, OpNextBlockEnvAttributes, OpRethReceiptBuilder}; use reth_optimism_forks::OpHardforks; @@ -43,6 +43,7 @@ use reth_optimism_payload_builder::{ use reth_optimism_primitives::{DepositReceipt, OpPrimitives}; use reth_optimism_rpc::{ eth::{ext::OpEthExtApi, OpEthApiBuilder}, + historical::{HistoricalRpc, HistoricalRpcClient}, miner::{MinerApiExtServer, OpMinerExtApi}, witness::{DebugExecutionWitnessApiServer, OpDebugWitnessApi}, OpEthApi, OpEthApiError, SequencerClient, @@ -231,6 +232,7 @@ where .with_da_config(self.da_config.clone()) .with_enable_tx_conditional(self.args.enable_tx_conditional) .with_min_suggested_priority_fee(self.args.min_suggested_priority_fee) + .with_historical_rpc(self.args.historical_rpc.clone()) .build() } } @@ -259,10 +261,11 @@ impl NodeTypes for OpNode { /// This type provides optimism-specific addons to the node and exposes the RPC server and engine /// API. #[derive(Debug)] -pub struct OpAddOns, EV, EB> { +pub struct OpAddOns, EV, EB, RpcMiddleware = Identity> +{ /// Rpc add-ons responsible for launching the RPC servers and instantiating the RPC handlers /// and eth-api. - pub rpc_add_ons: RpcAddOns, + pub rpc_add_ons: RpcAddOns, /// Data availability configuration for the OP builder. pub da_config: OpDAConfig, /// Sequencer client, configured to forward submitted transactions to sequencer of given OP @@ -270,6 +273,10 @@ pub struct OpAddOns, EV, EB> { pub sequencer_url: Option, /// Headers to use for the sequencer client requests. pub sequencer_headers: Vec, + /// RPC endpoint for historical data. + /// + /// This can be used to forward pre-bedrock rpc requests (op-mainnet). + pub historical_rpc: Option, /// Enable transaction conditionals. enable_tx_conditional: bool, min_suggested_priority_fee: u64, @@ -308,18 +315,22 @@ where } } -impl OpAddOns +impl OpAddOns where N: FullNodeComponents, EthB: EthApiBuilder, { /// Maps the [`reth_node_builder::rpc::EngineApiBuilder`] builder type. - pub fn with_engine_api(self, engine_api_builder: T) -> OpAddOns { + pub fn with_engine_api( + self, + engine_api_builder: T, + ) -> OpAddOns { let Self { rpc_add_ons, da_config, sequencer_url, sequencer_headers, + historical_rpc, enable_tx_conditional, min_suggested_priority_fee, } = self; @@ -329,12 +340,16 @@ where sequencer_url, sequencer_headers, enable_tx_conditional, + historical_rpc, min_suggested_priority_fee, } } /// Maps the [`EngineValidatorBuilder`] builder type. - pub fn with_engine_validator(self, engine_validator_builder: T) -> OpAddOns { + pub fn with_engine_validator( + self, + engine_validator_builder: T, + ) -> OpAddOns { let Self { rpc_add_ons, da_config, @@ -342,6 +357,7 @@ where sequencer_headers, enable_tx_conditional, min_suggested_priority_fee, + historical_rpc, } = self; OpAddOns { rpc_add_ons: rpc_add_ons.with_engine_validator(engine_validator_builder), @@ -350,6 +366,35 @@ where sequencer_headers, enable_tx_conditional, min_suggested_priority_fee, + historical_rpc, + } + } + + /// Sets the RPC middleware stack for processing RPC requests. + /// + /// This method configures a custom middleware stack that will be applied to all RPC requests + /// across HTTP, `WebSocket`, and IPC transports. The middleware is applied to the RPC service + /// layer, allowing you to intercept, modify, or enhance RPC request processing. + /// + /// See also [`RpcAddOns::with_rpc_middleware`]. + pub fn with_rpc_middleware(self, rpc_middleware: T) -> OpAddOns { + let Self { + rpc_add_ons, + da_config, + sequencer_url, + sequencer_headers, + enable_tx_conditional, + min_suggested_priority_fee, + historical_rpc, + } = self; + OpAddOns { + rpc_add_ons: rpc_add_ons.with_rpc_middleware(rpc_middleware), + da_config, + sequencer_url, + sequencer_headers, + enable_tx_conditional, + min_suggested_priority_fee, + historical_rpc, } } @@ -374,7 +419,8 @@ where } } -impl NodeAddOns for OpAddOns, EV, EB> +impl NodeAddOns + for OpAddOns, EV, EB, RpcMiddleware> where N: FullNodeComponents< Types: OpFullNodeTypes, @@ -388,6 +434,7 @@ where NetworkT: op_alloy_network::Network + Unpin, EV: EngineValidatorBuilder, EB: EngineApiBuilder, + RpcMiddleware: RethRpcMiddleware, { type Handle = RpcHandle>; @@ -401,9 +448,32 @@ where sequencer_url, sequencer_headers, enable_tx_conditional, + historical_rpc, .. } = self; + let maybe_pre_bedrock_historical_rpc = historical_rpc + .and_then(|historical_rpc| { + ctx.node + .provider() + .chain_spec() + .op_fork_activation(OpHardfork::Bedrock) + .block_number() + .filter(|activation| *activation > 0) + .map(|bedrock_block| (historical_rpc, bedrock_block)) + }) + .map(|(historical_rpc, bedrock_block)| -> eyre::Result<_> { + info!(target: "reth::cli", %bedrock_block, ?historical_rpc, "Using historical RPC endpoint pre bedrock"); + let provider = ctx.node.provider().clone(); + let client = HistoricalRpcClient::new(&historical_rpc)?; + let layer = HistoricalRpc::new(provider, client, bedrock_block); + Ok(layer) + }) + .transpose()? + ; + + let rpc_add_ons = rpc_add_ons.option_layer_rpc_middleware(maybe_pre_bedrock_historical_rpc); + let builder = reth_optimism_payload_builder::OpPayloadBuilder::new( ctx.node.pool().clone(), ctx.node.provider().clone(), @@ -513,6 +583,8 @@ pub struct OpAddOnsBuilder { sequencer_url: Option, /// Headers to use for the sequencer client requests. sequencer_headers: Vec, + /// RPC endpoint for historical data. + historical_rpc: Option, /// Data availability configuration for the OP builder. da_config: Option, /// Enable transaction conditionals. @@ -528,6 +600,7 @@ impl Default for OpAddOnsBuilder { Self { sequencer_url: None, sequencer_headers: Vec::new(), + historical_rpc: None, da_config: None, enable_tx_conditional: false, min_suggested_priority_fee: 1_000_000, @@ -566,6 +639,12 @@ impl OpAddOnsBuilder { self.min_suggested_priority_fee = min; self } + + /// Configures the endpoint for historical RPC forwarding. + pub fn with_historical_rpc(mut self, historical_rpc: Option) -> Self { + self.historical_rpc = historical_rpc; + self + } } impl OpAddOnsBuilder { @@ -583,6 +662,7 @@ impl OpAddOnsBuilder { da_config, enable_tx_conditional, min_suggested_priority_fee, + historical_rpc, .. } = self; @@ -594,11 +674,12 @@ impl OpAddOnsBuilder { .with_min_suggested_priority_fee(min_suggested_priority_fee), EV::default(), EB::default(), - RpcServiceBuilder::new(), + Default::default(), ), da_config: da_config.unwrap_or_default(), sequencer_url, sequencer_headers, + historical_rpc, enable_tx_conditional, min_suggested_priority_fee, } diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 58466d18c2b..d31de8a0b43 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -62,6 +62,7 @@ parking_lot.workspace = true tokio.workspace = true reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } async-trait.workspace = true +tower.workspace = true # rpc jsonrpsee-core.workspace = true diff --git a/crates/optimism/rpc/src/historical.rs b/crates/optimism/rpc/src/historical.rs index 2f69b424fab..0f8824882b3 100644 --- a/crates/optimism/rpc/src/historical.rs +++ b/crates/optimism/rpc/src/historical.rs @@ -25,7 +25,7 @@ pub struct HistoricalRpcClient { impl HistoricalRpcClient { /// Constructs a new historical RPC client with the given endpoint URL. - pub async fn new(endpoint: &str) -> Result { + pub fn new(endpoint: &str) -> Result { let client = RpcClient::new_http( endpoint.parse::().map_err(|err| Error::InvalidUrl(err.to_string()))?, ); @@ -75,6 +75,30 @@ struct HistoricalRpcClientInner { client: RpcClient, } +/// A layer that provides historical RPC forwarding functionality for a given service. +#[derive(Debug, Clone)] +pub struct HistoricalRpc

{ + inner: Arc>, +} + +impl

HistoricalRpc

{ + /// Constructs a new historical RPC layer with the given provider, client and bedrock block + /// number. + pub fn new(provider: P, client: HistoricalRpcClient, bedrock_block: BlockNumber) -> Self { + let inner = Arc::new(HistoricalRpcInner { provider, client, bedrock_block }); + + Self { inner } + } +} + +impl tower::Layer for HistoricalRpc

{ + type Service = HistoricalRpcService; + + fn layer(&self, inner: S) -> Self::Service { + HistoricalRpcService::new(inner, self.inner.clone()) + } +} + /// A service that intercepts RPC calls and forwards pre-bedrock historical requests /// to a dedicated endpoint. /// @@ -84,12 +108,16 @@ struct HistoricalRpcClientInner { pub struct HistoricalRpcService { /// The inner service that handles regular RPC requests inner: S, - /// Client used to forward historical requests - historical_client: HistoricalRpcClient, - /// Provider used to determine if a block is pre-bedrock - provider: P, - /// Bedrock transition block number - bedrock_block: BlockNumber, + /// The context required to forward historical requests. + historical: Arc>, +} + +impl HistoricalRpcService { + /// Constructs a new historical RPC service with the given inner service, historical client, + /// provider, and bedrock block number. + const fn new(inner: S, historical: Arc>) -> Self { + Self { inner, historical } + } } impl RpcServiceT for HistoricalRpcService @@ -104,9 +132,7 @@ where fn call<'a>(&self, req: Request<'a>) -> impl Future + Send + 'a { let inner_service = self.inner.clone(); - let historical_client = self.historical_client.clone(); - let provider = self.provider.clone(); - let bedrock_block = self.bedrock_block; + let historical = self.historical.clone(); Box::pin(async move { let maybe_block_id = match req.method_name() { @@ -125,8 +151,10 @@ where // if we've extracted a block ID, check if it's pre-Bedrock if let Some(block_id) = maybe_block_id { - let is_pre_bedrock = if let Ok(Some(num)) = provider.block_number_for_id(block_id) { - num < bedrock_block + let is_pre_bedrock = if let Ok(Some(num)) = + historical.provider.block_number_for_id(block_id) + { + num < historical.bedrock_block } else { // If we can't convert the hash to a number, assume it's post-Bedrock debug!(target: "rpc::historical", ?block_id, "hash unknown; not forwarding"); @@ -140,7 +168,8 @@ where let params = req.params(); let params = params.as_str().unwrap_or("[]"); if let Ok(params) = serde_json::from_str::(params) { - if let Ok(raw) = historical_client + if let Ok(raw) = historical + .client .request::<_, serde_json::Value>(req.method_name(), params) .await { @@ -169,6 +198,16 @@ where } } +#[derive(Debug)] +struct HistoricalRpcInner

{ + /// Provider used to determine if a block is pre-bedrock + provider: P, + /// Client used to forward historical requests + client: HistoricalRpcClient, + /// Bedrock transition block number + bedrock_block: BlockNumber, +} + /// Parses a `BlockId` from the given parameters at the specified position. fn parse_block_id_from_params(params: &Params<'_>, position: usize) -> Option { let values: Vec = params.parse().ok()?; @@ -181,6 +220,17 @@ mod tests { use super::*; use alloy_eips::{BlockId, BlockNumberOrTag}; use jsonrpsee::types::Params; + use jsonrpsee_core::middleware::layer::Either; + use reth_node_builder::rpc::RethRpcMiddleware; + use reth_storage_api::noop::NoopProvider; + use tower::layer::util::Identity; + + #[test] + fn check_historical_rpc() { + fn assert_historical_rpc() {} + assert_historical_rpc::>(); + assert_historical_rpc::, Identity>>(); + } /// Tests that various valid id types can be parsed from the first parameter. #[test] diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 9a16c079461..ca61d4504ef 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -26,11 +26,8 @@ use error::{ConflictingModules, RpcError, ServerKind}; use http::{header::AUTHORIZATION, HeaderMap}; use jsonrpsee::{ core::RegisterMethodError, - server::{ - middleware::rpc::{RpcService, RpcServiceBuilder, RpcServiceT}, - AlreadyStoppedError, IdProvider, ServerHandle, - }, - MethodResponse, Methods, RpcModule, + server::{middleware::rpc::RpcServiceBuilder, AlreadyStoppedError, IdProvider, ServerHandle}, + Methods, RpcModule, }; use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; use reth_consensus::{ConsensusError, FullConsensus}; @@ -62,7 +59,6 @@ use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, time::{Duration, SystemTime, UNIX_EPOCH}, }; -use tower::Layer; use tower_http::cors::CorsLayer; pub use cors::CorsDomainError; @@ -82,6 +78,9 @@ pub mod auth; /// RPC server utilities. pub mod config; +/// Utils for installing Rpc middleware +pub mod middleware; + /// Cors utilities. mod cors; @@ -94,6 +93,7 @@ pub use eth::EthHandlers; // Rpc server metrics mod metrics; +use crate::middleware::RethRpcMiddleware; pub use metrics::{MeteredRequestFuture, RpcRequestMetricsService}; use reth_chain_state::CanonStateSubscriptions; use reth_rpc::eth::sim_bundle::EthSimBundle; @@ -1043,7 +1043,7 @@ pub struct RpcServerConfig { /// JWT secret for authentication jwt_secret: Option, /// Configurable RPC middleware - rpc_middleware: RpcServiceBuilder, + rpc_middleware: RpcMiddleware, } // === impl RpcServerConfig === @@ -1062,7 +1062,7 @@ impl Default for RpcServerConfig { ipc_server_config: None, ipc_endpoint: None, jwt_secret: None, - rpc_middleware: RpcServiceBuilder::new(), + rpc_middleware: Default::default(), } } } @@ -1114,7 +1114,7 @@ impl RpcServerConfig { impl RpcServerConfig { /// Configure rpc middleware - pub fn set_rpc_middleware(self, rpc_middleware: RpcServiceBuilder) -> RpcServerConfig { + pub fn set_rpc_middleware(self, rpc_middleware: T) -> RpcServerConfig { RpcServerConfig { http_server_config: self.http_server_config, http_cors_domains: self.http_cors_domains, @@ -1256,15 +1256,7 @@ impl RpcServerConfig { /// Returns the [`RpcServerHandle`] with the handle to the started servers. pub async fn start(self, modules: &TransportRpcModules) -> Result where - RpcMiddleware: Layer> + Clone + Send + 'static, - >>::Service: Send - + Sync - + 'static - + RpcServiceT< - MethodResponse = MethodResponse, - BatchResponse = MethodResponse, - NotificationResponse = MethodResponse, - >, + RpcMiddleware: RethRpcMiddleware, { let mut http_handle = None; let mut ws_handle = None; @@ -1325,14 +1317,16 @@ impl RpcServerConfig { )), ) .set_rpc_middleware( - self.rpc_middleware.clone().layer( - modules - .http - .as_ref() - .or(modules.ws.as_ref()) - .map(RpcRequestMetrics::same_port) - .unwrap_or_default(), - ), + RpcServiceBuilder::default() + .layer( + modules + .http + .as_ref() + .or(modules.ws.as_ref()) + .map(RpcRequestMetrics::same_port) + .unwrap_or_default(), + ) + .layer(self.rpc_middleware.clone()), ) .set_config(config.build()) .build(http_socket_addr) @@ -1374,9 +1368,9 @@ impl RpcServerConfig { .option_layer(Self::maybe_jwt_layer(self.jwt_secret)), ) .set_rpc_middleware( - self.rpc_middleware - .clone() - .layer(modules.ws.as_ref().map(RpcRequestMetrics::ws).unwrap_or_default()), + RpcServiceBuilder::default() + .layer(modules.ws.as_ref().map(RpcRequestMetrics::ws).unwrap_or_default()) + .layer(self.rpc_middleware.clone()), ) .build(ws_socket_addr) .await @@ -1400,9 +1394,11 @@ impl RpcServerConfig { .option_layer(Self::maybe_compression_layer(self.http_disable_compression)), ) .set_rpc_middleware( - self.rpc_middleware.clone().layer( - modules.http.as_ref().map(RpcRequestMetrics::http).unwrap_or_default(), - ), + RpcServiceBuilder::default() + .layer( + modules.http.as_ref().map(RpcRequestMetrics::http).unwrap_or_default(), + ) + .layer(self.rpc_middleware.clone()), ) .build(http_socket_addr) .await diff --git a/crates/rpc/rpc-builder/src/middleware.rs b/crates/rpc/rpc-builder/src/middleware.rs new file mode 100644 index 00000000000..c03f63501fc --- /dev/null +++ b/crates/rpc/rpc-builder/src/middleware.rs @@ -0,0 +1,37 @@ +use jsonrpsee::server::middleware::rpc::RpcService; +use tower::Layer; + +/// A Helper alias trait for the RPC middleware supported by the server. +pub trait RethRpcMiddleware: + Layer< + RpcService, + Service: jsonrpsee::server::middleware::rpc::RpcServiceT< + MethodResponse = jsonrpsee::MethodResponse, + BatchResponse = jsonrpsee::MethodResponse, + NotificationResponse = jsonrpsee::MethodResponse, + > + Send + + Sync + + Clone + + 'static, + > + Clone + + Send + + 'static +{ +} + +impl RethRpcMiddleware for T where + T: Layer< + RpcService, + Service: jsonrpsee::server::middleware::rpc::RpcServiceT< + MethodResponse = jsonrpsee::MethodResponse, + BatchResponse = jsonrpsee::MethodResponse, + NotificationResponse = jsonrpsee::MethodResponse, + > + Send + + Sync + + Clone + + 'static, + > + Clone + + Send + + 'static +{ +} diff --git a/crates/rpc/rpc-builder/tests/it/middleware.rs b/crates/rpc/rpc-builder/tests/it/middleware.rs index 80c94110a74..60541a57c39 100644 --- a/crates/rpc/rpc-builder/tests/it/middleware.rs +++ b/crates/rpc/rpc-builder/tests/it/middleware.rs @@ -2,7 +2,7 @@ use crate::utils::{test_address, test_rpc_builder}; use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction, TransactionRequest}; use jsonrpsee::{ core::middleware::{Batch, Notification}, - server::middleware::rpc::{RpcServiceBuilder, RpcServiceT}, + server::middleware::rpc::RpcServiceT, types::Request, }; use reth_rpc_builder::{RpcServerConfig, TransportRpcModuleConfig}; @@ -79,7 +79,7 @@ async fn test_rpc_middleware() { let handle = RpcServerConfig::http(Default::default()) .with_http_address(test_address()) - .set_rpc_middleware(RpcServiceBuilder::new().layer(mylayer.clone())) + .set_rpc_middleware(mylayer.clone()) .start(&modules) .await .unwrap(); From 05d44bba90606361b4e055660beffc71ba98b46a Mon Sep 17 00:00:00 2001 From: FT <140458077+zeevick10@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:11:37 +0200 Subject: [PATCH 208/274] refactor(rpc): replace `ExtendedTxEnvelopeRepr` with `ExtendedRepr` in `serde_bincode_compat` (#17033) --- crates/primitives-traits/src/extended.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/primitives-traits/src/extended.rs b/crates/primitives-traits/src/extended.rs index 69aa3efa63c..e235f47033e 100644 --- a/crates/primitives-traits/src/extended.rs +++ b/crates/primitives-traits/src/extended.rs @@ -358,7 +358,7 @@ mod serde_bincode_compat { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug)] - pub enum ExtendedTxEnvelopeRepr<'a, B: SerdeBincodeCompat, T: SerdeBincodeCompat> { + pub enum ExtendedRepr<'a, B: SerdeBincodeCompat, T: SerdeBincodeCompat> { BuiltIn(B::BincodeRepr<'a>), Other(T::BincodeRepr<'a>), } @@ -368,19 +368,19 @@ mod serde_bincode_compat { B: SerdeBincodeCompat + core::fmt::Debug, T: SerdeBincodeCompat + core::fmt::Debug, { - type BincodeRepr<'a> = ExtendedTxEnvelopeRepr<'a, B, T>; + type BincodeRepr<'a> = ExtendedRepr<'a, B, T>; fn as_repr(&self) -> Self::BincodeRepr<'_> { match self { - Self::BuiltIn(tx) => ExtendedTxEnvelopeRepr::BuiltIn(tx.as_repr()), - Self::Other(tx) => ExtendedTxEnvelopeRepr::Other(tx.as_repr()), + Self::BuiltIn(tx) => ExtendedRepr::BuiltIn(tx.as_repr()), + Self::Other(tx) => ExtendedRepr::Other(tx.as_repr()), } } fn from_repr(repr: Self::BincodeRepr<'_>) -> Self { match repr { - ExtendedTxEnvelopeRepr::BuiltIn(tx_repr) => Self::BuiltIn(B::from_repr(tx_repr)), - ExtendedTxEnvelopeRepr::Other(tx_repr) => Self::Other(T::from_repr(tx_repr)), + ExtendedRepr::BuiltIn(tx_repr) => Self::BuiltIn(B::from_repr(tx_repr)), + ExtendedRepr::Other(tx_repr) => Self::Other(T::from_repr(tx_repr)), } } } From 6d04e66d396a13e1e39bb5f8e3947a406c07f1e6 Mon Sep 17 00:00:00 2001 From: Alex Pikme Date: Tue, 24 Jun 2025 17:12:35 +0200 Subject: [PATCH 209/274] chore: fix spelling errors (#17029) --- crates/alloy-provider/src/lib.rs | 2 +- crates/e2e-test-utils/src/testsuite/setup.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/alloy-provider/src/lib.rs b/crates/alloy-provider/src/lib.rs index 52334d23317..de3ff9dc19e 100644 --- a/crates/alloy-provider/src/lib.rs +++ b/crates/alloy-provider/src/lib.rs @@ -143,7 +143,7 @@ impl AlloyRethProvider { tokio::task::block_in_place(move || Handle::current().block_on(fut)) } - /// Get a reference to the conon state notification sender + /// Get a reference to the canon state notification sender pub const fn canon_state_notification( &self, ) -> &broadcast::Sender>> { diff --git a/crates/e2e-test-utils/src/testsuite/setup.rs b/crates/e2e-test-utils/src/testsuite/setup.rs index 1c1f0297064..bb6140ed9f1 100644 --- a/crates/e2e-test-utils/src/testsuite/setup.rs +++ b/crates/e2e-test-utils/src/testsuite/setup.rs @@ -24,7 +24,7 @@ use tokio::{ }; use tracing::{debug, error}; -/// Configuration for setting upa test environment +/// Configuration for setting up test environment #[derive(Debug)] pub struct Setup { /// Chain specification to use From df13c6e58b8aa2092fc69a15a9e901a18628f82a Mon Sep 17 00:00:00 2001 From: FT <140458077+zeevick10@users.noreply.github.com> Date: Tue, 24 Jun 2025 17:29:57 +0200 Subject: [PATCH 210/274] docs: fix typo in transaction expiration comment (#17037) --- crates/storage/provider/src/providers/static_file/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 1594941e903..c54b4f28e0a 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -242,7 +242,7 @@ pub struct StaticFileProviderInner { /// block number that has been expired (missing). The first, non expired block is /// `expired_history_height + 1`. /// - /// This is effecitvely the transaction range that has been expired: + /// This is effectively the transaction range that has been expired: /// [`StaticFileProvider::delete_transactions_below`] and mirrors /// `static_files_min_block[transactions] - blocks_per_file`. /// From eb5e367152c1f206f8c786290b346e79c37ed746 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:16:37 +0530 Subject: [PATCH 211/274] chore(`ci`): rm concurrency from book workflow (#17038) --- .github/workflows/book.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index abc93f85c2b..2460d00d581 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -10,11 +10,6 @@ on: types: [opened, reopened, synchronize, closed] merge_group: -# Add concurrency to prevent conflicts when multiple PR previews are being deployed -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} - jobs: build: runs-on: ubuntu-latest From 48743963fc7a9445533391fb5057f42f51c84db8 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Tue, 24 Jun 2025 22:15:48 +0530 Subject: [PATCH 212/274] fix(`docs`): broken links for images on landing (#17043) --- book/vocs/docs/components/SdkShowcase.tsx | 4 ++-- book/vocs/docs/components/TrustedBy.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/book/vocs/docs/components/SdkShowcase.tsx b/book/vocs/docs/components/SdkShowcase.tsx index 14a1f491b81..5f878206a84 100644 --- a/book/vocs/docs/components/SdkShowcase.tsx +++ b/book/vocs/docs/components/SdkShowcase.tsx @@ -36,7 +36,7 @@ const projects: SdkProject[] = [ description: "BNB Smart Chain execution client implementation", loc: '~6K', githubUrl: 'https://github.com/loocapro/reth-bsc', - company: 'LooCa Protocol' + company: 'Binance Smart Chain' } ] @@ -60,7 +60,7 @@ export function SdkShowcase() { {project.name}

- by {project.company} + {project.company}

Reth mdbook has been migrated to new docs. If you are not redirected please click here.

diff --git a/book/vocs/docs/components/TrustedBy.tsx b/book/vocs/docs/components/TrustedBy.tsx index fdda21d0a0e..ef50527f8ea 100644 --- a/book/vocs/docs/components/TrustedBy.tsx +++ b/book/vocs/docs/components/TrustedBy.tsx @@ -8,19 +8,19 @@ interface TrustedCompany { const companies: TrustedCompany[] = [ { name: 'Flashbots', - logoUrl: '/reth/flashbots.png' + logoUrl: '/flashbots.png' }, { name: 'Coinbase', - logoUrl: '/reth/coinbase.png' + logoUrl: '/coinbase.png' }, { name: 'Alchemy', - logoUrl: '/reth/alchemy.png' + logoUrl: '/alchemy.png' }, { name: 'Succinct Labs', - logoUrl: '/reth/succinct.png' + logoUrl: '/succinct.png' } ] From 5f688bb831115146caf4799dcb875287646d76bf Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Wed, 25 Jun 2025 09:44:37 +0200 Subject: [PATCH 213/274] docs: fix errors and correction (#17047) --- crates/engine/service/src/service.rs | 2 +- crates/ethereum/payload/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/engine/service/src/service.rs b/crates/engine/service/src/service.rs index d9093aed30a..f634d2a3264 100644 --- a/crates/engine/service/src/service.rs +++ b/crates/engine/service/src/service.rs @@ -51,7 +51,7 @@ type EngineServiceType = ChainOrchestrator< /// The type that drives the chain forward and communicates progress. #[pin_project] #[expect(missing_debug_implementations)] -// TODO(mattsse): remove hidde once fixed : +// TODO(mattsse): remove hidden once fixed : // otherwise rustdoc fails to resolve the alias #[doc(hidden)] pub struct EngineService diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 9fd7145033f..603e7ab74e5 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -307,7 +307,7 @@ where } } - // update add to total fees + // update and add to total fees let miner_fee = tx.effective_tip_per_gas(base_fee).expect("fee is always valid; execution succeeded"); total_fees += U256::from(miner_fee) * U256::from(gas_used); From a78be9c133689521f561cca9f6a7c1176ceb0b7f Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Wed, 25 Jun 2025 13:26:24 +0530 Subject: [PATCH 214/274] fix(`docs`): banner on landing (#17048) --- book/vocs/docs/public/logo.png | Bin 100250 -> 396131 bytes book/vocs/docs/public/reth-prod.png | Bin 324203 -> 320812 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/book/vocs/docs/public/logo.png b/book/vocs/docs/public/logo.png index 04a889b9d29a37b462ff33adf36f67b5210a75c0..fa113d2a6744da587b5f157de35b38a7a52af6f3 100644 GIT binary patch literal 396131 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

ORXoM2#&-;=q?7R)R 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

    t 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 z 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

    ^I*5mlad;oQ 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

    G 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 literal 100250 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 diff --git a/book/vocs/docs/public/reth-prod.png b/book/vocs/docs/public/reth-prod.png index d06c4579ccfbf6c8be4b77bd4d9438dd8c80e591..5b31a569a36867ba7f5a69f53aa76b567a6424ed 100644 GIT binary patch literal 320812 zcmZU52RNKx)Aw3LSt05Y(W0|@MDIy-605DcM2QlFU`3Z8B8gtZF40-hqD60ES4$AR zNAI0gKY5-v&;NPf?;4ley07b;IcLty{ASL~Mm*M0y-9kT6aWC+R9AbT2LRkq1OV_^ ziGkPuIruDf697;Is6SBBN8oKVbf=njzwq?s=uYGuEB9MHxj^+Wd7t|?p6?lyFNg&_ z5vP{{1BSU^28tpTF}~m5Gl+>k%@hQvuW~nC9+q4+shTQWtm?|`U8Rq>`_+W{wD0I% zF6drv^j~fyHJvZcocGS0A5}M9^zWaxv0Tn>%716_5mdlobSLrV6)tz9SeCr@LpwJS zxe8af{;SLFt4pEFd7;bYz{_RS}Sj# z#9m-@5xIV<^LkBJ7uy%4>0lamU$l(-hL4@Fx6KRKR@dpw)x``>i)G!bTDM!gRw$AU z(TP)Z9g){aoOq$#s1vyVX~xYCxzu!Z)>P57liS_>8|m7sjZ;63v^)<<5(F<0@ml6J zzEYps#~hCd>ICI|KOI!KfHJR_1RNI4Tx@xtzr|fMy`AJ&pojRmPmb9a2yE5;*>o|{ zbWzlF%HOPT`CDOcZLzWbmDG-V5;$P7aa(&iW%>A4Blv|5x0Oj`4$)d7?ILEX1K`@j$_ywMI?=B=D= z%(mm+nXoU=7bt{kzC88Ep{v|=XFGLf?YngklKdJ3I5oTbr6&bDH!H|BJ1JNKysTNA zf%SmKYW~1d?wXPc?o#fZ7Ov7J?!Z!_(~OZh&y(1s`O2BQX#WgLQI?f_iqm(mx}P>J zd~t=JBt$;S1DVlyGYyqihxJ@}681*5Uw zOzj2n%q8Ps)Y?f_yHIj}VtpIIk&0hSrM+%b8F$IwT{SH5tm9` zTo855uEy^i1p8T#_eCtb!Vd!Gm3+%R4wqrSp}N3u%h&sPbG`PBaTHQ`jrzyzjcwf3 z0YM#_Wbt&cBe&$mZ&_=Hq?2BU7Ety4`PaNu8iT`Du(p({ znn+}ih>SguLg$s9iL!Sub-v)VLog=aYgLaHfCa-es4~RRJc--^cj^*=(dU`}_PRy5vr~ z;LLwm`nOj8`F*lPZ9KiexTWy!a>-*l%vCmcdpYZy1oH+{)&AM9z2Xh>ew^H$)!pmU zeCcp3S1)+XbWCvc{}J;m;Ms=>6@^Cs6S?4PQ}z1dCi+5qhul)-Ozj{3Q0dK;sM-B( z>jV2wDB6BL6BdvsoC2Y&+#W01c`JZYpC4{X&MH=<$91D;>!|qb-upBz3AfXZ_K#o? z)KJDYb`xi8K%8GcbTEeaUrIO2s5ASAMTz?Js`39?B_fIBz=sI6dQ$eW6M&D^phH%2 zE5UOUTOAU5OjNuQMHy3Wf;K$kTONP0JJq7VM(|8 zQIo4;05BQ@7~}$C-&iFp2W7H`|6dm72$(lew*uNexfcDgpMOc>QxJ<+4~1lV0IPd@ zr0Ccl-n-=}0vpa``L~qWBK4U@DVNWTNrky1%7->YeYIPLAo3%6P_ALw#?oBo*vF4- zxs%fJ-YL~~6>5vg$+X@0N0fj5nsEc3xx)S1tX%%rq+so4iU;R4*%V6XH#VV!&lb)y zIBo*faKFE31@L*kh7nXk>o*KxpJUVAjtroxwhq7qBDGMwP#ES`q6P)~Dl>}00D`** z!0ACtB!d;p;|;R`^Sq<~pTyyTL5DZPYm5I|8iHEQx)SHfS@$@|=FVr^bk*nayR=|} z!^3+Hz(=gqrNSxXI0&pQ@(hBP^+Fe&@uQ6-XGiSU;58`3en)-uD+n(M0Y2T z4t{p+TRu>xM!uHRMA}5^ih`!{C`JW&@kj*i4D2f7yPZC}Q$E1v5xspbU1W_Gw=oj( z`Eq%npv?)xQr(NB4e`_9wCH0;HPQ*kZp4wfmR?)Oe`x16^(YIx;k$nI@*kV@mqJN; zqGV4cAH|w0rW70@nEG1}OXPD>P^}O8vW7+;y@A976J`k)GK}T21qS#(xsZ+zg6xvm z#Xb##U`2aAfO1;q;AMrqS9Zi*Vk!(?h6`W6eb?>%{=Y?Uc|Z6AczJ5=vc z;C~yg>1)^M3UBzC;r;2a?6w_&jc1#3!rsTvg=L%?@wi@!M%Yf~?TrJ((hT8rix3bA zUZ}N-SBHZQME|J;ER3VF7Sk;d2~(8M@^Z*OT~%v&>KZIY8$acw-mu@ z(cBknk6M~9?;NsmPJcZ&cDBk8F;<|rBrmQ>8hlzip>HRpn29u4^e9V2M{bCn-{VYn zFqNcWbrA4NRh*AU=-3L-MXNOPDFZYc#*}++j?w1DD6tDAvxU%||1VeJDja-$9vR7e zs{h*elSZy)nF+xS##zi;Zn2IzMU5HrivAq38_{~QDh!|&sUXu-nm%_C87FLej96$t z+0TVLimd&88WeE#wWsvFKZqEb{eczTNQ88LaGBW$HZnyrAxUbKeB+a+!zMSUXibU5 z2(1Yt2(yi|Cpn{T(-YWsYaiF+XDRzj4%T0#~U*^Q)W|mx=!f$Q#&DOn9*5)kuxZ zN^y-Cgly;hWGQAXu5|pV74C+;Ex<+cvLZVDA>U)KHMZq)j_+=3)XyRZ=zt*YUOoh9 z!gffP6Ns@jdMc!S>(;lzyFDH)p~OEb;G<42mvk}<4nmX(;7OHmB^PP>M{666(V-^2 z=F5&Cyu*YZ4q?LCldMK`hO&~?R02aT>>Sgo&uDy7kV0++itF{7BM*CT3j&0Q?Y?|p znp$}OuLS-)5e+086r~ofRTKYq`G1xT9sQ8o~v9}&{HV=p0@y?z|K~v>2hSMY`FM!)x)>}my@Cs2og_>)p(v1Gc0{VWimR(kE=dXv5j0P_G(NOutQAB;r*p@gc)`=<302ntxi73R3h=}U8 zQ1KNxV%A&}X{=wo2Q;?clLqI;8_ggkFYcvrl6Gk@>x%m99Hbzy^UG|UR)BXCc@V0M z8Z=y=2jCFPi(bJUdKD4>|&2q&fq@unEMbTy> zM4`M}`Z)(e1ZL~v;*5WR_rJI;BH3sPJ}l;{_5a6n1PBLDZ{0xDb>FqY%e@`;V?+HQ zEgg&KND#`Dk04b2u!t#)H3}>vjpaK`f!NH&FIE92;l|@8qAGPg7Z8Q#vHNtGp1$;W z0ght#a7_EgfjUg4ML&$v2=ul|ThJhs8vd|Gcr3N}cUj5%#gq&pqfZkwD-mHi@+q11 zHlZmP{bPkuTtChxrm2oCswKXKAe6R05O!|pT^1}V!Y~G+9116OIy;&X*l+#H zYKh1VAYqtD5O3pBiYYLe3@`K>=B;+w$WUtzA64`~A?H`O+vC6#CzF&^WMUE9C)P*8 z-p-(3sC9Y&&)EQ~S>z`&tI#=FJn0&6ZjYD*bd|*1D*_c1@5G0y9ULV1lxh%Xr05k= zcvsx2fBAMCYjpXZo@(GdPgH0$1Txr5iFKb$q)op(9;Qqy%EIz3_7H2F4e*? za>dDR`7lFqL)PB}E^|j0Jzhr;wBtX<_QX2Xv>8kK-~YRmKbD|IF8HD;*wa^DrtW_n z`OYYHvMYP7x~q6Y0_+$^N8|iVz0L+13P_&-2NG%?<=4(Zg^I{zlKb9BC5v*%K;MY$ zK-z)~-gxqIMtkQOUffN5Fk_P1$|9b+5|KgG+>V_*Y%&dEaU?GI-isT2!t(CY()W_=IA19~-mCm2MRtz80pLD4$ zUp31NK@V;C{|5=rKGm9yxrXMQ$tR5+f93s2JL5R`rCF)fHopIek)$p?Z^q+haythX zV|H;IYb4%V{H|vE3YKJU;qEWxf?<1!_Jr_XMjyyH6m@sirH3C5$Qsa5@yUEM_%g~Q zm%&$-EeTr$V3M&<2coSrg=pD%zA}}^i;@6x7Y@^m@oq!8vzB9G^-@eT0XaeD8SYzq zP==})-$lELvQ%Sx&DJm}07OX`^HkCnPIudjsl9@`Kk+7D@Jk!N@QzCe!pJ+AT#r-1Bu?b7)%q}Bs(85msmk&4r z{DErzLE$wefA!bR3fK^1>fUhtngV1eDWWmMK>Bg+E3m)xIH&T~Gh!V;=`B5rXhy3O zo?+FSqDi7&xYp(KJY9ZkKD48HFCE+#7%#4I<5pc32AAf8wOAJ|qSMZle-ojJ=a>X| zq{Mph@>az%E7ZW6LD|T~lZL|-koi6|1u)+-rjeR}(y$?>%QZO^Pi5r~N=7THD4Pmd ziB8$QjivRtFKS8c23J*cX`OnUW&<>z8!<5;z$3Y z^wYVVJ-5|{>8Cqo|`FRVQY@oo@b-Pb)OkYD#Ue$v@-yCA5<}{^F`)&+& zzc_N~8V#d(5s8b`M#(jFVwLf?66MlY{q!%Ux(m}@F=~I`)A$H%UxNbUVYZSWs7XF# zcWfK8Z>>?JwP%F3?*mWKeF)pwV<4SW@0eJrVNXu&c)pgvW_(XXwM^^PWhiS;?c89I z%RDxZ4cLvjksFa$=tWXlF3hhiBGWzIA=`$TkZ6#912E*o>vf_G%(8L(kCvZ=keHnz z&j}6d7C`?p&LNqbv3~&!Q8J*(>R_0BRz}fV+0c8%J4Pd(Emuh)tB0)K?xk7`> z;}y#*?sDfpc${nzCj3|f#o5!cL8aVk{MkN4v5I)8lw~TnpJs(Rje>Wzb z^n1Q~LW0BtlD84@)l(ri^V}(iz!_w-h3{_3w!IPnaK88D1@+%RSunoA04q3x7qjVz zQX>6$0Yy;lqEwB<5HP;6^fNtr9!(Ms8Gd)5=EqQI#edoJHtj#*nOl@I%ip+d_93-o zqtELUj+*MgdI0Z?Y}cCw#y zW-?S%$6^NoU0l(Y$yu-EBl0GG-l{&3)(xWpg(){flHyZRd8s>X?grUwdmx~ZQlf}1 zkB&SFrv=Uq``39{SYzATv7;PM?L5q9UU8q&NQibzr5eTCV|Zk_b0x8{n7)_~;ZEd$ z+ZOJ>{PE7pA0?MNrJh{=n`x;L51yXt^?ntXjcAmjsQ`VwzaeMUY@#AAoYD`;4ug`} z{dz`e+_$Asr5_{Sq2owW^va##O2ihEq0lhaSGe)=Q%xh#t7QldtQD5#_d+$mIf_&1 z$E*zuk>+mt*bG&;fi?+@HbfosmY*`ahfS$6s8m!X0S@3N0?ZZI$@aFA*d-uDD~yn; z12lDgRd)8ghKegbOL$rs6u-Rxxa}r-!0laS?8rMgB`rgsxPkWAHJGwZ04@30IVJ`r z3ES3J2Ziqf;?yZ>ypV;t#uCuO^2l;^Lt;!hC2PRzzX$s|EEf3_dvqc`p3(hV{V5cJ z_hm{3y*9aWmjd(+$!E>)fIi5;?YIMJdLW~24(5^02H3jpS=0^Ik7$|sayl%8%hB%F zT|ry+3b>D#PusoVv9H5H2O-H>^85*_p6o?%yOwAX(%6;?V%s$}9v1wdvY!P@;X*+v z>6Hnn+i8};8$)5#D;r;KID?f&O}a?-(hOA55tosfE2>%^IcZ4;2=)rLAwA*Fh813k{mN54g(%q0 zOT*~*5GQHwAaI;q(dBkj;CU83>(l2a#Dxv{5XPDnMX!>7He>|Z+;36Wa`g8oTswb} zVBD4_tOvc9PRkncT2&%D%PT`Bu8o zJwx+}uQ@eqhSuDkZv#{_eCo`}S^(a`+&$TaBz#Y_YS4n^#Eu`3G@6g4v{Xe+9Yg$R zirzn95ai5~mz`L(%+% z$1Hks30RY@cY1WpGzLsAjvN1zpV+xUJ$ju)y%|<-`tK-VgTyQuupMeFR`w?66%nlm zfqg}uM1yTqs4$xM)`&@I*Nb8i%M9#Bkl*7H5$oBiqmOkI08q*Jnk|)*q#v)H1Ki(R-&RKHB0UZyO zdyFBlvN=uTM;5~G9l*D+AL~LBwQMV6cv?0hjUVY|+o!;Y=bM77$mg85nL(6ayF#Wsg@-J|-QbP~_TYRx zp;V8{)u-uMv3v)c;_p&8qfbB4PPmAO3~eUI{)EMqa)P}J`SeTBCdKg)lmKHsfuyub zICMjzJ@LO0VI}vU$bOT=_3E#>fT(wh<4LB$w5N6jz!`D%uwPjb;FHUDQ=tIiV!oKK z8s}yQ2NoB`Z=GX9RjKI{Ixj|zP-%qH5cy$2a<9BX<(rEws?ij@M(?>oL6Ua7Mq>UP zQvz1tY>8~c$HtuwdIqoq2*OL70IrsZb>aX*?KcOxVK`%Xr^-+a>?$Xcd^2KQSwoL2 zxG(3cXE@pwA^OS$q}#PcwVs7GAK=^d_(@@L6V~G(DFF~f4YGUI`CHyJI_E)A;lUptacbq=&vc043AB*+ zRbUJ(ACAo2K=*3Lnn#aP_r*1rQuV}F+VvOemOC&Ss!Mi&yVtF~RxL2CjIqT}<%85i zKtGLu4$1N^y}UgRNI%_gFg9QI;(m%1E~K2a1Y(ANJ_uWdb6CCPuWb3NM#yF--9PYRoO=55FW6is zcty>by!|#-uwYu0m**3++X1YQ6sq$mob34vf$Uqsi<=S}j@ee6IDFO@T?84tfMUjDR1!y4rbDR0R#S>PdGn~m-Uo7YuB)f}B>pMj{v4s*tT$`Tg zvESs!umB=dxB#|sDW_;b()+dS)1{-nizve5z=|8dUG}!QsqY(o=*T z1(!?sSH~^hhLE}Tg#SwY?%bzFbX|d41F(&~@4b@#s*tNZ-V9f-gjyx*;xTSoC?++# zGIj$q)RkXA8e2)DbK`x~)#;<@D_Z-~BEkOl!L#Ql&e{T2r+#!weY>J7R&0 z8qEP^B{1#QQ{&j>mukE#&ZqhT=!YZ@24VYf20kdh4m4qDZ9ffe2y^iU^;!&#@Y|YO zb4ji}e_#+=83uDp1uQ{TjDLNOIF=1_9516<7b|;TDFxu$rB8fN2;t!GgN1wH&AhdX z;OdrV`udPHf0#s<<6; zYqu|)79ZXck5z-sW5Nj&Uza&r-rFN>jxQy}kGYPn`rkv8ZlF92M5v>+AJb~}EuTAx zb(2@c$nTEvsfnp{@Zw3S=B9+&ax(~;K5vf4b^*q;2tuF5*A|f(NityW5O*tw*kdn&-a?@ogHR3$b;FUbS zI=Gt_o~d!c!C%rrpeX~5_F~g#RmpEs+}#lOsn{6GHEXxy%+tDvflX57+xII77vIGn zd|WO2lgGn|0U285mM2S;d=NdG!dDj^*^kEFylJ30r!UAW_unr0|2ia9B)}DJW(6p%4(ns$w$xJxwYAUtwJStPsRpyKEQWi_oGU_E5!z?c49rgARDD}?^ri$!murzP=<+;)e`kH5cOl-mDG6$wWF1DvzOZ? zfjWIpmVY%zqeD5gRyT903aGAb&ix0RGtAU(0gspc!Mc zK6~>91u&toyrp->XcYTyP0Hjr(DxAttAqGa1;X&70W5xYgxPtXVjNln&ty;)%^YmS zYLWW`e~7aUCRuixd;n^WP9Srhi%+pN;#IT5e2yZsiKXJw3zBTO6xju|hjz85t|*jo z9#-0r+C^BKuE=`Fi;2rsYNs{5*C@^Obs5Zy4?9)VX)6s4i_^FKr4@=-VPs#*2Rpan z8s;d{3o_)wJ0!LN?uv$X!PjD=WY6^9;lOTVIv4FcFibN87yG*CF9rTEeQKVeaN!I) zzLqe&_SH>VkA+Q7XSR}xMLls4xru8v2YWLxb_T}?}0{LH5e2GNeYJCl_m9S1Bjj=%?55t(3 zAc3ilAzj~|xg*u@IU2x)t?a!Mfe8ywNOJ~XnV^>BH8YWO> zqeK&O>@XagUGjvQ`N$H(d;YzJ+_qKzq^zKhFgTbCaBRQD`_*Xi4U%-sy`J+y?KS%U zOG*EVg#X@@={AiWGZr`f7WAl96HKMJ6EM5(EABO&0&&$L)9wxTr3;{1j*d-RKm(n4 z80fx&ct$dr9!HRi>}X;wVwV>NWgbm+kVUE_tH~6fZ=WJc%LwO@i#$T_eAXkMx?$M zGVqn2=>=?vQxr6Xsai~QCB`m_)5R~ruMKa;-Se_mWbZ(VIO0$qKITh${=krRNtSvRany#8`kt6};*{AS4 zF=3{_CLu?GoKwK10N3E=N*2-?>rwDYZd;=A#(Xsz{ z@IJTDFE$~SJhGU4BP9M;t39z=B0v;9%H%$5QbX*R6k#~%cG~0aWILd%r>kc-_0!ba z&>1^^Qn9nTh2`=Pl~a&Z7pdeE$2`v1`>Y-U`^;U3)RgJc8>9{=B@{_zDT-w)O@ddc ziFJ#Aiz#N+%{;SDR-7%Y;&Ul$e(i-yY7J4iCzL$BUXfLFxkyzKI_Yq2dMp}Qcz zF8{Ftb|#(5r8%OkbZ5s2Xp81@Dtd zI})4vQDLe$LoU0yJcF?=2g3WZSh%x@cx(h^vNnD`Hm*6NIvdT~RX~{}4VLTWrY}3ww9}iwtKR4hvHVhxhX?wRf>K2&QW@uhF79IaEGiP z$K5L)K!+?s-APnQjLqW<=JnUJo0uS1+rs4vOKnLaE=1#Yn>kvTCP7{yI%gnL4Gk#&r&fi|LZpGs$U@x!z zaZ2Cmg4CgOvfiC9q#2Ov!lC9OD+TLhbsY%=+WKQv93Cnise#sRRv~!W!AE#BXYvW( zFLYJB#EX{~{Wycx0Plr-P=x(2NU%hjul1b;+gvNI2=1E1hgl@Rk%KgHIKdjBW>sta zZIjRR+U1HaB6v=RDcLS`eeU)q^AD8xi(XPUzQU7<$A`5SR(o(b! z>0}fvK9<-Yu8DbjoPOe`zWuKegG1f%)?I~;#S>+OT2*-J)5jL~hMIG!@QZlW5|Uh;WD;uKlIvgeej*=ywB zP3CtL(PV~$6^-0-lhNr~ONHM~ucc0|w^sM*u&#im=s`YW?sF+R+AXd-CDZ>9AsEug zjgd-0Ci#tFdvBMs!%5VblsH7o$ah0U1|Mr-_jq-dtTa3e$uvoZ>YRKhpFH}k-(xW- z%WkherYQRSH=$iemE@i+VRxf`OuB0C%+{NP$VE=>xXqU;xtVorXZI-C;R#d~akjns z$Ot_2LzRSjUzkt2Is?~@Y?^%a64TSHHnd)kOYPFI9euLsb#6*BA)!A`mvi*vme@l9 zO{J^tV5r@FZNt=j4w(!7U`WpN;}(d>1X4@;{2t}Rd{S`?PS zBu6xyY_4OFnxQB@nTgXW_eWR1d)Z>5vB4(Ix*?`5SXJSw?dEwUMLo-klP=b5WN%P0P!y zu~uv(@^?{d*O=h>HUQV`HR?|m#u#kopIas4&m)2tNn-`91c0Nt!4NIh72!82LdL!N zYhli@w*@(ng7N55>*PW;0~s9ej}~+?BvdqoF?9f{ja6$d7mdo(knOf2TPMU1v(dhw z>FS@N5~sZjP{C3Q20d1~_CS)15r<0m<+v^MZugmp)M*fIUh*qfq}Nh4b9k<(<;)W% zj##g8<^|HjIFA~5zVO~AyiUk*4Jmo(K_OD!-A{*5S}03qKm}f_%`8Tjg{kMAKzvIu zAu`itZ|gRnN6EwaiJl&Lf~9$i7k&O+U>&%^4=}l7_m&4RAhPjAm5g7g!gnHLRQkq; z%rHPN%A#$)y@ibSWx5G5KxYDrV!SXrCpf576Jd{`E@U|gizL~I` z?a~*Lo_RZ3Bp}H^Bt+c{aM=o~m#N&l6re8)TV1yx0jlF^_|I>CYV`8C;U4tF^&J%* z%+MNOlNoY=r79Sxv~&A$l5K{Ei0VYuy~nrypnfF9w)+(lq=Bk_~)R!r+N7 z?<*2PD}=*_%A3Q0^iFxN3u;lCBXj!vJ9LBgq%S{>`2-=f)o`Mw7S8m7d3;~pwmP>& zcZWWE2cJn?7s!HQJ;VGYvr|`5?}r{b(^lCEH02wK9dkjYLTg~i1hVYj~4}1Vb`6ex5ym(#im5|&wJJ3wF z!wZc>?wIh&KYp&&tUm`NJ=641WtdZ1%y-F3l2vp1;qI@?igHXznY4`RF?d3I#0iI zR?)fh@T^8?D({NubN{plXxbJCwloG~yrrf`B+|W8PS(?_+vK!nLlxGTFMcYV;-Z9T zT^1^y24XgU@YI!|ICW7PkjS!q-BS&^zb)OO zCKw*$Ou^CZVKu*w1W@~1a|tIXqM$8v72A;phn%JcSaNDs@iMH#XT@o;w@@9C(I5VN z+oUW}-u2_=ObC{x>yq4)gQiSe)wggGGvD@+6JN=F&Ay$t%g3F!&IT3O(!6&j1kX4u zMc)?VmZ*26Y`dvH7!I<2#7S+_C{?k4b!qQ&{Ir#nMWzcOx?|_ou zpa^_#yOxf~?3Tr_(29bR=$LK@ z@7^_SHGwK)J=SKJyN3BR?}D7z@+YI^xT5uzk1)Dvtcq2HB=;8!T346z8-R5|GRSJF~pd4z3H_9~E)Fd9YY=LUSmwA`8`sM@jk2*S{cvjZi!C+rcM zQr8t54+qCyVH{-$H3v_D(Y=osnYM1K{xFtxv?>1rU|)@&X92&=>TGD0$5oP?m~gH` zkGl6$5&Or&!OUYH+RibkbCR)#Fa$~$XMf?mZ{02NSSOHs+2vzeoEW25Rr80=ePdq1 zu6fSGihbZoHuFU;xWE6G@np&ujDRyzK&zbx%JCgf2(=7nZrW<$hRQ9ppBH@^S zNS@HoeXv7;;!`}uZ>G#D#pN(UDckBYn3w;(q22}ykk7_vsfd`2^6+%(sRNqY) zd5ymr$>7v9$L9{y3_CX-7Rln%F5TG4hqg(WHWKbWW z4>h^qQEBAEC7%81figS#(?9g59jdeBF>*=MFItMaeV@Y`^DtU82w|;)B0T?)VruZ5 zwGH!oPfGIUq08sf}>o8nGZg8PPDkO zGjoC$WC%fFXXUIdBj4SBuDM`b<{}d(c0a~&cS}4-Zq$$Sj0s2l=zQuMAk;06XGSm! zN=6^MAD^1Tb`-(KAse#LtyT=KV+iG;wKVRQc-AP}e)Y*WpvqUc*I^{_W0;Hmu7 zkchm?g}t_IsW(snvPWFj)&U^*R;?G5kR(2g=I87EUlp#jYegj zdmSVN;*$RK4O(5UH(f^dmkI_1Rmz@9$e(w3-Ep`8tbg9}S0|F_pOpY5>n(TgzE)^Q zo~pL{jqRv>G@qrYpJr5;{~%dmDYjqWL)rN5%c5g8-IBSo?lY&Tb_Ws}spf6v^S<7@-JoO^&u$9O?Pmt^A&VBL6V~dK z!~VIf;369_T6ti436pNa*{bgrPInsd)4j7%7B9X+dK3+8i@Q1cc{~!0+iI$?W1g+L41KT6!dety$<-N9R^_@IyB;1Hn_yuY8=D;WnrOrzMGdKCgsW z@?UNB0VgyEKI${{`N!@f_P`afgMqilL3YDj!s{g5wQm!Y1kFzb+oR|MhTUufVu$%N z@()NjZXbeOLv2@T{8+8E?p3oDQ&bY&0MH$!SCcFn z5GUoK6_bxdD+=b(T=`}L+kC$g4~4z#!5p99xm-tN!mmuI)Da(0OS2u&wv$L0W>rB> zPK7)dvTNHfANX8xHl8od1+;|#U0KY%Ag*`0Xfp|TAF{kn_7=wEiQ~F;#S>JwGb>#@ zk^+Vtz71MG`Ml`+gg=%s#lO91N^*ad@YQ0{8%6_)q`i-q8M_~P+RUw(5sfq*%bh2I z=T);a&DP=9J)-Bj0cFc0H!Qz9{xB82IYZgsCVO#REdSf#OC>U|KDV|-?38iLSI6f? zUq6^aVd8|XbDgAT@34KY$z5{W^I~x{)UR?Ubb-4i;6_ge%P~pc?&<6(j;pJ$bK+0` zX&)1=69NdhdUP)0vQ}IRib`lm;`cr<`uc?-`SmN}o?pgyJb?k;A_u9HBXuf!tuB!1 z@jbO-vKhoO2XjFuUK>W{Mm?_=bUIQlS`tnYa<0A_%_V%n2~E`?c?-MuMsyQKvSjrm ze)A0-7dyee@pH$B+b_sTCtvWJDSnuRi36Q{b9EWnoWh37O&7laa>)QP6;8*vQZj%vNN;XF}M&J<63_q zo1Qd45GDl)3o{Ww#2Tn4l5zlERz;5GXokM!PvaP+2||gU(=Vd5PCGd+9SRj}`D$-| z6!_Q!V&Wq(cHbo(nS}HcjM<#nb#EHi2em-&-~6Oi{DjYLL`*Jq?4f+P4Q*i*iuLBg zOJd(Jjrm_f25)ZXf_n<-y9$mq0~$-qVjp1kI8M9!1&Qs5KT@u+me5KDIt|iS=@JP? z&O~Mz%6v}yCD82Zw&XhfdH*a_ zeI;u{n#9qZZ;364BJ!e!dH!5Tii09@BJ-fYb1-R0{#}@6??|@y!oziMwg8XWgmWHm z+=i6zN1ltUXY!BiFPg57uTB8nmeIM7+N4nZRS2<`en0(gO17B|f|1)($>0K=E((Ci z*+QANM{$S$o_SgcOVgW^7k#1!dJbFrqzXs+W*3P zq7L#lZS#g#NrepZ*orNm7jVlWzrk>VGydC28e&2xZpol>$tsz^v6kg7r_oQhkp`&g zS|4xCrG%)So<_MPSvf8M+jWa;x3oW_bYOCyIF57?)3}2e-1oxRCx8jTn28TvdL7fY zgC3TFwrm|tB-%OXAB|zKUN=feD%o67`ACP{#Y0o|;>S8|PghinHqPfmVr5PRz?|#W z-k5J@L&*)rwM+YBh5e+`dPb#hlK3SE@r})n{JM3U;!SxX-rsT-@D&0e)PqI6hS7Hq z0I}EgxcAAx4Gy17kU_W5nZ_;clY+WF8Yh%(L|*%ZT8>{FWN;B+ZSDtRyAU7zo`RLu zzUr*S;8&%6rD=h7j1Jbp=RG>IOm>;KC#Ig6NZl@jn*`_Kk1mctK5W=x+6RAt1wtbg zwS5&~)BM*VjFtg-vVyXMyHpgtQDv27pKLq=h@Jfn`on<6z z?%rFT^Fc>%Tq4p7C|=`qy0=Nr8CaT#F0UKZH%p=l z_eB0?K&bnuHHyX33Z2T-cI@s{r?|8jkt_81Shn!@pw=(aG(9HP0PZuYX9_UAE7@Uu%5NK z#8414h@0D<8)hDjdn=O}H1rUO=C$}1@cv;JQ!Uyhl45R?f2NOlsxcWi|7fzW1S+_a zb&$I;^62Gmw53;&P|-I;&;-W^M24)YjaiCo?Zzz5ScrM)u20>FeMQNfI@Ea#hilbt zF^eX`62iR5UMdfXleql-E&%$n47b=>Mn7-ZJ?SBD5wlj9FJqiRb7WN#f7Gw0SHmN` zspOA@gzG}%gv2YTO+P#HjzC}3m}n-V^Fr#&hI-6ByqI}pi`<3JZKNaGMAAYWqu^!A zZU-oX@y2#D?eNv;LCe^5?oZ8PJ-O5)Cc?+O_)nkR&zSwJQmJ`#7iBeX1E|O$i191f zOGf-GeBu<%d=P=4ELqxwn^7Eh zx!g%+j9EVU&L;)%u0hxny-z(?r)VFm-NUc$<}Q$VZUC%`9;D~n;8*V z{~&|2GoL4?IqsXXJ04SWIGF)3;L9Teu6@L}Fco2S$B33i0;;p<=c?gvBBYRs7r`Dc3zM5alB>jY z2A&{La)CseK=6>$dAaZF#~i6T%*CWLfQ zzcPpbj{v640%|CyXJGvhDq`y)=DFbUI7Qb`w99U@Cj@3I6c)Q#0q%n^)ns%heX)Tl zhMEY>J={Z-_TP4t>23S=Q;k}tyxY6=AeSadE^%Gu7bLBc^rP=-kikaPr+Qure1(MB z+Qh;XD(+if^WLmJIgKo6df%$69ZngI<>PY4dlSj^UC`#=(>j2>hGSDJ#e9bYUg7mT z-VkZG6|Y{U>FTlIEfD{dP~O^6e=CE8l}KrhW`FjCtvuTeu2fFbZ>|^Y8w9Xm)v^;sM|+&TTM53onuDrMX>UM$hbnkXGVM%y`tu1E1ano&d3>fGdg8Mt6d!v($ZqR-AdCRg0xm z+8OV*W(a7{pj~KkLs#NTQK6ebGRb&9{A8z%c~$~8<&{ZtLv2r znX7Eg+Ph@fn)h6KVpY@FBO;UF`dY5PGVVZcq34+~v-~U@+mqbj>-1ROX~pZhSA;m? zTL7)a(r09Gt1zA)+?S0>$-|yf3vGaJH z>s;q`;`w}DA<@<<72^gjj(ym#1o{DveGeMBv)ZDcrMc~^zOP*UR;C`Lu>+XM`Z4lF z8qCG%w}1OHsUbnA8K2`3ag{^!ljk48n+*IsHFNZ#J4I}~@j$4KlT~;!_Ging@fF)h zSx72h=yj{k>Vdwqa#nYjJ;<)nnr65kiyTYf7j0NgAczp1ySx89kGBrK2^zL|peK1T zW~Jg4v#6-$?PyDQ9v&LaPoA(NMawc~(bQn@EB49-zU~+8$musUZ6P%!z8T;xOE3Ls zU*j{)9&XFw8U67<_a~2mK%(yAwCL}-FIeu0NUyobu3yV>=B3L#96t=$B)tL71rVPf z+?>g0)>K7`T4GzSpE5G0wcn{E}Mq3@m~r2?J!7ISb$fp0JweMPKJt znyDnk8h!uAxXcy7XOK)S;xIlLsymvuWLiq6b!Is1-9cIRR40&0sU_?}vmhITxc9{{wODE%E>5g`NE_`E$T@rhPdN=M?BUAkDUVAF;43c3R>hUB` zacL%-AJLo&`sUk&TErh5A$A*Z>U)0R4SbX=F2FI%6wK=CK(H~{($5nwoi!W?x8cg; z(BG63>zNtH;_|35sDt&Jnvy*5@gW}|9H4Qz3E{pf&l}vkM7H%^$9g96{ zC~Y6?nde)>%J)LWWh{<{J$Ep4?_)S<-lEgTy@O#$;`Y(Xy2{s?c4&8gSHs2GZRZJ= zbk}ij5N94nEM!5dp0H(!@ZJ_7!d05rwdeSh!}n8ihiiKtYAA!0aK&itDuJebBH2ez zRiS*>i#j>w#9a4wr9$TcanCec@kT}xOZJGO7WP?)JmJOjuS8Kdea&jvk%_Ha-wyRA zFkKO+{Rmt~J_*rN%h1G0X!lkJ;-sd7D6jsf+TOh zj7IQ$_npsK|Mf%Z1~%RAsLg57%n~N#FpoOGTAZxVJRiyv}xEa+-^gc9!2@&!vmF3 zBrz7{l47vEaL|70l>Aja{XT2tuToVvVMJOA$)WBpa9kk5I3=wlhlA(lK089J4=L@R zQTrjQD5it>ytT^j=?lnEVsW4#|Y8f`$R36DK{ZdM`Tqn zgA*?Sv>5+((Gi#!{H+99V}j?jNw-4r9g@vXku0hw7V(HNiT?=Cyo(gL9MslRtAe=C z#YQLww}g%>aag33{P?D0KXh3>zN@5|Rq_K9t67R=Rxa?8UyGmMOa^@<+%M|bZfBtE zM!ewTC7$fCw&D?K1s!^VG!xzFz~`lEeNM?>PY$TD+EacQ{49q#Vt7x^FF)3%7`7<&QuZ*K`8NP0 z1K#K5M9j$$v-tj)AV&;Q8!Yw)IaFHb!-H6{crG5NVGvW?+BU(+-H*k#m$i9u?mBht+c9Y@OuM zD*3Vn#xuPAJOxNUJ<52LP-?I;7v53yA@Le-0gPv&JUFx6^No6zDOD(nzs4=i^FTE; zH?n947E%_Oyw@a>Wk6xb4r}gkvl`#uIHq$bk#ommvRDmqYYn`<|vvS0bXPjPm zi1-lVhg2C`E_t-@F*oVK>G^dceAVOWE=eTc`*T$s7=PBl-N)!@-FiK%@x_})MmpVO z+<+Jo-RsH6bvsm=x1zig?>y&+X8K1wP3bi%A}jW#(`Y~(taCjtW)Ay(q}SA33V;kG|; zIqdf_ys9$Wbjp6L{fc_ZH^$i2*srV}k8fkJQ2~**&XAE$adHYeG`S@TzbL-gS4oZ6 z?HgHd>KT+~&@)ON7&1H|0FdY=jvGaE-M?dDwfl%*8oZewL^ID?jZpb}(s~6lQUPUl`Dbo)2 zLrFYp+w-*XPVYoQ4?aky+Un4Gc=VxQ-qiUVUpM__L&#cA7L)zJ?H2VgMC}DY4r*-?*Rr=ROnbJip z0l(|fNhU+%CPEz~gqT%Rmj4J%`Q%~{*`4~24V)4lT5m`Qrl32NT#n+YzC7`8eQ(zY zExuz~dk&*O8(jpXf$euRs3AD8u(h591;alAjmW!7WUHl7$q_7gWa#g)&$ir%7x7dQ zgzM906t%mTTrn?EVc^dQ1 z(FIoOV|FDCgvX5wp~Q=+i#}if0r{_g!~WTX;n=mD%EZp>1+0J5igo!x&T%RgiXom! zk^EMyimxf6Np+sg>z2_#rk}>T7G8Nyxq76Dw^y#;`ymOp9O?4PxUf#7>V0WE$C1|^ z57@Q)NTXRSYg1xlEoCC=bM2J8lkcpb+HFM31s=BQp-r+jcEq=e{gL0KT_9U07o?vE z2s+OXjb@*2>eZBw)E@?9@|KDPK}Sbkp&>a28#)(?V#1qzl38D#M!wOfQDt1A3Lv`d zp0OLK&dySiA84q7X0_>6P=$iwZxwz-tC{*Bmy8`tf3MVxJXCGce^0I=f90$0{`6xH zpvdS%fDUX0eg&CWD*2t1YAdLZA7hk>u{{fFEQjNZrsQbwcBp9ncH=J>4SQ*hS7NhO zTqGY7cAUql@ID?MX0SL`?w7|shTW|e6aKVVc1z{)4+jcCIYiZT-`&)#cEKRRs;mI+ z#{s_<4Z@Vjm4XT=nIpiu>QRWJwf_jEFd;ysKQS^|i2j?L0MHorcXT8=L28A4Zw7HkL7rnz2N`9grN+n+)>k^o zL>0<~@d06BfI7LJemc+wVby(UCWXwKam~u?@yY$+86Oawn^RD1%%bHkV69S39@}As zDkb+v!^yObb0^U(FK7bnN2#0yD%7H2rF$f!VGjXEJHJ8mIDfV{(o?I)rF4WmR}PIK zO#y#NdlMt&|6TW1-2e7jHkI7B56qub?*qrw0Qm|{k*Tc{hyBw%y);?#iV<!gwnZ`el_I(!?@braa&bzrj1``N)1WM%~ssJ|T&bYbH)o8Ba z1Yj=NNmkQ~z0z zIh&OE`GV7%Q2)%Zn9QbD@VG!;Dx5?v8R~IW83*VLL!o3V%BgLlChs_~m#SsLjd(AH z%IhhgBhZHdQUP-8pwWx4-gW@lsO!|w@*)QdY7s~A=Qgx+8DGr1x#s`^SlTDyD z*o*F2 zbq|5}i7)ao)Kda}v)*ARK~ROr6IgpzZ$=i;f>2*?pxVy8;RnP3Tx^H!f#k&I`NVve z|7xeK%wl6Ynzo9e)6BjT*2@P`x&J6zd^e)ls0a?;jDGf0G?xL(jPv4~c$82XN@tEo z0elQOd@v5&7)^LuLF>5FQ>g>=6HU;?YK4=j)Z?P4is(baxAusd%wFhPt8>&?(f19P z!_0V2ZF?H?BPj|CQuN{Jy^5#_KIIJv81sK}`V>eCt;#jZ4)tagWCr77_Ans=8!Pst zBMInpX&A~0PFTwlfZo(S?odh1^=-n_eM1X#^rrJx?K5^i_Lyq7sft0T3X<4603V1_ zlGl5dM#n5>Mn%L#R3>%{;HjY5@Uc*Q+D=jC-}VZ|%_-_Dx#P5-j`Gy2Z{D9KelFdO zAgI(2N0xH{I7xUjSTc&U{pF?PLE}mSTgG;+?H%ptWc&#|+HU!0KHk(U@D09ePt5_u zw{+p0oi^hg3uTNnk}wrk1`!57_tKc>vw-F7j9gZ(zpC?yfgPbjgF$iThonRPF?_6I zHm~KI|L_OQ|KSgK8OA@mXY)r?V*{7si@4W|Iu7v{bvV2k@QYva6E4ngK&r&?J4;Du zX|2?~WcJ!3p%C`4(19wG`bsr`^rE2 zT1Y)fwZ2`XF=6#%$~Feio~0`nbth9z7augLQ!YM!7m#l=hpPMdF|HVN#tl}lao9AT zX>$Y_2#xIJ`;FEM@ezZI0T*C4-4$8H^qVEjqk}zU)znHu7$lHCJu7U|W? zm30qL7X1{#ZZFMc=9SYY%wQj3TQv6t@xjGK*sqHMf)k-YZh1J5MjfsVf03F`A2#5- z$iwkU5JO~ofOz1>*dnnSWK)Lz~D z`4~KJ8mkDKPkCy3wQoVZ`I9gT+L6MU!IAi1HKUHN|LFF8mHNHOCv4W5Z9HA2<;%lt?vNTNsI*6P-uEYp9T> zKk}9jp^p)lD&(kZiiQSp%df)fvxj-jOy}e+UTxAd!F3KGQ1x*)4v3_AFd~`C3NF{kC;hFqEy*uUC*l&0MzLpik0W) zsh3tJ?E$Kv0N?O_E5zX-r(0NhaQlkim8P^mzm}($0H}5JqBPTb>^Y2G6Tm%pS@}>n z=`a;{3U7&G85|5E7FCsW>`~UxP~Wq+yM8-@E3|Cv%lB0XS^EK!I1_q7ss&5MD5Q$< ztJ7*Smo||Pz=6>mr`yT+YKwJFxC{T`?Rz%;Y-I-Uul=XZD|=m&Kfn#vO>38&1H>rdgV`qU=9B%jCy z*$rBGVfifRFaQlbjWJ;R!Sf%2Z}e?6$|AY-Fcm&jr?dTMN>BD^Rb{tR7Q6bFUb^l$ z9hhKm2IpSGI60UaW#&-wrmWR8Oi4(Cjm868;M$n0k7NYY^6^n$NjewH_6%DFyKVOD zP`NZdP2i+=Q+9@T17Vqga#lEebCDH6V5|QDSYZB_NL@&%?k;3DJeU@vz6c$sBkjP{P1sk+Sw zORS78NT}QaU1iX}$Ko@t+IQy*5hi@vAu*m*HZG}Cyt$&EHEYHZ!Ckt_<_I)SAC^NS z9dS#|DvYJ*zO$R9z^#lx&ip_@w2dQifsng3;YA7!XC7MJK{94Xd5W5_=0(}F_UIqn z2SS;{YnsaT7JXS9R>|#-`}k7iv;s!# z`UM}jHjLRKsEmI0_HCB{GUZ>pfWLO9M5}sr1tV#b6!A2TsbK~{Bjatp$aA9qD_+v6 z0mEqh1_a!{k^a}QS`=SsJRiT=Qwct@A*)YT^F}=is)VWS=)U~?`>Ay*$rR5{ept!x z@=Udn^OTg-CkdoPW8|q2d*9ZSO3 z+V%kn*!6dG4KIscoL#&dWF750q_>C@%icj3!Q zy%A&a+-g$N>h(cZ1g|zv;qOKNv$Nr{E*ZcxE1(7=b0Bh$=y0Z zL?qaB`NPXaf$+!cI!_5-lbP|-gN#Agt=sc&IUcjps`4e;j4Fd>DyQuI`83$cQn30U z%Ei#oCZW0gMjjztU=>S*IB>&Zvc+3esQU10|Ls8#))n!JxZ8TS_;ai)(%P=b_)XdV zzdx=ZT_@)E$J(f(Yj!*R?!lJPjfNoc`eK5mRk3-HGJb8XL(M4S=@Ju5l4gZnFP@!w zHWD`CNTS=xP^^AxFc5}8VoOGqfFBTukk2@*{P24`*t`1Hgi?Fy%=%07j_rhfZ(SHh6RqIR&zLlK+uW?7PRv+M$*hf*7EJeBo^tc(1d5^?i&6n`Q-TUPK&|Gux&rF4?$^ARAnCK%lw z?ORxnCH<16ai?ffg$k~np#9ZBZJR!IyCWV%POnot zu3OQ-zTR!>JAcdly6gmtkJSIO?ZED%M?!DFPd^8lxXuo@%)%jYJqqVSJ)}yhRXc^W zs*x~z@RY8a_v2U;N-G3Pwu5Q&^bfc6nF4Lw9hOe<1c4Us=iJo{w0H{$Vnnf(I=#UI zvP54)-Iu=L{Ef{Zto2^v6^s-gwQSf8(cd4JiBG1+>&cA#D%PZy#OS)yT{F)+6A$XC zJ}dGy2nf?78UaTjA@$4+)s9AWtAzRh?xv#*Hx$)8fu>KkL@vjY^^Qcz}o;Z%^fRhUm?;;sK)Gr z`Q6kJ6^6m2Ljpb26`+yO4FTWQQ!+N4VIs1o19LnT-%h+Si@+hV8L9Q|V}Irr;>_qQ z+5lJ5wEFc0(olX{H+_TqBa=&1>!?KV!zx(~y@Jz#-{cs)QdF|o+XSgjxNt{qOKIO8 zYt#W71I10=S1{@pv0JTVxh$c)$E#w0dD(z39JnT>aVk_3w+n*4@Q> zrN(a`JEOX+2d%*AmbcNM!>XAojrfskT({5$@-P^gDFraEZ(ykwuRn*WwO zL)Em>D0-SW#A?8a7ylbXT1U>D{m9C2&H_3Bx-fybR21+}jZqQ`rirs8i6M|>yr)zh zlqm_nWcH2>!J1`FdV@)@Ips?@GPUZ;xg_60cB?|s{en^TIji52+(K0quDv?3osXq( zYU+kWNuh0bNrH*FQGzjJ)mY+Bhm}%$>P!XW3aD#4P4Frsd-QLM8{s5Djg2|IkZTx_ zD%}~XMmv&06bTOTHZ~eZNryWB+}+tV3yt(g zps$x*A%DGFvC@4jSlcym7&nS4DU-WUB1chJH_cxT zf4|BkuQvk8PT>Yez&|9#uBf!56RT~1vgV8R09V9;)0%IS+NRx@7wZ^9UzUmL0%?N> zMoXRf(xF+Q?r#32cGDDr#{r zR>wle6qXSO4OCn8h)@z9VCFLDTgL;f%@RS<=BJ0l1kW(W=g@0rE)Lln0H!3UZ|!nf z*+ZEa^@k6e9_$Y{D^BPETY_?R%ttAWHs@#Ym~D8fT~eks01bCcJr=jZ>K+nUhJ-4w z+|U6=)JWLWDZV9Y1AkP=C7pm9=f|J5|7+bMZ?f}mqP@ZC`tc_(?BDANLjLvn1@Ym5 zRFuX0pt`i{@dHs-gKA{9fJ^92OI{l5-Bg zZaU^-72|;}n?PdHQ*kz4`!iM%Rca%u@Z(CIJ$+Mm%-hwJh9KXDL*Q>jfJ6nvl=t81 z^6Q1LpaCAEK5%^}{f#(NLbE$-BIa=9xWZeJ zO^GQE=BG;h4k6Rlv#wo*<(rLmqaq(oHbMs!tNxsKwVL`=|Ks`plC9+q&M`(#G1c*% zc;`R&*$s}x3gGMLjX&e1Xq(t#sxal_3TFbcep})9W!_3j4*fCP;JBt zuISm29u0hGfj@^jRiZ#xu!>jRnMm$t&S!o3+Z#RvvKR4X&vyX@kWnk{#)M+~ zRHoTms|d}qE7kHFMI+nNOdedI9;LiCkLtny$F2(Dh21)N{=3r2j9mN-wAu)5Nd!s+ zer!7H+u}4yEAX^T*esfz2)5VB@nx0qB8|I%-nxqF$x_l&JXW{*ltj`!T{*C}Jjd#^4Pfk4 z>U&ugvz0o)^>lF^${ZjdrEzwI9%wsh+)<+?L;zRmk~hS#;-HR zJ@VfEq%C$Od2Du8gO^`njzIn|*0W+c%=HEfSr!J@8gRr}MNM9o-5PZL`n@Ii;kT~8 z?QS=tsawz`(* z8;(%#0LT-TPeMut{yMlSDy-OrVvb43~>%kpB}yh!xCRe>X&gmF1Skv=lNvc6CtZ1CsVKB z8US@B-8r_dwAZ7aR(Lm}shj0G%iz&?#=!0(DA$gq!@=cN;_UN(iYi~M&%akc@iOhK z{rI;U_@2Actt>dysT4pu>R+7JSp3jjqmZwC(k8s2JB4n6v8O22>Bjv^OyB9XXb`a9kBMdI(3&;Ii?rZa)%B?6ZIHFDUW}SGS;bovR=1N~m zFU?B4C7_kW+(SkPeHmvZcsBK1p6sGO)dPp(^`8S=HT;rQZ$UBjTouH$j_T&o{Geg@ zZsS1cd$V7I|9#QlE&jRg-&g-w`|___eOKgMyE`A=yV}b3+-8{$X7|E8*j1Y66@w(T zlAB`Y33dF~b^r}5XDgL0h_?KwVSfLonmd)Tq0Pkvci2AsI`2$HDRQgp#*IJte#A+} zJ>3td;UiVDR4llqcb{br_k1&PzuHUJl$Uyhr$Z7#V4YF#t=GQ2Mb1`nHqoKX%*+wn zVVZ&^kue)hHAEv&c&pX~hKK@GuBWnGG<^}98ZTF%ZfkI7tKKmz%B>l(9i_(>%}>8t z#rZpxH^WP^Nfmf?E4RrIrmIjP{Mp07DUL3OoL;F>kQ@3FS#+?nX1=g%M`(tI2X+H~ z62#9wece6;nI3|#hSg#}&IIfGM3_FgCgP4YE!T7(tdG6P+T;dQLxu?Zfg=<<;Zz8g zc_^GAY@=;p)uP5&q;FE-|r%PID@sY7`q>4 zsI+_zD%oc!<{SEnF5vdN2B8n>NN(E_7IhNK8nakylq{mJ&xSl77~V1XXo2K&j&0$e z8lq))4KuC@%~P=S~hdm=neaIQp+mR_KK#!EXkOY5yi;Dwm2X%HUh4DngPkMy<8(fR2 zT6aD!SRmiB=~Ji5+JFT+3DlPPi6Teh*n_+?sJEl~03zsp@v2v;RRu?E(c~MUxXm-X zYf*9t+XUlV6!!;2wvk0A+#c5$B)!m-lirPefq&+rX>Vc&ZgpzTDm*KnH!_j@Kezso z>&7qOoa>9vS8@LY@ku}ja4oVXewDCsx^SvZNT8sT9Rd|9bpkcM(B`jC+HcxSfsCM45OizKXyIZit2OXiQw#sohi{nzsNNdwFv*3W?KM7vzol`5otN6BBw5O z1bS;%c4ECee5jckVjK(N(8b!XN}?Y)K|0R7X(e!LrLF6p(P#*J&d3k{aaBYbA3`20 z>C$MgNzrk1^;5Av(ZUfIFQKnoremUG$hW)syOJ~0>xeOca@1q0~W&9Z;S6!EGyagloO3RPzq(zKXpx=;R?q!Dg zJ$yI?<>LraIT3!i^oguVzZIYTg;Mxn7-OdT&w3@03EOL$JBsQEwx-ze;Qx~f4*#SA z8^a)1&wqwjSHvB+xu5;Xy6%tNfL3|ys|r&YSt<;hl={0~;8xRL3{f}X8ox%4K0J&V z_@qCXowuUU)LbKt)BOYf;5W)KW zTg&`K@hn1D-`9OpwjvAo99}k9U&L(!WFxn$3&!TnP)Q`wqZv3Ql;o*W@ z6d$bsf}$>-IYQ~vAmYV^zToYaXBWkF0JX}@zwpsR<4L1W3;G^a&y3iwDQXo3I?3J*SrM{t36nsB=|T3GBWPwz^g z(K2es=G=#=MwP`eGi0Lp*2?^SfbB~nFp#do7BCUHgj^OSrv=$?7g#@+ME9~ieKzog zOJ`9OxJD+bet9Npa&O3=sj`gd=nOr;|~>Q)ALrd3Id?RmRfI`9XhGz^mKV@Y7yr*32eTvOhvSMm)- z_ASg($WSHChHThD={R3#``?Mn74gr(KfRZD*GWM8pI$5%GvE@x*J5Z}jnbZ{q)Mp zVtnux(P&LKf_0$3f>+GT0T4uO)pw0eylc5{S4Aijh)yJ8r4vn9rpFuMdE%~YlmqG? z@;tJJgJP{r%R#>Bb*;>Oz~Pwvbm5`V6e`_{)kIEJ($|kaD&Z4ow`vR7@YrmRRty*^ zoz0kqNukkQi1*4J-V~$Qe5~)bdERS(wOUFvm`4>)8XZQl81tm+FLQBp|f*?Nr4M`_bmrB?$q2td_imwWhU#70_gIIjC{OsPvh$>Tv25M&|6N#j_}|Qmu16 zwjz*;`^D(|Z-3Xof0G5~mdjr4zQw9sKP>+m`)5+)6NJ?zd?RqIc;0qsXPKKi0w;wR z8GXStXg2oJICg19L^+%2C2G%Qd$?>cWwd%WXiR)_U&D>9D80FJ%gi~QjQIn*f{a+I zrMs}VM>)sBuK~-SV7WuOkB*Pzx2OVm`ksM&(S3Mx0{Atuk249g?t6FU$U{2g8V%PU zFumg;`FLN~Z~Jam%9z5Gb^1!q0LQ(5EsIRmFR6EAYRn$ujn6>qrX3ZVMn6Ahn)kZ6 zKmE%y@Q1DmM5e%;`ZG@nw_J|R1D0HB)6ni8P7+aSFR!5zxr?ja-_yCdV1|B8yBUo3f+W}-4YeWNwoy?1s zXlb0U0n{BAPs`WIBrtZ(7ne0Bj7pqW?Ro}B$0}(C!vJ#5K5~WDR`nIlU6ciIZoI%E zrvu{t)ZiY6US5li!2#L}6FQkl%ij|(NWZZwFMV45{%?1I8Q<8h|Ks2MAg|`rzm41! z!^Z6On5CN`-6j8ucOB=+Y)2VLIZ3a_ixNxj8SmN&lh=iH@_fL;X}KTWR-=*Wh5Dd+ zlj6~?#Oh1#msb39ieAD&5s$8C<+w3zow*-alT};R>02tyG_6VPqp#+If?|%W5ki%yjnzJF?bxY4>7SoWax6CR zzB=^JK$ZoJW0oiDvv$2`l-j=v7bA}RoIIQl1caRlU}V;-LFxACDsPP%XVL{yH9g)0 zF09p*Heo9pesw9zXsc6_ywCRDHUl3171hEvoso>gjC%VVO>^Y&t2JKhRC)GfRiv1; z32B^ZYgZ1VpcM_u#Mv9Uh|&*@_x2MSAt>E7yEv}ajx~zL*PxIxmqnPCO1kG{6|z6Z z2T*Ss__RXKaKfG6^KFt{O|LiYfA#-w+BoThj&+TL zQ*Y8L8N-VykiwM(KPbEK6S%8+l1Y7H&H<=aO1@7jG7DVgau${3W=jg^zi8gLuk>cs zQ-^!e5|5OIQ!{mRNCdDB%$)l>zi;kp+;$-7b7z$cEEq32;ml{vs@Q1@xJK+JJsLLY z(;b~2_1%bDWtk5)B7(v+`8QNN8W~#ErSXNcIq>_YNLqGz)hnmw_Z&> zb51$8h%=)@CZ&7g5o+w2&38p@3!)lXK?%qyyhx0gqu8ZhQ@z*lE}SIDMh_hH8Io7NR{Hcv_v%)T`__!C>jYA1j|hF4CF@remE;ES{|`v zU&v0wu-tVNs^t0^S{B4VuPxBe%?eCdZp;0NDo%8Ad|VBN4Ma4*dXL)<{TMAQ7;|9$ zy|w0rIoJkUt_vK*VELPAw{Qslqb2n7J!a798SGxv(#*ns&pquW_GljBZiqu&refI} zjX>B%oghqQQ+r04Kf+2>FB4*Al%0<#oKm+5q2a6+rRaH+XtmVa8=4Lj zCzp6AvS)}j_n|ST`wjFZOq3nV`^_8ve(%@~XR>91NDM%Uz+hcz*g)454|<@sB^qTC z2NjUZI?=RDVgPyXfo!h;dO%jWkhZ&3;7Ta_f&onC9PRwZkSSXFVq@%;A&R|FE#p&;=Bgc|r8^xWNeNSpRgSJT*iiHNMBD;`1YiQN64JCpWu!Y- zy&Lt&dTW^`9GY(QK97x0E(V9MAbdxB2#y`0G}u|m?dU@=DDTxSk`$ps=OsSSQXgY4 zEtf@wGz|c#nD*JPF3oX?o`qo%QC0?vig*3n^@NOwK2%sD=>#0lz# z{QT8;P(FSZR!}v4FLU{R*S_%E#7C$^0=xJ|XBoNCexb@$_Tqv>$ViiHznz*v;ceZ} zO?m`14@lG?vm9b>4WG!+vb3r2gUf&olwS8W?azDdAkM8zFyV3Z573)kz}Ui&4K|&n zMxVsIp%kg%vi=Xw!3IRcO~IU2G8Hb$Q|qE7kgD!1rgy>MHXgIJvnfU`0C^p?{XFvs)@CU(zQn~=Iath%sGeR=O; z(;ENNv;=7SY$+mPPiI}_&LUl$(ULyTvN<_(yNjNQ-21sc-&Y@ZseJRk&)yqt|GR6$ zgf!Y^;}?z5yE%x(a#M7ElS>weYF#yP0@7Jae?Iml{~}JYsWP8M*ZCRd!37fwb)J`RHP+Y2p0J zyK)%a)+L8gI~tHFvQ7%@SVRvlWG{r)_Z@o<5J0E(dyn5jl^#RkMBSy&w!75ZYDCi^ zrXKWQ1FSWuh=3^f`d_DV?JG@~y=xysw8+&NLs?^fYtq{~(;~%I zfvvJU46jIh1Zg z=k;yUo<;FKv-fu-jm^vsWNVxUi5Tfc`OrMiM0T`UpEOOa{C@)ZpNkMm6EO6*U5tO{ z+tLwzb$sOdrVCpEf+h&$Ut7|5c;l7D8-jnr&Cp3H6~t|zMU~OvZ&ta}1HrYwNqKk6 zh#XIeXh|&K$40k^9G~_Mn~j!!?Js$^s><;lFQavY%O1b6T3ZK&Z&-kk~zeDMXK5|5Zu}ZO$_u2NCIc3Egrm6r*DjE{HdwZzHSdx>Ij^B*rwMy5h zC;V%xo5lx-rTFA+GT#h+HF|bb8yf?iJQXb!YCut4CPqrLq zMmOHP$$8&|C(8!Pbx)T%&i}mo9%{CnsAt zm$?@|qKH^Cp-&yHi?*61m%f_`_WAfb^?w<4!dvjZ3f+5knz)B`l%&)i3I{0(=Q^uS zkprewg8SJBJu&%8n1%1Tf44O@njKI*$f}zTQGmH0p(=zW5iEc@bSFr;UfSy-5%&w= z%m`LFhTNGCK5P$-Z~71_ry;O>raOq8H`kmWbXPz`=wS8dj=l~H=~^tbiKl?xqGHMb zQ5T`T!|_s-smPgCZUn=VH@JjDX5+udAk*plmgR3BS0^UwstTv3Ap&w7L}Mt`ts}`y zuV@M4wwKI={hJ@EA;C`i)M=K263?V#irV{dr=HMvfjTS9{u%fPUusMTATD3QOa}~T z%?-eqQyyz-c+iRmPEg=DnY76D`9!~5ENzNTSzK_V@7`K%>lOoZ1!u|98Tx(tTP=5m zNlZ$oA>uvSX=wG{ z+7xZwD25)XdY8&9RD8?Ti;ZMjfc=3dMgNQv9Xnx>D3AK+ML2Ljb+Y~(CyG2J9x$#Z z*x(PI>EAOnkRSD4e^mh&GggG}^GnhWnrN2gTgIYdkhl~V1D7_kn$6GNRvT}UvjY#b$nqfzE~ z0}dHL$Sk(<%x%6Bbr*f1#V)5u8#-~rL+oc*e#~QoZ3JnFOHYVbiN>92& zK$6Z6LSn|h)$c{3cp~Ktf41*E7dttFs6XU?M+W$ROnr4+lWp7oHW-a4-60{}A=09w zmF|*~E@_E@2ug!ADkw0Tkps!mjG=T(h=jz5Azi=S_xrr>@A-S54;SaPbH{le$G5~g zQYZuBW*gT+a$Jl0@qC4F7bC%xY7mQ=MD@nv;l+%O;x5A~UTaz12=huYX_!0|+;gAY z9aex~3-TBJ%W%B+A}(aD!|P=(8I4Zi-?V4n_sJ4W3C-?2@GH_THq{w6GJf}Z4of9w zk)Re;`xahtk5*>9B8Fd`pgE>6GQs9hV&#EBPEVRligJwkBwE6Otl*Rjgm=uy-LbtO zP%F!yJiF`6_;zv9mFRfz{YVCcI|Dxx1o)@6I}kQZDupHA_cEj}C}_8v6!nOQ(&EGV z+-h-*)A>?!j1e@d=Qts_HCA=yT}#gAF3H;JGfT1mLR+|H4)AUOJ6Vo zj8N$If$Dfi->|>1Y?U(2p-~Z54w0yMPc_KwztCo82jItJ_zkyHW4l)BQ;oP*u|6;M z&z1%?q!F)-c@dFS6*1zKG5I#@la#kmn*ar6G+mh9uM`do68m;JN55ccP0y+2?KNpD zLOI1vbmH^n_g%plHZ1VZT57eBR;>}8!7@T5*{C!p?Xn~_HqFlknRrFW)@)f#3VvW%- z)|JvvD(D!I`bcMIv*1_2pEEDF$jrxkWLiLdYf465PsPQAg7D;$i;hSCX!4Z`={TLv z4_h)!u;p!R(|gnwe!^0H;0zENfF#&jf6!?JUIdZbym~28k&N!0TFmoxvd9Cb) zlsGpRY3cL3x;3p5cEw(t%FVL?TJL^4ib%}Rbo2RvuzfNTk_Z1ZH28NhgEw$Tzo+^S>_FbpJC335smG{?Ek8 z53hayLfE3>aoPpyz+mmq#kS>-{_19Wa04K#P&_^=S-aCtLvn4p1TK#KDH@xSUPnl*-wx;b-xbMGtgy*v*#Se6fo6818o!B-9aCBVIk_ za(l8Bh2K`G_KvX^y@NB1dc~bCpc^1s+_nE_J))Bs7$Owc$Md@TpHo6)Q59zGdyLL< zQrE*a&>uaHE31&y)jD$?b31=5B5v$rM`gXiTYw`}lcs*DQ?F-y?Kr(~t|M?nBx~+C zW0l^OHL>TGs*MLtmH+W-_p+|91C2k<-X+|V6`bF8ov8uaC{6D9J`hb097ZG4fGvvp z-Sy)lo}@sg#;m&7qt%RWaas~ZrQP{2l0Q5Yk8RO}RloU3+lFcVJwdl7=3$euSHa^5 z_|B@mgwJ2~aL&m!{oC}{`JK@Po>rcp!~a*hM_T{6g(&SWdCvT&dt4<5u~=VkJe`Mn zHC*QADJqv{Z@V(hCrS~DYb9tV?tof{CM(AH-;e<~bwyx%kRfW_JeZzsy2I+q9ZKa=$oGP%7B5dNq5BjR~V(3DvB; zb>TYWhG%ATevE#j!O%<+^6?sl%k2@PfeRnrJRF`iVqNX2#uk(6*BV%yVupz({c64% zch((yn#)4CUEHXIWW2>^3 z>H9I1Htr$>atxVb8&2vaDaC-F??mCR`8I%RAW|({j)+XYIn<(^j4%ENn0C%Ot{X*< zJzEgA0~=;7Q6{}9KL2C8Z4%(F=F3>Chk69yNFnsv8O*%_FtFVlnJP2v2C_Hd07_Eb zSF`>aJTUOWwm#yxX7|s3F*ptgZVpJV|95NS2CU6M8`t2jW1Umydjmy>9bklsJmUUu$|~-W7vv<)j&Q9Vj#^2$Fe9fTDEH(u2?x0g(=51NcL8-UpnA zQxEq3csF`gXZUB6Pl5`4k5PAUgFWBiv^y)-+>#kh?ay8iX>(fFE}(SXWQOj#DqnL* z|7_7i`Q4+uJJ1V%%KQpoemW!oUCw{OVlW>rYRo7r8o7YT-$5%-l|{X z5akCLxMrKbAC*P(jEri(K~MRn z8V7fIdLfr}Yz_~Tmy2u;xlX4$_sDoOA` zkHX#qoy~V>UIN(*38Y8yC&*&&2{Ndv)7!#^AX@y=RG%9Is2muB&D9tkS55M@#>;me zMfo^96mm;k!3ShA-gc$iRTQ>0ynrf^$PUiiPjHqqAIlw!t{34m=gb65Bt@G8+ycea z`mHpLw-}IsiiEm~{w-I#={h}P_N=DHY7_T9Qe{>0Sx>>Bi6lKC{`$K{3a!t0uedAwQkcuGqP zhnOb&TwhRvsre`xkG%<&jI)IZS?d+>)Le*imCJnJwJ5zxu}dwezQROeT}D~;Y=dBe?Ys%ML1ba0 zyOY5&&dIo~mT-c)!;0Oa#eGT;?|B}5q*lkhwJ(-k91`UECRI>#^C-%X+g9Qt%8Fjf zi5QjY>;GR_BF1sMoT^)CtHtv_P3FOapUV5KZEYt>uTwf^9-l-Blsa|_+Mc-ym@RU8 zCPgixkqGrR3-{dS=nTx>y5O9(n(gXHw4Ax)%fUv*3=86wM`dA8a)x_7lyJ6zzP5G&1~oZk)6=DiS_ zKS0e2Fmvvmn$^X}`#zujxp`&9*nN*09ezq*(|zXH-&R0Qe!>=M|F;V+8!2Al)ck0ZqwKrwt; zSylA*GamND4T#kUVWd>3PA;X16sS{OiFphU}Q^|sU9U+ODOr@Y5RR&I}-7R?Gx zrOdGUZg6?pq}tG{NVgAGJ!4P!d)lD^Qg%XoQwDiMqp*7d?*d)@sXyQp%jv=pz$uMi zjJc%X8C2G&gkimYHWPw=7XTJFDYkOEV)@i4sEtaJqf*grXH=hM|aiqqCg3~Acw?m(6%e>OJZt^AeV$V z%0d^?1D+naN0LK^tdVI%d;b3ie*GgA!K<`cXWUQuPdooWW<_rA$m;aL{&E`{5pEaM zA$ICCOqAfufa-Y~UzvL0AL#~uzM42`vC)_zdp4CfEyny!PaHtTu_4*018LD2d=%Y z$i}D#hmdU{%={4Z)wT`}*Jt9!L$d2mOa?f{XM8ccUfpGCedtpwD$G!08~7gB@ODfU7 z?&(^wy>%}jCN$6j=`BQY0xRF>QQQV|R?{?qi*m$Fychj)?C_qs$)u!CaCgPA=qX`Y zs}rz0{6(0L)>1^K2Zpac_cHw^3YQ`&#qLlVEE4J5zW?g5*C@9kE+Gf#FO^I~Yynq$ z0hwHO-bP->F4w4fN%1ci;*n+O_W9ZrJocDizBhg?x#|EduPD6c86yI?z6y=L&oJ=r zUt+|}1|5^Pxt6U3pu1HM6*rT&D*=((c^vAHNu{gmsA8U%X?%x$z30esD_L4DG&dmC z-H%3uO8CKVkdK`T$bw&+5pM#2o;OUGJ`>dSA&43?lQKSitdD4A7amP?hHG<1TJX9p z7bX(=?Q&Cu;?{aX{;-O>3J>?VcssSpW1dzy*YV>(N6_lv^xI9&G+nHh=l99>1 zqETt;r$^J~uAm%+#DX<*an4&s9Uu3mZD;OT7y=k(HN`7vZS&4#BX)%gTH;SKaN?#R zZc_!*+5)7yU{dK7X%{C$S|j73v4j?c0-@v7>L899klrVy+qo+d{ay*yNTd&9RT*)V z*40&jZHSUP*FFYSasxgJ*AFG~3uzTF(+jqgl2{}5ve|1wYtuH~RWO!`l+08)Dvs|% z_>>;y*}(*RL1ID`sutHn)iW8v zHFI1c*_K*SEG}g_^$Di!0|ExEMGu{yrCE*k9mu@$-&`OD}37EEN zM+xxCDzMKzBll_$TEl)D2e4PXHvn-lZB5aXbx7+3H1TfV9qo^{4-lcul<5pxxZ@4~ zzsai7F~MF$;I!vlqS}ivtJg4@_3Wo-RUV_Il;KroN0d%IV~x!j!SUj1uv>|W;b$d z0h0nq3JWA1ji8(r7nUK3G!<&YarXr6LE3sI(47>IC?>OCfzfUjuRf<8sV7Pk%8|x2 zvXj|#*s48WF7ZtmM;m~hq{DKS{U{DxO7g$j;mKvm^H(XA*mJ6}yfN;tY7zs@ zW6u2>Q!y{Z?viVD8LNX)*->U9$IsZz;N#NYm^J=7Db};u&_k!13Q@`ApWUQvo9U@+ z<61zyvF=nPp`ORQOdTGw#MnprSMP#^rTv!^#8ce{QpvoncIBn2oKf5sb$%fHi>DAyc>V12b@(v+^}pikz3b)Hf2j+nA$aZY_eiTLemA`m zsKcw|N$58LRtQs0Yvkw0s~ki&ay-N;=7Pj5XpDv^@>!dS7vGJAzHes*wWOvw9lY})rutw$q%c4Jf4%lZvU!S}5C5ck%cIT`t%lDa>D0n4Jc zj{W8`IJ+Dm=K`SWVthO=;14OCL$ZjPZiRSztyaro|Mt@!&;kNS3n9`KhCh`H1)ZA< zP3{hqM$?o=lxZ7_v#+Deq8ywrY!(>4UKf_b)*P@#_$-{?(|FQ9+^gYQqOZj`ZDd%g z6e@Fw{!$iC*1Wv;@Qwx=3xHPW`O4_zqwNO+DOFC+vT#$j3K^xkfSkUaw}C?EIrtPQ zE6&S3iYy|ItLZImwV^p4&y1Gm|!m(&&K6QRt~4oA{LxGApz_EaS~$-c2`C^DTHXjyqwtE%$9C;Ya@EX`TYoGb2uQ8Bob&q#|*(ob4~vdE5*(j$CNQn zRHhLpB_<&p&VJ~ECX5}30Va^XlyB8Y7YKgd?j-Uo@u~L#_aIx8da!xZXk)XfhIwWX zLqbD>{1m!;Nr>`EclyE4H6eZ#q?J2dj^`EW{NMK+RxB{RgiV!zH7?{=ba~~xv&^IM z>VEtUcH0~P)bOClrh&xJ)iORi8X{V4Z==r8x1AUk*mlim214RU>^IKwiTatT_m&c# z-?RGe1;X#Q$S;l2?O%24Ux@y;yOwcz9Cr zdMq8_?Py369Mt5Wn>xlLCnXwge~&qo7#qv3|9M*IL|uujV`-S;$oce5D2n_FX|rI1 zJ!{$+3B>!S2Rd5BeEOH*+8NK(+yNaj4b#B126-6lsn?fKJtyjHvg;MNZn=BFBTWV9Spte zotd->OZVL`KdpMrf6i28*|hz#Rb%w%s?2cAf*aKn$EIfTX?2t(80i$w!*=h_rMfG{ zJw=>8|3>^&<#8a5!!HxRu$ihZCGO07)TyZXsxE;iHYL zAFCT47zClAKyj7==`2}d>nKa01LnT)UGi*h(V76N>en0=?ITEaKhduupS#4`GUfBw-wQ>>0Erj_TR*3PWU z_@CWuJlP*s7=1_Bq8ho7+?^acHlV?2Ct+iN{f&Q7!J;j>KDpJiaIl{*1912UMJj4Ta@T9z$G(4$b~;5Ox4W#L?ATt4qD6*T`0vw9S~F z>LsL}5Vkkxq?*+IUElDO-KSu9in`H4U7C{1nV)LE%l&9Psx0a^GL59pZv~8#*Ciu8 zGEllx+1mag(0N1<*N+*YhlO?w0@?+o^vVwB#Xv)X>Z+5+07ss122&XAJhb2K4ya?} zy3V8%1(u%gQBT{lE6NHu#=5TJ#Yt6?rau7^H{N79v=$ zoHA2zvK7ZdrqFtsgRuw4_in!{4u;h%EOt_HTJknmq z9%gq&^WY)0YUnRh6!fID^>w5cE%=*7_6KaraZ{INtayjcd7-KgHfSQ@E&Iig&!%+tr@i* zB!jcT0AenWEHXmB)R9>9MgHwMTUzp>QhlGN!(^O&Kx<>e*st=wAbA%(0PO^MbZyv{ ztGOckxFo+D_$l~~3J2lsIYKB6TwRFpPRDZIwMi$o&Z6)Q`)HXD1jG1(bF#!UERrSC zN9ke`JSM=3sSBuqSsu@20aNk``@o*6SS&l-ASd}udV9E)&a}`#6un%O2w)3NF7^7h z{;ip$JT_E*b6w`9DfC7}DK9z3TK5{L*qfDwe0A&`nKi@)U7NT9e*UUv&{CvT=e>!;i(B@3nhW9daKT_>a54c{w3aY35P(jltH*EZGs>w#Np6?E0 zeI@e4F0Ym{xzNri+V7dN9$Tt;x&mdyMU(M7Ww+~!qdQt;XpxF7Y937B+K|>C705@R z8ig0QN63 zuX-kT2ga5bhp#chFS4|WBi#(iRvxNWlbd{~ldg><{;R+62N^p?`&zsPBVH`#ss8&hfhK3RRz58zQ}PGbq?9?36lj{uFmH_VJ}WE;P_a-JHa<#2b<}>U*^=@;ZeKX! zW6SjDR2#OhyV_pAvhDP=u^tONh05P~xnUN<98n({NP#`NGi-1fL3bW};`MX=tBKPkPA z`9-?JcJqtvW^eyy&+GQYiz8UlMm4OTZABy4l`Zq-FT879(BRJP=C`1=f5P2v#1y}O z9vk>2;duRh`HGw-h9Z6H!JVVUs-0iwl4e(tgR!U&SSarA+!DN@{E0U-8wH#x?OhfO z3jgf240w2}7)8;nO^=*}NB(A=4B{XO3Xn2uOOy&wnp^xDrz}{|@)QHA)w8t_;35^# zI{2tWQj%f908f)9g-D5_xSGEIff!~+fl)Yw^ILN$Ae7d^j^kSU{P#Ph^hwk6?W$uy zgSj;#1`ZKiP7-lUQG?a(1YnJxm(tZqv%iNff01Wjz7AgcHxl!o96ZP+p$MZfDk?16 zn>`V5x!0;^2GZ_{)#`9*wM=d*>?xk5NKGXT;{c+vgoJ~;zC^Z#P-qF^6c%{y3zCit zLb|j)C0qIJw`a+>gMs?i9A^&aXo4Kx{5h}U>8r(tD!kY+%tx54xsVp@=UC*aXu?^W zo%3QA$;kZ_xG8_dM+d+;WrHdKrIxXd0S{uMm>lQIm=wl6;TIFYM+hAr+B~~er0cY} z-M_u=yv2&#V%csF*={hkq17M-w(FR@07HBX({y8_1FBHRh1#RF==pDFso08C`G^B- z4))0n8CtFI#81ul9Q_8~%5~S4x`3LrTpRvN>ygf@28@0I$o%sm?mOMT-O9gtPjQMX zYWcUFo2!oR)%Kp-I_x?A2sDvZihrIs^y*M(1DCYAeC-gNxl8#MIn>0)~ zlSuN!X5;f-CQt3=aBw+2ZASA_i6#*AL~kL~hek4R$trIm@y?O~0$`5#jpwa7Sr<8>{tI znggQnfVTw>D%f-EFa0oUD@F0BtP$aePu&i5_RrfmgLBs5XXRrH)@Jx7tnJInWrDX$ z+1W1iwyiHu+}H_#Xe&TSS*sTakBV*_GM|-Y_S+TNnwlLOwJAd1CVId+sCFK4GjjV2 z^N!6nIEV_#d)_P4HKwO9qB1_TKUw;lHS|jOMI^^0cYKByClqb9)H(ncZ4&|n-R}qq z6BZ9Xap<4EkuK1IudD;ptV%-l+vpB+E>PG=ti7P(lXUqq^zvjr@wfi)H)JELbPtr; zSK~9;CJytc(+aW9iu*D_yGmQ?^6YH1aFWoZ&_3)2a)zG_ojkI6{Xx9bvDPL(yiRGh z5Yu=`jLoS#V>Ww+CvYPy`rx$wcSGp=nDg=DShDcea|r8E^3p4t*UQThc=2$do1zs` z^uFsA@oLW*@p0c@H8}ga4EVzKVJF2P9c{(H6&7CO$$pS1zolrO|0%*jHy8)AI8|Yp zWUof#2q?OLA6SOQ{yu&Sx29J#RtHrniJUX}91;mCiLhlZ7YT|^iP*Z97`*|y9EMB|C5Dn8FV37%6k*xh^f)LvjkB<|`3T5b2XxECK^u;ya0Fhj{9HNtbWWTVh$ zMd7mp=gr{pa@Jwn4ORCJUN|b!ziM14^L(!P$Yvx!ly5P<0{Tm}=&1(!`6GZlAH|P+ zz4_~vHTmQAk`hNOJAr-EiQHHz|SzB1?gID3VT4Z+mSM83wn`r zrW`=;L)<9N3h?bT=0xjRG9ml+HF4LGP}k7hcV*7QVzTH{qGpJ3r&#bK)|^f33;4|O zwIb(8_p9?K@^x(Yt5#lzRx5>UY`(nYRoa%g$idPqj4)iF(PuVCfy0IU`&;`TJ+==9NZlJnm#kn5Gpl>z%(%3}B(I32W)bjZeEo_%D8 zjqCH|m`&ZH5^Hz;!Q+EA`7#PL9=52h_haIgQh2vg7=R5EM3O(5`utR6Irdinpyc_P z41;Vqv}4ezb) zi9^<_tl}e$Br656qJXB?KiI%Lin@1@OhG95tD_`y@-uptWRzjpl{s_q7mQibEX(@i zp4H_Shb4APJ@Q}!^;f>cj9|m@7KB1gl$}f1*F^#S!)b>WW(=)pV^lgY=*AtJV0eR9 zj5Pr)F-xVHSb|nWr_(ml`Kzzrq2k%e9X#lHYJbUpviWdzdYChQv4MYSl3dNd@^E%T zVsSk{G&UwWnI0l{{RB~KUbCgTu@y+8 zsNdiL!$OBuZXeXqDQU!GhC|r#J{=_Vs{72GjVWv_Gxk_S_j78#jj3oQ>r0=t8IU~4 z%g|ca`k}SK=dtHp>a4vcw{8k!o${F9+@aMGWmn@M+^5swJKLQM7ykLBP4`7uvbPBZ z=~qS;L-EhME+yJt%l=CKd2+j%JgRj6nnBXC3EkuUTd+EBofPl(&GQ=x`5&7v4@6`o z+bhCz)Q-A>`GAVloke&Do>%{byJAI{j_UT-uNWzQEPDtlG6Z|GrocrMH{geUif`e3 zURF0030JLUS9kl|hF0MLHz_B%_l+5j)&cOsmm5!a&5~X|8Eq7OY}{J-xt*C|q1+lG zAm^Eus?Aij!tSOp%%9AxHfj8l7Wuj+Au8ibq$R;sn&F1^*1~<5&^X~ZfD5PdK9OQ3 zhni&e?HvDmutfO9DAjjVx=4rC=1i~S;OpK2BJ`nPgPHZSMegs1lXIzGegUTzx^bkl zegOM#iS0$&1nu@vQ)MGnEB75*zGRm5#42kI`>NOUqVU3tDoB@l<}TZ;x$^KA!7jYc z^cO1ARCC?*MogRqb)j)J%A~L7q}RRlZ=8Nn(L0rRj3u!UIC7aB^I9jos}OmDJ{4m?jlgsN2LYIuRmBGF;Y0uB7EOha~#h*1r_7ODUy@;Hfe|E#w;I;bg(UCkU#ZydagRNrxSvRt^DcRD-3_>yq zuZ_+Jwq5T}-jJ!Cr%IeL`qlsR&!-#sqc$GyoA`W94n7i}e>EKOC#XFMz?#yRf1ef* za~wM!{;c--ul(Ch2?f^e^UCDdWBbFIpz*!sK==Ij?Uk3=D~eR>Zs>EA%D_^-GL9$; z4a$$IC~zdvRjshDq31EnvXA0;M+Y_Ic{smq7}>mKPT5?@DSgbK5S;6)c^CQm$7ek> zB1CJJHdMa3Dz{H`*BOHpXEoBdw~JVM2x+*aww*qj_=BO+h}27{i!V!HS70@AtlhJI zSKWKK^SItiNGHl!{-`&`aS^4v6od`t4`G=%0gK1m>lsK5r4%YH{V7W;qkf@qBuDgLaoKzYlA%s6hOPmfW_u2cR7D6eE`0vVr%gPhIPUNbl+Xq9b7_b#2XD$}%VT<}&&80TJ`m1VtF)No;Z22PS-g$9GryIeU0U2Y) zd-C(R3__papuJc^iio}kA;C^Ify+&8_NV(hVRY?(!)4RxCMAxXt3*zzm4a+pz1a|f z`xNt?DG4${B^cG+byRC&)Ec;kM72Z6hcG}7IN?Ok%LKffd2_5i*| zg?Z&K8yWTpgN`O|(4lSq{A-rlUfY7SR1kkUfXqR#7efg+sMh9*Ec`CRF*D|58Sl`F ze@CZn#hI$73B3t$B<0TDMoD0e>Ew!8nNWoq@0CMMUCFgO&oMtFE1|AYguFUA$NUf5 zh{kccI~k9{@#(2rw5;U@bq7+JLClCi~0Je z)-v1u)tuR%JXf?1Dbm)dmk#M0`HDho+hC%G;r@LUC@*+*CsOmuADFc=(y!Qk#Q3A) ztmXLtD$Fcc+gfv(tfS86;!F6!f#M}aTm*0qe$ghMwZ9~C!KiRUcfWHLdt|q4WUKz! z5d}YU{*)tQ+}V!(_Rm4V5Z2x+&nFcB($AlwlRhY-V?ZgQR_(V>2rt0Pb|x)f-nZ_# z4DCJ|Ii~%3%8NCXV%J1-kI2syX&KuW0q+t%N zS{dDKU{?2zEuj%0VDrd11PT??WZ25Qb&r?gH+C@;N8y~hOS&y&JgAbi7j_pI8?7bE zQt6MS$Nd}nHNVm|(0QEcj^}n(zcFhy^~v!xk@_HQ7HM9&%~0FR;4&qdrsrMtjAnmw z7M2J|s&gkjp}04Zp27kdFL!$78wOJE+2hZdW-dX;!$H|Bd@Bce6Jl}SK|$Xd=*MAE z=80NS98JDy;Vz9=JbAk2#IFf77-|AEFflzf0p=A2(Ip38hC8Z*94B|}`aE(?+S^cD z9u@p$JKw+VJo>&wT+duEop^_{>es*$@OHwhtEuzj&rM2iItto51{BU#*mNik zBRa0Tafy;y$2;w{Ayj{1_a=GHwpT9Z&tGntggJ&i+P<;6nX>6k{xPzU(fPxSjkq~B z|6Hj3CDkk2&){8@N1uT4D_$-2Z?d+=w952LLJprl3V5Yu+S_D}UuFJEC7lWPSRCB;OCC$|RAbi^u!}78P*A0{ zU*OFm6E`*;)d1*pJ$v26`@!UW^T{mlA%W($CiIXy#&Uf_P*@_(UMr-CQ{IeSrQeZ@ z2K|8a?@g=*adk!!w~GEK1p4?c8okS_z-U7!k*t#;3xfB1XIniv=Wmdz7VlfP#c%DM zZX>QHZcbx46FNMnpiHU7pM=(#OEny%<>5Umfbfv7bv2bSEAigVC znI+v6F9ce$ws2WrmJ2s}eIHjAQ`67QlaIz+!ce7QBwvBp^*)PuS($CzFXr|2bF3L&i|4_PRs7<#J-T-f<=H*2& zE6*a|%tinB==hwTRyTvVwEBB2$1-EhlvXiKVA@}CL~1^i5|Bq6z+GTuFyDm~;S@$p zEB984vhyr$=`E}t%(J#CGg@rWFrTmQLQE0_#o2>BUU0!*qbw9Gh9Cfw^G1Sc;o0_+ zvYvP3MUUr)-f@`W*oRftU~#rB_*JSc)3+*X{e1VIn=P}G`PVGK&TWakqnnsBnP4+Go0|OD$kndRz7oe@>C3@cC9Ttefu;V$ zh#xrA9CGz>^Xl?hAKNbxA7a)zijRr|mmecv%;}q1GLlUhUybR&w2ix?9M+NSrY5sh zWfdA^rj>3P^S@=(3ReSw%^&q$8VAzZf@IpS4X$zfF2W;mTz@x8hle9qW|EPT z+0-_daSS~gk5TW&Pl~QEd2PYV8yRp2bNp+F;jPKd)GF@Jc%PhzweH%WeER!2Z##E6W2Cz zk53ByDdx(TadP^Ey3B0q`<;KdI)$^lfyA0A+NuSasw=?Cxa5X;$qjmSqZ%pw0$;0l zEI?Bh7dFIQeA`UoNu0xTc@zH<-z*D5-viHWb;UXJ`@m0|oLO>+c%jwo8e*I3lP5NN zYZ0r+4%dk8APbz1oxyX3a2ct@dpROMHggG{j@Yo?v-;OSV81!C0|UNEvbvcc{{4E^ z2U}>wtbxAGSU5Tx8*wfXNzPpXAT~^87%HJd4J{Uxw(Z+mqCcZ|MKoYol-*jHC!_M{ zT#{aHTO4^7wVUNfI^V-&H$z0Dh3+-+(AKn3ESuB+dI2c(WQe>}vmKdEPss*x-$NIT zc@_x!K)?;Sv?k97E>Ckd80&@>oqdFRJZNzAv=11=89gdyX~|}6{c^)Im}yty!qF7Ka9Ac@IXPDq+{o znGYPX{_orvGT6`4JHzC20c^yDedRa}L3bX1N(o6W?eTeO7{zN@faK7mArKp<1=a2$ z+MOR3<*d~+H0I)*FZA4zB!HgYJG_C*Mfymrr`4@^bUn(FN53f~zx3C%(huW=(klwuyGg0& z{1O1{3(L8x;k)J{_^wt<4s@6a9H_m%FKGZ$D$Fbmk!8Ye0aBi|{zBL}pf<;E@-o&d zoQK=zx2WD>=UU;dfBn?A8I1SqVlrbrGTYVrzdM$wkwmxUsZp22?$zCCuSuzW z=iIrBuAt-*qYpDW{CD-{ZRXTW$ND@urUi?B?b8nZ`fyx^L0nULmWqLS5QA#vOd`n^ zmuQ-3B+gS#eA>v>jMeg4t7Rl@hDH21mQ!!$An%*kidReI@9T3Jj4#eD;(!xJ9|!HI zd2UVrE423t!5ffKpT8lYs4!bkLJGp4dpF=KQa{)E^~X_iXkSvL4oX{&GGZ_{5$~G6 z$869(1?JNjb(RvPL_Th#S^p5;%2sH*Rz^Bc1n8{F5#yPC#Q%UB>c3_iX=#rm+`l5O zD;=};;GZprHKZS;P1?JtpzNNI){s!U>%jW>U4m9AtCbU%@C0Ba*`R=l^-q}q<#X@w zuG7wuKlNvp;tH2(3WWumiAugJs25;Lz)F)s4Xs8^h=v6{U?-OozLJZ?+2hS~ulJCY zibc)iEBsErInc;kL4y;F!sm&ufAGd<;$&MC z>JUjeY4ZJxKQ~n+OPAP`1r4(}r9&kF1Y}ANwo3<{8H>XN&y*R@=Q*RLBRS1c=>X%p z#Hf-+0_~JxZuK%4uZlsJF}URp95u~(;Q1fdBpl7>;Ff?kfA=)@k}iz!(&TLa9DD4s z5#JHexlkLHg)Jv8ZI80!k9VsLQChjq|E$Aq24b&#+cNy>2BQ_gsX}YF__%|AOY%F~ zL2J&6s*QPF0v|)Im62l3RhlS*Reb$(Fy0vasb#m+8sF_+ ztiZ&YQu%e0MC@$nPKf>FKHlD7$&NY*Y)GqZy-bF{in^ut7W0e>0}erfC?;hpg60M` zT9j(a_2R{ew63L5OuzQX&q+iy-owRQK%Mpk^V`LlA@$Eg^TsAi<_?}fDgatZ|Fi6N zau3%+8J7sJT|20T-I_olL0F(_Sj__h-UId1AcQ!yKux2o4RIBXIq~Nn)U=zb)uix!Xd*dlhSuNfY zOkaP#+VWgE=Q!U~;*Mz9JpX=YZF#+OSoJbXn1Z|Dg1DnCh|MfVQ(&SER;<_h$i_AJwO~5&u%oLH~KnSH*`+EUi>Em}kt$KoUVBkMBt?t9UpL)fT%3E^{lG zwsBhUYwhnb5sOOeTadn>gi@w0=KxO|FL{!j-QaaYYYh(lcwQ0D4tG z%!r9>)6Fty8mq_2#(1SS3y*O9De7t@I&gWhJ2YEedBJ0zWDP#N-9>!oV`U;fwgn4| z^ywlR6^W5g^Ux|#kWBI=p481FDUI?SN5LjeGwAsK{+4h z4Iy*@w+-AfZ5-{KLf13(YkRIdvl3lXTxW9U1i<|~F%JCpjA3i-@xq!6S>uHs-q!tU zn8M<(EhcC{KB7T$57=Y2Qnxi=3Zv+JnfaQZVbw}JgFb%@;^3cB=$})FgwV#BR7pRp zq{qO~uNpFAC1&~RzmcqmzOYJlAQ%n+^yhvC#G|HEYoRzX`rI+13rOtzhL0AN%6N?1 zi-8G_K0xG{ta268iUV%8D<^qzc_n0*r?T0xJphu<6p+P6_N%LZp6*mIGCh<=y)Kxj zGX2IjY;_&>YEXdYt$EWx-?yX@E@AstRLbl)giFynjl9KP>R0$nBb~~ksk~VJK*_Xr z-q32AuBwYjPueEBHaFqS;b8?mRSqYqRNJ(HFZugdgPryF=?9;kp59)dP#3mOiuLzq z^J+SeL7iK+RAcx8;}6E@;qNIYGwtGbyXkH>!cN}`aqK*30o@5)6LcP9Mpr5O4pJ6M8>HT2l%wx4zobqQMWlSThsz-nQ|9ew}Ham~#=SFfgPfWo2_B z!@bwfBW&v>O6|jnOM?B|39jxFId~s*`Tj_sjk2wruykE>g$hFL+TB>6?*Lg1|2NQ` zFG{__pB)x5TW^;;dmjaB6|kpen4PVgnsP!@XB&NraF@s;iD$k%Z(M#|ra8KCP-V2) z2T$&7XLn@2hUWJy5nEJacVME&!;tzF`(`7XHU+4V{?}pRc=tWA#C{}Dq8r3eP3_>6C!v7yLL86XRnmW+br7dz4>7#~^b#>3l8 zEn{JEZM1!cmn_;KN9Lo$O>o8dv7-^r>DR|ju7}e=AtMp<9?=dVZw}qSocl+^>X*r) zB|7gcW^u~tI;(Fsv)KBqIn=>Wmt!tAm-~(P9v%Kzs4f z1?NvizTV=+_kEDAP;?eCj3pl4dy2`;qrA&-!D@U2VkmOfX{f^ae=_CA!LLUdu~FV<;sMccUzHqz+bdf}~*lGsggc{$*gTDv4k4 zcNVApWSjKXoQAgvbiAR=T5Z?HZGPRhKA6Jp-LlMsD41XQzXQ2*C=)dy&!|xUPD-oBeUYB&4i=Qf)|hv;9?;c!{42ENbNu4H3-cV1MrZQ20t^d7^5mNGAq?ym zERBJxPgETBJ#~+Q*+yz4vgN9j zW3NgTdSHJ>;e}WOT7ss#h5hyB$y50G!aVz|fpsPyls7#ICjBlrg`ff^AN>j)bBXCz zLgzjxm$=5x3@uY_(9W^#Xi(x?Q5V-EPtBNlb*s3H%HL5ntJ7ePb(6?=bM`emG( zIDTLdzpI9{K6k`K@6w%eE(5L{3x85P^+W#rq<+HEalBnaePhCXm#TkjZk5c*Zg~xK zMc?_L(-ARC{kHyN{HlKJVw1ZfucP}i?S>iCnkGlxuSDtqGnb*2y1W&0a5*t+(jL4A zr1+ytbM#ngJT$9x1=AD28pIieM-s*Hd_kXX8@m424@LsC zb0#aPjtw=R^5uUdbLGTSV~?5(_e~eNROJL(N`u46kN(BK%>^A=Z00axCXxjSL{YtzXH}Pdp*RCqK-G?6G;`V)_bWsVZ2W-lC#L z{;2Qua3un+pWibslTb*b@JUH`Y_YuGFo|CIc6H2Iai7_;4Co0RL&B}HIjA9`fEFtU zD2LCwPk?t|z;dG52Z=o3tlC>4vIMEBe1K66)$d$ahHl=kH2Gg?LyD)2c-@uX zX^}53{jW6!`V}kY5pDsAEEhWOVjgau8!rNoY;k+_QueK}vMfPiLGqpUdeoKF30va) z)B40LN@{Ry`Ji3^?FF3WHaQvfJ!`829h^B-gM4Pq=78o}S`AKc35R{v$vTmF)V~jD zsuVE;+`Dvhovq?FgTG+yN2S$ku56Cz`AITtZ~cQq7M)N7751AQV$Ydvv6|zTtx;c) z%+;Mi9sq01j0=|_DELfJ6|&iIS~)+~heFYvUliOaYwYS{H} z#X_0ICb&K`S2Q@cnMTD-vt%IUk+}vkxoV2m0i_X!yx?0vRTmvo)CUkPUc%>QmMl%t zL-zWTHy(?ZwB|qcU|efO6)*iMS)R}t?2v z5KKO6*-4^qQd*1}oR=#*HPmCb^7da$+IQwKnwv-G?VtY6@$V%6A$aY+Um(}*+2Y?9 z{DknW#&cjJQHI<#8)2d%T(XR^L40ZnYQ@N}{A&Y@hTgBzYQt1-)O^yKX$r>X4M+qP z=rj5YHe@KR2k}zl1f0c~V8`HF9BP^keCrJ+xzNJw)XM-q_fZy z5r)pzTg|%D$}{`Uf)Q$6J^33${{#+;LA!=y7WDOHzb1y=gpBFf^VhhfGJ(2CNO;M2 z!H%Dgq*Ff#`|O$htaXB&Y+p^=X!w3oCyuld`lhdcBIJ)?vV1zIg5cfQ=4wJ+oO_)u zV)(H`>SIt(F;LjpeSbo~*uqo8CVK-c%vUnzWu#cC zn`e#St?uPvX%aU@9j}4WfGyX)TgQe4gYDVZ`G{w7UrfBCoXuc69Y6nmUsmUxU9{%* z*Tp|a>m}jYnx`%BZ%?~!o*{9{3_LzFgAX=i z=kwj0;>I8`A8>Ic*tw=Rv-8VS(dUP#RRdk-OS1Ppl!zl{$xvJS1j~OOz~A<7x?N&zW1Di$Zv?jq5VZ1fhkLHIv;>j%K?`e+w#HvQ}Vu z!3SIM?g%UQ==JV%b^izRo>f60;An+lHX9oA?b~HUmh&EA3p5>zvWfh^nIBZ(C>U-R z@b?!-{C-Q-(fk{y_a+Za$`=*g@rr)CWp?h17RfJHTPF7*5-Nx%m8TQ>$jA)dw{^Mq zU97~SGT{qp2lruO=JdS-#0Uc&h$hD4%M>R|Fb zXfjER%9PHZuc#!mitB!nfKmB+|U3 zUu@I|lF7EA*if$!x_XiDmbM7_)@qiJ`HfbhQ=6$#73Yxk;AmGA>-Xlp!6eP?%?e-% zDTHcO6-l>5$Kw8>)n)fa5G(nP$M2^ix*!iFbb5(}O}1vWu<%*;X27hPt>O!N$vuYM zQN--I`oge&L9~kKbSCMCN(`<=q~?snz5W6CqEv(EzS;cu;mupda5ux)`MpP$KiqDH zIVa?On^OOnmel?)7;raz4k~Q+yKnmq?&i@IQSB$6{%b`$Pw9Sm7(o^#9YE??4rXoS zk$YbhZ$x#w(3rS&TRnBopeqJQP}{we9BV!!lmP-1b7(kg+0u?=QVchU+|LFaxuGDX zA+tR%)UH?+SAW_}fBQty-32I4&RoEtiU}%Du};;Qn@lNbrZc(Ljwx848J@2>F@i?$ zue}0*ycFXAYIZREK7_aBk6?HuNe7fu(3?o4PoA}8cdxzbI;1(8jUEat?NKBz$3`Xi zeJ;%@bur)I{Kdn2u96(mfToVj{VA)I1+f8^BDauzpkFa$M9 z8k;I)$t}&1cH*rm3DfJnU)MwYTiMjnFIFu{Z=$n3KcL)!fk`Q_=AN=Kt&LGb?$t4B zmKHlh|DW1Q8CC63v+E6c5ZvyYvsMeHvpqKgjMO3|?|z5LA6NCuO1<@0FN3uA=HldH zT;wI%Xob9kxZN~o@}&vWqFZQjRFd-+4yWN&i`w2&f=PAv8ut~v+vq)m|U|$G)2G}{Ibf2mZk&V z+>~oRI2vig!AUS_fUF2Ch&_V4HRgTG5v1o0HBcE)bMkQCaMoQB-SxIDCn(xk13)qj z0GYux%SWQD*5^H)B8RfJ+;zqD;Q|W6Fsn#aEF`sdA3Qed3g|H|cp$qeR~CJh z8bkK2dPwT%EkRY(`)J*rQ{|@~b}*_cm&_tL*Z8kGtxko!Qc1@BjCZfB{-Ge97Vz)# zf7;W@%H1iHV}D(pNq8xnUv}@kXn3h;#xqH&izh-*#^u+IeC0BX$~Ub7=rhd5J56N= zGUSqqgcgp*lPAwM$t&=BiKR|RbP;2Tk#q$c;jVKBo@SAySMnU|B!}i`_1d6;s&B%x z%CrM9z{r$(?VJ^(%lA`iP7v07R%0?0IYSn-O)hNfT*kGbMlS7#21XV)LUOjAYa}<0 z;~QDOqNxdXFgm_xDsK>J$aVz#)N1OYe79HIwiT!`n~$Z{Sk+aHYpfSwW!&ps1cL^% zB@Tgu2)nHxTX*-&yKPk!bGEbgKb$UjwVuT5nA7h`Xg4mHf7yP*Fcf%@-Nw~tX2N8s z7=og93hSU&HsQ?)Sj=}EIr?6-D~nxpxHqt7OER(=iGTQr(_+0N+n);Wn5vzccs}~*-wM$BzyR3f=*R6Wa)=KrP496BJ_(mSdH8)=8Y0HiHgVO{tYw{RP`GynM+c9j=1t0f*^PCo21a*Z)S@MBcicu^t7h}|l4q{Z z9l!r4*7{Z>CurKHUQC{zufWZ=N$dcTwZ*C9rJCfHU&Z3xDn3~1 zk0g?%rbf0+(h5V2q@`~1Zb4c+tK%yAtyURc#rgA{8HxKkw}|Pb(jHHd~pRrI9#+DXL!Emm!J|X9cKgU9G#`mDX5lxn|kb`f)r= zsYvq1Fc6it#s22@f%pr+bhW_CO^a#=D^Nb1 zQkb0e5kmF|13LgAX~R{Wmts^BTJCwF3(x*4HtfKTuF)CPXS2}bm@9_R13{BV6nd1-esTjvgbsKf66EyH)z|j?W1uq*mC1K6*ySk~c!=aRY71gtB~+rRmbz$+r2Kl5t8SlO~qm zNI~t*U^ogIB!^mw8aie3j|lw^t#{3o0oQVtdd>ryOmV7Mwi5q?=_jx?za?6wY0`Rv|e?w4g1C@^zEp5|KxlcQPi1YsSOWTtzfE-mRL?em zJ18F{;+zG~S1F?JcV%#hQ#N*wHOw*NXi5IoJ%HhO!>HC|xeO?I_kst~K_lLGFU2#o z0L$1IfmPX6;89vcxC%w~O z;7k)2PTzgSP5@vFrk0j|{3M5`sJ+@Rw}3Fb`;dMQkQu6h0+p~BZWu=Cb%O(ZQO zV?T@U{A=;EA{P2_PI#9xD(CP`U#^Trl#KyBV5xUlU^T zG3`%0fAOr684Ws&$vQ{YD?*T%2cr?Ayy0;V#>4>=*(d`jT5*4wN6?=WFdlX0&AzQ9 z;@e=)b9NACQwMM?TVJ+fWq`7&DAjURk#TFw{pDaL+E8q^@6fl9pX>VIbDd3PX_k6^ z&7=nBtjtzu5(ndx@}rQ3K3CRK=@%+F^q~E9(Gf=zix-WMx)LV<4&M!t1zZX)I}c^3 zz^jlXt$m7mJp;biOgD&!D8e9P*dH~i=j`~vLTjw8mEu3x&zU+JlA$>n5zIExsq(*h zreUC0*K*(*Oj9ca1`Q=hKXz+kBP34gM|X?pe`?IV_kZ`&$d2vr)_>O;mU--=Slyzk z`?-#5VFpuXNZLduIhJ_yx>fhptaT1buT$tPf^q20QItx&1$g%i`W>!Jg_P;?==DRE zX}8YrPro*iMI0r>j8s|PpS)*!(I^0OJWveZdegUMyznjh*XL-~&au|GGffv}N$nSa z7zXdsDbgd(vwxD5(v}3}S(>(blj^uBcLWy!Rohz9YQIZr}2IUJFEVWEJ_P_K{{3 z2T~mkR?=Q_Z5L;5uCnpy89%$4q^?#% zMGof|l0-8j*y;z^iYHxtFv<6hi5L4ohMm?%JQI=?p)a)?-*SGw-rsn)vMs`oYGC=( zRUYY3$ztG%o#U5_=6C;63GdLHtgzGgm|_+GF_`$8{N-bjRI+6(_D3~Y-dG=*Ejmpj zLqXa2ZSdCetgZkVSiamX5UFfb34OqX^{PW_LB!p*E-kf2deDYHHkQ<)ptjhj9z8JA zsCpyy_9g;tDIIdt{R1K!A68%cy851hawL->#|8Ca=MXJ-8^hcJ($j10oT&5m%+kW_ zLo!jo{9rCa-2^{SpuGw(pwNxbHg(BKd1#QPMUaAB$fM7ao*!i31x&h7LOx|eCUj}u zsWW}T#ke=V#!6J~#q6ix_>zA$V4+=ZdOHI9riJod3X0x2;nGuG3;?{}O=7mnYzYg#yf^%6S$G*lUIpmEQZ3z+0oyB z*^vf%c){Dj8KIH?kqU?ZEdT2=zW6K3-Lf3ieE z*88S*eQR`qCDGm0Et(_H0<9Qh-9kk##Dx+jXI09Bmi=kI)%t#`%&T>yWMLg%O6p`Ts-MBmY^Bf;&0>`sq4i-uogu;Xfp}R7Ijx zPqsLKV*WMUGi_Jg48K$kKn8H0DOk?4OJ>ckgkm9*JntCf_om zsO)r+h53CL;VftQSJHZX-`#rlHgdB=zA&rqL)s{ydm@GZ>#s zsZXNwL=7aM;2184-=&U<;`Des8+Nr2PE?d}Dnx*BNNnXNi_}E9L8pBOlS?t>&21RtbYBSs^*UdCq#b#8N5%PraCLSyPmiWPZXbrfv5s*g3 zBS(a`okKH-BvtjyDEK}*67k_L5UZ5c0Z8rxW~oOr^!|jP{Ea9;X!G;iF<@Fyg`ClP zu!II`mM_r`s`@5MX}NEMTRrIi`Nbd3GdXitn3;T1_Vs8zT!NVWXrZoV4g8R1O+BWP zksWzmTKD;5hzzFvr7Qa&-u*l1lB+09Gx8Nc|D0KDm~(myeH*Dv8FI}`9#nwCy3biU zywSk6E?GsH`qYVqj#T$P;L-DGP+F?tDN;j>qGYL1J(U_Z5d71ACg8j=n1HkFi7N#r za?B!#>X33a?J2bkjbfxZ==Iq6wCBn#0vRvsl1y;|6cHRVC}Ju_mh z+h9|VnIx&e;sB)a6`Om!9Osgk&y{#w;`)-M_?bsOeKa>o&bz3glB!k{L6keviJAv1 zoN-v<$>`-&+Lqc95Tg+d>&NT8jl=i=Mvwd%g<2cer!=FNQBVfJU*?_*dU|OBDsRrJ{

    2=Cv*8+a$yYjDow&GilvNv$rgcyjOhE(S4u`iDFg({y1Q(xH2PkH?g z;y$rQP3#bB1TgiFLAuEeS(ahAYhuoa2!Rd&(hlt`t?S+Og4>qLRC@;@5g8BAD4xAq z6saz!4PGrzM{ZSYJTPanb#hnL#_1z6$f68(eFz2TCSdh6OkP#tA`o8{b!^2;LC$Gv zu@RMD;Ml91tG1h;DGpQz*}5w6Whe1M7~qfZh5|OG9+~yengJfUF{a%n&S?S)6!PJq zig)Vi=+)Szzw2Y;v4)NWvgYs$Z4nm|uNFsxV24o2kmG^keb?ew^Q3aq4wml*ZM7jZ z%3>yYA?Vk#cdmdI8jsb_D$2B@4PjCe&igLqPe{d$4&rusc%q0)ya*4s6poNVZX?Tt zsoQ855mM2eMG=Tex9D%l9YH>V!(ZA{x8VZ!fNZ7U5EO$iLw=vnNDuKaroj%#DQDf1 zlL?H$c;WtZgor10n3jMhZfuplf|2$+YlY7@34&0yIs%KP!mvT>=Tl9m|82(fzs+zi zKMQG58f4$k>!z06=J;(ji>Ii25s^BPD1Si28D9l+3XgOk)A{^_k*jrmd$1|l4rdFF zp+U#Ji}S`eExP`t-3D_&?luA&fiqv>ne1FAnmdiZSop0?X(=g$BxGAI!Mb?DoZ}X! zi|=Up#Zlo_o&@I8f~y~|rv2>1U(6}QBWtahn;geo9*Wq1yFdJbom8M7VbIN?pR9Ae zNuuc>SWTQoVrCdmj-h4xtGw(;hx93;6E$ST>`!e%Za7clo0iN%xVu%g zC2B3hLs9%$_#j+YvoY9zXYu6^R%M>Y0yd0n5y(1e0>|gV)K&Zd5o9iV1$SDwI50t6 zBXTWezoONx(E(9OEKQlvylnm&&^d)~4x91%=s-&0HM_iyJ1rckeranO=A4iErLdjq zydlV6`m{uFD(MoT7vZc?u)-Cm4X_aqPxg*B|JuBv7@8nyzW(o_;oVI4>Tffu_Ei!1 z^I}13a`Tk`4*q|rg8TdyW!hHb+RCq5@fSweH=g&MBEot?_53hN@vqtr$SyLy3488B zEK-o@He`jm#9f1J*ydVZOwz5+;-L*#6?W@7Dtz(?)WO!^VZ8jU#!co+o?)!1=Li2C z>~?!BZH*Xpx0$O1FT_zI+E%0uBoFUSbANN`+ZFhPp$Zl0(12MZnG9Qz30=9Xbl&Ho zz|a;I|EO26pJMT1>PM)0qqDic05|kIWyvJcnI!gGRVk>w*xEigi~U2@Y7?G$R7OS6 z$ya+af>A4g$M#s{z_W|7?e^>_w#Z_?pB&5XK2f?fck$1#fyTb`ceBH+9y=?k3g)oL z{45%tOzZRK^~dgn(luwUUS9shfgoX*8YgWxf7*yMDhl0^rQ06y<5fU>djj3c*{hvb zJUb08EkV=ks!?y-nI^N>HOq8pKEvO@=XwD^i7ddX5qacUY8>#Du4~)lzGwYrJFfvO z^+0pHA z_JyAMg`N6^{`3p|Qy<0(&tKeJi5X*C`cTS`*Iaf4s6<=sfP*x5iXXN`QfjDgW)Jj; z9x|cJ{53M@7>viI*f=W8Km)EKbiO}6L3n_W@OUr;y^UrOEFw3A84f&l&IR}H#w`0D zLs#h;ow88cRsj%lr%K_TDx)XBJNGySvfGfPIkWu_`)ormMsmJhz6<;9feHI96ZYFQ z?ATO(;9|9we{!y|oO#J_*T{?UP-}nscKMr-rvixwyg?im(Ixx=en~r_Y+$)u=m*|E zL$&g~1sg8OIK@XIl`2|5osJ3EhK{C~bkeFNqU+h$)VLg%d72mblG)e~lKxT=3bw#L z%jk8{e@9JRIB@$dhK3)%ZXP^^&;PB{XvYEzR!KFnNz0r zh?BV?woIH+Z>I{fB;9c8iEwpbhGxXljs^=z>Dh5~*je=D_7ipd8HFg))Ft(^MP(Fk zQ1>T*GhkU04o7l%{xD?ZYo~g!e=Tdq(Ef3jwq22Kia}NmgAD(jUqV%VoXiy2Ag~4~TWxosE>UP!_QA~b9czMupmFEYEVmdBXozq!-C#I>l z^&N=YP;-g(cE6oTsnW4HIdn#yd3F8~{!N$-EG_yLSV4I^x1xJa?x~UVT@qYA8v!t6d+<#-o4SSl5ej*)tN+AMwzifPPJK|dmfdit(vXUp&puo8a zmV+1b+}*eo*X7UMdIu=GZ4*Go8{p!&?h{z8-{erCKTerkA&;Wq<$_{#EL@E%nz0k! zWCWiB)U-+Xy*FJ9IY;6uIB1YjPN2iG+@ySb|_E8AiAuIBe}ItM2UXMqAe z&y?DUle}77iT8xH880Ybl~8<^-$n!`#c+Ca-aL0tb1oh@qw+i+#u5&qF+(nsR)o`5 z32G_sTNq!1G95O$UUHsI4o?1k?%@03(OS6%!!4NRSRp*&ym$pBbb{HSM3x;DI)3?Z z!lF3Y)laJ2$K(3%f%R{m^8A;lz7_{tul&tZ`{V5Ubb_30cz;yxtuK~CLwVB{UQIBa zff*4xZ(NzhSKE!rOaN;B=R*4G5bucR@JT|`Ju0A3+-+l+nlz0Y5G?YmKi)J(ojFK5 zqHk>zcN0VDl&VBT!pSE|;!+p;8-O5VpBVMiLldSz7~*Vst= zDE!EnDkjUgcLGIZ?&1OF$8jOU#^RqzU>4|xkkz-Lu<-(7967)uk4=fHslN{#Oi`Ml zFW4(5b-H#)H(I?o;2uu2Y?xGs4!I8Oow32#`+UD##6}9er*!#wTPUi`BP3r~to7;x z4Ph$hb+^^X%H*o3lGBOJZqAi%o_0#u%pPLx~l3{(nQ!7DKb_Sw-9BuCJb zNJ>A_hq?_!(%u>4M9)bAn}rbw@NV>kAqtJeU(q$9iswaE4D9ONb(GKz>GQ2e^sUMI zX{K|VQapdy&ULam)^bfUVBhXg9+?XUmEUh4Mq~0&$5=X z4?iKb;kLh-3_f3Qbw&hNx(37=bjy7<^rW}s^oC1BSkZ9$@7QdNIqC3On||ZK>2Od~ zFL%XGeK4Ea=nRU|1js{LGFp@mx<$v~S=yCZ$C1$Dhi}gSNQEo;;ogYKezES05v41m zJWpq1G3N2L?Crq(qv(+Hu7}Kb#KQ>1Q_(_$z&d(nY8!VJvqR947bh*jD1W$d9k;mN zxg^wtxhS3>{nEFCY_LPKNyZ7!PTNm}xcglJ8tIMx0S+dcZ=2*eTV1<=#8XU{1O}XZ)XqR|V*_ zs9d`cFX$$<>C6(A?F)5uG%KrWKd{8A;CjGt;+B zbt$U4_-Z_g@3A4*+Qp(X_Ifa6+3G5SG|jS{afSuZK?$O-vX5bT@k}syxM6cVi(mG~ z{c2PB4)bBjp1zk_HdC>;G5EtuuQ==CNf4!{I5Qw0`i-}dSReR(yCsVrC;trBtme`t z08#`$NwP`243R?Y^eV7y%SMr}Wmaf}U`@Y@#Sfs3F5bM`w#`y}(Danz^T%xpiQ-1G zR4Gu1LF8Kd9FoUK;?FPAXfG?`zQHHsp}bFZa!{*L9Cq5*LoaU*t*_%4v~hWCxx+%} zn)~M0u?QWW$XjaDEKF2J3H-E^B=(7N-!MPS{t`$~V(BO->+8^OEJ~6`;2{j^C-_m| zcyHp^*a)cIF;icgS$@E0qoMysT_oB78W>JK&j3>ie_Ec~zGzV&Z=k`^B|i!EKvMXZeU{XvJHdM>9Y`Ex3*2EdrLB!bsk`H7wT zlakE8BJ4y2L9SL><|63b*8m$~1E6K}0F60{kP5ASg6Zi^S1H|w564oBzJ^9*u;a;X z6@d&FwP?1!2&tXZ9vwX6H^C3!H71faavOy#_r&aP67D!*qLmHNBAJwxDHdgho9kPrgH#k6($z%1?lwf+0or`%j9 z>=Bm#{K^YgbgnJJ^VX)SJ8=zZGfM?3unStoTRNUNncQ78J8sv6BqSS8%oV`XWYj69 zF;7P93JT!u0*`&Cs^5r=kicecr6&^+>GX>-spHH}d9$#9(lzTyMJh5Tr=m)B+F%mi zu?Z|Koqi4m^YY<9>TL_$eREMw_~BNXt>4fE$==OBbOaY(JdOCLZK`KH0o;52{u+L{ zNj!TS1A7Ob=btLIH2%pfcP%>VSTIW9W$414yvwpQErcq3aF{@qNU!~^+GSU8Af0hl zHP;+u#&C9)mcik+KhdeXR57x0A1ueXk_hu)`ZnfNaI(+A<952K->ciY{(=bZ$iUz0 z6aVHkIncEFWKFJ7-J$sQ@3%ZkSDSk6vG_>&n!jDj$xPeTi~?!oO_x|`dt2Md-xKu! z_+c&~D2Wu~vj*pV7diQg$)P`YBHN(bu(A(3O~0*`XtI`wjF;$)$rfY;w3;AWF6m#6 z^s@46eIJqvlj4(i3~kt1Bax4nl^~s8^@s0L?L5(689c%}FK{Dm6s-^qs3<#Db+Ki1 z!t=}4_*ty<4GYbUV?RjS1Jtn){0Msa0X6hqT>ggbF1bzI2hp4LMX|7R$b#vi*ey=A zvkpIJ=+gkx(j$i}q_#`vHu4^Y& zjz~GPS~y+65)TDgO9gZO`FX=}@!fZW%+iy0#*W&}d^PXJ)(G?Ln?9U*B|vg9#b*03 zSi4d_UmmM;*`aiKdUL&S^L_71yPSFjk5B+8<7w+9|DB8{S>c?c7jKo$=_(_{RyZd_Y3`Ct z)>^(_B+KH!@cWh%I26MBzJH2Wp)1NZOl5}x(Yr1#&@5&XXVtR!L-fd(IK{kh2IcKp z-wvTC9H{QYq+x;tXC0?cS##7^g6^f0lFWanJ`tC!bBILy=osF7drd=8I+Js!P=BSM zI@+?*W4_k^Hqe{h`~R#Dqgvh%aFOBQR?J$8df^Fw6-Ew-m+*+7;vFJkVZ}~8x^(N=_66f{0$8}U#dGg-=&9c}vuv?(pCEAvroLS8I^y5XYWyz$%mF>re zi~3TC_iq(!>K#NTyy5;Lgz7u@#I_SHDl!*UR|RrnS@R!xp+&U|{Jv_6WWQ2a+~DJz zG3$DivKR7F$zgiR9iF1+VZ+kDLcyoDo+{7`&dpG;DSidp&1jauQ>}CD+YLgC>XrLD z#id4@T$U=IST_H@no=XM2_61|^mROt>Pqka5qACDLYG)Fg<;YpcoiJQ|NTy;VuCVy zNgxL(*;+}r^+@sNu+i};K_C$!09$y zgTRzQ*{yk`gRjAiC@*ve2L|n`6CV;lFvX--~~HsUbEV zGRir|-k@X{TrO5vU;iTja4_$y6+m5WCGm&s!E+cIYa-yN1g1`ilr(LjTS&g2Wi?33 z=;o#!0dCnw^p{JZSbUpAEr>mEMKm=!4jig>cn5&KTM=gf(|^3&R5y*kMv{2h>p-v! zC&ij_F&7|g#k7M^%7;z{!&YJo(H7I%KYqXtj$-##Z~jW_U6Q+oMtf8#y$4d;97DoP zURLo_>r7dCp(=OT)lETj;wOh`Yo!z~;%i6K+!zNFrtCXr>`p2A0JxDByRrxTLRh@9 z(}kg2c7FA6DaGIP%CVZP741j?fX$?SSUhVcveROh#n%Ne2W;RDtxw&BWa4 z%^q7RpONS_b5-&PHaaV|EW7`B7n}pLEnFq04)_9Uy> z1Z9NKVGdaj!7D4SUSo_I>u+99fHd1|>{E(v^T&OR??K&i92w@HL~5T;SZ>_fpk-T& zl23C7o1qt_#F|S_!g++-@w}c^=aYAEI5pjWr`(V7ClpVMOz%u0XB5X=BB9-FqRkX)&ApJPV zQK=~{%74>!+fjBoE7_){FWwt_D+u9AobLJUuE-ux#CL?>3>uLwh-&5yH04E^B~uu> zP0;GDu9wKPyOQp&m6*+&;m@s2;UA>#CZpigxgXJMfzDGtVxIRF9#?Gb?!jdo{QTSS zf3o^>*iS8+-dz4g7TT_`sT!t=9m~-bX8~_}8Wo#w=>16#vh`{4n!zfUfwh=P1w`_p zd18pgVz)@xz!&4UKI%lLUhQYTYsaqYG9V0<=*BmGfLa~K zaczd(rl%F&p_@;@xVJOYYDb4X&?Ffk-qx;LP52)G(me8S2mik`Iwpr9Zdh5#LIsN` z?br6agPbD_lA?C)m~nRvdY)c=Er0r?LVo#W34x^0;=|Le+-8z0;Y}xV->J;d^|YalExZi!6^CemXwNlb~0SQ6rY+#>joK~dzRHq z{FYGVz7xam?V@NPB@ib!ET>nrI(4&mbJcdaR)qU~<40lDX7Ce_VU=S*r6&a}c*5)Q zAT9jE*pzTJ>oZcvF>}xTSEGcSR_5X^Sz9kz&uf1^fkO1DQ1Q=`hG}|-Q~N5&7yC*5 zZ7lwT_bInb+sYQID?6+Zs0-Lr=bnP^0Xui6M5MADh1R=Hj>Q59z^~ab&qzP~N=|#8 z|IwQ0@(3i9AlElyF+Zt@Nx126yX+77+3}kvws-JUs_Nw#CG_&q*KNS#uc^1>-;`g^ z<_RweCL1Jg_b$-J>zH05UlSxtnI2Aq1w-*&*HBxjc*Ue8RhurYee)91HzZ;`ZzsP^ z4(iGZr^RZSRaVv@M~6!QEb#61Nf!bTBw-~MYp5!!Qr4lW<(Sn~Cp}e0F-`9FnEJ!=i5cSj4%Do!~jY8AXNl+Nr?So7|a1|Ne*~o2!zopwW=d`$g?8- zf1lgF#hL0|&Cm20?t2J_4oNL^{N{@HSjowD(DD{$)fQNJbLI*aiVOcR9<`x6uHP=$ zfn!fO@2*dVd@(3+ZHZm5abman&hvYtxtY}Bki=3EQ6!{AY9sbgI8&B}deF{Q%|)b< zl*}Kpoe3Fv-OA<_WI!J8V(084{dHMu+@>73!36MM@q=nV% z#W$+H;%PNC6>2O_G9`TE+7^+Wq`+Rxbk|JmeFB7V8h{!pL6b8%o=sNp>Ig*O!fj$r z0tZ_-s+(u&Q#nk2PSB+wk?RGc*Rq$K44MXc$q@xnIPz4gc%3#rZ_vw8n47X3yzgB> zyn^z?Yl->B{~L9;EJT!FFHE0?f=g`wC9Zq~gMcwT-GTv1JEHjx%Af;W%MxpgH9 ziNbtKdPPnAeplj7(xLeD*haPlZTGzfi!w9C5R?I;WW_>5OE}?eQ9fby8u#Py0px{G z3@Mb>tN-%D9h{j4MIW#lFy#&%CdoBCZPOq%D8InK?wJ}f_ThVW$?^l3wcWsmx5M%*pU0B!R`BAe7YjZg31uQZyU zeDZ9ayKc;uYR$(9yTT-pRNW;f6J+ya<;Xp)?mkvhG)jQG`P`->uGF>FWAoaMOUe7} z$Iayr=ir~epRD8waTpxvF*^;l-rd{W|2{n;>i7y}CxM1n;w2fYDu=4sy!d$E0o*h4 zE{s7 zwSovaWiC|wT1vb`CO2~X9ckUGr&4!dgNa?M;-jVV)EK+0 zeXhe@ld=Qn;#cgY3VaECvFxTR68WAS% zhtXp_C5{U_7i#i53{?6<(UZF}k}YzoO7Q01FPBpcmKOhObU0fkkZuy2q0DCw;)+}D zP_4?UefYr7&!L29O7I?t#j^_Fa6)M3Yr+lc#E+f+7*{POARQd=V}+{Ruh1#N+BE-S z?k&4Bl;Y`A+ZPjSRQNW`H9o(!Amc&ik7^MQ2t@!9tJ8qjGA1e|`&G zA!2{5$an8@^g;P%=>KdxGY42Ley<~SnOz(E`9RgAO?2Pqs2$PF&Ay(&)Mi)M``Pc6 zo!14eHq7SsK!JOKh+iJ2#4hR8I@yW1shg$&E4g&0mD{3S!q)Job$!7tiZrOhd+!0k zU$XnL-vVYf(g8}wz3;5If56V;W&)bm-nEtA8fI(Rb73-JT()HDI^4y|8=|$v9)dlx zD5HfnKm%mkUn{ir-{OZIBX}Yab=V#6{Zg{{o`t7=e%QE z^EpXLvdM&We7IA$t`>+t_ikB;AIfEAQcvRMI^xUElwj2bZsdZJP$d9;)2)0v#diVB zkP5gw%2&g(Qu};Ph9+CdJznr`GI_U*NSK~UKj+mD=hQLY_`&y6p+|8`kMFB{gfb1L zotP{CvYHa4-UgMRh93Ehfk{RiAYbq-$jTgaU`!R4DztWf_jr;_<_Ul&$d`s3q#7~t zAZF=U+T_;DLS?T~tZMH^h(Mm9VK#2!Y2?{^STLo7-rWzRhF6BWLq!7y1m!_GBlr-< z#(#bIexDqM@s_n^4q`6SdWHRL+N0-EDtm<^1VkK^cY%np9 zPzRACl;319EFf=|8QXF%aiEz6-TdSYt}BnwFf}{NzVSkq?@Ak#)yj~hmcbhc%(ouw zvsChm250Wy@j_`b6$d|foFE4rZ{j~zjrH3Dq>u;O0Xu-z9^M*} z)cCN`B#>iISFF5t%LMyXs$%av4Iy8lC1b`jk;)NneA|p|yq&E8uI0WUmoH8G1qaT{wzR@8FqDa4rdA%GPH+9+~4+K6HbU=!Yd#gb zioWmAKLh?P-AqV59TWsS z+g?=DgYelrc3xj4W2!~PU#QYlBziItzQ+F=0FQ?kN(!p<5mnWXy76V?6#wnE*DuAc zm&F33y7G!|F05~g+lWXQt+HX~E2hEq9+7fZhcfpS!?`H*#hjrNHQFj!{ymi)4{3X2 zYf6NyPnUwu#~(8*3wz4!EK(1%X%Z89j)8SmCd<&PL$NPD{fXia_nXqL0N{b-%6UYU zmjD)L3~Kw!$;3?^RvP{iVL5xY{H?p5 z;()OqWJ>#SOHbvZh~zN`0lT#mYspYPDz;#rMc0@quSP$iQ8T0#vsj<#XVju*-aBv$ zuN)-3lzvjzCT8-rVX`oQlSu!LqZSzzrGqos^awe{P$)bL4Rk2o4gxeWmjl+Gc#6MM zSG5Z8HCau3!5&Qr<_-Q8blAc8O4j=|dn0e+uHzgm6uRnhZDDgKhy(Sa+Q4##mf;>E3!}R6~ z2N{T46P90HwF@WOAaxcOcYHaNd&qx2dQW?aNOxd#=wsU3h~Dq`mOkZRWloj1Y$wnz ziilLh7Z{;h!E?L6oa~+zd2$qKZ)rqTQ7v9qwTHLCaUx=%NS^`a${jcYZUzK9Qt}qm>&aZh<#0njg0;;yY5|3&9-%3S+xb9F8yos%69=dJC_LG;NCok9b#E+ z_vEDY32h4(s`QH!M@FW+s6(2(SGeBg0B>h|{U^O~Rvm~Gsc}0$^UxGRH{leD7Ue8( zi;Psio#v%-{vp4NG>yD$r4BM0$Qr>1JA7d5(frces_?U9K#O2)e+NNl({lYgz~UDR zKKXnryD3WW*~=Mc$5KIW(y!@i#Cj3Rk)7nyc<^gZyTa9J zQCTres#P-Kph10Y-#$_(SrU3(A^X*2dHoJUdGRx4DE9%eK38MJeltuQvL2#6K8A)f z<@r-HXpztFt9RZ`m7y%{K$#7{`btO^AFCq9S&g!~w^n@@pO;^k83bW_$wARZD%1ME zc!d4yA6l9XPw)TR#roZF&Y=^0XwHwz!2&2$L=!#bURk}t(R`j7_@oL_2AL%p!4fna zvb2(tAaw*hNE1yhOE6cmFqX5{Ok<;7FIr*nH{C{OA5eKv*&#$!BRmz(CFeR)2a>*f zt=a$1EaZ}&jqcXs69&!aohE!{c~)wZP%LeKgBq}cd>THva~S;VZ##T`m*fUV7Xqa2 z%}-g8oUtokvKME|glq@*zDVXhZw8qmY>;zv)la8h<+)NtuEmrF^FY7F!pl}3Jgp?w z2L$Ls&UGbY2+h2Aig>3|+Mg>;b){;2N1C>$IL54fLA*fDbtb&IRdaW8wdFJYn*$(Q zy^c0PdCS%?U?9fpbLBSg{z)AgD^6W_wq_#Vv25CQ&{@Y8uJkF5orp10cqd%bVp~{K zYDZ9{)&Up$^cge)){Kj)CW<?L^7MaV&FQ6Mqf@|g#^^7w#vlTb*G))#A+G}KzE`*H!=H_C?XgbvSC9lc6S zZ2$VHhWv4XX-mH7F|_VDtKN+6kfDLwIU9)3m<}wr>dHQ!R#0c_^Yt0}!m6(WO44O?{+$2CSOaUr(&EBd!ovQ~y5K;cc>RHA+e1?O&aMg3xEXP|4|$lH@>kZm z3G?rB_G}G9hsfwCFo!39(yL;>%x_Bre0Ti=>2-BXdcli@zGq_|EP?Z+Ju7#+^!$2u z5M2{x18F^kn_8VN{&#;PTyh4m#jF1hS?B%F1{=2hB=+93wA3Ckg5tKdMa8I5dlj); zd(SAXO^KLAtEeLOj1jFJMeR+^+N-wm=Dt7A^ZfGu2jt3`>$uMIIKF4@jiPX#y{7{s zxHV4hZSNztRG8{@XXd<)F`LJLs~_476TuZupkAZd7eE&zEB_dRzDFF^LOkdiP@|2D zY}ka$4)j805V5tGI(h?J|75+#I4oHI>9LFIwzeq$h&~jJT1YE;@A*5)WF7vVebaQc zK4)40H*nyKAW37KXuF0d2e7Stwb4q%~?B0 zYr?{O`>LpiQ142?3V!oLpnc*%<8ZkpedrU;(l^{X@cD&*;(K_D*eqUy;5oUVF`(VS z>bu=B=#i|Vmm|!dv7%(bK2kEm%raxp^Wuer0N)0n88`PIVGc_;1dxtL@Wd}1SCu&9 zN2F_sqA`rYJrN+k%tN)(R>)y$PGq-?fr62bmPpgZDijH>EP&<$NT9n1J!QUrr8cWbFpSl z>n|!Ds{OKX0a#}o6YSp@6i!3l;=GYO`}udSKKY0L6Uo`-lTf<3{G~-T4I@jXwJ@z4 zJmx106c4$vp{P1tRty<_tFK~jg~?NKYgAye^u`TK@qHS7abS=vk?vg(;>QGo=oev$ zDlk>Z=d6T5l#Qi!h-P*})*8Y=#xhIov-C-}g|JNq2{z)M@sPN2s3msl)9>Sjde$=w zpgD3kSg-EUqg-hl;z%#R$`9+orIwX^;kDk%lq`$(nuP~1H-jN6J(SO(DK>$d?eDdq ze}R6anf2AbN<#GdtnMYgpM>sDrBQ8E@lm#hrnW@XO9u5%lwA%0Q~`lDvm1DW5Odg*%cYdnhnHDClT3Z@l!Fnh>zSZEX=&s3?$L6|AfbdZPc z?Tu(F^&ernt}-A~eGOrSNe$!Gp2#EINK_ncv+0shLf@g0pBTYyFcpc%M`uxJ^AN9x z(TW!&jthJytpB{+*6m0XJf!d%re=t$dO@(-#zXe1|k ztkBJeLtngAxy`cbs%v1Hon`u%a|G|z6&Wl>Z3i=15T2L9fP+CX7p7V8_&~LrIpCwHb#uF z^g2Jytm9pI;$>-~{xY|7c|_zV6i8~oi-!)AR%MCv)mdG$PVqPl^REVKnH)G2t1N%Z z`CAHs@TgUtwM3d7RYvmxKFdN@C)gIGBKLx-3ZM3?Ru$lE1)@7&WiSo#bD8>=RPubrkEho2Mt|5}eC|49eS zR;_c+|D=QNJY4Zp(P~;2nOYTV=40oqX#Kw}HOjbg!ZL@Dce;S`vVN6}T7e>M(cpyF z@4rJHr|QHorbu$P((=)$!WW1LOBS1SY)OdbebfL!FQ>EAxK?-SoL5~!wyiT5C9iQJ z-KljdC$yS+k8D`efl^DaZrduok9PB^s6b&`o20tQG0vt(pb=irgd&5J$&rXao^wmM&KC`mV(3w0L2-ET`_$wo%KjK&FuNER} z4vwSLmG60g_O#FQZ0I`f#v-epn-BV4wE!V8x@%tU$@U_X>cu@p+xDOe%BNwc|BASdL&^)(;$Lg#fVl&?!yfj3dyAgl`;-4YwoGvuIAuS!@nJ_2 zkWgB=uZf}J-&~^p0ZM8+8u!?mkA7+WLW&9XHNS0_F!sI#4)+&6?Ka?9p*E|zr5K;` zSwL;DuPgf0*Niig3yfAutP<}F13zoMOAwd|Ip2v%fd3J1Ca=UV(8~`1f-@M!mbuP> zb7mRY^}7gHjsg4@KXvOSO`KEVeTT&FVXO2i$Hm-FXY%g=+GijFxkh}AK>u{!2+kAG zavIRY&j2MJ;v~qxT%$(&;_6(OpJdH)_g@XkV!^9Nf6@@%Go@*#nwZ zWl~U3=&xf&-`0Y|^2ZjSinbeYb-Am#iCg*!?ytIz5*p($V}Hg1BZnOBI|+~BKiaM; zc=>E!)GN|+qG*>H-jWp1A5@;aPqma>Dh+2R(aIKU{XAIBM^`SB8``GRniOvuY_R>> zZ~*`j!&3WY$WODuHO-I3;8a7EOKj*}-raVv_uyT>ZkYZ~S_Gywrdy}FTxc58T4w0c zBxqfWmG*y3i8WY%5&q;=-;3R&x`;;kgu1h@$@}+;vWTrlmD6ws%%7Ljv5#fJ}0KslaX z;sYbXxHS)Q>O5nuv1?4Q_MBGXXF$v8B|!9#s;zN-B!XRaj-q%I^v2@>dqiVh&QGJ@ zg-jtQqvfy7S>eCf1NI26n0gk^a7XsQKSGJrWJ&$_{6pZKP}I^n`)$%mX#VAcuLm+$ zC-K*V9bfX=NmKGKtCc#YF6Z*UWMG1aL{`fT~tf&?D*eWPd&b1FfjhHzfsaS{{{bpJ$mK%NA(kH|I6-ogrNxqv`iP-t&yn& z+x2WEA-rqJZQ1tmoqxWCUgLLzddEjDP zg_+o|DU9^b){Rgm`2)47^U&FS`_+)AnPdh`$^u*ODNP3y4pV1${?X~&r?l+(xJ}_{ z%%xMS3?x?$PO!gr!3h6p5r%c?NKzBB2$^yK$(Ph>9O*YfBSFss%O5`zD6||*YQUI@ zj+!2qk>gRfI5y6qB2bVwRBooKS#kclTppyLlOONNr@(v};KFHwt+dW~29t+r_Dq=C zY}+BEAXVN{SUMqm0YR2(sp75uoI3C~RTx{7c7z682z7@LSjMVky5IU$vRGRC^5JuIIN&;VP0f94S$z<}|i%1}c)2%>S()|-oc zWorVZb||{?_Xh>cmFi;rjrzKIt5nB_9dq)lTVbV0=*3&tx2lN zVE%}5e4fW~xJdhfKI|wK={?$ycw zrfe9*LZ#d@i=V}_!|LVR?<(0!Q#VdFZK=5S*QD)6)X%aq(dx+dw6b{x$AU>enRT*v z+9rg6;>@Zd@t8oZ>&v76t3<6n>uM*N@ zAx<`mXIKLjOta>_=6y$|Lg?oV#ACJJ(A%E%Y>^zmBHTbc5k;eHQYo>(hS_?4oAB5G z;q*QW4*>4dPX2Kn{+}4c73Hb*n!k_e;*2%t&8OS7JzKoiYQ~Pj^BGt5JuiT6bw z`havwO}_{=GA&F@0#Aw-wTa4_G%#4~&sr1CI+nita3cGy8qI>ZW)nuc6Ramw{E{4ps?hs7?^cKSc zePobB}Fy?EbHvx=78Q<2~_z{pYH%`Coqe&g#M~{TS2lC3uf*X-!?rQ+kfdTHO2YQ{`30w^fdBWtthJUU^8$6Cvhgna{9Cn z+LahuU5^9!+ICz6gD!TMQfnTZN1rEA$hXNJWQSbV+yL*)_wN-q(iL{zUzR$SS&Xaq zzRUQtUgf*K2f=#oUy~x8g(EW$f6eOukUErkmc?I7tcA9vlTXK)b7GI6A|fHlaAi9j zqD@KiNPrGg_2`iQh30FlR!b^x#LI`>z3rTHW?|LNySkOCi@B(W9*0O*61n&6J%8(| z(ND8RW&kT(y*IuJ49kg1!sbD5yMV3QI0LT$kVr)7`L|A|aJKtA9JR<=;-5VTl@kOiFQ9M8D)2HxDGK~6I8_O0$+JiTb zS^`d&`v9M>>>fr!@M5}qzKXj95_JPi$ElPzF2?N4iD!?CQ+uiwCdS_P=^%d|*JaX*%_BhLJP&JZ{8otNhID zUVXqYp`sZQozco+yv$mEb+03oC};wpdo*`;K6<*aLJ)}_9SVoJw7Ft@_lyV7?MU7G z?eTjPh}b*(Z&aceKZdqrbP#psg8UF8A~MsZ7e z;X**6>s-;zpa#@R)0dLP1d0K~LqyMk3c2R8!lXK{7nYUD5fxz|9r zi`FyMfl<^#`l~6Av&S~g!6;0<(Dy)mL9QH-frbs(N^dHCBMtMjFE7Zy>*LSgoyYgD ziiHX-R0`84Xc495mYxf0fQNX{;(45rU{fnZTRFk)j#^{4e}RjjUiicGe4)j`&w*6J zw@(#?XaUY7qOX%p+x#RgSVNrQAYe|4_y$#!vdFURuuIvcX@7 zHN*N*-G|}49KmrN%}qBYvZ*2YTl~q*6Bl3X*(`T+?4tTP!%e0K)3sg8mo{XHs5mHKGI_Uq z-K&XT^v*L>S46CHvB*EJt;aneplfM=eNm2AM04bJ_8GlJNyZzR*0T~Y?akfYPIf7i zMN9xXDJI;!xu-r#u4*ukt^Ds-=Py^Shc7#lBVV$UsF%M<*;?2)D3&Z~boI%_g|qA2 zh@#lJPv{@~5`a3Pz|g`!Jr8(7YQDdip>LDh5jZg%q1XK+j>aHYDMKsvG$EALR0Mu? zntp{w!eJHbFXyF4RF38ZsjF__rzk?TL>qsJEwz1w>hH*jCtn9$vmCJBU+?U-O@j#1 zNf!G8AKM8XAc>5;Exx%>b~?OeT3&z%A8_>9TOLPRkkNC`G0H63pZyWQ+1Pg<5^5Az zzQ|_nCTg&+By&i2j?r=~3+AV4Uwsw8ZG{IE*@OPG@V9V#J)DfjX-2_L8ODiq54#a3 zxZkso(qro4Gce!<7I^e>jPgbjC#G!n2JBhYXT&E7oSQ$}GBY;yR`THg{!~i8^N$81 zZpOJttKla8Pj$kTN$B|<)`u}aDTXj)X|?^D?80zm^~wr`g%;k>SPQrt8Z~x7QLUZV zZFoy-MK|;9VZ-^DmHq4WbCz~;cJeA~)!+^HW7d$j8E1bqEO_H5lea!437vu-gmbsM zB@5kFwe66dx3o-aT4^30?Q5SE0xh^Y?B9iNBrH{6af^(v_(b-aNP4>`6)tpLbA}9Zj&u)rN!;Ic=!=s~LkX*dT@Uz4R*=XISe^PW7 zpAR|vlbmq`TZR4om-Jxu+$baKR%W7@g~z+EHR>@(;4Ui^du*>b|7lG;I6k$U zV0wDbMK&QCk1}@(sxZBAz$tw>u(NPd*2xp7 zWx>OG8)M+CQ=1B81={pmoO{)9Bng|Q>OU74d-pKHaaAZ119{(R@Njw%A5dt^Um1|) zMY-4?>c=ZT@WY(H@5yHhb07S%e$Bu5vV8H?H7Ml4Sa_AzqVBEw&_O{f z>Vex8l~`X!!iV+r7CF{qBAM^!(w|OFHtgey+%4NxHo9S#iAU%DZyzW&mu+k}nV{N? zC$A3|?)Q6!UP`l9HCn49rNa#T<1f>NdZbZi^1#y>}b{LO>fDdR#NGVYAb9<{=9_7Igb45|Ef1M4f-?qv;d zT&EDUFmK=W0dXx}mj!|Kv3kKTVt}qWNuD3)so?e6YmHTz)bO)!Z!Qfoupi4XT+z}u zTQ7|cMSFvtd#mMAvtK+quj<;VCwq2De~m0lx5PoBm~ePd{RrV@;lvbN%J7c)W4X|x zz5QDYfAhQs)E(@oqok{b_Ts9Y#-z`YvNdXNBDr^>`hs-b``f|o{=sp$ z|It4KyHf&M@PqwXYt*4<7~I{kGU=fGyamzSN;Z-eX!$L7K7NmOaiMIoe~Hk7SCpR& zGna|I*$BT4p*&x^46j&f->?qOxc9?9;pTm3=sCLdH4mo#pxgQy-mw5|QdZJALPFqB zyr%Gz!QnF_56-;3BZfaO-*`^^-7vm#)w}ZY*qADwJO0$9vDgywZg40bn;g)(5tMx6 zNq5nZ?^tQsB)yFeH6;lLk2GIz`^c;CG+tp^&Ului(Wy;0wZ9#eu4fM98SkDh@yRuf z*!W02tr^8DFs&;2ly1US=$hOzt%CimG-PwzBeIVtE&64OACpd_;OaPIMwmd@{`Ys- z&gf6}jlJB}+}Qd%9Q)zEC>zu77jOP{JWCNcypj7ERZOJ0AM~ml&#iyAGq(oK%2K7r zZr3IN%w0-dFg0CC9t?oLsjC%AGPJ8PDT;L1`)wcbxM8uSj4H>ic}&_P5TV|z*Sr3l zm$F=u-07k6V3Mm;ewR-F#K?s!)|6NDt==G%iaPl7nS~$tI{?pal!?;mH>W{}30p-O zE;30%KuDYE7xVrIx3#p$#$UAKyn~CrJWPBk^0n&fNKBV`Pm=1ho6H}S1fushR^Yfzs5-m7H0 zUWsSBAv4;Kx5u5G#zLBCKk*QvAyfdqHl&^IJUx%*9doN6d zT2B3KJqX`xySx5yXLj$J?qv!%duTzWDnG$T0M69#UawOv%ZO#(X0vD#~{B)Evw<@MSgqI{(uvYRO z&|QSSVM2VE`nj|Ibj?<7ua)HeImp9Hr0c}nS6H)MGt>b8P_P9P?5WrL`2O&)keqdA z;d;A-#qn9r&-M)=tnbgD3D@u!od!)xJHwbDQm&33nt9WGDP8nsD_%Hx$QIOk#hvx0 zs2V-PWV`H|d*QSDVaXG5^R`dwIESl);THEjTDl@4~@gVddcuTYAPXKrGLy^72Hjz;is2fj2 z&fpL*rTSfo!+l+tEj$ud^0eu|@?tM0I%2x{LShl$-<=?`I0jmrO9#%dbRSUW1oWA@ zyu1>(5Wg9PFpf%~Pt2*~<7wGS`j$tBM1E`NWSzgvqpMc`fXedgnqB_@k&>`*J028O zk(0A-Y=^F%u7d@pp#3~)HMN#e< zL80X5FHfl}YeEM`HU0_ZbncwiZy-h*@hHR2%udM3jM8{K*j_amVEz`j;R||K*5`In z`-P{eJnFbT`f9CVe>k`fE&^qC{b(UZ_&nf#p`39fDi^5+3@6TC=q7mYjaDN*op0`b$V`OBel29WEjkN7!;uW zH*IdxJfMN^!4JLsC%f_8L%1l3-=SL8)VX*5HCtt==uTSHJX50RgG%1;zRS&A|1FY( zi8N~mIJ_0xC17G^MhghY5^;GxJ2C1?v{kls@Dxjy%@eUe6ZWF77JE<8-#iUiGS%)Q zl-AFmcWvnR3kMFok?sA~H*qv@mWQ6sN)tMM0arJ(e}95u!>rbCsuJeCQ*xojb8zDk zASR##NFbiUV zySN{IF*GvdfS+BEj7WY_BBLGAzGR7k{f-D7WMu3_I`xbqLFcR5?M@aNSi*E+T$f4C{H%0s z3e>Tf&X9gA1t$c{9(|TF!wf~9WA8^~avd4LpZ4c}4r^s1&kwkzEnas?sWFY@oU3Fv z-h3unki%3i9?yXl?i_0}5kcOMtwTZrDru}JZdzSD;7*u*!09d%fyCkOuVrm(LjW!o ziN|iu4|Knk4OU-DkA?If5mAJH{tE9JeUg^3AmD;Q>FCQoM!iV3UUxo7>9=~o?+rvG zcxW!4CN>#}prWCxS(FFcfxk?wFTODa7!Q*L9p!F|Hta*T$a;;1-IYS7JIz%ztzl^Y z%bUtAH_N05%R}G5>UkDae0N#@1CiFUP1otUiRg}E^f{vY{D976o?~BFI$MtNOcdu3 ziOcvXT8}Yk|7eYs8Fm91_ZHzog-!%r7RP&ZCAWU-V+URhL&K*ndAH*2$k>I&Krj>D z3$M1D$>E`EhDG#Y341|Gd{3~ZSE5CRd($R?&JCee_A04G@i@g7ivj13(&!!!zjoVm zY4BW+Iin)QK&7eGbIo00Bp3bk3`w&aPf+j&@?uY=P$mUq-EBh@}ksKGqcpVlzkYY#~X|F@D~gyPvoqNk31eF6A3&x zq++!flNh0CJZO16CI9l=HFkU2YW!H;vENcgo%X?S@ieBi<7xAg7glu-`u}|J&SFR& z?IySq)vyZqMX>b9UWGbL_4)17(1nPaqSoJ`q1^qf*Q?*l-iELnpI;3f`dGA+tOZ?W zShc=9jOAWl@u_Uf@z(PudX~Hg?Vkc~Zi*XK?z$=*j@^L*zTF$@G}JjXtK47QYBLd; z?`VEr(-hadG!s9o_{h(Z%Kyk_-f3w5!PzR-F67T^mk@#GkapX@pH6cvOKqB!Di6%aUk;@95NEfrZuYETNmd9XsY_{Y5CP?>#t(w3i}n7~<45D+()(l;_bbU}v`}Qpn92IH z?R+xXJpra4+IokS1I&ju$j_z1XS~LK$|#m1JzjZBvPVy9asw0aeG|bY`WZFRTJkFW zsYq4|cs}b1!vg}=65fMReY+{-wfx-E1o8|ue2K})r%XX;XI{W6#QWLnsO4#eBnjg1EAxY+|A;c5(0W{&r&aeZ6(`i7*}j`L%rkJ6ji-hltCQO~^aIUz3I2hR z+C2RAW7tIC4a8lUqG%95{(ezHL2#zHiaM>#CxuXF)|pq@Mbm7&$M49j-9pUyar1Kn z!nVlnfP`$Vl?YDf1cm#GZOW#LmF$u~8zb?r8kwoixG#C+V=utthuE~O5VcgYHQzPZ zDaEgQVbG;-u7BdAjH9|UL^1|XuimCG4!Zs`H`XD+cHOVrWW7G|z!CQw0?)sxvyQLX zp*{Skh4+EOh-*utHMTQvK3G%<-o1Wgu5_NAgWaEqG(BN7P`pfRa@C|fV4@>hm40Gm z{7>=~_f&wMg!?#oNcQxbK-wxEqRb2DyiJPy35N9D?&EiCRC4VX-J(}gA&8=m5{z+G z<1&_P_LR`-9Ok7&;oNoe?JPr!wU{HP$ou`%>_;q8$1Ad+yl|FvcXg}EN@tO@oSZ3| zK26F&fe-e1RVl^9(?4s4Je+-K`E6IRb_EW!!rTkp= zA{|}d%0RV)d-*lrhW1SIz|%OMYQR7`$`*Glqfm2*A^+8=9x&;d zOM-p*Tu08NFoZLZsV&l8LK7wncs%_#+V6;|9^(Bc@??^N4PCoVEalKrm=)1T8Zt-e ze)p%Qwiuh{x^)APiUghNpevgF#rk4T9NuM(QFKDa#xXvrfelqz&g0V6DfVeuoHocF zNyzP1hCEybIAnQ-IntXrRT{dXRM10DL15>zogx8^$G3@;;tki4bHd3L-&oQZ@F)r|C z0q8zKlIO8swkX%$*RIk&^t#1v{$WPs&i1zPsy3=IC0}%=%l+5;#qpm@%hvhxbC;)= zgo`e=+9#C5#{t7z_hGXo)vMYNi^dS;OcX9cS)rVj6CMuTMsehZK~j01Pe$Vp8!bdf zg@3DO&WUcDF*+>E%~ur`%lCk>fQFjDgGsBktz+?SdPoYLI)g>1 z2g*M?Yo!~}e$Glh?RDUu!y}i8k7eOWqRPaoCnko*>5 zyafNgGJZW!$Ry`_oF2jR(LTbGz{ZGkZrL~q^&;|W>r*t@2Cq9~=Oa67=!G`VENwCG zqNJ1oM={x#dtY3K);b+N{Z#Z%bTYB^tIEz)uK6LB%5Yewg)U;PA}S!a9!wtpA~dRn zjg~ZpbA1C5?6Q==Qh(9v6S|cqkdJ_$JkR1)hVJIoY!z#V9St$A@b#;tmDR0sjOh~= z6Hvq>%a$Aja%_dx$DtzN#F;U+%^opO))>3A|Akrc3sPQzJioLVE>D3R^n!iE1_>@a zVn5yao31yblH-T4w|4a!Vj{FwziO>rz2I4ehTIVNIu73?dQ=6U z0Cw*E>TU{1)V(0vxS{y7vVfLbqiav{-^92tK5JsYUa>m9Hv)A~^SfWIr;Oq)tCnW| zbwUWs7j#2fW7aoOVR^t}S_=f9r}U%(1rHVzkFMS~ko9zis)}@*$|s}xSU~-n4#Eiq zCIrZR100o4=!;>;ZjVcTP{7o zG-f^u%fjs;&r&SF)OJo0PwP%|ez;t`W+$AmRwq6F1@&p#Sr5;^I5;~x z|Ce;&uliwXuh+>CQx-X^Dag-{`^13#fsL45gMyzoTiSGDE`-I>S#4}3Sbm9Kc0wNL z%sl7MCcl0o-jpo0mh=|-;p8?=Syxaua&JYtQ7Ime?SrcIY&KsW1fjH?3!&41@ zwK(Oj4&ox=V)^V4aqwlIa)m$=J%^5Ul9oKclc%PNb3?cM>HXcBTscO|+ni1N2m!pQ zmRNNg#ELx&_6eiWBWRT@5D(`{k$raB>>xz0JTe1fde*|iRIX?YQ7$`-2*QWJ1F{BS zPO>M~7j|3BBuUGk$e)?8n*A>ez-|!(F%0vgd0yjC%;zsMof`Tc41)S2=rTR%5%T6_ zYK6jiqiJ1Q%C3G)l>_Fqb$mJfx{>v@TzQ3jkJJ;VPY_EiprWk-IhCedlHV=doe_SN zV#CzQgkfe~dx8SV$OJ!zL4iX=`RKRqAtOs=5TJ-W6XvFdm4w@yF^1X$>=?V%z-z)Z z_Eh|tMVSjU(PYz(WV_ib&L4L=5zg*J0fH?p8;am{Yr_pl1#OC=m{bzKMd3R4`>lT( zQUx*#yMxkrG6%cz#@Ic<%4I1z08R4^r>Ii=$B&9~opVh>wXO)L0|qT59RnBPCzQGV z%an`o>puY3=FpqTo39;mqDc3Y<5mg(!|2Gb`n z9d{685A$QBO08BbjfH6p98vMA=fZr9=HPFyGYg6FE`3$P+I;orev#&=@rl=2Ow>Bm z&Nd{-e%=Sgt{hq>&>~sVS?`&maURj)?qd}b=dmvlmR`_3cpIS7#vadhF({J89Sf zRN7re*|nPQQSYe2lpA<;=(rVclU?Qjv(SOZ6c%%R9K4hhN`XCSbw6v8xD%NNw)Z4e z$|Zchwv^cnd>fCew>w79Bl6Uc(m(a|1yUU;D;bqSm$dv6Zzn$c{1)yMG2cr`A{(Kt zji1Tj($+o`IH~IgkWCgPd?<1l;E;PW?M37I@?GP)mCgFo>kXF5B$0F|2$C8286Vg( zpij#BlO>g8zJz`C=LCnHoK`Ec^_b!V0A>qAu$DmO2@DF0?1)B1=~jUa^N!8AF1#G8 z&*;cm8H>EvE&5f&))?MI?Sc z-}suFjc>NGN|ohcx(yx^;pT8Fg|uvO<{X@c75_Ygw3RskyEO%iKXbfY9Zl=n&A^8XxOC=SGU zL1%Kl&X~U$k}4;5=dq`|^GxwsU+E=bD_+R^LxR<4G6$AC|DMXS1a+})n^VBpDAWBk z6SBzG23uN8BYCbI_&b)yP|#-Tg&`;)1MHDq=-PmZLfE+?yZ68LR;p&hjNsc8!fTmS zb|7>XG7)b)J8SFVs&6$&Cr| z>FZ?3#;jj2Tip!0<1g4k(_-^`o~DJyeUDqy-(K1~0YNwzy zHcOL$%+3bADgFo|rYmG>#;QV8@Z>QIRVK^?j;>~L&xU~c6b~@}-Cj7}&?R68)%I{R zut9DlAHDei<-bSvDPv=wtz6nZY+d+Za03T8bhk!K+qJfArT;8TSSa!&ty%IxJ0z_L zNAMs=_q!$g0;*wa69VVdg`~NKqDh&0Z>hI13m>w&Zv+&Q!hDw2!;=o}CX^TeX2v!y`sF#a|$%W;GiL-RPZL*l}z82Wy zGA{hxWDl~ufUMeiCRsP5uZ(^fP?J2DJ*8V6jnN;>vw;k_l)4iQ7A06JV@))@XYpnQ zHby#CI3ABK13rZ9Om_66g-{q*v@@j zi%Gb3sNYEEDyAYA%pzj%fn^$&Ie(OvWiEL5;}%J&kfyHd&xmwHY3#pDzB7}NA{Ok{ zLA1s|p$GIh{fz%!t-lsa#&{&Y$4kf+zdtVrxasP5m>p+^pdlx!NK}O^LA(f@+1Q3R z>K+ZwKa&HfY|=fqC&^R-i}^8W^F1pN5)A_H_c?XDwe3vR@n^MppL&Tp^JGrGR<*R0 z=*V@@PB{*USS9{sVv4hy)&ZFDoRM`y1)<2NKU7F<`MZ;_if~y$hT7U<0`fF9x4t!o zzTt&05MU_W8U9imDpQ%TKEL`bWmqT$H{wgyc;uaAxtrQ&fm>U|t{h4ZlL`F{H+z`8 zoGDg_+0aT&@hWCdg!B=V8EXl_uN2(pyrT7L_!rh4n{70aT!5yHrcVypjxjTM4HR!- z#;LRbu4bS5Y5nm1|E{AzqJ1p8%mbT+4C8J2&Hsw%l5*F5_+#Kb9xRIq^U@@ z!tpjaVaukzP5 zxUPt(N!XUlmf@J!k=P6n6$se;HbN_Xj$G>kN^Dxq(r7U;T6*V#&Sn%grolg=qJ>=s z3?~IyM7hbR1xz#u4E*e?22)Jm5(F{T)KF(?sPJS5GG8)l9?};!YMC8L5*B%d&0>VnB8R29Wjpfl^phum}Ni z*T=j8r{x64RG|gYmY&=^LHz{kq*~P0b;6(5J^dx+g|vj5R3iNlWM^W$)0$jgU~UUu zT3?=sN%_F)gwhaME2=s;QT|`)BrU;lpp_ei{Vg_J2rT5EsnZSZz{mU3_h^r=P)AaU zVcIKG{&{1kYyQiqHnzOYi`#M`+=Tx)F?&U9V5Y|6EZ#YFBc`7OV_rjsOflaT*wo_! z_I^w(oRir@dKI1bQlX|7ER738Qb1Dio5K`2pEw1 zG-o*U{fVQ|GQQ$cE{5^OyRSI*8TyfHT$bWTQJe=;V5W zdIik8YI9}etUQPs>f#sEO~j;FlEJMh1{Om;0%bfgPx=MbEtsm#ACT32=fl39TKK|c z=g`Wz8`*QI^(f1EUvM)|SRW8BPsnW9i3wt*?h+b!BveSL6bSE>?cUcZq(i8eCHd9Q5Km5u&EB z%qmT0q9=adGnASShwM4Y^Cc|v=OqN~(UoOsRuN^|d+&VXfC}i!669&am^~~uoJ!0! z$d$FAaxl%)`Wn$U^xTE6F*d&h`0y5WJpPUX&Tc1OB2|zxzOOc1ugCf5so!R3nmk?dtyEHS_-8<dbo5P4qr$%qDGAujn-MIF#Ar%0 zjZNAzR7Q#eVrs)Ul5nDg4S0{!u7&0VN6#9(8nJQ3HHkt%yY zJO>N_hh!$KY`u!($XHB(&d|f*yJ0I6U(6y|2n-6WpaBCj*&(tL))2dOc3|#Zg+hOK z@ag3Jt=zDG%WhDvDh%X$rW5jiH3(dCJoJ0Vqqtw!L>i!CY)Inq@g)xcvy;AbZOq!C zC#0Z!WGg+xFJ-ojmo(xO6Py*t<9J)gp+lua*R|J2O5(l|IssBPT$Hr@Q6v5L(}(@> zvStCL`^$n70IL1EbU@$5Q}=9Je|$6jq3LHjZ6f{-SyjnxtO``h#+_OQHt}u90hSkJ z97OI4Q6xX%o&TW`&_#+JAzT{VV8mM|5Lbjd1KmF@>d`bF;JBgBTZ*SsnujsY^bhSjz9i;CXn*92ffhNT}T=|RY>0|(!EN+pfPMkL_y%VRrc2&4;!or;>o z3HFYr1i#Kfculx7u+x_hP)1Wl_D2WZzkv}2Cpz!m_qW6CD_tB)3Cn4$%xnc+I_HMS zC}%RVB-Den>*ho{d<4iyaFr040|73W>a?!;XTlhfnUWjKl(Ho;e1)zM*mfJgM-D6|Ltampvd9!^Deao+Cwo=lR_E=|eG}J}@ z{*Cz3jo%)I+G|ZM2_OpkK4q@3H29;kY;_LZ0)P5=CfoW;ipew&L3$Cv#)sz)LO?)R zsx*u?+D(uwxwothiSmm8fp-xzd%Po3czUWsMeMv)Hr>%OuE=-{&ep?E_c-#`!Wi-R zrhaZCE|cRbP`S02A0+Wj%d?4QD;aF}%Qd~Sj5^(yV5|tB;CgJMRQ}PX!5*mq*V`qY zCphkR@L$0_d_`FNM;TRWQR{O`KiMw13=EgQT=pN_RV3g3E*W&guPG(RwQXzyW;2ur zSRN{}LS-V=GRV2I0t9}+l5TETpKrX}c<*ug<}zLtkk5?yXQPCuYJ_;+e{5B0hQdOU z|DOG=o|uGaUT4Z%!Z_L{X-XC?to}i6WJJvi8NjFrdVN>(8?oTTvO>d1rYY3(n)YG% z`_JoHCpw!@?j{_%+$pfAQhkbn%t(POWenFn+Hrc$bV>?4{33~nAP<^b=aby>X3sko}j(+ zPPjMOFVvdwHu1{Qw}SZ{5{swCzvY(}n($f~c9^ioB0un~b~Vo)?LSO;V%jK#jCIFX zZY74y)<3b61=sPvN*6UHzH#f;jj$Q|RG)*oJ)5wIz-{kbAEa4Q9ZHj_``NVps9S{z zFT?cz(e>6*QAb_WFexGk3P?&xBRRCRLCpXHN;eE0(w!pGATcnsG}0YIr!a(+^w3B* zLo?s#`#jJ3*1Oib*8Fw-aExxhl)|)!B%iG0O;ERAw3_F zYzz6q|FyN-_o#Igb%EMJT~*yVC8-?-9Q4NC{VbG1c4u90)87tnwxMPzQ1h|3!& zOQNjeIkLe~K?9$;7Y7!?`(qtbok_L#KqkX+QJmXdjq|B3Lwl*33LRSMBtG6jK?0nz z?t)z1v5VWdOz%JE<9Hzzgf!j*KM@8cw+!e7uf?T8JN!K4;CXJk1Y3;%^%4h0$f>t$ff|nY320o@bT@qeE?oP zJFya23SRE!04mbGANv&FsIk#w%;te`)1NuDQG_WP9xK~jqB-Fc&OMP)&I9Q|_2jZ^ z79uAp0f35aqM?d}X>*C*e>ZR!_V+NSJ(g6a)BN!u(tb;4UVj2_yzqGZGcGNot!Df23w`p^@-j@bC+&c4;UTXEzQ5AW(<{2uwdnOc8%H82a6#AQDkGW84 zRk3Pe>9Tigu!_Ot33I;~=wrDnx{$YpEAubV@|PU-#4R`J?fekxe39K^O=at`>ct1+ z;}XI`ns#IAhE090BLh#PO@w|m;z`_#&yZwRaR3t8Nh4<|pr%(naT=^Km?0lW#ygM( zH;-C1X0YL+o`mk0^jB=scYE_j_glmPJEOiR$CBGEder1i+f^@~k>6RB^!@H=>l%wf zrr}+e>g`F5W5BkV^jYzrw(X4qf`?Cs!ZnzP6G$X_~tQy8~2G9{OKqAe+Qw9y~al?S6ath4{fc@>^bVd_QtbkmNfz0W3a}2#mU55v_u-f1%q{VaQEIRKM&WI1fhq$m7Rq) z(!;2?SrxG39C=Tzv0{#Y?4rrhbhp2y#zOzUcXMpToY&^R;(v$eNHa?0&0fP^e1;pOaluSP{w zt00vi-$Aqb?|3y~fnEVVUh~`Fv7eR651s(g`urW!b}+YLBH#9KB9|tgkg1n@Q27ZH zwm?13d6Lacp)H5?is|+^;M^E42;p>j*Y`Zv9BHj(gdqZsFW|y7l&>T(C-FV|8gNH} z+FQCWzSBwi?0@ScbrZ*XluUn{FMSnG9B_DeKjY!IQYdvaPwaVgGxc>;^7KSLU~5|X z(tZ@xm36;y(YpIiaI|fo<&0A2FyJDx{b=-VDd5WJTJMYaq(QU~+jlaby~{xFW&(kU z!4KUB0AKGvo#|VML>&I$$eS0L)Vk@IeP3l&f}rH+RA6kg^Se4NnZ$0uq1fVJeeXwq zdorZ7l;o57-Utp`pP9)!-9Z^mVO&;688Gz(WT-jVnZ0|{Jbh3wE>dQ8;b{%%Gv4mF zwwR)BIW*UJup`oMM^S;naZ>^$5m;VUDWj~dywc2|!%0EN(OnKNg1%SC@M}s53SMYESF_gg4+Y>#D;s04~5Q?d=5^FPVD92&jfs*t=HLSB_#wBGcN zwj7PL`qa4vilx3OcsiZYvR;*BfvMp&mev8AZ=RoX4(?qf-vs18~`Uqc?kct3ZRLOD!vf>yZm!9;KYU@wvD zxUc?YrMS=$_($tahd#c~y}K<|UGO~jO`*fB`Eay7SCR;B9Y1SSK` zcP{=^YpCbk2Eli`5N>D_8__Sf1MVRwD>gy>!y)v3u6zsBt6Xdv{eBE*S;|9Cgb*V( z&r%D5kkO zJ<~+>fCYf_I`Xw~p|XsuLpoMj2~y_yG!mI8Ad`3t$Oj=fsPu55=Oz1lsI?dNQ5}XC zChN}`P5~0RJqNMq8kiM3jpVVZk)Q?#^6SB8z0(f`1Fp}K+zYFmg7ZOMI2O(GlHRl) zYPsNg<4r21v|PsIcpS}JkhUBPmu2ErF)I5DH+y2$qHh3W?^ zp)Ys^ZRyQ3y&D)kSp3w$-n}3I?lwnKlg5Y;8fl+T?ErzPa5H~|6idWwlYuDAOTP21 zo)rw(Hn^q7zjF%4ofL}ZVF2(CeaoY9Y%s`NH0v4d2v6$8FuqKgYQI=R9je}aN5#g6 z5V!sD@~?iTnla-Qb-p0Ld(0g)e?jcP(o%*O>)G`MO04B~&bTCexdo;JOX5i9^xT|u zANo%xH>g_Mi%E2Q|MZ}Ebtm6`cZ%wxzr#aa6W`{>tWV!mL8w;txi?{QA?@QE)lg+Et-uf0 z+nhh*QqJlgfv~f8NqBj5oBq=CM*_@HW*>Z2qK7LUKz9f}29y<2+UFGW^M$q8fJ#os zPzw&LC{}+lbT9{$o2aO2t;>Tu0MXfYbRJOEnfWd+%T$m}Ixh5`w4edk2>3pnfspU3 zq`Do@c@9pv<>XA|}prjqH+gNyl1 z4mgu(gQ~uCgq_pD_#0x=a#jBe){LMlDa_v)e%2$OGd$27Ud0O0**)Q?FPkNffnw0% zN)Hf-<^x-S(D87a4xB;#A;)xNaq&j>>ksY^;6I@ZIz%j@epM5CT70bo&9#(fmd1U7 zJiw&phu_Vio0+!poP`+EA4X)%fnxF@xS^m@5uIGL$t1^-G3V|Nlh`aYf$;35ndNM} zr770ch;g`-a+&0&4N&uBFM`&DNF3;-lmZnnSXuNqul_MAB}A}Y8W~*H8@=wLe%o>G z(Rz7!8yNe1U4``$pAf<<=tr=Vc{puNEDREfrXIUn&w zEP?@&qGuf+FCNBY-i9^~Y$a_oC-Ohgd^AIG>>SF$z{o%I`#2rIkwisf-NYW*g=NUa z2RE5iRWEIL5~zJwEdZ9fNrSF%Www(T10-JhXR1AdnTsPQK@NKrRdNSoVw}c7}2=Z-N6I~WtuIF;! z$|d7IsbTw{GUdh@Z^V(Y<$qj~aVvHU@#hoTnVv`$O!1dgGJ7##2)lA9nTQofvJPdoZTexZYfNGE)_@DHM#wI}EIv2jTDsnr;Vk&!S^4JUM%YBq&t@`ZG5Y z!n7c?`AS8eZ-RW9KLD@_IE9E&p>O9`{qa}fGJZ4FpD;k{SkCwh=f8Ce=DFE`QP=rV zZ&f1~>p~_>#k$C=-@QSDmm&s!=RdRnSg@e zv3R~QWNs=fDM%+Fi^UVTG+hAGd>y#xac$$`ZCyREY~a1Zjmb5DqZPhJZ}X&Q;Qi)Y zFjCt?7vntG9Tx(AQ1A}4tH>2mgE2HoP;dU#gd+|IsF|8u3NfzlY1B`|$ojFRj1eZw zL~l0j9N+ZfG*t#_Yt513!`P>8<5ajVO>nt#;fM5F8dB9>GJ@SXP2EqFA}!T#5;!si z?TOPa2Q;;af7E9=_F@D_=3Xu#b1U3G-C@_%7iwuS_b+B@ckZR`J57}@(TCWp z&A%!4le|GLp~f6Kc&fJrZ1@YHhJswqGlepM{Lj=P z=hU{(a^wt^nJh%Fw;$t?y{FsnWz7V9ihRed@q8wpHuQ_da8kaP-HBWpt$!?6_DTgN!zd%OVYf`^;(aJ*v-7Pku54!NV6ys~@e=}5o6ZjT~Yen{G7MSP<1&R>d39EqQoim0pDzj=!9 zd3J!a0zbYgyh%ycRFz=ELz5BCnr0D*BoJyy`;Q&Jyqkh;xokAhSr^ z_~nBBs50m%CR0pKxLa^@U^lV~GgIXEbzwIqau00pL--13QVFZEXuq|FM;62-2AZ+* zc>XpuGEGvBhHwyw;|5te6y>YMA1GIVwtFH6Hiraz^Qsh#^D!$;alUQ!QZJfddB<*a zB|FY7oc@6N0E8$;g$Ldl;WL;(R;($|8wg$;H@2_?lP7*tp5z%d6vH)ztv*l&P#NIv zu|zsXWGo&Lir|SeEcBS|4WQ*{|Bm;@W7#**?ZPPcG~^#bW$%%FVIJt(l7QWxcT8(| zc={{v!-Q0>#Ry57ot$DSm$1o5Ft6qzSLbP5Ju^DDa%lc4J&P=GI^r@H2x#uv8?<B~;!^$o7-=>zK|E9U6Y4nbxc2ZP6>1}3e;3sdvMhvGODMA<=NG&49MzL(PZ+hg%5Wmh^cW91AQzh_->eyw)hn# z`A@E(HOq;#GMM><8zhEU!7QjE!6nID26e5){E*mVg^_*FN1PPOzE^=igNmuGC?$GUEOhd@?uND`C z`^V@hurIP*Lvktfr%#{f4yISV!_;o(@n$O~vC!=hm^}r_wcef;;H2;VJv#JrN3t3F zPs0;LF2%0I&VMPD8)0@tLKRLc6z!$F`)qT!;WCmSQVpxBkL5|Vj(`_YhC~XErd%R- zQX+-yt*J-?Pm9`I1OE*J5GjaXU`QgDpYORD7Ok$K6+iw2W=sKA5;|fDNl<2e&i+V} zdBdDJGAG|j%RUWk&6!N%tV3y1`xQF^{~HwXMYbEjd6P51Q-NrOEL-i#1M<^TbFc&~ zIafH$e$`lbkx_mDed4N`&rsWBmpc-XIyI!DIX5fAxbYy6x`%4|n=RM3*9H(TPc=N2 z@;w7AaW@8ZFC(La<~^{w_Lk@x?}keEVYnh27PFaQ_KSDuMs7pH)A{Uuaq@3YQFrr% zS6nkKFOHm}7a`_+rRZ7#)BJ(AY5@VFe6@H;s#U{1(zw2w5)met`NKKbOPDt<;~8;w zPxXXWyGksR>DfrN(Ht8R6^vkd-_65^2}*?x(Y0^D_6)V=rR)0jnpJfgI}$OH zsgK?<#G)xc4J97m&!8Fto%^29$-(%SeLC#8)cZ<2RdMBLokN9FRf@g{Y&Ff@z^x7l zA^tRLkHd4RGgzrgqCEI1ker}3D*vV{+hi;PFQkw^+S>z{Km#lLs1mXkM%6eZ`TtD3 z`$SxhUF<$ftcF8v(c*1T_?I?NP+9memvrlGRH|rlV1|P>A5|6p0g+94F!+_&z@t$B z)$#a?32oRaxQC1(S05*y71U|^A=^Xo{j3xWEgyItlP$}_{DyE_ikLxDbD7z`ih_4X6T!|K*j3Zbwr3)-Vq@&#WRM7Q5`JAh7VX0qz8?e7YqFgrlLbI`vX=Z z4-xcgXs^k7Jk|RsKU<3J=Rm#Jb0{;tw6tT>ZF6!DpBo;qjQx8b|!d$s0@ce;CuS)Gn$VKfl2ofI7uP_**MK>nH zVi9|xL+rX5(#LjSD1O8&<6<&4bDSlwW&MlfaIlIKwZc`GBc=v^rdCyB!MKj0f#=u~ zNgey>e}$t4ZU4xd+nsbv@xSPV;RexVXla8}o6oL+ZcDavC?`brJ;%@2_~mbTnNot# z)pYZKc+$Eyr-dc=$~LAu?s4eMxUDUGZu&)voGwqu^wEiQWVNz_SZ&8aLOOI-Wd1yt zv{S_{Ku;x?UkB{n)5I!=Ux8ShP(jF^M58Av4DfWo)kk?a!tgq@QqgKf@&;b7fy%)9 zbs)yC)O=5sL^X(LoCpxDi5;TV+8C1d$BE(*dnZ&Lf@78G)34QjmSl;P*SeWtr<1T( z{f3nPpfy_P>s{v;XLA#hxQ=w<^rVyCC(LS{qw};M-1i}Au>5!v;QNX6riq*0?KsMA z`lvGMP;^1+s_s~8qG4R~Vd7=GZiG^$=S%+Jq>YG> z2H2K{#Rq6934^=plbIO*R)!#U7IwFHo1D>*a$QWlBnnw~Y}yQGD3-JU1+T%mHK^o+ z@ocu-L(bUp++G(IFxv#vCZf#h^T*|4!^S#)l5%RjF zSZXM)jU4C4nlN6qY!D=!(b;6LA*w}N1sLlhNMU=& zBADkQZX3z6@GUWLC#B9#a)vpT{Bzimr?`x1;JToSM3PGT%oi@{AmdpIoyAKp&*nrf zu=V<^3FLrh=?vo>pRkHcDUKvgtyK-Ez#Ryovc(by-b@!&=HKr7k@rvzOmJSK> zSVwl0uMce-KY{>S$>CyTrD~P6|9=JeUltZcox%363U+A0J~g0y0gqho4MX&8e3X0b zcvE0u%FSNrxtS8ztKRu;eC!}X8Nnvu&n5}3Wmn#%j6ZO5d-n*!#9>h=uDTVFGZNx_ zqOu_<4DZO5bi$v=iTpjDQ64{*LSKZ-Zqg|ScDh!7eL}%TIw@YX2)SYI6!`RrNB2Y} z7k{KzNN?vRc32q(IFDo@L6~v%6(4%D9VtwF^oU;(Qv>5CEp^o}`m;aFlqb&gnFr3Z z6^bruUOx{fH`&QpATu(j9k8+-YZzll6lHF-~Fi2&Sg@6ah#iq=*oRzpJED0 zeD7!!XestAYs&j_nYd-G_YdMep15+)n`RNbiRe{1VQmRZ2EpP^e05YL^Qz(=u`~7V zkg@~KtS<^kf9N!`fTYwvX@qYe!@-CbHN6jImA`EO%3KKKvdGvCZyDA z*(=^`(5Fv$e*th;FQKgNBAhLOT`RqNHsr<)*m+r3x&UE<;U0*F?^t~4)6k)@TI~cz zC3Qo6sf`|L3Lq8??H?4)&Zk9+x%G^BUVv_CM=-Dn@Mvbh5eLJmEO$)vR=p27)xqW2_w;(%iwVmAJl8^ z{~qC@%)2V);a%u&vwmkDf+P~&H`ZE=!BqVja5W&^7?7N`wAH!*OCPAJjRuWrX4*e~n!o$~`e1y@gW~!#;;u^PrZIZy zOz)lt(K??{y00*=h(vMb^m|@)qHe{P?w}Pdk*6*vJcT7|x-BnsPv!Dk9!8y5G}~q| zW%9iTTBZ!dZ?dKR8Nd}oo2t;gDaif(>ck3CzQGLF*@NsoBABlQ>rn$YiJNsAltb_k<<{g?r6=g1=-fvSR&O z%5hCcP{Fh<6v|Wp`|?pz1P1zClp1ww1(gTVd|zx%cSl*xBZf3xt-scr_F}rjf~`~t*A(}lsUYe%*PpH4q*mfBj$cy!w9uB?Pky&9ODEyH_R=uF z8?xU>QEMO9I+K9lo2k!vCBKmqvkh5}`5l%YiLWXi8%meYna`t>tWGHtqtK!0K9C>cQkiCS~vP2iWAXYS;FR~ zR7gdY^p0ArmF}(1J+AZm6D%8LKq+0c*zy19p4fl=19Yw0f7C|*_QGi`1Z`xbODdrK z2rgArEW9oXq^dSvveF_tc_Hr>tnj>2-U$FFQqzEIT#?P}1(7K%#vWdD?m@x3o7ba~ z%T8e%oO8D0F)i!D)lx+mpsbGQLQKzeKVh^nu$YTf*$@|C z3YN!A#laGUH!AH9GDj-Akak3BOHvoUhcHnwwLiC@Ggf3L<$F_`;y&Rpi8|XpT#%CJ z4R{hqQz@CVIxu$65YDFTi?HFn7*({VPkOz^R)!n}U7gN}@DryXM@=N>vb6WHUE_DIf0uo+4<*y6clI%_54g3t2|t`~yd#t#o3Gq}pl*Sqw;6xh%OWKaIOQ32 z`gPT5JktIJwA_Gw5qyb2OLi|T-NZ*dH8jHXvZ;a8=zi!HPHmC`f0~*l5qHljCJ4u) zv$h`?RH;xq>Zm+1@VRQy>}dB1fb3qZa)6JW0-tu^5^7#eD73yJnX)$1dd|+!Qf< zjM^x-T5{Yb5?73g7?^wR9=UjL92X_BfH}^jeMZ^+zl8f=^idjnH>O?Y&oR13;m<*! zW9&g!m9I#BdZCEr2_)V9DI38hM1{(G9~dS<4faL8?L2l!5q?wf;6}M`mV_qXudj>w z0khS6rk!z4lXxAh$J}}3a;^K|A5g=6P9R1P!NM~J!{(#-&0or9lj9ldAj7B_J)D|! zvE>4;ROcs^&el~vq~Qp`=)u|u%PVQsox8*9L1N!i0@UD*RE=cSoPXan5;ecn5!T#{R#V_w15Du&|fk=mdd3V(%VselfeHyU7skmTrdNZmGJ%hF0^esy+Hzp z(ursT{q{x}E8QECY#r0b+X^abSwLWwwNee3iX$6WRF_2j-E1|@u0xl3R}DCo6ljP; z%jKj71R?~h)b=nvaR;z;2Ezx`(bkg^1jg1P(JjHVCww)L1*CM?mK4ds3cypQKaDs+ z%^5wvokPt9op00`}K8-uv_PGo}PqeqhL>R)xgtJJngocT=K8d)8V!5uj! z+X!KIVL*MBJ}II1A3b!wL0ZbqfBjw`YqZ~ct~GogRGP6mHjc!J_^O|_?0uzgYlcRU zwRc__mk6I~*7%0FeN?cgZ*SI}f7*bULK%IWHq@r~vmdR$A9h52J<0Og&XB%|MfD`3 zEw(9U>O7L`TX@#I$GK7IB0E-zCcGv@&YCylspuvcdIYwh!jo+)_-l-lh_wXeHujF5 zX7i4^Raz4&op1!lU<|1S*7(pLzoXD1eWLeThQsNiUa&nP&K=grzJvJVK<`k6LT%)7 z#aPhxm(q0iBNAH?evBa4Dj&F-iln#$;$zrpH{9gg3R18s439waC|wpfewjJ=(_~Z zlXsXTiA*(C5a$TX*9eY|-S~B8u9RYE9^)RVHV+SOyat1Vqj!{aaK?AOgD=_zd3jTG z?SZ{@t{hK`u9n5KmI6$hr7Qey?%VDH=7~#8hmb*~C63Gs?bESe)%-1&+NAHpwbFJw z=lz#e19pgS2~aCb-bd2@C!L>@`3_>s%L5jskbc28#53NPJ#S4{8fGn z!?EpHTrRS@dk?6P-GPb)@UrWj&lj!x@I#-QWSABl>>VxQMp2bdeh3O4%)#LDl2HoW zSV$f+6Wez;fgBBj7tTVTN{4f`c0AoH!{GyFv-na6vT*+%i;w4-ktC4>`qy{xz;-m` zuzL2kl5h)v&vW~p#*tF83z#Jdp>=$Fi4#PQP|y=9nd0dpB#s;Ik?y0A@I2g+7cu%# zxL8(A)k8d&kT2iLEq^%_zq2^m;>N9;1gbo~RUa&}%)&SB(U94Wkgzd+VVNxv75O|6 zh9Mgziip3nxji>{{ptS*XV4<4N z*tf(QVg^8GZuu0Z;zhKNanjDqXCN5kL%wEs9)AFYGXZlvB#k`BPxR_1C!f?v-3X#0l5 z6*(^JL@Vk-e$7T7FATdFE(~Q zs(q1*B|f6v`i7)<|EBv|mo&{~Sd*j-pOczq3#!vG+3pcUz|t zI&=P@n>Bi)y2sv!7wxCg_g|OJ9Z?>r)qs=f`&-oYmg*fbu@nkSueCqXv~`DPA-RAy zG_ABdJQ!^%AOH0UBgfGHNK5L%FY9u!MxTSC_atI-SPT)uW}@&3N|?z5?B6%5Fys*8 zYhFm=#;+Zet{<`%`BkgXsch&;yy;NPQb4jCHjZVZ&gnAxtk@QsWEajUfGPL8ix&j8 zPO~)PyG`h>q!^zyw{yE+ytteeYOxLd%xK2RNJ9n%!Q&l0n#u2;3xbd88L$PEt2GTV zVFUz$wD}!R22);c*V*ZkEme;WnY+B0k&PC}BIy`Wa^#SX22elyth0r?x5HA%+KE-r z_Z$i!;PTZ~dP6T=RX&@uJLvP@JbrQT@0(Zzy3<{@5Rk>V{C5@r`ujlZH+-}D#cO8M z94Ebr*^DWQRPpi4KN@45xt;5({FMU+!_1+yZW@QS)}5V|yq^m|(L-dmK~VFcZ(nkk z>S7(UQ*peL>fSW7k`g_^wP19JxQgS4tsg^Vm<)Ssd`l7E)Ht7s@H^`=7{rsZea0Eh z;Bf18kyk91qB3sYdEHFSC==KBWH1q906DvuK5ZnRf%emSLyozT!$B?smsDO(S@XaE zYaJc)`oRNE^$zDV@s53MZWyM{J)Rm$mamG9_329rG>^YM;Bn~IqHnwFM+FB-U${tL zWL~uVnQl8<=Di!$KajG;)_svP=pQoYb0*;Rs4`r`NZ?0iken(mNt^3`Y%yy%N zNVBcEsCg7}OsZ;*&e~pD#s3EMTZ`Ur^I~ZLn~T@dFwp~}&#i@6wd;xcNKV5$QC}vq z!Vsgqi7!Ly#*^{y6RF+7^7rl*&6H}hUa5$0gdnlg&Lb~Ng7|TRwh%=XU^SYd$)$xV zMY$2^DBy4#9K^B1$I7+t#)Ct~k~|et7IP^r;sy8!jfZ`!`gAc;3Z@r_)|y!}O$D}1 z&1ZFcZ03?aa+VXOST|Wpx0xj>w^)63HPP}_mS|(-bNwSl%$g~#l>5rWQjbz8k*AUg zXIaSra-hvTzXA_W2U&0Q2XXc6Y1B`|E``l2=``{PQ0yt%)s;X8 zTXw|pB>;F$@Wkx+_#~NV=)M4sT{sYPBlGyuk@^IGFqZ$j0iyc*7i=5wQ{%}`q=oCKt=RzJL1laxcwBH2<^UrCsn!9eo}H% zSc3ej;jg({5pX?rfzpuv9(DJI{`$!cufJ))qrUFqeb_@k(K^4|K~>}99vgAW+j}m% z8&^uye4F&~$|!1t*z4J1P(0?TX=hD(btEPKKqyV!jJ^H$o7dQ8WFZWFI7*s}J#qNV zf6AqX?G{7+JY?%jNvN8+q!euGsv%Ip&?b3~If|v~WF0BDvDcTf9ELVciv;J@yvhrT zKVa8u;M1`nV@BxLo3evX#)B34yatN3yvWdPYcAvB;P_?dxGLkBkGiat`K=iTWIcHG zzjkL7Bdo2pu&rm?pcO)qlF`2TGG-c}P_myjY!gXbQX5ZGSeJf90+RLyy0$Ed@>54# z2qmEZ)fOQCMTI?}3*Fw|OyQX%{^^*OlDXu<#-u~p$=bE5W}JW(Ti7Ok8*T9;0j4H> zxn=ByY0^w;JD@-?^oq3`L?_|WlXg0nCgr?SIHUuA3*;(|TxAM(4&}mv?Ynk2vKt6s zb~1~T$nYAp3MeJ7rXhz?t;6q zal|#OU7%2MnF?}V;pY^eIA z9w!;)yS%-mjEpRY0Xz~PjFg)Xt?nz@TGLj5FmJ}=M;n=$EWVA#j310h-Y@znY8G3B ze~D7t^kU$~hU~EP4)5?`ox7i6b+MG7r#g`H`?MHCmI1NedQCXn9ALl$aykNA#%J`3 zi5nQ?L5oeQPTI+^T*m=L2{X56Gl~`?lYs`h1%x0tjtsZ##yc~Y{SisCm2lhQLURrc z0d9fQ|3Tx2{~EuRrVk{p{s}bEimu<$2wyflqheOD3_&&eam9*Vjx&k7{s;u1Ffee# zjdv{^SMQT_(!hGS_lWb^3<*sVe`QE=0)r2}HmeGL(^1BL5!9uy3WrOBiKo!~JTcfT zvCMjO^&vdo5~}5FZ6bEEv7U5{f&L%k1k_co$t3(rm@Nf0S4n`iAU`eOC29Nr=ABH@apyrZ8-|cG8NP$G)4K z`Y4*)aQM9C)idIz@{yxaiBoaZm5=nus1utx;yHYu$PmTyn?tqHHvM8DJ-79H(7Vsy zAO;sW4aq+x`X=Ei#o3Zn%LPtUv8L?}WK7hsQ%};g0RA5{ugPd^6}aJ0{g=~(`mCm| z9OX3HElG`C5wl<%7EpdLQ$hm>1K=gQkq*F)Wz1x3OE2gURlxY2!?327t8RFl0}bxN z)`C?t4|FanoBsjW{%@Mg0fybmhQ`OouaG?BVT1TQs|du{$0u*dX2!{38xmUq^JEys#%lG41m_rxnnZl})tyW(a@;tSw9g^7t(QOK zA_FEMj`peYu&+RwR7@Ka&fE$q&j~VwO%^Hr5G+oW`S39?EK#XUhVcgjR=zTDuC^#? zE>2jHrdTO*qMh+=PPmm)zB>D(hc6zoV}kfsVu$Bownyb25;vBcNU=Wxr?dTshv@<} zFc-^%3`wIQ*yv3Q`nPm=k?wQYWO>+H0+{)*O!tBSN_Ll0eXvmap~k^H>s-=Ax2y0K zhGt)M=eb69?c0bqW~?vZVVLiKG_+#U6nraUs|L>z?(+eqLK)|MUy_d7XRErZ*^{bL zE1!0nsI$b#HNTRJYbbBqP~P>ZI4S>i-Ee!USaR;8o#s(pej;BgeE87-m9XF9Q|F&% zeTw*f{pWiyz1NDy(y8Q9w*{83z^-KS@9H{HX#uzW%a`3}{P zNfGJtIxn~#tycwSz;8)BgF{Zos@9eU(RRP{+plz2+m61=(_%s+Z+YHF8GD&6=6JqT zJRD~nNpakt(@-wf1JN>mGBc@uPO(6gckRmI83seArVabjGHOB%)5j$njXxpG2Cw)e z0aQWxf#l29IC%t?xftChX*-$uUV@z|BKW!83fnpS{6Mfw51;F|;1!q+6IRB@@l3(M zcR;=xEHb^PZIA9MAXqE?<(Ly}t$LShM;jyS`__mWjnrp(Xj+sN6B=&CcTejM-kf@(pGgy^==y z_C9i%wJMlUS}89c>x}9fExu@D1+Tz($t`L8^j-=zG^-ieDjD?F7+)3R^O`+WOO z3F6H4qLqo1$DNACfZ};60->%xm+bFjotwO9*}oHS!j)j(KL}%d-2CzOP2Rc`BUL{c zzf6J?gi$>2->I)WP+58W9MSRW3-O={@ZVAyqQg_tGL3f)(xK&psq<*>v6VX4`*7@Q zR`82BRi8@IdHbK09~rV~Ps^l+#_d6_-q^ZPbHdntkl|%QX+ENQk!%srVWoYtsqRrN z0Tr_Yw@XBa9OV@qg|ennD%)cRC$kc}Ktlye-zd?SR?%;s3V`Ew`Wf`d&{0+dD#;!3 zRj;8aoG}yN%JR;Unu-+Wqt%bkx`T0`-Pg&LwxSELN}RMD8)`6{iTZo0XC z$WpjiEzbmepJEg!4|noC#%CRp_2 z9>z`ycu^TLF=2i7;y(?6*5;0Yyd!uPs@lfvv$3XXj~8D7dYUI#()4G%1>=(*lQ%{I zQNgosZn!+&B~vdjkC7&?Ez?dinEK$khhWJ%Vym+j-y6IRv=kSKX#;ydat-}v`Q)jx zQpOJ9Y;+m%h_2>#wFtv#ExQSh^^p4Ws5H>7e7Ky#&;E1ii(7-b zX|xNG-^J|q{w_5RJU-fatk&*L(EYN874J=WvJ>sZJcj`v+P=-l`&4k%V(m%4Fv#OE zi%S(?{Pp3?j8#7sSP9l6%0x*N-_6N^!lFX(jK8(y#x(UzGzpC*lvoAp*Egt|^=}Ue zlW?FB|9)n+kEA5?!ge4n%B06dF_dr4J{t*p=iF~*l?!jv7Q7nLcqij(1y@&^!%EqJ z*wJIn9Sh2xc1Di+PNxNMOPkgC&vPCF1zZcZV1>iOMF!tQ|4&DNN1!ols~HL7By+1;(|~jn&s#m%CxoueE?snCbj*`HwJ4YI zQvP>xC7Cmn`QAW2U*sot2P@a#od`^C$7MPSiL=lA8@U659g-NvuDea~n8T5E9~C*i z#Vg?kkJ&0!NbTUs7;DLl+JM%cC_JeB_=$WqkWR@)h(!{2PA*g(Ritf}eEj{C6MLAghK~5f}a>D2DJtOn-U$N0C zVC4ak$fs1t<)a6`t^Hkze8m4QAr}KH20Uqn^IG0&ByfTPE9Q&F6{aX$BJ|!&ji)Hg zrSe+|#!NY!!tD=-dq(o=Q^b*U-oa|M5wPiyF&q{G>gKW*ovMz_3v*IIOK$XiEg zShFkYSdUKMZ{T|Wd%ARW?#KK0mWi`N*Kenn#v(=wZUG59B9iX4-qVj?1q|E3UwNaO zUFrSs{>-&dkUv(jJ>JBt`0dBLgy`eBUW4<}q81J_QC}(OQ713yeJ%%+=vz;={7Xk=G4N6=`}V z-k_Im3wM4mU@R}vvGgEsojah`uJt zwCM+9Kv@^gglTREXSCX2b`WuBzY@Pj)R95H zZB%ziIhde@6ti#a%SYAi?Fj5H%g#6+$!-xUKXu&>co8FpU)r8CW%U0-!gl+=kbvTU zwRiM4B+PhWw@Br-?7lPn0l}Ip@iN-^GW5F7R?1W%a80;tcd##J3wbbQ5|RS})&zZnR(24#WZvXRqJ*_8bb-qL2>w_s4L@+vU5_@};xzDVy`q+!9rKquXx;hj*k}{`^C{&)c`Z z!2d|OZ4^simbdWNUWMA1>9@}&y7~2750|=a_h%X2{UYs`Mh|nXkeoh$E_bPwt?xGAOf1NujeJ$-b z@~3@e?v&U_50#7;usZIuxyi$&@aAD=xeu^SR(HPzKMR-~HP zU>Z?+g_9>|PU~bP`w6GVU@DV91w>$Q5F+^PM}2FM?A$oD-21K{xkQ97qA`?JRXNNN z8owntL;>_YtZrt_(Rp%uSRaDH)LW$4r32Vf{|On_|3b!gKopU<#NQ=%gp6U34!lHU zJh1eruMB|G5~>kSV_g|-QvD=%d&buslG@Hu%pgPh{Op?+;b#WX4iarSmSA#`ddWaT zYP=k0|4{*5@q4|H}w{`3z0h*7W z&}hs}(Pi2f;eVd;?;BqGWh&g=1FoGTN|jmtjdjJ!1Ya!-T0O$f^6#g}ycweRKPn#9 zU`)z&Y&<(|zuNY=pZU_?e(+_wS3iMadT!gx!i&by9j zcuVP;DrdWF&PRDF?iZvbM;wX#Ct$Jnw-gm0rBTIe?dN*(sN=7(s8e2pyRn8sYvls3 zJ5<_wf0piDQtWfT!{qfc9-G}OFM(a@>!V|l_M1ydVpfAwRRb~q6M+)8ic&hi<*i71 zF@KKM{*qhQset3Io7e;K>(;ERXe^1o3t?_7vbE#n@qzAb^HFUJMa- zX5aHppj;cYxJV^6v5V#(fK5Ls1sER^wNU4~j-v{gr)j_((a~H|D7-OksN3T>k05W~}8IoHY=_?t^7sP}{!`Wu&u}#t=Ag>R4 zj!HZRIwYtUOVZHJn+_d`+np_&n8%Zp;#L~;X1U{}d%6i}|3@?Iwy=oYEWV^E@BJM6 z-z$qD!V|KQ*AO%KP3*{T_X$HsvOFcfWq`?m?Eos-wT?TS&j`Vpez0pw$q8j#;CX{Z z{(O+G>b;YS{FUiuOmBu*RdrPKS@~xV|XoUdZl9M6RM&$ac+VBPO$!d}g zb?Y!Bp8^+-PC;HXc^<*$6 z`Pk-}vFa-w>J|KO@)U!ef4!Ko`dE9_zFGAPwGaMTJLII7Eo86D`sVmH_!NCCbmo&? z;ed&O%+vX)OjFIH|2JhG+8?7Y9_~HiPk%Yjlq+XtqU+}eCVoq0KUIZ1{F}(X&$?%= zoXLMG9QJte@VnjTe)a&1an*>;AX@VK2w7rHiidOCB_Y{2Up z(t4`C;&txtCKkf}bkSYZ66k%}%o}PXzT$p3cxQgyGqp{ByNkW@aC!GVXzONm8C3N+ zw!Qku%-i-t((1PHWhcp!+MXWv4)c!VQWuS|awUWg&@R-2-R=53F22eVF2<4_uGdu? z2JR)y-1q9xw?io4w&>9cIB6Wmtl6VTY+Xc7H72k;yh?8%FOAB7L5Gl8Yk|7rkb!*u z4Y=O%Gc}Mkisvc<*EKq0beS^uQ>d&lb*h>OiE@4#?2`$ftLV;!j0qOF2`D5!M+SCs z@!0G_mU=kR1v5#O%)V5K;n+}sAbztDfb|b4*K&1RhxvY~kCz+i}fj5E7gGriQSf4$bKyHX716%~7;9RJmAukn9 zcdm8=;lF-NP>+p)S0VV4{LNcfF9HH>#4)$H>ux^L_WREN33&gdG z>&#j?n7dQqLdyQ73GKtTuC;Us&4N8X+I5whDii@+J2{M`)KDwRrUEe$MY9vOj=s!q z^;tzejoyWBm8kEVFPl73t*ft|tdRq67%SP117EsXm`aAQ?_NpYtt7U2@gIwB{VI68 zLB4pnM0b0vFNz91nm>E`Gxh7~GEm=5%s1%bdZ)w*-gDmx&-lLeP&(60C-xZgL+bVq zKD_GZQf(YuKHLvcds$H+0M+x9%;hXYUI`+tqfGC{jv z+PArt6Q$~sU;K~`xY+PDeXP-@{qfW`bNn#uhC1_)QS`U6CkAEb=iFpl|vLW{z z+g4j^Kz7@h#&`uNnhg@BPO13Sc8`Ecxzft^_XFzvd$BJED?f8QN zXkbOddf}A7t?9gH(8I;bI)dt4q=!H=)=qY_ZU|!s;T_e=scFAUmS^m+m`9*zjXSQl zhKGBDVZXb`7m z(1J?|^r(`^^fZm-ah0CaK%JKmVsKSFI%=Dq>*GnTW}@N$;BG_MlqY(W=ufRNYmd2s z|GhdHbnVFSMVu~Z(Iw%i2;*1BwE~m4p$=^q>fPV5ILFzbQvGr1#n$i6N51Ks8-Bqp94-4%s7WMzYY zQB2tvif%X%jN6odrOr$7L^mXD=)xe+(cs$Hov`=7;Sa8^*%b5I%#WBJ?CvXnXu1m2v}bY{fi%ZRpRNN z!!B{noM;yEHyLT>w`kJ7by{?vJN@}L*>4%X`p4B<&F?h3Gn?(_VKX|sm5)`SUiT5G z!m}E3eO(r6cX8Y%>gHAzTF7y1q8R!hRq3?v+qRJkc?8_^N?#*3z^)a zf)Dn3LyrZ`FJqjZ)pp-sz;%5q6tbfHaKd~@YHIaH@a3|s zno+Y&Y>@{>Ls1Ff8nlu1a;io%_yGvl25<{hVe6zQyWtt8xY!F9tCLy8$x4>S8NH3{ zOoCkPsVNdMbv$UtT(kp3-ruyLNnsidnaL6BqsN`x~)o(K7`q0L6o$NtGi? z-`f>{wfq;wJ$3!)U7JNNP>l*BQ-M#C|NqU^{@PEUuDn~CSk%2C%pbk&eo$S=1Kq4? zVB9U+G4n`~ff+~JKrmXOB3Xf68Zfz=lup4VN1Zm0nbnW8xmH9hNX1NRBdnostn|#r zW<3HJGvf662WxMY8<{rS!povsB6~9S@;U>Hp-iS9lwf1Vj&|nTsv6iEh&S=_WPEgU zg}gRJnWz&Si@c0&L@hfr-N#TspN7vqnzCc_ARVoTR3UeoN){E974$gvcy#ylk0x{% zA7v=zdDMKnOdQH0lYTKg(c6C2@$4ZdBXpg4d-Z(6llW;*J>zkymi}6s?zUI`$~4%^ zdu#07?aaOAWs6(=bFc1DDX!F6ezx}q=J$m&`N4ZqtD);7yshqA5~~*z^Z}=nTbpO) zTWoOYkHDR!J>Hg6QO{<6KaYpcG0IQB>c<|!GU!5&78(yE$^Q*S z&`Ug>$h4g_mTz5Qx8E<72^<|uKQ-o<(`h_x1)Ak$*ScM{w53;?QdbN$NN>dR1ZUTM zs(6HIA${gopz@?yJX+!vLXqdc4JP{0U8(0QB9>moImm*Rr$xz^vXAs4Bc(WJHA$Wy z3N)?rOHmsjn&=#9O3KviZ?d>3MST+D=%pXprYGX=` zrV%~lf&KS?Gzvwp9oqh5RyPx`4i5igR=(&hV*B3~GHhG&Jb!R+A5!O&rVnNWFJP{LLx+>2C5_SI`-{KinyezGWi=dj>EbH-}8#)o&!6Vl7Bc7janOv58s0 zD-2U!3Igeb2~@;n<}mFT*>CU(y>w$0|AJc0>Joz)>C(Wl6^_r^r4}`9p^u#lhz86( z5BpG%=1z)kqOA6duAfXKP#6ABsy3D>nL@4V46AzmJri{A_g^bHa3=UyG{qYQQcnqh ztrY7%5}5GS8iTRCV#Qn;>nCX;t&*n8i>&s@b%lr7$dFCDXzq`i_N&K+BhB_l>qHE6-+mtT-mDt&NmobVR!hm%V z0jeh1xJ10x3p6B1t_D#-h&m_*fu(KbN3}b*tx|8b*y_(uotC*T=%en>$d9g_`)qHnUYz7aK9LMfFuySD$at36Kw!*7Rw4zKVbq~FtmM77!6{y)` z)|U}hw%4`TaIL7XP3N?x2z+C}`&>l6S=DOyZJX!=P|H3)v^m4gK&CLQd?Kr+Z`DF*EUWdvC2x_aiiB%!p=< z>U%^I1B|%R?6#}O?qBoAM#MXjy!PsV9c8nC?L_lnCwduFPUY#YdD{uX?#QH__42Za z(>(Zz4ZUr1%KUP7bN@#HUl|S;bAn_evM?fZ)Y`!&kcLf}+NG^j+n zRJYqs&Uli1@nk}_0{7!h3tqL>AvA|Nlc^e6h zu$!I6Ne}IO&Cq3anAXJv##1p!sUQ*XL4$U8JdQ(OgN^4oEv-0)YLrFR<(tw1QtazI zW}7@lfrL^D(MYstfdBzmW<`w;dKoI>iyOwGmr!vCV7@&nO#bE%5e6O^(Sw9lxq(Wm zV8SV>d#yp{PygpdJZh)|9R?IL`I zF2~tcH1-P}pnP%G%*LL{;uC@W7y>d?n=U4x5{3XC;eCKZS~mdh8?!EaaN*H#7EESW zdf|g#nQmykVkEmS1I(NwpTsh)9a#{?h!{{#(&-P zxM`4H%{5FQ7}YvZ4fr64gu`$lmv=8dlY3ht~xjNwO<)MG6vn!p#Vb)ftP)gwr^^ z^eJE!uxQ@?_XhMNrbUhx>p7H6|7EvLCk_SRQn~4%cb4jgkG(VMqcVY8F-%1d4)tDq zk5pJTW_BInOT+>fpg!-CY{YEwGkSo&xUz${7i<0R6as1H5=9R{vjE4Al85MPun4Xz zB$u?(9?jl>Se_k(qR7p12t#t)iI*fQg$7)*kuJro-uNZni$;dx^_Zt*zEeZgWh#bcd5hliFytHi;l;O{MML*_&bkOGXq(bnOvbD_J;yda>kp0x)@6;i5xch7}5HgAR3b8E$87Zs)6U19~z*x5QP-_^BM?8F1 zCfUyNm`6ac198o#d`C<1WF&4AXUpdKE`8Knf;X#(qcXTA!Z6;m`;Szm%!7nE3RpZX z(1)y*yZP;GhTb(I+uc`h5|^&t_ry4}%Q#&4ngty!?CqFKTun%lJ&wR-e2$9RpLWXW zn=a>gO#`pXH}Q|=F9EYf%Dne*<@TEy_&x6BJ$Kc}S&+_k)l5ly$l*hV!S+qMNY2&e z#^w3V*!qXUhiv@(`x!WK@RQxjixOY5yfY3@;wp#X5Lebxe3Qy8wM+)cjv< zXiZD;h)}x7k6WyNJ%KZ=QGMNnErm2J!*ut(J;^gx2v_Y zMk*utP|o5~Lt!d1+nH54ZNeH|T#$!by?}T|p-KETQJ6!-grfB^H`+j7l3~2g$E7B* z&(*@t^7L5A)Dxho?|S`QAQ$h57>j{7lh{DD>TD>u7Y)|4LLC10EM_!O&ber8lA}YH zYbwGyOh}KG;k~{U=)zP_mU4?OO!_b~#Hsutx!Q8U<+T4~i09^iJs8|&z}OhMfXrN* z^na@A{+WkN?(OiBY&(<3RGUz;e{lGg)Om)gdGO^{+%r#mU9@p)#bW$6VUDXfd(FF# zzblYc(leE?N;HjfFoN>%1?ilVDm}||X3A|P8)M{*(eUYOk#p(Bu>WJ}1Twa1_)wNdKI zZNF_XjJdFo(wIQ>21*=fHF8Eb8|wS`PgL?6C8{n-uwqg_@*2>O5xYDj-$`emReg6^ z>1+y_n|W557koTX`{aJ1=Ou+`x-Ux%xtW+bbK2V5S&0ATqa)SU()1K%koX`2A5*Lh z6`_jMgfNASJhY!hjJ2P|!1rYuF$G6w*r*RPiMPM*8mY%c1SQfT90R@;7ZB=+SSVz! zuWBq+hI?4xWk%BkA=lm?D7iD~=>YS}Yq}&#lnWBUfBdXPZ45FTjBEZxd|8Q4iEd_P zUBE8atJt`tEiQq=zLBxJ#N*Je%h@nNr`5(I_qQZItTP$)ACP27>)iJstc>0YviaqA; z5T2)=DPVv3b~BsUO#a1>Z`B(;|Kb_HPYluhfBM613($;Gy{uq(O`HwF8z_n07b-NV-lKm2tKX?TX8X%UNScZEngg8^-#$)y1KI=Y z38+wx&}fxi=`)3rHdm}KD<#qeXpVcSN!+`?iEC@NnZ|%Ve>iMEdqn0plVzd8{alZa z>18h4%>%dVH;e8w(9iyiO6btI=U!j^kh(no+b);cckjAp{^~I$UpxdJ4{h!qft+zDk8#j$Dx0p)kEjfT-=6(3- zYEzcw2Ne|3H*sBylb-KGWYF6pw>}Vof1G0x$%I{~xg8Y9psx0|u+xEa1>aO7yRFpv z-!)5T`h9_$^)KGCmQw=^!muj2imfyeDT9}Q;XwPJp6ET{d`k9d%6-*LI>(C;jnk&46@q7)y*vE##C{TlLeA@F;}BzxXlOB-#P&jn8wU1I&V+d!`6y=Y{-yLi|N;h3_ zRSB))De+R@dw`^gD2q!jw5pRyOB0~Us~oA$i#|!jvINXT{d>zPg?8JWIFivCJs=+S zOC~-ZlQ&>vNM)5PcP(|W^@3EiYasN|aAGJV69-*?6DKWBwv=s#XJBod^m%wmM0~=} z4AXf!IVy+L&N?9+{evBT_26r+3!rKhmW^sSxx-iZBBebjaz;SO;&YAlIV>O+d`M2l zX>yYM+sEsxw%2a0t}Cj`4KKz152d5p7#gUa7h$E~Dc=hb|Aw#(w~h=kUY#G-bwM6~ z@dbGC0IMuNZoGELL^&cL0uF+kw}*})Nva-eCad~KUE(;#^MA;q2E+k%O%+$4$CtTy#}Ioz%CHcLzvQM z0bpfvJuDYPGg)A}KiN$$c_n>9ji?;RF@7a(aS%(fT_w#Otuf-(4wLB#305`{F7K2H z4qBK0y>j<;Ch%-jx4$fr7xKa2x2Zn7#qH|6I7$O?}Tu-e1O9OiErs4K@wo=#Di@ zGYNB*mz+Frf&(fMj(rqOxlH3fpA%x1JA%kOkX{$1AYfjTaCwBq@CR$sHF9WL!9y~) z-6sb!S`Ym*J_TXta#3(XAJxx|zl@aVWZlfQ>R5Sp#;|De8GI%7prM|K1G2bsFh`gpJ`NaXBRw&S2b=Je1TTNWTx`#g z3nnQ9_Mwo#X2s_$*$F1Z0{z8x4Z!El87%~X`xJ3sr8QU*j5roQ_VL7j<#yHMWa5c_uDuAeK^T06T16midXD5q1 z&!v>hDMI2yH9U|XH3EHKoS-OB0D9E+u;z~)2C-clv3tVG)VB4--Oy{8hlCr-Tlmy! zqqMm{BEK%*HGY?RREijbGJcFz2#qaA&3MfCsc6ts_=~Fl!o=l&%$_97`un zDphW%4U+0M>6)F??~Z1k6AIQZ^7QD&qm^1UEl|xaX;f2vG5rQ_m6~5bCu)xwG+6gW z4TB4FqM;DiRddo+qg)5BsT9EoC9A&ZB$(78@2!%n^z@DIgDF{KSO&!%m=0#vs9OjZ z_@a$bsKuz(zEtf}M`QX-;{lsS>N)k1DycQMN?E}LY^ZJl*kQlxX>2+mkt-eu{7cxh z*z3PkVetptwKncJsU;@XnEL2S&8*b%>iMWbW@44a>h5}(N(p3Wu@X)q6+(CQa4*}j zR4VRKaKTJ4SzMkol)ZK>{*z8ZqLz?ts!xZc_!auH8T(;QbqDeIS=PMm_vD@9P z9xt`m?J{fqngs934TrSnrCF{BPT04i#-%*#ejm!+p7mC}3m-cZe4uqd3paUnrzvcv z1@jOgw0Hv$d#h<}bH2x5fnJIxN`)mLvs#zuwTbx+`ZdOqgc!?KEdb9QmAPQokMNPDOwpL4C=Kcs12vfY-X9 zn(A(NVqZSyoB4Rdw`igfkk%4?AMbq}41|L9=Uc3DC4won0Y-31g&Fx@rP})7d39!# z0ZfcBqmTWN7sv+UGP4HF39mvlv9EAM9LNbp;ubX3zYT?z5>BerFB`jainZS2Fk~F! zkm*1KCv6QtUmRdxktXRH2AxvWEeCjsnG-L%6Dtr<+u~MRje{@6c(2raT()#<1$Fs5 z^-G1>Z3&gOPlnPsOo2w+44QErWDYh2kkxW4I50^`GG-66p}sWj-y9Q>D`AnfJteSZ zB1U&YK4HammvC)W`ejBA?hj|Ldd5SJREW#s`!^N8UTp+vCKEuB)L}Dy9Oa1j1ic*L zT+cC6B#fYR9pa&0e;d9__@3YQ)eCU;ta=yQ!W`=j7UHMn1+>RBVshtot#Ua7 zx8DN3#XFngJxXGrbhnM+{3JN3?kaMz;$nh9eQ8y(J~+JD7pCE%HTcf>TN8ONnr%3& zV}$EMV9&B4>l^{lER>KdRgx-zr+Xb$j2dza3rN9V+>h(b>ovRLE!lNci80W~H+x*K z(aS~53XP(`=KwoHp#J8T>I+9zERt>*ON8&p3}g6x4%Vd5;7}d9*xTpa8nlP~*>$AB z8q{L^P&uuyZzn0BZHs~S*U1R7B@eh^>Vsa;{fL*#ZjHg(NT3k*#1L@e4k+NP*-G`m z4eUCmo>mijp}fhbx^UR(EZbJaU;@w@u2Jkx>J-CSDE_tnQ)I!5G(%qi-Rt)UHu)g3 z-MJHxhXF$9@@Ht2e2_|p=o)5Tq_SsYE8l!(&Jyd~V7}s{a;MJKd{rbx{y%HFkWVKt z8QI^+-($74)tkQ&L$&`-&-S!#V(CWSVeZe2G$EU>b#v|p_$5B<@x8m4#lO;TCWS~5 zNnvhN@rlP3Q>&^GyiQaaeupWd1aMWF^vUeus_aSVTOvhyE$#X~ONzM$c_=ymAaa3& zZ7|d|LC$(uYtT$3RU7VtI>h(m*QYF#lX{u9aeW?hU>|0*QgZ&U>R^FqtU<4>@KrQs zY$>&^G1j2=HA?CrXJz%a(^`JOd{aA*qf?PIVL-sy+NEw5gzqgdj_6;wEiRS`99;~D zZ)xX~1_l8(L_M4le=Pc|L0@HYKUwXhqP06`L^o&Y$M?%V@?1ZCW#;PIB<&u#)_)9DXI-(*E zU2XB@rjk^;+t_>UCnEC+526iI(Q$f#W}z!U`*tS#mUmG;iR3{Zm9X!#%BNKEp%=NM z!L@pqeWv6Ycf_favqdhH`B*8QjpQQv{VA*Hg$H|l2b1ibbcAcj@iLo0q%V!MIPL8D zrIvqdc?C5ao{!mIC&@Ie8-##S?Bt)3&6o@=%kFr&CO4J4c132J+6pP{l2r?ek0^h^RniK5-vmvAotTE z!{S8M8kIoz1(Q3HwxrlW0gDdpVX?yQ)2}e4WwaVEa*J;!LIUxuZ+&njF$M)*q6raE zGBjU+=mTjav^Z^ET2G-cGVmoxI=K#01Y%308NM7-QU#XcF%-S2 zbDFX7yErM;U^x^sZlXZRuBH7cJXa#1OZM_^vUB^~ZlUMS&k)6#Zgsq&f9V}ymj9yB zosqt~aiy3Rg#kW_1gaS`P#A-BI3;}2V8lX^MV4V+gVa!f)A)I z7(&IT!Q@#&J?v}LgxhD$sBJI_eN<7FjXMN*v7%LiG12GR4;k+iPFxA#3i=hIYq$B; ztduNz%up}lI?$F$K1UwodNO-y#lZMS;)qBUM+vmWoD7Wd1UktcbzshOY4L;d!s=wX z>#2IUDEDCc-GsZ7#H|7xjRt5>R*Q-NbOwV;HOI?7Iilyr^f#Hd3RLl_9>@(M&~n)i z^R1e>t=)=;4(A&vcSI^$pL=M>t(YlWzpk*fS^H}6lah&aNWCyHm{QDJgXp*pQ%-d9e&l~&-RW*R-mGmcvzPmd#ZwnS_+UoUZ{11 zMXz?1ij|tSgN^!Cc+v6P61miL$l0kq(~8>BRb%ta;s3R&~iz`mh%h>uT(&-qwqcmWh*#C9JEt;qsEhB4@|MIN9yH$}&I zNm-M1|259E8FUT8 z>apy%`Nkw~?W~F9Jc zD32+4R;Jp`rz&!C(ls2ToQ3D;~ZLqB6P$x!Fa`=x8nAyJZjMzhgcYG$CXlxGJQ-7m>X=^9^O1Eg($OaX0bD)`n7P0X(k(`Grn zkQc(e5;hSQ~@6T?UCo6w0_pd*qryBN-9b$lbFvrJ0O)S>Cw#wW@KS`V&oH` zMZ93OS0AVnv;XR!eyv{Y&cP!&PHc5a^8l3L=~L0w+@#Ob*z%RQ(ub!L=6dZLi=SgP zVTLpkX1=W6JN)E~#HoqwAcaL&;YJXvVOANCC)qZGEU4jqJKbU`*pX!NJ_b2+__8uR zF7-X)Z>;oxf}IEEBwM4SYFXi89T6_4%)J~I#S>+ulPgt!AVt!sLf>pWUFLM4GX_{7 zO%-6}XRp3U8G>jRV|u2^kI(c?)LOm%>;M=A*2O)19EL>t^&E|qNn8-tp%xTjMWjZ4 zLUB7di43jA<;3pjophGF4!i-@5<)R_-3U`FP@tyeiG!pM*~2$+rop~eR!xA%PrN># z{%MSt_&%}HIBW7~mwp>Tfkm5{A%P>u=`>OQyh#@uGKj=@M0nA7-!+`9MG*g0bkq7G zhG8c)vR%T}=0BhuYc4!`+c+00BY5^joo=uP7DVg$r?TXlg6=7Zu1u$)O3Gy8l6>CZ z%016AcsFoiOf3fe-&+`Qu_RLLhjyhw0Ljr|n$MTIJkzZ=ylvN3M46!BdF_Dn7nfsS zYXpSGW@rWTuU7!?&d#ag61bHAM#f)A6HA=S8GM&{!dE^HsqD&R(xH`CHuvNp*0xeUlGQY907UuOEPq9{rBsBg5da>J+4^tAC z+=q0;6p~uNP1G!`IK4}K>l*B2D-wRb)vkEi{I-NO3o23v$BfU~Qcx=%T6y0SyC@pM79;JwtMlle8=KpQoNvX=0tUyAaUiPjvW?f+WyZy_(@PxEo5pl_@x&5<&j z-SB3=LHhr8wGF<%^MAbAK6`05v0|R+^L#}y{WDF#Owrjl8~@`U3)?WE54j1g z)hA!_9CB+s@rzqu!8lwPH}a#=pt0nFgX*-6 z^MKjN=~$zfLt9ttNf$n2utt#$vU*Ym1n=QTo7c~y3Phr*X!H~#*lfAW{4Q3e_bpNXz1qlcU)0%Gq2C>>T?x=NU(nsvv4B4&W*XNBS9R>X2x>BtEVQ3FhDj9P* z*;>nGdFXrrjX+HdAuAAp_JWO?$B;CTySuZWG}^>A#eTBCpcnuO9uHKEMT*oA8h0Fh zJfBe%O}|{(u4*W^GiViESPTNy_zxs^xv}be$UOwQDy8`N_P%FmJTv7!dHLcO4#bM! z0>evqZi}Jw(hPW`W^^x2|qaYdiktKLaYv{OqNQ0%;FB}P2s zVMz~vOpuXL$ggiEiR$Xo&%4&xFS#5r_W3}Nf#~`gMeK^=7%zU<^vc^8`ugaT8|*M) zS@hZ=8EPtlMdZ=QUVJI}eOc{N%$;OsIh)&|$A=EdApbrvmYo279^L()s}F2o_m!5+ zvG$_i%XzyqrX)5Ov*+(6vF+$rhQG|s;tuzBXQX+ue)wJ+!o)N1D|(Y<!RyK8M11toqF z`ZWMNn;zLHzFGl#76z@1ZTH;2gRFG_YbjeV_BXr1SoC~KpXL?K|2b~Vqz)umcW{t0evTtTD|I-j*I4r7?(rdH!s$a!S?T#uA@$Y*A_Y#=t#HM!!6?DWMk2~OI}5XxVU#o5?(l{c&_wv#ln zsVWjBbaDgfw&mLh4QM0;`9H5@?5e8N@cLG+wwQa1b z%5@LVTf(W9a6rBYcZZ|1EQBP?G<>37rndqamB@d16Q8i4=OuM?I_&S!6}|4B#9Vt((Ex%v6NGU2Mu!U#HQ%w6aASumkG! z!19lTjAmw*Ihk>vjHt_w_D+yVjphZu@yrgh8)$F;V3`B^LM7P>U-AU*onx&<65 zE9&X@3%c{V3)J?U(ZRVMWexnxPv{mC(ZMO^QYJAL;kAIMx07ic4{_ZB8kz+x{ObPs z>%=SFk6x&(e>=L8Rxpu*I9=^kb@ zNqd$xGPs{9*~b5S^?TK53fhQ+mw2QpVXbh9V(?_2{w^!i8>%El_Qn3PqRM+5S)w6j zCl%@UQ4l0x_TRgRtITRLW%NiPGf~rfYmKjYJfCpl)|(ZLj6Zy@4C+uKNrLp}inE&s z1PC(jdh2I$37w~4xpDl?|5l8L))ABJQj_sq)nx`}3EG-&vBT;U)5mAz5}vXGL(}Dm z@GWd12={TNomcr%hz=cDYtOCull569Z%8iT4HtUBrwST-dKw_CqjexhAUe5m5kT=sHPQL=gJ#i1647LC_t*;i4Rs^DBYlNfZS{dM?2LUt z@LPHKOBakeMRB1ovHZIEWAGryEqu*UP#99vVB^F0$Z-3}ltXrRA%YpNPLnj>R7X%4 z1K7wu&T^p)(<1P+=Z!O7EyWOE{B!E1m7paFOB1#_-}1$RMx8+-t$AG!F3XV1T;eN@q;8Pg zQgMrjbjW$g@}{M@)Ynt9l4r=$A?uuSQp9ApUFGt9_pk9Xw%ywnZWO<81T@#xFX(iP zlAU8s&+SB)IP($Ilde%zs3VUybduC)uV4PnTCys=5`#mvB7WJ^=nSe29wu{u^_FC% ztZ093aN>j~EGM9r#-&rxG{g^m0;=$)4;-VEMTGb}ecBs|fK@EsZ0LOaf0E)U!fJFc zjKt@f^^}-dFSBq8SX|>bFfp4l8OI$d%PG_A;nJnv?iPJSv(2#Dfq$5|WYV zxyi-q7k{RPL>c)mIf#f81UU1jQo2Z*`NP4g%xhC!O;3H;y!lK_2#QJpS=LO)pPbP6q3B)CK*2=^F;BU9YN%2nP%+~ zj-HYp5!g;l>_0JZ=*i(0S-=!TXp=L+8`^kD9WLB82}jo-3<4E=okpEXVc~TcIamta zVzPq$6oLt`^hKK5u*#Cq{zZwMqoysfn{R)Lyw89E50oLRXUog$PWhuoADW=KqVyF; zKb9}QOI+)o8&QLaZMBpM*2s@%t0rBK^$Ck3F71yr%6)SL|DL%>Utyi36x`8S>B1cBS2nF?-_kTk-n+`H9{2&yA1* z)9EIp?T+y|mhY^nXux}M0(Jx{?A6a|_#B9dk|w_1fOC-51B|7`kOJp{@s!pQ1zmJd z`C!|}uZ&wQx3y8QHOvwD1=j`cg-MkqH3Wm9s9!0DXA*xMp`KOBoZga8GNKOpWgNu$ zK$G}T7pY|J>$02~fzcY4#607V8yT8+)+i<26xcna1j1%F<9L-2k#13EA>3~OL!eJ7 z-_2E-7!NkJHfL*RI5>rGMR`tsSDFn!qAp3lku;dfdl@Lh?UZj(^~XU&92yC_uU)Lj*)$FZ8h()Z*IGSM^8Ta=*N441Ir#70 zH)o0XWr<6u0Gc19N{Kqeu=Ax8v3J9e6bExSX-`1*g)M6q-l{Lzb+FSNr3pnoRyrZzt%J9Q3&EJWZMk9V1 zr#ydjJ4YUBXKeX#RF^ylb!!Y}lnEvtfF>05tu z8h2Gq2&Coh+S965V&D7Y+J*a55+6B8KTDhz(=EdxV>BTS@h+obP7z1z=En(^)-2o8 zIxxPh@Cb~xMGH_ntLy%TYp>x=5ZfWQhFFemW(_~8se|*QnRL8#aSn@0iJN=iMaHY% z6;*$R6spUCa@l)1@VV%fa>>wW#rFX7?3tO{k*n5~Q8Nu|M~B}nDp1H2;HFSn6Ntiy zinf!{Rd@`@^zogD8Ayz5{_JMbWXiPU43~}x+GJXahkeSV3avmzl8|efVu1jZBG@-9 zW5$#k`9dzyg4iow<@4=Z&5G^sF5_L%_LoEHkI2!us_d0Qd67Qw3^HO7%W#i^taNj7 z_VVqAItJ-jSAib01t?&dq&d()V!DQBzmr2FDLKqco_-0 z29nMt)Z1z33(J+H;ruIJO!SVJ=1W9b9em66)Hy98;)W$Z)RTk+j1QI|&~iRqUby_; zX!p+mM~dLe39sMVZ7b^Fylj(#+54&44-?xR>B>Mna!v7Svbnxmf=`sq)>i*04AUn- z<!K#0!B~R9+!v)1<5L)SYbdv&y2(I!jI3$$ zj@27Z?cx)i5T&-sgqxX>M6CpE+CJN`#b0z!|7#+bIIf0m;#D*q^fUe>Hm$xR2F<1l z$cFN0wX7_yt5j_IhEJh+LPRscT*TK=?7rhbQ05k;DYf-{fZUJoTi$rLpe491k~WME(Xl# z<2|~p5hT%p7$tnWtb-_@QnDwJsJA3W>8=*%ebL?|1oTrlJq^#hB{*e`WPOE0JCOTW zS_6cMzk+}7UiM2yqq4kBUG0*$->k?(GNtinOeF_x>t7sEUA`M4EUqh~et6uQJQ?Nt z{Uq4x`+m+T>x1}@qBSSrcgZ^`K#9)(ke}@)E}aIl|=;l&;UmO;nUK!!PBK z(P1)BEgCdkhBxbS&{Gkf7Y@=cjAM!SBK?I!6ZnxDPFQ##w6R6ezOOE9g(=?cyO2rh zB}`9%f>e0mgjUwH>R`WYg!~LxY{t+hACHsf_ABvg%~^bRs%K1<`QwO^T8k4zGeL)qs z=!-Z%7(#$?8U41HZ%>ny&4snW?kjP(nt?ta>Cg39n;sq&YvdG7iC(5wZ}R+*7!#Vh zct^o=l#TuH8ZyCE)2+ir0g4(Oa3zF%h^l&~+*04FTsPBH8(6q<=bQQQ8CiI&ZTv_SQ<|4lQ0r`ah}JK>pszV+}k!26E=S(j=2 z^%pC0bz+Aj-RtgjY<e&=6H_7U#y62C3=|%BXiBnC!})R!Gi&MP3yK!sC1FSjobi;jn`cI z?jPAAzuO2ui%210W#R8F3zV^?g>QM49WuSz0NHh;Yu#l@!iYMly#bTd$V!$VOR7lz z1Y$A#B3lk1PK!6CkEjU$Bg5{jJ+b2lsb(&glf@WhDWS4v)?Q@CRW7lQM>Zxhu~0{{ zv?CkRz`UT*+#RSLtrx5SZ$ZGvZ{Uv{W+=eB&@i@`(=-4I?f-i6(WSysrGLVp_;eC9xwUM>5&|AGD_dmcO>Q#nA9(bj}FS+*IdWfdK>bw z5G%50nr9i!G>pNhQ^}Q;P2TzQdnZjLF30}9JNK|4&8uRT^u!YImHvDooB_<5v zkOA+(-z2bAN$Y-j<9zLKB2Tir zcRsam<*C27=|Oeu+gu$vhWWfV^_-h-w)S@Y$l>~ph)sd|V2R|645`~|VPp;R@`^P{ z(sNW*flX4|aV_7KrmZe!sH(~+A1p}flzJWaJelH3c=$Mgi>JwbDQjx?Ce8L*=4a2! zI9X|N1b-2;XqF~hsUJ;1*_*@~6Lk&7zmNl|j}k~{?I6Zt65*vP4Rw7&C(#dLN_k|; zNe?yg3)hD+6~|>ZR}!SP-1*QGoiGxPS=plPqQ@^=h-L9UBpjCD6j#m_z&2KTD^ZkS; z8E?f>mEDyaWMN=tBR9Lg5Wso%?Ku(=KX4Uf<<1|YUCqKVdAkm66>lvOg!yCMHO5M( zsh?($l*D)1xIDumG`Az?d5Z!Uq-|&;!>%&=?B$^Z?&XE;?iw=m94h)X%aP=h!0|Wt z;OC!s{G71;0JClPx$wkB!&Xza$n-vfDovZL4S1@wg;vD_wKMWu^W&9W2q)YcSGsER zb>DWz#Jwxq6NnQ`qho7WI*0l}k1#=kQK;AICo8i`C zEo3lbN2B!p48&Sz0SVFypfD5DewMBv_4V-2q*n#L;v!3@#eF$HUNnpkT6gV{x14SH zyjkINxH#Sz5F~pfJpJOkcrJ*G<-7(@N0my6espwoJoRAvE^9OMI>|BR)yd!|YdM86$lv zS~)*$B3=Z=-@rALPdeh|)@Z69w(9aisCq`1S|CZW4fqWjly{Z-@#>~v*+{2W&(nfK z7P5Vt_bk}<@v$jI>Dy{k5`$W(Io2*OR3y10)HxGi!>@&s`{oxZD=jZCbgy5e{_VqweMNqSnvpeA6s1#!@lw;Sm_{YNNGyI^5~3y=uGu zo93c0Eu$a4G*i|<`m^#93$L*yKXoCp(bofA=ECq$?$cE^!j`q&pz}00SK`EAEK@^^ zRKj-1xdTxp=$(O5(a3H#*rkDgJUO`uQT@E zZ8ljldO1&?)(o06dFEwqT(ZRb&Md|XHjRdPIFk#QUr3r(p1)Hlf(jG3V%EXTOQ5uO z0eRTaVH)_z#(9wrgXQN@N6nCKxsTmy*9>$=Bb_e%rN^S-`m)x_kY<5Yw=W8Ydw>{W zFq?NO%MPm_Yh7iTLF&1--c1I2s0IzMbVKN9KIi5JX|4ih`)y|IM}iiVzw)K!Rn+9w zSgE%->f9*${PnLx__5L9m(CB$9E5a#IsGv!&FS~a4?|-QzbkgNZ_k8|snT0_wf~LT zGFsCpl~9*deI!pedk#-Fb-Z1NAk%oe)uw~VXHh3;nVqy|E0=ef2mw9_f2Ssb1oosN z^z_HE1ySEtS-O;4f#s>0PJgStM!DTkQGH)5suRjR)#f1IA8PZ12m`m6H??#cz`6|0 zgBHg23Oj)QdH~5z1+sgvffKd#{rQ_+#@c-~YBmc#zdcq1^Ep}a_w8@Y%P9UF!`MHr zSpfABHh>q6oz*q9E6N_-Ix&cZNQea0=Ue*Ni)D9n)bq-wh}ihhbA%d1sjTyB_!5AQwV$R8Q6;bfr^y1fi) z(sM>J=L}elelt_9N@`m3j|khS1H&cuUf*H;LEVly!zwd36!85I%DaV>XS0GrUI(Va zxTpY2?TQ!B8u3XB!yZAu$(m&$g4`GdDT19d)2-!Odn`J- zf8Qm*54f2)hh=25I2PULf~hi*D0dYtQ`J4RLG*I9wI7aVHG89k^D`ZReer1Wf-2JW zv&xC+ipmo57+0r9zqN;bD}y%XI|uv6va_+fc5K)7n6Gl5U2-Pk+4U!Jq8a z&);jYCza~ra>|O<8}y%ep{v;(P3%djrTJY16cgpo!ce;lj05x{A_sGsn5;1TS^Qd_ zukmSZX7y`5KllE)zEd9vuYvDoR9Br+buQS4H+6haBWz;mIYem|CQ&i=Mt1}X?xgbB zF*m9K)b5q$W)X(nBi}vOfkm?zy36vNCK5HPnlO4OQVcU@-j0}k!4FNf`U1aWJk?4b zUd?VfhLSaUcf?67kSyPB5lumH$TFBft>iQsM(yGX!kZ5azKRl8z_pZ%N2~P&5MA#| zuD-S1`uTt_;*Dij>d_|-{OyyL+ecju<2>&Yo+{J%V}1VSp4`(-d_Z+d(wL}0n$lnx znS^lhh=0qlnNfhsN3WAK@5nMv#y%vbNK47Jx&xhFoDrTv(-SYLtZ-V~Et2KTtB6z` z|AGqtjDT0eF35)xS(r*KfF`8ns`*H@|+M~%}PQBy{jAbs}=JLAc+&Sxl z_yG2h-%9GlyLNw#-l1M!ew!nDVLmPY3e!0V^DE^>!DAw01BqWORlKehLPAhd zTbLd)Q^5cN(owVA9U8Cl?%3GU5rhlC!ujwcK*5mqEu3xxZ&SV(s1G338KDpWGG4p6I1%z}MpK9P*FO5j&*@_d91-W^vnH((IJa=@_waSn zi@5Ld4-yAL)?D9Te|&XxY#nj_lg#SFrbY19^>5OP*JDo}VIGiuxT5nuSsf|~epg%L z_#`!LUW!e%35=<~#p_jCsW*Wq@?@x)n@-5^u_VSO08~0;I35;SkWRAn+ConRJzgof zL#ioN=}bC-{WpHsf2(qnU81C~;X+(MC_cn+X)a}*{dfT48eFbdy8J-l0B|#<<3Woi zNFT66p#t5^9Bam~=zBfl)X#hi#(S`N>X*BA#ML11XwmDy)lO<@>M#j(?V^B38DLF5 zQuGD|lyb|jB#iM3QTfOaJ}fDHJvl|R4@m@kysVX*>zuwoAt?tzaNAT2VJZ+f@+w%( zPpY>+bpoGMljkpWx+2doeX+5)tGElppXkm$I;Vdg#q(t9r@@-SaoQSA=J)62t{6|fobi+|YMd>sK(mWZ zQ40);dkH`c5Np#WF>o}IqN^|^0IbHh#`r6SYPMd^sb778zW9OhZK-Ks%rb&*kx;XnjKA;Cjfl2g+ z5R*{1xMSaOKNV+c`+UIrWG9M`FfjC#K|`bTo-4C}ma;w#uM$zC6*?<85 z=UNH38+*=)ac7>5-tBA>065AJXYf_ksZidSr@{~BG5hg5emZhLbweW}PlP8+MrZ~Ko{d628uIfuCLUf|QCRohK7(*BgEhpJ!oJ`J4w(HVL+ z1xf8M;f_1|a7L@Cnk^wOQI`709mV)vIg{_BXg|^9W_kQK`X!T(MqFWVig7#jR;~+JcwdUp^R{d(4>jPIvJ4As4DY z?m$apk6g`i^`12#B2uzC?Tg_|d1gi~fbF{?j$BXtb2+Vp`#W8=*I9W{F#jqD5o!#X|8w;^8AihOD0*%>@#E~NSs{3jdRyMcE5Ghg?Vl6Yt0;UBCG z)ZsmF_MD?`fdVJIM~#WOp%d%cBmqLe_$;?5^YqoaNN0Eaphzs- z8el9wyh6$I`^PNa{P9Im;^|*}u&(Tep&N~T^{f#`e(^n4;b&t0nMjMG6$!8q3X16- zi9Qx!`>JB`TF*^sIxK!LJ!tNBhICYaUGzqv;L}wmNaEahV7W;4|Qe1R$wCRx|QDNBB?_5Z^In3GdXZpg^E#7-Vll*ztBhY4$l56^`$E zBcDc|-+g6z^!m}(v&N&t(%q%1mbZ0VFHBOzo0m@5fHeCzNth8nrX#k+tnXfK>&(JdZ98t>HfO=uR z`nR{c!0`wV4G*^>J_%I;r>R;j7vju;R}EmYjW?tCK*U7Nb|YUthtt`eH=*11EGbi# zRZ1dXBGFDW`p3L>0h#8)V3z{)R-L4mlvT=QVvCisGk!5=VrBxeYCq`OoQn=|{BYJ8 ze#R23u;PTS1y_7iMhlm<-IEVFA^Z4AVOvi*85^YIDn2<)tvELa5X&8lS5;Y8)(ko? zDEi=r_1Qo!Fwxc&iwHbpB966hcl3sautzy`;6K;Xg_5QpVniS&M}KCX5*gFc z?nf36kNAlTd~^IGs8#%o^0=Bs5j3I#vz*3%Bkl)o=n?V8khSy<6-Bzv4x_TxU6o~i zkyn0|EBEY==%ogheM{|Y&Zy8(D=3klPYv4nc!%rtBj#7%F;!g4N`lVl(4~`Rr11r! zYdfch9HVTn?)4@fY1P9*)N>vf$C+>TQh}Ea3b|Sb$e`{Oek=?ACgkvCoqs0wc{Y!R zCC}I##tlYTmzHd+#G2Z_d$-A2MbW0I#JeeYiXC%}Wa@bv!h^^1Fc!JqB2|Jd}Tgn0a*S-Ga!Kf?9=6km<{ z_wf6)6fjStuPo3xzv(=y$=B<=G-DQmIkY_eiQRljh$hn)R``&886SgeF0iWFB-aCSFD|>QC<2FD@pULh6&ef)@oxOCJ1!Qtb0elhLFWH z$57lHXcpiDSzz1Q(8S5#r%QuY{-nSo^{d4!{lKhp-IWh@jAN`7@ZRt8Vh0zWZZq5W zYdC4#4=qH!6@|%hfezVys1l>4pP!EAReyK9;RbX=^X9V}h;NV)g`V8+2IgiWg!L~f zm7SHe!lg04XtxZM;BMLw@%Q$q@dbU7Q$0o`il3b~ChvmCr zG^eYlHwnMEn&-WHM2?v--BrE^qiu5RpBR!&6F1fk5qq0B-9e~4{>1_A5t@CvR_>8J z6p#NW+^Z;&6^*sr4*sLLt5h2pDl~#4kClp=UF-)^bA)|kjtouq^|(5YFPT*!!WWmg ziKzpS0Br5v$y)I89&<*VPR}M#AHw}~YXC$X&H*TPZkKY9+bYQ;=rb2P>m~((`w;dV z5)CmhAc~zxj`Wzif)$WJ41Z=J>=oi3X)Kc}P$o3klW9^qzhW;up7C}3A)=10q_^mS z$-{%X*fVefFZ&D#QXepZ4->_x5_6{G)N}IZxHTZtLrxT60UHw36xOs_?~#Qmj9$8h zByXGSjL?S_Jf?ml@%ZYKRjuc9nu^i7jYvL7@|@PnK^1PE_>+nJ{SUS898~F~RJ}go zLZ687nO%dSh?ewYN9or(At<<~4Y+f%UZJxq#ZHi^*9wB45~I>BD*%el4OgCcTO1=p>vz$rSuCVym;n8HxnC`PQN~sOJeyh z7ZQQeQxqwgsMkc!x>fKrV^z0m=3f2JD)Th^19vW3U)f({{}ZcwfNN1zS3hVGar?pk zuL7y?Fpnz~t!qr|x6aE{CxQ4)Ofn3eYn9f%WSak6`bY)bb^g?YITgcy6xmdr~>pu3c8n!4ZX1DM$ywWeKnljl=^e~VMGkI z+A9*Qm!$YaQ&(Puu^M`_HexYmmUeZ*j&e4^PMQFl|{MvLmBNf|$HZ%De=V((qZ6h%aO`_3+N^z*rNUhn< z0grVEDuzzRkX}3JeaPO^KQHq1ZN$4*fp&gBlAonzFV3C0-lEm!59U8qi zStyNqfpcOpH=M4M4Va*o?hy&DBJ740-uz$g5z=@<+tFQD1KjT{FGI|}aM1k)P7cGH ze_(p`o!p_fxkD(IUOG}@aB&K4hZW5MLU%QEJ*syNb15bzHRcN;q7|AjE`8n9y`HR> ze@eunRi{o9E0m16bI7^nwxwbnLzrxW5^1&t-M@U)S?6ULFof6}+1$u@8{`Mbb=Y^h z@YnSa>bAs{?PJ#Q=9Gx64P8OSIKjJ&Df*$#^T(>OJCsTwFSZV3U`9L%+P+F45*0J> zGw4?N!He_IA(>q#*+-?fuhcSgZ-5`;ln;D~MZX&IzQ%0DEV~fW4-6r;tdMC17>iceZ+#(Mfh@xd~Ti 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 z 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 BtbhOj literal 324203 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%0DOzT1?3!a~KMdsUSn8Tdjc9 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%0AF= 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

    4WMX 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 Am;e9( From 4afe7906d237efd5ab9dd642c1266988872fc92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Narzis?= <78718413+lean-apple@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:28:31 +0200 Subject: [PATCH 215/274] feat: introduce reth era export (#15909) Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- Cargo.lock | 3 + crates/era-utils/Cargo.toml | 3 + crates/era-utils/src/export.rs | 342 +++++++++++++++++++++++++++ crates/era-utils/src/lib.rs | 8 + crates/era-utils/tests/it/genesis.rs | 30 +++ crates/era-utils/tests/it/history.rs | 106 ++++++++- crates/era-utils/tests/it/main.rs | 9 +- crates/era/Cargo.toml | 1 + crates/era/src/execution_types.rs | 12 + 9 files changed, 508 insertions(+), 6 deletions(-) create mode 100644 crates/era-utils/src/export.rs create mode 100644 crates/era-utils/tests/it/genesis.rs diff --git a/Cargo.lock b/Cargo.lock index ccba6bab4cf..8bdc00c91ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8028,7 +8028,9 @@ dependencies = [ name = "reth-era-utils" version = "1.4.8" dependencies = [ + "alloy-consensus", "alloy-primitives", + "alloy-rlp", "bytes", "eyre", "futures", @@ -8038,6 +8040,7 @@ dependencies = [ "reth-db-common", "reth-era", "reth-era-downloader", + "reth-ethereum-primitives", "reth-etl", "reth-fs-util", "reth-primitives-traits", diff --git a/crates/era-utils/Cargo.toml b/crates/era-utils/Cargo.toml index 006e084aec8..6d48e338386 100644 --- a/crates/era-utils/Cargo.toml +++ b/crates/era-utils/Cargo.toml @@ -11,13 +11,16 @@ exclude.workspace = true [dependencies] # alloy +alloy-consensus.workspace = true alloy-primitives.workspace = true +alloy-rlp.workspace = true # reth reth-db-api.workspace = true reth-era.workspace = true reth-era-downloader.workspace = true reth-etl.workspace = true +reth-ethereum-primitives.workspace = true reth-fs-util.workspace = true reth-provider.workspace = true reth-stages-types.workspace = true diff --git a/crates/era-utils/src/export.rs b/crates/era-utils/src/export.rs new file mode 100644 index 00000000000..2eba464e509 --- /dev/null +++ b/crates/era-utils/src/export.rs @@ -0,0 +1,342 @@ +//! Logic to export from database era1 block history +//! and injecting them into era1 files with `Era1Writer`. + +use alloy_consensus::{BlockBody, BlockHeader, Header}; +use alloy_primitives::{BlockNumber, B256, U256}; +use eyre::{eyre, Result}; +use reth_era::{ + era1_file::Era1Writer, + era1_types::{BlockIndex, Era1Id}, + execution_types::{ + Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, + TotalDifficulty, MAX_BLOCKS_PER_ERA1, + }, +}; +use reth_fs_util as fs; +use reth_storage_api::{BlockNumReader, BlockReader, HeaderProvider}; +use std::{ + path::PathBuf, + time::{Duration, Instant}, +}; +use tracing::{info, warn}; + +const REPORT_INTERVAL_SECS: u64 = 10; +const ENTRY_HEADER_SIZE: usize = 8; +const VERSION_ENTRY_SIZE: usize = ENTRY_HEADER_SIZE; + +/// Configuration to export block history +/// to era1 files +#[derive(Clone, Debug)] +pub struct ExportConfig { + /// Directory to export era1 files to + pub dir: PathBuf, + /// First block to export + pub first_block_number: BlockNumber, + /// Last block to export + pub last_block_number: BlockNumber, + /// Number of blocks per era1 file + /// It can never be larger than `MAX_BLOCKS_PER_ERA1 = 8192` + /// See also <`https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era1.md`> + pub max_blocks_per_file: u64, + /// Network name + pub network: String, +} + +impl Default for ExportConfig { + fn default() -> Self { + Self { + dir: PathBuf::new(), + first_block_number: 0, + last_block_number: (MAX_BLOCKS_PER_ERA1 - 1) as u64, + max_blocks_per_file: MAX_BLOCKS_PER_ERA1 as u64, + network: "mainnet".to_string(), + } + } +} + +impl ExportConfig { + /// Validates the export configuration parameters + pub fn validate(&self) -> Result<()> { + if self.max_blocks_per_file > MAX_BLOCKS_PER_ERA1 as u64 { + return Err(eyre!( + "Max blocks per file ({}) exceeds ERA1 limit ({})", + self.max_blocks_per_file, + MAX_BLOCKS_PER_ERA1 + )); + } + + if self.max_blocks_per_file == 0 { + return Err(eyre!("Max blocks per file cannot be zero")); + } + + Ok(()) + } +} + +/// Fetches block history data from the provider +/// and prepares it for export to era1 files +/// for a given number of blocks then writes them to disk. +pub fn export(provider: &P, config: &ExportConfig) -> Result> +where + P: BlockReader, + B: Into>, + P::Header: Into

    , +{ + config.validate()?; + info!( + "Exporting blockchain history from block {} to {} with this max of blocks per file of {}", + config.first_block_number, config.last_block_number, config.max_blocks_per_file + ); + + // Determine the actual last block to export + // best_block_number() might be outdated, so check actual block availability + let last_block_number = determine_export_range(provider, config)?; + + info!( + target: "era::history::export", + first = config.first_block_number, + last = last_block_number, + max_blocks_per_file = config.max_blocks_per_file, + "Preparing era1 export data" + ); + + if !config.dir.exists() { + fs::create_dir_all(&config.dir) + .map_err(|e| eyre!("Failed to create output directory: {}", e))?; + } + + let start_time = Instant::now(); + let mut last_report_time = Instant::now(); + let report_interval = Duration::from_secs(REPORT_INTERVAL_SECS); + + let mut created_files = Vec::new(); + let mut total_blocks_processed = 0; + + let mut total_difficulty = if config.first_block_number > 0 { + let prev_block_number = config.first_block_number - 1; + provider + .header_td_by_number(prev_block_number)? + .ok_or_else(|| eyre!("Total difficulty not found for block {prev_block_number}"))? + } else { + U256::ZERO + }; + + // Process blocks in chunks according to `max_blocks_per_file` + for start_block in + (config.first_block_number..=last_block_number).step_by(config.max_blocks_per_file as usize) + { + let end_block = (start_block + config.max_blocks_per_file - 1).min(last_block_number); + let block_count = (end_block - start_block + 1) as usize; + + info!( + target: "era::history::export", + "Processing blocks {start_block} to {end_block} ({block_count} blocks)" + ); + + let headers = provider.headers_range(start_block..=end_block)?; + + let era1_id = Era1Id::new(&config.network, start_block, block_count as u32); + let file_path = config.dir.join(era1_id.to_file_name()); + let file = std::fs::File::create(&file_path)?; + let mut writer = Era1Writer::new(file); + writer.write_version()?; + + let mut offsets = Vec::with_capacity(block_count); + let mut position = VERSION_ENTRY_SIZE as i64; + let mut blocks_written = 0; + let mut final_header_data = Vec::new(); + + for (i, header) in headers.into_iter().enumerate() { + let expected_block_number = start_block + i as u64; + + let (compressed_header, compressed_body, compressed_receipts) = compress_block_data( + provider, + header, + expected_block_number, + &mut total_difficulty, + )?; + + // Save last block's header data for accumulator + if expected_block_number == end_block { + final_header_data = compressed_header.data.clone(); + } + + let difficulty = TotalDifficulty::new(total_difficulty); + + let header_size = compressed_header.data.len() + ENTRY_HEADER_SIZE; + let body_size = compressed_body.data.len() + ENTRY_HEADER_SIZE; + let receipts_size = compressed_receipts.data.len() + ENTRY_HEADER_SIZE; + let difficulty_size = 32 + ENTRY_HEADER_SIZE; // U256 is 32 + 8 bytes header overhead + let total_size = header_size + body_size + receipts_size + difficulty_size; + + let block_tuple = BlockTuple::new( + compressed_header, + compressed_body, + compressed_receipts, + difficulty, + ); + + offsets.push(position); + position += total_size as i64; + + writer.write_block(&block_tuple)?; + blocks_written += 1; + total_blocks_processed += 1; + + if last_report_time.elapsed() >= report_interval { + info!( + target: "era::history::export", + "Export progress: block {expected_block_number}/{last_block_number} ({:.2}%) - elapsed: {:?}", + (total_blocks_processed as f64) / + ((last_block_number - config.first_block_number + 1) as f64) * + 100.0, + start_time.elapsed() + ); + last_report_time = Instant::now(); + } + } + if blocks_written > 0 { + let accumulator_hash = + B256::from_slice(&final_header_data[0..32.min(final_header_data.len())]); + let accumulator = Accumulator::new(accumulator_hash); + let block_index = BlockIndex::new(start_block, offsets); + + writer.write_accumulator(&accumulator)?; + writer.write_block_index(&block_index)?; + writer.flush()?; + created_files.push(file_path.clone()); + + info!( + target: "era::history::export", + "Wrote ERA1 file: {file_path:?} with {blocks_written} blocks" + ); + } + } + + info!( + target: "era::history::export", + "Successfully wrote {} ERA1 files in {:?}", + created_files.len(), + start_time.elapsed() + ); + + Ok(created_files) +} + +// Determines the actual last block number that can be exported, +// Uses `headers_range` fallback when `best_block_number` is stale due to static file storage. +fn determine_export_range

    (provider: &P, config: &ExportConfig) -> Result +where + P: HeaderProvider + BlockNumReader, +{ + let best_block_number = provider.best_block_number()?; + + let last_block_number = if best_block_number < config.last_block_number { + warn!( + "Last block {} is beyond current head {}, setting last = head", + config.last_block_number, best_block_number + ); + + // Check if more blocks are actually available beyond what `best_block_number()` reports + if let Ok(headers) = provider.headers_range(best_block_number..=config.last_block_number) { + if let Some(last_header) = headers.last() { + let highest_block = last_header.number(); + info!("Found highest available block {} via headers_range", highest_block); + highest_block + } else { + warn!("No headers found in range, using best_block_number {}", best_block_number); + best_block_number + } + } else { + warn!("headers_range failed, using best_block_number {}", best_block_number); + best_block_number + } + } else { + config.last_block_number + }; + + Ok(last_block_number) +} + +// Compresses block data and returns compressed components with metadata +fn compress_block_data( + provider: &P, + header: P::Header, + expected_block_number: BlockNumber, + total_difficulty: &mut U256, +) -> Result<(CompressedHeader, CompressedBody, CompressedReceipts)> +where + P: BlockReader, + B: Into>, + P::Header: Into

    , +{ + let actual_block_number = header.number(); + + if expected_block_number != actual_block_number { + return Err(eyre!("Expected block {expected_block_number}, got {actual_block_number}")); + } + + let body = provider + .block_by_number(actual_block_number)? + .ok_or_else(|| eyre!("Block body not found for block {}", actual_block_number))?; + + let receipts = provider + .receipts_by_block(actual_block_number.into())? + .ok_or_else(|| eyre!("Receipts not found for block {}", actual_block_number))?; + + *total_difficulty += header.difficulty(); + + let compressed_header = CompressedHeader::from_header(&header.into())?; + let compressed_body = CompressedBody::from_body(&body.into())?; + let compressed_receipts = CompressedReceipts::from_encodable_list(&receipts) + .map_err(|e| eyre!("Failed to compress receipts: {}", e))?; + + Ok((compressed_header, compressed_body, compressed_receipts)) +} + +#[cfg(test)] +mod tests { + use crate::ExportConfig; + use reth_era::execution_types::MAX_BLOCKS_PER_ERA1; + use tempfile::tempdir; + + #[test] + fn test_export_config_validation() { + let temp_dir = tempdir().unwrap(); + + // Default config should pass + let default_config = ExportConfig::default(); + assert!(default_config.validate().is_ok(), "Default config should be valid"); + + // Exactly at the limit should pass + let limit_config = + ExportConfig { max_blocks_per_file: MAX_BLOCKS_PER_ERA1 as u64, ..Default::default() }; + assert!(limit_config.validate().is_ok(), "Config at ERA1 limit should pass validation"); + + // Valid config should pass + let valid_config = ExportConfig { + dir: temp_dir.path().to_path_buf(), + max_blocks_per_file: 1000, + ..Default::default() + }; + assert!(valid_config.validate().is_ok(), "Valid config should pass validation"); + + // Zero blocks per file should fail + let zero_blocks_config = ExportConfig { + max_blocks_per_file: 0, // Invalid + ..Default::default() + }; + let result = zero_blocks_config.validate(); + assert!(result.is_err(), "Zero blocks per file should fail validation"); + assert!(result.unwrap_err().to_string().contains("cannot be zero")); + + // Exceeding era1 limit should fail + let oversized_config = ExportConfig { + max_blocks_per_file: MAX_BLOCKS_PER_ERA1 as u64 + 1, // Invalid + ..Default::default() + }; + let result = oversized_config.validate(); + assert!(result.is_err(), "Oversized blocks per file should fail validation"); + assert!(result.unwrap_err().to_string().contains("exceeds ERA1 limit")); + } +} diff --git a/crates/era-utils/src/lib.rs b/crates/era-utils/src/lib.rs index a2c6cdaedb7..966709d2f21 100644 --- a/crates/era-utils/src/lib.rs +++ b/crates/era-utils/src/lib.rs @@ -1,9 +1,17 @@ //! Utilities to store history from downloaded ERA files with storage-api +//! and export it to recreate era1 files. //! //! The import is downloaded using [`reth_era_downloader`] and parsed using [`reth_era`]. mod history; +/// Export block history data from the database to recreate era1 files. +mod export; + +/// Export history from storage-api between 2 blocks +/// with parameters defined in [`ExportConfig`]. +pub use export::{export, ExportConfig}; + /// Imports history from ERA files. pub use history::{ build_index, decode, import, open, process, process_iter, save_stage_checkpoints, ProcessIter, diff --git a/crates/era-utils/tests/it/genesis.rs b/crates/era-utils/tests/it/genesis.rs new file mode 100644 index 00000000000..dacef15eeac --- /dev/null +++ b/crates/era-utils/tests/it/genesis.rs @@ -0,0 +1,30 @@ +use reth_db_common::init::init_genesis; +use reth_era_utils::{export, ExportConfig}; +use reth_fs_util as fs; +use reth_provider::{test_utils::create_test_provider_factory, BlockReader}; +use tempfile::tempdir; + +#[test] +fn test_export_with_genesis_only() { + let provider_factory = create_test_provider_factory(); + init_genesis(&provider_factory).unwrap(); + let provider = provider_factory.provider().unwrap(); + assert!(provider.block_by_number(0).unwrap().is_some(), "Genesis block should exist"); + assert!(provider.block_by_number(1).unwrap().is_none(), "Block 1 should not exist"); + + let export_dir = tempdir().unwrap(); + let export_config = ExportConfig { dir: export_dir.path().to_owned(), ..Default::default() }; + + let exported_files = + export(&provider_factory.provider_rw().unwrap().0, &export_config).unwrap(); + + assert_eq!(exported_files.len(), 1, "Should export exactly one file"); + + let file_path = &exported_files[0]; + assert!(file_path.exists(), "Exported file should exist on disk"); + let file_name = file_path.file_name().unwrap().to_str().unwrap(); + assert!(file_name.starts_with("mainnet-0-"), "File should have correct prefix"); + assert!(file_name.ends_with(".era1"), "File should have correct extension"); + let metadata = fs::metadata(file_path).unwrap(); + assert!(metadata.len() > 0, "Exported file should not be empty"); +} diff --git a/crates/era-utils/tests/it/history.rs b/crates/era-utils/tests/it/history.rs index d1d9e994513..4811e729539 100644 --- a/crates/era-utils/tests/it/history.rs +++ b/crates/era-utils/tests/it/history.rs @@ -2,11 +2,18 @@ use crate::{ClientWithFakeIndex, ITHACA_ERA_INDEX_URL}; use reqwest::{Client, Url}; use reth_db_common::init::init_genesis; use reth_era_downloader::{EraClient, EraStream, EraStreamConfig}; +use reth_era_utils::{export, import, ExportConfig}; use reth_etl::Collector; -use reth_provider::test_utils::create_test_provider_factory; +use reth_fs_util as fs; +use reth_provider::{test_utils::create_test_provider_factory, BlockNumReader, BlockReader}; use std::str::FromStr; use tempfile::tempdir; +const EXPORT_FIRST_BLOCK: u64 = 0; +const EXPORT_BLOCKS_PER_FILE: u64 = 250; +const EXPORT_TOTAL_BLOCKS: u64 = 900; +const EXPORT_LAST_BLOCK: u64 = EXPORT_FIRST_BLOCK + EXPORT_TOTAL_BLOCKS - 1; + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_history_imports_from_fresh_state_successfully() { // URL where the ERA1 files are hosted @@ -30,7 +37,102 @@ async fn test_history_imports_from_fresh_state_successfully() { let mut hash_collector = Collector::new(4096, folder); let expected_block_number = 8191; - let actual_block_number = reth_era_utils::import(stream, &pf, &mut hash_collector).unwrap(); + let actual_block_number = import(stream, &pf, &mut hash_collector).unwrap(); assert_eq!(actual_block_number, expected_block_number); } + +/// Test that verifies the complete roundtrip from importing to exporting era1 files. +/// It validates : +/// - Downloads the first era1 file from ithaca's url and import the file data, into the database +/// - Exports blocks from database back to era1 format +/// - Ensure exported files have correct structure and naming +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_roundtrip_export_after_import() { + // URL where the ERA1 files are hosted + let url = Url::from_str(ITHACA_ERA_INDEX_URL).unwrap(); + let download_folder = tempdir().unwrap(); + let download_folder = download_folder.path().to_owned().into_boxed_path(); + + let client = EraClient::new(ClientWithFakeIndex(Client::new()), url, download_folder); + let config = EraStreamConfig::default().with_max_files(1).with_max_concurrent_downloads(1); + + let stream = EraStream::new(client, config); + let pf = create_test_provider_factory(); + init_genesis(&pf).unwrap(); + + let folder = tempdir().unwrap(); + let folder = Some(folder.path().to_owned()); + let mut hash_collector = Collector::new(4096, folder); + + // Import blocks from one era1 file into database + let last_imported_block_height = import(stream, &pf, &mut hash_collector).unwrap(); + + assert_eq!(last_imported_block_height, 8191); + let provider_ref = pf.provider_rw().unwrap().0; + let best_block = provider_ref.best_block_number().unwrap(); + + assert!(best_block <= 8191, "Best block {best_block} should not exceed imported count"); + + // Verify some blocks exist in the database + for &block_num in &[0, 1, 2, 10, 50, 100, 5000, 8190, 8191] { + let block_exists = provider_ref.block_by_number(block_num).unwrap().is_some(); + assert!(block_exists, "Block {block_num} should exist after importing 8191 blocks"); + } + + // The import was verified let's start the export! + + // 900 blocks will be exported from 0 to 899 + // It should be split into 3 files of 250 blocks each, and the last file with 150 blocks + let export_folder = tempdir().unwrap(); + let export_config = ExportConfig { + dir: export_folder.path().to_path_buf(), + first_block_number: EXPORT_FIRST_BLOCK, // 0 + last_block_number: EXPORT_LAST_BLOCK, // 899 + max_blocks_per_file: EXPORT_BLOCKS_PER_FILE, // 250 blocks per file + network: "mainnet".to_string(), + }; + + // Export blocks from database to era1 files + let exported_files = export(&provider_ref, &export_config).expect("Export should succeed"); + + // Calculate how many files we expect based on the configuration + // We expect 4 files for 900 blocks: first 3 files with 250 blocks each, + // then 150 for the last file + let expected_files_number = EXPORT_TOTAL_BLOCKS.div_ceil(EXPORT_BLOCKS_PER_FILE); + + assert_eq!( + exported_files.len(), + expected_files_number as usize, + "Should create {expected_files_number} files for {EXPORT_TOTAL_BLOCKS} blocks with {EXPORT_BLOCKS_PER_FILE} blocks per file" + ); + + for (i, file_path) in exported_files.iter().enumerate() { + // Verify file exists and has content + assert!(file_path.exists(), "File {} should exist", i + 1); + let file_size = fs::metadata(file_path).unwrap().len(); + assert!(file_size > 0, "File {} should not be empty", i + 1); + + // Calculate expected file parameters + let file_start_block = EXPORT_FIRST_BLOCK + (i as u64 * EXPORT_BLOCKS_PER_FILE); + let remaining_blocks = EXPORT_TOTAL_BLOCKS - (i as u64 * EXPORT_BLOCKS_PER_FILE); + let blocks_numbers_per_file = std::cmp::min(EXPORT_BLOCKS_PER_FILE, remaining_blocks); + + // Verify chunking : first 3 files have 250 blocks, last file has 150 blocks - 900 total + let expected_blocks = if i < 3 { 250 } else { 150 }; + assert_eq!( + blocks_numbers_per_file, + expected_blocks, + "File {} should contain exactly {} blocks, got {}", + i + 1, + expected_blocks, + blocks_numbers_per_file + ); + + // Verify exact ERA1 naming convention: `mainnet-{start_block}-{block_count}.era1` + let file_name = file_path.file_name().unwrap().to_str().unwrap(); + let expected_filename = + format!("mainnet-{file_start_block}-{blocks_numbers_per_file}.era1"); + assert_eq!(file_name, expected_filename, "File {} should have correct name", i + 1); + } +} diff --git a/crates/era-utils/tests/it/main.rs b/crates/era-utils/tests/it/main.rs index bd49aca6879..94805c5b356 100644 --- a/crates/era-utils/tests/it/main.rs +++ b/crates/era-utils/tests/it/main.rs @@ -1,9 +1,5 @@ //! Root module for test modules, so that the tests are built into a single binary. -mod history; - -const fn main() {} - use alloy_primitives::bytes::Bytes; use futures_util::{stream, Stream, TryStreamExt}; use reqwest::{Client, IntoUrl}; @@ -16,6 +12,11 @@ const ITHACA_ERA_INDEX_URL: &str = "https://era.ithaca.xyz/era1/index.html"; // The response containing one file that the fake client will return when the index Url is requested const GENESIS_ITHACA_INDEX_RESPONSE: &[u8] = b"mainnet-00000-5ec1ffb8.era1"; +mod genesis; +mod history; + +const fn main() {} + /// An HTTP client that fakes the file list to always show one known file /// /// but passes all other calls including actual downloads to a real HTTP client diff --git a/crates/era/Cargo.toml b/crates/era/Cargo.toml index d8259ec813c..09d5b8b9180 100644 --- a/crates/era/Cargo.toml +++ b/crates/era/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "reth-era" +description = "e2store and era1 files core logic" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/era/src/execution_types.rs b/crates/era/src/execution_types.rs index 4591abb281a..27030b112a1 100644 --- a/crates/era/src/execution_types.rs +++ b/crates/era/src/execution_types.rs @@ -333,6 +333,18 @@ impl CompressedReceipts { let compressed = encoder.encode(data)?; Ok(Self::new(compressed)) } + /// Encode a list of receipts to RLP format + pub fn encode_receipts_to_rlp(receipts: &[T]) -> Result, E2sError> { + let mut rlp_data = Vec::new(); + alloy_rlp::encode_list(receipts, &mut rlp_data); + Ok(rlp_data) + } + + /// Encode and compress a list of receipts + pub fn from_encodable_list(receipts: &[T]) -> Result { + let rlp_data = Self::encode_receipts_to_rlp(receipts)?; + Self::from_rlp(&rlp_data) + } } impl DecodeCompressed for CompressedReceipts { From 5221b6d2811320d566353eccea2a4beea9c6fa1a Mon Sep 17 00:00:00 2001 From: Kendra Karol Sevilla Date: Wed, 25 Jun 2025 12:01:36 +0200 Subject: [PATCH 216/274] chore: fix typo execution.rs (#17004) --- crates/stages/stages/src/stages/execution.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 9de94ee197f..e5592cd8dec 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -71,7 +71,6 @@ where evm_config: E, /// The consensus instance for validating blocks. consensus: Arc>, - /// The consensu /// The commit thresholds of the execution stage. thresholds: ExecutionStageThresholds, /// The highest threshold (in number of blocks) for switching between incremental From d2b4dd561199e0060dd2ce6f2f2c800268faec54 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 25 Jun 2025 11:10:00 +0100 Subject: [PATCH 217/274] perf(trie): implement `ParallelSparseTrie::root` method (#17030) Co-authored-by: Claude Co-authored-by: Claude --- crates/trie/sparse-parallel/Cargo.toml | 3 +- crates/trie/sparse-parallel/src/trie.rs | 284 +++++++++++++++++++++--- crates/trie/sparse/src/trie.rs | 15 ++ 3 files changed, 264 insertions(+), 38 deletions(-) diff --git a/crates/trie/sparse-parallel/Cargo.toml b/crates/trie/sparse-parallel/Cargo.toml index 9f944382db6..21764ff429f 100644 --- a/crates/trie/sparse-parallel/Cargo.toml +++ b/crates/trie/sparse-parallel/Cargo.toml @@ -16,7 +16,7 @@ workspace = true reth-execution-errors.workspace = true reth-trie-common.workspace = true reth-trie-sparse.workspace = true -tracing.workspace = true +tracing = { workspace = true, features = ["attributes"] } alloy-trie.workspace = true # alloy @@ -31,6 +31,7 @@ smallvec.workspace = true reth-primitives-traits.workspace = true reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } reth-trie.workspace = true +reth-trie-sparse = { workspace = true, features = ["test-utils"] } arbitrary.workspace = true assert_matches.workspace = true diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index ae3e8da1fad..45b6b6b5795 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -16,7 +16,7 @@ use reth_trie_sparse::{ TrieMasks, }; use smallvec::SmallVec; -use tracing::trace; +use tracing::{instrument, trace}; /// The maximum length of a path, in nibbles, which belongs to the upper subtrie of a /// [`ParallelSparseTrie`]. All longer paths belong to a lower subtrie. @@ -202,7 +202,7 @@ impl ParallelSparseTrie { /// /// This function first identifies all nodes that have changed (based on the prefix set) below /// level [`UPPER_TRIE_MAX_DEPTH`] of the trie, then recalculates their RLP representation. - pub fn update_subtrie_hashes(&mut self) { + pub fn update_lower_subtrie_hashes(&mut self) { trace!(target: "trie::parallel_sparse", "Updating subtrie hashes"); // Take changed subtries according to the prefix set @@ -215,8 +215,9 @@ impl ParallelSparseTrie { // Update subtrie hashes in parallel // TODO: call `update_hashes` on each subtrie in parallel let (tx, rx) = mpsc::channel(); - for subtrie in subtries { - tx.send((subtrie.index, subtrie.subtrie)).unwrap(); + for ChangedSubtrie { index, mut subtrie, mut prefix_set } in subtries { + subtrie.update_hashes(&mut prefix_set); + tx.send((index, subtrie)).unwrap(); } drop(tx); @@ -226,6 +227,43 @@ impl ParallelSparseTrie { } } + /// Updates hashes for the upper subtrie, using nodes from both upper and lower subtries. + #[instrument(level = "trace", target = "engine::tree", skip_all, ret)] + fn update_upper_subtrie_hashes(&mut self, prefix_set: &mut PrefixSet) -> RlpNode { + trace!(target: "trie::parallel_sparse", "Updating upper subtrie hashes"); + + debug_assert!(self.upper_subtrie.inner.buffers.path_stack.is_empty()); + self.upper_subtrie.inner.buffers.path_stack.push(RlpNodePathStackItem { + path: Nibbles::default(), // Start from root + is_in_prefix_set: None, + }); + + while let Some(stack_item) = self.upper_subtrie.inner.buffers.path_stack.pop() { + let path = stack_item.path; + let node = if path.len() < UPPER_TRIE_MAX_DEPTH { + self.upper_subtrie.nodes.get_mut(&path).expect("upper subtrie node must exist") + } else { + let index = path_subtrie_index_unchecked(&path); + let node = self.lower_subtries[index] + .as_mut() + .expect("lower subtrie must exist") + .nodes + .get_mut(&path) + .expect("lower subtrie node must exist"); + // Lower subtrie root node hashes must be computed before updating upper subtrie + // hashes + debug_assert!(node.hash().is_some()); + node + }; + + // Calculate the RLP node for the current node using upper subtrie + self.upper_subtrie.inner.rlp_node(prefix_set, stack_item, node); + } + + debug_assert_eq!(self.upper_subtrie.inner.buffers.rlp_node_stack.len(), 1); + self.upper_subtrie.inner.buffers.rlp_node_stack.pop().unwrap().rlp_node + } + /// Calculates and returns the root hash of the trie. /// /// Before computing the hash, this function processes any remaining (dirty) nodes by @@ -234,7 +272,17 @@ impl ParallelSparseTrie { /// 2. The keccak256 hash of the root node's RLP representation pub fn root(&mut self) -> B256 { trace!(target: "trie::parallel_sparse", "Calculating trie root hash"); - todo!() + + // Update all lower subtrie hashes + self.update_lower_subtrie_hashes(); + + // Update hashes for the upper subtrie using our specialized function + // that can access both upper and lower subtrie nodes + let mut prefix_set = core::mem::take(&mut self.prefix_set).freeze(); + let root_rlp = self.update_upper_subtrie_hashes(&mut prefix_set); + + // Return the root hash + root_rlp.as_hash().unwrap_or(EMPTY_ROOT_HASH) } /// Configures the trie to retain information about updates. @@ -576,9 +624,9 @@ impl SparseSubtrie { /// # Panics /// /// If the node at the root path does not exist. - #[allow(unused)] + #[instrument(level = "trace", target = "engine::tree", skip_all, fields(root = ?self.path), ret)] pub fn update_hashes(&mut self, prefix_set: &mut PrefixSet) -> RlpNode { - trace!(target: "trie::parallel_sparse", root=?self.path, "Updating subtrie hashes"); + trace!(target: "trie::parallel_sparse", "Updating subtrie hashes"); debug_assert!(prefix_set.iter().all(|path| path.starts_with(&self.path))); @@ -588,29 +636,14 @@ impl SparseSubtrie { .path_stack .push(RlpNodePathStackItem { path: self.path, is_in_prefix_set: None }); - while let Some(RlpNodePathStackItem { path, mut is_in_prefix_set }) = - self.inner.buffers.path_stack.pop() - { + while let Some(stack_item) = self.inner.buffers.path_stack.pop() { + let path = stack_item.path; let node = self .nodes .get_mut(&path) .unwrap_or_else(|| panic!("node at path {path:?} does not exist")); - trace!( - target: "trie::parallel_sparse", - root = ?self.path, - ?path, - ?is_in_prefix_set, - ?node, - "Popped node from path stack" - ); - // Check if the path is in the prefix set. - // First, check the cached value. If it's `None`, then check the prefix set, and update - // the cached value. - let mut prefix_set_contains = - |path: &Nibbles| *is_in_prefix_set.get_or_insert_with(|| prefix_set.contains(path)); - - self.inner.rlp_node(prefix_set_contains, path, node); + self.inner.rlp_node(prefix_set, stack_item, node); } debug_assert_eq!(self.inner.buffers.rlp_node_stack.len(), 1); @@ -644,21 +677,69 @@ struct SparseSubtrieInner { } impl SparseSubtrieInner { + /// Computes the RLP encoding and its hash for a single (trie node)[`SparseNode`]. + /// + /// # Deferred Processing + /// + /// When an extension or a branch node depends on child nodes that haven't been computed yet, + /// the function pushes the current node back onto the path stack along with its children, + /// then returns early. This allows the iterative algorithm to process children first before + /// retrying the parent. + /// + /// # Parameters + /// + /// - `prefix_set`: Set of prefixes (key paths) that have been marked as updated + /// - `stack_item`: The stack item to process + /// - `node`: The sparse node to process (will be mutated to update hash) + /// + /// # Side Effects + /// + /// - Updates the node's hash field after computing RLP + /// - Pushes nodes to [`SparseSubtrieBuffers::path_stack`] to manage traversal + /// - Updates the (trie updates)[`SparseTrieUpdates`] accumulator when tracking changes, if + /// [`Some`] + /// - May push items onto the path stack for deferred processing + /// + /// # Exit condition + /// + /// Once all nodes have been processed and all RLPs and hashes calculated, pushes the root node + /// onto the [`SparseSubtrieBuffers::rlp_node_stack`] and exits. fn rlp_node( &mut self, - mut prefix_set_contains: impl FnMut(&Nibbles) -> bool, - path: Nibbles, + prefix_set: &mut PrefixSet, + mut stack_item: RlpNodePathStackItem, node: &mut SparseNode, ) { + let path = stack_item.path; + trace!( + target: "trie::parallel_sparse", + ?path, + ?node, + "Calculating node RLP" + ); + + // Check if the path is in the prefix set. + // First, check the cached value. If it's `None`, then check the prefix set, and update + // the cached value. + let mut prefix_set_contains = |path: &Nibbles| { + *stack_item.is_in_prefix_set.get_or_insert_with(|| prefix_set.contains(path)) + }; + let (rlp_node, node_type) = match node { SparseNode::Empty => (RlpNode::word_rlp(&EMPTY_ROOT_HASH), SparseNodeType::Empty), - SparseNode::Hash(hash) => (RlpNode::word_rlp(hash), SparseNodeType::Hash), + SparseNode::Hash(hash) => { + // Return pre-computed hash of a blinded node immediately + (RlpNode::word_rlp(hash), SparseNodeType::Hash) + } SparseNode::Leaf { key, hash } => { let mut path = path; path.extend(key); if let Some(hash) = hash.filter(|_| !prefix_set_contains(&path)) { + // If the node hash is already computed, and the node path is not in + // the prefix set, return the pre-computed hash (RlpNode::word_rlp(&hash), SparseNodeType::Leaf) } else { + // Encode the leaf node and update its hash let value = self.values.get(&path).unwrap(); self.buffers.rlp_buf.clear(); let rlp_node = LeafNodeRef { key, value }.rlp(&mut self.buffers.rlp_buf); @@ -672,11 +753,15 @@ impl SparseSubtrieInner { if let Some((hash, store_in_db_trie)) = hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) { + // If the node hash is already computed, and the node path is not in + // the prefix set, return the pre-computed hash ( RlpNode::word_rlp(&hash), SparseNodeType::Extension { store_in_db_trie: Some(store_in_db_trie) }, ) } else if self.buffers.rlp_node_stack.last().is_some_and(|e| e.path == child_path) { + // Top of the stack has the child node, we can encode the extension node and + // update its hash let RlpNodeStackItem { path: _, rlp_node: child, node_type: child_node_type } = self.buffers.rlp_node_stack.pop().unwrap(); self.buffers.rlp_buf.clear(); @@ -705,7 +790,8 @@ impl SparseSubtrieInner { }, ) } else { - // need to get rlp node for child first + // Need to defer processing until child is computed, on the next + // invocation update the node's hash. self.buffers.path_stack.extend([ RlpNodePathStackItem { path, @@ -720,6 +806,8 @@ impl SparseSubtrieInner { if let Some((hash, store_in_db_trie)) = hash.zip(*store_in_db_trie).filter(|_| !prefix_set_contains(&path)) { + // If the node hash is already computed, and the node path is not in + // the prefix set, return the pre-computed hash self.buffers.rlp_node_stack.push(RlpNodeStackItem { path, rlp_node: RlpNode::word_rlp(&hash), @@ -729,6 +817,7 @@ impl SparseSubtrieInner { }); return } + let retain_updates = self.updates.is_some() && prefix_set_contains(&path); self.buffers.branch_child_buf.clear(); @@ -804,6 +893,8 @@ impl SparseSubtrieInner { self.buffers.branch_value_stack_buf[original_idx] = child; added_children = true; } else { + // Need to defer processing until children are computed, on the next + // invocation update the node's hash. debug_assert!(!added_children); self.buffers.path_stack.push(RlpNodePathStackItem { path, @@ -827,6 +918,8 @@ impl SparseSubtrieInner { "Branch node masks" ); + // Top of the stack has all children node, we can encode the branch node and + // update its hash self.buffers.rlp_buf.clear(); let branch_node_ref = BranchNodeRef::new(&self.buffers.branch_value_stack_buf, *state_mask); @@ -888,15 +981,13 @@ impl SparseSubtrieInner { } }; + self.buffers.rlp_node_stack.push(RlpNodeStackItem { path, rlp_node, node_type }); trace!( target: "trie::parallel_sparse", ?path, - ?node, ?node_type, - "Added node to rlp node stack" + "Added node to RLP node stack" ); - - self.buffers.rlp_node_stack.push(RlpNodeStackItem { path, rlp_node, node_type }); } } @@ -1479,13 +1570,31 @@ mod tests { fn test_update_subtrie_hashes() { // Create a trie with three subtries let mut trie = ParallelSparseTrie::default(); - let subtrie_1 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0]))); + let mut subtrie_1 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x0, 0x0]))); let subtrie_1_index = path_subtrie_index_unchecked(&subtrie_1.path); - let subtrie_2 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0]))); + let mut subtrie_2 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x1, 0x0]))); let subtrie_2_index = path_subtrie_index_unchecked(&subtrie_2.path); - let subtrie_3 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0]))); + let mut subtrie_3 = Box::new(SparseSubtrie::new(Nibbles::from_nibbles([0x3, 0x0]))); let subtrie_3_index = path_subtrie_index_unchecked(&subtrie_3.path); + // Reveal dummy leaf nodes that form an incorrect trie structure but enough to test the + // method + let leaf_1_full_path = Nibbles::from_nibbles([0; 64]); + let leaf_1_path = leaf_1_full_path.slice(..2); + let leaf_1_key = leaf_1_full_path.slice(2..); + let leaf_2_full_path = Nibbles::from_nibbles([vec![1, 0], vec![0; 62]].concat()); + let leaf_2_path = leaf_2_full_path.slice(..2); + let leaf_2_key = leaf_2_full_path.slice(2..); + let leaf_3_full_path = Nibbles::from_nibbles([vec![3, 0], vec![0; 62]].concat()); + let leaf_3_path = leaf_3_full_path.slice(..2); + let leaf_3_key = leaf_3_full_path.slice(2..); + let leaf_1 = create_leaf_node(leaf_1_key.to_vec(), 1); + let leaf_2 = create_leaf_node(leaf_2_key.to_vec(), 2); + let leaf_3 = create_leaf_node(leaf_3_key.to_vec(), 3); + subtrie_1.reveal_node(leaf_1_path, &leaf_1, TrieMasks::none()).unwrap(); + subtrie_2.reveal_node(leaf_2_path, &leaf_2, TrieMasks::none()).unwrap(); + subtrie_3.reveal_node(leaf_3_path, &leaf_3, TrieMasks::none()).unwrap(); + // Add subtries at specific positions trie.lower_subtries[subtrie_1_index] = Some(subtrie_1); trie.lower_subtries[subtrie_2_index] = Some(subtrie_2); @@ -1505,7 +1614,7 @@ mod tests { trie.prefix_set = prefix_set; // Update subtrie hashes - trie.update_subtrie_hashes(); + trie.update_lower_subtrie_hashes(); // Check that the prefix set was updated assert_eq!(trie.prefix_set, unchanged_prefix_set); @@ -1633,4 +1742,105 @@ mod tests { let subtrie_leaf_3_hash = subtrie.nodes.get(&leaf_3_path).unwrap().hash().unwrap(); assert_eq!(hash_builder_leaf_3_hash, subtrie_leaf_3_hash); } + + #[test] + fn test_parallel_sparse_trie_root() { + let mut trie = ParallelSparseTrie::default().with_updates(true); + + // Step 1: Create the trie structure + // Extension node at 0x with key 0x2 (goes to upper subtrie) + let extension_path = Nibbles::new(); + let extension_key = Nibbles::from_nibbles([0x2]); + + // Branch node at 0x2 with children 0 and 1 (goes to upper subtrie) + let branch_path = Nibbles::from_nibbles([0x2]); + + // Leaf nodes at 0x20 and 0x21 (go to lower subtries) + let leaf_1_path = Nibbles::from_nibbles([0x2, 0x0]); + let leaf_1_key = Nibbles::from_nibbles(vec![0; 62]); // Remaining key + let leaf_1_full_path = Nibbles::from_nibbles([vec![0x2, 0x0], vec![0; 62]].concat()); + + let leaf_2_path = Nibbles::from_nibbles([0x2, 0x1]); + let leaf_2_key = Nibbles::from_nibbles(vec![0; 62]); // Remaining key + let leaf_2_full_path = Nibbles::from_nibbles([vec![0x2, 0x1], vec![0; 62]].concat()); + + // Create accounts + let account_1 = create_account(1); + let account_2 = create_account(2); + + // Create leaf nodes + let leaf_1 = create_leaf_node(leaf_1_key.to_vec(), account_1.nonce); + let leaf_2 = create_leaf_node(leaf_2_key.to_vec(), account_2.nonce); + + // Create branch node with children at indices 0 and 1 + let branch = create_branch_node_with_children( + &[0, 1], + vec![ + RlpNode::from_rlp(&alloy_rlp::encode(&leaf_1)), + RlpNode::from_rlp(&alloy_rlp::encode(&leaf_2)), + ], + ); + + // Create extension node pointing to branch + let extension = create_extension_node( + extension_key.to_vec(), + RlpNode::from_rlp(&alloy_rlp::encode(&branch)).as_hash().unwrap(), + ); + + // Step 2: Reveal nodes in the trie + trie.reveal_node(extension_path, extension, TrieMasks::none()).unwrap(); + trie.reveal_node(branch_path, branch, TrieMasks::none()).unwrap(); + trie.reveal_node(leaf_1_path, leaf_1, TrieMasks::none()).unwrap(); + trie.reveal_node(leaf_2_path, leaf_2, TrieMasks::none()).unwrap(); + + // Step 3: Reset hashes for all revealed nodes to test actual hash calculation + // Reset upper subtrie node hashes + trie.upper_subtrie.nodes.get_mut(&extension_path).unwrap().set_hash(None); + trie.upper_subtrie.nodes.get_mut(&branch_path).unwrap().set_hash(None); + + // Reset lower subtrie node hashes + let leaf_1_subtrie_idx = path_subtrie_index_unchecked(&leaf_1_path); + let leaf_2_subtrie_idx = path_subtrie_index_unchecked(&leaf_2_path); + + trie.lower_subtries[leaf_1_subtrie_idx] + .as_mut() + .unwrap() + .nodes + .get_mut(&leaf_1_path) + .unwrap() + .set_hash(None); + trie.lower_subtries[leaf_2_subtrie_idx] + .as_mut() + .unwrap() + .nodes + .get_mut(&leaf_2_path) + .unwrap() + .set_hash(None); + + // Step 4: Add changed leaf node paths to prefix set + trie.prefix_set.insert(leaf_1_full_path); + trie.prefix_set.insert(leaf_2_full_path); + + // Step 5: Calculate root using our implementation + let root = trie.root(); + + // Step 6: Calculate root using HashBuilder for comparison + let (hash_builder_root, _, _proof_nodes, _, _) = run_hash_builder( + [(leaf_1_full_path, account_1), (leaf_2_full_path, account_2)], + NoopAccountTrieCursor::default(), + Default::default(), + [extension_path, branch_path, leaf_1_full_path, leaf_2_full_path], + ); + + // Step 7: Verify the roots match + assert_eq!(root, hash_builder_root); + + // Verify hashes were computed + let leaf_1_subtrie = trie.lower_subtries[leaf_1_subtrie_idx].as_ref().unwrap(); + let leaf_2_subtrie = trie.lower_subtries[leaf_2_subtrie_idx].as_ref().unwrap(); + assert!(trie.upper_subtrie.nodes.get(&extension_path).unwrap().hash().is_some()); + assert!(trie.upper_subtrie.nodes.get(&branch_path).unwrap().hash().is_some()); + assert!(leaf_1_subtrie.nodes.get(&leaf_1_path).unwrap().hash().is_some()); + assert!(leaf_2_subtrie.nodes.get(&leaf_2_path).unwrap().hash().is_some()); + } } diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 619cadac8f5..e2f28c2417f 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -2063,6 +2063,21 @@ impl SparseNode { } } } + + /// Sets the hash of the node for testing purposes. + /// + /// For [`SparseNode::Empty`] and [`SparseNode::Hash`] nodes, this method does nothing. + #[cfg(any(test, feature = "test-utils"))] + pub const fn set_hash(&mut self, new_hash: Option) { + match self { + Self::Empty | Self::Hash(_) => { + // Cannot set hash for Empty or Hash nodes + } + Self::Leaf { hash, .. } | Self::Extension { hash, .. } | Self::Branch { hash, .. } => { + *hash = new_hash; + } + } + } } /// A helper struct used to store information about a node that has been removed From 14c6b5f5e36de0268ed01b69fb3863a13ed082a0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 25 Jun 2025 13:26:39 +0200 Subject: [PATCH 218/274] chore: use payload_builder target (#17049) --- crates/payload/builder/src/service.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/payload/builder/src/service.rs b/crates/payload/builder/src/service.rs index 928b7747218..3c4daf25557 100644 --- a/crates/payload/builder/src/service.rs +++ b/crates/payload/builder/src/service.rs @@ -337,7 +337,7 @@ where .map(|(j, _)| j.payload_attributes()); if attributes.is_none() { - trace!(%id, "no matching payload job found to get attributes for"); + trace!(target: "payload_builder", %id, "no matching payload job found to get attributes for"); } attributes @@ -374,10 +374,10 @@ where match job.poll_unpin(cx) { Poll::Ready(Ok(_)) => { this.metrics.set_active_jobs(this.payload_jobs.len()); - trace!(%id, "payload job finished"); + trace!(target: "payload_builder", %id, "payload job finished"); } Poll::Ready(Err(err)) => { - warn!(%err, ?id, "Payload builder job failed; resolving payload"); + warn!(target: "payload_builder",%err, ?id, "Payload builder job failed; resolving payload"); this.metrics.inc_failed_jobs(); this.metrics.set_active_jobs(this.payload_jobs.len()); } @@ -399,13 +399,13 @@ where let mut res = Ok(id); if this.contains_payload(id) { - debug!(%id, parent = %attr.parent(), "Payload job already in progress, ignoring."); + debug!(target: "payload_builder",%id, parent = %attr.parent(), "Payload job already in progress, ignoring."); } else { // no job for this payload yet, create one let parent = attr.parent(); match this.generator.new_payload_job(attr.clone()) { Ok(job) => { - info!(%id, %parent, "New payload job created"); + info!(target: "payload_builder", %id, %parent, "New payload job created"); this.metrics.inc_initiated_jobs(); new_job = true; this.payload_jobs.push((job, id)); @@ -413,7 +413,7 @@ where } Err(err) => { this.metrics.inc_failed_jobs(); - warn!(%err, %id, "Failed to create payload builder job"); + warn!(target: "payload_builder", %err, %id, "Failed to create payload builder job"); res = Err(err); } } From 51bda0dcb77de89b4886a5d5903f6cc5247ab1a5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 25 Jun 2025 13:47:10 +0200 Subject: [PATCH 219/274] chore: use earliest block number (#17044) --- .../provider/src/providers/blockchain_provider.rs | 4 ++++ crates/storage/provider/src/providers/database/mod.rs | 11 +++++++++++ .../provider/src/providers/static_file/manager.rs | 2 ++ 3 files changed, 17 insertions(+) diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 5ee86e8cd73..5bc5e707153 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -261,6 +261,10 @@ impl BlockNumReader for BlockchainProvider { self.database.last_block_number() } + fn earliest_block_number(&self) -> ProviderResult { + self.database.earliest_block_number() + } + fn block_number(&self, hash: B256) -> ProviderResult> { self.consistent_provider()?.block_number(hash) } diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 1801c148ecb..6f735942607 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -345,6 +345,17 @@ impl BlockNumReader for ProviderFactory { self.provider()?.last_block_number() } + fn earliest_block_number(&self) -> ProviderResult { + // expired height tracks the lowest block number that has been expired, therefore the + // earliest block number is one more than that. + let mut earliest = self.static_file_provider.expired_history_height(); + if earliest > 0 { + // If the expired history height is 0, then the earliest block number is still 0. + earliest += 1; + } + Ok(earliest) + } + fn block_number(&self, hash: B256) -> ProviderResult> { self.provider()?.block_number(hash) } diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index c54b4f28e0a..3d4b0083150 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -1019,6 +1019,8 @@ impl StaticFileProvider { /// /// The earliest block that is still available in the static files is `expired_history_height + /// 1`. + /// + /// Returns `0` if no history has been expired. pub fn expired_history_height(&self) -> BlockNumber { self.expired_history_height.load(std::sync::atomic::Ordering::Relaxed) } From 56f6da5ed1f3fc7745218eccb743eb97cc49c8bd Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 25 Jun 2025 14:13:22 +0100 Subject: [PATCH 220/274] feat: make jwt-secret argument consistent across reth-bench commands (#17050) Co-authored-by: Claude --- crates/node/core/src/args/benchmark_args.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/node/core/src/args/benchmark_args.rs b/crates/node/core/src/args/benchmark_args.rs index 1ff49c9c84d..0f2a2b2d68c 100644 --- a/crates/node/core/src/args/benchmark_args.rs +++ b/crates/node/core/src/args/benchmark_args.rs @@ -21,7 +21,13 @@ pub struct BenchmarkArgs { /// /// If no path is provided, a secret will be generated and stored in the datadir under /// `//jwt.hex`. For mainnet this would be `~/.reth/mainnet/jwt.hex` by default. - #[arg(long = "jwtsecret", value_name = "PATH", global = true, required = false)] + #[arg( + long = "jwt-secret", + alias = "jwtsecret", + value_name = "PATH", + global = true, + required = false + )] pub auth_jwtsecret: Option, /// The RPC url to use for sending engine requests. From eef134521c567aeeea1e20210bd4264ac92b5947 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 25 Jun 2025 15:54:37 +0200 Subject: [PATCH 221/274] chore: Add precompile cache hit rate graph to grafana overview (#17055) --- etc/grafana/dashboards/overview.json | 104 +++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 88a8d437971..af794a3b1c5 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -3792,6 +3792,110 @@ "title": "Block validation overhead", "type": "timeseries" }, + { + "datasource": { + "type": "prometheus", + "uid": "${datasource}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "max": 1, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 87 + }, + "id": 1005, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.5.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "reth_sync_caching_precompile_cache_hits{$instance_label=\"$instance\"} / (reth_sync_caching_precompile_cache_hits{$instance_label=\"$instance\"} + reth_sync_caching_precompile_cache_misses{$instance_label=\"$instance\"})", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "Precompile cache hits", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Precompile cache hitrate", + "type": "timeseries" + }, { "collapsed": false, "gridPos": { From 7267734d5c1bedcd34d8bb2e900fb6c982d76ddd Mon Sep 17 00:00:00 2001 From: Eric Woolsey Date: Wed, 25 Jun 2025 16:20:24 +0200 Subject: [PATCH 222/274] chore: delete reth-performance dashboard (#16635) --- etc/grafana/dashboards/reth-performance.json | 346 ------------------- 1 file changed, 346 deletions(-) delete mode 100644 etc/grafana/dashboards/reth-performance.json diff --git a/etc/grafana/dashboards/reth-performance.json b/etc/grafana/dashboards/reth-performance.json deleted file mode 100644 index 02d890dceef..00000000000 --- a/etc/grafana/dashboards/reth-performance.json +++ /dev/null @@ -1,346 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "Prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__elements": {}, - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "11.1.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "1.0.0" - }, - { - "type": "panel", - "id": "timeseries", - "name": "Time series", - "version": "" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "id": null, - "links": [], - "panels": [ - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 3, - "panels": [], - "title": "Block Validation", - "type": "row" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "This tracks the proportion of various tasks that take up time during block validation", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 25, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "percent" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 1 - }, - "id": 1, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "reth_sync_block_validation_state_root_duration{instance=\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "State Root Duration", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "reth_sync_execution_execution_duration{instance=\"$instance\"}", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "Execution Duration", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Block Validation Overview", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "This tracks the total block validation latency, as well as the latency for validation sub-tasks ", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 25, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "s" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 1 - }, - "id": 2, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "reth_sync_block_validation_state_root_duration{instance=\"$instance\"}", - "fullMetaSearch": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "State Root Duration", - "range": true, - "refId": "A", - "useBackend": false - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "disableTextWrap": false, - "editorMode": "builder", - "expr": "reth_sync_execution_execution_duration{instance=\"$instance\"}", - "fullMetaSearch": false, - "hide": false, - "includeNullMetadata": true, - "instant": false, - "legendFormat": "Execution Duration", - "range": true, - "refId": "B", - "useBackend": false - } - ], - "title": "Block Validation Latency", - "type": "timeseries" - } - ], - "refresh": "30s", - "schemaVersion": 39, - "tags": [], - "templating": { - "list": [ - { - "current": {}, - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "definition": "query_result(reth_info)", - "hide": 0, - "includeAll": false, - "label": "instance", - "multi": false, - "name": "instance", - "options": [], - "query": { - "qryType": 3, - "query": "query_result(reth_info)", - "refId": "PrometheusVariableQueryEditor-VariableQuery" - }, - "refresh": 1, - "regex": "/.*instance=\\\"([^\\\"]*).*/", - "skipUrlSync": false, - "sort": 0, - "type": "query" - } - ] - }, - "time": { - "from": "now-1h", - "to": "now" - }, - "timepicker": {}, - "timezone": "browser", - "title": "Reth - Performance", - "uid": "bdywb3xjphfy8a", - "version": 2, - "weekStart": "" -} From 30110bca049a7d50ca53c6378e693287bcddaf5a Mon Sep 17 00:00:00 2001 From: Maximilian Hubert <64627729+gap-editor@users.noreply.github.com> Date: Wed, 25 Jun 2025 17:21:49 +0200 Subject: [PATCH 223/274] docs: fix typo "takes effect" (#17053) --- crates/storage/libmdbx-rs/src/environment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/libmdbx-rs/src/environment.rs b/crates/storage/libmdbx-rs/src/environment.rs index fac3cd5084c..ba3ecb95c42 100644 --- a/crates/storage/libmdbx-rs/src/environment.rs +++ b/crates/storage/libmdbx-rs/src/environment.rs @@ -258,7 +258,7 @@ unsafe impl Sync for EnvironmentInner {} /// Determines how data is mapped into memory /// -/// It only takes affect when the environment is opened. +/// It only takes effect when the environment is opened. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum EnvironmentKind { /// Open the environment in default mode, without WRITEMAP. From f6278a19895417cc1abf967f0fc92ca97af8b2b7 Mon Sep 17 00:00:00 2001 From: 0xsensei Date: Thu, 26 Jun 2025 02:35:54 +0530 Subject: [PATCH 224/274] feat(trie): add assert_eq_parallel_sparse_trie_proof_nodes (#17052) Co-authored-by: Aditya Pandey --- crates/trie/sparse-parallel/src/trie.rs | 60 ++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 45b6b6b5795..7b6402d4d37 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -1094,9 +1094,10 @@ mod tests { map::{B256Set, HashMap}, B256, }; - use alloy_rlp::Encodable; + use alloy_rlp::{Decodable, Encodable}; use alloy_trie::Nibbles; use assert_matches::assert_matches; + use itertools::Itertools; use reth_primitives_traits::Account; use reth_trie::{ hashed_cursor::{noop::NoopHashedAccountCursor, HashedPostStateAccountCursor}, @@ -1224,6 +1225,63 @@ mod tests { (root, trie_updates, proof_nodes, branch_node_hash_masks, branch_node_tree_masks) } + /// Assert that the parallel sparse trie nodes and the proof nodes from the hash builder are + /// equal. + #[allow(unused)] + fn assert_eq_parallel_sparse_trie_proof_nodes( + sparse_trie: &ParallelSparseTrie, + proof_nodes: ProofNodes, + ) { + let proof_nodes = proof_nodes + .into_nodes_sorted() + .into_iter() + .map(|(path, node)| (path, TrieNode::decode(&mut node.as_ref()).unwrap())); + + let lower_sparse_nodes = sparse_trie + .lower_subtries + .iter() + .filter_map(Option::as_ref) + .flat_map(|subtrie| subtrie.nodes.iter()); + + let upper_sparse_nodes = sparse_trie.upper_subtrie.nodes.iter(); + + let all_sparse_nodes = + lower_sparse_nodes.chain(upper_sparse_nodes).sorted_by_key(|(path, _)| *path); + + for ((proof_node_path, proof_node), (sparse_node_path, sparse_node)) in + proof_nodes.zip(all_sparse_nodes) + { + assert_eq!(&proof_node_path, sparse_node_path); + + let equals = match (&proof_node, &sparse_node) { + // Both nodes are empty + (TrieNode::EmptyRoot, SparseNode::Empty) => true, + // Both nodes are branches and have the same state mask + ( + TrieNode::Branch(BranchNode { state_mask: proof_state_mask, .. }), + SparseNode::Branch { state_mask: sparse_state_mask, .. }, + ) => proof_state_mask == sparse_state_mask, + // Both nodes are extensions and have the same key + ( + TrieNode::Extension(ExtensionNode { key: proof_key, .. }), + SparseNode::Extension { key: sparse_key, .. }, + ) | + // Both nodes are leaves and have the same key + ( + TrieNode::Leaf(LeafNode { key: proof_key, .. }), + SparseNode::Leaf { key: sparse_key, .. }, + ) => proof_key == sparse_key, + // Empty and hash nodes are specific to the sparse trie, skip them + (_, SparseNode::Empty | SparseNode::Hash(_)) => continue, + _ => false, + }; + assert!( + equals, + "path: {proof_node_path:?}\nproof node: {proof_node:?}\nsparse node: {sparse_node:?}" + ); + } + } + #[test] fn test_get_changed_subtries_empty() { let mut trie = ParallelSparseTrie::default(); From 79d737e6c87fa16d0c82599d644e0519aea07dd8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 25 Jun 2025 23:11:24 +0200 Subject: [PATCH 225/274] chore: bump alloy patches (#17067) --- Cargo.lock | 82 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 64 +++++++++++++++++++++--------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bdc00c91ac..60ebcbd7b2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,7 +113,7 @@ dependencies = [ [[package]] name = "alloy-consensus" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-eips", "alloy-primitives", @@ -138,7 +138,7 @@ dependencies = [ [[package]] name = "alloy-consensus-any" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-eips", @@ -152,7 +152,7 @@ dependencies = [ [[package]] name = "alloy-contract" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -234,7 +234,7 @@ dependencies = [ [[package]] name = "alloy-eips" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -276,7 +276,7 @@ dependencies = [ [[package]] name = "alloy-genesis" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-eips", "alloy-primitives", @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "alloy-json-rpc" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -328,7 +328,7 @@ dependencies = [ [[package]] name = "alloy-network" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -353,7 +353,7 @@ dependencies = [ [[package]] name = "alloy-network-primitives" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-eips", @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "alloy-provider" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-chains", "alloy-consensus", @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "alloy-pubsub" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -509,7 +509,7 @@ dependencies = [ [[package]] name = "alloy-rpc-client" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -536,7 +536,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -548,7 +548,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -559,7 +559,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -570,7 +570,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -580,7 +580,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-eips", "alloy-primitives", @@ -597,7 +597,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "serde", @@ -606,7 +606,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-eips", @@ -626,7 +626,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -646,7 +646,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-eips", @@ -660,7 +660,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -673,7 +673,7 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -684,7 +684,7 @@ dependencies = [ [[package]] name = "alloy-serde" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "arbitrary", @@ -695,7 +695,7 @@ dependencies = [ [[package]] name = "alloy-signer" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "async-trait", @@ -709,7 +709,7 @@ dependencies = [ [[package]] name = "alloy-signer-local" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-consensus", "alloy-network", @@ -796,7 +796,7 @@ dependencies = [ [[package]] name = "alloy-transport" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -818,7 +818,7 @@ dependencies = [ [[package]] name = "alloy-transport-http" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -832,7 +832,7 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -851,7 +851,7 @@ dependencies = [ [[package]] name = "alloy-transport-ws" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "alloy-tx-macros" version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=08fa016ed950b6e65f810fc9cdef7cf38fbc63f6#08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" +source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" dependencies = [ "alloy-primitives", "darling", @@ -2690,7 +2690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.104", ] [[package]] @@ -5930,9 +5930,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.6" +version = "0.18.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5448a4ef9ef1ee241a67fe533ae3563716bbcd719acbd0544ad1ac9f03cfde6" +checksum = "a335f4cbd98a5d7ce0551b296971872e0eddac8802ff9b417f9e9a26ea476ddc" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5956,9 +5956,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.6" +version = "0.18.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4220106305f58d92e566be1a644d776ccfb3bafa6279a2203112d799ea20f5d" +checksum = "97d1fec6af9ec4977f932f8ecf8027e16d53474dabc1357fa0a05ab71eae1f60" dependencies = [ "alloy-consensus", "alloy-network", @@ -5972,9 +5972,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.6" +version = "0.18.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8666d4478630ef2a9b2b5f7d73b4d94f2ff43ce4132d30b433825ffc869aa70" +checksum = "6396255882bb38087dc0099d3631634f764e604994ef7fc8561ced40872a84fd" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -5982,9 +5982,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.6" +version = "0.18.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbf5ee458a2ad66833e7dcc28d78f33d3d4eba53f432c93d7030f5c02331861" +checksum = "e500589dead5a243a17254b0d8713bba1b7f3a96519708adc25a8ddc64578e13" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6001,9 +6001,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.6" +version = "0.18.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c2b1be5c3414f29800d1b29cd16f0049b847687143adb19814de587ecb3f6" +checksum = "bcbce08900e92a17b490d8e24dd18f299a58b3da8b202ed38992d3f56fa42d84" dependencies = [ "alloy-consensus", "alloy-eips", diff --git a/Cargo.toml b/Cargo.toml index b6b64130603..292256b38df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -507,11 +507,11 @@ alloy-transport-ws = { version = "1.0.12", default-features = false } # op alloy-op-evm = { version = "0.12", default-features = false } alloy-op-hardforks = "0.2.2" -op-alloy-rpc-types = { version = "0.18.6", default-features = false } -op-alloy-rpc-types-engine = { version = "0.18.6", default-features = false } -op-alloy-network = { version = "0.18.6", default-features = false } -op-alloy-consensus = { version = "0.18.6", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.18.6", default-features = false } +op-alloy-rpc-types = { version = "0.18.7", default-features = false } +op-alloy-rpc-types-engine = { version = "0.18.7", default-features = false } +op-alloy-network = { version = "0.18.7", default-features = false } +op-alloy-consensus = { version = "0.18.7", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.18.7", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # misc @@ -706,33 +706,33 @@ walkdir = "2.3.3" vergen-git2 = "1.0.5" [patch.crates-io] -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } # op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } From 7349abd12639cebc7a5377a841376b0fd4781bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Wed, 25 Jun 2025 23:16:09 +0200 Subject: [PATCH 226/274] refactor(examples): Use `TransactionEnvelope` macro from `alloy` for `CustomTransaction` in the `custom-node` example (#17057) --- examples/custom-node/src/evm/env.rs | 36 ++--- examples/custom-node/src/evm/executor.rs | 12 +- examples/custom-node/src/pool.rs | 25 +++- examples/custom-node/src/primitives/tx.rs | 129 +++++++++++++++--- .../custom-node/src/primitives/tx_custom.rs | 12 +- .../custom-node/src/primitives/tx_type.rs | 40 ++---- 6 files changed, 171 insertions(+), 83 deletions(-) diff --git a/examples/custom-node/src/evm/env.rs b/examples/custom-node/src/evm/env.rs index 91e7293b92c..dfe0fe93f9c 100644 --- a/examples/custom-node/src/evm/env.rs +++ b/examples/custom-node/src/evm/env.rs @@ -257,28 +257,6 @@ impl TransactionEnv for CustomTxEnv { } } -impl FromRecoveredTx for PaymentTxEnv { - fn from_recovered_tx(tx: &CustomTransaction, sender: Address) -> Self { - PaymentTxEnv(match tx { - CustomTransaction::BuiltIn(tx) => { - OpTransaction::::from_recovered_tx(tx, sender).base - } - CustomTransaction::Other(tx) => TxEnv::from_recovered_tx(tx, sender), - }) - } -} - -impl FromTxWithEncoded for PaymentTxEnv { - fn from_encoded_tx(tx: &CustomTransaction, sender: Address, encoded: Bytes) -> Self { - PaymentTxEnv(match tx { - CustomTransaction::BuiltIn(tx) => { - OpTransaction::::from_encoded_tx(tx, sender, encoded).base - } - CustomTransaction::Other(tx) => TxEnv::from_encoded_tx(tx, sender, encoded), - }) - } -} - impl FromRecoveredTx for TxEnv { fn from_recovered_tx(tx: &TxPayment, caller: Address) -> Self { let TxPayment { @@ -317,6 +295,12 @@ impl FromTxWithEncoded for TxEnv { } } +impl FromTxWithEncoded for TxEnv { + fn from_encoded_tx(tx: &TxPayment, sender: Address, _encoded: Bytes) -> Self { + Self::from_recovered_tx(tx, sender) + } +} + impl FromRecoveredTx for CustomTxEnv { fn from_recovered_tx(tx: &OpTxEnvelope, sender: Address) -> Self { Self::Op(OpTransaction::from_recovered_tx(tx, sender)) @@ -332,8 +316,8 @@ impl FromTxWithEncoded for CustomTxEnv { impl FromRecoveredTx for CustomTxEnv { fn from_recovered_tx(tx: &CustomTransaction, sender: Address) -> Self { match tx { - CustomTransaction::BuiltIn(tx) => Self::from_recovered_tx(tx, sender), - CustomTransaction::Other(tx) => { + CustomTransaction::Op(tx) => Self::from_recovered_tx(tx, sender), + CustomTransaction::Payment(tx) => { Self::Payment(PaymentTxEnv(TxEnv::from_recovered_tx(tx, sender))) } } @@ -343,8 +327,8 @@ impl FromRecoveredTx for CustomTxEnv { impl FromTxWithEncoded for CustomTxEnv { fn from_encoded_tx(tx: &CustomTransaction, sender: Address, encoded: Bytes) -> Self { match tx { - CustomTransaction::BuiltIn(tx) => Self::from_encoded_tx(tx, sender, encoded), - CustomTransaction::Other(tx) => { + CustomTransaction::Op(tx) => Self::from_encoded_tx(tx, sender, encoded), + CustomTransaction::Payment(tx) => { Self::Payment(PaymentTxEnv(TxEnv::from_encoded_tx(tx, sender, encoded))) } } diff --git a/examples/custom-node/src/evm/executor.rs b/examples/custom-node/src/evm/executor.rs index 976c45ef528..2c5a58d7584 100644 --- a/examples/custom-node/src/evm/executor.rs +++ b/examples/custom-node/src/evm/executor.rs @@ -43,13 +43,11 @@ where f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, ) -> Result, BlockExecutionError> { match tx.tx() { - CustomTransaction::BuiltIn(op_tx) => { - self.inner.execute_transaction_with_commit_condition( - Recovered::new_unchecked(op_tx, *tx.signer()), - f, - ) - } - CustomTransaction::Other(..) => todo!(), + CustomTransaction::Op(op_tx) => self.inner.execute_transaction_with_commit_condition( + Recovered::new_unchecked(op_tx, *tx.signer()), + f, + ), + CustomTransaction::Payment(..) => todo!(), } } diff --git a/examples/custom-node/src/pool.rs b/examples/custom-node/src/pool.rs index 8fda09d7129..09f0b667c79 100644 --- a/examples/custom-node/src/pool.rs +++ b/examples/custom-node/src/pool.rs @@ -1,5 +1,28 @@ -use crate::primitives::CustomTransactionEnvelope; +use crate::primitives::{CustomTransaction, CustomTransactionEnvelope}; +use alloy_consensus::error::ValueError; use op_alloy_consensus::OpPooledTransaction; use reth_ethereum::primitives::Extended; pub type CustomPooledTransaction = Extended; + +impl From for CustomTransaction { + fn from(tx: CustomPooledTransaction) -> Self { + match tx { + CustomPooledTransaction::BuiltIn(tx) => Self::Op(tx.into()), + CustomPooledTransaction::Other(tx) => Self::Payment(tx), + } + } +} + +impl TryFrom for CustomPooledTransaction { + type Error = ValueError; + + fn try_from(tx: CustomTransaction) -> Result { + match tx { + CustomTransaction::Op(op) => Ok(Self::BuiltIn( + OpPooledTransaction::try_from(op).map_err(|op| op.map(CustomTransaction::Op))?, + )), + CustomTransaction::Payment(payment) => Ok(Self::Other(payment)), + } + } +} diff --git a/examples/custom-node/src/primitives/tx.rs b/examples/custom-node/src/primitives/tx.rs index b96ffed76ba..48348f6839a 100644 --- a/examples/custom-node/src/primitives/tx.rs +++ b/examples/custom-node/src/primitives/tx.rs @@ -1,14 +1,17 @@ -use super::{TxPayment, TxTypeCustom}; +use super::TxPayment; use alloy_consensus::{ crypto::{ secp256k1::{recover_signer, recover_signer_unchecked}, RecoveryError, }, transaction::SignerRecoverable, - SignableTransaction, Signed, Transaction, + SignableTransaction, Signed, Transaction, TransactionEnvelope, }; -use alloy_eips::{eip2718::Eip2718Result, Decodable2718, Encodable2718, Typed2718}; -use alloy_primitives::{keccak256, Sealed, Signature, TxHash}; +use alloy_eips::{ + eip2718::{Eip2718Result, IsTyped2718}, + Decodable2718, Encodable2718, Typed2718, +}; +use alloy_primitives::{bytes::Buf, keccak256, Sealed, Signature, TxHash, B256}; use alloy_rlp::{BufMut, Decodable, Encodable, Result as RlpResult}; use op_alloy_consensus::{OpTxEnvelope, TxDeposit}; use reth_codecs::{ @@ -16,19 +19,21 @@ use reth_codecs::{ Compact, }; use reth_ethereum::primitives::{serde_bincode_compat::RlpBincode, InMemorySize}; -use reth_op::{ - primitives::{Extended, SignedTransaction}, - OpTransaction, -}; +use reth_op::{primitives::SignedTransaction, OpTransaction}; use revm_primitives::{Address, Bytes}; use serde::{Deserialize, Serialize}; -/// An [`OpTxEnvelope`] that is [`Extended`] by one more variant of [`CustomTransactionEnvelope`]. -pub type CustomTransaction = ExtendedOpTxEnvelope; - -/// A [`SignedTransaction`] implementation that combines the [`OpTxEnvelope`] and another -/// transaction type. -pub type ExtendedOpTxEnvelope = Extended; +/// Either [`OpTxEnvelope`] or [`CustomTransactionEnvelope`]. +#[derive(Debug, Clone, TransactionEnvelope)] +#[envelope(tx_type_name = TxTypeCustom)] +pub enum CustomTransaction { + /// A regular Optimism transaction as defined by [`OpTxEnvelope`]. + #[envelope(flatten)] + Op(OpTxEnvelope), + /// A [`TxPayment`] tagged with type 0x7E. + #[envelope(ty = 42)] + Payment(CustomTransactionEnvelope), +} #[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] pub struct CustomTransactionEnvelope { @@ -98,7 +103,7 @@ impl Transaction for CustomTransactionEnvelope { self.inner.tx().access_list() } - fn blob_versioned_hashes(&self) -> Option<&[revm_primitives::B256]> { + fn blob_versioned_hashes(&self) -> Option<&[B256]> { self.inner.tx().blob_versioned_hashes() } @@ -199,6 +204,7 @@ impl ToTxCompact for CustomTransactionEnvelope { } impl RlpBincode for CustomTransactionEnvelope {} +impl RlpBincode for CustomTransaction {} impl reth_codecs::alloy::transaction::Envelope for CustomTransactionEnvelope { fn signature(&self) -> &Signature { @@ -206,14 +212,14 @@ impl reth_codecs::alloy::transaction::Envelope for CustomTransactionEnvelope { } fn tx_type(&self) -> Self::TxType { - TxTypeCustom::Custom + TxTypeCustom::Payment } } impl Compact for CustomTransactionEnvelope { fn to_compact(&self, buf: &mut B) -> usize where - B: alloy_rlp::bytes::BufMut + AsMut<[u8]>, + B: BufMut + AsMut<[u8]>, { self.inner.tx().to_compact(buf) } @@ -226,6 +232,31 @@ impl Compact for CustomTransactionEnvelope { } } +impl reth_codecs::Compact for CustomTransaction { + fn to_compact(&self, buf: &mut Buf) -> usize + where + Buf: BufMut + AsMut<[u8]>, + { + buf.put_u8(self.ty()); + match self { + Self::Op(tx) => tx.to_compact(buf), + Self::Payment(tx) => tx.to_compact(buf), + } + } + + fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) { + let type_byte = buf.get_u8(); + + if ::is_type(type_byte) { + let (tx, remaining) = OpTxEnvelope::from_compact(buf, len); + return (Self::Op(tx), remaining); + } + + let (tx, remaining) = CustomTransactionEnvelope::from_compact(buf, len); + (Self::Payment(tx), remaining) + } +} + impl OpTransaction for CustomTransactionEnvelope { fn is_deposit(&self) -> bool { false @@ -235,3 +266,67 @@ impl OpTransaction for CustomTransactionEnvelope { None } } + +impl OpTransaction for CustomTransaction { + fn is_deposit(&self) -> bool { + match self { + CustomTransaction::Op(op) => op.is_deposit(), + CustomTransaction::Payment(payment) => payment.is_deposit(), + } + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + CustomTransaction::Op(op) => op.as_deposit(), + CustomTransaction::Payment(payment) => payment.as_deposit(), + } + } +} + +impl SignerRecoverable for CustomTransaction { + fn recover_signer(&self) -> Result { + match self { + CustomTransaction::Op(tx) => SignerRecoverable::recover_signer(tx), + CustomTransaction::Payment(tx) => SignerRecoverable::recover_signer(tx), + } + } + + fn recover_signer_unchecked(&self) -> Result { + match self { + CustomTransaction::Op(tx) => SignerRecoverable::recover_signer_unchecked(tx), + CustomTransaction::Payment(tx) => SignerRecoverable::recover_signer_unchecked(tx), + } + } +} + +impl SignedTransaction for CustomTransaction { + fn recover_signer_unchecked_with_buf( + &self, + buf: &mut Vec, + ) -> Result { + match self { + CustomTransaction::Op(tx) => { + SignedTransaction::recover_signer_unchecked_with_buf(tx, buf) + } + CustomTransaction::Payment(tx) => { + SignedTransaction::recover_signer_unchecked_with_buf(tx, buf) + } + } + } + + fn tx_hash(&self) -> &B256 { + match self { + CustomTransaction::Op(tx) => SignedTransaction::tx_hash(tx), + CustomTransaction::Payment(tx) => SignedTransaction::tx_hash(tx), + } + } +} + +impl InMemorySize for CustomTransaction { + fn size(&self) -> usize { + match self { + CustomTransaction::Op(tx) => InMemorySize::size(tx), + CustomTransaction::Payment(tx) => InMemorySize::size(tx), + } + } +} diff --git a/examples/custom-node/src/primitives/tx_custom.rs b/examples/custom-node/src/primitives/tx_custom.rs index 8ff92d2f626..8729378bd59 100644 --- a/examples/custom-node/src/primitives/tx_custom.rs +++ b/examples/custom-node/src/primitives/tx_custom.rs @@ -1,4 +1,4 @@ -use crate::primitives::{TxTypeCustom, TRANSFER_TX_TYPE_ID}; +use crate::primitives::PAYMENT_TX_TYPE_ID; use alloy_consensus::{ transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx}, SignableTransaction, Transaction, @@ -71,8 +71,8 @@ pub struct TxPayment { impl TxPayment { /// Get the transaction type #[doc(alias = "transaction_type")] - pub const fn tx_type() -> TxTypeCustom { - TxTypeCustom::Custom + pub const fn tx_type() -> super::tx::TxTypeCustom { + super::tx::TxTypeCustom::Payment } /// Calculates a heuristic for the in-memory size of the [TxPayment] @@ -115,7 +115,7 @@ impl RlpEcdsaEncodableTx for TxPayment { } impl RlpEcdsaDecodableTx for TxPayment { - const DEFAULT_TX_TYPE: u8 = { Self::tx_type() as u8 }; + const DEFAULT_TX_TYPE: u8 = { PAYMENT_TX_TYPE_ID }; /// Decodes the inner [TxPayment] fields from RLP bytes. /// @@ -244,7 +244,7 @@ impl Transaction for TxPayment { impl Typed2718 for TxPayment { fn ty(&self) -> u8 { - TRANSFER_TX_TYPE_ID + PAYMENT_TX_TYPE_ID } } @@ -254,7 +254,7 @@ impl SignableTransaction for TxPayment { } fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - out.put_u8(Self::tx_type() as u8); + out.put_u8(Self::tx_type().ty()); self.encode(out) } diff --git a/examples/custom-node/src/primitives/tx_type.rs b/examples/custom-node/src/primitives/tx_type.rs index 36160024792..20b9e4be4cd 100644 --- a/examples/custom-node/src/primitives/tx_type.rs +++ b/examples/custom-node/src/primitives/tx_type.rs @@ -1,21 +1,8 @@ +use crate::primitives::TxTypeCustom; use alloy_primitives::bytes::{Buf, BufMut}; use reth_codecs::{txtype::COMPACT_EXTENDED_IDENTIFIER_FLAG, Compact}; -use serde::{Deserialize, Serialize}; -pub const TRANSFER_TX_TYPE_ID: u8 = 42; - -/// An enum for the custom transaction type(s) -#[repr(u8)] -#[derive(Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq)] -pub enum TxTypeCustom { - Custom = TRANSFER_TX_TYPE_ID, -} - -impl From for u8 { - fn from(value: TxTypeCustom) -> Self { - value as Self - } -} +pub const PAYMENT_TX_TYPE_ID: u8 = 42; impl Compact for TxTypeCustom { fn to_compact(&self, buf: &mut B) -> usize @@ -23,26 +10,27 @@ impl Compact for TxTypeCustom { B: BufMut + AsMut<[u8]>, { match self { - Self::Custom => { - buf.put_u8(TRANSFER_TX_TYPE_ID); + Self::Op(ty) => ty.to_compact(buf), + Self::Payment => { + buf.put_u8(PAYMENT_TX_TYPE_ID); COMPACT_EXTENDED_IDENTIFIER_FLAG } } } fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) { - ( - match identifier { - COMPACT_EXTENDED_IDENTIFIER_FLAG => { + match identifier { + COMPACT_EXTENDED_IDENTIFIER_FLAG => ( + { let extended_identifier = buf.get_u8(); match extended_identifier { - TRANSFER_TX_TYPE_ID => Self::Custom, + PAYMENT_TX_TYPE_ID => Self::Payment, _ => panic!("Unsupported TxType identifier: {extended_identifier}"), } - } - _ => panic!("Unknown identifier for TxType: {identifier}"), - }, - buf, - ) + }, + buf, + ), + v => Self::from_compact(buf, v), + } } } From bde35a329cc5a7ed0965f7d5f54ccaf020bea994 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Wed, 25 Jun 2025 22:20:29 +0100 Subject: [PATCH 227/274] docs: add libmdbx restriction to CLAUDE.md (#17060) Co-authored-by: Claude --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index 5d53439d235..fe757610ef4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -162,6 +162,7 @@ Based on PR patterns, avoid: 2. **Mixing unrelated changes**: One logical change per PR 3. **Ignoring CI failures**: All checks must pass 4. **Incomplete implementations**: Finish features before submitting +5. **Modifying libmdbx sources**: Never modify files in `crates/storage/libmdbx-rs/mdbx-sys/libmdbx/` - this is vendored third-party code ### CI Requirements From 142c6342e332937b8c84dea347e7bd9c1d8bd13a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 25 Jun 2025 23:49:41 +0200 Subject: [PATCH 228/274] fix(cli): propagate max-tx-input-bytes setting (#17066) --- crates/ethereum/node/src/node.rs | 1 + crates/optimism/node/src/node.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 1d6488b62f1..6dc0d66a1b4 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -397,6 +397,7 @@ where let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) .with_head_timestamp(ctx.head().timestamp) + .with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes) .kzg_settings(ctx.kzg_settings()?) .with_local_transactions_config(pool_config.local_transactions_config.clone()) .set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap) diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index d4870ae77a2..2d33f05f4ae 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -803,6 +803,7 @@ where let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone()) .no_eip4844() .with_head_timestamp(ctx.head().timestamp) + .with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes) .kzg_settings(ctx.kzg_settings()?) .set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap) .with_additional_tasks( From 988c0f0c53e564a4716ce81828dca21c034fe140 Mon Sep 17 00:00:00 2001 From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:17:22 +0300 Subject: [PATCH 229/274] docs: typo in comment for `get_pending_transactions_by_origin` (#17065) --- crates/transaction-pool/src/pool/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index 90bd7dcb207..5f99790c080 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -902,7 +902,7 @@ where .collect() } - /// Returns all pending transactions filted by [`TransactionOrigin`] + /// Returns all pending transactions filtered by [`TransactionOrigin`] pub fn get_pending_transactions_by_origin( &self, origin: TransactionOrigin, From 4e4937ffd15a5bc5431a9ade512eace244d350b6 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 26 Jun 2025 10:30:31 +0200 Subject: [PATCH 230/274] feat: include eth_sendRawTransactionSync in eth namesapce (#17070) --- crates/rpc/rpc-eth-api/src/core.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/rpc/rpc-eth-api/src/core.rs b/crates/rpc/rpc-eth-api/src/core.rs index f7185a73c11..0f2b9eb3896 100644 --- a/crates/rpc/rpc-eth-api/src/core.rs +++ b/crates/rpc/rpc-eth-api/src/core.rs @@ -339,6 +339,12 @@ pub trait EthApi RpcResult; + /// Sends a signed transaction and awaits the transaction receipt. + /// + /// This will return a timeout error if the transaction isn't included within some time period. + #[method(name = "sendRawTransactionSync")] + async fn send_raw_transaction_sync(&self, bytes: Bytes) -> RpcResult; + /// Returns an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" /// + len(message) + message))). #[method(name = "sign")] @@ -804,6 +810,12 @@ where Ok(EthTransactions::send_raw_transaction(self, tx).await?) } + /// Handler for: `eth_sendRawTransactionSync` + async fn send_raw_transaction_sync(&self, tx: Bytes) -> RpcResult> { + trace!(target: "rpc::eth", ?tx, "Serving eth_sendRawTransactionSync"); + Ok(EthTransactions::send_raw_transaction_sync(self, tx).await?) + } + /// Handler for: `eth_sign` async fn sign(&self, address: Address, message: Bytes) -> RpcResult { trace!(target: "rpc::eth", ?address, ?message, "Serving eth_sign"); From f9e6b10730a546a33185e82fc69024b6a1e25bfc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 26 Jun 2025 10:58:39 +0200 Subject: [PATCH 231/274] chore: bump alloy 1.0.13 (#17072) --- Cargo.lock | 152 ++++++++++++++++++++++++++++++++--------------------- Cargo.toml | 110 +++++++++++++++++++------------------- 2 files changed, 146 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60ebcbd7b2e..1021e09bc94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,8 +112,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64ec76fe727969728f4396e3f410cdd825042d81d5017bfa5e58bf349071290" dependencies = [ "alloy-eips", "alloy-primitives", @@ -137,8 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec637158ca8070cab850524ad258a8b7cee090e1e1ce393426c4247927f0acb0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -151,8 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719905011359b266bdbf1f76f4ef7df6df75ef33c55c47a1f6b26bbdefbf4cb0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -233,8 +236,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d95a9829fef493824aad09b3e124e95c5320f8f6b3b9943fd7f3b07b4d21ddd" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -275,8 +279,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a004f3ce55b81321147249d8a5e9b7da83dbb7291555ef8b61834b1a08249e" dependencies = [ "alloy-eips", "alloy-primitives", @@ -313,8 +318,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aeb6bdadf68e4f075147141af9631e4ea8af57649b0738db8c4473f9872b5f" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -327,8 +333,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5fe3fc1d6e5e5914e239f9fb7fb06a55b1bb8fe6e1985933832bd92da327a20" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -352,8 +359,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140e30026c6f1ed5a10c02b312735b1b84d07af74d1a90c87c38ad31909dd5f2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -423,8 +431,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58276c10466554bcd9dd93cd5ea349d2c6d28e29d59ad299e200b3eedbea205" dependencies = [ "alloy-chains", "alloy-consensus", @@ -466,8 +475,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419cd1a961142f6fb8575e3dead318f07cf95bc19f1cb739ff0dbe1f7b713355" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -508,8 +518,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ede96b42460d865ff3c26ce604aa927c829a7b621675d0a7bdfc8cdc9d5f7fb" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -535,8 +546,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34217bac065fd6c5197ecb4bbdcf82ce28893c679d90d98e4e5a2700b7994f69" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -547,8 +559,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb178fa8f0b892d4e3eebde91b216d87538e82340655ac6a6eb4c50c2a41e407" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -558,8 +571,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263a868fbe924c00f4af91532a8a890dfeb0af6199ca9641f2f9322fa86e437e" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -569,8 +583,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470d43aabf4a58794465671e5b9dad428e66dcf4f11e23e3c3510e61759d0044" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -579,8 +594,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67abcd68143553be69c70b5e2bfffe62bb1a784228f330bd7326ab2f4e19d92" dependencies = [ "alloy-eips", "alloy-primitives", @@ -596,8 +612,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "971864f4d2add7a7792e126e2d1a39cc251582d6932e5744b9703d846eda9beb" dependencies = [ "alloy-primitives", "serde", @@ -605,8 +622,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a01bab0f1ecf7075fe6cf9d9fa319cbc1c5717537697ee36526d4579f1963e6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -625,8 +643,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d912d255847cc46bfc8f698d6f3f089f021431e35b6b65837ca1b18d461f9f10" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -637,7 +656,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arbitrary", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "thiserror 2.0.12", @@ -645,8 +664,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfaa3dda6691cd07010b59bd5de87963ace137d32f95b434f733b7c21fe2470" dependencies = [ "alloy-consensus", "alloy-eips", @@ -659,8 +679,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8517dc56fd52a1f88b1847cebdd36a117a64ffe3cc6c3da8cb14efb41ecbcb06" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -672,8 +693,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de4448330bd57eb6b2ecb4ee2f986fe2d9e846e7fb3cb13a112e5714fb4c47b8" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -683,8 +705,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e63928922253ad23b902e968cfbc31bb7f7ddd2b59c34e58b2b28ea032aff4a" dependencies = [ "alloy-primitives", "arbitrary", @@ -694,8 +717,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db794f239d3746fd116146acca55a1ca3362311ecc3bdbfc150aeeaa84f462ff" dependencies = [ "alloy-primitives", "async-trait", @@ -708,8 +732,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fcb2567f89f31dec894d3cd1d0c42cfcd699acd181a03e7339492b20eea302" dependencies = [ "alloy-consensus", "alloy-network", @@ -795,8 +820,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67353ab9f9ae5eacf31155cf395add8bcfd64d98246f296a08a2e6ead92031dc" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -817,8 +843,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b18ce0732e42186d10479b7d87f96eb3991209c472f534fb775223e1e51f4f" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -831,8 +858,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c43322dbaabe886f69cb9647a449ed50bf18b8bba8804fefd825a2af0cd1c7e" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -850,8 +878,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13318c049f3d9187a88856b7ba0d12fda90f92225780fb119c0ac26e3fbfd5d2" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -887,8 +916,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.12" -source = "git+https://github.com/alloy-rs/alloy?rev=3049f232fbb44d1909883e154eb38ec5962f53a3#3049f232fbb44d1909883e154eb38ec5962f53a3" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e325be6a1f80a744343d62b7229cae22b8d4b1ece6e61b8f8e71cfb7d3bcc0c8" dependencies = [ "alloy-primitives", "darling", diff --git a/Cargo.toml b/Cargo.toml index 292256b38df..0ff5e59e393 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -476,33 +476,33 @@ alloy-trie = { version = "0.9.0", default-features = false } alloy-hardforks = "0.2.7" -alloy-consensus = { version = "1.0.12", default-features = false } -alloy-contract = { version = "1.0.12", default-features = false } -alloy-eips = { version = "1.0.12", default-features = false } -alloy-genesis = { version = "1.0.12", default-features = false } -alloy-json-rpc = { version = "1.0.12", default-features = false } -alloy-network = { version = "1.0.12", default-features = false } -alloy-network-primitives = { version = "1.0.12", default-features = false } -alloy-provider = { version = "1.0.12", features = ["reqwest"], default-features = false } -alloy-pubsub = { version = "1.0.12", default-features = false } -alloy-rpc-client = { version = "1.0.12", default-features = false } -alloy-rpc-types = { version = "1.0.12", features = ["eth"], default-features = false } -alloy-rpc-types-admin = { version = "1.0.12", default-features = false } -alloy-rpc-types-anvil = { version = "1.0.12", default-features = false } -alloy-rpc-types-beacon = { version = "1.0.12", default-features = false } -alloy-rpc-types-debug = { version = "1.0.12", default-features = false } -alloy-rpc-types-engine = { version = "1.0.12", default-features = false } -alloy-rpc-types-eth = { version = "1.0.12", default-features = false } -alloy-rpc-types-mev = { version = "1.0.12", default-features = false } -alloy-rpc-types-trace = { version = "1.0.12", default-features = false } -alloy-rpc-types-txpool = { version = "1.0.12", default-features = false } -alloy-serde = { version = "1.0.12", default-features = false } -alloy-signer = { version = "1.0.12", default-features = false } -alloy-signer-local = { version = "1.0.12", default-features = false } -alloy-transport = { version = "1.0.12" } -alloy-transport-http = { version = "1.0.12", features = ["reqwest-rustls-tls"], default-features = false } -alloy-transport-ipc = { version = "1.0.12", default-features = false } -alloy-transport-ws = { version = "1.0.12", default-features = false } +alloy-consensus = { version = "1.0.13", default-features = false } +alloy-contract = { version = "1.0.13", default-features = false } +alloy-eips = { version = "1.0.13", default-features = false } +alloy-genesis = { version = "1.0.13", default-features = false } +alloy-json-rpc = { version = "1.0.13", default-features = false } +alloy-network = { version = "1.0.13", default-features = false } +alloy-network-primitives = { version = "1.0.13", default-features = false } +alloy-provider = { version = "1.0.13", features = ["reqwest"], default-features = false } +alloy-pubsub = { version = "1.0.13", default-features = false } +alloy-rpc-client = { version = "1.0.13", default-features = false } +alloy-rpc-types = { version = "1.0.13", features = ["eth"], default-features = false } +alloy-rpc-types-admin = { version = "1.0.13", default-features = false } +alloy-rpc-types-anvil = { version = "1.0.13", default-features = false } +alloy-rpc-types-beacon = { version = "1.0.13", default-features = false } +alloy-rpc-types-debug = { version = "1.0.13", default-features = false } +alloy-rpc-types-engine = { version = "1.0.13", default-features = false } +alloy-rpc-types-eth = { version = "1.0.13", default-features = false } +alloy-rpc-types-mev = { version = "1.0.13", default-features = false } +alloy-rpc-types-trace = { version = "1.0.13", default-features = false } +alloy-rpc-types-txpool = { version = "1.0.13", default-features = false } +alloy-serde = { version = "1.0.13", default-features = false } +alloy-signer = { version = "1.0.13", default-features = false } +alloy-signer-local = { version = "1.0.13", default-features = false } +alloy-transport = { version = "1.0.13" } +alloy-transport-http = { version = "1.0.13", features = ["reqwest-rustls-tls"], default-features = false } +alloy-transport-ipc = { version = "1.0.13", default-features = false } +alloy-transport-ws = { version = "1.0.13", default-features = false } # op alloy-op-evm = { version = "0.12", default-features = false } @@ -705,34 +705,34 @@ visibility = "0.1.1" walkdir = "2.3.3" vergen-git2 = "1.0.5" -[patch.crates-io] -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# [patch.crates-io] +# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } +# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } # op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" } From 471f6a375eefc116480bf0ec828ff1ac3286c940 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:37:49 +0530 Subject: [PATCH 232/274] fix(`docs`): redo system reqs, fix links, rebrand to docs (#17071) --- README.md | 39 +++++----- book/vocs/docs/pages/exex/hello-world.mdx | 2 +- book/vocs/docs/pages/exex/remote.mdx | 2 +- book/vocs/docs/pages/exex/tracking-state.mdx | 2 +- book/vocs/docs/pages/installation/docker.mdx | 2 +- .../vocs/docs/pages/installation/overview.mdx | 8 +-- book/vocs/docs/pages/installation/source.mdx | 2 +- book/vocs/docs/pages/jsonrpc/intro.mdx | 16 ++--- book/vocs/docs/pages/jsonrpc/trace.mdx | 2 +- book/vocs/docs/pages/overview.mdx | 6 +- book/vocs/docs/pages/run/ethereum.mdx | 2 +- .../docs/pages/run/faq/troubleshooting.mdx | 2 +- .../docs/pages/run/system-requirements.mdx | 71 +++++++++---------- book/vocs/redirects.config.ts | 5 +- 14 files changed, 82 insertions(+), 79 deletions(-) diff --git a/README.md b/README.md index abac066fd16..390868e3976 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ![](./assets/reth-prod.png) **[Install](https://paradigmxyz.github.io/reth/installation/installation.html)** -| [User Book](https://reth.rs) +| [User Docs](https://reth.rs) | [Developer Docs](./docs) | [Crate Docs](https://reth.rs/docs) @@ -40,13 +40,14 @@ More concretely, our goals are: Reth is production ready, and suitable for usage in mission-critical environments such as staking or high-uptime services. We also actively recommend professional node operators to switch to Reth in production for performance and cost reasons in use cases where high performance with great margins is required such as RPC, MEV, Indexing, Simulations, and P2P activities. More historical context below: -* We released 1.0 "production-ready" stable Reth in June 2024. - * Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf). - * Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. -* We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3rd 2024 the last beta release. -* We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4th 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives. -* We shipped iterative improvements until the last alpha release on February 28th 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21). -* We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) in June 20th 2023. + +- We released 1.0 "production-ready" stable Reth in June 2024. + - Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf). + - Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. +- We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3rd 2024 the last beta release. +- We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4th 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives. +- We shipped iterative improvements until the last alpha release on February 28th 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21). +- We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) in June 20th 2023. ### Database compatibility @@ -60,7 +61,7 @@ If you had a database produced by alpha versions of Reth, you need to drop it wi ## For Users -See the [Reth Book](https://paradigmxyz.github.io/reth) for instructions on how to install and run Reth. +See the [Reth documentation](https://paradigmxyz.github.io/reth) for instructions on how to install and run Reth. ## For Developers @@ -76,8 +77,8 @@ For a general overview of the crates, see [Project Layout](./docs/repo/layout.md If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/paradigm_reth) to chat with us about the development of Reth! -- Our contributor guidelines can be found in [`CONTRIBUTING.md`](./CONTRIBUTING.md). -- See our [contributor docs](./docs) for more information on the project. A good starting point is [Project Layout](./docs/repo/layout.md). +- Our contributor guidelines can be found in [`CONTRIBUTING.md`](./CONTRIBUTING.md). +- See our [contributor docs](./docs) for more information on the project. A good starting point is [Project Layout](./docs/repo/layout.md). ### Building and testing @@ -90,7 +91,7 @@ When updating this, also update: The Minimum Supported Rust Version (MSRV) of this project is [1.86.0](https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/). -See the book for detailed instructions on how to [build from source](https://paradigmxyz.github.io/reth/installation/source.html). +See the docs for detailed instructions on how to [build from source](https://paradigmxyz.github.io/reth/installation/source). To fully test Reth, you will need to have [Geth installed](https://geth.ethereum.org/docs/getting-started/installing-geth), but it is possible to run a subset of tests without Geth. @@ -119,13 +120,13 @@ Using `cargo test` to run tests may work fine, but this is not tested and does n ## Getting Help -If you have any questions, first see if the answer to your question can be found in the [book][book]. +If you have any questions, first see if the answer to your question can be found in the [docs][book]. If the answer is not there: -- Join the [Telegram][tg-url] to get help, or -- Open a [discussion](https://github.com/paradigmxyz/reth/discussions/new) with your question, or -- Open an issue with [the bug](https://github.com/paradigmxyz/reth/issues/new?assignees=&labels=C-bug%2CS-needs-triage&projects=&template=bug.yml) +- Join the [Telegram][tg-url] to get help, or +- Open a [discussion](https://github.com/paradigmxyz/reth/discussions/new) with your question, or +- Open an issue with [the bug](https://github.com/paradigmxyz/reth/issues/new?assignees=&labels=C-bug%2CS-needs-triage&projects=&template=bug.yml) ## Security @@ -137,9 +138,9 @@ Reth is a new implementation of the Ethereum protocol. In the process of develop None of this would have been possible without them, so big shoutout to the teams below: -- [Geth](https://github.com/ethereum/go-ethereum/): We would like to express our heartfelt gratitude to the go-ethereum team for their outstanding contributions to Ethereum over the years. Their tireless efforts and dedication have helped to shape the Ethereum ecosystem and make it the vibrant and innovative community it is today. Thank you for your hard work and commitment to the project. -- [Erigon](https://github.com/ledgerwatch/erigon) (fka Turbo-Geth): Erigon pioneered the ["Staged Sync" architecture](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) that Reth is using, as well as [introduced MDBX](https://github.com/ledgerwatch/erigon/wiki/Choice-of-storage-engine) as the database of choice. We thank Erigon for pushing the state of the art research on the performance limits of Ethereum nodes. -- [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80) . Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages. +- [Geth](https://github.com/ethereum/go-ethereum/): We would like to express our heartfelt gratitude to the go-ethereum team for their outstanding contributions to Ethereum over the years. Their tireless efforts and dedication have helped to shape the Ethereum ecosystem and make it the vibrant and innovative community it is today. Thank you for your hard work and commitment to the project. +- [Erigon](https://github.com/ledgerwatch/erigon) (fka Turbo-Geth): Erigon pioneered the ["Staged Sync" architecture](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) that Reth is using, as well as [introduced MDBX](https://github.com/ledgerwatch/erigon/wiki/Choice-of-storage-engine) as the database of choice. We thank Erigon for pushing the state of the art research on the performance limits of Ethereum nodes. +- [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80) . Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages. ## Warning diff --git a/book/vocs/docs/pages/exex/hello-world.mdx b/book/vocs/docs/pages/exex/hello-world.mdx index 547f6e4e31d..30eac91ee99 100644 --- a/book/vocs/docs/pages/exex/hello-world.mdx +++ b/book/vocs/docs/pages/exex/hello-world.mdx @@ -92,4 +92,4 @@ What we've arrived at is the [minimal ExEx example](https://github.com/paradigmx ## What's next? -Let's do something a bit more interesting, and see how you can [keep track of some state](./tracking-state) inside your ExEx. +Let's do something a bit more interesting, and see how you can [keep track of some state](/exex/tracking-state) inside your ExEx. diff --git a/book/vocs/docs/pages/exex/remote.mdx b/book/vocs/docs/pages/exex/remote.mdx index 92da3372089..536dc93b288 100644 --- a/book/vocs/docs/pages/exex/remote.mdx +++ b/book/vocs/docs/pages/exex/remote.mdx @@ -26,7 +26,7 @@ $ cargo new --lib exex-remote $ cd exex-remote ``` -We will also need a bunch of dependencies. Some of them you know from the [Hello World](./hello-world) chapter, +We will also need a bunch of dependencies. Some of them you know from the [Hello World](/exex/hello-world) chapter, but some of specific to what we need now. ```toml diff --git a/book/vocs/docs/pages/exex/tracking-state.mdx b/book/vocs/docs/pages/exex/tracking-state.mdx index cd704c88969..fb3486e7fab 100644 --- a/book/vocs/docs/pages/exex/tracking-state.mdx +++ b/book/vocs/docs/pages/exex/tracking-state.mdx @@ -6,7 +6,7 @@ description: How to track state in a custom ExEx. In this chapter, we'll learn how to keep track of some state inside our ExEx. -Let's continue with our Hello World example from the [previous chapter](./hello-world). +Let's continue with our Hello World example from the [previous chapter](/exex/hello-world). ### Turning ExEx into a struct diff --git a/book/vocs/docs/pages/installation/docker.mdx b/book/vocs/docs/pages/installation/docker.mdx index 8774d549a5c..ecf55f6b3da 100644 --- a/book/vocs/docs/pages/installation/docker.mdx +++ b/book/vocs/docs/pages/installation/docker.mdx @@ -131,7 +131,7 @@ docker exec -it reth bash **If Reth is running with Docker Compose, replace `reth` with `reth-reth-1` in the above command** -Refer to the [CLI docs](#TODO) to interact with Reth once inside the Reth container. +Refer to the [CLI docs](/cli/reth) to interact with Reth once inside the Reth container. ## Run only Grafana in Docker diff --git a/book/vocs/docs/pages/installation/overview.mdx b/book/vocs/docs/pages/installation/overview.mdx index 8101c509cdd..2a5c21522e2 100644 --- a/book/vocs/docs/pages/installation/overview.mdx +++ b/book/vocs/docs/pages/installation/overview.mdx @@ -8,11 +8,11 @@ Reth runs on Linux and macOS (Windows tracked). There are three core methods to obtain Reth: -- [Pre-built binaries](./binaries) -- [Docker images](./docker) -- [Building from source.](./source) +- [Pre-built binaries](/installation/binaries) +- [Docker images](/installation/docker) +- [Building from source.](/installation/source) :::note -If you have Docker installed, we recommend using the [Docker Compose](./docker#using-docker-compose) configuration +If you have Docker installed, we recommend using the [Docker Compose](/installation/docker#using-docker-compose) configuration that will get you Reth, Lighthouse (Consensus Client), Prometheus and Grafana running and syncing with just one command. ::: diff --git a/book/vocs/docs/pages/installation/source.mdx b/book/vocs/docs/pages/installation/source.mdx index d3d412a58f3..a7e1a2c33cc 100644 --- a/book/vocs/docs/pages/installation/source.mdx +++ b/book/vocs/docs/pages/installation/source.mdx @@ -65,7 +65,7 @@ cargo build --release This will place the reth binary under `./target/release/reth`, and you can copy it to your directory of preference after that. -Compilation may take around 10 minutes. Installation was successful if `reth --help` displays the [command-line documentation](#TODO). +Compilation may take around 10 minutes. Installation was successful if `reth --help` displays the [command-line documentation](/cli/reth). If you run into any issues, please check the [Troubleshooting](#troubleshooting) section, or reach out to us on [Telegram](https://t.me/paradigm_reth). diff --git a/book/vocs/docs/pages/jsonrpc/intro.mdx b/book/vocs/docs/pages/jsonrpc/intro.mdx index dac173142a6..93cccf46921 100644 --- a/book/vocs/docs/pages/jsonrpc/intro.mdx +++ b/book/vocs/docs/pages/jsonrpc/intro.mdx @@ -18,14 +18,14 @@ The methods are grouped into namespaces, which are listed below: | Namespace | Description | Sensitive | | -------------------- | ------------------------------------------------------------------------------------------------------ | --------- | -| [`eth`](./eth) | The `eth` API allows you to interact with Ethereum. | Maybe | -| [`web3`](./web3) | The `web3` API provides utility functions for the web3 client. | No | -| [`net`](./net) | The `net` API provides access to network information of the node. | No | -| [`txpool`](./txpool) | The `txpool` API allows you to inspect the transaction pool. | No | -| [`debug`](./debug) | The `debug` API provides several methods to inspect the Ethereum state, including Geth-style traces. | No | -| [`trace`](./trace) | The `trace` API provides several methods to inspect the Ethereum state, including Parity-style traces. | No | -| [`admin`](./admin) | The `admin` API allows you to configure your node. | **Yes** | -| [`rpc`](./rpc) | The `rpc` API provides information about the RPC server and its modules. | No | +| [`eth`](/jsonrpc/eth) | The `eth` API allows you to interact with Ethereum. | Maybe | +| [`web3`](/jsonrpc/web3) | The `web3` API provides utility functions for the web3 client. | No | +| [`net`](/jsonrpc/net) | The `net` API provides access to network information of the node. | No | +| [`txpool`](/jsonrpc/txpool) | The `txpool` API allows you to inspect the transaction pool. | No | +| [`debug`](/jsonrpc/debug) | The `debug` API provides several methods to inspect the Ethereum state, including Geth-style traces. | No | +| [`trace`](/jsonrpc/trace) | The `trace` API provides several methods to inspect the Ethereum state, including Parity-style traces. | No | +| [`admin`](/jsonrpc/admin) | The `admin` API allows you to configure your node. | **Yes** | +| [`rpc`](/jsonrpc/rpc) | The `rpc` API provides information about the RPC server and its modules. | No | Note that some APIs are sensitive, since they can be used to configure your node (`admin`), or access accounts stored on the node (`eth`). diff --git a/book/vocs/docs/pages/jsonrpc/trace.mdx b/book/vocs/docs/pages/jsonrpc/trace.mdx index 38157e44230..464832db70e 100644 --- a/book/vocs/docs/pages/jsonrpc/trace.mdx +++ b/book/vocs/docs/pages/jsonrpc/trace.mdx @@ -8,7 +8,7 @@ description: Trace API for inspecting Ethereum state and transactions. The `trace` API provides several methods to inspect the Ethereum state, including Parity-style traces. -A similar module exists (with other debug functions) with Geth-style traces ([`debug`](./debug)). +A similar module exists (with other debug functions) with Geth-style traces ([`debug`](/jsonrpc/debug)). The `trace` API gives deeper insight into transaction processing. diff --git a/book/vocs/docs/pages/overview.mdx b/book/vocs/docs/pages/overview.mdx index e41ca3ad83a..e467dacc03f 100644 --- a/book/vocs/docs/pages/overview.mdx +++ b/book/vocs/docs/pages/overview.mdx @@ -105,10 +105,10 @@ Here are some useful sections to jump to: - Set up your [development environment and contribute](/introduction/contributing)! :::note -The book is continuously rendered [here](https://reth.rs)! -You can contribute to the docs on [GitHub][gh-book]. +The documentation is continuously rendered [here](https://reth.rs)! +You can contribute to the docs on [GitHub][gh-docs]. ::: [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fparadigm%5Freth [tg-url]: https://t.me/paradigm_reth -[gh-book]: https://github.com/paradigmxyz/reth/tree/main/book +[gh-docs]: https://github.com/paradigmxyz/reth/tree/main/book diff --git a/book/vocs/docs/pages/run/ethereum.mdx b/book/vocs/docs/pages/run/ethereum.mdx index 7e0d01daa1f..3c488416ec9 100644 --- a/book/vocs/docs/pages/run/ethereum.mdx +++ b/book/vocs/docs/pages/run/ethereum.mdx @@ -84,7 +84,7 @@ Your Reth node should start receiving "fork choice updated" messages, and begin ## Verify the chain is growing You can easily verify that by inspecting the logs, and seeing that headers are arriving in Reth. Sit back now and wait for the stages to run! -In the meantime, consider setting up [observability](/run/monitoring) to monitor your node's health or [test the JSON RPC API](../jsonrpc/intro). +In the meantime, consider setting up [observability](/run/monitoring) to monitor your node's health or [test the JSON RPC API](/jsonrpc/intro). {/* TODO: Add more logs to help node operators debug any weird CL to EL messages! */} diff --git a/book/vocs/docs/pages/run/faq/troubleshooting.mdx b/book/vocs/docs/pages/run/faq/troubleshooting.mdx index 3dafa678ac2..d1f0e8500ff 100644 --- a/book/vocs/docs/pages/run/faq/troubleshooting.mdx +++ b/book/vocs/docs/pages/run/faq/troubleshooting.mdx @@ -95,7 +95,7 @@ equal to the [freshly synced node](/installation/overview#hardware-requirements) It will take the same time as initial sync. 1. Stop Reth -2. Drop the database using [`reth db drop`](#TODO) +2. Drop the database using [`reth db drop`](/cli/reth/db/drop) 3. Start reth ### Database write error diff --git a/book/vocs/docs/pages/run/system-requirements.mdx b/book/vocs/docs/pages/run/system-requirements.mdx index 5db81bc29b8..60e30189f6a 100644 --- a/book/vocs/docs/pages/run/system-requirements.mdx +++ b/book/vocs/docs/pages/run/system-requirements.mdx @@ -4,76 +4,68 @@ The hardware requirements for running Reth depend on the node configuration and The most important requirement is by far the disk, whereas CPU and RAM requirements are relatively flexible. -## Ethereum Mainnet Requirements +## Chain Specific Requirements -Below are the requirements for Ethereum Mainnet: +### Ethereum Mainnet + +Below are the requirements for running an Ethereum Mainnet node as of 2025-06-23 block number `22700000`: | | Archive Node | Full Node | | --------- | ------------------------------------- | ------------------------------------- | -| Disk | At least 2.8TB (TLC NVMe recommended) | At least 1.8TB (TLC NVMe recommended) | +| Disk | At least 2.8TB (TLC NVMe recommended) | At least 1.2TB (TLC NVMe recommended) | | Memory | 16GB+ | 8GB+ | | CPU | Higher clock speed over core count | Higher clock speeds over core count | | Bandwidth | Stable 24Mbps+ | Stable 24Mbps+ | -## Base System Requirements +### Base Mainnet -Below are the minimum system requirements for running a Base node as of 2025-06-23, block number 31.9M: +Below are the minimum system requirements for running a Base Mainnet node as of 2025-06-23, block number `31900000`: -| | Archive Node | Full Node | -| --------- | ------------------------------------- | ------------------------------------- | -| Disk | At least 4.1TB (TLC NVMe recommended) | At least 2TB (TLC NVMe recommended) | -| Memory | 128GB+ | 128GB+ | +| | Archive Node | Full Node | +| --------- | -------------------------------------------- | -------------------------------------------- | +| Disk | At least 4.1TB (TLC NVMe recommended) | At least 2TB (TLC NVMe recommended) | +| Memory | 128GB+ | 128GB+ | | CPU | 6 cores+, Higher clock speed over core count | 6 cores+, Higher clock speed over core count | -| Bandwidth | Stable 24Mbps+ | Stable 24Mbps+ | +| Bandwidth | Stable 24Mbps+ | Stable 24Mbps+ | :::note -**On CPU clock speeds**: We've seen >1s payload latency on EPYC GENOA 9254 (2.9 GHz/3.9 GHz), best performance we see on AMD EPYC™ 4004. +**On CPU clock speeds**: The AMD EPYC 4005/4004 series is a cost-effective high-clock speed option with support for up to 192GB memory. **On CPU cores for Base**: 5+ cores are needed because the state root task splits work into separate threads that run in parallel with each other. The state root task is generally more performant and can scale with the number of CPU cores, while regular state root always uses only one core. This is not a requirement for Mainnet, but for Base you may encounter block processing latencies of more than 2s, which can lead to lagging behind the head of the chain. ::: +## Disk -#### QLC and TLC +Simplest approach: Use a [good TLC NVMe](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038) drive for everything. -It is crucial to understand the difference between QLC and TLC NVMe drives when considering the disk requirement. - -QLC (Quad-Level Cell) NVMe drives utilize four bits of data per cell, allowing for higher storage density and lower manufacturing costs. However, this increased density comes at the expense of performance. QLC drives have slower read and write speeds compared to TLC drives. They also have a lower endurance, meaning they may have a shorter lifespan and be less suitable for heavy workloads or constant data rewriting. - -TLC (Triple-Level Cell) NVMe drives, on the other hand, use three bits of data per cell. While they have a slightly lower storage density compared to QLC drives, TLC drives offer faster performance. They typically have higher read and write speeds, making them more suitable for demanding tasks such as data-intensive applications, gaming, and multimedia editing. TLC drives also tend to have a higher endurance, making them more durable and longer-lasting. +Advanced Storage Optimization (Optional): -Prior to purchasing an NVMe drive, it is advisable to research and determine whether the disk will be based on QLC or TLC technology. An overview of recommended and not-so-recommended NVMe boards can be found at [here](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038). +- TLC NVMe: All application data except static files (`--datadir`) +- SATA SSD/HDD: Static files can be stored on slower & cheaper storage (`--datadir.static-files`) -### Disk +### QLC and TLC -There are multiple types of disks to sync Reth, with varying size requirements, depending on the syncing mode. -As of April 2025 at block number 22.1M: - -- Archive Node: At least 2.8TB is required -- Full Node: At least 1.8TB is required - -NVMe based SSD drives are recommended for the best performance, with SATA SSDs being a cheaper alternative. HDDs are the cheapest option, but they will take the longest to sync, and are not recommended. +It is crucial to understand the difference between QLC and TLC NVMe drives when considering the disk requirement. -As of February 2024, syncing an Ethereum mainnet node to block 19.3M on NVMe drives takes about 50 hours, while on a GCP "Persistent SSD" it takes around 5 days. +QLC (Quad-Level Cell) NVMe drives utilize four bits of data per cell, allowing for higher storage density and lower manufacturing costs. However, this increased density comes at the expense of performance. QLC drives have slower read and write speeds compared to TLC drives. They also have a lower endurance, meaning they may have a shorter lifespan and be less suitable for heavy workloads or constant data rewriting. -:::tip -It is highly recommended to choose a TLC drive when using an NVMe drive, and not a QLC drive. See [the note](#qlc-and-tlc) above. A list of recommended drives can be found [here](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038). -::: +TLC (Triple-Level Cell) NVMe drives, on the other hand, use three bits of data per cell. While they have a slightly lower storage density compared to QLC drives, TLC drives offer faster performance. They typically have higher read and write speeds, making them more suitable for demanding tasks such as data-intensive applications, gaming, and multimedia editing. TLC drives also tend to have a higher endurance, making them more durable and longer-lasting. -### CPU +## CPU Most of the time during syncing is spent executing transactions, which is a single-threaded operation due to potential state dependencies of a transaction on previous ones. As a result, the number of cores matters less, but in general higher clock speeds are better. More cores are better for parallelizable [stages](https://github.com/paradigmxyz/reth/blob/main/docs/crates/stages) (like sender recovery or bodies downloading), but these stages are not the primary bottleneck for syncing. -### Memory +## Memory -It is recommended to use at least 8GB of RAM. +It is recommended to use at least 16GB of RAM. Most of Reth's components tend to consume a low amount of memory, unless you are under heavy RPC load, so this should matter less than the other requirements. Higher memory is generally better as it allows for better caching, resulting in less stress on the disk. -### Bandwidth +## Bandwidth A stable and dependable internet connection is crucial for both syncing a node from genesis and for keeping up with the chain's tip. @@ -83,6 +75,13 @@ Once you're synced to the tip you will need a reliable connection, especially if ## What hardware can I get? -If you are buying your own NVMe SSD, please consult [this hardware comparison](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038) which is being actively maintained. We recommend against buying DRAM-less or QLC devices as these are noticeably slower. +### Build your own + +- Storage: Consult the [Great and less great SSDs for Ethereum nodes](https://gist.github.com/yorickdowne/f3a3e79a573bf35767cd002cc977b038) gist. The Seagate Firecuda 530 and WD Black SN850(X) are popular TLC NVMEe options. Ensure proper cooling via heatsinks or active fans. +- CPU: AMD Ryzen 5000/7000/9000 series, AMD EPYC 4004/4005 or Intel Core i5/i7 (11th gen or newer) with at least 6 cores. The AMD Ryzen 9000 series and the AMD EPYC 4005 series offer good value. +- Memory: 32GB DDR4 or DDR5 (ECC if your motherboard & CPU supports it). + +### Hosted -All our benchmarks have been produced on [Latitude.sh](https://www.latitude.sh/), a bare metal provider. We use `c3.large.x86` boxes, and also recommend trying the `c3.small.x86` box for pruned/full nodes. So far our experience has been smooth with some users reporting that the NVMEs there outperform AWS NVMEs by 3x or more. We're excited for more Reth nodes on Latitude.sh, so for a limited time you can use `RETH400` for a $250 discount. [Run a node now!](https://metal.new/reth) +- [Latitude.sh](https://www.latitude.sh): `f4.metal.small`, `c3.large.x86` or better +- [OVH](https://www.ovhcloud.com/en/bare-metal/advance/): `Advance-1` or better diff --git a/book/vocs/redirects.config.ts b/book/vocs/redirects.config.ts index 21521d5e86b..6d30c882a14 100644 --- a/book/vocs/redirects.config.ts +++ b/book/vocs/redirects.config.ts @@ -1,7 +1,10 @@ export const redirects: Record = { '/intro': '/overview', // Installation redirects - '/installation/installation': '/installation/binaries', + '/installation/installation': '/installation/overview', + '/binaries': '/installation/binaries', + '/docker': '/installation/docker', + '/source': '/installation/source', // Run a node redirects '/run/run-a-node': '/run/overview', '/run/mainnet': '/run/ethereum', From 55840bb32b06fbbf03b2d048d3df2f3f6495ed29 Mon Sep 17 00:00:00 2001 From: Skylar Ray <137945430+sky-coderay@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:49:28 +0300 Subject: [PATCH 233/274] docs: error fixes for clarity (#17062) --- crates/engine/invalid-block-hooks/src/witness.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index b3b54281128..54e18c07a70 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -281,7 +281,7 @@ where let filename = format!("{}_{}.bundle_state.diff", block.number(), block.hash()); // Convert bundle state to sorted struct which has BTreeMap instead of HashMap to - // have deterministric ordering + // have deterministic ordering let bundle_state_sorted = BundleStateSorted::from_bundle_state(&bundle_state); let output_state_sorted = BundleStateSorted::from_bundle_state(&output.state); From 2e799062f11ca0f77a857e47e0595d7e2600cf59 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin <5773434+shekhirin@users.noreply.github.com> Date: Thu, 26 Jun 2025 11:53:23 +0100 Subject: [PATCH 234/274] feat: convert reth-bench scripts to use uv script format (#17078) Co-authored-by: Claude --- bin/reth-bench/scripts/compare_newpayload_latency.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/reth-bench/scripts/compare_newpayload_latency.py b/bin/reth-bench/scripts/compare_newpayload_latency.py index 55c90615cee..ff9cdad5262 100755 --- a/bin/reth-bench/scripts/compare_newpayload_latency.py +++ b/bin/reth-bench/scripts/compare_newpayload_latency.py @@ -1,4 +1,12 @@ -#!/usr/bin/env python3 +#!/usr/bin/env -S uv run +# /// script +# requires-python = ">=3.8" +# dependencies = [ +# "pandas", +# "matplotlib", +# "numpy", +# ] +# /// # A simple script which plots graphs comparing two combined_latency.csv files # output by reth-bench. The graphs which are plotted are: From 777ee2de2967c57d5c1aa0ea899d66033431aede Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:45:59 +0530 Subject: [PATCH 235/274] fix(`docs/sdk`): heading hierarchy (#17079) --- book/vocs/docs/pages/sdk/overview.mdx | 80 +++++++++++++-------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/book/vocs/docs/pages/sdk/overview.mdx b/book/vocs/docs/pages/sdk/overview.mdx index b427ae8834d..b308dee77ae 100644 --- a/book/vocs/docs/pages/sdk/overview.mdx +++ b/book/vocs/docs/pages/sdk/overview.mdx @@ -5,19 +5,20 @@ Reth can be used as a library to build custom Ethereum nodes, interact with bloc ## What is the Reth SDK? The Reth SDK allows developers to: -- Use components of the Reth node as libraries -- Build custom Ethereum execution nodes with modified behavior (e.g. payload building) -- Access blockchain data directly from the database -- Create high-performance indexing solutions -- Extend a new with new RPC endpoints and functionality -- Implement custom consensus mechanisms -- Build specialized tools for blockchain analysis + +- Use components of the Reth node as libraries +- Build custom Ethereum execution nodes with modified behavior (e.g. payload building) +- Access blockchain data directly from the database +- Create high-performance indexing solutions +- Extend a new with new RPC endpoints and functionality +- Implement custom consensus mechanisms +- Build specialized tools for blockchain analysis ## Quick Start -Add Reth to your project: +Add Reth to your project -## Ethereum +### Ethereum ```toml [dependencies] @@ -25,7 +26,7 @@ Add Reth to your project: reth-ethereum = { git = "https://github.com/paradigmxyz/reth" } ``` -## Opstack +### Opstack ```toml [dependencies] @@ -38,27 +39,27 @@ reth-op = { git = "https://github.com/paradigmxyz/reth" } Reth is built with modularity in mind. The main components include: -- **Primitives**: Core data type abstractions like `Block` -- **Node Builder**: Constructs and configures node instances -- **Database**: Efficient storage using MDBX and static files -- **Network**: P2P communication and block synchronization -- **Consensus**: Block validation and chain management -- **EVM**: Transaction execution and state transitions -- **RPC**: JSON-RPC server for external communication -- **Transaction Pool**: Pending transaction management +- **Primitives**: Core data type abstractions like `Block` +- **Node Builder**: Constructs and configures node instances +- **Database**: Efficient storage using MDBX and static files +- **Network**: P2P communication and block synchronization +- **Consensus**: Block validation and chain management +- **EVM**: Transaction execution and state transitions +- **RPC**: JSON-RPC server for external communication +- **Transaction Pool**: Pending transaction management ### Dependency Management -Reth is primarily built on top of the [alloy](https://github.com/alloy-rs/alloy) ecosystem, which provides the necessary abstractions and implementations for core ethereum blockchain data types, transaction handling, and EVM execution. +Reth is primarily built on top of the [alloy](https://github.com/alloy-rs/alloy) ecosystem, which provides the necessary abstractions and implementations for core ethereum blockchain data types, transaction handling, and EVM execution. ### Type System Reth uses its own type system to handle different representations of blockchain data: -- **Primitives**: Core types like `B256`, `Address`, `U256` -- **Transactions**: Multiple representations for different contexts (pooled, consensus, RPC) -- **Blocks**: Headers, bodies, and sealed blocks with proven properties -- **State**: Accounts, storage, and state transitions +- **Primitives**: Core types like `B256`, `Address`, `U256` +- **Transactions**: Multiple representations for different contexts (pooled, consensus, RPC) +- **Blocks**: Headers, bodies, and sealed blocks with proven properties +- **State**: Accounts, storage, and state transitions ### Building Custom Nodes @@ -89,13 +90,13 @@ graph TD A --> E[EVM] A --> F[RPC Server] A --> G[Transaction Pool] - + B --> H[DB Storage] B --> I[Static Files] - + C --> J[Discovery] C --> K[ETH Protocol] - + E --> L[State Provider] E --> M[Block Executor] ``` @@ -104,24 +105,21 @@ graph TD Several production networks have been built using Reth's node builder pattern: -### [BSC Reth](https://github.com/loocapro/reth-bsc) -A Binance Smart Chain execution client, implementing BSC-specific consensus rules and features. - -### [Bera Reth](https://github.com/berachain/bera-reth) -Berachain's execution client. - -### [Gnosis Reth](https://github.com/gnosischain/reth_gnosis) -Gnosis Chain's implementation using Reth. - +| Node | Company | Description | Lines of Code | +|------|---------|-------------|---------------| +| [Base Node](https://github.com/base/node-reth) | Coinbase | Coinbase's L2 scaling solution node implementation | ~3K | +| [Bera Reth](https://github.com/berachain/bera-reth) | Berachain | Berachain's high-performance EVM node with custom features | ~1K | +| [Reth Gnosis](https://github.com/gnosischain/reth_gnosis) | Gnosis | Gnosis Chain's xDai-compatible execution client | ~5K | +| [Reth BSC](https://github.com/loocapro/reth-bsc) | Binance Smart Chain | BNB Smart Chain execution client implementation | ~6K | ## Next Steps -- **[Node Components](/sdk/node-components)**: Deep dive into each component -- **[Type System](/sdk/typesystem/block)**: Understanding Reth's type system -- **[Custom Nodes](/sdk/custom-node/prerequisites)**: Building production nodes -- **[Examples](/sdk/examples/modify-node)**: Real-world implementations +- **[Node Components](/sdk/node-components)**: Deep dive into each component +- **[Type System](/sdk/typesystem/block)**: Understanding Reth's type system +- **[Custom Nodes](/sdk/custom-node/prerequisites)**: Building production nodes +- **[Examples](/sdk/examples/modify-node)**: Real-world implementations ## Resources -- [API Documentation](https://docs.rs/reth/latest/reth/) -- [GitHub Repository](https://github.com/paradigmxyz/reth) +- [API Documentation](https://docs.rs/reth/latest/reth/) +- [GitHub Repository](https://github.com/paradigmxyz/reth) From 9b3f2576d1c3aa740c0523cf3cca7173d64d110d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 26 Jun 2025 13:43:52 +0200 Subject: [PATCH 236/274] feat: add blanket impl for Receipt trait (#17082) --- crates/ethereum/primitives/src/receipt.rs | 2 -- crates/optimism/primitives/src/receipt.rs | 2 -- crates/primitives-traits/src/receipt.rs | 21 ++++++++++++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/ethereum/primitives/src/receipt.rs b/crates/ethereum/primitives/src/receipt.rs index 2893c36159e..ffc06c7fc82 100644 --- a/crates/ethereum/primitives/src/receipt.rs +++ b/crates/ethereum/primitives/src/receipt.rs @@ -283,8 +283,6 @@ impl InMemorySize for Receipt { } } -impl reth_primitives_traits::Receipt for Receipt {} - impl From> for Receipt where T: Into, diff --git a/crates/optimism/primitives/src/receipt.rs b/crates/optimism/primitives/src/receipt.rs index f5f960a0034..e0ef6318081 100644 --- a/crates/optimism/primitives/src/receipt.rs +++ b/crates/optimism/primitives/src/receipt.rs @@ -377,8 +377,6 @@ impl InMemorySize for OpReceipt { } } -impl reth_primitives_traits::Receipt for OpReceipt {} - /// Trait for deposit receipt. pub trait DepositReceipt: reth_primitives_traits::Receipt { /// Converts a `Receipt` into a mutable Optimism deposit receipt. diff --git a/crates/primitives-traits/src/receipt.rs b/crates/primitives-traits/src/receipt.rs index 3e2e64ad923..9be419987f0 100644 --- a/crates/primitives-traits/src/receipt.rs +++ b/crates/primitives-traits/src/receipt.rs @@ -14,7 +14,6 @@ pub trait FullReceipt: Receipt + MaybeCompact {} impl FullReceipt for T where T: Receipt + MaybeCompact {} /// Abstraction of a receipt. -#[auto_impl::auto_impl(&, Arc)] pub trait Receipt: Send + Sync @@ -34,6 +33,26 @@ pub trait Receipt: { } +// Blanket implementation for any type that satisfies all the supertrait bounds +impl Receipt for T where + T: Send + + Sync + + Unpin + + Clone + + fmt::Debug + + TxReceipt + + RlpEncodableReceipt + + RlpDecodableReceipt + + Encodable + + Decodable + + Eip2718EncodableReceipt + + Typed2718 + + MaybeSerde + + InMemorySize + + MaybeSerdeBincodeCompat +{ +} + /// Retrieves gas spent by transactions as a vector of tuples (transaction index, gas used). pub fn gas_spent_by_transactions(receipts: I) -> Vec<(u64, u64)> where From 61e38f9af154fe91e776d8f5e449d20a1571e8cf Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 26 Jun 2025 13:50:41 +0200 Subject: [PATCH 237/274] chore: bump version 1.5.0 (#17083) --- Cargo.lock | 264 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 133 insertions(+), 133 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1021e09bc94..fc828424417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3031,7 +3031,7 @@ dependencies = [ [[package]] name = "ef-tests" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -3619,7 +3619,7 @@ dependencies = [ [[package]] name = "exex-subscription" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "clap", @@ -6053,7 +6053,7 @@ dependencies = [ [[package]] name = "op-reth" -version = "1.4.8" +version = "1.5.0" dependencies = [ "clap", "reth-cli-util", @@ -7131,7 +7131,7 @@ checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" [[package]] name = "reth" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-rpc-types", "aquamarine", @@ -7179,7 +7179,7 @@ dependencies = [ [[package]] name = "reth-alloy-provider" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7207,7 +7207,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7230,7 +7230,7 @@ dependencies = [ [[package]] name = "reth-bench" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-json-rpc", @@ -7268,7 +7268,7 @@ dependencies = [ [[package]] name = "reth-chain-state" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7299,7 +7299,7 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7319,7 +7319,7 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-genesis", "clap", @@ -7332,7 +7332,7 @@ dependencies = [ [[package]] name = "reth-cli-commands" -version = "1.4.8" +version = "1.5.0" dependencies = [ "ahash", "alloy-chains", @@ -7410,7 +7410,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" -version = "1.4.8" +version = "1.5.0" dependencies = [ "reth-tasks", "tokio", @@ -7419,7 +7419,7 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7439,7 +7439,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7463,7 +7463,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.4.8" +version = "1.5.0" dependencies = [ "convert_case", "proc-macro2", @@ -7474,7 +7474,7 @@ dependencies = [ [[package]] name = "reth-config" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "eyre", @@ -7491,7 +7491,7 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7503,7 +7503,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7517,7 +7517,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7540,7 +7540,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7573,7 +7573,7 @@ dependencies = [ [[package]] name = "reth-db-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7603,7 +7603,7 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7632,7 +7632,7 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7649,7 +7649,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7676,7 +7676,7 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7701,7 +7701,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7729,7 +7729,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7768,7 +7768,7 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7815,7 +7815,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.4.8" +version = "1.5.0" dependencies = [ "aes", "alloy-primitives", @@ -7845,7 +7845,7 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7868,7 +7868,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7892,7 +7892,7 @@ dependencies = [ [[package]] name = "reth-engine-service" -version = "1.4.8" +version = "1.5.0" dependencies = [ "futures", "pin-project", @@ -7922,7 +7922,7 @@ dependencies = [ [[package]] name = "reth-engine-tree" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7991,7 +7991,7 @@ dependencies = [ [[package]] name = "reth-engine-util" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8017,7 +8017,7 @@ dependencies = [ [[package]] name = "reth-era" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8039,7 +8039,7 @@ dependencies = [ [[package]] name = "reth-era-downloader" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "bytes", @@ -8056,7 +8056,7 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8085,7 +8085,7 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -8095,7 +8095,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -8133,7 +8133,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -8158,7 +8158,7 @@ dependencies = [ [[package]] name = "reth-ethereum" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8256,7 +8256,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8272,7 +8272,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8290,7 +8290,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -8303,7 +8303,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8329,7 +8329,7 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8354,7 +8354,7 @@ dependencies = [ [[package]] name = "reth-etl" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "rayon", @@ -8364,7 +8364,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8389,7 +8389,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8411,7 +8411,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-evm", "alloy-primitives", @@ -8423,7 +8423,7 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8443,7 +8443,7 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8487,7 +8487,7 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "eyre", @@ -8519,7 +8519,7 @@ dependencies = [ [[package]] name = "reth-exex-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8536,7 +8536,7 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "1.4.8" +version = "1.5.0" dependencies = [ "serde", "serde_json", @@ -8545,7 +8545,7 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8572,7 +8572,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.4.8" +version = "1.5.0" dependencies = [ "bytes", "futures", @@ -8594,7 +8594,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.4.8" +version = "1.5.0" dependencies = [ "bitflags 2.9.1", "byteorder", @@ -8613,7 +8613,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "1.4.8" +version = "1.5.0" dependencies = [ "bindgen", "cc", @@ -8621,7 +8621,7 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "1.4.8" +version = "1.5.0" dependencies = [ "futures", "metrics", @@ -8632,14 +8632,14 @@ dependencies = [ [[package]] name = "reth-net-banlist" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-net-nat" -version = "1.4.8" +version = "1.5.0" dependencies = [ "futures-util", "if-addrs", @@ -8653,7 +8653,7 @@ dependencies = [ [[package]] name = "reth-network" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8714,7 +8714,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rpc-types-admin", @@ -8736,7 +8736,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8758,7 +8758,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8775,7 +8775,7 @@ dependencies = [ [[package]] name = "reth-network-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -8788,7 +8788,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.4.8" +version = "1.5.0" dependencies = [ "anyhow", "bincode 1.3.3", @@ -8806,7 +8806,7 @@ dependencies = [ [[package]] name = "reth-node-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -8829,7 +8829,7 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8894,7 +8894,7 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8946,7 +8946,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-contract", @@ -8999,7 +8999,7 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9022,7 +9022,7 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "1.4.8" +version = "1.5.0" dependencies = [ "eyre", "http", @@ -9044,7 +9044,7 @@ dependencies = [ [[package]] name = "reth-node-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "reth-chainspec", "reth-db-api", @@ -9056,7 +9056,7 @@ dependencies = [ [[package]] name = "reth-op" -version = "1.4.8" +version = "1.5.0" dependencies = [ "reth-chainspec", "reth-cli-util", @@ -9095,7 +9095,7 @@ dependencies = [ [[package]] name = "reth-optimism-chainspec" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -9121,7 +9121,7 @@ dependencies = [ [[package]] name = "reth-optimism-cli" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9169,7 +9169,7 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -9201,7 +9201,7 @@ dependencies = [ [[package]] name = "reth-optimism-evm" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9227,7 +9227,7 @@ dependencies = [ [[package]] name = "reth-optimism-forks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-op-hardforks", "alloy-primitives", @@ -9237,7 +9237,7 @@ dependencies = [ [[package]] name = "reth-optimism-node" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9297,7 +9297,7 @@ dependencies = [ [[package]] name = "reth-optimism-payload-builder" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9335,7 +9335,7 @@ dependencies = [ [[package]] name = "reth-optimism-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9362,7 +9362,7 @@ dependencies = [ [[package]] name = "reth-optimism-rpc" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9422,7 +9422,7 @@ dependencies = [ [[package]] name = "reth-optimism-storage" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9440,7 +9440,7 @@ dependencies = [ [[package]] name = "reth-optimism-txpool" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9477,7 +9477,7 @@ dependencies = [ [[package]] name = "reth-payload-builder" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9497,7 +9497,7 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "pin-project", "reth-payload-primitives", @@ -9508,7 +9508,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9527,7 +9527,7 @@ dependencies = [ [[package]] name = "reth-payload-util" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9536,7 +9536,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -9545,7 +9545,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9567,7 +9567,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9605,7 +9605,7 @@ dependencies = [ [[package]] name = "reth-provider" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9654,7 +9654,7 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9686,7 +9686,7 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "arbitrary", @@ -9705,7 +9705,7 @@ dependencies = [ [[package]] name = "reth-ress-protocol" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9731,7 +9731,7 @@ dependencies = [ [[package]] name = "reth-ress-provider" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9757,7 +9757,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9771,7 +9771,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -9849,7 +9849,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-genesis", @@ -9876,7 +9876,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9895,7 +9895,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-network", @@ -9950,7 +9950,7 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-json-rpc", @@ -9971,7 +9971,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10007,7 +10007,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -10050,7 +10050,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10092,7 +10092,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-rpc-types-engine", "http", @@ -10109,7 +10109,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10124,7 +10124,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10185,7 +10185,7 @@ dependencies = [ [[package]] name = "reth-stages-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10214,7 +10214,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "arbitrary", @@ -10231,7 +10231,7 @@ dependencies = [ [[package]] name = "reth-stateless" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10256,7 +10256,7 @@ dependencies = [ [[package]] name = "reth-static-file" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "assert_matches", @@ -10280,7 +10280,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "clap", @@ -10292,7 +10292,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10315,7 +10315,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10330,7 +10330,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "auto_impl", "dyn-clone", @@ -10347,7 +10347,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10362,7 +10362,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.4.8" +version = "1.5.0" dependencies = [ "tokio", "tokio-stream", @@ -10371,7 +10371,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.4.8" +version = "1.5.0" dependencies = [ "clap", "eyre", @@ -10385,7 +10385,7 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" -version = "1.4.8" +version = "1.5.0" dependencies = [ "opentelemetry", "opentelemetry-otlp", @@ -10398,7 +10398,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10444,7 +10444,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10476,7 +10476,7 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -10508,7 +10508,7 @@ dependencies = [ [[package]] name = "reth-trie-db" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10534,7 +10534,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10563,7 +10563,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10595,7 +10595,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse-parallel" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10618,7 +10618,7 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "zstd", ] diff --git a/Cargo.toml b/Cargo.toml index 0ff5e59e393..1c8a80d099b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.4.8" +version = "1.5.0" edition = "2021" rust-version = "1.86" license = "MIT OR Apache-2.0" From a7e19963fb8dd1833afa8a445746872c43200b68 Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:36:01 +0530 Subject: [PATCH 238/274] feat(`docs`): serve rustdocs (#17084) Co-authored-by: Claude --- .github/workflows/book.yml | 6 + book/vocs/bun.lockb | Bin 302056 -> 311438 bytes book/vocs/package.json | 10 +- book/vocs/scripts/build-cargo-docs.sh | 14 +++ book/vocs/{ => scripts}/check-links.ts | 0 book/vocs/{ => scripts}/generate-redirects.ts | 2 +- book/vocs/scripts/inject-cargo-docs.ts | 105 ++++++++++++++++++ book/vocs/vocs.config.ts | 1 + 8 files changed, 134 insertions(+), 4 deletions(-) create mode 100755 book/vocs/scripts/build-cargo-docs.sh rename book/vocs/{ => scripts}/check-links.ts (100%) rename book/vocs/{ => scripts}/generate-redirects.ts (96%) create mode 100644 book/vocs/scripts/inject-cargo-docs.ts diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 2460d00d581..fb04b785ce5 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -28,6 +28,12 @@ jobs: bun i npx playwright install --with-deps chromium + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Build docs + run: cd book/vocs && bash scripts/build-cargo-docs.sh + - name: Build Vocs run: | cd book/vocs/ && bun run build diff --git a/book/vocs/bun.lockb b/book/vocs/bun.lockb index 0f15cd4dc666167798797dafba290536329e65f4..bc5c53c3de1c23052b64668edb1dabe914f230ad 100755 GIT binary patch delta 27270 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

    @l3zM8&oZ|xO$3^?~2xP*x|gl%A1f+ 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 delta 21532 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 VKk1C|;gA07{rrLX?$MY<{|9-$+b#eA diff --git a/book/vocs/package.json b/book/vocs/package.json index 912bc136345..72b6ee76138 100644 --- a/book/vocs/package.json +++ b/book/vocs/package.json @@ -5,10 +5,12 @@ "type": "module", "scripts": { "dev": "vocs dev", - "build": "vocs build && bun generate-redirects.ts", + "build": "bash scripts/build-cargo-docs.sh && vocs build && bun scripts/generate-redirects.ts && bun scripts/inject-cargo-docs.ts", "preview": "vocs preview", - "check-links": "bun check-links.ts", - "generate-redirects": "bun generate-redirects.ts" + "check-links": "bun scripts/check-links.ts", + "generate-redirects": "bun scripts/generate-redirects.ts", + "build-cargo-docs": "bash scripts/build-cargo-docs.sh", + "inject-cargo-docs": "bun scripts/inject-cargo-docs.ts" }, "dependencies": { "react": "latest", @@ -16,7 +18,9 @@ "vocs": "latest" }, "devDependencies": { + "@types/node": "latest", "@types/react": "latest", + "glob": "^10.3.10", "typescript": "latest" } } \ No newline at end of file diff --git a/book/vocs/scripts/build-cargo-docs.sh b/book/vocs/scripts/build-cargo-docs.sh new file mode 100755 index 00000000000..a1a8eeec0a7 --- /dev/null +++ b/book/vocs/scripts/build-cargo-docs.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Script to build cargo docs with the same flags as used in CI + +# Navigate to the reth root directory (two levels up from book/vocs) +cd ../.. || exit 1 + +echo "Building cargo docs..." + +# Build the documentation +export RUSTDOCFLAGS="--cfg docsrs --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options" +cargo docs --exclude "example-*" + +echo "Cargo docs built successfully at ./target/doc" \ No newline at end of file diff --git a/book/vocs/check-links.ts b/book/vocs/scripts/check-links.ts similarity index 100% rename from book/vocs/check-links.ts rename to book/vocs/scripts/check-links.ts diff --git a/book/vocs/generate-redirects.ts b/book/vocs/scripts/generate-redirects.ts similarity index 96% rename from book/vocs/generate-redirects.ts rename to book/vocs/scripts/generate-redirects.ts index 99466c294e1..c56861a5a90 100644 --- a/book/vocs/generate-redirects.ts +++ b/book/vocs/scripts/generate-redirects.ts @@ -1,7 +1,7 @@ #!/usr/bin/env bun import { writeFileSync, mkdirSync } from 'fs' import { join, dirname } from 'path' -import { redirects, basePath } from './redirects.config' +import { redirects, basePath } from '../redirects.config' // Base path for the site function generateRedirectHtml(targetPath: string): string { diff --git a/book/vocs/scripts/inject-cargo-docs.ts b/book/vocs/scripts/inject-cargo-docs.ts new file mode 100644 index 00000000000..1f8fee260d9 --- /dev/null +++ b/book/vocs/scripts/inject-cargo-docs.ts @@ -0,0 +1,105 @@ +import { promises as fs } from 'fs'; +import { join, relative } from 'path'; +import { glob } from 'glob'; + +const CARGO_DOCS_PATH = '../../target/doc'; +const VOCS_DIST_PATH = './docs/dist/docs'; +const BASE_PATH = '/docs'; + +async function injectCargoDocs() { + console.log('Injecting cargo docs into Vocs dist...'); + + // Check if cargo docs exist + try { + await fs.access(CARGO_DOCS_PATH); + } catch { + console.error(`Error: Cargo docs not found at ${CARGO_DOCS_PATH}`); + console.error("Please run: cargo doc --no-deps --workspace --exclude 'example-*'"); + process.exit(1); + } + + // Check if Vocs dist exists + try { + await fs.access('./docs/dist'); + } catch { + console.error('Error: Vocs dist not found. Please run: bun run build'); + process.exit(1); + } + + // Create docs directory in dist if it doesn't exist + await fs.mkdir(VOCS_DIST_PATH, { recursive: true }); + + // Copy all cargo docs to the dist/docs folder + console.log(`Copying cargo docs to ${VOCS_DIST_PATH}...`); + await fs.cp(CARGO_DOCS_PATH, VOCS_DIST_PATH, { recursive: true }); + + // Fix relative paths in HTML files to work from /reth/docs + console.log('Fixing relative paths in HTML files...'); + + const htmlFiles = await glob(`${VOCS_DIST_PATH}/**/*.html`); + + for (const file of htmlFiles) { + let content = await fs.readFile(file, 'utf-8'); + + // Fix static file references + content = content + // CSS and JS in static.files + .replace(/href="\.\/static\.files\//g, `href="${BASE_PATH}/static.files/`) + .replace(/src="\.\/static\.files\//g, `src="${BASE_PATH}/static.files/`) + .replace(/href="\.\.\/static\.files\//g, `href="${BASE_PATH}/static.files/`) + .replace(/src="\.\.\/static\.files\//g, `src="${BASE_PATH}/static.files/`) + + // Fix the dynamic font loading in the script tag + .replace(/href="\$\{f\}"/g, `href="${BASE_PATH}/static.files/\${f}"`) + .replace(/href="\.\/static\.files\/\$\{f\}"/g, `href="${BASE_PATH}/static.files/\${f}"`) + + // Fix crate navigation links + .replace(/href="\.\/([^/]+)\/index\.html"/g, `href="${BASE_PATH}/$1/index.html"`) + .replace(/href="\.\.\/([^/]+)\/index\.html"/g, `href="${BASE_PATH}/$1/index.html"`) + // Fix simple crate links (without ./ or ../) + .replace(/href="([^/:"]+)\/index\.html"/g, `href="${BASE_PATH}/$1/index.html"`) + + // Fix root index.html links + .replace(/href="\.\/index\.html"/g, `href="${BASE_PATH}/index.html"`) + .replace(/href="\.\.\/index\.html"/g, `href="${BASE_PATH}/index.html"`) + + // Fix rustdoc data attributes + .replace(/data-root-path="\.\/"/g, `data-root-path="${BASE_PATH}/"`) + .replace(/data-root-path="\.\.\/"/g, `data-root-path="${BASE_PATH}/"`) + .replace(/data-static-root-path="\.\/static\.files\/"/g, `data-static-root-path="${BASE_PATH}/static.files/"`) + .replace(/data-static-root-path="\.\.\/static\.files\/"/g, `data-static-root-path="${BASE_PATH}/static.files/"`) + + // Fix search index paths + .replace(/data-search-index-js="([^"]+)"/g, `data-search-index-js="${BASE_PATH}/static.files/$1"`) + .replace(/data-search-js="([^"]+)"/g, `data-search-js="${BASE_PATH}/static.files/$1"`) + .replace(/data-settings-js="([^"]+)"/g, `data-settings-js="${BASE_PATH}/static.files/$1"`) + + // Fix logo paths + .replace(/src="\.\/static\.files\/rust-logo/g, `src="${BASE_PATH}/static.files/rust-logo`) + .replace(/src="\.\.\/static\.files\/rust-logo/g, `src="${BASE_PATH}/static.files/rust-logo`); + + await fs.writeFile(file, content, 'utf-8'); + } + + // Also fix paths in JavaScript files + const jsFiles = await glob(`${VOCS_DIST_PATH}/**/*.js`); + + for (const file of jsFiles) { + let content = await fs.readFile(file, 'utf-8'); + + // Fix any hardcoded paths in JS files + content = content + .replace(/"\.\/static\.files\//g, `"${BASE_PATH}/static.files/`) + .replace(/"\.\.\/static\.files\//g, `"${BASE_PATH}/static.files/`) + .replace(/"\.\/([^/]+)\/index\.html"/g, `"${BASE_PATH}/$1/index.html"`) + .replace(/"\.\.\/([^/]+)\/index\.html"/g, `"${BASE_PATH}/$1/index.html"`); + + await fs.writeFile(file, content, 'utf-8'); + } + + console.log('Cargo docs successfully injected!'); + console.log(`The crate documentation will be available at ${BASE_PATH}`); +} + +// Run the script +injectCargoDocs().catch(console.error); \ No newline at end of file diff --git a/book/vocs/vocs.config.ts b/book/vocs/vocs.config.ts index 7cb376d3cd4..299963fc36f 100644 --- a/book/vocs/vocs.config.ts +++ b/book/vocs/vocs.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ topNav: [ { text: 'Run', link: '/run/ethereum' }, { text: 'SDK', link: '/sdk/overview' }, + { text: 'Rustdocs', link: '/docs' }, { text: 'GitHub', link: 'https://github.com/paradigmxyz/reth' }, { text: 'v1.4.8', From 3c2ef0e28f056347f936a5b4d655ae66ff5bd9cb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 26 Jun 2025 14:30:21 +0200 Subject: [PATCH 239/274] chore: bump version in docs (#17085) --- book/vocs/vocs.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/vocs/vocs.config.ts b/book/vocs/vocs.config.ts index 299963fc36f..1f1b76f6a70 100644 --- a/book/vocs/vocs.config.ts +++ b/book/vocs/vocs.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ { text: 'Rustdocs', link: '/docs' }, { text: 'GitHub', link: 'https://github.com/paradigmxyz/reth' }, { - text: 'v1.4.8', + text: 'v1.5.0', items: [ { text: 'Releases', From d635035be72d5fc63329b66e9fa40365a2c8b05a Mon Sep 17 00:00:00 2001 From: Varun Doshi Date: Thu, 26 Jun 2025 17:46:34 +0530 Subject: [PATCH 240/274] feat: punish malicious peers (#16818) Co-authored-by: Matthias Seitz --- crates/net/network/src/transactions/fetcher.rs | 15 +++++++++++---- crates/net/network/src/transactions/mod.rs | 5 ++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/net/network/src/transactions/fetcher.rs b/crates/net/network/src/transactions/fetcher.rs index a6a7b902d80..2656840128c 100644 --- a/crates/net/network/src/transactions/fetcher.rs +++ b/crates/net/network/src/transactions/fetcher.rs @@ -882,15 +882,19 @@ impl TransactionFetcher { if unsolicited > 0 { self.metrics.unsolicited_transactions.increment(unsolicited as u64); } - if verification_outcome == VerificationOutcome::ReportPeer { - // todo: report peer for sending hashes that weren't requested + + let report_peer = if verification_outcome == VerificationOutcome::ReportPeer { trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), unverified_len, verified_payload_len=verified_payload.len(), "received `PooledTransactions` response from peer with entries that didn't verify against request, filtered out transactions" ); - } + true + } else { + false + }; + // peer has only sent hashes that we didn't request if verified_payload.is_empty() { return FetchEvent::FetchError { peer_id, error: RequestError::BadResponse } @@ -952,7 +956,7 @@ impl TransactionFetcher { let transactions = valid_payload.into_data().into_values().collect(); - FetchEvent::TransactionsFetched { peer_id, transactions } + FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } } Ok(Err(req_err)) => { self.try_buffer_hashes_for_retry(requested_hashes, &peer_id); @@ -1039,6 +1043,9 @@ pub enum FetchEvent { peer_id: PeerId, /// The transactions that were fetched, if available. transactions: PooledTransactions, + /// Whether the peer should be penalized for sending unsolicited transactions or for + /// misbehavior. + report_peer: bool, }, /// Triggered when there is an error in fetching transactions. FetchError { diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index 7c73025a213..18233700e25 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -1456,8 +1456,11 @@ where /// Processes a [`FetchEvent`]. fn on_fetch_event(&mut self, fetch_event: FetchEvent) { match fetch_event { - FetchEvent::TransactionsFetched { peer_id, transactions } => { + FetchEvent::TransactionsFetched { peer_id, transactions, report_peer } => { self.import_transactions(peer_id, transactions, TransactionSource::Response); + if report_peer { + self.report_peer(peer_id, ReputationChangeKind::BadTransactions); + } } FetchEvent::FetchError { peer_id, error } => { trace!(target: "net::tx", ?peer_id, %error, "requesting transactions from peer failed"); From 07b19553a14fe9c2e6c36a7c3b8b793bbc9c50a7 Mon Sep 17 00:00:00 2001 From: Rez Date: Fri, 27 Jun 2025 00:38:31 +1000 Subject: [PATCH 241/274] feat: centralize EIP-1559 base fee calculation in EthChainSpec (#16927) Co-authored-by: rose2221 Co-authored-by: Rose Jethani <101273941+rose2221@users.noreply.github.com> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> --- crates/chainspec/src/api.rs | 18 +++- crates/chainspec/src/lib.rs | 30 ++++++ crates/consensus/common/src/validation.rs | 14 +-- crates/ethereum/evm/src/lib.rs | 4 +- crates/ethereum/node/tests/e2e/rpc.rs | 13 +-- crates/optimism/chainspec/src/lib.rs | 12 +-- .../optimism/consensus/src/validation/mod.rs | 12 +-- crates/optimism/rpc/src/eth/mod.rs | 2 +- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 36 +++++--- crates/rpc/rpc-eth-types/src/fee_history.rs | 91 ++++++++----------- crates/rpc/rpc/src/eth/builder.rs | 14 ++- crates/rpc/rpc/src/eth/core.rs | 21 +++-- crates/rpc/rpc/src/eth/helpers/fees.rs | 4 +- crates/rpc/rpc/src/eth/helpers/state.rs | 5 +- crates/transaction-pool/src/maintain.rs | 16 ++-- 15 files changed, 154 insertions(+), 138 deletions(-) diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index 79cf2233582..c21f60dbd6c 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -1,8 +1,8 @@ use crate::{ChainSpec, DepositContract}; use alloc::{boxed::Box, vec::Vec}; use alloy_chains::Chain; -use alloy_consensus::Header; -use alloy_eips::{eip1559::BaseFeeParams, eip7840::BlobParams}; +use alloy_consensus::{BlockHeader, Header}; +use alloy_eips::{calc_next_block_base_fee, eip1559::BaseFeeParams, eip7840::BlobParams}; use alloy_genesis::Genesis; use alloy_primitives::{B256, U256}; use core::fmt::{Debug, Display}; @@ -65,6 +65,20 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// Returns the final total difficulty if the Paris hardfork is known. fn final_paris_total_difficulty(&self) -> Option; + + /// See [`calc_next_block_base_fee`]. + fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> Option + where + Self: Sized, + H: BlockHeader, + { + Some(calc_next_block_base_fee( + parent.gas_used(), + parent.gas_limit(), + parent.base_fee_per_gas()?, + self.base_fee_params_at_timestamp(target_timestamp), + )) + } } impl EthChainSpec for ChainSpec { diff --git a/crates/chainspec/src/lib.rs b/crates/chainspec/src/lib.rs index d140bf88bee..5ba42529399 100644 --- a/crates/chainspec/src/lib.rs +++ b/crates/chainspec/src/lib.rs @@ -145,4 +145,34 @@ mod tests { let chain: Chain = NamedChain::Holesky.into(); assert_eq!(s, chain.public_dns_network_protocol().unwrap().as_str()); } + + #[test] + fn test_centralized_base_fee_calculation() { + use crate::{ChainSpec, EthChainSpec}; + use alloy_consensus::Header; + use alloy_eips::eip1559::INITIAL_BASE_FEE; + + fn parent_header() -> Header { + Header { + gas_used: 15_000_000, + gas_limit: 30_000_000, + base_fee_per_gas: Some(INITIAL_BASE_FEE), + timestamp: 1_000, + ..Default::default() + } + } + + let spec = ChainSpec::default(); + let parent = parent_header(); + + // For testing, assume next block has timestamp 12 seconds later + let next_timestamp = parent.timestamp + 12; + + let expected = parent + .next_block_base_fee(spec.base_fee_params_at_timestamp(next_timestamp)) + .unwrap_or_default(); + + let got = spec.next_block_base_fee(&parent, next_timestamp).unwrap_or_default(); + assert_eq!(expected, got, "Base fee calculation does not match expected value"); + } } diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index b3e75677b1f..1d2ee7bc871 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -3,7 +3,7 @@ use alloy_consensus::{ constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader as _, EMPTY_OMMER_ROOT_HASH, }; -use alloy_eips::{calc_next_block_base_fee, eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams}; +use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams}; use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks}; use reth_consensus::ConsensusError; use reth_primitives_traits::{ @@ -266,15 +266,9 @@ pub fn validate_against_parent_eip1559_base_fee< { alloy_eips::eip1559::INITIAL_BASE_FEE } else { - // This BaseFeeMissing will not happen as previous blocks are checked to have - // them. - let base_fee = parent.base_fee_per_gas().ok_or(ConsensusError::BaseFeeMissing)?; - calc_next_block_base_fee( - parent.gas_used(), - parent.gas_limit(), - base_fee, - chain_spec.base_fee_params_at_timestamp(header.timestamp()), - ) + chain_spec + .next_block_base_fee(parent, header.timestamp()) + .ok_or(ConsensusError::BaseFeeMissing)? }; if expected_base_fee != base_fee { return Err(ConsensusError::BaseFeeDiff(GotExpected { diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 91dbf410225..3160105f1e1 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -214,9 +214,7 @@ where BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } }); - let mut basefee = parent.next_block_base_fee( - self.chain_spec().base_fee_params_at_timestamp(attributes.timestamp), - ); + let mut basefee = chain_spec.next_block_base_fee(parent, attributes.timestamp); let mut gas_limit = attributes.gas_limit; diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index 57462fbfc6d..ea49d8b3c8e 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -1,5 +1,5 @@ use crate::utils::eth_payload_attributes; -use alloy_eips::{calc_next_block_base_fee, eip2718::Encodable2718}; +use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{Address, B256, U256}; use alloy_provider::{network::EthereumWallet, Provider, ProviderBuilder, SendableTx}; use alloy_rpc_types_beacon::relay::{ @@ -9,7 +9,7 @@ use alloy_rpc_types_beacon::relay::{ use alloy_rpc_types_engine::{BlobsBundleV1, ExecutionPayloadV3}; use alloy_rpc_types_eth::TransactionRequest; use rand::{rngs::StdRng, Rng, SeedableRng}; -use reth_chainspec::{ChainSpecBuilder, MAINNET}; +use reth_chainspec::{ChainSpecBuilder, EthChainSpec, MAINNET}; use reth_e2e_test_utils::setup_engine; use reth_node_ethereum::EthereumNode; use reth_payload_primitives::BuiltPayload; @@ -98,14 +98,9 @@ async fn test_fee_history() -> eyre::Result<()> { .unwrap() .header; for block in (latest_block + 2 - block_count)..=latest_block { - let expected_base_fee = calc_next_block_base_fee( - prev_header.gas_used, - prev_header.gas_limit, - prev_header.base_fee_per_gas.unwrap(), - chain_spec.base_fee_params_at_block(block), - ); - let header = provider.get_block_by_number(block.into()).await?.unwrap().header; + let expected_base_fee = + chain_spec.next_block_base_fee(&prev_header, header.timestamp).unwrap(); assert_eq!(header.base_fee_per_gas.unwrap(), expected_base_fee); assert_eq!( diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index a53a3b4c8d3..ba3f317d198 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -736,9 +736,7 @@ mod tests { genesis.hash_slow(), b256!("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd") ); - let base_fee = genesis - .next_block_base_fee(BASE_MAINNET.base_fee_params_at_timestamp(genesis.timestamp)) - .unwrap(); + let base_fee = BASE_MAINNET.next_block_base_fee(genesis, genesis.timestamp).unwrap(); // assert_eq!(base_fee, 980000000); } @@ -750,9 +748,7 @@ mod tests { genesis.hash_slow(), b256!("0x0dcc9e089e30b90ddfc55be9a37dd15bc551aeee999d2e2b51414c54eaf934e4") ); - let base_fee = genesis - .next_block_base_fee(BASE_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp)) - .unwrap(); + let base_fee = BASE_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap(); // assert_eq!(base_fee, 980000000); } @@ -764,9 +760,7 @@ mod tests { genesis.hash_slow(), b256!("0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d") ); - let base_fee = genesis - .next_block_base_fee(OP_SEPOLIA.base_fee_params_at_timestamp(genesis.timestamp)) - .unwrap(); + let base_fee = OP_SEPOLIA.next_block_base_fee(genesis, genesis.timestamp).unwrap(); // assert_eq!(base_fee, 980000000); } diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 1432d0ca37a..bb0756c6fee 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -200,9 +200,7 @@ pub fn next_block_base_fee( if chain_spec.is_holocene_active_at_timestamp(parent.timestamp()) { Ok(decode_holocene_base_fee(chain_spec, parent, timestamp)?) } else { - Ok(parent - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(timestamp)) - .unwrap_or_default()) + Ok(chain_spec.next_block_base_fee(&parent, timestamp).unwrap_or_default()) } } @@ -255,9 +253,7 @@ mod tests { let base_fee = next_block_base_fee(&op_chain_spec, &parent, 0); assert_eq!( base_fee.unwrap(), - parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) - .unwrap_or_default() + op_chain_spec.next_block_base_fee(&parent, 0).unwrap_or_default() ); } @@ -275,9 +271,7 @@ mod tests { let base_fee = next_block_base_fee(&op_chain_spec, &parent, 1800000005); assert_eq!( base_fee.unwrap(), - parent - .next_block_base_fee(op_chain_spec.base_fee_params_at_timestamp(0)) - .unwrap_or_default() + op_chain_spec.next_block_base_fee(&parent, 0).unwrap_or_default() ); } diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index 83210fff801..651dbc77d77 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -235,7 +235,7 @@ where } #[inline] - fn fee_history_cache(&self) -> &FeeHistoryCache { + fn fee_history_cache(&self) -> &FeeHistoryCache> { self.inner.eth_api.fee_history_cache() } diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 75dcb8673e8..3ad83c6102d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -13,7 +13,7 @@ use reth_rpc_eth_types::{ fee_history::calculate_reward_percentiles_for_block, EthApiError, FeeHistoryCache, FeeHistoryEntry, GasPriceOracle, RpcInvalidTransactionError, }; -use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider}; +use reth_storage_api::{BlockIdReader, BlockReaderIdExt, HeaderProvider, ProviderHeader}; use tracing::debug; /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the @@ -136,7 +136,8 @@ pub trait EthFees: LoadFee { } for entry in &fee_entries { - base_fee_per_gas.push(entry.base_fee_per_gas as u128); + base_fee_per_gas + .push(entry.header.base_fee_per_gas().unwrap_or_default() 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); @@ -153,8 +154,12 @@ pub trait EthFees: LoadFee { // 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_gas.push( + self.provider() + .chain_spec() + .next_block_base_fee(&last_entry.header, last_entry.header.timestamp()) + .unwrap_or_default() as u128, + ); base_fee_per_blob_gas.push(last_entry.next_block_blob_fee().unwrap_or_default()); } else { @@ -166,13 +171,12 @@ pub trait EthFees: LoadFee { return Err(EthApiError::InvalidBlockRange.into()) } - + let chain_spec = self.provider().chain_spec(); 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); - let blob_params = self.provider() - .chain_spec() + let blob_params = chain_spec .blob_params_at_timestamp(header.timestamp()) .unwrap_or_else(BlobParams::cancun); @@ -209,18 +213,16 @@ pub trait EthFees: LoadFee { // 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( - last_header.next_block_base_fee( - self.provider() - .chain_spec() - .base_fee_params_at_timestamp(last_header.timestamp())).unwrap_or_default() as u128 + chain_spec + .next_block_base_fee(last_header.header(), last_header.timestamp()) + .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 .maybe_next_block_blob_fee( - self.provider().chain_spec().blob_params_at_timestamp(last_header.timestamp()) + chain_spec.blob_params_at_timestamp(last_header.timestamp()) ).unwrap_or_default() ); }; @@ -238,7 +240,11 @@ pub trait EthFees: LoadFee { /// 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 { + 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; @@ -266,7 +272,7 @@ where /// Returns a handle for reading fee history data from memory. /// /// Data access in default (L1) trait method implementations. - fn fee_history_cache(&self) -> &FeeHistoryCache; + fn fee_history_cache(&self) -> &FeeHistoryCache>; /// Returns the gas price if it is set, otherwise fetches a suggested gas price for legacy /// transactions. diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 6d64c668a4f..011099bf053 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -6,9 +6,8 @@ use std::{ sync::{atomic::Ordering::SeqCst, Arc}, }; -use alloy_consensus::{BlockHeader, Transaction, TxReceipt}; -use alloy_eips::{eip1559::calc_next_block_base_fee, eip7840::BlobParams}; -use alloy_primitives::B256; +use alloy_consensus::{BlockHeader, Header, Transaction, TxReceipt}; +use alloy_eips::eip7840::BlobParams; use alloy_rpc_types_eth::TxGasAndReward; use futures::{ future::{Fuse, FusedFuture}, @@ -29,11 +28,14 @@ use super::{EthApiError, EthStateCache}; /// /// Purpose for this is to provide cached data for `eth_feeHistory`. #[derive(Debug, Clone)] -pub struct FeeHistoryCache { - inner: Arc, +pub struct FeeHistoryCache { + inner: Arc>, } -impl FeeHistoryCache { +impl FeeHistoryCache +where + H: BlockHeader + Clone, +{ /// Creates new `FeeHistoryCache` instance, initialize it with the more recent data, set bounds pub fn new(config: FeeHistoryCacheConfig) -> Self { let inner = FeeHistoryCacheInner { @@ -73,7 +75,7 @@ impl FeeHistoryCache { /// Insert block data into the cache. async fn insert_blocks<'a, I, B, R, C>(&self, blocks: I, chain_spec: &C) where - B: Block + 'a, + B: Block

    + 'a, R: TxReceipt + 'a, I: IntoIterator, &'a [R])>, C: EthChainSpec, @@ -83,14 +85,14 @@ impl FeeHistoryCache { let percentiles = self.predefined_percentiles(); // Insert all new blocks and calculate approximated rewards for (block, receipts) in blocks { - let mut fee_history_entry = FeeHistoryEntry::new( + let mut fee_history_entry = FeeHistoryEntry::::new( block, chain_spec.blob_params_at_timestamp(block.header().timestamp()), ); fee_history_entry.rewards = calculate_reward_percentiles_for_block( &percentiles, - fee_history_entry.gas_used, - fee_history_entry.base_fee_per_gas, + fee_history_entry.header.gas_used(), + fee_history_entry.header.base_fee_per_gas().unwrap_or_default(), block.body().transactions(), receipts, ) @@ -142,7 +144,7 @@ impl FeeHistoryCache { &self, start_block: u64, end_block: u64, - ) -> Option> { + ) -> Option>> { if end_block < start_block { // invalid range, return None return None @@ -198,7 +200,7 @@ impl Default for FeeHistoryCacheConfig { /// Container type for shared state in [`FeeHistoryCache`] #[derive(Debug)] -struct FeeHistoryCacheInner { +struct FeeHistoryCacheInner { /// Stores the lower bound of the cache lower_bound: AtomicU64, /// Stores the upper bound of the cache @@ -207,13 +209,13 @@ struct FeeHistoryCacheInner { /// and max number of blocks config: FeeHistoryCacheConfig, /// Stores the entries of the cache - entries: tokio::sync::RwLock>, + entries: tokio::sync::RwLock>>, } /// Awaits for new chain events and directly inserts them into the cache so they're available /// immediately before they need to be fetched from disk. pub async fn fee_history_cache_new_blocks_task( - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache, mut events: St, provider: Provider, cache: EthStateCache, @@ -222,6 +224,7 @@ pub async fn fee_history_cache_new_blocks_task( Provider: BlockReaderIdExt + ChainSpecProvider + 'static, N: NodePrimitives, + N::BlockHeader: BlockHeader + Clone, { // We're listening for new blocks emitted when the node is in live sync. // If the node transitions to stage sync, we need to fetch the missing blocks @@ -336,9 +339,9 @@ where /// A cached entry for a block's fee history. #[derive(Debug, Clone)] -pub struct FeeHistoryEntry { - /// The base fee per gas for this block. - pub base_fee_per_gas: u64, +pub struct FeeHistoryEntry { + /// The full block header. + pub header: H, /// Gas used ratio this block. pub gas_used_ratio: f64, /// The base per blob gas for EIP-4844. @@ -349,35 +352,28 @@ pub struct FeeHistoryEntry { /// Calculated as the ratio of blob gas used and the available blob data gas per block. /// Will be zero if no blob gas was used or pre EIP-4844. pub blob_gas_used_ratio: f64, - /// The excess blob gas of the block. - pub excess_blob_gas: Option, - /// The total amount of blob gas consumed by the transactions within the block, - /// added in EIP-4844 - pub blob_gas_used: Option, - /// Gas used by this block. - pub gas_used: u64, - /// Gas limit by this block. - pub gas_limit: u64, - /// Hash of the block. - pub header_hash: B256, /// Approximated rewards for the configured percentiles. pub rewards: Vec, - /// The timestamp of the block. - pub timestamp: u64, /// Blob parameters for this block. pub blob_params: Option, } -impl FeeHistoryEntry { +impl FeeHistoryEntry +where + H: BlockHeader + Clone, +{ /// Creates a new entry from a sealed block. /// /// Note: This does not calculate the rewards for the block. - pub fn new(block: &SealedBlock, blob_params: Option) -> Self { + pub fn new(block: &SealedBlock, blob_params: Option) -> Self + where + B: Block
    , + { + let header = block.header(); Self { - base_fee_per_gas: block.header().base_fee_per_gas().unwrap_or_default(), - gas_used_ratio: block.header().gas_used() as f64 / block.header().gas_limit() as f64, - base_fee_per_blob_gas: block - .header() + header: block.header().clone(), + gas_used_ratio: header.gas_used() as f64 / header.gas_limit() as f64, + base_fee_per_blob_gas: header .excess_blob_gas() .and_then(|excess_blob_gas| Some(blob_params?.calc_blob_fee(excess_blob_gas))), blob_gas_used_ratio: block.body().blob_gas_used() as f64 / @@ -386,27 +382,11 @@ impl FeeHistoryEntry { .map(|params| params.max_blob_gas_per_block()) .unwrap_or(alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK_DENCUN) as f64, - excess_blob_gas: block.header().excess_blob_gas(), - blob_gas_used: block.header().blob_gas_used(), - gas_used: block.header().gas_used(), - header_hash: block.hash(), - gas_limit: block.header().gas_limit(), rewards: Vec::new(), - timestamp: block.header().timestamp(), blob_params, } } - /// Returns the base fee for the next block according to the EIP-1559 spec. - pub fn next_block_base_fee(&self, chain_spec: impl EthChainSpec) -> u64 { - calc_next_block_base_fee( - self.gas_used, - self.gas_limit, - self.base_fee_per_gas, - chain_spec.base_fee_params_at_timestamp(self.timestamp), - ) - } - /// Returns the blob fee for the next block according to the EIP-4844 spec. /// /// Returns `None` if `excess_blob_gas` is None. @@ -421,8 +401,11 @@ impl FeeHistoryEntry { /// /// Returns a `None` if no excess blob gas is set, no EIP-4844 support pub fn next_block_excess_blob_gas(&self) -> Option { - self.excess_blob_gas.and_then(|excess_blob_gas| { - Some(self.blob_params?.next_block_excess_blob_gas(excess_blob_gas, self.blob_gas_used?)) + self.header.excess_blob_gas().and_then(|excess_blob_gas| { + Some( + self.blob_params? + .next_block_excess_blob_gas(excess_blob_gas, self.header.blob_gas_used()?), + ) }) } } diff --git a/crates/rpc/rpc/src/eth/builder.rs b/crates/rpc/rpc/src/eth/builder.rs index dbc7af09d0b..732ae1edf11 100644 --- a/crates/rpc/rpc/src/eth/builder.rs +++ b/crates/rpc/rpc/src/eth/builder.rs @@ -160,7 +160,11 @@ where + StateProviderFactory + ChainSpecProvider + CanonStateSubscriptions< - Primitives: NodePrimitives, + Primitives: NodePrimitives< + Block = Provider::Block, + Receipt = Provider::Receipt, + BlockHeader = Provider::Header, + >, > + Clone + Unpin + 'static, @@ -188,7 +192,7 @@ where let gas_oracle = gas_oracle.unwrap_or_else(|| { GasPriceOracle::new(provider.clone(), gas_oracle_config, eth_cache.clone()) }); - let fee_history_cache = FeeHistoryCache::new(fee_history_cache_config); + let fee_history_cache = FeeHistoryCache::::new(fee_history_cache_config); let new_canonical_blocks = provider.canonical_state_stream(); let fhc = fee_history_cache.clone(); let cache = eth_cache.clone(); @@ -232,7 +236,11 @@ where Provider: BlockReaderIdExt + StateProviderFactory + CanonStateSubscriptions< - Primitives: NodePrimitives, + Primitives: NodePrimitives< + Block = Provider::Block, + Receipt = Provider::Receipt, + BlockHeader = Provider::Header, + >, > + ChainSpecProvider + Clone + Unpin diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index f5ff036b73f..f6cceee46e0 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -19,7 +19,8 @@ use reth_rpc_eth_types::{ EthApiError, EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock, }; use reth_storage_api::{ - BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock, ProviderReceipt, + BlockReader, BlockReaderIdExt, NodePrimitivesProvider, ProviderBlock, ProviderHeader, + ProviderReceipt, }; use reth_tasks::{ pool::{BlockingTaskGuard, BlockingTaskPool}, @@ -127,7 +128,7 @@ where max_simulate_blocks: u64, eth_proof_window: u64, blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache>, evm_config: EvmConfig, proof_permits: usize, ) -> Self { @@ -276,7 +277,7 @@ pub struct EthApiInner { /// A pool dedicated to CPU heavy blocking tasks. blocking_task_pool: BlockingTaskPool, /// Cache for block fees history - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache>, /// The type that defines how to configure the EVM evm_config: EvmConfig, @@ -303,7 +304,7 @@ where max_simulate_blocks: u64, eth_proof_window: u64, blocking_task_pool: BlockingTaskPool, - fee_history_cache: FeeHistoryCache, + fee_history_cache: FeeHistoryCache>, evm_config: EvmConfig, task_spawner: Box, proof_permits: usize, @@ -411,7 +412,7 @@ where /// Returns a handle to the fee history cache. #[inline] - pub const fn fee_history_cache(&self) -> &FeeHistoryCache { + pub const fn fee_history_cache(&self) -> &FeeHistoryCache> { &self.fee_history_cache } @@ -470,7 +471,7 @@ mod tests { use jsonrpsee_types::error::INVALID_PARAMS_CODE; use rand::Rng; use reth_chain_state::CanonStateSubscriptions; - use reth_chainspec::{BaseFeeParams, ChainSpec, ChainSpecProvider}; + use reth_chainspec::{ChainSpec, ChainSpecProvider, EthChainSpec}; use reth_ethereum_primitives::TransactionSigned; use reth_evm_ethereum::EthEvmConfig; use reth_network_api::noop::NoopNetwork; @@ -582,9 +583,11 @@ mod tests { // Add final base fee (for the next block outside of the request) let last_header = last_header.unwrap(); - base_fees_per_gas - .push(last_header.next_block_base_fee(BaseFeeParams::ethereum()).unwrap_or_default() - as u128); + let spec = mock_provider.chain_spec(); + base_fees_per_gas.push( + spec.next_block_base_fee(&last_header, last_header.timestamp).unwrap_or_default() + as u128, + ); let eth_api = build_test_eth_api(mock_provider); diff --git a/crates/rpc/rpc/src/eth/helpers/fees.rs b/crates/rpc/rpc/src/eth/helpers/fees.rs index 9ee8b9702be..ddefc6ec9ff 100644 --- a/crates/rpc/rpc/src/eth/helpers/fees.rs +++ b/crates/rpc/rpc/src/eth/helpers/fees.rs @@ -3,7 +3,7 @@ use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_rpc_eth_api::helpers::{EthFees, LoadBlock, LoadFee}; use reth_rpc_eth_types::{FeeHistoryCache, GasPriceOracle}; -use reth_storage_api::{BlockReader, BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{BlockReader, BlockReaderIdExt, ProviderHeader, StateProviderFactory}; use crate::EthApi; @@ -27,7 +27,7 @@ where } #[inline] - fn fee_history_cache(&self) -> &FeeHistoryCache { + fn fee_history_cache(&self) -> &FeeHistoryCache> { self.inner.fee_history_cache() } } diff --git a/crates/rpc/rpc/src/eth/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs index 19b857fa986..90c9e32c64d 100644 --- a/crates/rpc/rpc/src/eth/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -36,6 +36,7 @@ where #[cfg(test)] mod tests { use super::*; + use alloy_consensus::Header; use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M; use alloy_primitives::{Address, StorageKey, StorageValue, U256}; use reth_evm_ethereum::EthEvmConfig; @@ -67,7 +68,7 @@ mod tests { DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_ETH_PROOF_WINDOW, BlockingTaskPool::build().expect("failed to build tracing pool"), - FeeHistoryCache::new(FeeHistoryCacheConfig::default()), + FeeHistoryCache::
    ::new(FeeHistoryCacheConfig::default()), evm_config, DEFAULT_PROOF_PERMITS, ) @@ -93,7 +94,7 @@ mod tests { DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_ETH_PROOF_WINDOW + 1, BlockingTaskPool::build().expect("failed to build tracing pool"), - FeeHistoryCache::new(FeeHistoryCacheConfig::default()), + FeeHistoryCache::
    ::new(FeeHistoryCacheConfig::default()), evm_config, DEFAULT_PROOF_PERMITS, ) diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 1c6a4a52a89..e6982c70f13 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -137,8 +137,8 @@ pub async fn maintain_transaction_pool( block_gas_limit: latest.gas_limit(), last_seen_block_hash: latest.hash(), last_seen_block_number: latest.number(), - pending_basefee: latest - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(latest.timestamp())) + pending_basefee: chain_spec + .next_block_base_fee(latest.header(), latest.timestamp()) .unwrap_or_default(), pending_blob_fee: latest .maybe_next_block_blob_fee(chain_spec.blob_params_at_timestamp(latest.timestamp())), @@ -317,11 +317,8 @@ pub async fn maintain_transaction_pool( let chain_spec = client.chain_spec(); // fees for the next block: `new_tip+1` - let pending_block_base_fee = new_tip - .header() - .next_block_base_fee( - chain_spec.base_fee_params_at_timestamp(new_tip.timestamp()), - ) + let pending_block_base_fee = chain_spec + .next_block_base_fee(new_tip.header(), new_tip.timestamp()) .unwrap_or_default(); let pending_block_blob_fee = new_tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(new_tip.timestamp()), @@ -423,9 +420,8 @@ pub async fn maintain_transaction_pool( let chain_spec = client.chain_spec(); // fees for the next block: `tip+1` - let pending_block_base_fee = tip - .header() - .next_block_base_fee(chain_spec.base_fee_params_at_timestamp(tip.timestamp())) + let pending_block_base_fee = chain_spec + .next_block_base_fee(tip.header(), tip.timestamp()) .unwrap_or_default(); let pending_block_blob_fee = tip.header().maybe_next_block_blob_fee( chain_spec.blob_params_at_timestamp(tip.timestamp()), From 8aeaa4ef352bb82078ee18a895537e8b7286f10c Mon Sep 17 00:00:00 2001 From: Bilog WEB3 <155262265+Bilogweb3@users.noreply.github.com> Date: Thu, 26 Jun 2025 17:10:32 +0200 Subject: [PATCH 242/274] docs: error fixes for clarity (#17091) --- crates/engine/primitives/src/config.rs | 2 +- docs/repo/layout.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/engine/primitives/src/config.rs b/crates/engine/primitives/src/config.rs index 639b227679d..9794caf4473 100644 --- a/crates/engine/primitives/src/config.rs +++ b/crates/engine/primitives/src/config.rs @@ -11,7 +11,7 @@ pub const DEFAULT_MAX_PROOF_TASK_CONCURRENCY: u64 = 256; /// Default number of reserved CPU cores for non-reth processes. /// -/// This will be deducated from the thread count of main reth global threadpool. +/// This will be deducted from the thread count of main reth global threadpool. pub const DEFAULT_RESERVED_CPU_CORES: usize = 1; const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256; diff --git a/docs/repo/layout.md b/docs/repo/layout.md index c7102c0e5b1..46a91b3f0b2 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -135,7 +135,7 @@ The IPC transport lives in [`rpc/ipc`](../../crates/rpc/ipc). #### Utilities Crates -- [`rpc/rpc-convert`](../../crates/rpc/rpc-convert): This crate various helper functions to convert between reth primitive types and rpc types. +- [`rpc/rpc-convert`](../../crates/rpc/rpc-convert): This crate provides various helper functions to convert between reth primitive types and rpc types. - [`rpc/layer`](../../crates/rpc/rpc-layer/): Some RPC middleware layers (e.g. `AuthValidator`, `JwtAuthValidator`) - [`rpc/rpc-testing-util`](../../crates/rpc/rpc-testing-util/): Reth RPC testing helpers From 0e832c2c30cb06923f27fe4976c273fc8e5793ed Mon Sep 17 00:00:00 2001 From: Femi Bankole Date: Thu, 26 Jun 2025 16:28:16 +0100 Subject: [PATCH 243/274] chore: replace revm_utils with alloy_evm helpers (#17046) Co-authored-by: Matthias Seitz --- Cargo.lock | 10 +- .../engine/tree/src/tree/precompile_cache.rs | 2 +- crates/rpc/rpc-eth-api/Cargo.toml | 2 + crates/rpc/rpc-eth-api/src/helpers/call.rs | 17 +- .../rpc/rpc-eth-api/src/helpers/estimate.rs | 2 +- crates/rpc/rpc-eth-types/Cargo.toml | 1 + crates/rpc/rpc-eth-types/src/error/mod.rs | 51 ++++ crates/rpc/rpc-eth-types/src/lib.rs | 1 - crates/rpc/rpc-eth-types/src/revm_utils.rs | 248 ------------------ crates/rpc/rpc/Cargo.toml | 2 +- crates/rpc/rpc/src/eth/sim_bundle.rs | 5 +- 11 files changed, 76 insertions(+), 265 deletions(-) delete mode 100644 crates/rpc/rpc-eth-types/src/revm_utils.rs diff --git a/Cargo.lock b/Cargo.lock index fc828424417..363153bef7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94611bc515c42aeb6ba6211d38ac890247e3fd9de3be8f9c77fa7358f1763e7" +checksum = "b05e6972ba00d593694e2223027a0d841f531cd3cf3e39d60fc8748b1d50d504" dependencies = [ "alloy-consensus", "alloy-eips", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.12.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e387323716e902aa2185cb9cf90917a41cfe0f446e98287cf97f4f1094d00b" +checksum = "3c2bfe13687fb8ea8ec824f6ab758d1a59be1a3ee8b492a9902e7b8806ee57f4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10012,6 +10012,7 @@ dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eips", + "alloy-evm", "alloy-json-rpc", "alloy-network", "alloy-primitives", @@ -10054,6 +10055,7 @@ version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", + "alloy-evm", "alloy-primitives", "alloy-rpc-types-eth", "alloy-sol-types", diff --git a/crates/engine/tree/src/tree/precompile_cache.rs b/crates/engine/tree/src/tree/precompile_cache.rs index eaec22f2b61..ffa9c392a3f 100644 --- a/crates/engine/tree/src/tree/precompile_cache.rs +++ b/crates/engine/tree/src/tree/precompile_cache.rs @@ -191,7 +191,7 @@ where } } - let result = self.precompile.call(input.clone()); + let result = self.precompile.call(input); match &result { Ok(output) => { diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index 69dd83026d8..af8bcb90def 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -33,6 +33,7 @@ reth-trie-common = { workspace = true, features = ["eip1186"] } reth-payload-builder.workspace = true # ethereum +alloy-evm = { workspace = true, features = ["overrides", "call-util"] } alloy-rlp.workspace = true alloy-serde.workspace = true alloy-eips.workspace = true @@ -66,4 +67,5 @@ op = [ "reth-evm/op", "reth-primitives-traits/op", "reth-rpc-convert/op", + "alloy-evm/op", ] diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index ba8122017f4..12d63243f1c 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -7,6 +7,10 @@ use crate::{ }; use alloy_consensus::BlockHeader; use alloy_eips::eip2930::AccessListResult; +use alloy_evm::{ + call::caller_gas_allowance, + overrides::{apply_block_overrides, apply_state_overrides}, +}; use alloy_primitives::{Bytes, B256, U256}; use alloy_rpc_types_eth::{ simulate::{SimBlock, SimulatePayload, SimulatedBlock}, @@ -31,7 +35,6 @@ use reth_rpc_convert::{RpcConvert, RpcTypes}; use reth_rpc_eth_types::{ cache::db::{StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, error::{api::FromEvmHalt, ensure_success, FromEthApiError}, - revm_utils::{apply_block_overrides, apply_state_overrides, caller_gas_allowance}, simulate::{self, EthSimulateError}, EthApiError, RevertError, RpcInvalidTransactionError, StateCacheDb, }; @@ -132,7 +135,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA apply_block_overrides(block_overrides, &mut db, &mut evm_env.block_env); } if let Some(state_overrides) = state_overrides { - apply_state_overrides(state_overrides, &mut db)?; + apply_state_overrides(state_overrides, &mut db) + .map_err(Self::Error::from_eth_err)?; } let block_gas_limit = evm_env.block_env.gas_limit; @@ -381,7 +385,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let mut db = CacheDB::new(StateProviderDatabase::new(state)); if let Some(state_overrides) = state_override { - apply_state_overrides(state_overrides, &mut db)?; + apply_state_overrides(state_overrides, &mut db).map_err(Self::Error::from_eth_err)?; } let mut tx_env = self.create_txn_env(&evm_env, request.clone(), &mut db)?; @@ -399,7 +403,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA evm_env.cfg_env.disable_eip3607 = true; if request.gas.is_none() && tx_env.gas_price() > 0 { - let cap = caller_gas_allowance(&mut db, &tx_env)?; + let cap = caller_gas_allowance(&mut db, &tx_env).map_err(Self::Error::from_eth_err)?; // no gas limit was provided in the request, so we need to cap the request's gas limit tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit)); } @@ -762,7 +766,8 @@ pub trait Call: apply_block_overrides(*block_overrides, db, &mut evm_env.block_env); } if let Some(state_overrides) = overrides.state { - apply_state_overrides(state_overrides, db)?; + apply_state_overrides(state_overrides, db) + .map_err(EthApiError::from_state_overrides_err)?; } let request_gas = request.gas; @@ -773,7 +778,7 @@ pub trait Call: if tx_env.gas_price() > 0 { // If gas price is specified, cap transaction gas limit with caller allowance trace!(target: "rpc::eth::call", ?tx_env, "Applying gas limit cap with caller allowance"); - let cap = caller_gas_allowance(db, &tx_env)?; + let cap = caller_gas_allowance(db, &tx_env).map_err(EthApiError::from_call_err)?; // ensure we cap gas_limit to the block's tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit)); } diff --git a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs index efee8b5b58f..91af2c37e4c 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs @@ -2,6 +2,7 @@ use super::{Call, LoadPendingBlock}; use crate::{AsEthApiError, FromEthApiError, IntoEthApiError}; +use alloy_evm::{call::caller_gas_allowance, overrides::apply_state_overrides}; use alloy_primitives::{TxKind, U256}; use alloy_rpc_types_eth::{state::StateOverride, transaction::TransactionRequest, BlockId}; use futures::Future; @@ -11,7 +12,6 @@ use reth_evm::{ConfigureEvm, Database, Evm, EvmEnvFor, EvmFor, TransactionEnv, T use reth_revm::{database::StateProviderDatabase, db::CacheDB}; use reth_rpc_eth_types::{ error::{api::FromEvmHalt, FromEvmError}, - revm_utils::{apply_state_overrides, caller_gas_allowance}, EthApiError, RevertError, RpcInvalidTransactionError, }; use reth_rpc_server_types::constants::gas_oracle::{CALL_STIPEND_GAS, ESTIMATE_GAS_ERROR_RATIO}; diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml index f969a9a4891..4a2104d9146 100644 --- a/crates/rpc/rpc-eth-types/Cargo.toml +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -30,6 +30,7 @@ reth-trie.workspace = true # ethereum alloy-eips.workspace = true +alloy-evm = { workspace = true, features = ["overrides", "call-util"] } alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-sol-types.workspace = true diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 1d936fe87a3..96adc4e67b2 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -3,6 +3,7 @@ pub mod api; use crate::error::api::FromEvmHalt; use alloy_eips::BlockId; +use alloy_evm::{call::CallError, overrides::StateOverrideError}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError}; use alloy_sol_types::{ContractError, RevertReason}; @@ -185,6 +186,22 @@ impl EthApiError { pub const fn is_gas_too_low(&self) -> bool { matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooLow)) } + + /// Converts the given [`StateOverrideError`] into a new [`EthApiError`] instance. + pub fn from_state_overrides_err(err: StateOverrideError) -> Self + where + E: Into, + { + err.into() + } + + /// Converts the given [`CallError`] into a new [`EthApiError`] instance. + pub fn from_call_err(err: CallError) -> Self + where + E: Into, + { + err.into() + } } impl From for jsonrpsee_types::error::ErrorObject<'static> { @@ -259,6 +276,40 @@ impl From for EthApiError { } } +impl From> for EthApiError +where + E: Into, +{ + fn from(value: CallError) -> Self { + match value { + CallError::Database(err) => err.into(), + CallError::InsufficientFunds(insufficient_funds_error) => { + Self::InvalidTransaction(RpcInvalidTransactionError::InsufficientFunds { + cost: insufficient_funds_error.cost, + balance: insufficient_funds_error.balance, + }) + } + } + } +} + +impl From> for EthApiError +where + E: Into, +{ + fn from(value: StateOverrideError) -> Self { + match value { + StateOverrideError::InvalidBytecode(bytecode_decode_error) => { + Self::InvalidBytecode(bytecode_decode_error.to_string()) + } + StateOverrideError::BothStateAndStateDiff(address) => { + Self::BothStateAndStateDiffInOverride(address) + } + StateOverrideError::Database(err) => err.into(), + } + } +} + impl From for EthApiError { fn from(value: EthTxEnvError) -> Self { match value { diff --git a/crates/rpc/rpc-eth-types/src/lib.rs b/crates/rpc/rpc-eth-types/src/lib.rs index 8d92fda9c33..815160abf4e 100644 --- a/crates/rpc/rpc-eth-types/src/lib.rs +++ b/crates/rpc/rpc-eth-types/src/lib.rs @@ -17,7 +17,6 @@ pub mod id_provider; pub mod logs_utils; pub mod pending_block; pub mod receipt; -pub mod revm_utils; pub mod simulate; pub mod transaction; pub mod utils; diff --git a/crates/rpc/rpc-eth-types/src/revm_utils.rs b/crates/rpc/rpc-eth-types/src/revm_utils.rs deleted file mode 100644 index 69f51ec0ed8..00000000000 --- a/crates/rpc/rpc-eth-types/src/revm_utils.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! utilities for working with revm - -use alloy_primitives::{keccak256, Address, B256, U256}; -use alloy_rpc_types_eth::{ - state::{AccountOverride, StateOverride}, - BlockOverrides, -}; -use revm::{ - context::BlockEnv, - database::{CacheDB, State}, - state::{Account, AccountStatus, Bytecode, EvmStorageSlot}, - Database, DatabaseCommit, -}; -use std::collections::{BTreeMap, HashMap}; - -use super::{EthApiError, EthResult, RpcInvalidTransactionError}; - -/// Calculates the caller gas allowance. -/// -/// `allowance = (account.balance - tx.value) / tx.gas_price` -/// -/// Returns an error if the caller has insufficient funds. -/// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned. -/// -/// Note: this takes the mut [Database] trait because the loaded sender can be reused for the -/// following operation like `eth_call`. -pub fn caller_gas_allowance(db: &mut DB, env: &T) -> EthResult -where - DB: Database, - EthApiError: From<::Error>, - T: revm::context_interface::Transaction, -{ - // Get the caller account. - let caller = db.basic(env.caller())?; - // Get the caller balance. - let balance = caller.map(|acc| acc.balance).unwrap_or_default(); - // Get transaction value. - let value = env.value(); - // Subtract transferred value from the caller balance. Return error if the caller has - // insufficient funds. - let balance = balance - .checked_sub(env.value()) - .ok_or_else(|| RpcInvalidTransactionError::InsufficientFunds { cost: value, balance })?; - - Ok(balance - // Calculate the amount of gas the caller can afford with the specified gas price. - .checked_div(U256::from(env.gas_price())) - // This will be 0 if gas price is 0. It is fine, because we check it before. - .unwrap_or_default() - .saturating_to()) -} - -/// Helper trait implemented for databases that support overriding block hashes. -/// -/// Used for applying [`BlockOverrides::block_hash`] -pub trait OverrideBlockHashes { - /// Overrides the given block hashes. - fn override_block_hashes(&mut self, block_hashes: BTreeMap); -} - -impl OverrideBlockHashes for CacheDB { - fn override_block_hashes(&mut self, block_hashes: BTreeMap) { - self.cache - .block_hashes - .extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash))) - } -} - -impl OverrideBlockHashes for State { - fn override_block_hashes(&mut self, block_hashes: BTreeMap) { - self.block_hashes.extend(block_hashes); - } -} - -/// Applies the given block overrides to the env and updates overridden block hashes in the db. -pub fn apply_block_overrides( - overrides: BlockOverrides, - db: &mut impl OverrideBlockHashes, - env: &mut BlockEnv, -) { - let BlockOverrides { - number, - difficulty, - time, - gas_limit, - coinbase, - random, - base_fee, - block_hash, - } = overrides; - - if let Some(block_hashes) = block_hash { - // override block hashes - db.override_block_hashes(block_hashes); - } - - if let Some(number) = number { - env.number = number.saturating_to(); - } - if let Some(difficulty) = difficulty { - env.difficulty = difficulty; - } - if let Some(time) = time { - env.timestamp = U256::from(time); - } - if let Some(gas_limit) = gas_limit { - env.gas_limit = gas_limit; - } - if let Some(coinbase) = coinbase { - env.beneficiary = coinbase; - } - if let Some(random) = random { - env.prevrandao = Some(random); - } - if let Some(base_fee) = base_fee { - env.basefee = base_fee.saturating_to(); - } -} - -/// Applies the given state overrides (a set of [`AccountOverride`]) to the [`CacheDB`]. -pub fn apply_state_overrides(overrides: StateOverride, db: &mut DB) -> EthResult<()> -where - DB: Database + DatabaseCommit, - EthApiError: From, -{ - for (account, account_overrides) in overrides { - apply_account_override(account, account_overrides, db)?; - } - Ok(()) -} - -/// Applies a single [`AccountOverride`] to the [`CacheDB`]. -fn apply_account_override( - account: Address, - account_override: AccountOverride, - db: &mut DB, -) -> EthResult<()> -where - DB: Database + DatabaseCommit, - EthApiError: From, -{ - let mut info = db.basic(account)?.unwrap_or_default(); - - if let Some(nonce) = account_override.nonce { - info.nonce = nonce; - } - if let Some(code) = account_override.code { - // we need to set both the bytecode and the codehash - info.code_hash = keccak256(&code); - info.code = Some( - Bytecode::new_raw_checked(code) - .map_err(|err| EthApiError::InvalidBytecode(err.to_string()))?, - ); - } - if let Some(balance) = account_override.balance { - info.balance = balance; - } - - // Create a new account marked as touched - let mut acc = revm::state::Account { - info, - status: AccountStatus::Touched, - storage: HashMap::default(), - transaction_id: 0, - }; - - let storage_diff = match (account_override.state, account_override.state_diff) { - (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)), - (None, None) => None, - // If we need to override the entire state, we firstly mark account as destroyed to clear - // its storage, and then we mark it is "NewlyCreated" to make sure that old storage won't be - // used. - (Some(state), None) => { - // Destroy the account to ensure that its storage is cleared - db.commit(HashMap::from_iter([( - account, - Account { - status: AccountStatus::SelfDestructed | AccountStatus::Touched, - ..Default::default() - }, - )])); - // Mark the account as created to ensure that old storage is not read - acc.mark_created(); - Some(state) - } - (None, Some(state)) => Some(state), - }; - - if let Some(state) = storage_diff { - for (slot, value) in state { - acc.storage.insert( - slot.into(), - EvmStorageSlot { - // we use inverted value here to ensure that storage is treated as changed - original_value: (!value).into(), - present_value: value.into(), - is_cold: false, - transaction_id: 0, - }, - ); - } - } - - db.commit(HashMap::from_iter([(account, acc)])); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::{address, bytes}; - use reth_revm::db::EmptyDB; - - #[test] - fn state_override_state() { - let code = bytes!( - "0x63d0e30db05f525f5f6004601c3473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5260205ff3" - ); - let to = address!("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); - - let mut db = State::builder().with_database(CacheDB::new(EmptyDB::new())).build(); - - let acc_override = AccountOverride::default().with_code(code.clone()); - apply_account_override(to, acc_override, &mut db).unwrap(); - - let account = db.basic(to).unwrap().unwrap(); - assert!(account.code.is_some()); - assert_eq!(account.code_hash, keccak256(&code)); - } - - #[test] - fn state_override_cache_db() { - let code = bytes!( - "0x63d0e30db05f525f5f6004601c3473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5260205ff3" - ); - let to = address!("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); - - let mut db = CacheDB::new(EmptyDB::new()); - - let acc_override = AccountOverride::default().with_code(code.clone()); - apply_account_override(to, acc_override, &mut db).unwrap(); - - let account = db.basic(to).unwrap().unwrap(); - assert!(account.code.is_some()); - assert_eq!(account.code_hash, keccak256(&code)); - } -} diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 5c83527e1b5..2f41caa5480 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -42,7 +42,7 @@ reth-node-api.workspace = true reth-trie-common.workspace = true # ethereum -alloy-evm.workspace = true +alloy-evm = { workspace = true, features = ["overrides"] } alloy-consensus.workspace = true alloy-signer.workspace = true alloy-signer-local.workspace = true diff --git a/crates/rpc/rpc/src/eth/sim_bundle.rs b/crates/rpc/rpc/src/eth/sim_bundle.rs index 0bd3fb67076..cfc11658575 100644 --- a/crates/rpc/rpc/src/eth/sim_bundle.rs +++ b/crates/rpc/rpc/src/eth/sim_bundle.rs @@ -2,6 +2,7 @@ use alloy_consensus::BlockHeader; use alloy_eips::BlockNumberOrTag; +use alloy_evm::overrides::apply_block_overrides; use alloy_primitives::U256; use alloy_rpc_types_eth::BlockId; use alloy_rpc_types_mev::{ @@ -17,9 +18,7 @@ use reth_rpc_eth_api::{ helpers::{block::LoadBlock, Call, EthTransactions}, FromEthApiError, FromEvmError, }; -use reth_rpc_eth_types::{ - revm_utils::apply_block_overrides, utils::recover_raw_transaction, EthApiError, -}; +use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; use reth_storage_api::ProviderTx; use reth_tasks::pool::BlockingTaskGuard; use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool}; From cfdd173afc6c05b280f0dd6aa3449be59d572849 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Thu, 26 Jun 2025 18:33:42 +0200 Subject: [PATCH 244/274] perf(trie): implement remove_leaf for ParallelSparseTrie (#17035) --- crates/evm/execution-errors/src/trie.rs | 6 + crates/trie/sparse-parallel/src/trie.rs | 972 +++++++++++++++++++++++- 2 files changed, 959 insertions(+), 19 deletions(-) diff --git a/crates/evm/execution-errors/src/trie.rs b/crates/evm/execution-errors/src/trie.rs index b8a1a3e9bd3..7c5ad72b1cb 100644 --- a/crates/evm/execution-errors/src/trie.rs +++ b/crates/evm/execution-errors/src/trie.rs @@ -170,6 +170,12 @@ pub enum SparseTrieErrorKind { /// RLP error. #[error(transparent)] Rlp(#[from] alloy_rlp::Error), + /// Node not found in provider during revealing. + #[error("node {path:?} not found in provider during removal")] + NodeNotFoundInProvider { + /// Path to the missing node. + path: Nibbles, + }, /// Other. #[error(transparent)] Other(#[from] Box), diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 7b6402d4d37..b2d8d147f8c 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -1,5 +1,3 @@ -use std::sync::mpsc; - use alloy_primitives::{ map::{Entry, HashMap}, B256, @@ -12,10 +10,11 @@ use reth_trie_common::{ BranchNodeRef, ExtensionNodeRef, LeafNodeRef, Nibbles, RlpNode, TrieNode, CHILD_INDEX_RANGE, }; use reth_trie_sparse::{ - blinded::BlindedProvider, RlpNodeStackItem, SparseNode, SparseNodeType, SparseTrieUpdates, - TrieMasks, + blinded::{BlindedProvider, RevealedNode}, + RlpNodeStackItem, SparseNode, SparseNodeType, SparseTrieUpdates, TrieMasks, }; use smallvec::SmallVec; +use std::sync::mpsc; use tracing::{instrument, trace}; /// The maximum length of a path, in nibbles, which belongs to the upper subtrie of a @@ -57,8 +56,10 @@ impl Default for ParallelSparseTrie { } impl ParallelSparseTrie { - /// Returns mutable ref to the lower `SparseSubtrie` for the given path, or None if the path - /// belongs to the upper trie. + /// Returns a mutable reference to the lower `SparseSubtrie` for the given path, or None if the + /// path belongs to the upper trie. + /// + /// This method will create a new lower subtrie if one doesn't exist for the given path. fn lower_subtrie_for_path(&mut self, path: &Nibbles) -> Option<&mut Box> { match SparseSubtrieType::from_path(path) { SparseSubtrieType::Upper => None, @@ -73,6 +74,24 @@ impl ParallelSparseTrie { } } + /// Returns a mutable reference to either the lower or upper `SparseSubtrie` for the given path, + /// depending on the path's length. + /// + /// This method will create a new lower subtrie if one doesn't exist for the given path. + fn subtrie_for_path(&mut self, path: &Nibbles) -> &mut Box { + match SparseSubtrieType::from_path(path) { + SparseSubtrieType::Upper => &mut self.upper_subtrie, + SparseSubtrieType::Lower(idx) => { + if self.lower_subtries[idx].is_none() { + let upper_path = path.slice(..UPPER_TRIE_MAX_DEPTH); + self.lower_subtries[idx] = Some(Box::new(SparseSubtrie::new(upper_path))); + } + + self.lower_subtries[idx].as_mut().unwrap() + } + } + } + /// Creates a new revealed sparse trie from the given root node. /// /// # Returns @@ -103,7 +122,6 @@ impl ParallelSparseTrie { node: TrieNode, masks: TrieMasks, ) -> SparseTrieResult<()> { - // TODO parallelize if let Some(subtrie) = self.lower_subtrie_for_path(&path) { return subtrie.reveal_node(path, &node, masks); } @@ -175,23 +193,426 @@ impl ParallelSparseTrie { todo!() } - /// Removes a leaf node from the trie at the specified key path. + /// Returns the next node in the traversal path from the given path towards the leaf for the + /// given full leaf path, or an error if any node along the traversal path is not revealed. + /// + /// + /// ## Panics + /// + /// If `from_path` is not a prefix of `leaf_full_path`. + fn find_next_to_leaf( + from_path: &Nibbles, + from_node: &SparseNode, + leaf_full_path: &Nibbles, + ) -> SparseTrieResult { + debug_assert!(leaf_full_path.len() >= from_path.len()); + debug_assert!(leaf_full_path.starts_with(from_path)); + + match from_node { + SparseNode::Empty => Err(SparseTrieErrorKind::Blind.into()), + SparseNode::Hash(hash) => { + Err(SparseTrieErrorKind::BlindedNode { path: *from_path, hash: *hash }.into()) + } + SparseNode::Leaf { key, .. } => { + let mut found_full_path = *from_path; + found_full_path.extend(key); + + if &found_full_path == leaf_full_path { + return Ok(FindNextToLeafOutcome::Found) + } + Ok(FindNextToLeafOutcome::NotFound) + } + SparseNode::Extension { key, .. } => { + if leaf_full_path.len() == from_path.len() { + return Ok(FindNextToLeafOutcome::NotFound) + } + + let mut child_path = *from_path; + child_path.extend(key); + + if !leaf_full_path.starts_with(&child_path) { + return Ok(FindNextToLeafOutcome::NotFound) + } + Ok(FindNextToLeafOutcome::ContinueFrom(child_path)) + } + SparseNode::Branch { state_mask, .. } => { + if leaf_full_path.len() == from_path.len() { + return Ok(FindNextToLeafOutcome::NotFound) + } + + let nibble = leaf_full_path.get_unchecked(from_path.len()); + if !state_mask.is_bit_set(nibble) { + return Ok(FindNextToLeafOutcome::NotFound) + } + + let mut child_path = *from_path; + child_path.push_unchecked(nibble); + + Ok(FindNextToLeafOutcome::ContinueFrom(child_path)) + } + } + } + + /// Called when a child node has collapsed into its parent as part of `remove_leaf`. If the + /// new parent node is a leaf, then the previous child also was, and if the previous child was + /// on a lower subtrie while the parent is on an upper then the leaf value needs to be moved to + /// the upper. + fn move_value_on_leaf_removal( + &mut self, + parent_path: &Nibbles, + new_parent_node: &SparseNode, + prev_child_path: &Nibbles, + ) { + // If the parent path isn't in the upper then it doesn't matter what the new node is, + // there's no situation where a leaf value needs to be moved. + if SparseSubtrieType::from_path(parent_path).lower_index().is_some() { + return; + } + + if let SparseNode::Leaf { key, .. } = new_parent_node { + let Some(prev_child_subtrie) = self.lower_subtrie_for_path(prev_child_path) else { + return; + }; + + let mut leaf_full_path = *parent_path; + leaf_full_path.extend(key); + + let val = prev_child_subtrie.inner.values.remove(&leaf_full_path).expect("ParallelSparseTrie is in an inconsistent state, expected value on subtrie which wasn't found"); + self.upper_subtrie.inner.values.insert(leaf_full_path, val); + } + } + + /// Given the path to a parent branch node and a child node which is the sole remaining child on + /// that branch after removing a leaf, returns a node to replace the parent branch node and a + /// boolean indicating if the child should be deleted. + /// + /// ## Panics + /// + /// - If either parent or child node is not already revealed. + /// - If parent's path is not a prefix of the child's path. + fn branch_changes_on_leaf_removal( + parent_path: &Nibbles, + remaining_child_path: &Nibbles, + remaining_child_node: &SparseNode, + ) -> (SparseNode, bool) { + debug_assert!(remaining_child_path.len() > parent_path.len()); + debug_assert!(remaining_child_path.starts_with(parent_path)); + + let remaining_child_nibble = remaining_child_path.get_unchecked(parent_path.len()); + + // If we swap the branch node out either an extension or leaf, depending on + // what its remaining child is. + match remaining_child_node { + SparseNode::Empty | SparseNode::Hash(_) => { + panic!("remaining child must have been revealed already") + } + // If the only child is a leaf node, we downgrade the branch node into a + // leaf node, prepending the nibble to the key, and delete the old + // child. + SparseNode::Leaf { key, .. } => { + let mut new_key = Nibbles::from_nibbles_unchecked([remaining_child_nibble]); + new_key.extend(key); + (SparseNode::new_leaf(new_key), true) + } + // If the only child node is an extension node, we downgrade the branch + // node into an even longer extension node, prepending the nibble to the + // key, and delete the old child. + SparseNode::Extension { key, .. } => { + let mut new_key = Nibbles::from_nibbles_unchecked([remaining_child_nibble]); + new_key.extend(key); + (SparseNode::new_ext(new_key), true) + } + // If the only child is a branch node, we downgrade the current branch + // node into a one-nibble extension node. + SparseNode::Branch { .. } => ( + SparseNode::new_ext(Nibbles::from_nibbles_unchecked([remaining_child_nibble])), + false, + ), + } + } + + /// Given the path to a parent extension and its key, and a child node (not necessarily on this + /// subtrie), returns an optional replacement parent node. If a replacement is returned then the + /// child node should be deleted. + /// + /// ## Panics + /// + /// - If either parent or child node is not already revealed. + /// - If parent's path is not a prefix of the child's path. + fn extension_changes_on_leaf_removal( + parent_path: &Nibbles, + parent_key: &Nibbles, + child_path: &Nibbles, + child: &SparseNode, + ) -> Option { + debug_assert!(child_path.len() > parent_path.len()); + debug_assert!(child_path.starts_with(parent_path)); + + // If the parent node is an extension node, we need to look at its child to see + // if we need to merge it. + match child { + SparseNode::Empty | SparseNode::Hash(_) => { + panic!("child must be revealed") + } + // For a leaf node, we collapse the extension node into a leaf node, + // extending the key. While it's impossible to encounter an extension node + // followed by a leaf node in a complete trie, it's possible here because we + // could have downgraded the extension node's child into a leaf node from a + // branch in a previous call to `branch_changes_on_leaf_removal`. + SparseNode::Leaf { key, .. } => { + let mut new_key = *parent_key; + new_key.extend(key); + Some(SparseNode::new_leaf(new_key)) + } + // Similar to the leaf node, for an extension node, we collapse them into one + // extension node, extending the key. + SparseNode::Extension { key, .. } => { + let mut new_key = *parent_key; + new_key.extend(key); + Some(SparseNode::new_ext(new_key)) + } + // For a branch node, we just leave the extension node as-is. + SparseNode::Branch { .. } => None, + } + } + + /// Removes a leaf node from the trie at the specified full path of a value (that is, the leaf's + /// path + its key). /// /// This function removes the leaf value from the internal values map and then traverses /// the trie to remove or adjust intermediate nodes, merging or collapsing them as necessary. /// /// # Returns /// - /// Returns `Ok(())` if the leaf is successfully removed, otherwise returns an error - /// if the leaf is not present or if a blinded node prevents removal. + /// Returns `Ok(())` if the leaf is successfully removed or was not present in the trie, + /// otherwise returns an error if a blinded node prevents removal. pub fn remove_leaf( &mut self, - path: &Nibbles, + leaf_full_path: &Nibbles, provider: impl BlindedProvider, ) -> SparseTrieResult<()> { - let _path = path; - let _provider = provider; - todo!() + // When removing a leaf node it's possibly necessary to modify its parent node, and possibly + // the parent's parent node. It is not ever necessary to descend further than that; once an + // extension node is hit it must terminate in a branch or the root, which won't need further + // updates. So the situation with maximum updates is: + // + // - Leaf + // - Branch with 2 children, one being this leaf + // - Extension + // + // ...which will result in just a leaf or extension, depending on what the branch's other + // child is. + // + // Therefore, first traverse the trie in order to find the leaf node and at most its parent + // and grandparent. + + let leaf_path; + let leaf_subtrie; + + let mut branch_parent_path: Option = None; + let mut branch_parent_node: Option = None; + + let mut ext_grandparent_path: Option = None; + let mut ext_grandparent_node: Option = None; + + let mut curr_path = Nibbles::new(); // start traversal from root + let mut curr_subtrie = self.upper_subtrie.as_mut(); + let mut curr_subtrie_is_upper = true; + + loop { + let curr_node = curr_subtrie.nodes.get_mut(&curr_path).unwrap(); + + match Self::find_next_to_leaf(&curr_path, curr_node, leaf_full_path)? { + FindNextToLeafOutcome::NotFound => return Ok(()), // leaf isn't in the trie + FindNextToLeafOutcome::Found => { + // this node is the target leaf + leaf_path = curr_path; + leaf_subtrie = curr_subtrie; + break; + } + FindNextToLeafOutcome::ContinueFrom(next_path) => { + // Any branches/extensions along the path to the leaf will have their `hash` + // field unset, as it will no longer be valid once the leaf is removed. + match curr_node { + SparseNode::Branch { hash, .. } => { + *hash = None; + + // If there is already an extension leading into a branch, then that + // extension is no longer relevant. + match (&branch_parent_path, &ext_grandparent_path) { + (Some(branch), Some(ext)) if branch.len() > ext.len() => { + ext_grandparent_path = None; + ext_grandparent_node = None; + } + _ => (), + }; + branch_parent_path = Some(curr_path); + branch_parent_node = Some(curr_node.clone()); + } + SparseNode::Extension { hash, .. } => { + *hash = None; + + // We can assume a new branch node will be found after the extension, so + // there's no need to modify branch_parent_path/node even if it's + // already set. + ext_grandparent_path = Some(curr_path); + ext_grandparent_node = Some(curr_node.clone()); + } + SparseNode::Empty | SparseNode::Hash(_) | SparseNode::Leaf { .. } => { + unreachable!("find_next_to_leaf errors on non-revealed node, and return Found or NotFound on Leaf") + } + } + + curr_path = next_path; + + // If we were previously looking at the upper trie, and the new path is in the + // lower trie, we need to pull out a ref to the lower trie. + if curr_subtrie_is_upper { + if let SparseSubtrieType::Lower(idx) = + SparseSubtrieType::from_path(&curr_path) + { + curr_subtrie = self.lower_subtries[idx].as_mut().unwrap(); + curr_subtrie_is_upper = false; + } + } + } + }; + } + + // We've traversed to the leaf and collected its ancestors as necessary. Remove the leaf + // from its SparseSubtrie. + self.prefix_set.insert(*leaf_full_path); + leaf_subtrie.inner.values.remove(leaf_full_path); + leaf_subtrie.nodes.remove(&leaf_path); + + // If the leaf was at the root replace its node with the empty value. We can stop execution + // here, all remaining logic is related to the ancestors of the leaf. + if leaf_path.is_empty() { + self.upper_subtrie.nodes.insert(leaf_path, SparseNode::Empty); + return Ok(()) + } + + // If there is a parent branch node (very likely, unless the leaf is at the root) execute + // any required changes for that node, relative to the removed leaf. + if let (Some(branch_path), Some(SparseNode::Branch { mut state_mask, .. })) = + (&branch_parent_path, &branch_parent_node) + { + let child_nibble = leaf_path.get_unchecked(branch_path.len()); + state_mask.unset_bit(child_nibble); + + let new_branch_node = if state_mask.count_bits() == 1 { + // If only one child is left set in the branch node, we need to collapse it. Get + // full path of the only child node left. + let remaining_child_path = { + let mut p = *branch_path; + p.push_unchecked( + state_mask.first_set_bit_index().expect("state mask is not empty"), + ); + p + }; + + trace!( + target: "trie::parallel_sparse", + ?leaf_path, + ?branch_path, + ?remaining_child_path, + "Branch node has only one child", + ); + + let remaining_child_subtrie = self.subtrie_for_path(&remaining_child_path); + + // If the remaining child node is not yet revealed then we have to reveal it here, + // otherwise it's not possible to know how to collapse the branch. + let remaining_child_node = + match remaining_child_subtrie.nodes.get(&remaining_child_path).unwrap() { + SparseNode::Hash(_) => { + trace!( + target: "trie::parallel_sparse", + ?remaining_child_path, + "Retrieving remaining blinded branch child", + ); + if let Some(RevealedNode { node, tree_mask, hash_mask }) = + provider.blinded_node(&remaining_child_path)? + { + let decoded = TrieNode::decode(&mut &node[..])?; + trace!( + target: "trie::parallel_sparse", + ?remaining_child_path, + ?decoded, + ?tree_mask, + ?hash_mask, + "Revealing remaining blinded branch child" + ); + remaining_child_subtrie.reveal_node( + remaining_child_path, + &decoded, + TrieMasks { hash_mask, tree_mask }, + )?; + remaining_child_subtrie.nodes.get(&remaining_child_path).unwrap() + } else { + return Err(SparseTrieErrorKind::NodeNotFoundInProvider { + path: remaining_child_path, + } + .into()) + } + } + node => node, + }; + + let (new_branch_node, remove_child) = Self::branch_changes_on_leaf_removal( + branch_path, + &remaining_child_path, + remaining_child_node, + ); + + if remove_child { + remaining_child_subtrie.nodes.remove(&remaining_child_path); + self.move_value_on_leaf_removal( + branch_path, + &new_branch_node, + &remaining_child_path, + ); + } + + if let Some(updates) = self.updates.as_mut() { + updates.updated_nodes.remove(branch_path); + updates.removed_nodes.insert(*branch_path); + } + + new_branch_node + } else { + // If more than one child is left set in the branch, we just re-insert it with the + // updated state_mask. + SparseNode::new_branch(state_mask) + }; + + let branch_subtrie = self.subtrie_for_path(branch_path); + branch_subtrie.nodes.insert(*branch_path, new_branch_node.clone()); + branch_parent_node = Some(new_branch_node); + }; + + // If there is a grandparent extension node then there will necessarily be a parent branch + // node. Execute any required changes for the extension node, relative to the (possibly now + // replaced with a leaf or extension) branch node. + if let (Some(ext_path), Some(SparseNode::Extension { key: shortkey, .. })) = + (ext_grandparent_path, &ext_grandparent_node) + { + let ext_subtrie = self.subtrie_for_path(&ext_path); + let branch_path = branch_parent_path.as_ref().unwrap(); + + if let Some(new_ext_node) = Self::extension_changes_on_leaf_removal( + &ext_path, + shortkey, + branch_path, + branch_parent_node.as_ref().unwrap(), + ) { + ext_subtrie.nodes.insert(ext_path, new_ext_node.clone()); + self.subtrie_for_path(branch_path).nodes.remove(branch_path); + self.move_value_on_leaf_removal(&ext_path, &new_ext_node, branch_path); + } + } + + Ok(()) } /// Recalculates and updates the RLP hashes of nodes up to level [`UPPER_TRIE_MAX_DEPTH`] of the @@ -387,6 +808,18 @@ pub struct SparseSubtrie { inner: SparseSubtrieInner, } +/// Returned by the `find_next_to_leaf` method to indicate either that the leaf has been found, +/// traversal should be continued from the given path, or the leaf is not in the trie. +enum FindNextToLeafOutcome { + /// `Found` indicates that the leaf was found at the given path. + Found, + /// `ContinueFrom` indicates that traversal should continue from the given path. + ContinueFrom(Nibbles), + /// `NotFound` indicates that there is no way to traverse to the leaf, as it is not in the + /// trie. + NotFound, +} + impl SparseSubtrie { fn new(path: Nibbles) -> Self { Self { path, ..Default::default() } @@ -401,8 +834,7 @@ impl SparseSubtrie { self } - /// Returns true if the current path and its child are both found in the same level. This - /// function assumes that if `current_path` is in a lower level then `child_path` is too. + /// Returns true if the current path and its child are both found in the same level. fn is_child_same_level(current_path: &Nibbles, child_path: &Nibbles) -> bool { let current_level = core::mem::discriminant(&SparseSubtrieType::from_path(current_path)); let child_level = core::mem::discriminant(&SparseSubtrieType::from_path(child_path)); @@ -1091,13 +1523,14 @@ mod tests { }; use crate::trie::ChangedSubtrie; use alloy_primitives::{ - map::{B256Set, HashMap}, + map::{foldhash::fast::RandomState, B256Set, DefaultHashBuilder, HashMap}, B256, }; use alloy_rlp::{Decodable, Encodable}; - use alloy_trie::Nibbles; + use alloy_trie::{BranchNodeCompact, Nibbles}; use assert_matches::assert_matches; use itertools::Itertools; + use reth_execution_errors::SparseTrieError; use reth_primitives_traits::Account; use reth_trie::{ hashed_cursor::{noop::NoopHashedAccountCursor, HashedPostStateAccountCursor}, @@ -1112,7 +1545,38 @@ mod tests { BranchNode, ExtensionNode, HashBuilder, HashedPostState, LeafNode, RlpNode, TrieMask, TrieNode, EMPTY_ROOT_HASH, }; - use reth_trie_sparse::{SparseNode, TrieMasks}; + use reth_trie_sparse::{ + blinded::{BlindedProvider, RevealedNode}, + SparseNode, TrieMasks, + }; + + /// Mock blinded provider for testing that allows pre-setting nodes at specific paths. + /// + /// This provider can be used in tests to simulate blinded nodes that need to be revealed + /// during trie operations, particularly when collapsing branch nodes during leaf removal. + #[derive(Debug, Clone)] + struct MockBlindedProvider { + /// Mapping from path to revealed node data + nodes: HashMap, + } + + impl MockBlindedProvider { + /// Creates a new empty mock provider + fn new() -> Self { + Self { nodes: HashMap::with_hasher(RandomState::default()) } + } + + /// Adds a revealed node at the specified path + fn add_revealed_node(&mut self, path: Nibbles, node: RevealedNode) { + self.nodes.insert(path, node); + } + } + + impl BlindedProvider for MockBlindedProvider { + fn blinded_node(&self, path: &Nibbles) -> Result, SparseTrieError> { + Ok(self.nodes.get(path).cloned()) + } + } fn create_account(nonce: u64) -> Account { Account { nonce, ..Default::default() } @@ -1225,6 +1689,26 @@ mod tests { (root, trie_updates, proof_nodes, branch_node_hash_masks, branch_node_tree_masks) } + /// Returns a `ParallelSparseTrie` pre-loaded with the given nodes, as well as leaf values + /// inferred from any provided leaf nodes. + fn new_test_trie(nodes: Nodes) -> ParallelSparseTrie + where + Nodes: Iterator, + { + let mut trie = ParallelSparseTrie::default().with_updates(true); + + for (path, node) in nodes { + let subtrie = trie.subtrie_for_path(&path); + if let SparseNode::Leaf { key, .. } = &node { + let mut full_key = path; + full_key.extend(key); + subtrie.inner.values.insert(full_key, "LEAF VALUE".into()); + } + subtrie.nodes.insert(path, node); + } + trie + } + /// Assert that the parallel sparse trie nodes and the proof nodes from the hash builder are /// equal. #[allow(unused)] @@ -1801,6 +2285,456 @@ mod tests { assert_eq!(hash_builder_leaf_3_hash, subtrie_leaf_3_hash); } + #[test] + fn test_remove_leaf_branch_becomes_extension() { + // + // 0x: Extension (Key = 5) + // 0x5: └── Branch (Mask = 1001) + // 0x50: ├── 0 -> Extension (Key = 23) + // 0x5023: │ └── Branch (Mask = 0101) + // 0x50231: │ ├── 1 -> Leaf + // 0x50233: │ └── 3 -> Leaf + // 0x53: └── 3 -> Leaf (Key = 7) + // + // After removing 0x53, extension+branch+extension become a single extension + // + let mut trie = new_test_trie( + [ + (Nibbles::default(), SparseNode::new_ext(Nibbles::from_nibbles([0x5]))), + (Nibbles::from_nibbles([0x5]), SparseNode::new_branch(TrieMask::new(0b1001))), + ( + Nibbles::from_nibbles([0x5, 0x0]), + SparseNode::new_ext(Nibbles::from_nibbles([0x2, 0x3])), + ), + ( + Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3]), + SparseNode::new_branch(TrieMask::new(0b0101)), + ), + ( + Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3, 0x1]), + SparseNode::new_leaf(Nibbles::new()), + ), + ( + Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3, 0x3]), + SparseNode::new_leaf(Nibbles::new()), + ), + ( + Nibbles::from_nibbles([0x5, 0x3]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x7])), + ), + ] + .into_iter(), + ); + + let provider = MockBlindedProvider::new(); + + // Remove the leaf with a full path of 0x537 + let leaf_full_path = Nibbles::from_nibbles([0x5, 0x3, 0x7]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + let lower_subtrie_50 = trie.lower_subtries[0x50].as_ref().unwrap(); + let lower_subtrie_53 = trie.lower_subtries[0x53].as_ref().unwrap(); + + // Check that the leaf value was removed from the appropriate `SparseSubtrie`. + assert_matches!(lower_subtrie_53.inner.values.get(&leaf_full_path), None); + + // Check that the leaf node was removed, and that its parent/grandparent were modified + // appropriately. + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::from_nibbles([])), + Some(SparseNode::Extension{ key, ..}) + if key == &Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3]) + ); + assert_matches!(upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x5])), None); + assert_matches!(lower_subtrie_50.nodes.get(&Nibbles::from_nibbles([0x5, 0x0])), None); + assert_matches!( + lower_subtrie_50.nodes.get(&Nibbles::from_nibbles([0x5, 0x0, 0x2, 0x3])), + Some(SparseNode::Branch{ state_mask, .. }) + if *state_mask == 0b0101.into() + ); + assert_matches!(lower_subtrie_53.nodes.get(&Nibbles::from_nibbles([0x5, 0x3])), None); + } + + #[test] + fn test_remove_leaf_branch_becomes_leaf() { + // + // 0x: Branch (Mask = 0011) + // 0x0: ├── 0 -> Leaf (Key = 12) + // 0x1: └── 1 -> Leaf (Key = 34) + // + // After removing 0x012, branch becomes a leaf + // + let mut trie = new_test_trie( + [ + (Nibbles::default(), SparseNode::new_branch(TrieMask::new(0b0011))), + ( + Nibbles::from_nibbles([0x0]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x1, 0x2])), + ), + ( + Nibbles::from_nibbles([0x1]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x3, 0x4])), + ), + ] + .into_iter(), + ); + + // Add the branch node to updated_nodes to simulate it being modified earlier + if let Some(updates) = trie.updates.as_mut() { + updates + .updated_nodes + .insert(Nibbles::default(), BranchNodeCompact::new(0b11, 0, 0, vec![], None)); + } + + let provider = MockBlindedProvider::new(); + + // Remove the leaf with a full path of 0x012 + let leaf_full_path = Nibbles::from_nibbles([0x0, 0x1, 0x2]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + + // Check that the leaf's value was removed + assert_matches!(upper_subtrie.inner.values.get(&leaf_full_path), None); + + // Check that the branch node collapsed into a leaf node with the remaining child's key + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::default()), + Some(SparseNode::Leaf{ key, ..}) + if key == &Nibbles::from_nibbles([0x1, 0x3, 0x4]) + ); + + // Check that the remaining child node was removed + assert_matches!(upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x1])), None); + // Check that the removed child node was also removed + assert_matches!(upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x0])), None); + + // Check that updates were tracked correctly when branch collapsed + let updates = trie.updates.as_ref().unwrap(); + + // The branch at root should be marked as removed since it collapsed + assert!(updates.removed_nodes.contains(&Nibbles::default())); + + // The branch should no longer be in updated_nodes + assert!(!updates.updated_nodes.contains_key(&Nibbles::default())); + } + + #[test] + fn test_remove_leaf_extension_becomes_leaf() { + // + // 0x: Extension (Key = 5) + // 0x5: └── Branch (Mask = 0011) + // 0x50: ├── 0 -> Leaf (Key = 12) + // 0x51: └── 1 -> Leaf (Key = 34) + // + // After removing 0x5012, extension+branch becomes a leaf + // + let mut trie = new_test_trie( + [ + (Nibbles::default(), SparseNode::new_ext(Nibbles::from_nibbles([0x5]))), + (Nibbles::from_nibbles([0x5]), SparseNode::new_branch(TrieMask::new(0b0011))), + ( + Nibbles::from_nibbles([0x5, 0x0]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x1, 0x2])), + ), + ( + Nibbles::from_nibbles([0x5, 0x1]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x3, 0x4])), + ), + ] + .into_iter(), + ); + + let provider = MockBlindedProvider::new(); + + // Remove the leaf with a full path of 0x5012 + let leaf_full_path = Nibbles::from_nibbles([0x5, 0x0, 0x1, 0x2]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + let lower_subtrie_50 = trie.lower_subtries[0x50].as_ref().unwrap(); + let lower_subtrie_51 = trie.lower_subtries[0x51].as_ref().unwrap(); + + // Check that the full key was removed + assert_matches!(lower_subtrie_50.inner.values.get(&leaf_full_path), None); + + // Check that the other leaf's value was moved to the upper trie + let other_leaf_full_value = Nibbles::from_nibbles([0x5, 0x1, 0x3, 0x4]); + assert_matches!(lower_subtrie_51.inner.values.get(&other_leaf_full_value), None); + assert_matches!(upper_subtrie.inner.values.get(&other_leaf_full_value), Some(_)); + + // Check that the extension node collapsed into a leaf node + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::default()), + Some(SparseNode::Leaf{ key, ..}) + if key == &Nibbles::from_nibbles([0x5, 0x1, 0x3, 0x4]) + ); + + // Check that intermediate nodes were removed + assert_matches!(upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x5])), None); + assert_matches!(lower_subtrie_50.nodes.get(&Nibbles::from_nibbles([0x5, 0x0])), None); + assert_matches!(lower_subtrie_51.nodes.get(&Nibbles::from_nibbles([0x5, 0x1])), None); + } + + #[test] + fn test_remove_leaf_branch_on_branch() { + // + // 0x: Branch (Mask = 0101) + // 0x0: ├── 0 -> Leaf (Key = 12) + // 0x2: └── 2 -> Branch (Mask = 0011) + // 0x20: ├── 0 -> Leaf (Key = 34) + // 0x21: └── 1 -> Leaf (Key = 56) + // + // After removing 0x2034, the inner branch becomes a leaf + // + let mut trie = new_test_trie( + [ + (Nibbles::default(), SparseNode::new_branch(TrieMask::new(0b0101))), + ( + Nibbles::from_nibbles([0x0]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x1, 0x2])), + ), + (Nibbles::from_nibbles([0x2]), SparseNode::new_branch(TrieMask::new(0b0011))), + ( + Nibbles::from_nibbles([0x2, 0x0]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x3, 0x4])), + ), + ( + Nibbles::from_nibbles([0x2, 0x1]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x5, 0x6])), + ), + ] + .into_iter(), + ); + + let provider = MockBlindedProvider::new(); + + // Remove the leaf with a full path of 0x2034 + let leaf_full_path = Nibbles::from_nibbles([0x2, 0x0, 0x3, 0x4]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + let lower_subtrie_20 = trie.lower_subtries[0x20].as_ref().unwrap(); + let lower_subtrie_21 = trie.lower_subtries[0x21].as_ref().unwrap(); + + // Check that the leaf's value was removed + assert_matches!(lower_subtrie_20.inner.values.get(&leaf_full_path), None); + + // Check that the other leaf's value was moved to the upper trie + let other_leaf_full_value = Nibbles::from_nibbles([0x2, 0x1, 0x5, 0x6]); + assert_matches!(lower_subtrie_21.inner.values.get(&other_leaf_full_value), None); + assert_matches!(upper_subtrie.inner.values.get(&other_leaf_full_value), Some(_)); + + // Check that the root branch still exists unchanged + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::default()), + Some(SparseNode::Branch{ state_mask, .. }) + if *state_mask == 0b0101.into() + ); + + // Check that the inner branch became an extension + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x2])), + Some(SparseNode::Leaf{ key, ..}) + if key == &Nibbles::from_nibbles([0x1, 0x5, 0x6]) + ); + + // Check that the branch's child nodes were removed + assert_matches!(lower_subtrie_20.nodes.get(&Nibbles::from_nibbles([0x2, 0x0])), None); + assert_matches!(lower_subtrie_21.nodes.get(&Nibbles::from_nibbles([0x2, 0x1])), None); + } + + #[test] + fn test_remove_leaf_remaining_child_needs_reveal() { + // + // 0x: Branch (Mask = 0011) + // 0x0: ├── 0 -> Leaf (Key = 12) + // 0x1: └── 1 -> Hash (blinded leaf) + // + // After removing 0x012, the hash node needs to be revealed to collapse the branch + // + let mut trie = new_test_trie( + [ + (Nibbles::default(), SparseNode::new_branch(TrieMask::new(0b0011))), + ( + Nibbles::from_nibbles([0x0]), + SparseNode::new_leaf(Nibbles::from_nibbles([0x1, 0x2])), + ), + (Nibbles::from_nibbles([0x1]), SparseNode::Hash(B256::repeat_byte(0xab))), + ] + .into_iter(), + ); + + // Create a mock provider that will reveal the blinded leaf + let mut provider = MockBlindedProvider::new(); + let revealed_leaf = create_leaf_node([0x3, 0x4], 42); + let mut encoded = Vec::new(); + revealed_leaf.encode(&mut encoded); + provider.add_revealed_node( + Nibbles::from_nibbles([0x1]), + RevealedNode { node: encoded.into(), tree_mask: None, hash_mask: None }, + ); + + // Remove the leaf with a full path of 0x012 + let leaf_full_path = Nibbles::from_nibbles([0x0, 0x1, 0x2]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + + // Check that the leaf value was removed + assert_matches!(upper_subtrie.inner.values.get(&leaf_full_path), None); + + // Check that the branch node collapsed into a leaf node with the revealed child's key + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::default()), + Some(SparseNode::Leaf{ key, ..}) + if key == &Nibbles::from_nibbles([0x1, 0x3, 0x4]) + ); + + // Check that the remaining child node was removed (since it was merged) + assert_matches!(upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x1])), None); + } + + #[test] + fn test_remove_leaf_root() { + // + // 0x: Leaf (Key = 123) + // + // After removing 0x123, the trie becomes empty + // + let mut trie = new_test_trie(std::iter::once(( + Nibbles::default(), + SparseNode::new_leaf(Nibbles::from_nibbles([0x1, 0x2, 0x3])), + ))); + + let provider = MockBlindedProvider::new(); + + // Remove the leaf with a full key of 0x123 + let leaf_full_path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + + // Check that the leaf value was removed + assert_matches!(upper_subtrie.inner.values.get(&leaf_full_path), None); + + // Check that the root node was changed to Empty + assert_matches!(upper_subtrie.nodes.get(&Nibbles::default()), Some(SparseNode::Empty)); + } + + #[test] + fn test_remove_leaf_unsets_hash_along_path() { + // + // Creates a trie structure: + // 0x: Branch (with hash set) + // 0x0: ├── Extension (with hash set) + // 0x01: │ └── Branch (with hash set) + // 0x012: │ ├── Leaf (Key = 34, with hash set) + // 0x013: │ ├── Leaf (Key = 56, with hash set) + // 0x014: │ └── Leaf (Key = 78, with hash set) + // 0x1: └── Leaf (Key = 78, with hash set) + // + // When removing leaf at 0x01234, all nodes along the path (root branch, + // extension at 0x0, branch at 0x01) should have their hash field unset + // + + let mut trie = new_test_trie( + [ + ( + Nibbles::default(), + SparseNode::Branch { + state_mask: TrieMask::new(0b0011), + hash: Some(B256::repeat_byte(0x10)), + store_in_db_trie: None, + }, + ), + ( + Nibbles::from_nibbles([0x0]), + SparseNode::Extension { + key: Nibbles::from_nibbles([0x1]), + hash: Some(B256::repeat_byte(0x20)), + store_in_db_trie: None, + }, + ), + ( + Nibbles::from_nibbles([0x0, 0x1]), + SparseNode::Branch { + state_mask: TrieMask::new(0b11100), + hash: Some(B256::repeat_byte(0x30)), + store_in_db_trie: None, + }, + ), + ( + Nibbles::from_nibbles([0x0, 0x1, 0x2]), + SparseNode::Leaf { + key: Nibbles::from_nibbles([0x3, 0x4]), + hash: Some(B256::repeat_byte(0x40)), + }, + ), + ( + Nibbles::from_nibbles([0x0, 0x1, 0x3]), + SparseNode::Leaf { + key: Nibbles::from_nibbles([0x5, 0x6]), + hash: Some(B256::repeat_byte(0x50)), + }, + ), + ( + Nibbles::from_nibbles([0x0, 0x1, 0x4]), + SparseNode::Leaf { + key: Nibbles::from_nibbles([0x6, 0x7]), + hash: Some(B256::repeat_byte(0x60)), + }, + ), + ( + Nibbles::from_nibbles([0x1]), + SparseNode::Leaf { + key: Nibbles::from_nibbles([0x7, 0x8]), + hash: Some(B256::repeat_byte(0x70)), + }, + ), + ] + .into_iter(), + ); + + let provider = MockBlindedProvider::new(); + + // Remove the leaf at path 0x01234 + let leaf_full_path = Nibbles::from_nibbles([0x0, 0x1, 0x2, 0x3, 0x4]); + trie.remove_leaf(&leaf_full_path, provider).unwrap(); + + let upper_subtrie = &trie.upper_subtrie; + let lower_subtrie_10 = trie.lower_subtries[0x01].as_ref().unwrap(); + + // Verify that hash fields are unset for all nodes along the path to the removed leaf + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::default()), + Some(SparseNode::Branch { hash: None, .. }) + ); + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x0])), + Some(SparseNode::Extension { hash: None, .. }) + ); + assert_matches!( + lower_subtrie_10.nodes.get(&Nibbles::from_nibbles([0x0, 0x1])), + Some(SparseNode::Branch { hash: None, .. }) + ); + + // Verify that nodes not on the path still have their hashes + assert_matches!( + upper_subtrie.nodes.get(&Nibbles::from_nibbles([0x1])), + Some(SparseNode::Leaf { hash: Some(_), .. }) + ); + assert_matches!( + lower_subtrie_10.nodes.get(&Nibbles::from_nibbles([0x0, 0x1, 0x3])), + Some(SparseNode::Leaf { hash: Some(_), .. }) + ); + assert_matches!( + lower_subtrie_10.nodes.get(&Nibbles::from_nibbles([0x0, 0x1, 0x4])), + Some(SparseNode::Leaf { hash: Some(_), .. }) + ); + } + #[test] fn test_parallel_sparse_trie_root() { let mut trie = ParallelSparseTrie::default().with_updates(true); From 8066771473734a5e5e2a1701d50d2b2a08a7d148 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 27 Jun 2025 01:38:26 +0200 Subject: [PATCH 245/274] fix: use safe conversions for number and timestamps (#17093) --- crates/ethereum/evm/src/build.rs | 4 ++-- crates/optimism/evm/src/build.rs | 4 ++-- crates/rpc/rpc-eth-api/src/helpers/trace.rs | 2 +- crates/rpc/rpc/src/debug.rs | 4 ++-- crates/rpc/rpc/src/eth/bundle.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/ethereum/evm/src/build.rs b/crates/ethereum/evm/src/build.rs index bb7500f579c..5e80ca9ba46 100644 --- a/crates/ethereum/evm/src/build.rs +++ b/crates/ethereum/evm/src/build.rs @@ -52,7 +52,7 @@ where .. } = input; - let timestamp = evm_env.block_env.timestamp.to(); + let timestamp = evm_env.block_env.timestamp.saturating_to(); let transactions_root = proofs::calculate_transaction_root(&transactions); let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts); @@ -101,7 +101,7 @@ where mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(evm_env.block_env.basefee), - number: evm_env.block_env.number.to(), + number: evm_env.block_env.number.saturating_to(), gas_limit: evm_env.block_env.gas_limit, difficulty: evm_env.block_env.difficulty, gas_used: *gas_used, diff --git a/crates/optimism/evm/src/build.rs b/crates/optimism/evm/src/build.rs index 2b7bf540e02..94d9822e78a 100644 --- a/crates/optimism/evm/src/build.rs +++ b/crates/optimism/evm/src/build.rs @@ -52,7 +52,7 @@ impl OpBlockAssembler { .. } = input; - let timestamp = evm_env.block_env.timestamp.to(); + let timestamp = evm_env.block_env.timestamp.saturating_to(); let transactions_root = proofs::calculate_transaction_root(&transactions); let receipts_root = @@ -97,7 +97,7 @@ impl OpBlockAssembler { mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), nonce: BEACON_NONCE.into(), base_fee_per_gas: Some(evm_env.block_env.basefee), - number: evm_env.block_env.number.to(), + number: evm_env.block_env.number.saturating_to(), gas_limit: evm_env.block_env.gas_limit, difficulty: evm_env.block_env.difficulty, gas_used: *gas_used, diff --git a/crates/rpc/rpc-eth-api/src/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/helpers/trace.rs index 400159c64d3..31085bdc08f 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/trace.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/trace.rs @@ -315,7 +315,7 @@ pub trait Trace: let state_at = block.parent_hash(); let block_hash = block.hash(); - let block_number = evm_env.block_env.number.to(); + let block_number = evm_env.block_env.number.saturating_to(); let base_fee = evm_env.block_env.basefee; // now get the state diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 024d2a83a29..632811962d6 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -362,7 +362,7 @@ where let db = db.0; let tx_info = TransactionInfo { - block_number: Some(evm_env.block_env.number.to()), + block_number: Some(evm_env.block_env.number.saturating_to()), base_fee: Some(evm_env.block_env.basefee), hash: None, block_hash: None, @@ -727,7 +727,7 @@ where .map(|c| c.tx_index.map(|i| i as u64)) .unwrap_or_default(), block_hash: transaction_context.as_ref().map(|c| c.block_hash).unwrap_or_default(), - block_number: Some(evm_env.block_env.number.to()), + block_number: Some(evm_env.block_env.number.saturating_to()), base_fee: Some(evm_env.block_env.basefee), }; diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index 2fe1a478eb9..08fe3c84cd4 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -110,7 +110,7 @@ where .eth_api() .provider() .chain_spec() - .blob_params_at_timestamp(evm_env.block_env.timestamp.to()) + .blob_params_at_timestamp(evm_env.block_env.timestamp.saturating_to()) .unwrap_or_else(BlobParams::cancun); if transactions.iter().filter_map(|tx| tx.blob_gas_used()).sum::() > blob_params.max_blob_gas_per_block() From a33be2e02ee1dc9f6cfb568aafa120d00a84f72a Mon Sep 17 00:00:00 2001 From: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:18:45 +0530 Subject: [PATCH 246/274] chore(`docs`): move to docs from book (#17096) Co-authored-by: Claude --- .github/workflows/book.yml | 8 ++++---- .github/workflows/lint.yml | 4 ++-- .gitignore | 2 +- Makefile | 2 +- {book => docs}/cli/help.rs | 0 {book => docs}/cli/update.sh | 10 +++++----- docs/vocs/.claude/settings.local.json | 8 ++++++++ {book => docs}/vocs/CLAUDE.md | 0 {book => docs}/vocs/README.md | 0 {book => docs}/vocs/bun.lockb | Bin 311438 -> 312478 bytes .../vocs/docs/components/SdkShowcase.tsx | 0 .../vocs/docs/components/TrustedBy.tsx | 0 .../vocs/docs/pages/cli/SUMMARY.mdx | 0 {book => docs}/vocs/docs/pages/cli/cli.mdx | 0 {book => docs}/vocs/docs/pages/cli/op-reth.md | 0 {book => docs}/vocs/docs/pages/cli/reth.mdx | 0 .../vocs/docs/pages/cli/reth/config.mdx | 0 .../vocs/docs/pages/cli/reth/db.mdx | 0 .../vocs/docs/pages/cli/reth/db/checksum.mdx | 0 .../vocs/docs/pages/cli/reth/db/clear.mdx | 0 .../docs/pages/cli/reth/db/clear/mdbx.mdx | 0 .../pages/cli/reth/db/clear/static-file.mdx | 0 .../vocs/docs/pages/cli/reth/db/diff.mdx | 0 .../vocs/docs/pages/cli/reth/db/drop.mdx | 0 .../vocs/docs/pages/cli/reth/db/get.mdx | 0 .../vocs/docs/pages/cli/reth/db/get/mdbx.mdx | 0 .../pages/cli/reth/db/get/static-file.mdx | 0 .../vocs/docs/pages/cli/reth/db/list.mdx | 0 .../vocs/docs/pages/cli/reth/db/path.mdx | 0 .../vocs/docs/pages/cli/reth/db/stats.mdx | 0 .../vocs/docs/pages/cli/reth/db/version.mdx | 0 .../vocs/docs/pages/cli/reth/debug.mdx | 0 .../docs/pages/cli/reth/debug/build-block.mdx | 0 .../docs/pages/cli/reth/debug/execution.mdx | 0 .../pages/cli/reth/debug/in-memory-merkle.mdx | 0 .../vocs/docs/pages/cli/reth/debug/merkle.mdx | 0 .../vocs/docs/pages/cli/reth/download.mdx | 0 .../vocs/docs/pages/cli/reth/dump-genesis.mdx | 0 .../vocs/docs/pages/cli/reth/import-era.mdx | 0 .../vocs/docs/pages/cli/reth/import.mdx | 0 .../vocs/docs/pages/cli/reth/init-state.mdx | 0 .../vocs/docs/pages/cli/reth/init.mdx | 0 .../vocs/docs/pages/cli/reth/node.mdx | 0 .../vocs/docs/pages/cli/reth/p2p.mdx | 0 .../vocs/docs/pages/cli/reth/p2p/body.mdx | 0 .../vocs/docs/pages/cli/reth/p2p/header.mdx | 0 .../vocs/docs/pages/cli/reth/p2p/rlpx.mdx | 0 .../docs/pages/cli/reth/p2p/rlpx/ping.mdx | 0 .../vocs/docs/pages/cli/reth/prune.mdx | 0 .../vocs/docs/pages/cli/reth/recover.mdx | 0 .../pages/cli/reth/recover/storage-tries.mdx | 0 .../vocs/docs/pages/cli/reth/stage.mdx | 0 .../vocs/docs/pages/cli/reth/stage/drop.mdx | 0 .../vocs/docs/pages/cli/reth/stage/dump.mdx | 0 .../cli/reth/stage/dump/account-hashing.mdx | 0 .../pages/cli/reth/stage/dump/execution.mdx | 0 .../docs/pages/cli/reth/stage/dump/merkle.mdx | 0 .../cli/reth/stage/dump/storage-hashing.mdx | 0 .../vocs/docs/pages/cli/reth/stage/run.mdx | 0 .../vocs/docs/pages/cli/reth/stage/unwind.mdx | 0 .../cli/reth/stage/unwind/num-blocks.mdx | 0 .../pages/cli/reth/stage/unwind/to-block.mdx | 0 .../pages/cli/reth/test-vectors/tables.mdx | 0 .../vocs/docs/pages/exex/hello-world.mdx | 0 .../vocs/docs/pages/exex/how-it-works.mdx | 0 .../vocs/docs/pages/exex/overview.mdx | 0 .../vocs/docs/pages/exex/remote.mdx | 0 .../vocs/docs/pages/exex/tracking-state.mdx | 0 {book => docs}/vocs/docs/pages/index.mdx | 0 .../vocs/docs/pages/installation/binaries.mdx | 0 .../installation/build-for-arm-devices.mdx | 0 .../vocs/docs/pages/installation/docker.mdx | 0 .../vocs/docs/pages/installation/overview.mdx | 0 .../docs/pages/installation/priorities.mdx | 0 .../vocs/docs/pages/installation/source.mdx | 0 .../docs/pages/introduction/contributing.mdx | 0 .../vocs/docs/pages/introduction/why-reth.mdx | 0 .../vocs/docs/pages/jsonrpc/admin.mdx | 0 .../vocs/docs/pages/jsonrpc/debug.mdx | 0 .../vocs/docs/pages/jsonrpc/eth.mdx | 0 .../vocs/docs/pages/jsonrpc/intro.mdx | 0 .../vocs/docs/pages/jsonrpc/net.mdx | 0 .../vocs/docs/pages/jsonrpc/rpc.mdx | 0 .../vocs/docs/pages/jsonrpc/trace.mdx | 0 .../vocs/docs/pages/jsonrpc/txpool.mdx | 0 .../vocs/docs/pages/jsonrpc/web3.mdx | 0 {book => docs}/vocs/docs/pages/overview.mdx | 0 .../vocs/docs/pages/run/configuration.mdx | 0 .../vocs/docs/pages/run/ethereum.mdx | 0 .../docs/pages/run/ethereum/snapshots.mdx | 0 {book => docs}/vocs/docs/pages/run/faq.mdx | 0 .../vocs/docs/pages/run/faq/ports.mdx | 0 .../vocs/docs/pages/run/faq/profiling.mdx | 0 .../vocs/docs/pages/run/faq/pruning.mdx | 0 .../docs/pages/run/faq/sync-op-mainnet.mdx | 0 .../vocs/docs/pages/run/faq/transactions.mdx | 0 .../docs/pages/run/faq/troubleshooting.mdx | 0 .../vocs/docs/pages/run/monitoring.mdx | 0 .../vocs/docs/pages/run/networks.mdx | 0 .../vocs/docs/pages/run/opstack.mdx | 0 .../pages/run/opstack/op-mainnet-caveats.mdx | 0 .../vocs/docs/pages/run/overview.mdx | 0 .../vocs/docs/pages/run/private-testnets.mdx | 0 .../docs/pages/run/system-requirements.mdx | 0 .../pages/sdk/custom-node/modifications.mdx | 0 .../pages/sdk/custom-node/prerequisites.mdx | 0 .../docs/pages/sdk/examples/modify-node.mdx | 0 .../sdk/examples/standalone-components.mdx | 0 .../vocs/docs/pages/sdk/node-components.mdx | 0 .../pages/sdk/node-components/consensus.mdx | 0 .../docs/pages/sdk/node-components/evm.mdx | 0 .../pages/sdk/node-components/network.mdx | 0 .../docs/pages/sdk/node-components/pool.mdx | 0 .../docs/pages/sdk/node-components/rpc.mdx | 0 .../vocs/docs/pages/sdk/overview.mdx | 0 .../vocs/docs/pages/sdk/typesystem/block.mdx | 0 .../sdk/typesystem/transaction-types.mdx | 0 {book => docs}/vocs/docs/public/alchemy.png | Bin {book => docs}/vocs/docs/public/coinbase.png | Bin {book => docs}/vocs/docs/public/flashbots.png | Bin {book => docs}/vocs/docs/public/logo.png | Bin .../vocs/docs/public/remote_exex.png | Bin {book => docs}/vocs/docs/public/reth-prod.png | Bin {book => docs}/vocs/docs/public/succinct.png | Bin .../vocs/docs/snippets/sources/Cargo.toml | 0 .../sources/exex/hello-world/Cargo.toml | 0 .../sources/exex/hello-world/src/bin/1.rs | 0 .../sources/exex/hello-world/src/bin/2.rs | 0 .../sources/exex/hello-world/src/bin/3.rs | 0 .../snippets/sources/exex/remote/Cargo.toml | 0 .../snippets/sources/exex/remote/build.rs | 0 .../sources/exex/remote/proto/exex.proto | 0 .../sources/exex/remote/src/consumer.rs | 0 .../snippets/sources/exex/remote/src/exex.rs | 0 .../sources/exex/remote/src/exex_1.rs | 0 .../sources/exex/remote/src/exex_2.rs | 0 .../sources/exex/remote/src/exex_3.rs | 0 .../sources/exex/remote/src/exex_4.rs | 0 .../snippets/sources/exex/remote/src/lib.rs | 0 .../sources/exex/tracking-state/Cargo.toml | 0 .../sources/exex/tracking-state/src/bin/1.rs | 0 .../sources/exex/tracking-state/src/bin/2.rs | 0 {book => docs}/vocs/docs/styles.css | 0 {book => docs}/vocs/links-report.json | 0 {book => docs}/vocs/package.json | 2 +- {book => docs}/vocs/redirects.config.ts | 0 .../vocs/scripts/build-cargo-docs.sh | 0 {book => docs}/vocs/scripts/check-links.ts | 0 .../vocs/scripts/generate-redirects.ts | 0 .../vocs/scripts/inject-cargo-docs.ts | 0 {book => docs}/vocs/sidebar.ts | 0 {book => docs}/vocs/tsconfig.json | 0 {book => docs}/vocs/vocs.config.ts | 0 153 files changed, 22 insertions(+), 14 deletions(-) rename {book => docs}/cli/help.rs (100%) rename {book => docs}/cli/update.sh (60%) create mode 100644 docs/vocs/.claude/settings.local.json rename {book => docs}/vocs/CLAUDE.md (100%) rename {book => docs}/vocs/README.md (100%) rename {book => docs}/vocs/bun.lockb (95%) rename {book => docs}/vocs/docs/components/SdkShowcase.tsx (100%) rename {book => docs}/vocs/docs/components/TrustedBy.tsx (100%) rename {book => docs}/vocs/docs/pages/cli/SUMMARY.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/cli.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/op-reth.md (100%) rename {book => docs}/vocs/docs/pages/cli/reth.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/config.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/checksum.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/clear.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/clear/static-file.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/diff.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/drop.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/get.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/get/mdbx.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/get/static-file.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/list.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/path.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/stats.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/db/version.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/debug.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/debug/build-block.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/debug/execution.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/debug/in-memory-merkle.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/debug/merkle.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/download.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/dump-genesis.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/import-era.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/import.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/init-state.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/init.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/node.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/p2p.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/p2p/body.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/p2p/header.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/p2p/rlpx.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/prune.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/recover.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/recover/storage-tries.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/drop.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/dump.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/dump/execution.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/run.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/unwind.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx (100%) rename {book => docs}/vocs/docs/pages/cli/reth/test-vectors/tables.mdx (100%) rename {book => docs}/vocs/docs/pages/exex/hello-world.mdx (100%) rename {book => docs}/vocs/docs/pages/exex/how-it-works.mdx (100%) rename {book => docs}/vocs/docs/pages/exex/overview.mdx (100%) rename {book => docs}/vocs/docs/pages/exex/remote.mdx (100%) rename {book => docs}/vocs/docs/pages/exex/tracking-state.mdx (100%) rename {book => docs}/vocs/docs/pages/index.mdx (100%) rename {book => docs}/vocs/docs/pages/installation/binaries.mdx (100%) rename {book => docs}/vocs/docs/pages/installation/build-for-arm-devices.mdx (100%) rename {book => docs}/vocs/docs/pages/installation/docker.mdx (100%) rename {book => docs}/vocs/docs/pages/installation/overview.mdx (100%) rename {book => docs}/vocs/docs/pages/installation/priorities.mdx (100%) rename {book => docs}/vocs/docs/pages/installation/source.mdx (100%) rename {book => docs}/vocs/docs/pages/introduction/contributing.mdx (100%) rename {book => docs}/vocs/docs/pages/introduction/why-reth.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/admin.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/debug.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/eth.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/intro.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/net.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/rpc.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/trace.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/txpool.mdx (100%) rename {book => docs}/vocs/docs/pages/jsonrpc/web3.mdx (100%) rename {book => docs}/vocs/docs/pages/overview.mdx (100%) rename {book => docs}/vocs/docs/pages/run/configuration.mdx (100%) rename {book => docs}/vocs/docs/pages/run/ethereum.mdx (100%) rename {book => docs}/vocs/docs/pages/run/ethereum/snapshots.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq/ports.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq/profiling.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq/pruning.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq/sync-op-mainnet.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq/transactions.mdx (100%) rename {book => docs}/vocs/docs/pages/run/faq/troubleshooting.mdx (100%) rename {book => docs}/vocs/docs/pages/run/monitoring.mdx (100%) rename {book => docs}/vocs/docs/pages/run/networks.mdx (100%) rename {book => docs}/vocs/docs/pages/run/opstack.mdx (100%) rename {book => docs}/vocs/docs/pages/run/opstack/op-mainnet-caveats.mdx (100%) rename {book => docs}/vocs/docs/pages/run/overview.mdx (100%) rename {book => docs}/vocs/docs/pages/run/private-testnets.mdx (100%) rename {book => docs}/vocs/docs/pages/run/system-requirements.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/custom-node/modifications.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/custom-node/prerequisites.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/examples/modify-node.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/examples/standalone-components.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/node-components.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/node-components/consensus.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/node-components/evm.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/node-components/network.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/node-components/pool.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/node-components/rpc.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/overview.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/typesystem/block.mdx (100%) rename {book => docs}/vocs/docs/pages/sdk/typesystem/transaction-types.mdx (100%) rename {book => docs}/vocs/docs/public/alchemy.png (100%) rename {book => docs}/vocs/docs/public/coinbase.png (100%) rename {book => docs}/vocs/docs/public/flashbots.png (100%) rename {book => docs}/vocs/docs/public/logo.png (100%) rename {book => docs}/vocs/docs/public/remote_exex.png (100%) rename {book => docs}/vocs/docs/public/reth-prod.png (100%) rename {book => docs}/vocs/docs/public/succinct.png (100%) rename {book => docs}/vocs/docs/snippets/sources/Cargo.toml (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/hello-world/Cargo.toml (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/hello-world/src/bin/1.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/hello-world/src/bin/2.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/hello-world/src/bin/3.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/Cargo.toml (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/build.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/proto/exex.proto (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/consumer.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/exex.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/exex_1.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/exex_2.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/exex_3.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/exex_4.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/remote/src/lib.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/tracking-state/Cargo.toml (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/tracking-state/src/bin/1.rs (100%) rename {book => docs}/vocs/docs/snippets/sources/exex/tracking-state/src/bin/2.rs (100%) rename {book => docs}/vocs/docs/styles.css (100%) rename {book => docs}/vocs/links-report.json (100%) rename {book => docs}/vocs/package.json (96%) rename {book => docs}/vocs/redirects.config.ts (100%) rename {book => docs}/vocs/scripts/build-cargo-docs.sh (100%) rename {book => docs}/vocs/scripts/check-links.ts (100%) rename {book => docs}/vocs/scripts/generate-redirects.ts (100%) rename {book => docs}/vocs/scripts/inject-cargo-docs.ts (100%) rename {book => docs}/vocs/sidebar.ts (100%) rename {book => docs}/vocs/tsconfig.json (100%) rename {book => docs}/vocs/vocs.config.ts (100%) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index fb04b785ce5..0c0bf1e053b 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -24,7 +24,7 @@ jobs: - name: Install Playwright browsers # Required for rehype-mermaid to render Mermaid diagrams during build run: | - cd book/vocs/ + cd docs/vocs/ bun i npx playwright install --with-deps chromium @@ -32,11 +32,11 @@ jobs: uses: dtolnay/rust-toolchain@nightly - name: Build docs - run: cd book/vocs && bash scripts/build-cargo-docs.sh + run: cd docs/vocs && bash scripts/build-cargo-docs.sh - name: Build Vocs run: | - cd book/vocs/ && bun run build + cd docs/vocs/ && bun run build echo "Vocs Build Complete" - name: Setup Pages @@ -45,7 +45,7 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: "./book/vocs/docs/dist" + path: "./docs/vocs/docs/dist" deploy: # Only deploy if a push to main diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7a167da8b19..2d30f5b69b5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -184,8 +184,8 @@ jobs: - run: cargo build --bin reth --workspace --features ethereum env: RUSTFLAGS: -D warnings - - run: ./book/cli/update.sh target/debug/reth - - name: Check book changes + - run: ./docs/cli/update.sh target/debug/reth + - name: Check docs changes run: git diff --exit-code codespell: diff --git a/.gitignore b/.gitignore index e4ca0420bad..7335978db14 100644 --- a/.gitignore +++ b/.gitignore @@ -55,7 +55,7 @@ rustc-ice-* book/sources/Cargo.lock # vocs node_modules -book/vocs/node_modules +docs/vocs/node_modules # Cargo chef recipe file recipe.json diff --git a/Makefile b/Makefile index c9c09a6171f..fdfd0b6ee3b 100644 --- a/Makefile +++ b/Makefile @@ -368,7 +368,7 @@ db-tools: ## Compile MDBX debugging tools. .PHONY: update-book-cli update-book-cli: build-debug ## Update book cli documentation. @echo "Updating book cli doc..." - @./book/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth + @./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth .PHONY: profiling profiling: ## Builds `reth` with optimisations, but also symbols. diff --git a/book/cli/help.rs b/docs/cli/help.rs similarity index 100% rename from book/cli/help.rs rename to docs/cli/help.rs diff --git a/book/cli/update.sh b/docs/cli/update.sh similarity index 60% rename from book/cli/update.sh rename to docs/cli/update.sh index 01593bfb794..b75dbd789af 100755 --- a/book/cli/update.sh +++ b/docs/cli/update.sh @@ -1,16 +1,16 @@ #!/usr/bin/env bash set -eo pipefail -BOOK_ROOT="$(dirname "$(dirname "$0")")" -RETH=${1:-"$(dirname "$BOOK_ROOT")/target/debug/reth"} -VOCS_PAGES_ROOT="$BOOK_ROOT/vocs/docs/pages" +DOCS_ROOT="$(dirname "$(dirname "$0")")" +RETH=${1:-"$(dirname "$DOCS_ROOT")/target/debug/reth"} +VOCS_PAGES_ROOT="$DOCS_ROOT/vocs/docs/pages" echo "Generating CLI documentation for reth at $RETH" -echo "Using book root: $BOOK_ROOT" +echo "Using docs root: $DOCS_ROOT" echo "Using vocs pages root: $VOCS_PAGES_ROOT" cmd=( "$(dirname "$0")/help.rs" - --root-dir "$BOOK_ROOT/" + --root-dir "$DOCS_ROOT/" --root-indentation 2 --root-summary --verbose diff --git a/docs/vocs/.claude/settings.local.json b/docs/vocs/.claude/settings.local.json new file mode 100644 index 00000000000..c2dc67502f5 --- /dev/null +++ b/docs/vocs/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(git checkout:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/book/vocs/CLAUDE.md b/docs/vocs/CLAUDE.md similarity index 100% rename from book/vocs/CLAUDE.md rename to docs/vocs/CLAUDE.md diff --git a/book/vocs/README.md b/docs/vocs/README.md similarity index 100% rename from book/vocs/README.md rename to docs/vocs/README.md diff --git a/book/vocs/bun.lockb b/docs/vocs/bun.lockb similarity index 95% rename from book/vocs/bun.lockb rename to docs/vocs/bun.lockb index bc5c53c3de1c23052b64668edb1dabe914f230ad..a975dd0d492b53cbc4e9cd653281dcc371755ef8 100755 GIT binary patch delta 4020 zcmdUyiC0ud7Kh(^-83}VU_e_y5LrwFrDfAbQG&=sql{Zn!IhQN%YG52g;qt@~Bgt*ZN~Zgs6% z>QYkZQl#-;RycIMwqUq@<8J@LVV@@?pZjU}o<(_~RU;$)457jLGRG>pK&f%fBrmD) z>fXF?wIS$*AUFs@!MxP;)J$DY&T>CNP(pVHS3~BaJ?Z%`1)&>cBP8)pP)~f(D?v~} zs@g32k`_U*hd%h3MV3PE1U>AvrN6$_GF}$iW88=W&o>qWX^_;>YRKC0e>xP17`QXq z?Ko30zS|e-s1=>3rllCW_Vt*x08 z5BxN>xwF(G>Lmf z2cvhGRvCc-BQc<4)WvI+3!#NWvo&rB(kk~ti-6`}Y|?0zPoTv?6OBz%!tLNFsTJ#Z7C{>fO=0w&8g9oG z8?%u%Xi*j;{j|za@b*M#GIO^r&}apAX6-iDKw|i%CovTqd zSu3TytC)Omj+*_`S?p}UCKHlu>Ayvakb3AOlQ}0zdJ3chG7VA&nZbD`Bqfr>bGY77 zQvdn9eJ-zu9ANH{$2;cpijGo3$HknJBtuIovi=?Mf4u-xf{S?hRaAZb4^QhSU*4vl zt`m0V-btSKzl?aIw{%)5~OcK*w}rC!c1gDM^+N$$<}pZ6Q(r8&L2t-hrt zB{M9scHPd`xBqVJ>HF2PuNOYAsZ6b2b|4!r6@_-EcF1bhU?)vtz3n7bY}AT|vCeVT zPb)l!k3H1CWTl&j)9p3=>vg>|pjZTWAZj^)PGb`6jnz|JvmP62?}KGw$AWT2SBG-Zo`GbYyaDo zKIKP>KdWMkm6EIBY^aN=AtvMao@Ze*w~W{}QaPgbeBTYF`sT=kEk`N?XFiQz&%6Uq zlpQZ0J2pG9Y}WWlwQ%N`LZb^!Rj13(?y-~ce96Toi-uCOmV0lXuys}sOg z_MX6xDu8J!fCg5k0*H46P&oozX9GaIm#mitwx;*7#np_w7Vo#c?B2pOVuiZeTu^<)95nls$4!hYWFU!38B z6pCr)NT)f&Eh@a`?apvU6At6{XnRT&VR20hQM4Q++7x8R5t#xiP4WU~PAJdfv95+X zL&uFPq;giv+kFByhqFtZIfKpRtPYHZb3rzI$6fO)Z`cKJ8OoHda@G~)IKDdid{k5; zKS8F{z*#qxop`%zoVkK2IlIo88<+@&`51&79J&LhaM#@AtUFjRpY<)yJiyA(nOuIG zGf$M`dAml=dVq!V#Wiu}1vU&zCy(DTGx)zJ6uO(py?1#-Z@y^FetY7`gHu zXTB(>b9SFIKd@Fl@B_|zfyHCeIRX4fcVv zJlZHo`iE>lgB0mWw|yw~%M{3|$kULgBhNriG_7fnTtw9(lqnw2PZ*tP-!^pf zZM`9l6rJfP(b1u!LPtYyYihqKwTseW8`Fo|lA~yo4Vr6GHA%xoF~>CFj1<20q*W2NHp^i5nx(HCifB6^ z{aGPn+6Kv6yk$zgC(ZRV&~hmnQ0So0Lt%x&5``SnDSVtmrszNikWTs$G zK_*U&q65jx$P@wSz|#SzLrMLqFJ(HoKTCL1#GpV2kq+4bh2>K&Kh08PvH-SDCW}!7 zYl1YPg6Wn__WVeQ_2<9!7rP`*57mTfunnv)66+6p)P!k*HNopEWnC4)L1cuTKOyTb zy0dYWGPh9c%g6eFK>H9h!X~r6fvisn)CAKcg3SF5)~As5MS<%6mX6lPk@byXoIVMHM6x~s=0=j0KIhHeaN4EB7WUANvs6v z1`idmV<%O+ZMKob#~?WScto+xDw%8d0K5tigbIS~U$g{#(M2wa zY7oTUf-LrZm29IF_L@bVl)3h|zQSf^`3;G3a0LwtvTiP0 zeNe7;$kffB9hj4po1MLo?foe8VB-$S-Pm|vxgRS#BoAl(S}~on7-TWOpwwb9*)1@aR>jT-U-SVFQZG62{?k$F~ xy?y1rtm%+ktv2tpjvbTIQgc(;&>ESCDgUs%hn$@`DF24_OqScS%}3>}{{b%xwgUhF delta 3463 zcmd6pi&xag6~})s9zS4RKt&#cfKX9nSw!8f#)xRuQ`96D3r0m(P@cldLLw>!8Li|<)FOFvURWLme4KL0bN(NZmcf6+`DPWxw)Bw`Q47Tfn$n5 z<$7hx2L(zJp-E&$HJkfeUr*>6#ezw_Uv53%B=-rPTcI{|2 z(Cd4xKl+6v-s=;DVNf$3IEIaaPQx!gc9?}f5d;srxnr77?+b$0f7$QP88*|;SmX8e zz|Ye!jSukk%}XAsZLOQ1T`vt>ta#gdabDg=vC86FQJVhoq&3F_BV7+KUDp1$l7WKw z>Y!=Xo*46fvu@cw(ZgCf!JtltyUCbewECnN)Gg3vLzAtQGYo1Qw3neNR-agdIus8w z)o%G(t3l0&W`yQuZP6Rl-Owi4$GtYgpm`205gLWi)4D+yZ9T4YSEu$mY}~CaaR#-U zYZz}bxQs&-Ub1RZCad`*VXTYYpBiWh(BJ`95fP)`+gHIDH3b@Zhxuxa?p#r4KWu3GL$|OAG3OD@mmh?=nQ|k(S6@)T#cCiQPE3lGw;bdrK*7wkm1ZW^XBJZZo|MUX&&O(A1}$O1dyQ zVSeeM&LwN<7I*D@y8EK}O*H=IgytyyS@ zS@hH6;jCN5LeJeX?^nN^*O+;^2mULlp&I-S$yKDD6yE`SfI{wzHZtN2e$$!pI3oZP|gSL{BxU_cuFh-c4Low?NyyAUZ5AWLI_XsE4n7 z@-jjy3Io~!v}f5uPlO@W6Ja>&iMX6+suzH}7r-hnfEM;o0tX2MYXB~?Yz;t`2H-4# zODtdzfd3$X(m?=iteHR)ffy~oWmcjESfd5FPT(rLOwo_>mNd30K9cR%Hhq#5{GNyY zk|SDUFSC1X(zLRHpB;tX%Q1A(X21z&FHW6CIFsr)x1o*yp5t5=zT}M5O`M(J%ms{O zD)pRE5@t|`Ae0FwIi#F9zzt7v<_`8Td4$ry8BTNIF}G{v3`evu-Z7!j1V-IB#)Sm( z66G|vqX(l?fck0Ih*+OK2SOIl*fW6CIS4g}vvYPsLC}Kba(13Ge0qdD&Mtt_dOoOI zc@!>kJ72K3AX91OY%t{HGX8Y*R!AIz+J#ExYtDv3)^fWx&M1pj&c5M{4j&PW0&$r$ zf527cq3^pB)P9gu6vk=I1 zpi*?(IU51_O)!e&cMeu2ghI&&q)^`EhGCEkIQyQnaIgn_;ai-I1WSd}6w2G2ML?bh zMv?5`EE4iaFpAn8&VB`Xih}n~Pt?ibD9E$;B6q>?6QWQH_(rW* z$_$z>4s{L$a^MMP;~bORS(%3S(=oNyCHbvk;GMvlQ)O)Fr5k zP?w>m+4fzN28%w+A+JQGli?6-Gi)u_r5{Aw$#13EqAz6>We{ZyWyoO{TgZ3PfM^T- zUh)!MEI@DA;%-T^M6t+La$9;!6pPvO4r!gsU*SzAd;5+Q9lRD+g{ZWV=xa@zhrZsl zUkcdSJCZ>|*lA{FKAqC}-~Cr@m7P*~^3W7t7gdA6%w7q5%c+g#n`o;p_BAP0Io@2}2_BU4m4%s3vmQrrfS zZ{^M#rX;4t>EiGcoL6G!T@*c-Rfk;?$(AlEg`y0Uj?q2$n)}l|F|yeMCymTB(a1|Ym~!=e}N91x#bxP>rZUgR=Y@IH3$wqT-4bg z8fE{WcIY@u%Hup9-XAk_h$4w+K#=6+qu9Pid7E^vkIid>q4Vm#e09W>1W&CItHW)? zMF?jbn&bd+6x-G$Z(}(KfJa4x<1O?wviFUcpk4+5gs=|k7A4^5j0X`Ed$u+?G82-bH*F|wm|ieF?rmW?O# zglvn+lx~Sl&&w{(PB7 Date: Fri, 27 Jun 2025 13:53:47 +0100 Subject: [PATCH 247/274] feat: add per-address metrics for precompile cache (#17058) Co-authored-by: Claude --- crates/engine/tree/src/tree/mod.rs | 17 +++++++++++------ .../tree/src/tree/payload_processor/prewarm.rs | 2 +- crates/engine/tree/src/tree/precompile_cache.rs | 10 ++++++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index a6816d29312..860243cb17e 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -10,7 +10,7 @@ use crate::{ use alloy_consensus::BlockHeader; use alloy_eips::{merge::EPOCH_SLOTS, BlockNumHash, NumHash}; use alloy_evm::block::BlockExecutor; -use alloy_primitives::B256; +use alloy_primitives::{Address, B256}; use alloy_rpc_types_engine::{ ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError, }; @@ -50,6 +50,7 @@ use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError}; use state::TreeState; use std::{ borrow::Cow, + collections::HashMap, fmt::Debug, sync::{ mpsc::{Receiver, RecvError, RecvTimeoutError, Sender}, @@ -276,9 +277,8 @@ where evm_config: C, /// Precompile cache map. precompile_cache_map: PrecompileCacheMap>, - /// Metrics for precompile cache, saved between block executions so we don't re-allocate for - /// every block. - precompile_cache_metrics: CachedPrecompileMetrics, + /// Metrics for precompile cache, stored per address to avoid re-allocation. + precompile_cache_metrics: HashMap, } impl std::fmt::Debug @@ -373,7 +373,7 @@ where payload_processor, evm_config, precompile_cache_map, - precompile_cache_metrics: Default::default(), + precompile_cache_metrics: HashMap::new(), } } @@ -2443,11 +2443,16 @@ where if !self.config.precompile_cache_disabled() { executor.evm_mut().precompiles_mut().map_precompiles(|address, precompile| { + let metrics = self + .precompile_cache_metrics + .entry(*address) + .or_insert_with(|| CachedPrecompileMetrics::new_with_address(*address)) + .clone(); CachedPrecompile::wrap( precompile, self.precompile_cache_map.cache_for_address(*address), *self.evm_config.evm_env(block.header()).spec_id(), - Some(self.precompile_cache_metrics.clone()), + Some(metrics), ) }); } diff --git a/crates/engine/tree/src/tree/payload_processor/prewarm.rs b/crates/engine/tree/src/tree/payload_processor/prewarm.rs index c69df3172fe..85e9e803305 100644 --- a/crates/engine/tree/src/tree/payload_processor/prewarm.rs +++ b/crates/engine/tree/src/tree/payload_processor/prewarm.rs @@ -275,7 +275,7 @@ where precompile, precompile_cache_map.cache_for_address(*address), spec_id, - None, // CachedPrecompileMetrics + None, // No metrics for prewarm ) }); } diff --git a/crates/engine/tree/src/tree/precompile_cache.rs b/crates/engine/tree/src/tree/precompile_cache.rs index ffa9c392a3f..a3eb3a5ba2b 100644 --- a/crates/engine/tree/src/tree/precompile_cache.rs +++ b/crates/engine/tree/src/tree/precompile_cache.rs @@ -225,6 +225,16 @@ pub(crate) struct CachedPrecompileMetrics { precompile_errors: metrics::Counter, } +impl CachedPrecompileMetrics { + /// Creates a new instance of [`CachedPrecompileMetrics`] with the given address. + /// + /// Adds address as an `address` label padded with zeros to at least two hex symbols, prefixed + /// by `0x`. + pub(crate) fn new_with_address(address: Address) -> Self { + Self::new_with_labels(&[("address", format!("0x{address:02x}"))]) + } +} + #[cfg(test)] mod tests { use std::hash::DefaultHasher; From 384e64ed004fe6e52ae8adb98a1df280e0cc32b7 Mon Sep 17 00:00:00 2001 From: kevaundray Date: Fri, 27 Jun 2025 14:05:00 +0100 Subject: [PATCH 248/274] feat: Add StatelessTrie trait for `reth-stateless` (#17098) Co-authored-by: Roman Krasiuk --- crates/stateless/src/lib.rs | 6 +++ crates/stateless/src/trie.rs | 59 ++++++++++++++++++++++++++++-- crates/stateless/src/validation.rs | 33 ++++++++++++++++- crates/stateless/src/witness_db.rs | 23 ++++++++---- 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/crates/stateless/src/lib.rs b/crates/stateless/src/lib.rs index 254a2433ef1..35289f3cf51 100644 --- a/crates/stateless/src/lib.rs +++ b/crates/stateless/src/lib.rs @@ -37,6 +37,12 @@ extern crate alloc; /// Sparse trie implementation for stateless validation pub mod trie; + +#[doc(inline)] +pub use trie::StatelessTrie; +#[doc(inline)] +pub use validation::stateless_validation_with_trie; + /// Implementation of stateless validation pub mod validation; pub(crate) mod witness_db; diff --git a/crates/stateless/src/trie.rs b/crates/stateless/src/trie.rs index b6c57a4c72a..5a35e52a7f3 100644 --- a/crates/stateless/src/trie.rs +++ b/crates/stateless/src/trie.rs @@ -13,13 +13,42 @@ use reth_trie_sparse::{ SparseTrie, }; -/// `StatelessTrie` structure for usage during stateless validation +/// Trait for stateless trie implementations that can be used for stateless validation. +pub trait StatelessTrie: core::fmt::Debug { + /// Initialize the stateless trie using the `ExecutionWitness` + fn new( + witness: &ExecutionWitness, + pre_state_root: B256, + ) -> Result<(Self, B256Map), StatelessValidationError> + where + Self: Sized; + + /// Returns the `TrieAccount` that corresponds to the `Address` + /// + /// This method will error if the `ExecutionWitness` is not able to guarantee + /// that the account is missing from the Trie _and_ the witness was complete. + fn account(&self, address: Address) -> Result, ProviderError>; + + /// Returns the storage slot value that corresponds to the given (address, slot) tuple. + /// + /// This method will error if the `ExecutionWitness` is not able to guarantee + /// that the storage was missing from the Trie _and_ the witness was complete. + fn storage(&self, address: Address, slot: U256) -> Result; + + /// Computes the new state root from the `HashedPostState`. + fn calculate_state_root( + &mut self, + state: HashedPostState, + ) -> Result; +} + +/// `StatelessSparseTrie` structure for usage during stateless validation #[derive(Debug)] -pub struct StatelessTrie { +pub struct StatelessSparseTrie { inner: SparseStateTrie, } -impl StatelessTrie { +impl StatelessSparseTrie { /// Initialize the stateless trie using the `ExecutionWitness` /// /// Note: Currently this method does not check that the `ExecutionWitness` @@ -99,6 +128,30 @@ impl StatelessTrie { } } +impl StatelessTrie for StatelessSparseTrie { + fn new( + witness: &ExecutionWitness, + pre_state_root: B256, + ) -> Result<(Self, B256Map), StatelessValidationError> { + Self::new(witness, pre_state_root) + } + + fn account(&self, address: Address) -> Result, ProviderError> { + self.account(address) + } + + fn storage(&self, address: Address, slot: U256) -> Result { + self.storage(address, slot) + } + + fn calculate_state_root( + &mut self, + state: HashedPostState, + ) -> Result { + self.calculate_state_root(state) + } +} + /// Verifies execution witness [`ExecutionWitness`] against an expected pre-state root. /// /// This function takes the RLP-encoded values provided in [`ExecutionWitness`] diff --git a/crates/stateless/src/validation.rs b/crates/stateless/src/validation.rs index de1af4cfce4..98a089e0129 100644 --- a/crates/stateless/src/validation.rs +++ b/crates/stateless/src/validation.rs @@ -1,4 +1,8 @@ -use crate::{trie::StatelessTrie, witness_db::WitnessDatabase, ExecutionWitness}; +use crate::{ + trie::{StatelessSparseTrie, StatelessTrie}, + witness_db::WitnessDatabase, + ExecutionWitness, +}; use alloc::{ boxed::Box, collections::BTreeMap, @@ -133,6 +137,31 @@ pub fn stateless_validation( where ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug, E: ConfigureEvm + Clone + 'static, +{ + stateless_validation_with_trie::( + current_block, + witness, + chain_spec, + evm_config, + ) +} + +/// Performs stateless validation of a block using a custom `StatelessTrie` implementation. +/// +/// This is a generic version of `stateless_validation` that allows users to provide their own +/// implementation of the `StatelessTrie` for custom trie backends or optimizations. +/// +/// See `stateless_validation` for detailed documentation of the validation process. +pub fn stateless_validation_with_trie( + current_block: Block, + witness: ExecutionWitness, + chain_spec: Arc, + evm_config: E, +) -> Result +where + T: StatelessTrie, + ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug, + E: ConfigureEvm + Clone + 'static, { let current_block = current_block .try_into_recovered() @@ -169,7 +198,7 @@ where }; // First verify that the pre-state reads are correct - let (mut trie, bytecode) = StatelessTrie::new(&witness, pre_state_root)?; + let (mut trie, bytecode) = T::new(&witness, pre_state_root)?; // Create an in-memory database that will use the reads to validate the block let db = WitnessDatabase::new(&trie, bytecode, ancestor_hashes); diff --git a/crates/stateless/src/witness_db.rs b/crates/stateless/src/witness_db.rs index 531aa2a27b1..4a99c286ad3 100644 --- a/crates/stateless/src/witness_db.rs +++ b/crates/stateless/src/witness_db.rs @@ -11,13 +11,16 @@ use reth_revm::{bytecode::Bytecode, state::AccountInfo, Database}; /// /// This struct implements the [`reth_revm::Database`] trait, allowing the EVM to execute /// transactions using: -/// - Account and storage slot data provided by a [`StatelessTrie`]. +/// - Account and storage slot data provided by a [`StatelessTrie`] implementation. /// - Bytecode and ancestor block hashes provided by in-memory maps. /// /// This is designed for stateless execution scenarios where direct access to a full node's /// database is not available or desired. #[derive(Debug)] -pub(crate) struct WitnessDatabase<'a> { +pub(crate) struct WitnessDatabase<'a, T> +where + T: StatelessTrie, +{ /// Map of block numbers to block hashes. /// This is used to service the `BLOCKHASH` opcode. // TODO: use Vec instead -- ancestors should be contiguous @@ -32,10 +35,13 @@ pub(crate) struct WitnessDatabase<'a> { /// TODO: Ideally we do not have this trie and instead a simple map. /// TODO: Then as a corollary we can avoid unnecessary hashing in `Database::storage` /// TODO: and `Database::basic` without needing to cache the hashed Addresses and Keys - trie: &'a StatelessTrie, + trie: &'a T, } -impl<'a> WitnessDatabase<'a> { +impl<'a, T> WitnessDatabase<'a, T> +where + T: StatelessTrie, +{ /// Creates a new [`WitnessDatabase`] instance. /// /// # Assumptions @@ -50,7 +56,7 @@ impl<'a> WitnessDatabase<'a> { /// contiguous chain of blocks. The caller is responsible for verifying the contiguity and /// the block limit. pub(crate) const fn new( - trie: &'a StatelessTrie, + trie: &'a T, bytecode: B256Map, ancestor_hashes: BTreeMap, ) -> Self { @@ -58,12 +64,15 @@ impl<'a> WitnessDatabase<'a> { } } -impl Database for WitnessDatabase<'_> { +impl Database for WitnessDatabase<'_, T> +where + T: StatelessTrie, +{ /// The database error type. type Error = ProviderError; /// Get basic account information by hashing the address and looking up the account RLP - /// in the underlying [`StatelessTrie`]. + /// in the underlying [`StatelessTrie`] implementation. /// /// Returns `Ok(None)` if the account is not found in the trie. fn basic(&mut self, address: Address) -> Result, Self::Error> { From 1d9a255f18ed99389b381036356b4c27c36fdd4e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 27 Jun 2025 16:17:24 +0200 Subject: [PATCH 249/274] chore: rm redundant bounds (#17104) --- crates/rpc/rpc-builder/src/lib.rs | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index ca61d4504ef..4dcce346c0d 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -51,7 +51,7 @@ use reth_storage_api::{ StateProviderFactory, }; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; -use reth_transaction_pool::{noop::NoopTransactionPool, PoolTransaction, TransactionPool}; +use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -126,9 +126,6 @@ pub struct RpcModuleBuilder { impl RpcModuleBuilder -where - N: NodePrimitives, - EvmConfig: Clone, { /// Create a new instance of the builder pub const fn new( @@ -146,12 +143,7 @@ where pub fn with_provider

    ( self, provider: P, - ) -> RpcModuleBuilder - where - P: BlockReader - + StateProviderFactory - + 'static, - { + ) -> RpcModuleBuilder { let Self { pool, network, executor, evm_config, consensus, _primitives, .. } = self; RpcModuleBuilder { provider, network, pool, executor, evm_config, consensus, _primitives } } @@ -160,10 +152,7 @@ where pub fn with_pool

    ( self, pool: P, - ) -> RpcModuleBuilder - where - P: TransactionPool> + 'static, - { + ) -> RpcModuleBuilder { let Self { provider, network, executor, evm_config, consensus, _primitives, .. } = self; RpcModuleBuilder { provider, network, pool, executor, evm_config, consensus, _primitives } } @@ -192,10 +181,7 @@ where pub fn with_network( self, network: Net, - ) -> RpcModuleBuilder - where - Net: NetworkInfo + Peers + 'static, - { + ) -> RpcModuleBuilder { let Self { provider, pool, executor, evm_config, consensus, _primitives, .. } = self; RpcModuleBuilder { provider, network, pool, executor, evm_config, consensus, _primitives } } @@ -247,11 +233,7 @@ where pub fn with_evm_config( self, evm_config: E, - ) -> RpcModuleBuilder - where - EvmConfig: 'static, - E: ConfigureEvm + Clone, - { + ) -> RpcModuleBuilder { let Self { provider, pool, executor, network, consensus, _primitives, .. } = self; RpcModuleBuilder { provider, network, pool, executor, evm_config, consensus, _primitives } } @@ -288,6 +270,7 @@ where /// See also [`EthApiBuilder`]. pub fn bootstrap_eth_api(&self) -> EthApi where + N: NodePrimitives, Provider: BlockReaderIdExt + StateProviderFactory + CanonStateSubscriptions From 43b091b0e61b8c72702d7ba5afbb489f67643787 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 27 Jun 2025 16:18:03 +0200 Subject: [PATCH 250/274] docs: debug clarify healtyh node rpc url setting (#17100) --- crates/node/core/src/args/debug.rs | 5 +++++ docs/vocs/docs/pages/cli/reth/node.mdx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/crates/node/core/src/args/debug.rs b/crates/node/core/src/args/debug.rs index 83c5c268d7d..d8b6d570384 100644 --- a/crates/node/core/src/args/debug.rs +++ b/crates/node/core/src/args/debug.rs @@ -80,6 +80,11 @@ pub struct DebugArgs { pub invalid_block_hook: Option, /// The RPC URL of a healthy node to use for comparing invalid block hook results against. + /// + ///Debug setting that enables execution witness comparison for troubleshooting bad blocks. + /// When enabled, the node will collect execution witnesses from the specified source and + /// compare them against local execution when a bad block is encountered, helping identify + /// discrepancies in state execution. #[arg( long = "debug.healthy-node-rpc-url", help_heading = "Debug", diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 9556c3256c3..efd96c90028 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -619,6 +619,11 @@ Debug: --debug.healthy-node-rpc-url The RPC URL of a healthy node to use for comparing invalid block hook results against. + Debug setting that enables execution witness comparison for troubleshooting bad blocks. + When enabled, the node will collect execution witnesses from the specified source and + compare them against local execution when a bad block is encountered, helping identify + discrepancies in state execution. + Database: --db.log-level Database logging level. Levels higher than "notice" require a debug build From 5f8aa53c6ce7f4451ed4a9c13958381a1b070f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Hodul=C3=A1k?= Date: Fri, 27 Jun 2025 16:26:15 +0200 Subject: [PATCH 251/274] deps: Upgrade `alloy` and `op-alloy` versions `1.0.13` => `0.18.7` and `0.18.9` (#17103) Co-authored-by: Matthias Seitz --- Cargo.lock | 219 +++++++++--------- crates/rpc/rpc-engine-api/tests/it/payload.rs | 10 - 2 files changed, 110 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 363153bef7b..9c0cd7329c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64ec76fe727969728f4396e3f410cdd825042d81d5017bfa5e58bf349071290" +checksum = "806275fdf84a9677bba30ab76887983fc669b4dbf52e0b9587fc65c988aa4f06" dependencies = [ "alloy-eips", "alloy-primitives", @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec637158ca8070cab850524ad258a8b7cee090e1e1ce393426c4247927f0acb0" +checksum = "badfd375a1a059ac27048bc9fc73091940b0693c3018a0238383ead5ebe48a47" dependencies = [ "alloy-consensus", "alloy-eips", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719905011359b266bdbf1f76f4ef7df6df75ef33c55c47a1f6b26bbdefbf4cb0" +checksum = "624cac8e9085d1461bb94f487fe38b3bd3cfad0285ae022a573e2b7be1dd7434" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d95a9829fef493824aad09b3e124e95c5320f8f6b3b9943fd7f3b07b4d21ddd" +checksum = "334bca3e3b957e4272f6f434db27dc0f39c27ae9de752eacb31b7be9cce4dd0c" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05e6972ba00d593694e2223027a0d841f531cd3cf3e39d60fc8748b1d50d504" +checksum = "ff5aae4c6dc600734b206b175f3200085ee82dcdaa388760358830a984ca9869" dependencies = [ "alloy-consensus", "alloy-eips", @@ -279,9 +279,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a004f3ce55b81321147249d8a5e9b7da83dbb7291555ef8b61834b1a08249e" +checksum = "614462440df2d478efa9445d6b148f5f404fd8b889ffe53aeb9236eb2d318894" dependencies = [ "alloy-eips", "alloy-primitives", @@ -292,9 +292,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.2.7" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977d2492ce210e34baf7b36afaacea272c96fbe6774c47e23f97d14033c0e94f" +checksum = "4ce138b29a2f8e7ed97c064af8359dfa6559c12cba5e821ae4eb93081a56557e" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aeb6bdadf68e4f075147141af9631e4ea8af57649b0738db8c4473f9872b5f" +checksum = "0623849314fecb86fc335774b7049151adaec19ebac2426c42a1c6d43df2650c" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -333,9 +333,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fe3fc1d6e5e5914e239f9fb7fb06a55b1bb8fe6e1985933832bd92da327a20" +checksum = "1e1d6de8ee0ef8992d0f6e20d576a7bcfb1f5e76173738deb811969296d2f46a" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140e30026c6f1ed5a10c02b312735b1b84d07af74d1a90c87c38ad31909dd5f2" +checksum = "58652eeeec752eb0b54de2b8b1e3c42ec35be3e99b6f9ac4209bf0d38ecd0241" dependencies = [ "alloy-consensus", "alloy-eips", @@ -372,9 +372,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2bfe13687fb8ea8ec824f6ab758d1a59be1a3ee8b492a9902e7b8806ee57f4" +checksum = "588a87b77b30452991151667522d2f2f724cec9c2ec6602e4187bc97f66d8095" dependencies = [ "alloy-consensus", "alloy-eips", @@ -389,10 +389,11 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.2.7" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b147547aff595aa3d4c2fc2c8146263e18d3372909def423619ed631ecbcfa" +checksum = "4a9a510692bef4871797062ca09ec7873c45dc68c7f3f72291165320f53606a3" dependencies = [ + "alloy-chains", "alloy-hardforks", "auto_impl", "serde", @@ -414,7 +415,7 @@ dependencies = [ "foldhash", "getrandom 0.3.3", "hashbrown 0.15.4", - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "k256", "keccak-asm", @@ -431,9 +432,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a58276c10466554bcd9dd93cd5ea349d2c6d28e29d59ad299e200b3eedbea205" +checksum = "160e90c39e471a835af97dc91cac75cf16c3d504ad2ce20c17e51a05a754b45c" dependencies = [ "alloy-chains", "alloy-consensus", @@ -475,9 +476,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419cd1a961142f6fb8575e3dead318f07cf95bc19f1cb739ff0dbe1f7b713355" +checksum = "08745d745c4b9f247baf5d1324c4ee24f61ad72b37702b4ccb7dea10299dd7de" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -518,9 +519,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ede96b42460d865ff3c26ce604aa927c829a7b621675d0a7bdfc8cdc9d5f7fb" +checksum = "e2b0f1ddddf65f5b8df258ab204bb719a298a550b70e5d9355e56f312274c2e7" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -546,9 +547,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34217bac065fd6c5197ecb4bbdcf82ce28893c679d90d98e4e5a2700b7994f69" +checksum = "9577dbe16550c3d83d6ac7656a560ecfe82b9f1268c869eab6cc9a82de417c64" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -559,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb178fa8f0b892d4e3eebde91b216d87538e82340655ac6a6eb4c50c2a41e407" +checksum = "d975fd5df7b1d0cba9e2ae707076ecc9b589f58217107cf92a8eb9f11059e216" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -571,9 +572,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "263a868fbe924c00f4af91532a8a890dfeb0af6199ca9641f2f9322fa86e437e" +checksum = "c9baee80951da08017fd2ab68ddc776087b8439c97b905691a6bf35d3328e749" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -583,9 +584,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "470d43aabf4a58794465671e5b9dad428e66dcf4f11e23e3c3510e61759d0044" +checksum = "635e602d271a907875a7f65a732dad523ca7a767c7ca9d20fec6428c09d3fb88" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -594,9 +595,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67abcd68143553be69c70b5e2bfffe62bb1a784228f330bd7326ab2f4e19d92" +checksum = "7ba36a2f724f5848e1adef4de52c889a4e2869791ad9714600c2e195b0fd8108" dependencies = [ "alloy-eips", "alloy-primitives", @@ -612,9 +613,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "971864f4d2add7a7792e126e2d1a39cc251582d6932e5744b9703d846eda9beb" +checksum = "ad7dca384a783e2598d8cfcbac598b9ff08345ed0ab1bbba93ee69ae60898b30" dependencies = [ "alloy-primitives", "serde", @@ -622,9 +623,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a01bab0f1ecf7075fe6cf9d9fa319cbc1c5717537697ee36526d4579f1963e6" +checksum = "fbeb91b73980ede87c4f62b03316cc86a4e76ea13eaf35507fabd6e717b5445f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -643,9 +644,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d912d255847cc46bfc8f698d6f3f089f021431e35b6b65837ca1b18d461f9f10" +checksum = "4d4785c613476178a820685135db58472734deba83fa0700bd19aa81b3aec184" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -656,7 +657,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arbitrary", - "itertools 0.13.0", + "itertools 0.14.0", "serde", "serde_json", "thiserror 2.0.12", @@ -664,9 +665,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfaa3dda6691cd07010b59bd5de87963ace137d32f95b434f733b7c21fe2470" +checksum = "26e0820f4b92e669558e804b2152f2358ead50ce8188cd221d21b89f482b4c09" dependencies = [ "alloy-consensus", "alloy-eips", @@ -679,9 +680,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8517dc56fd52a1f88b1847cebdd36a117a64ffe3cc6c3da8cb14efb41ecbcb06" +checksum = "308e2c5d343297f0145e0e5ca367b07807f3e4d0bbf43c19cbee541f09d091d4" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -693,9 +694,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4448330bd57eb6b2ecb4ee2f986fe2d9e846e7fb3cb13a112e5714fb4c47b8" +checksum = "f07c583f414beb5c403a94470b3275bbbc79b947bb17e8646b8a693e3c879d8b" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -705,9 +706,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e63928922253ad23b902e968cfbc31bb7f7ddd2b59c34e58b2b28ea032aff4a" +checksum = "a4a9c0a4942ae1458f054f900b1f2386a1101331b3220313189fc811502d4f1a" dependencies = [ "alloy-primitives", "arbitrary", @@ -717,9 +718,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db794f239d3746fd116146acca55a1ca3362311ecc3bdbfc150aeeaa84f462ff" +checksum = "5d23d8fcd0531ecd6b41f84f1acabf174033ad20a0da59f941064fa58c89c811" dependencies = [ "alloy-primitives", "async-trait", @@ -732,9 +733,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fcb2567f89f31dec894d3cd1d0c42cfcd699acd181a03e7339492b20eea302" +checksum = "d7f87574b7feb48e1011b718faf0c17d516743de3d4e08dbb9fb1465cfeddfd1" dependencies = [ "alloy-consensus", "alloy-network", @@ -771,7 +772,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.9.0", + "indexmap 2.10.0", "proc-macro-error2", "proc-macro2", "quote", @@ -820,9 +821,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67353ab9f9ae5eacf31155cf395add8bcfd64d98246f296a08a2e6ead92031dc" +checksum = "ac51fda778a8b4012c91cc161ed8400efedd7c2e33d937439543d73190478ec9" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -843,9 +844,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b18ce0732e42186d10479b7d87f96eb3991209c472f534fb775223e1e51f4f" +checksum = "2f63d5f69ab4a7e2607f3d9b6c7ee6bf1ee1b726d411aa08a00a174d1dde37ac" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -858,9 +859,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c43322dbaabe886f69cb9647a449ed50bf18b8bba8804fefd825a2af0cd1c7e" +checksum = "785ea5c078dc8d6545dad222e530a816a8a497b7058e3ce398dcebe386f03f27" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -878,9 +879,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13318c049f3d9187a88856b7ba0d12fda90f92225780fb119c0ac26e3fbfd5d2" +checksum = "69060d439b5bb919440d75d87b5f598738d043a51a5932943ab65fa92084e1f6" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -916,9 +917,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e325be6a1f80a744343d62b7229cae22b8d4b1ece6e61b8f8e71cfb7d3bcc0c8" +checksum = "1c194c0f69223d1b3a1637c3ceaf966f392fc9a2756264e27d61bb72bd0c4645" dependencies = [ "alloy-primitives", "darling", @@ -1710,7 +1711,7 @@ dependencies = [ "boa_interner", "boa_macros", "boa_string", - "indexmap 2.9.0", + "indexmap 2.10.0", "num-bigint", "rustc-hash 2.1.1", ] @@ -1736,7 +1737,7 @@ dependencies = [ "fast-float2", "hashbrown 0.15.4", "icu_normalizer 1.5.0", - "indexmap 2.9.0", + "indexmap 2.10.0", "intrusive-collections", "itertools 0.13.0", "num-bigint", @@ -1782,7 +1783,7 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.15.4", - "indexmap 2.9.0", + "indexmap 2.10.0", "once_cell", "phf", "rustc-hash 2.1.1", @@ -1892,9 +1893,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byte-slice-cast" @@ -4080,7 +4081,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.9.0", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -4732,9 +4733,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "arbitrary", "equivalent", @@ -5233,9 +5234,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libp2p-identity" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb68ea10844211a59ce46230909fd0ea040e8a192454d4cc2ee0d53e12280eb" +checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774" dependencies = [ "asn1_der", "bs58", @@ -5263,9 +5264,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", @@ -5531,7 +5532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" dependencies = [ "base64 0.22.1", - "indexmap 2.9.0", + "indexmap 2.10.0", "metrics", "metrics-util", "quanta", @@ -5563,7 +5564,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.15.4", - "indexmap 2.9.0", + "indexmap 2.10.0", "metrics", "ordered-float", "quanta", @@ -5960,9 +5961,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.7" +version = "0.18.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a335f4cbd98a5d7ce0551b296971872e0eddac8802ff9b417f9e9a26ea476ddc" +checksum = "a8719d9b783b29cfa1cf8d591b894805786b9ab4940adc700a57fd0d5b721cf5" dependencies = [ "alloy-consensus", "alloy-eips", @@ -5986,9 +5987,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.7" +version = "0.18.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d1fec6af9ec4977f932f8ecf8027e16d53474dabc1357fa0a05ab71eae1f60" +checksum = "839a7a1826dc1d38fdf9c6d30d1f4ed8182c63816c97054e5815206f1ebf08c7" dependencies = [ "alloy-consensus", "alloy-network", @@ -6002,9 +6003,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.7" +version = "0.18.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6396255882bb38087dc0099d3631634f764e604994ef7fc8561ced40872a84fd" +checksum = "6b9d3de5348e2b34366413412f1f1534dc6b10d2cf6e8e1d97c451749c0c81c0" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -6012,9 +6013,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.7" +version = "0.18.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e500589dead5a243a17254b0d8713bba1b7f3a96519708adc25a8ddc64578e13" +checksum = "9640f9e78751e13963762a4a44c846e9ec7974b130c29a51706f40503fe49152" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6031,9 +6032,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.7" +version = "0.18.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbce08900e92a17b490d8e24dd18f299a58b3da8b202ed38992d3f56fa42d84" +checksum = "6a4559d84f079b3fdfd01e4ee0bb118025e92105fbb89736f5d77ab3ca261698" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8601,7 +8602,7 @@ dependencies = [ "codspeed-criterion-compat", "dashmap 6.1.0", "derive_more", - "indexmap 2.9.0", + "indexmap 2.10.0", "parking_lot", "rand 0.9.1", "reth-mdbx-sys", @@ -10806,7 +10807,7 @@ dependencies = [ "revm-primitives", "ripemd", "rug", - "secp256k1 0.31.0", + "secp256k1 0.31.1", "sha2 0.10.9", ] @@ -11272,9 +11273,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3dff2d01c9aa65c3186a45ff846bfea52cbe6de3b6320ed2a358d90dad0d76" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", "rand 0.9.1", @@ -11396,7 +11397,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "memchr", "ryu", @@ -11445,7 +11446,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.10.0", "schemars", "serde", "serde_derive", @@ -12298,7 +12299,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", @@ -12342,7 +12343,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.9.0", + "indexmap 2.10.0", "pin-project-lite", "slab", "sync_wrapper", @@ -12547,9 +12548,9 @@ dependencies = [ [[package]] name = "tracy-client" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3927832d93178f979a970d26deed7b03510586e328f31b0f9ad7a73985b8332a" +checksum = "ef54005d3d760186fd662dad4b7bb27ecd5531cdef54d1573ebd3f20a9205ed7" dependencies = [ "loom", "once_cell", @@ -12559,9 +12560,9 @@ dependencies = [ [[package]] name = "tracy-client-sys" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c032d68a49d25d9012a864fef1c64ac17aee43c87e0477bf7301d8ae8bfea7b7" +checksum = "5f9612d9503675b07b244922ea6f6f3cdd88c43add1b3498084613fc88cdf69d" dependencies = [ "cc", "windows-targets 0.52.6", @@ -13710,9 +13711,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" dependencies = [ "libc", "rustix 1.0.7", diff --git a/crates/rpc/rpc-engine-api/tests/it/payload.rs b/crates/rpc/rpc-engine-api/tests/it/payload.rs index fad5b7bc654..81359969c76 100644 --- a/crates/rpc/rpc-engine-api/tests/it/payload.rs +++ b/crates/rpc/rpc-engine-api/tests/it/payload.rs @@ -87,16 +87,6 @@ fn payload_validation_conversion() { Err(PayloadError::ExtraData(data)) if data == block_with_invalid_extra_data ); - // Zero base fee - let block_with_zero_base_fee = transform_block(block.clone(), |mut b| { - b.header.base_fee_per_gas = Some(0); - b - }); - assert_matches!( - block_with_zero_base_fee.try_into_block_with_sidecar::(&ExecutionPayloadSidecar::none()), - Err(PayloadError::BaseFee(val)) if val.is_zero() - ); - // Invalid encoded transactions let mut payload_with_invalid_txs = ExecutionPayloadV1::from_block_unchecked(block.hash(), &block.into_block()); From b2000155de28c17e969de5a86d28fbcc0bb1c547 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 27 Jun 2025 17:37:23 +0300 Subject: [PATCH 252/274] feat: use Header AT in `EthChainSpec::next_block_base_fee` (#17101) --- Cargo.lock | 22 +++++++++---------- crates/chainspec/src/api.rs | 8 ++----- crates/consensus/common/src/validation.rs | 9 +++----- crates/ethereum/consensus/src/lib.rs | 10 ++++----- crates/ethereum/evm/src/lib.rs | 2 +- crates/optimism/consensus/src/lib.rs | 18 ++++++++++----- .../optimism/consensus/src/validation/mod.rs | 8 +++---- crates/optimism/evm/src/lib.rs | 2 +- crates/optimism/rpc/src/eth/mod.rs | 6 ++++- crates/rpc/rpc-eth-api/src/helpers/fee.rs | 6 ++++- crates/rpc/rpc/src/eth/helpers/fees.rs | 6 ++++- crates/stateless/src/validation.rs | 6 ++--- crates/transaction-pool/src/lib.rs | 3 ++- crates/transaction-pool/src/maintain.rs | 12 ++++++++-- 14 files changed, 69 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c0cd7329c6..b95b5e57b97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2295,7 +2295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -3165,7 +3165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.52.0", ] [[package]] @@ -4867,7 +4867,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5223,7 +5223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.53.2", + "windows-targets 0.48.5", ] [[package]] @@ -6769,7 +6769,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11070,7 +11070,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11083,7 +11083,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11141,7 +11141,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11919,7 +11919,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -12565,7 +12565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f9612d9503675b07b244922ea6f6f3cdd88c43add1b3498084613fc88cdf69d" dependencies = [ "cc", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -13111,7 +13111,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index c21f60dbd6c..e22ebc721cb 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -13,7 +13,7 @@ use reth_network_peers::NodeRecord; #[auto_impl::auto_impl(&, Arc)] pub trait EthChainSpec: Send + Sync + Unpin + Debug { /// The header type of the network. - type Header; + type Header: BlockHeader; /// Returns the [`Chain`] object this spec targets. fn chain(&self) -> Chain; @@ -67,11 +67,7 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { fn final_paris_total_difficulty(&self) -> Option; /// See [`calc_next_block_base_fee`]. - fn next_block_base_fee(&self, parent: &H, target_timestamp: u64) -> Option - where - Self: Sized, - H: BlockHeader, - { + fn next_block_base_fee(&self, parent: &Self::Header, target_timestamp: u64) -> Option { Some(calc_next_block_base_fee( parent.gas_used(), parent.gas_limit(), diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 1d2ee7bc871..a682bc2f910 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -249,12 +249,9 @@ pub fn validate_against_parent_hash_number( /// Validates the base fee against the parent and EIP-1559 rules. #[inline] -pub fn validate_against_parent_eip1559_base_fee< - H: BlockHeader, - ChainSpec: EthChainSpec + EthereumHardforks, ->( - header: &H, - parent: &H, +pub fn validate_against_parent_eip1559_base_fee( + header: &ChainSpec::Header, + parent: &ChainSpec::Header, chain_spec: &ChainSpec, ) -> Result<(), ConsensusError> { if chain_spec.is_london_active_at_block(header.number()) { diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 89ce9f72e0d..a017220489d 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -98,7 +98,7 @@ impl EthBeaconConsensus impl FullConsensus for EthBeaconConsensus where - ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug, + ChainSpec: Send + Sync + EthChainSpec

    + EthereumHardforks + Debug, N: NodePrimitives, { fn validate_block_post_execution( @@ -110,10 +110,10 @@ where } } -impl Consensus - for EthBeaconConsensus +impl Consensus for EthBeaconConsensus where B: Block, + ChainSpec: EthChainSpec
    + EthereumHardforks + Debug + Send + Sync, { type Error = ConsensusError; @@ -130,10 +130,10 @@ where } } -impl HeaderValidator - for EthBeaconConsensus +impl HeaderValidator for EthBeaconConsensus where H: BlockHeader, + ChainSpec: EthChainSpec
    + EthereumHardforks + Debug + Send + Sync, { fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { let header = header.header(); diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 3160105f1e1..ecd7c9c1338 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -119,7 +119,7 @@ impl EthEvmConfig { impl ConfigureEvm for EthEvmConfig where - ChainSpec: EthExecutorSpec + EthChainSpec + Hardforks + 'static, + ChainSpec: EthExecutorSpec + EthChainSpec
    + Hardforks + 'static, EvmF: EvmFactory< Tx: TransactionEnv + FromRecoveredTx diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index 164af8ab923..3e4201dc73b 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -57,8 +57,10 @@ impl OpBeaconConsensus { } } -impl> - FullConsensus for OpBeaconConsensus +impl FullConsensus for OpBeaconConsensus +where + N: NodePrimitives, + ChainSpec: EthChainSpec
    + OpHardforks + Debug + Send + Sync, { fn validate_block_post_execution( &self, @@ -69,8 +71,10 @@ impl Consensus - for OpBeaconConsensus +impl Consensus for OpBeaconConsensus +where + B: Block, + ChainSpec: EthChainSpec
    + OpHardforks + Debug + Send + Sync, { type Error = ConsensusError; @@ -128,8 +132,10 @@ impl Consensus } } -impl HeaderValidator - for OpBeaconConsensus +impl HeaderValidator for OpBeaconConsensus +where + H: BlockHeader, + ChainSpec: EthChainSpec
    + OpHardforks + Debug + Send + Sync, { fn validate_header(&self, header: &SealedHeader) -> Result<(), ConsensusError> { let header = header.header(); diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index bb0756c6fee..4977647d89c 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -189,9 +189,9 @@ pub fn decode_holocene_base_fee( /// Read from parent to determine the base fee for the next block /// /// See also [Base fee computation](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/holocene/exec-engine.md#base-fee-computation) -pub fn next_block_base_fee( - chain_spec: impl EthChainSpec + OpHardforks, - parent: impl BlockHeader, +pub fn next_block_base_fee( + chain_spec: impl EthChainSpec
    + OpHardforks, + parent: &H, timestamp: u64, ) -> Result { // If we are in the Holocene, we need to use the base fee params @@ -200,7 +200,7 @@ pub fn next_block_base_fee( if chain_spec.is_holocene_active_at_timestamp(parent.timestamp()) { Ok(decode_holocene_base_fee(chain_spec, parent, timestamp)?) } else { - Ok(chain_spec.next_block_base_fee(&parent, timestamp).unwrap_or_default()) + Ok(chain_spec.next_block_base_fee(parent, timestamp).unwrap_or_default()) } } diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 8861367a590..a3f4e2042af 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -101,7 +101,7 @@ impl OpEvmConfig impl ConfigureEvm for OpEvmConfig where - ChainSpec: EthChainSpec + OpHardforks, + ChainSpec: EthChainSpec
    + OpHardforks, N: NodePrimitives< Receipt = R::Receipt, SignedTx = R::Transaction, diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index 651dbc77d77..29384e3aa0b 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -270,7 +270,11 @@ where impl EthFees for OpEthApi where - Self: LoadFee, + Self: LoadFee< + Provider: ChainSpecProvider< + ChainSpec: EthChainSpec
    >, + >, + >, N: OpNodeCore, { } diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index 3ad83c6102d..3e63c04f75f 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -18,7 +18,11 @@ use tracing::debug; /// Fee related functions for the [`EthApiServer`](crate::EthApiServer) trait in the /// `eth_` namespace. -pub trait EthFees: LoadFee { +pub trait EthFees: + LoadFee< + Provider: ChainSpecProvider>>, +> +{ /// Returns a suggestion for a gas price for legacy transactions. /// /// See also: diff --git a/crates/rpc/rpc/src/eth/helpers/fees.rs b/crates/rpc/rpc/src/eth/helpers/fees.rs index ddefc6ec9ff..87adb42b2b5 100644 --- a/crates/rpc/rpc/src/eth/helpers/fees.rs +++ b/crates/rpc/rpc/src/eth/helpers/fees.rs @@ -9,7 +9,11 @@ use crate::EthApi; impl EthFees for EthApi where - Self: LoadFee, + Self: LoadFee< + Provider: ChainSpecProvider< + ChainSpec: EthChainSpec
    >, + >, + >, Provider: BlockReader, { } diff --git a/crates/stateless/src/validation.rs b/crates/stateless/src/validation.rs index 98a089e0129..a2a93f38e26 100644 --- a/crates/stateless/src/validation.rs +++ b/crates/stateless/src/validation.rs @@ -135,7 +135,7 @@ pub fn stateless_validation( evm_config: E, ) -> Result where - ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug, + ChainSpec: Send + Sync + EthChainSpec
    + EthereumHardforks + Debug, E: ConfigureEvm + Clone + 'static, { stateless_validation_with_trie::( @@ -160,7 +160,7 @@ pub fn stateless_validation_with_trie( ) -> Result where T: StatelessTrie, - ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug, + ChainSpec: Send + Sync + EthChainSpec
    + EthereumHardforks + Debug, E: ConfigureEvm + Clone + 'static, { let current_block = current_block @@ -251,7 +251,7 @@ fn validate_block_consensus( block: &RecoveredBlock, ) -> Result<(), StatelessValidationError> where - ChainSpec: Send + Sync + EthChainSpec + EthereumHardforks + Debug, + ChainSpec: Send + Sync + EthChainSpec
    + EthereumHardforks + Debug, { let consensus = EthBeaconConsensus::new(chain_spec); diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 102f0140a0c..67bee20b558 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -235,9 +235,10 @@ //! use reth_transaction_pool::{TransactionValidationTaskExecutor, Pool}; //! use reth_transaction_pool::blobstore::InMemoryBlobStore; //! use reth_transaction_pool::maintain::{maintain_transaction_pool_future}; +//! use alloy_consensus::Header; //! //! async fn t(client: C, stream: St) -//! where C: StateProviderFactory + BlockReaderIdExt + ChainSpecProvider + Clone + 'static, +//! where C: StateProviderFactory + BlockReaderIdExt
    + ChainSpecProvider + Clone + 'static, //! St: Stream + Send + Unpin + 'static, //! { //! let blob_store = InMemoryBlobStore::default(); diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index e6982c70f13..b076255f1f7 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -100,7 +100,11 @@ pub fn maintain_transaction_pool_future( ) -> BoxFuture<'static, ()> where N: NodePrimitives, - Client: StateProviderFactory + BlockReaderIdExt + ChainSpecProvider + Clone + 'static, + Client: StateProviderFactory + + BlockReaderIdExt
    + + ChainSpecProvider> + + Clone + + 'static, P: TransactionPoolExt> + 'static, St: Stream> + Send + Unpin + 'static, Tasks: TaskSpawner + 'static, @@ -122,7 +126,11 @@ pub async fn maintain_transaction_pool( config: MaintainPoolConfig, ) where N: NodePrimitives, - Client: StateProviderFactory + BlockReaderIdExt + ChainSpecProvider + Clone + 'static, + Client: StateProviderFactory + + BlockReaderIdExt
    + + ChainSpecProvider> + + Clone + + 'static, P: TransactionPoolExt> + 'static, St: Stream> + Send + Unpin + 'static, Tasks: TaskSpawner + 'static, From e89ea409e48778fd05e50e2dbba7810afe8ae018 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 27 Jun 2025 18:26:16 +0300 Subject: [PATCH 253/274] feat: relax EthereumNode ChainSpec bounds (#17106) --- crates/ethereum/evm/src/lib.rs | 12 +++++++----- crates/ethereum/node/src/node.rs | 18 ++++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index ecd7c9c1338..c91fe4cee79 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -76,6 +76,13 @@ pub struct EthEvmConfig { } impl EthEvmConfig { + /// Creates a new Ethereum EVM configuration for the ethereum mainnet. + pub fn mainnet() -> Self { + Self::ethereum(MAINNET.clone()) + } +} + +impl EthEvmConfig { /// Creates a new Ethereum EVM configuration with the given chain spec. pub fn new(chain_spec: Arc) -> Self { Self::ethereum(chain_spec) @@ -85,11 +92,6 @@ impl EthEvmConfig { pub fn ethereum(chain_spec: Arc) -> Self { Self::new_with_evm_factory(chain_spec, EthEvmFactory::default()) } - - /// Creates a new Ethereum EVM configuration for the ethereum mainnet. - pub fn mainnet() -> Self { - Self::ethereum(MAINNET.clone()) - } } impl EthEvmConfig { diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 6dc0d66a1b4..02ebacdb7d7 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -12,7 +12,9 @@ use reth_ethereum_engine_primitives::{ EthBuiltPayload, EthPayloadAttributes, EthPayloadBuilderAttributes, }; use reth_ethereum_primitives::{EthPrimitives, TransactionSigned}; -use reth_evm::{ConfigureEvm, EvmFactory, EvmFactoryFor, NextBlockEnvAttributes}; +use reth_evm::{ + eth::spec::EthExecutorSpec, ConfigureEvm, EvmFactory, EvmFactoryFor, NextBlockEnvAttributes, +}; use reth_network::{primitives::BasicNetworkPrimitives, NetworkHandle, PeersInfo}; use reth_node_api::{ AddOnsContext, FullNodeComponents, NodeAddOns, NodePrimitives, PrimitivesTy, TxTy, @@ -61,7 +63,12 @@ impl EthereumNode { EthereumConsensusBuilder, > where - Node: FullNodeTypes>, + Node: FullNodeTypes< + Types: NodeTypes< + ChainSpec: Hardforks + EthereumHardforks + EthExecutorSpec, + Primitives = EthPrimitives, + >, + >, ::Payload: PayloadTypes< BuiltPayload = EthBuiltPayload, PayloadAttributes = EthPayloadAttributes, @@ -340,10 +347,13 @@ pub struct EthereumExecutorBuilder; impl ExecutorBuilder for EthereumExecutorBuilder where - Types: NodeTypes, + Types: NodeTypes< + ChainSpec: Hardforks + EthExecutorSpec + EthereumHardforks, + Primitives = EthPrimitives, + >, Node: FullNodeTypes, { - type EVM = EthEvmConfig; + type EVM = EthEvmConfig; async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { let evm_config = EthEvmConfig::new(ctx.chain_spec()) From 40e8fb6d4d5ab8bd3cebb076aa4ead87ae1adfc3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 27 Jun 2025 17:33:52 +0200 Subject: [PATCH 254/274] docs: fix typos across documentation (#17102) Co-authored-by: Claude --- docs/crates/db.md | 2 +- docs/crates/eth-wire.md | 4 ++-- docs/design/database.md | 2 +- docs/design/goals.md | 2 +- docs/design/review.md | 4 ++-- docs/repo/layout.md | 2 +- docs/vocs/docs/pages/introduction/contributing.mdx | 2 +- docs/vocs/docs/pages/run/faq/troubleshooting.mdx | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/crates/db.md b/docs/crates/db.md index 9ebcf10d67a..4790d8daf4e 100644 --- a/docs/crates/db.md +++ b/docs/crates/db.md @@ -67,7 +67,7 @@ There are many tables within the node, all used to store different types of data ## Database -Reth's database design revolves around it's main [Database trait](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/database.rs#L8-L52), which implements the database's functionality across many types. Let's take a quick look at the `Database` trait and how it works. +Reth's database design revolves around its main [Database trait](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/database.rs#L8-L52), which implements the database's functionality across many types. Let's take a quick look at the `Database` trait and how it works. [File: crates/storage/db-api/src/database.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db-api/src/database.rs#L8-L52) diff --git a/docs/crates/eth-wire.md b/docs/crates/eth-wire.md index 7ab87e914b2..1b4ba2d80e3 100644 --- a/docs/crates/eth-wire.md +++ b/docs/crates/eth-wire.md @@ -1,6 +1,6 @@ # eth-wire -The `eth-wire` crate provides abstractions over the [``RLPx``](https://github.com/ethereum/devp2p/blob/master/rlpx.md) and +The `eth-wire` crate provides abstractions over the [`RLPx`](https://github.com/ethereum/devp2p/blob/master/rlpx.md) and [Eth wire](https://github.com/ethereum/devp2p/blob/master/caps/eth.md) protocols. This crate can be thought of as having 2 components: @@ -334,7 +334,7 @@ impl Sink for EthStream { } ``` ## Unauthed streams -For a session to be established, peers in the Ethereum network must first exchange a `Hello` message in the ``RLPx`` layer and then a +For a session to be established, peers in the Ethereum network must first exchange a `Hello` message in the `RLPx` layer and then a `Status` message in the eth-wire layer. To perform these, reth has special `Unauthed` versions of streams described above. diff --git a/docs/design/database.md b/docs/design/database.md index b45c783bc5f..d81aced6f0c 100644 --- a/docs/design/database.md +++ b/docs/design/database.md @@ -23,7 +23,7 @@ ### Table layout -Historical state changes are indexed by `BlockNumber`. This means that `reth` stores the state for every account after every block that touched it, and it provides indexes for accessing that data quickly. While this may make the database size bigger (needs benchmark once `reth` is closer to prod). +Historical state changes are indexed by `BlockNumber`. This means that `reth` stores the state for every account after every block that touched it, and it provides indexes for accessing that data quickly. While this may make the database size bigger (needs benchmark once `reth` is closer to prod), it provides fast access to historical state. Below, you can see the table design that implements this scheme: diff --git a/docs/design/goals.md b/docs/design/goals.md index 819d6ca6fa9..a29b3a824c4 100644 --- a/docs/design/goals.md +++ b/docs/design/goals.md @@ -34,7 +34,7 @@ Why? This is a win for everyone. RPC providers meet more impressive SLAs, MEV se The biggest bottleneck in this pipeline is not the execution of the EVM interpreter itself, but rather in accessing state and managing I/O. As such, we think the largest optimizations to be made are closest to the DB layer. -Ideally, we can achieve such fast runtime operation that we can avoid storing certain things (e.g.?) on the disk, and are able to generate them on the fly, instead - minimizing disk footprint. +Ideally, we can achieve such fast runtime operation that we can avoid storing certain things (e.g., transaction receipts) on the disk, and are able to generate them on the fly, instead - minimizing disk footprint. --- diff --git a/docs/design/review.md b/docs/design/review.md index 2a3c5c20867..702ab7722f8 100644 --- a/docs/design/review.md +++ b/docs/design/review.md @@ -24,9 +24,9 @@ This document contains some of our research in how other codebases designed vari ## Header Downloaders * Erigon Header Downloader: - * A header downloader algo was introduced in [`erigon#1016`](https://github.com/ledgerwatch/erigon/pull/1016) and finished in [`erigon#1145`](https://github.com/ledgerwatch/erigon/pull/1145). At a high level, the downloader concurrently requested headers by hash, then sorted, validated and fused the responses into chain segments. Smaller segments were fused into larger as the gaps between them were filled. The downloader is also used to maintain hardcoded hashes (later renamed to preverified) to bootstrap the sync. + * A header downloader algorithm was introduced in [`erigon#1016`](https://github.com/ledgerwatch/erigon/pull/1016) and finished in [`erigon#1145`](https://github.com/ledgerwatch/erigon/pull/1145). At a high level, the downloader concurrently requested headers by hash, then sorted, validated and fused the responses into chain segments. Smaller segments were fused into larger as the gaps between them were filled. The downloader is also used to maintain hardcoded hashes (later renamed to preverified) to bootstrap the sync. * The downloader was refactored multiple times: [`erigon#1471`](https://github.com/ledgerwatch/erigon/pull/1471), [`erigon#1559`](https://github.com/ledgerwatch/erigon/pull/1559) and [`erigon#2035`](https://github.com/ledgerwatch/erigon/pull/2035). - * With PoS transition in [`erigon#3075`](https://github.com/ledgerwatch/erigon/pull/3075) terminal td was introduced to the algo to stop forward syncing. For the downward sync (post merge), the download was now delegated to [`EthBackendServer`](https://github.com/ledgerwatch/erigon/blob/3c95db00788dc740849c2207d886fe4db5a8c473/ethdb/privateapi/ethbackend.go#L245) + * With PoS transition in [`erigon#3075`](https://github.com/ledgerwatch/erigon/pull/3075) terminal td was introduced to the algorithm to stop forward syncing. For the downward sync (post merge), the downloader was now delegated to [`EthBackendServer`](https://github.com/ledgerwatch/erigon/blob/3c95db00788dc740849c2207d886fe4db5a8c473/ethdb/privateapi/ethbackend.go#L245) * Proper reverse PoS downloader was introduced in [`erigon#3092`](https://github.com/ledgerwatch/erigon/pull/3092) which downloads the header batches from tip until local head is reached. Refactored later in [`erigon#3340`](https://github.com/ledgerwatch/erigon/pull/3340) and [`erigon#3717`](https://github.com/ledgerwatch/erigon/pull/3717). * Akula Headers & Stage Downloader: diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 46a91b3f0b2..8626d264432 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -2,7 +2,7 @@ This repository contains several Rust crates that implement the different building blocks of an Ethereum node. The high-level structure of the repository is as follows: -Generally reth is composed of a few components, with supporting crates. The main components can be defined as: +Generally, reth is composed of a few components, with supporting crates. The main components can be defined as: - [Project Layout](#project-layout) - [Documentation](#documentation) diff --git a/docs/vocs/docs/pages/introduction/contributing.mdx b/docs/vocs/docs/pages/introduction/contributing.mdx index 63fc5987153..aa30ee5faf2 100644 --- a/docs/vocs/docs/pages/introduction/contributing.mdx +++ b/docs/vocs/docs/pages/introduction/contributing.mdx @@ -254,5 +254,5 @@ Reth follows the [Rust Code of Conduct](https://www.rust-lang.org/conduct.html). If you experience or witness behavior that violates our code of conduct, please report it to [georgios@paradigm.xyz](mailto:georgios@paradigm.xyz). :::note -Also read [CONTRIBUTING.md](https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md) for in depth guidelines. +Also read [CONTRIBUTING.md](https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md) for in-depth guidelines. ::: diff --git a/docs/vocs/docs/pages/run/faq/troubleshooting.mdx b/docs/vocs/docs/pages/run/faq/troubleshooting.mdx index d1f0e8500ff..08b9c6fbe5d 100644 --- a/docs/vocs/docs/pages/run/faq/troubleshooting.mdx +++ b/docs/vocs/docs/pages/run/faq/troubleshooting.mdx @@ -100,7 +100,7 @@ It will take the same time as initial sync. ### Database write error -If you encounter an irrecoverable database-related errors, in most of the cases it's related to the RAM/NVMe/SSD you use. For example: +If you encounter irrecoverable database-related errors, in most cases it's related to the RAM/NVMe/SSD you use. For example: ```console Error: A stage encountered an irrecoverable error. From 34d95414db4b817799fdc19484333acc83c7467c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 27 Jun 2025 18:33:45 +0200 Subject: [PATCH 255/274] fix: track earliest available block correctly (#17095) --- .../provider/src/providers/database/mod.rs | 11 ++---- .../src/providers/static_file/manager.rs | 36 ++++++++++--------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 6f735942607..6fdff7bfa88 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -346,14 +346,9 @@ impl BlockNumReader for ProviderFactory { } fn earliest_block_number(&self) -> ProviderResult { - // expired height tracks the lowest block number that has been expired, therefore the - // earliest block number is one more than that. - let mut earliest = self.static_file_provider.expired_history_height(); - if earliest > 0 { - // If the expired history height is 0, then the earliest block number is still 0. - earliest += 1; - } - Ok(earliest) + // earliest history height tracks the lowest block number that has __not__ been expired, in + // other words, the first/earlierst available block. + Ok(self.static_file_provider.earliest_history_height()) } fn block_number(&self, hash: B256) -> ProviderResult> { diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 3d4b0083150..aabcb248c02 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -229,15 +229,16 @@ pub struct StaticFileProviderInner { /// Maintains a map which allows for concurrent access to different `NippyJars`, over different /// segments and ranges. map: DashMap<(BlockNumber, StaticFileSegment), LoadedJar>, - /// Min static file block for each segment. - /// This index is initialized on launch to keep track of the lowest, non-expired static files. + /// Min static file range for each segment. + /// This index is initialized on launch to keep track of the lowest, non-expired static file + /// per segment. /// - /// This tracks the lowest static file per segment together with the highest block in that + /// This tracks the lowest static file per segment together with the block range in that /// file. E.g. static file is batched in 500k block intervals then the lowest static file - /// is [0..499K], and the highest block is 499K. + /// is [0..499K], and the block range is start = 0, end = 499K. /// This index is mainly used to History expiry, which targets transactions, e.g. pre-merge /// history expiry would lead to removing all static files below the merge height. - static_files_min_block: RwLock>, + static_files_min_block: RwLock>, /// This is an additional index that tracks the expired height, this will track the highest /// block number that has been expired (missing). The first, non expired block is /// `expired_history_height + 1`. @@ -248,7 +249,7 @@ pub struct StaticFileProviderInner { /// /// This additional tracker exists for more efficient lookups because the node must be aware of /// the expired height. - expired_history_height: AtomicU64, + earliest_history_height: AtomicU64, /// Max static file block for each segment static_files_max_block: RwLock>, /// Available static file block ranges on disk indexed by max transactions. @@ -282,7 +283,7 @@ impl StaticFileProviderInner { map: Default::default(), writers: Default::default(), static_files_min_block: Default::default(), - expired_history_height: Default::default(), + earliest_history_height: Default::default(), static_files_max_block: Default::default(), static_files_tx_index: Default::default(), path: path.as_ref().to_path_buf(), @@ -675,7 +676,7 @@ impl StaticFileProvider { for (segment, ranges) in iter_static_files(&self.path).map_err(ProviderError::other)? { // Update first and last block for each segment if let Some((first_block_range, _)) = ranges.first() { - min_block.insert(segment, first_block_range.end()); + min_block.insert(segment, *first_block_range); } if let Some((last_block_range, _)) = ranges.last() { max_block.insert(segment, last_block_range.end()); @@ -702,8 +703,10 @@ impl StaticFileProvider { self.map.clear(); // initialize the expired history height to the lowest static file block - if let Some(lowest_block) = min_block.get(&StaticFileSegment::Transactions) { - self.expired_history_height.store(*lowest_block, std::sync::atomic::Ordering::Relaxed); + if let Some(lowest_range) = min_block.get(&StaticFileSegment::Transactions) { + // the earliest height is the lowest available block number + self.earliest_history_height + .store(lowest_range.start(), std::sync::atomic::Ordering::Relaxed); } Ok(()) @@ -1015,14 +1018,15 @@ impl StaticFileProvider { Ok(None) } - /// Returns the highest block number that has been expired from the history (missing). + /// Returns the earliest available block number that has not been expired and is still + /// available. /// - /// The earliest block that is still available in the static files is `expired_history_height + - /// 1`. + /// This means that the highest expired block (or expired block height) is + /// `earliest_history_height.saturating_sub(1)`. /// /// Returns `0` if no history has been expired. - pub fn expired_history_height(&self) -> BlockNumber { - self.expired_history_height.load(std::sync::atomic::Ordering::Relaxed) + pub fn earliest_history_height(&self) -> BlockNumber { + self.earliest_history_height.load(std::sync::atomic::Ordering::Relaxed) } /// Gets the lowest transaction static file block if it exists. @@ -1040,7 +1044,7 @@ impl StaticFileProvider { /// /// If there is nothing on disk for the given segment, this will return [`None`]. pub fn get_lowest_static_file_block(&self, segment: StaticFileSegment) -> Option { - self.static_files_min_block.read().get(&segment).copied() + self.static_files_min_block.read().get(&segment).map(|range| range.end()) } /// Gets the highest static file's block height if it exists for a static file segment. From fae433319c446fb62c73396eefd2a68c8fb1cd2c Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 27 Jun 2025 22:39:07 +0300 Subject: [PATCH 256/274] refactor: simplify handling of `NetworkPrimitives` in CLI (#17112) --- crates/cli/commands/src/common.rs | 4 ++++ crates/cli/commands/src/import.rs | 6 ++++-- crates/cli/commands/src/p2p/mod.rs | 7 ++++--- crates/cli/commands/src/stage/mod.rs | 11 ++++++----- crates/cli/commands/src/stage/run.rs | 10 ++++------ crates/ethereum/cli/src/interface.rs | 12 ++++-------- crates/optimism/cli/src/app.rs | 8 +++----- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index cb005ae6ff4..be3bcec5a17 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -9,7 +9,9 @@ use reth_consensus::{noop::NoopConsensus, ConsensusError, FullConsensus}; use reth_db::{init_db, open_db_read_only, DatabaseEnv}; use reth_db_common::init::init_genesis; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; +use reth_eth_wire::NetPrimitivesFor; use reth_evm::{noop::NoopEvmConfig, ConfigureEvm}; +use reth_network::NetworkEventListenerProvider; use reth_node_api::FullNodeTypesAdapter; use reth_node_builder::{ Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter, @@ -218,6 +220,7 @@ type FullTypesAdapter = FullNodeTypesAdapter< /// [`NodeTypes`] in CLI. pub trait CliNodeTypes: NodeTypesForProvider { type Evm: ConfigureEvm; + type NetworkPrimitives: NetPrimitivesFor; } impl CliNodeTypes for N @@ -225,6 +228,7 @@ where N: Node> + NodeTypesForProvider, { type Evm = <>>::Components as NodeComponents>>::Evm; + type NetworkPrimitives = <<>>::Components as NodeComponents>>::Network as NetworkEventListenerProvider>::Primitives; } /// Helper trait aggregating components required for the CLI. diff --git a/crates/cli/commands/src/import.rs b/crates/cli/commands/src/import.rs index d821570901b..eef67117063 100644 --- a/crates/cli/commands/src/import.rs +++ b/crates/cli/commands/src/import.rs @@ -56,11 +56,13 @@ pub struct ImportCommand { impl> ImportCommand { /// Execute `import` command - pub async fn execute(self, components: F) -> eyre::Result<()> + pub async fn execute( + self, + components: impl FnOnce(Arc) -> Comp, + ) -> eyre::Result<()> where N: CliNodeTypes, Comp: CliNodeComponents, - F: FnOnce(Arc) -> Comp, { info!(target: "reth::cli", "reth {} starting", SHORT_VERSION); diff --git a/crates/cli/commands/src/p2p/mod.rs b/crates/cli/commands/src/p2p/mod.rs index 5e4d31464b1..ab07a553c19 100644 --- a/crates/cli/commands/src/p2p/mod.rs +++ b/crates/cli/commands/src/p2p/mod.rs @@ -2,6 +2,7 @@ use std::{path::PathBuf, sync::Arc}; +use crate::common::CliNodeTypes; use alloy_eips::BlockHashOrNumber; use backon::{ConstantBuilder, Retryable}; use clap::{Parser, Subcommand}; @@ -9,7 +10,7 @@ use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_cli::chainspec::ChainSpecParser; use reth_cli_util::{get_secret_key, hash_or_num_value_parser}; use reth_config::Config; -use reth_network::{BlockDownloaderProvider, NetworkConfigBuilder, NetworkPrimitives}; +use reth_network::{BlockDownloaderProvider, NetworkConfigBuilder}; use reth_network_p2p::bodies::client::BodiesClient; use reth_node_core::{ args::{DatabaseArgs, DatadirArgs, NetworkArgs}, @@ -76,7 +77,7 @@ pub enum Subcommands { impl> Command { /// Execute `p2p` command - pub async fn execute(self) -> eyre::Result<()> { + pub async fn execute>(self) -> eyre::Result<()> { let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain()); let config_path = self.config.clone().unwrap_or_else(|| data_dir.config()); @@ -100,7 +101,7 @@ impl let rlpx_socket = (self.network.addr, self.network.port).into(); let boot_nodes = self.chain.bootnodes().unwrap_or_default(); - let net = NetworkConfigBuilder::::new(p2p_secret_key) + let net = NetworkConfigBuilder::::new(p2p_secret_key) .peer_config(config.peers_config_with_basic_nodes_from_file(None)) .external_ip_resolver(self.network.nat) .disable_discv4_discovery_if(self.chain.chain().is_optimism()) diff --git a/crates/cli/commands/src/stage/mod.rs b/crates/cli/commands/src/stage/mod.rs index 09d6ea9c091..0401d06cd8c 100644 --- a/crates/cli/commands/src/stage/mod.rs +++ b/crates/cli/commands/src/stage/mod.rs @@ -7,7 +7,6 @@ use clap::{Parser, Subcommand}; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_cli::chainspec::ChainSpecParser; use reth_cli_runner::CliContext; -use reth_eth_wire::NetPrimitivesFor; pub mod drop; pub mod dump; @@ -41,15 +40,17 @@ pub enum Subcommands { impl> Command { /// Execute `stage` command - pub async fn execute(self, ctx: CliContext, components: F) -> eyre::Result<()> + pub async fn execute( + self, + ctx: CliContext, + components: impl FnOnce(Arc) -> Comp, + ) -> eyre::Result<()> where N: CliNodeTypes, Comp: CliNodeComponents, - F: FnOnce(Arc) -> Comp, - P: NetPrimitivesFor, { match self.command { - Subcommands::Run(command) => command.execute::(ctx, components).await, + Subcommands::Run(command) => command.execute::(ctx, components).await, Subcommands::Drop(command) => command.execute::().await, Subcommands::Dump(command) => command.execute::(components).await, Subcommands::Unwind(command) => command.execute::(components).await, diff --git a/crates/cli/commands/src/stage/run.rs b/crates/cli/commands/src/stage/run.rs index 312e9059b9f..f608a18fff4 100644 --- a/crates/cli/commands/src/stage/run.rs +++ b/crates/cli/commands/src/stage/run.rs @@ -16,7 +16,6 @@ use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, headers::reverse_headers::ReverseHeadersDownloaderBuilder, }; -use reth_eth_wire::NetPrimitivesFor; use reth_exex::ExExManagerHandle; use reth_network::BlockDownloaderProvider; use reth_network_p2p::HeadersClient; @@ -103,12 +102,11 @@ pub struct Command { impl> Command { /// Execute `stage` command - pub async fn execute(self, ctx: CliContext, components: F) -> eyre::Result<()> + pub async fn execute(self, ctx: CliContext, components: F) -> eyre::Result<()> where N: CliNodeTypes, Comp: CliNodeComponents, F: FnOnce(Arc) -> Comp, - P: NetPrimitivesFor, { // Raise the fd limit of the process. // Does not do anything on windows. @@ -174,7 +172,7 @@ impl let network = self .network - .network_config::

    ( + .network_config::( &config, provider_factory.chain_spec(), p2p_secret_key, @@ -186,7 +184,7 @@ impl let fetch_client = Arc::new(network.fetch_client().await?); // Use `to` as the tip for the stage - let tip: P::BlockHeader = loop { + let tip = loop { match fetch_client.get_header(BlockHashOrNumber::Number(self.to)).await { Ok(header) => { if let Some(header) = header.into_data() { @@ -229,7 +227,7 @@ impl let network = self .network - .network_config::

    ( + .network_config::( &config, provider_factory.chain_spec(), p2p_secret_key, diff --git a/crates/ethereum/cli/src/interface.rs b/crates/ethereum/cli/src/interface.rs index 6c96c6d2993..4419d700f93 100644 --- a/crates/ethereum/cli/src/interface.rs +++ b/crates/ethereum/cli/src/interface.rs @@ -12,7 +12,6 @@ use reth_cli_commands::{ }; use reth_cli_runner::CliRunner; use reth_db::DatabaseEnv; -use reth_network::EthNetworkPrimitives; use reth_node_builder::{NodeBuilder, WithLaunchContext}; use reth_node_core::{ args::LogArgs, @@ -162,7 +161,7 @@ impl, Ext: clap::Args + fmt::Debug> Cl runner.run_blocking_until_ctrl_c(command.execute::()) } Commands::Import(command) => { - runner.run_blocking_until_ctrl_c(command.execute::(components)) + runner.run_blocking_until_ctrl_c(command.execute::(components)) } Commands::ImportEra(command) => { runner.run_blocking_until_ctrl_c(command.execute::()) @@ -174,12 +173,9 @@ impl, Ext: clap::Args + fmt::Debug> Cl Commands::Download(command) => { runner.run_blocking_until_ctrl_c(command.execute::()) } - Commands::Stage(command) => runner.run_command_until_exit(|ctx| { - command.execute::(ctx, components) - }), - Commands::P2P(command) => { - runner.run_until_ctrl_c(command.execute::()) - } + Commands::Stage(command) => runner + .run_command_until_exit(|ctx| command.execute::(ctx, components)), + Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::()), #[cfg(feature = "dev")] Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), diff --git a/crates/optimism/cli/src/app.rs b/crates/optimism/cli/src/app.rs index b6c5b6b56c2..1c7af0d328c 100644 --- a/crates/optimism/cli/src/app.rs +++ b/crates/optimism/cli/src/app.rs @@ -6,7 +6,7 @@ use reth_cli_runner::CliRunner; use reth_node_metrics::recorder::install_prometheus_recorder; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::OpBeaconConsensus; -use reth_optimism_node::{OpExecutorProvider, OpNetworkPrimitives, OpNode}; +use reth_optimism_node::{OpExecutorProvider, OpNode}; use reth_tracing::{FileWorkerGuard, Layers}; use std::fmt; use tracing::info; @@ -84,13 +84,11 @@ where Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::()), Commands::Stage(command) => runner.run_command_until_exit(|ctx| { - command.execute::(ctx, |spec| { + command.execute::(ctx, |spec| { (OpExecutorProvider::optimism(spec.clone()), OpBeaconConsensus::new(spec)) }) }), - Commands::P2P(command) => { - runner.run_until_ctrl_c(command.execute::()) - } + Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), Commands::Recover(command) => { runner.run_command_until_exit(|ctx| command.execute::(ctx)) From 8980944997ddd21d0576af3c129cb3456f955a3f Mon Sep 17 00:00:00 2001 From: strmfos <155266597+strmfos@users.noreply.github.com> Date: Fri, 27 Jun 2025 23:58:13 +0200 Subject: [PATCH 257/274] docs: fix error in `config.rs` (#17113) --- crates/net/network/src/transactions/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/net/network/src/transactions/config.rs b/crates/net/network/src/transactions/config.rs index f23900aaffe..910e35d37be 100644 --- a/crates/net/network/src/transactions/config.rs +++ b/crates/net/network/src/transactions/config.rs @@ -120,7 +120,7 @@ pub trait TransactionPropagationPolicy: Send + Sync + Unpin + 'static { pub enum TransactionPropagationKind { /// Propagate transactions to all peers. /// - /// No restructions + /// No restrictions #[default] All, /// Propagate transactions to only trusted peers. From 5c828120723d75aac56c5179134b5e2d99d26988 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 28 Jun 2025 00:02:21 +0200 Subject: [PATCH 258/274] refactor: move ExEx launching to LaunchContext method (#17114) --- crates/node/builder/src/launch/common.rs | 22 +++++++++++++++++++++- crates/node/builder/src/launch/engine.rs | 21 +++++---------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 3c4583466a2..03bcb19e0ff 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -3,7 +3,7 @@ use crate::{ components::{NodeComponents, NodeComponentsBuilder}, hooks::OnComponentInitializedHook, - BuilderContext, NodeAdapter, + BuilderContext, ExExLauncher, NodeAdapter, PrimitivesTy, }; use alloy_consensus::BlockHeader as _; use alloy_eips::eip2124::Head; @@ -19,6 +19,7 @@ use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHe use reth_engine_local::MiningMode; use reth_engine_tree::tree::{InvalidBlockHook, InvalidBlockHooks, NoopInvalidBlockHook}; use reth_evm::{noop::NoopEvmConfig, ConfigureEvm}; +use reth_exex::ExExManagerHandle; use reth_fs_util as fs; use reth_invalid_block_hooks::InvalidBlockWitnessHook; use reth_network_p2p::headers::client::HeadersClient; @@ -932,6 +933,25 @@ where pub const fn components(&self) -> &CB::Components { &self.node_adapter().components } + + /// Launches ExEx (Execution Extensions) and returns the ExEx manager handle. + #[allow(clippy::type_complexity)] + pub async fn launch_exex( + &self, + installed_exex: Vec<( + String, + Box>>, + )>, + ) -> eyre::Result>>> { + ExExLauncher::new( + self.head(), + self.node_adapter().clone(), + installed_exex, + self.configs().clone(), + ) + .launch() + .await + } } impl diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 796a47b3db9..c490fe10dd3 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -5,7 +5,7 @@ use crate::{ hooks::NodeHooks, rpc::{EngineValidatorAddOn, RethRpcAddOns, RpcHandle}, setup::build_networked_pipeline, - AddOns, AddOnsContext, ExExLauncher, FullNode, LaunchContext, LaunchNode, NodeAdapter, + AddOns, AddOnsContext, FullNode, LaunchContext, LaunchNode, NodeAdapter, NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter, }; use alloy_consensus::BlockHeader; @@ -133,15 +133,8 @@ where // Try to expire pre-merge transaction history if configured ctx.expire_pre_merge_transactions()?; - // spawn exexs - let exex_manager_handle = ExExLauncher::new( - ctx.head(), - ctx.node_adapter().clone(), - installed_exex, - ctx.configs().clone(), - ) - .launch() - .await?; + // spawn exexs if any + let maybe_exex_manager_handle = ctx.launch_exex(installed_exex).await?; // create pipeline let network_handle = ctx.components().network().clone(); @@ -161,10 +154,6 @@ where let consensus = Arc::new(ctx.components().consensus().clone()); - // Configure the pipeline - let pipeline_exex_handle = - exex_manager_handle.clone().unwrap_or_else(ExExManagerHandle::empty); - let era_import_source = if node_config.era.enabled { EraImportSource::maybe_new( node_config.era.source.path.clone(), @@ -187,7 +176,7 @@ where max_block, static_file_producer, ctx.components().evm_config().clone(), - pipeline_exex_handle, + maybe_exex_manager_handle.clone().unwrap_or_else(ExExManagerHandle::empty), era_import_source, )?; @@ -197,7 +186,7 @@ where let pipeline_events = pipeline.events(); let mut pruner_builder = ctx.pruner_builder(); - if let Some(exex_manager_handle) = &exex_manager_handle { + if let Some(exex_manager_handle) = &maybe_exex_manager_handle { pruner_builder = pruner_builder.finished_exex_height(exex_manager_handle.finished_height()); } From 2c52fc3f93f7538d6b6262e85a2263cdaceff5f3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 28 Jun 2025 00:04:05 +0200 Subject: [PATCH 259/274] chore: tell claude to run fmt before opening a pr (#17118) --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLAUDE.md b/CLAUDE.md index fe757610ef4..99282fbf864 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -182,6 +182,7 @@ Label PRs appropriately, first check the available labels and then apply the rel * when changes are docs related, add C-docs label * when changes are optimism related (e.g. new feature or exclusive changes to crates/optimism), add A-op-reth label * ... and so on, check the available labels for more options. +* if being tasked to open a pr, ensure that all changes are properly formatted: `cargo +nightly fmt --all` If changes in reth include changes to dependencies, run commands `zepter` and `make lint-toml` before finalizing the pr. Assume `zepter` binary is installed. From 8fa928ec5f69cb2bb3c3b16e04b954c5f06903ec Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 28 Jun 2025 00:22:29 +0200 Subject: [PATCH 260/274] refactor: make get_healthy_node_client async (#17119) --- crates/node/builder/src/launch/common.rs | 54 ++++++++++++------------ crates/node/builder/src/launch/engine.rs | 2 +- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 03bcb19e0ff..786cecd5eb1 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -966,13 +966,13 @@ where CB: NodeComponentsBuilder, { /// Returns the [`InvalidBlockHook`] to use for the node. - pub fn invalid_block_hook( + pub async fn invalid_block_hook( &self, ) -> eyre::Result::Primitives>>> { let Some(ref hook) = self.node_config().debug.invalid_block_hook else { return Ok(Box::new(NoopInvalidBlockHook::default())) }; - let healthy_node_rpc_client = self.get_healthy_node_client()?; + let healthy_node_rpc_client = self.get_healthy_node_client().await?; let output_directory = self.data_dir().invalid_block_hooks(); let hooks = hook @@ -1000,33 +1000,31 @@ where } /// Returns an RPC client for the healthy node, if configured in the node config. - fn get_healthy_node_client(&self) -> eyre::Result> { - self.node_config() - .debug - .healthy_node_rpc_url - .as_ref() - .map(|url| { - let client = jsonrpsee::http_client::HttpClientBuilder::default().build(url)?; - - // Verify that the healthy node is running the same chain as the current node. - let chain_id = futures::executor::block_on(async { - EthApiClient::< - alloy_rpc_types::TransactionRequest, - alloy_rpc_types::Transaction, - alloy_rpc_types::Block, - alloy_rpc_types::Receipt, - alloy_rpc_types::Header, - >::chain_id(&client) - .await - })? - .ok_or_eyre("healthy node rpc client didn't return a chain id")?; - if chain_id.to::() != self.chain_id().id() { - eyre::bail!("invalid chain id for healthy node: {chain_id}") - } + async fn get_healthy_node_client( + &self, + ) -> eyre::Result> { + let Some(url) = self.node_config().debug.healthy_node_rpc_url.as_ref() else { + return Ok(None); + }; - Ok(client) - }) - .transpose() + let client = jsonrpsee::http_client::HttpClientBuilder::default().build(url)?; + + // Verify that the healthy node is running the same chain as the current node. + let chain_id = EthApiClient::< + alloy_rpc_types::TransactionRequest, + alloy_rpc_types::Transaction, + alloy_rpc_types::Block, + alloy_rpc_types::Receipt, + alloy_rpc_types::Header, + >::chain_id(&client) + .await? + .ok_or_eyre("healthy node rpc client didn't return a chain id")?; + + if chain_id.to::() != self.chain_id().id() { + eyre::bail!("invalid chain id for healthy node: {chain_id}") + } + + Ok(Some(client)) } } diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index c490fe10dd3..2a138e09726 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -238,7 +238,7 @@ where ctx.components().payload_builder_handle().clone(), engine_payload_validator, engine_tree_config, - ctx.invalid_block_hook()?, + ctx.invalid_block_hook().await?, ctx.sync_metrics_tx(), ctx.components().evm_config().clone(), ); From bfd745117bc41c922ee9b92d165e645510ad6454 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 28 Jun 2025 00:46:13 +0200 Subject: [PATCH 261/274] refactor: move ERA import source creation to LaunchContext (#17115) --- crates/node/builder/src/launch/common.rs | 24 ++++++++++++++++++++++-- crates/node/builder/src/launch/engine.rs | 15 +-------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 786cecd5eb1..9d514e71845 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -25,7 +25,7 @@ use reth_invalid_block_hooks::InvalidBlockWitnessHook; use reth_network_p2p::headers::client::HeadersClient; use reth_node_api::{FullNodeTypes, NodeTypes, NodeTypesWithDB, NodeTypesWithDBAdapter}; use reth_node_core::{ - args::InvalidBlockHookType, + args::{DefaultEraHost, InvalidBlockHookType}, dirs::{ChainPath, DataDirPath}, node_config::NodeConfig, primitives::BlockHeader, @@ -51,7 +51,10 @@ use reth_prune::{PruneModes, PrunerBuilder}; use reth_rpc_api::clients::EthApiClient; use reth_rpc_builder::config::RethRpcServerConfig; use reth_rpc_layer::JwtSecret; -use reth_stages::{sets::DefaultStages, MetricEvent, PipelineBuilder, PipelineTarget, StageId}; +use reth_stages::{ + sets::DefaultStages, stages::EraImportSource, MetricEvent, PipelineBuilder, PipelineTarget, + StageId, +}; use reth_static_file::StaticFileProducer; use reth_tasks::TaskExecutor; use reth_tracing::tracing::{debug, error, info, warn}; @@ -952,6 +955,23 @@ where .launch() .await } + + /// Creates the ERA import source based on node configuration. + /// + /// Returns `Some(EraImportSource)` if ERA is enabled in the node config, otherwise `None`. + pub fn era_import_source(&self) -> Option { + let node_config = self.node_config(); + if !node_config.era.enabled { + return None; + } + + EraImportSource::maybe_new( + node_config.era.source.path.clone(), + node_config.era.source.url.clone(), + || node_config.chain.chain().kind().default_era_host(), + || node_config.datadir().data_dir().join("era").into(), + ) + } } impl diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 2a138e09726..cfb9e5f5018 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -27,7 +27,6 @@ use reth_node_api::{ PayloadAttributesBuilder, PayloadTypes, }; use reth_node_core::{ - args::DefaultEraHost, dirs::{ChainPath, DataDirPath}, exit::NodeExitFuture, primitives::Head, @@ -37,7 +36,6 @@ use reth_provider::{ providers::{BlockchainProvider, NodeTypesForProvider}, BlockNumReader, }; -use reth_stages::stages::EraImportSource; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; use reth_tracing::tracing::{debug, error, info}; @@ -154,17 +152,6 @@ where let consensus = Arc::new(ctx.components().consensus().clone()); - let era_import_source = if node_config.era.enabled { - EraImportSource::maybe_new( - node_config.era.source.path.clone(), - node_config.era.source.url.clone(), - || node_config.chain.chain().kind().default_era_host(), - || node_config.datadir().data_dir().join("era").into(), - ) - } else { - None - }; - let pipeline = build_networked_pipeline( &ctx.toml_config().stages, network_client.clone(), @@ -177,7 +164,7 @@ where static_file_producer, ctx.components().evm_config().clone(), maybe_exex_manager_handle.clone().unwrap_or_else(ExExManagerHandle::empty), - era_import_source, + ctx.era_import_source(), )?; // The new engine writes directly to static files. This ensures that they're up to the tip. From 31d0bb1d5831f6eca846d55918178d07354d268c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 28 Jun 2025 01:19:05 +0200 Subject: [PATCH 262/274] refactor: move consensus layer events to launch context (#17117) --- crates/node/builder/src/launch/common.rs | 26 ++++++++++++++++++++++++ crates/node/builder/src/launch/engine.rs | 13 +++--------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 9d514e71845..f7696799e97 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -65,6 +65,9 @@ use tokio::sync::{ oneshot, watch, }; +use futures::{future::Either, stream, Stream, StreamExt}; +use reth_node_events::{cl::ConsensusLayerHealthEvents, node::NodeEvent}; + /// Reusable setup for launching a node. /// /// This provides commonly used boilerplate for launching a node. @@ -972,6 +975,29 @@ where || node_config.datadir().data_dir().join("era").into(), ) } + + /// Creates consensus layer health events stream based on node configuration. + /// + /// Returns a stream that monitors consensus layer health if: + /// - No debug tip is configured + /// - Not running in dev mode + /// + /// Otherwise returns an empty stream. + pub fn consensus_layer_events( + &self, + ) -> impl Stream>> + 'static + where + T::Provider: reth_provider::CanonChainTracker, + { + if self.node_config().debug.tip.is_none() && !self.is_dev() { + Either::Left( + ConsensusLayerHealthEvents::new(Box::new(self.blockchain_db().clone())) + .map(Into::into), + ) + } else { + Either::Right(stream::empty()) + } + } } impl diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index cfb9e5f5018..b9eca178acd 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -9,7 +9,7 @@ use crate::{ NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter, }; use alloy_consensus::BlockHeader; -use futures::{future::Either, stream, stream_select, StreamExt}; +use futures::{stream_select, StreamExt}; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_db_api::{database_metrics::DatabaseMetrics, Database}; use reth_engine_local::{LocalMiner, LocalPayloadAttributesBuilder}; @@ -31,7 +31,7 @@ use reth_node_core::{ exit::NodeExitFuture, primitives::Head, }; -use reth_node_events::{cl::ConsensusLayerHealthEvents, node}; +use reth_node_events::node; use reth_provider::{ providers::{BlockchainProvider, NodeTypesForProvider}, BlockNumReader, @@ -249,14 +249,7 @@ where let events = stream_select!( event_sender.new_listener().map(Into::into), pipeline_events.map(Into::into), - if ctx.node_config().debug.tip.is_none() && !ctx.is_dev() { - Either::Left( - ConsensusLayerHealthEvents::new(Box::new(ctx.blockchain_db().clone())) - .map(Into::into), - ) - } else { - Either::Right(stream::empty()) - }, + ctx.consensus_layer_events(), pruner_events.map(Into::into), static_file_producer_events.map(Into::into), ); From 0a8a4ac2ca5f9c081559af70d55e0233f3ae0474 Mon Sep 17 00:00:00 2001 From: kilavvy <140459108+kilavvy@users.noreply.github.com> Date: Sat, 28 Jun 2025 11:43:22 +0200 Subject: [PATCH 263/274] docs: fix typo in section of node-components.mdx (#17105) --- docs/vocs/docs/pages/sdk/node-components.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/vocs/docs/pages/sdk/node-components.mdx b/docs/vocs/docs/pages/sdk/node-components.mdx index cdd4b93650f..d569d499dd9 100644 --- a/docs/vocs/docs/pages/sdk/node-components.mdx +++ b/docs/vocs/docs/pages/sdk/node-components.mdx @@ -93,7 +93,7 @@ let node = NodeBuilder::new(config) ## Component Lifecycle -Components follow a specific lifecycle startng from node builder initialization to shutdown: +Components follow a specific lifecycle starting from node builder initialization to shutdown: 1. **Initialization**: Components are created with their dependencies 2. **Configuration**: Settings and parameters are applied From 6f1497cc18cd2d96922c98f3222ea2b63c80e6f3 Mon Sep 17 00:00:00 2001 From: cakevm Date: Sat, 28 Jun 2025 11:49:07 +0200 Subject: [PATCH 264/274] feat(alloy-provider): implement fetch block (#16934) --- Cargo.lock | 1 + crates/alloy-provider/Cargo.toml | 1 + crates/alloy-provider/src/lib.rs | 27 ++++++++++++++--- crates/rpc/rpc-convert/src/block.rs | 47 +++++++++++++++++++++++++++++ crates/rpc/rpc-convert/src/lib.rs | 2 ++ 5 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 crates/rpc/rpc-convert/src/block.rs diff --git a/Cargo.lock b/Cargo.lock index b95b5e57b97..b8cf9c75429 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7197,6 +7197,7 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-prune-types", + "reth-rpc-convert", "reth-stages-types", "reth-storage-api", "reth-trie", diff --git a/crates/alloy-provider/Cargo.toml b/crates/alloy-provider/Cargo.toml index 6eb47e1f4d5..22a8e724890 100644 --- a/crates/alloy-provider/Cargo.toml +++ b/crates/alloy-provider/Cargo.toml @@ -24,6 +24,7 @@ reth-node-types.workspace = true reth-trie.workspace = true reth-stages-types.workspace = true reth-db-api.workspace = true +reth-rpc-convert.workspace = true # alloy alloy-provider.workspace = true diff --git a/crates/alloy-provider/src/lib.rs b/crates/alloy-provider/src/lib.rs index de3ff9dc19e..ba4767006a4 100644 --- a/crates/alloy-provider/src/lib.rs +++ b/crates/alloy-provider/src/lib.rs @@ -46,6 +46,7 @@ use reth_provider::{ TransactionVariant, TransactionsProvider, }; use reth_prune_types::{PruneCheckpoint, PruneSegment}; +use reth_rpc_convert::TryFromBlockResponse; use reth_stages_types::{StageCheckpoint, StageId}; use reth_storage_api::{ BlockBodyIndicesProvider, BlockReaderIdExt, BlockSource, DBProvider, NodePrimitivesProvider, @@ -345,6 +346,7 @@ where P: Provider + Clone + 'static, N: Network, Node: NodeTypes, + BlockTy: TryFromBlockResponse, { type Block = BlockTy; @@ -356,8 +358,21 @@ where Err(ProviderError::UnsupportedProvider) } - fn block(&self, _id: BlockHashOrNumber) -> ProviderResult> { - Err(ProviderError::UnsupportedProvider) + fn block(&self, id: BlockHashOrNumber) -> ProviderResult> { + let block_response = self.block_on_async(async { + self.provider.get_block(id.into()).full().await.map_err(ProviderError::other) + })?; + + let Some(block_response) = block_response else { + // If the block was not found, return None + return Ok(None); + }; + + // Convert the network block response to primitive block + let block = as TryFromBlockResponse>::from_block_response(block_response) + .map_err(ProviderError::other)?; + + Ok(Some(block)) } fn pending_block(&self) -> ProviderResult>> { @@ -410,9 +425,13 @@ where P: Provider + Clone + 'static, N: Network, Node: NodeTypes, + BlockTy: TryFromBlockResponse, { - fn block_by_id(&self, _id: BlockId) -> ProviderResult> { - Err(ProviderError::UnsupportedProvider) + fn block_by_id(&self, id: BlockId) -> ProviderResult> { + match id { + BlockId::Number(number_or_tag) => self.block_by_number_or_tag(number_or_tag), + BlockId::Hash(hash) => self.block_by_hash(hash.block_hash), + } } fn sealed_header_by_id( diff --git a/crates/rpc/rpc-convert/src/block.rs b/crates/rpc/rpc-convert/src/block.rs new file mode 100644 index 00000000000..144bcdcac97 --- /dev/null +++ b/crates/rpc/rpc-convert/src/block.rs @@ -0,0 +1,47 @@ +//! Conversion traits for block responses to primitive block types. + +use alloy_network::Network; +use std::convert::Infallible; + +/// Trait for converting network block responses to primitive block types. +pub trait TryFromBlockResponse { + /// The error type returned if the conversion fails. + type Error: core::error::Error + Send + Sync + Unpin; + + /// Converts a network block response to a primitive block type. + /// + /// # Returns + /// + /// Returns `Ok(Self)` on successful conversion, or `Err(Self::Error)` if the conversion fails. + fn from_block_response(block_response: N::BlockResponse) -> Result + where + Self: Sized; +} + +impl TryFromBlockResponse for alloy_consensus::Block +where + N::BlockResponse: Into, +{ + type Error = Infallible; + + fn from_block_response(block_response: N::BlockResponse) -> Result { + Ok(block_response.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_consensus::{Block, TxEnvelope}; + use alloy_network::Ethereum; + use alloy_rpc_types_eth::BlockTransactions; + + #[test] + fn test_try_from_block_response() { + let rpc_block: alloy_rpc_types_eth::Block = + alloy_rpc_types_eth::Block::new(Default::default(), BlockTransactions::Full(vec![])); + let result = + as TryFromBlockResponse>::from_block_response(rpc_block); + assert!(result.is_ok()); + } +} diff --git a/crates/rpc/rpc-convert/src/lib.rs b/crates/rpc/rpc-convert/src/lib.rs index d274d3f9642..bdd8035780c 100644 --- a/crates/rpc/rpc-convert/src/lib.rs +++ b/crates/rpc/rpc-convert/src/lib.rs @@ -10,10 +10,12 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +pub mod block; mod fees; mod rpc; pub mod transaction; +pub use block::TryFromBlockResponse; pub use fees::{CallFees, CallFeesError}; pub use rpc::*; pub use transaction::{ From a8fa75148c4e452a7f47a57ee87467978d1a9175 Mon Sep 17 00:00:00 2001 From: PixelPilot <161360836+PixelPil0t1@users.noreply.github.com> Date: Sat, 28 Jun 2025 20:27:03 +0200 Subject: [PATCH 265/274] Replace Book with Docs references (#17125) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53f5c9075bc..a5d3775cd0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ If you have reviewed existing documentation and still have questions, or you are *opening a discussion**. This repository comes with a discussions board where you can also ask for help. Click the " Discussions" tab at the top. -As Reth is still in heavy development, the documentation can be a bit scattered. The [Reth Book][reth-book] is our +As Reth is still in heavy development, the documentation can be a bit scattered. The [Reth Docs][reth-docs] is our current best-effort attempt at keeping up-to-date information. ### Submitting a bug report @@ -235,7 +235,7 @@ _Adapted from the [Foundry contributing guide][foundry-contributing]_. [dev-tg]: https://t.me/paradigm_reth -[reth-book]: https://github.com/paradigmxyz/reth/tree/main/book +[reth-docs]: https://github.com/paradigmxyz/reth/tree/main/docs [mcve]: https://stackoverflow.com/help/mcve From a072de32d1b7b72bf6b1a6231edee5ebfab3c020 Mon Sep 17 00:00:00 2001 From: Hopium <135053852+Hopium21@users.noreply.github.com> Date: Sun, 29 Jun 2025 13:13:01 +0200 Subject: [PATCH 266/274] docs: fix broken tutorial link (#17127) --- docs/vocs/docs/pages/exex/remote.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/vocs/docs/pages/exex/remote.mdx b/docs/vocs/docs/pages/exex/remote.mdx index 536dc93b288..772b56d7fd7 100644 --- a/docs/vocs/docs/pages/exex/remote.mdx +++ b/docs/vocs/docs/pages/exex/remote.mdx @@ -35,7 +35,7 @@ but some of specific to what we need now. We also added a build dependency for Tonic. We will use it to generate the Rust code for our Protobuf definitions at compile time. Read more about using Tonic in the -[introductory tutorial](https://github.com/hyperium/tonic/blob/6a213e9485965db0628591e30577ed81cdaeaf2b/examples/helloworld-tutorial). +[introductory tutorial](https://github.com/hyperium/tonic/blob/6a213e9485965db0628591e30577ed81cdaeaf2b/examples/helloworld-tutorial.md). Also, we now have two separate binaries: From 63f6845152471f54a93180797a815133562dcfda Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 11:22:08 +0000 Subject: [PATCH 267/274] chore(deps): weekly `cargo update` (#17131) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- Cargo.lock | 142 ++++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b8cf9c75429..fad2c21f36d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806275fdf84a9677bba30ab76887983fc669b4dbf52e0b9587fc65c988aa4f06" +checksum = "d8b77018eec2154eb158869f9f2914a3ea577adf87b11be2764d4795d5ccccf7" dependencies = [ "alloy-eips", "alloy-primitives", @@ -138,9 +138,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badfd375a1a059ac27048bc9fc73091940b0693c3018a0238383ead5ebe48a47" +checksum = "65bf8b058ff364d6e94bcd2979d7da1862e94d2987065a4eb41fa9eac36e028a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624cac8e9085d1461bb94f487fe38b3bd3cfad0285ae022a573e2b7be1dd7434" +checksum = "049ed4836d368929d7c5e206bab2e8d92f00524222edc0026c6bf2a3cb8a02d5" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334bca3e3b957e4272f6f434db27dc0f39c27ae9de752eacb31b7be9cce4dd0c" +checksum = "33d134f3ac4926124eaf521a1031d11ea98816df3d39fc446fcfd6b36884603f" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -279,9 +279,9 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614462440df2d478efa9445d6b148f5f404fd8b889ffe53aeb9236eb2d318894" +checksum = "fb1c2792605e648bdd1fddcfed8ce0d39d3db495c71d2240cb53df8aee8aea1f" dependencies = [ "alloy-eips", "alloy-primitives", @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0623849314fecb86fc335774b7049151adaec19ebac2426c42a1c6d43df2650c" +checksum = "31cfdacfeb6b6b40bf6becf92e69e575c68c9f80311c3961d019e29c0b8d6be2" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -333,9 +333,9 @@ dependencies = [ [[package]] name = "alloy-network" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1d6de8ee0ef8992d0f6e20d576a7bcfb1f5e76173738deb811969296d2f46a" +checksum = "de68a3f09cd9ab029cf87d08630e1336ca9a530969689fd151d505fa888a2603" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58652eeeec752eb0b54de2b8b1e3c42ec35be3e99b6f9ac4209bf0d38ecd0241" +checksum = "fcc2689c8addfc43461544d07a6f5f3a3e1f5f4efae61206cb5783dc383cfc8f" dependencies = [ "alloy-consensus", "alloy-eips", @@ -432,9 +432,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "160e90c39e471a835af97dc91cac75cf16c3d504ad2ce20c17e51a05a754b45c" +checksum = "8ced931220f547d30313530ad315654b7862ef52631c90ab857d792865f84a7d" dependencies = [ "alloy-chains", "alloy-consensus", @@ -476,9 +476,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08745d745c4b9f247baf5d1324c4ee24f61ad72b37702b4ccb7dea10299dd7de" +checksum = "8e37d6cf286fd30bacac525ab1491f9d1030d39ecce237821f2a5d5922eb9a37" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -519,9 +519,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b0f1ddddf65f5b8df258ab204bb719a298a550b70e5d9355e56f312274c2e7" +checksum = "6d1d1eac6e48b772c7290f0f79211a0e822a38b057535b514cc119abd857d5b6" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -547,9 +547,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9577dbe16550c3d83d6ac7656a560ecfe82b9f1268c869eab6cc9a82de417c64" +checksum = "8589c6ae318fcc9624d42e9166f7f82b630d9ad13e180c52addf20b93a8af266" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -560,9 +560,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d975fd5df7b1d0cba9e2ae707076ecc9b589f58217107cf92a8eb9f11059e216" +checksum = "0182187bcbe47f3a737f5eced007b7788d4ed37aba19d43fd3df123169b3b05e" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -572,9 +572,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9baee80951da08017fd2ab68ddc776087b8439c97b905691a6bf35d3328e749" +checksum = "754d5062b594ed300a3bb0df615acb7bacdbd7bd1cd1a6e5b59fb936c5025a13" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -584,9 +584,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "635e602d271a907875a7f65a732dad523ca7a767c7ca9d20fec6428c09d3fb88" +checksum = "02cfd7ecb21a1bfe68ac6b551172e4d41f828bcc33a2e1563a65d482d4efc1cf" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba36a2f724f5848e1adef4de52c889a4e2869791ad9714600c2e195b0fd8108" +checksum = "32c1ddf8fb2e41fa49316185d7826ed034f55819e0017e65dc6715f911b8a1ee" dependencies = [ "alloy-eips", "alloy-primitives", @@ -613,9 +613,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-debug" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7dca384a783e2598d8cfcbac598b9ff08345ed0ab1bbba93ee69ae60898b30" +checksum = "7c81ae89a04859751bac72e5e73459bceb3e6a4d2541f2f1374e35be358fd171" dependencies = [ "alloy-primitives", "serde", @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeb91b73980ede87c4f62b03316cc86a4e76ea13eaf35507fabd6e717b5445f" +checksum = "662b720c498883427ffb9f5e38c7f02b56ac5c0cdd60b457e88ce6b6a20b9ce9" dependencies = [ "alloy-consensus", "alloy-eips", @@ -644,9 +644,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4785c613476178a820685135db58472734deba83fa0700bd19aa81b3aec184" +checksum = "bb082c325bdfd05a7c71f52cd1060e62491fbf6edf55962720bdc380847b0784" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -665,9 +665,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-mev" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26e0820f4b92e669558e804b2152f2358ead50ce8188cd221d21b89f482b4c09" +checksum = "84c1b50012f55de4a6d58ee9512944089fa61a835e6fe3669844075bb6e0312e" dependencies = [ "alloy-consensus", "alloy-eips", @@ -680,9 +680,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308e2c5d343297f0145e0e5ca367b07807f3e4d0bbf43c19cbee541f09d091d4" +checksum = "eaf52c884c7114c5d1f1f2735634ba0f6579911427281fb02cbd5cb8147723ca" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -694,9 +694,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07c583f414beb5c403a94470b3275bbbc79b947bb17e8646b8a693e3c879d8b" +checksum = "5e4fd0df1af2ed62d02e7acbc408a162a06f30cb91550c2ec34b11c760cdc0ba" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a9c0a4942ae1458f054f900b1f2386a1101331b3220313189fc811502d4f1a" +checksum = "c7f26c17270c2ac1bd555c4304fe067639f0ddafdd3c8d07a200b2bb5a326e03" dependencies = [ "alloy-primitives", "arbitrary", @@ -718,9 +718,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d23d8fcd0531ecd6b41f84f1acabf174033ad20a0da59f941064fa58c89c811" +checksum = "5d9fd649d6ed5b8d7e5014e01758efb937e8407124b182a7f711bf487a1a2697" dependencies = [ "alloy-primitives", "async-trait", @@ -733,9 +733,9 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f87574b7feb48e1011b718faf0c17d516743de3d4e08dbb9fb1465cfeddfd1" +checksum = "c288c5b38be486bb84986701608f5d815183de990e884bb747f004622783e125" dependencies = [ "alloy-consensus", "alloy-network", @@ -821,9 +821,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac51fda778a8b4012c91cc161ed8400efedd7c2e33d937439543d73190478ec9" +checksum = "e1b790b89e31e183ae36ac0a1419942e21e94d745066f5281417c3e4299ea39e" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -844,9 +844,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f63d5f69ab4a7e2607f3d9b6c7ee6bf1ee1b726d411aa08a00a174d1dde37ac" +checksum = "f643645a33a681d09ac1ca2112014c2ca09c68aad301da4400484d59c746bc70" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -859,9 +859,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "785ea5c078dc8d6545dad222e530a816a8a497b7058e3ce398dcebe386f03f27" +checksum = "1c2d843199d0bdb4cbed8f1b6f2da7f68bcb9c5da7f57e789009e4e7e76d1bec" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -879,9 +879,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69060d439b5bb919440d75d87b5f598738d043a51a5932943ab65fa92084e1f6" +checksum = "3d27aae8c7a6403d3d3e874ad2eeeadbf46267b614bac2d4d82786b9b8496464" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -917,9 +917,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c194c0f69223d1b3a1637c3ceaf966f392fc9a2756264e27d61bb72bd0c4645" +checksum = "d4ef40a046b9bf141afc440cef596c79292708aade57c450dc74e843270fd8e7" dependencies = [ "alloy-primitives", "darling", @@ -2295,7 +2295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -3165,7 +3165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -4867,7 +4867,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5223,7 +5223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.2", ] [[package]] @@ -6769,7 +6769,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11071,7 +11071,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11084,7 +11084,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11142,7 +11142,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs 0.26.11", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -11920,7 +11920,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix 1.0.7", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -12566,7 +12566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f9612d9503675b07b244922ea6f6f3cdd88c43add1b3498084613fc88cdf69d" dependencies = [ "cc", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -13112,7 +13112,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] From c08d41a2f764e47339590342d6dbc2b7b6ab7fb5 Mon Sep 17 00:00:00 2001 From: adust Date: Sun, 29 Jun 2025 20:35:24 +0900 Subject: [PATCH 268/274] docs: remove reference to ContextStatefulPrecompile in precompile cache example (#17130) Co-authored-by: Claude --- examples/precompile-cache/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/precompile-cache/src/main.rs b/examples/precompile-cache/src/main.rs index 8497726ddfa..e72fee598cc 100644 --- a/examples/precompile-cache/src/main.rs +++ b/examples/precompile-cache/src/main.rs @@ -45,11 +45,9 @@ type PrecompileLRUCache = LruMap<(Bytes, u64), PrecompileResult>; /// A cache for precompile inputs / outputs. /// -/// This assumes that the precompile is a standard precompile, as in `StandardPrecompileFn`, meaning -/// its inputs are only `(Bytes, u64)`. -/// -/// NOTE: This does not work with "context stateful precompiles", ie `ContextStatefulPrecompile` or -/// `ContextStatefulPrecompileMut`. They are explicitly banned. +/// This cache works with standard precompiles that take input data and gas limit as parameters. +/// The cache key is composed of the input bytes and gas limit, and the cached value is the +/// precompile execution result. #[derive(Debug)] pub struct PrecompileCache { /// Caches for each precompile input / output. From f67629fe918fcb90697b08e1d2b4d9dfafbfef49 Mon Sep 17 00:00:00 2001 From: Noisy <125606576+donatik27@users.noreply.github.com> Date: Sun, 29 Jun 2025 13:39:20 +0200 Subject: [PATCH 269/274] docs: fix installation source URL in ARM devices guide (#17128) Co-authored-by: Matthias Seitz --- docs/vocs/docs/pages/installation/build-for-arm-devices.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/vocs/docs/pages/installation/build-for-arm-devices.mdx b/docs/vocs/docs/pages/installation/build-for-arm-devices.mdx index 534fe1c014e..23b91e08770 100644 --- a/docs/vocs/docs/pages/installation/build-for-arm-devices.mdx +++ b/docs/vocs/docs/pages/installation/build-for-arm-devices.mdx @@ -46,7 +46,7 @@ Some newer versions of ARM architecture offer support for Large Virtual Address ## Build Reth -If both your CPU architecture and the memory layout are valid, the instructions for building Reth will not differ from [the standard process](https://paradigmxyz.github.io/reth/installation/source.html). +If both your CPU architecture and the memory layout are valid, the instructions for building Reth will not differ from [the standard process](https://reth.rs/installation/source/). ## Troubleshooting From 6051c38c90372d7fdbb72469dd2d8f4bce4299ca Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 9 Jul 2025 01:35:54 +0100 Subject: [PATCH 270/274] reconcile upstream merge --- Cargo.lock | 465 ++++++------------ Cargo.toml | 30 +- crates/cli/commands/src/stage/dump/merkle.rs | 2 +- crates/cli/commands/src/stage/run.rs | 15 +- crates/e2e-test-utils/src/lib.rs | 1 + crates/e2e-test-utils/src/testsuite/setup.rs | 4 +- crates/node/builder/src/launch/engine.rs | 11 +- crates/rpc/rpc-convert/Cargo.toml | 17 + crates/rpc/rpc-convert/src/lib.rs | 3 + crates/rpc/rpc-convert/src/transaction.rs | 72 +++ crates/scroll/alloy/evm/src/block/mod.rs | 14 +- crates/scroll/alloy/evm/src/lib.rs | 3 +- crates/scroll/alloy/evm/src/system_caller.rs | 9 +- crates/scroll/alloy/network/src/lib.rs | 4 + crates/scroll/cli/src/lib.rs | 11 +- crates/scroll/evm/src/build.rs | 6 +- crates/scroll/evm/src/config.rs | 16 +- crates/scroll/node/src/addons.rs | 1 + crates/scroll/openvm-compat/Cargo.toml | 2 +- crates/scroll/primitives/src/receipt.rs | 2 - crates/scroll/rpc/Cargo.toml | 1 + crates/scroll/rpc/src/error.rs | 7 + crates/scroll/rpc/src/eth/call.rs | 122 +---- crates/scroll/rpc/src/eth/mod.rs | 19 +- crates/scroll/rpc/src/eth/pending_block.rs | 3 +- crates/scroll/rpc/src/eth/transaction.rs | 4 +- crates/stages/stages/benches/criterion.rs | 8 +- crates/stages/stages/src/stages/merkle.rs | 15 +- examples/custom-node/Cargo.toml | 1 + examples/custom-node/src/primitives/header.rs | 6 + 30 files changed, 350 insertions(+), 524 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8681c5ef1d4..11d5c2cffb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,8 +272,8 @@ dependencies = [ "auto_impl", "derive_more", "op-alloy-consensus", - "op-revm 7.0.1", - "revm 26.0.1", + "op-revm", + "revm", "thiserror 2.0.12", ] @@ -383,8 +383,8 @@ dependencies = [ "alloy-primitives", "auto_impl", "op-alloy-consensus", - "op-revm 7.0.1", - "revm 26.0.1", + "op-revm", + "revm", ] [[package]] @@ -1696,9 +1696,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -3072,7 +3072,7 @@ dependencies = [ "reth-tracing", "reth-trie", "reth-trie-db", - "revm 24.0.0", + "revm", "serde", "serde_json", "thiserror 2.0.12", @@ -3417,7 +3417,7 @@ dependencies = [ "modular-bitfield", "op-alloy-consensus", "op-alloy-rpc-types-engine", - "op-revm 5.0.0", + "op-revm", "reth-chain-state", "reth-codecs", "reth-db-api", @@ -3427,10 +3427,11 @@ dependencies = [ "reth-op", "reth-optimism-forks", "reth-payload-builder", + "reth-primitives-traits", "reth-rpc-api", "reth-rpc-engine-api", - "revm 24.0.0", - "revm-primitives 19.1.0", + "revm", + "revm-primitives", "serde", "test-fuzz", "thiserror 2.0.12", @@ -6145,26 +6146,14 @@ dependencies = [ "tracing", ] -[[package]] -name = "op-revm" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "auto_impl", - "once_cell", - "revm 24.0.0", - "serde", -] - [[package]] name = "op-revm" version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b97d2b54651fcd2955b454e86b2336c031e17925a127f4c44e2b63b2eeda923" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", "once_cell", - "revm 26.0.1", + "revm", "serde", ] @@ -7309,8 +7298,8 @@ dependencies = [ "reth-stages-types", "reth-storage-api", "reth-trie", - "revm 24.0.0", - "revm-primitives 19.1.0", + "revm", + "revm-primitives", "tokio", "tracing", ] @@ -7399,8 +7388,8 @@ dependencies = [ "reth-storage-api", "reth-testing-utils", "reth-trie", - "revm-database 4.0.1", - "revm-state 4.0.1", + "revm-database", + "revm-state", "serde", "tokio", "tokio-stream", @@ -7916,7 +7905,7 @@ dependencies = [ "reth-tasks", "reth-tokio-util", "reth-tracing", - "revm 24.0.0", + "revm", "serde_json", "tokio", "tokio-stream", @@ -8091,9 +8080,9 @@ dependencies = [ "reth-trie-db", "reth-trie-parallel", "reth-trie-sparse", - "revm 24.0.0", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm", + "revm-primitives", + "revm-state", "schnellru", "serde_json", "thiserror 2.0.12", @@ -8435,7 +8424,7 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-transaction-pool", - "revm 24.0.0", + "revm", "tracing", ] @@ -8496,7 +8485,7 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-trie-common", - "revm 24.0.0", + "revm", "scroll-alloy-evm", ] @@ -8518,7 +8507,7 @@ dependencies = [ "reth-execution-types", "reth-primitives-traits", "reth-testing-utils", - "revm 24.0.0", + "revm", "secp256k1 0.30.0", ] @@ -8549,7 +8538,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-primitives-traits", "reth-trie-common", - "revm 24.0.0", + "revm", "serde", "serde_with", ] @@ -8677,8 +8666,8 @@ dependencies = [ "reth-rpc-api", "reth-tracing", "reth-trie", - "revm-bytecode 4.0.1", - "revm-database 4.0.1", + "revm-bytecode", + "revm-database", "serde", "serde_json", ] @@ -9107,7 +9096,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "reth-trie-db", - "revm 24.0.0", + "revm", "serde_json", "tokio", ] @@ -9309,7 +9298,7 @@ dependencies = [ "reth-storage-errors", "reth-trie", "reth-trie-common", - "revm 24.0.0", + "revm", "thiserror 2.0.12", "tracing", ] @@ -9325,7 +9314,7 @@ dependencies = [ "alloy-op-evm", "alloy-primitives", "op-alloy-consensus", - "op-revm 5.0.0", + "op-revm", "reth-chainspec", "reth-evm", "reth-execution-errors", @@ -9336,7 +9325,7 @@ dependencies = [ "reth-optimism-primitives", "reth-primitives-traits", "reth-revm", - "revm 24.0.0", + "revm", "thiserror 2.0.12", ] @@ -9367,7 +9356,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-network", "op-alloy-rpc-types-engine", - "op-revm 5.0.0", + "op-revm", "reth-chainspec", "reth-consensus", "reth-db", @@ -9404,7 +9393,7 @@ dependencies = [ "reth-transaction-pool", "reth-trie-common", "reth-trie-db", - "revm 24.0.0", + "revm", "serde", "serde_json", "tokio", @@ -9441,7 +9430,7 @@ dependencies = [ "reth-revm", "reth-storage-api", "reth-transaction-pool", - "revm 24.0.0", + "revm", "serde", "sha2 0.10.9", "thiserror 2.0.12", @@ -9501,7 +9490,7 @@ dependencies = [ "op-alloy-rpc-jsonrpsee", "op-alloy-rpc-types", "op-alloy-rpc-types-engine", - "op-revm 5.0.0", + "op-revm", "parking_lot", "reqwest", "reth-chain-state", @@ -9527,7 +9516,7 @@ dependencies = [ "reth-storage-api", "reth-tasks", "reth-transaction-pool", - "revm 24.0.0", + "revm", "serde_json", "thiserror 2.0.12", "tokio", @@ -9571,7 +9560,7 @@ dependencies = [ "op-alloy-consensus", "op-alloy-flz", "op-alloy-rpc-types", - "op-revm 5.0.0", + "op-revm", "parking_lot", "reth-chain-state", "reth-chainspec", @@ -9708,9 +9697,9 @@ dependencies = [ "rayon", "reth-chainspec", "reth-codecs", - "revm-bytecode 4.0.1", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm-bytecode", + "revm-primitives", + "revm-state", "scroll-alloy-consensus", "secp256k1 0.30.0", "serde", @@ -9760,9 +9749,9 @@ dependencies = [ "reth-testing-utils", "reth-trie", "reth-trie-db", - "revm-database 4.0.1", - "revm-database-interface 4.0.1", - "revm-state 4.0.1", + "revm-database", + "revm-database-interface", + "revm-state", "strum 0.27.1", "tempfile", "tokio", @@ -9883,7 +9872,7 @@ dependencies = [ "reth-storage-api", "reth-storage-errors", "reth-trie", - "revm 24.0.0", + "revm", ] [[package]] @@ -9950,9 +9939,9 @@ dependencies = [ "reth-testing-utils", "reth-transaction-pool", "reth-trie-common", - "revm 24.0.0", + "revm", "revm-inspectors", - "revm-primitives 19.1.0", + "revm-primitives", "serde", "serde_json", "sha2 0.10.9", @@ -10077,12 +10066,17 @@ dependencies = [ "jsonrpsee-types", "op-alloy-consensus", "op-alloy-rpc-types", - "op-revm 5.0.0", + "op-revm", "reth-evm", "reth-optimism-primitives", "reth-primitives-traits", + "reth-scroll-primitives", "reth-storage-api", - "revm-context 5.0.0", + "revm-context", + "revm-scroll", + "scroll-alloy-consensus", + "scroll-alloy-evm", + "scroll-alloy-rpc-types", "thiserror 2.0.12", ] @@ -10160,7 +10154,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie-common", - "revm 24.0.0", + "revm", "revm-inspectors", "tokio", "tracing", @@ -10198,7 +10192,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "reth-trie", - "revm 24.0.0", + "revm", "revm-inspectors", "schnellru", "serde", @@ -10351,8 +10345,8 @@ dependencies = [ "reth-scroll-chainspec", "reth-scroll-forks", "reth-scroll-primitives", - "revm 24.0.0", - "revm-primitives 19.1.0", + "revm", + "revm-primitives", "revm-scroll", "scroll-alloy-consensus", "scroll-alloy-evm", @@ -10415,7 +10409,7 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "reth-trie-db", - "revm 24.0.0", + "revm", "scroll-alloy-consensus", "scroll-alloy-evm", "scroll-alloy-hardforks", @@ -10448,7 +10442,7 @@ dependencies = [ "reth-scroll-primitives", "reth-storage-api", "reth-transaction-pool", - "revm 24.0.0", + "revm", "scroll-alloy-evm", "scroll-alloy-hardforks", "thiserror 2.0.12", @@ -10495,6 +10489,7 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-rpc", + "reth-rpc-convert", "reth-rpc-eth-api", "reth-rpc-eth-types", "reth-scroll-chainspec", @@ -10502,7 +10497,7 @@ dependencies = [ "reth-scroll-primitives", "reth-tasks", "reth-transaction-pool", - "revm 24.0.0", + "revm", "scroll-alloy-consensus", "scroll-alloy-evm", "scroll-alloy-hardforks", @@ -10729,7 +10724,7 @@ dependencies = [ "reth-storage-errors", "reth-trie-common", "reth-trie-db", - "revm-database 4.0.1", + "revm-database", ] [[package]] @@ -10743,7 +10738,7 @@ dependencies = [ "reth-primitives-traits", "reth-prune-types", "reth-static-file-types", - "revm-database-interface 4.0.1", + "revm-database-interface", "thiserror 2.0.12", ] @@ -10847,8 +10842,8 @@ dependencies = [ "reth-storage-api", "reth-tasks", "reth-tracing", - "revm-interpreter 20.0.0", - "revm-primitives 19.1.0", + "revm-interpreter", + "revm-primitives", "rustc-hash 2.1.1", "schnellru", "serde", @@ -10887,8 +10882,8 @@ dependencies = [ "reth-tracing", "reth-trie-common", "reth-trie-sparse", - "revm-database 4.0.1", - "revm-state 4.0.1", + "revm-database", + "revm-state", "tracing", "triehash", ] @@ -10918,8 +10913,8 @@ dependencies = [ "rayon", "reth-codecs", "reth-primitives-traits", - "revm-database 4.0.1", - "revm-state 4.0.1", + "revm-database", + "revm-state", "serde", "serde_json", "serde_with", @@ -10943,8 +10938,8 @@ dependencies = [ "reth-storage-errors", "reth-trie", "reth-trie-common", - "revm 24.0.0", - "revm-database 4.0.1", + "revm", + "revm-database", "serde_json", "similar-asserts", "tracing", @@ -11042,255 +11037,130 @@ dependencies = [ "zstd", ] -[[package]] -name = "revm" -version = "24.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "revm-bytecode 4.0.1", - "revm-context 5.0.0", - "revm-context-interface 5.0.0", - "revm-database 4.0.1", - "revm-database-interface 4.0.1", - "revm-handler 5.0.0", - "revm-inspector 5.0.0", - "revm-interpreter 20.0.0", - "revm-precompile 21.0.0", - "revm-primitives 19.1.0", - "revm-state 4.0.1", -] - [[package]] name = "revm" version = "26.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2a493c73054a0f6635bad6e840cdbef34838e6e6186974833c901dff7dd709" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ - "revm-bytecode 5.0.0", - "revm-context 7.0.1", - "revm-context-interface 7.0.1", - "revm-database 6.0.0", - "revm-database-interface 6.0.0", - "revm-handler 7.0.1", - "revm-inspector 7.0.1", - "revm-interpreter 22.0.1", - "revm-precompile 23.0.0", - "revm-primitives 20.0.0", - "revm-state 6.0.0", -] - -[[package]] -name = "revm-bytecode" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "bitvec", - "once_cell", - "phf", - "revm-primitives 19.1.0", - "serde", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", ] [[package]] name = "revm-bytecode" version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b395ee2212d44fcde20e9425916fee685b5440c3f8e01fabae8b0f07a2fd7f08" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "bitvec", "once_cell", "phf", - "revm-primitives 20.0.0", - "serde", -] - -[[package]] -name = "revm-context" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "cfg-if", - "derive-where", - "revm-bytecode 4.0.1", - "revm-context-interface 5.0.0", - "revm-database-interface 4.0.1", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm-primitives", "serde", ] [[package]] name = "revm-context" version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b97b69d05651509b809eb7215a6563dc64be76a941666c40aabe597ab544d38" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "cfg-if", "derive-where", - "revm-bytecode 5.0.0", - "revm-context-interface 7.0.1", - "revm-database-interface 6.0.0", - "revm-primitives 20.0.0", - "revm-state 6.0.0", - "serde", -] - -[[package]] -name = "revm-context-interface" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "auto_impl", - "either", - "revm-database-interface 4.0.1", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", "serde", ] [[package]] name = "revm-context-interface" version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8f4f06a1c43bf8e6148509aa06a6c4d28421541944842b9b11ea1a6e53468f" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "alloy-eip2930", "alloy-eip7702", "auto_impl", "either", - "revm-database-interface 6.0.0", - "revm-primitives 20.0.0", - "revm-state 6.0.0", - "serde", -] - -[[package]] -name = "revm-database" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "alloy-eips", - "revm-bytecode 4.0.1", - "revm-database-interface 4.0.1", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm-database-interface", + "revm-primitives", + "revm-state", "serde", ] [[package]] name = "revm-database" version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763eb5867a109a85f8e47f548b9d88c9143c0e443ec056742052f059fa32f4f1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "alloy-eips", - "revm-bytecode 5.0.0", - "revm-database-interface 6.0.0", - "revm-primitives 20.0.0", - "revm-state 6.0.0", - "serde", -] - -[[package]] -name = "revm-database-interface" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "auto_impl", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", "serde", ] [[package]] name = "revm-database-interface" version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5ecd19a5b75b862841113b9abdd864ad4b22e633810e11e6d620e8207e361d" -dependencies = [ - "auto_impl", - "revm-primitives 20.0.0", - "revm-state 6.0.0", - "serde", -] - -[[package]] -name = "revm-handler" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", - "revm-bytecode 4.0.1", - "revm-context 5.0.0", - "revm-context-interface 5.0.0", - "revm-database-interface 4.0.1", - "revm-interpreter 20.0.0", - "revm-precompile 21.0.0", - "revm-primitives 19.1.0", - "revm-state 4.0.1", + "revm-primitives", + "revm-state", "serde", ] [[package]] name = "revm-handler" version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b61f992beaa7a5fc3f5fcf79f1093624fa1557dc42d36baa42114c2d836b59" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", "derive-where", - "revm-bytecode 5.0.0", - "revm-context 7.0.1", - "revm-context-interface 7.0.1", - "revm-database-interface 6.0.0", - "revm-interpreter 22.0.1", - "revm-precompile 23.0.0", - "revm-primitives 20.0.0", - "revm-state 6.0.0", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", "serde", ] -[[package]] -name = "revm-inspector" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "auto_impl", - "revm-context 5.0.0", - "revm-database-interface 4.0.1", - "revm-handler 5.0.0", - "revm-interpreter 20.0.0", - "revm-primitives 19.1.0", - "revm-state 4.0.1", - "serde", - "serde_json", -] - [[package]] name = "revm-inspector" version = "7.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e4400a109a2264f4bf290888ac6d02432b6d5d070492b9dcf134b0c7d51354" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", "either", - "revm-context 7.0.1", - "revm-database-interface 6.0.0", - "revm-handler 7.0.1", - "revm-interpreter 22.0.1", - "revm-primitives 20.0.0", - "revm-state 6.0.0", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", "serde", "serde_json", ] [[package]] name = "revm-inspectors" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b50ef375dbacefecfdacf8f02afc31df98acc5d8859a6f2b24d121ff2a740a8" +checksum = "2aabdffc06bdb434d9163e2d63b6fae843559afd300ea3fbeb113b8a0d8ec728" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -11300,64 +11170,27 @@ dependencies = [ "boa_engine", "boa_gc", "colorchoice", - "revm 24.0.0", + "revm", "serde", "serde_json", "thiserror 2.0.12", ] -[[package]] -name = "revm-interpreter" -version = "20.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "revm-bytecode 4.0.1", - "revm-context-interface 5.0.0", - "revm-primitives 19.1.0", - "serde", -] - [[package]] name = "revm-interpreter" version = "22.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2481ef059708772cec0ce6bc4c84b796a40111612efb73b01adf1caed7ff9ac" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ - "revm-bytecode 5.0.0", - "revm-context-interface 7.0.1", - "revm-primitives 20.0.0", + "revm-bytecode", + "revm-context-interface", + "revm-primitives", "serde", ] -[[package]] -name = "revm-precompile" -version = "21.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "ark-bls12-381", - "ark-bn254", - "ark-ec", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "aurora-engine-modexp", - "blst", - "c-kzg", - "cfg-if", - "k256", - "libsecp256k1", - "once_cell", - "p256", - "revm-primitives 19.1.0", - "ripemd", - "secp256k1 0.30.0", - "sha2 0.10.9", -] - [[package]] name = "revm-precompile" version = "23.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d581e78c8f132832bd00854fb5bf37efd95a52582003da35c25cd2cbfc63849" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -11365,34 +11198,24 @@ dependencies = [ "ark-ff 0.5.0", "ark-serialize 0.5.0", "aurora-engine-modexp", + "blst", "c-kzg", "cfg-if", "k256", "libsecp256k1", "once_cell", "p256", - "revm-primitives 20.0.0", + "revm-primitives", "ripemd", "rug", "secp256k1 0.31.1", "sha2 0.10.9", ] -[[package]] -name = "revm-primitives" -version = "19.1.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "alloy-primitives", - "num_enum", - "serde", -] - [[package]] name = "revm-primitives" version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cdf897b3418f2ee05bcade64985e5faed2dbaa349b2b5f27d3d6bfd10fff2a" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "alloy-primitives", "num_enum", @@ -11402,37 +11225,25 @@ dependencies = [ [[package]] name = "revm-scroll" version = "0.1.0" -source = "git+https://github.com/scroll-tech/scroll-revm?rev=6ccb897197d7ed319463df487f428fce0a77b47f#6ccb897197d7ed319463df487f428fce0a77b47f" +source = "git+https://github.com/scroll-tech/scroll-revm?branch=feat%2Fv78#c0609bc9e8cb23aba8f560a82e040a49726cf760" dependencies = [ "auto_impl", "enumn", "once_cell", - "revm 24.0.0", - "revm-inspector 5.0.0", - "revm-primitives 19.1.0", - "serde", -] - -[[package]] -name = "revm-state" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" -dependencies = [ - "bitflags 2.9.1", - "revm-bytecode 4.0.1", - "revm-primitives 19.1.0", + "revm", + "revm-inspector", + "revm-primitives", "serde", ] [[package]] name = "revm-state" version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6274928dd78f907103740b10800d3c0db6caeca391e75a159c168a1e5c78f8" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "bitflags 2.9.1", - "revm-bytecode 5.0.0", - "revm-primitives 20.0.0", + "revm-bytecode", + "revm-primitives", "serde", ] @@ -11881,7 +11692,7 @@ dependencies = [ "reth-evm", "reth-scroll-chainspec", "reth-scroll-evm", - "revm 24.0.0", + "revm", "revm-scroll", "scroll-alloy-consensus", "scroll-alloy-hardforks", diff --git a/Cargo.toml b/Cargo.toml index 5ea09054311..800773f4b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -469,19 +469,19 @@ reth-ress-protocol = { path = "crates/ress/protocol" } reth-ress-provider = { path = "crates/ress/provider" } # revm -revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false, features = ["secp256r1", "enable_eip7702"] } -revm-bytecode = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-database = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-state = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-primitives = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-interpreter = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-inspector = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-context = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-context-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-database-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -op-revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } -revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", rev = "6ccb897197d7ed319463df487f428fce0a77b47f", default-features = false } -revm-inspectors = "0.23.0" +revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false, features = ["enable_eip7702"] } +revm-bytecode = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-database = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-state = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-primitives = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-interpreter = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-inspector = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-context = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-context-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-database-interface = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +op-revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78", default-features = false } +revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", branch = "feat/v78", default-features = false } +revm-inspectors = "0.25.0" # eth alloy-chains = { version = "0.2.0", default-features = false } @@ -749,8 +749,8 @@ walkdir = "2.3.3" vergen-git2 = "1.0.5" [patch.crates-io] -revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" } -op-revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" } +revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" } +op-revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" } # alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } # alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } # alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } diff --git a/crates/cli/commands/src/stage/dump/merkle.rs b/crates/cli/commands/src/stage/dump/merkle.rs index 7a7bd3d2c7e..cc21c0fc29f 100644 --- a/crates/cli/commands/src/stage/dump/merkle.rs +++ b/crates/cli/commands/src/stage/dump/merkle.rs @@ -161,7 +161,7 @@ where let mut stage = MerkleStage::::Execution { // Forces updating the root instead of calculating from scratch - clean_threshold: u64::MAX, + rebuild_threshold: u64::MAX, incremental_threshold: u64::MAX, consensus: NoopConsensus::arc(), }; diff --git a/crates/cli/commands/src/stage/run.rs b/crates/cli/commands/src/stage/run.rs index 0252db65c92..bba9858f212 100644 --- a/crates/cli/commands/src/stage/run.rs +++ b/crates/cli/commands/src/stage/run.rs @@ -299,13 +299,14 @@ impl StageEnum::Merkle => { let consensus = Arc::new(components.consensus().clone()); ( - Box::new(MerkleStage::new_execution( - config.stages.merkle.rebuild_threshold, - config.stages.merkle.incremental_threshold, - consensus.clone(), - )), - Some(Box::new(MerkleStage::new_unwind(consensus))), - )}, + Box::new(MerkleStage::::new_execution( + config.stages.merkle.rebuild_threshold, + config.stages.merkle.incremental_threshold, + consensus.clone(), + )), + Some(Box::new(MerkleStage::::new_unwind(consensus))), + ) + } StageEnum::AccountHistory => ( Box::new(IndexAccountHistoryStage::new( config.stages.index_account_history, diff --git a/crates/e2e-test-utils/src/lib.rs b/crates/e2e-test-utils/src/lib.rs index 1a30e33f6b4..2953e752009 100644 --- a/crates/e2e-test-utils/src/lib.rs +++ b/crates/e2e-test-utils/src/lib.rs @@ -150,6 +150,7 @@ where N: NodeBuilderHelper, LocalPayloadAttributesBuilder: PayloadAttributesBuilder<::PayloadAttributes>, + TmpNodeAddOnsHandle: RpcHandleProvider, TmpNodeEthApi>, { let tasks = TaskManager::current(); let exec = tasks.executor(); diff --git a/crates/e2e-test-utils/src/testsuite/setup.rs b/crates/e2e-test-utils/src/testsuite/setup.rs index 95f439175c8..0970451526b 100644 --- a/crates/e2e-test-utils/src/testsuite/setup.rs +++ b/crates/e2e-test-utils/src/testsuite/setup.rs @@ -1,8 +1,8 @@ //! Test setup utilities for configuring the initial state. use crate::{ - setup_engine, testsuite::Environment, Adapter, NodeBuilderHelper, PayloadAttributesBuilder, - RpcHandleProvider, TmpNodeAddOnsHandle, TmpNodeEthApi, + setup_engine_with_connection, testsuite::Environment, Adapter, NodeBuilderHelper, + PayloadAttributesBuilder, RpcHandleProvider, TmpNodeAddOnsHandle, TmpNodeEthApi, }; use alloy_eips::BlockNumberOrTag; use alloy_primitives::B256; diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 07ae8c7d50e..a7d31623cd2 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -3,7 +3,7 @@ use crate::{ common::{Attached, LaunchContextWith, WithConfigs}, hooks::NodeHooks, - rpc::{EngineValidatorAddOn, RethRpcAddOns, RpcHandle}, + rpc::{EngineValidatorAddOn, RethRpcAddOns, RpcHandleProvider}, setup::build_networked_pipeline, AddOns, AddOnsContext, FullNode, LaunchContext, LaunchNode, NodeAdapter, NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter, @@ -43,15 +43,6 @@ use std::sync::Arc; use tokio::sync::{mpsc::unbounded_channel, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; -use crate::{ - common::{Attached, LaunchContextWith, WithConfigs}, - hooks::NodeHooks, - rpc::{EngineValidatorAddOn, RethRpcAddOns, RpcHandleProvider}, - setup::build_networked_pipeline, - AddOns, AddOnsContext, ExExLauncher, FullNode, LaunchContext, LaunchNode, NodeAdapter, - NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter, -}; - /// The engine node launcher. #[derive(Debug)] pub struct EngineNodeLauncher { diff --git a/crates/rpc/rpc-convert/Cargo.toml b/crates/rpc/rpc-convert/Cargo.toml index 0ccf2107ad2..45721742310 100644 --- a/crates/rpc/rpc-convert/Cargo.toml +++ b/crates/rpc/rpc-convert/Cargo.toml @@ -30,6 +30,13 @@ op-alloy-rpc-types = { workspace = true, optional = true } reth-optimism-primitives = { workspace = true, optional = true } op-revm = { workspace = true, optional = true } +# scroll +scroll-alloy-consensus = { workspace = true, optional = true } +scroll-alloy-evm = { workspace = true, optional = true } +scroll-alloy-rpc-types = { workspace = true, optional = true } +reth-scroll-primitives = { workspace = true, optional = true } +revm-scroll = { workspace = true, optional = true } + # revm revm-context.workspace = true @@ -50,3 +57,13 @@ op = [ "reth-evm/op", "reth-primitives-traits/op", ] +scroll = [ + "dep:scroll-alloy-consensus", + "dep:scroll-alloy-evm", + "dep:scroll-alloy-rpc-types", + "dep:reth-scroll-primitives", + "dep:reth-storage-api", + "dep:revm-scroll", + "reth-evm/scroll-alloy-traits", + "reth-primitives-traits/scroll-alloy-traits", +] diff --git a/crates/rpc/rpc-convert/src/lib.rs b/crates/rpc/rpc-convert/src/lib.rs index bdd8035780c..db1d7b86fc7 100644 --- a/crates/rpc/rpc-convert/src/lib.rs +++ b/crates/rpc/rpc-convert/src/lib.rs @@ -25,3 +25,6 @@ pub use transaction::{ #[cfg(feature = "op")] pub use transaction::op::*; + +#[cfg(feature = "scroll")] +pub use transaction::scroll::*; diff --git a/crates/rpc/rpc-convert/src/transaction.rs b/crates/rpc/rpc-convert/src/transaction.rs index edb16d341ad..68dc1a2974e 100644 --- a/crates/rpc/rpc-convert/src/transaction.rs +++ b/crates/rpc/rpc-convert/src/transaction.rs @@ -426,6 +426,78 @@ where } } +/// Scroll specific RPC transaction compatibility implementations. +#[cfg(feature = "scroll")] +pub mod scroll { + use super::*; + use alloy_consensus::SignableTransaction; + use alloy_primitives::{Address, Bytes, Signature}; + use reth_primitives_traits::SignedTransaction; + use reth_scroll_primitives::ScrollReceipt; + use reth_storage_api::{errors::ProviderError, ReceiptProvider}; + use revm_scroll::l1block::TX_L1_FEE_PRECISION_U256; + use scroll_alloy_consensus::{ScrollAdditionalInfo, ScrollTransactionInfo, ScrollTxEnvelope}; + use scroll_alloy_rpc_types::ScrollTransactionRequest; + + /// Creates [`ScrollTransactionInfo`] by adding [`ScrollAdditionalInfo`] to [`TransactionInfo`] + /// if `tx` is not a L1 message. + pub fn try_into_scroll_tx_info>( + provider: &T, + tx: &ScrollTxEnvelope, + tx_info: TransactionInfo, + ) -> Result { + let additional_info = if tx.is_l1_message() { + None + } else { + provider + .receipt_by_hash(*tx.tx_hash())? + .map(|receipt| ScrollAdditionalInfo { l1_fee: receipt.l1_fee() }) + } + .unwrap_or_default(); + + Ok(ScrollTransactionInfo::new(tx_info, additional_info)) + } + + impl FromConsensusTx for scroll_alloy_rpc_types::Transaction { + type TxInfo = ScrollTransactionInfo; + + fn from_consensus_tx(tx: ScrollTxEnvelope, signer: Address, tx_info: Self::TxInfo) -> Self { + Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info) + } + } + + impl TryIntoSimTx for ScrollTransactionRequest { + fn try_into_sim_tx(self) -> Result> { + let tx = self + .build_typed_tx() + .map_err(|request| ValueError::new(request, "Required fields missing"))?; + + // Create an empty signature for the transaction. + let signature = Signature::new(Default::default(), Default::default(), false); + + Ok(tx.into_signed(signature).into()) + } + } + + impl TryIntoTxEnv> + for ScrollTransactionRequest + { + type Err = EthTxEnvError; + + fn try_into_tx_env( + self, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result, Self::Err> { + Ok(scroll_alloy_evm::ScrollTransactionIntoTxEnv::new( + self.as_ref().clone().try_into_tx_env(cfg_env, block_env)?, + Some(Bytes::new()), + Some(TX_L1_FEE_PRECISION_U256), + )) + } + } +} + /// Optimism specific RPC transaction compatibility implementations. #[cfg(feature = "op")] pub mod op { diff --git a/crates/scroll/alloy/evm/src/block/mod.rs b/crates/scroll/alloy/evm/src/block/mod.rs index aa7448fc992..ec613d5cd72 100644 --- a/crates/scroll/alloy/evm/src/block/mod.rs +++ b/crates/scroll/alloy/evm/src/block/mod.rs @@ -150,7 +150,7 @@ where fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { // set state clear flag if the block is after the Spurious Dragon hardfork. let state_clear_flag = - self.spec.is_spurious_dragon_active_at_block(self.evm.block().number); + self.spec.is_spurious_dragon_active_at_block(self.evm.block().number.to()); self.evm.db_mut().set_state_clear_flag(state_clear_flag); // load the l1 gas oracle contract in cache. @@ -164,7 +164,7 @@ where if self .spec .scroll_fork_activation(ScrollHardfork::Curie) - .transitions_at_block(self.evm.block().number) + .transitions_at_block(self.evm.block().number.to()) { if let Err(err) = apply_curie_hard_fork(self.evm.db_mut()) { return Err(BlockExecutionError::msg(format!( @@ -177,7 +177,7 @@ where if self .spec .scroll_fork_activation(ScrollHardfork::Feynman) - .active_at_timestamp(self.evm.block().timestamp) + .active_at_timestamp(self.evm.block().timestamp.to()) { if let Err(err) = apply_feynman_hard_fork(self.evm.db_mut()) { return Err(BlockExecutionError::msg(format!( @@ -214,14 +214,14 @@ where let block = self.evm.block(); // verify the transaction type is accepted by the current fork. - if tx.tx().is_eip2930() && !chain_spec.is_curie_active_at_block(block.number) { + if tx.tx().is_eip2930() && !chain_spec.is_curie_active_at_block(block.number.to()) { return Err(BlockValidationError::InvalidTx { hash, error: Box::new(InvalidTransaction::Eip2930NotSupported), } .into()) } - if tx.tx().is_eip1559() && !chain_spec.is_curie_active_at_block(block.number) { + if tx.tx().is_eip1559() && !chain_spec.is_curie_active_at_block(block.number.to()) { return Err(BlockValidationError::InvalidTx { hash, error: Box::new(InvalidTransaction::Eip1559NotSupported), @@ -235,7 +235,9 @@ where } .into()) } - if tx.tx().is_eip7702() && !chain_spec.is_euclid_v2_active_at_timestamp(block.timestamp) { + if tx.tx().is_eip7702() && + !chain_spec.is_euclid_v2_active_at_timestamp(block.timestamp.to()) + { return Err(BlockValidationError::InvalidTx { hash, error: Box::new(InvalidTransaction::Eip7702NotSupported), diff --git a/crates/scroll/alloy/evm/src/lib.rs b/crates/scroll/alloy/evm/src/lib.rs index 744bc1ee984..cdef9a41c29 100644 --- a/crates/scroll/alloy/evm/src/lib.rs +++ b/crates/scroll/alloy/evm/src/lib.rs @@ -128,8 +128,7 @@ where tx: Self::Tx, ) -> Result, Self::Error> { if self.inspect { - self.inner.set_tx(tx.into()); - self.inner.inspect_replay() + self.inner.inspect_tx(tx.into()) } else { self.inner.transact(tx.into()) } diff --git a/crates/scroll/alloy/evm/src/system_caller.rs b/crates/scroll/alloy/evm/src/system_caller.rs index 9da990d4c4d..83cd0033743 100644 --- a/crates/scroll/alloy/evm/src/system_caller.rs +++ b/crates/scroll/alloy/evm/src/system_caller.rs @@ -62,13 +62,13 @@ fn transact_blockhashes_contract_call( evm: &mut impl Evm, ) -> Result>, BlockExecutionError> { // if Feynman is not active at timestamp then no system transaction occurs. - if !spec.is_feynman_active_at_timestamp(evm.block().timestamp) { + if !spec.is_feynman_active_at_timestamp(evm.block().timestamp.to()) { return Ok(None); } // if the block number is zero (genesis block) then no system transaction may occur as per // EIP-2935 - if evm.block().number == 0 { + if evm.block().number.to::() == 0u64 { return Ok(None); } @@ -101,7 +101,6 @@ mod tests { use reth_scroll_evm::ScrollEvmConfig; use revm::{ bytecode::Bytecode, - context::ContextTr, database::{EmptyDBTyped, State}, state::AccountInfo, Database, @@ -151,7 +150,7 @@ mod tests { system_caller.apply_blockhashes_contract_call(block.parent_hash, &mut evm).unwrap(); // assert the storage slot remains unchanged. - let parent_hash = evm.db().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(); + let parent_hash = evm.db_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(); assert_eq!(parent_hash, U256::ZERO); } @@ -197,7 +196,7 @@ mod tests { system_caller.apply_blockhashes_contract_call(block.parent_hash, &mut evm).unwrap(); // assert the hash is written to storage. - let parent_hash = evm.db().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(); + let parent_hash = evm.db_mut().storage(HISTORY_STORAGE_ADDRESS, U256::ZERO).unwrap(); assert_eq!(Into::::into(parent_hash), block.parent_hash); } } diff --git a/crates/scroll/alloy/network/src/lib.rs b/crates/scroll/alloy/network/src/lib.rs index 102dd4247d1..3bfe222c55f 100644 --- a/crates/scroll/alloy/network/src/lib.rs +++ b/crates/scroll/alloy/network/src/lib.rs @@ -58,6 +58,10 @@ impl TransactionBuilder for ScrollTransactionRequest { self.as_mut().set_nonce(nonce); } + fn take_nonce(&mut self) -> Option { + self.as_mut().nonce.take() + } + fn input(&self) -> Option<&Bytes> { self.as_ref().input() } diff --git a/crates/scroll/cli/src/lib.rs b/crates/scroll/cli/src/lib.rs index 03c748dd6e2..4313f44bcaf 100644 --- a/crates/scroll/cli/src/lib.rs +++ b/crates/scroll/cli/src/lib.rs @@ -22,7 +22,6 @@ use reth_node_core::{ use reth_node_metrics::recorder::install_prometheus_recorder; use reth_scroll_chainspec::ScrollChainSpec; use reth_scroll_evm::ScrollExecutorProvider; -use reth_scroll_node::ScrollNetworkPrimitives; use reth_scroll_primitives::ScrollPrimitives; use reth_tracing::FileWorkerGuard; use std::{ffi::OsString, fmt, future::Future, sync::Arc}; @@ -128,16 +127,14 @@ where runner.run_blocking_until_ctrl_c(command.execute::()) } Commands::Import(command) => { - runner.run_blocking_until_ctrl_c(command.execute::(components)) + runner.run_blocking_until_ctrl_c(command.execute::(components)) } Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::()), - Commands::Stage(command) => runner.run_command_until_exit(|ctx| { - command.execute::(ctx, components) - }), - Commands::P2P(command) => { - runner.run_until_ctrl_c(command.execute::()) + Commands::Stage(command) => { + runner.run_command_until_exit(|ctx| command.execute::(ctx, components)) } + Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), Commands::Recover(command) => { runner.run_command_until_exit(|ctx| command.execute::(ctx)) diff --git a/crates/scroll/evm/src/build.rs b/crates/scroll/evm/src/build.rs index 58c8e31d00b..2645dad697b 100644 --- a/crates/scroll/evm/src/build.rs +++ b/crates/scroll/evm/src/build.rs @@ -68,14 +68,14 @@ where receipts_root, withdrawals_root: None, logs_bloom, - timestamp, + timestamp: timestamp.to(), mix_hash: evm_env.block_env.prevrandao.unwrap_or_default(), nonce: BEACON_NONCE.into(), base_fee_per_gas: self .chain_spec - .is_curie_active_at_block(evm_env.block_env.number) + .is_curie_active_at_block(evm_env.block_env.number.to()) .then_some(evm_env.block_env.basefee), - number: evm_env.block_env.number, + number: evm_env.block_env.number.to(), gas_limit: evm_env.block_env.gas_limit, difficulty: evm_env.block_env.difficulty, gas_used: *gas_used, diff --git a/crates/scroll/evm/src/config.rs b/crates/scroll/evm/src/config.rs index 2b550582bac..e9baba86dff 100644 --- a/crates/scroll/evm/src/config.rs +++ b/crates/scroll/evm/src/config.rs @@ -68,9 +68,9 @@ where }; let block_env = BlockEnv { - number: header.number(), + number: U256::from(header.number()), beneficiary: coinbase, - timestamp: header.timestamp(), + timestamp: U256::from(header.timestamp()), difficulty: header.difficulty(), prevrandao: header.mix_hash(), gas_limit: header.gas_limit(), @@ -106,9 +106,9 @@ where }; let block_env = BlockEnv { - number: parent.number() + 1, + number: U256::from(parent.number() + 1), beneficiary: coinbase, - timestamp: attributes.timestamp, + timestamp: U256::from(attributes.timestamp), difficulty: U256::ONE, prevrandao: Some(B256::ZERO), gas_limit: attributes.gas_limit, @@ -238,9 +238,9 @@ mod tests { // verify block env correctly updated let expected = BlockEnv { - number: header.number, + number: U256::from(header.number), beneficiary: config.chain_spec().config.fee_vault_address.unwrap(), - timestamp: header.timestamp, + timestamp: U256::from(header.timestamp), prevrandao: Some(header.mix_hash), difficulty: U256::ZERO, basefee: header.base_fee_per_gas.unwrap_or_default(), @@ -286,9 +286,9 @@ mod tests { // verify block env let expected = BlockEnv { - number: header.number + 1, + number: U256::from(header.number + 1), beneficiary: config.chain_spec().config.fee_vault_address.unwrap(), - timestamp: attributes.timestamp, + timestamp: U256::from(attributes.timestamp), prevrandao: Some(B256::ZERO), difficulty: U256::ONE, basefee: 155157341, diff --git a/crates/scroll/node/src/addons.rs b/crates/scroll/node/src/addons.rs index 880ee6a50ce..7e4a75b3396 100644 --- a/crates/scroll/node/src/addons.rs +++ b/crates/scroll/node/src/addons.rs @@ -134,6 +134,7 @@ impl ScrollAddOnsBuilder { ScrollEthApi::::builder(), Default::default(), Default::default(), + Default::default(), ), } } diff --git a/crates/scroll/openvm-compat/Cargo.toml b/crates/scroll/openvm-compat/Cargo.toml index 067990ba5fb..5f136c1fca4 100644 --- a/crates/scroll/openvm-compat/Cargo.toml +++ b/crates/scroll/openvm-compat/Cargo.toml @@ -28,4 +28,4 @@ scroll-alloy-consensus = { path = "../alloy/consensus", default-features = false scroll-alloy-rpc-types = { path = "../alloy/rpc-types", default-features = false } [patch.crates-io] -revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74" } +revm = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v78" } diff --git a/crates/scroll/primitives/src/receipt.rs b/crates/scroll/primitives/src/receipt.rs index 405e33eba23..6e214b31089 100644 --- a/crates/scroll/primitives/src/receipt.rs +++ b/crates/scroll/primitives/src/receipt.rs @@ -353,8 +353,6 @@ impl InMemorySize for ScrollReceipt { } } -impl reth_primitives_traits::Receipt for ScrollReceipt {} - #[cfg(feature = "serde-bincode-compat")] impl reth_primitives_traits::serde_bincode_compat::SerdeBincodeCompat for ScrollReceipt { type BincodeRepr<'a> = Self; diff --git a/crates/scroll/rpc/Cargo.toml b/crates/scroll/rpc/Cargo.toml index f1fbbc9289c..3abfd428dc3 100644 --- a/crates/scroll/rpc/Cargo.toml +++ b/crates/scroll/rpc/Cargo.toml @@ -22,6 +22,7 @@ reth-rpc-eth-types.workspace = true reth-tasks = { workspace = true, features = ["rayon"] } reth-transaction-pool.workspace = true reth-rpc.workspace = true +reth-rpc-convert = { workspace = true, features = ["scroll"] } reth-node-api.workspace = true reth-node-builder.workspace = true reth-network-api.workspace = true diff --git a/crates/scroll/rpc/src/error.rs b/crates/scroll/rpc/src/error.rs index 63570531669..db577f7b7b8 100644 --- a/crates/scroll/rpc/src/error.rs +++ b/crates/scroll/rpc/src/error.rs @@ -2,6 +2,7 @@ use alloy_rpc_types_eth::BlockError; use reth_evm::execute::ProviderError; +use reth_rpc_convert::transaction::EthTxEnvError; use reth_rpc_eth_api::{AsEthApiError, TransactionConversionError}; use reth_rpc_eth_types::{error::api::FromEvmHalt, EthApiError}; use revm::context::result::{EVMError, HaltReason}; @@ -30,6 +31,12 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { } } +impl From for ScrollEthApiError { + fn from(value: EthTxEnvError) -> Self { + Self::Eth(EthApiError::from(value)) + } +} + impl From for ScrollEthApiError { fn from(error: BlockError) -> Self { Self::Eth(error.into()) diff --git a/crates/scroll/rpc/src/eth/call.rs b/crates/scroll/rpc/src/eth/call.rs index 2edd64d7e0a..2181ed0f01f 100644 --- a/crates/scroll/rpc/src/eth/call.rs +++ b/crates/scroll/rpc/src/eth/call.rs @@ -1,24 +1,17 @@ use super::ScrollNodeCore; use crate::{ScrollEthApi, ScrollEthApiError}; -use alloy_consensus::transaction::Either; -use alloy_primitives::{TxKind, U256}; use alloy_rpc_types_eth::transaction::TransactionRequest; -use reth_evm::{block::BlockExecutorFactory, ConfigureEvm, EvmEnv, EvmFactory, SpecFor}; +use reth_evm::{block::BlockExecutorFactory, ConfigureEvm, EvmFactory, TxEnvFor}; use reth_primitives_traits::NodePrimitives; -use reth_provider::{ProviderHeader, ProviderTx}; +use reth_provider::{errors::ProviderError, ProviderHeader, ProviderTx}; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall, LoadBlock, LoadState, SpawnBlocking}, - FromEthApiError, FullEthApiTypes, IntoEthApiError, + FullEthApiTypes, RpcConvert, RpcTypes, }; -use reth_rpc_eth_types::{ - error::FromEvmError, revm_utils::CallFees, EthApiError, RpcInvalidTransactionError, -}; -use revm::{ - context::{Block, TxEnv}, - Database, -}; -use scroll_alloy_evm::{ScrollTransactionIntoTxEnv, TX_L1_FEE_PRECISION_U256}; +use reth_rpc_eth_types::error::FromEvmError; +use revm::context::TxEnv; +use scroll_alloy_evm::ScrollTransactionIntoTxEnv; impl EthCall for ScrollEthApi where @@ -47,7 +40,11 @@ where EvmFactory: EvmFactory>, >, >, - Error: FromEvmError, + RpcConvert: RpcConvert, Network = Self::NetworkTypes>, + NetworkTypes: RpcTypes>, + Error: FromEvmError + + From<::Error> + + From, > + SpawnBlocking, Self::Error: From, N: ScrollNodeCore, @@ -61,101 +58,4 @@ where fn max_simulate_blocks(&self) -> u64 { self.inner.eth_api.max_simulate_blocks() } - - fn create_txn_env( - &self, - evm_env: &EvmEnv>, - request: TransactionRequest, - mut db: impl Database>, - ) -> Result, Self::Error> { - // Ensure that if versioned hashes are set, they're not empty - if request.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) { - return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into_eth_err()) - } - - let tx_type = request.preferred_type() as u8; - - let TransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - input, - nonce, - access_list, - chain_id, - blob_versioned_hashes, - max_fee_per_blob_gas, - authorization_list, - transaction_type: _, - sidecar: _, - } = request; - - let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } = - CallFees::ensure_fees( - gas_price.map(U256::from), - max_fee_per_gas.map(U256::from), - max_priority_fee_per_gas.map(U256::from), - U256::from(evm_env.block_env.basefee), - blob_versioned_hashes.as_deref(), - max_fee_per_blob_gas.map(U256::from), - evm_env.block_env.blob_gasprice().map(U256::from), - )?; - - let gas_limit = gas.unwrap_or( - // Use maximum allowed gas limit. The reason for this - // is that both Erigon and Geth use pre-configured gas cap even if - // it's possible to derive the gas limit from the block: - // - evm_env.block_env.gas_limit, - ); - - let chain_id = chain_id.unwrap_or(evm_env.cfg_env.chain_id); - - let caller = from.unwrap_or_default(); - - let nonce = if let Some(nonce) = nonce { - nonce - } else { - db.basic(caller).map_err(Into::into)?.map(|acc| acc.nonce).unwrap_or_default() - }; - - let base = TxEnv { - tx_type, - gas_limit, - nonce, - caller, - gas_price: gas_price.saturating_to(), - gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()), - kind: to.unwrap_or(TxKind::Create), - value: value.unwrap_or_default(), - data: input - .try_into_unique_input() - .map_err(Self::Error::from_eth_err)? - .unwrap_or_default(), - chain_id: Some(chain_id), - access_list: access_list.unwrap_or_default(), - // EIP-4844 fields - blob_hashes: blob_versioned_hashes.unwrap_or_default(), - max_fee_per_blob_gas: max_fee_per_blob_gas - .map(|v| v.saturating_to()) - .unwrap_or_default(), - // EIP-7702 fields - authorization_list: authorization_list - .unwrap_or_default() - .into_iter() - .map(Either::Left) - .collect(), - }; - - Ok(ScrollTransactionIntoTxEnv::new( - base, - Some(Default::default()), - Some(TX_L1_FEE_PRECISION_U256), - )) - } } diff --git a/crates/scroll/rpc/src/eth/mod.rs b/crates/scroll/rpc/src/eth/mod.rs index c89c1509fbb..1eae675040c 100644 --- a/crates/scroll/rpc/src/eth/mod.rs +++ b/crates/scroll/rpc/src/eth/mod.rs @@ -68,8 +68,7 @@ pub struct ScrollEthApi { inner: Arc>, /// Marker for the network types. _nt: PhantomData, - tx_resp_builder: - RpcConverter>, + tx_resp_builder: RpcConverter>, } impl ScrollEthApi { @@ -110,14 +109,14 @@ where Self: Send + Sync + fmt::Debug, N: ScrollNodeCore, NetworkT: Network + Clone + fmt::Debug, + ::Evm: fmt::Debug, ::Primitives: fmt::Debug, { type Error = ScrollEthApiError; type NetworkTypes = Scroll; - type TransactionCompat = - RpcConverter>; + type RpcConvert = RpcConverter>; - fn tx_resp_builder(&self) -> &Self::TransactionCompat { + fn tx_resp_builder(&self) -> &Self::RpcConvert { &self.tx_resp_builder } } @@ -199,6 +198,7 @@ where Self: Send + Sync + Clone + 'static, N: ScrollNodeCore, NetworkT: Network, + ::Evm: fmt::Debug, ::Primitives: fmt::Debug, { #[inline] @@ -232,7 +232,7 @@ where } #[inline] - fn fee_history_cache(&self) -> &FeeHistoryCache { + fn fee_history_cache(&self) -> &FeeHistoryCache> { self.inner.eth_api.fee_history_cache() } } @@ -244,6 +244,7 @@ where Pool: TransactionPool, >, NetworkT: Network, + ::Evm: fmt::Debug, ::Primitives: fmt::Debug, { } @@ -261,7 +262,11 @@ where impl EthFees for ScrollEthApi where - Self: LoadFee, + Self: LoadFee< + Provider: ChainSpecProvider< + ChainSpec: EthChainSpec

    >, + >, + >, N: ScrollNodeCore, { } diff --git a/crates/scroll/rpc/src/eth/pending_block.rs b/crates/scroll/rpc/src/eth/pending_block.rs index 9bdb6b9b7d4..7b0323288ea 100644 --- a/crates/scroll/rpc/src/eth/pending_block.rs +++ b/crates/scroll/rpc/src/eth/pending_block.rs @@ -13,7 +13,7 @@ use reth_provider::{ use reth_rpc_eth_api::{ helpers::{LoadPendingBlock, SpawnBlocking}, types::RpcTypes, - EthApiTypes, RpcNodeCore, + EthApiTypes, RpcConvert, RpcNodeCore, }; use reth_rpc_eth_types::{error::FromEvmError, PendingBlock}; use reth_scroll_evm::ScrollNextBlockEnvAttributes; @@ -29,6 +29,7 @@ where Header = alloy_rpc_types_eth::Header>, >, Error: FromEvmError, + RpcConvert: RpcConvert, >, N: RpcNodeCore< Provider: BlockReaderIdExt< diff --git a/crates/scroll/rpc/src/eth/transaction.rs b/crates/scroll/rpc/src/eth/transaction.rs index d044b1c7d33..3eaac929e22 100644 --- a/crates/scroll/rpc/src/eth/transaction.rs +++ b/crates/scroll/rpc/src/eth/transaction.rs @@ -11,10 +11,10 @@ use reth_node_api::FullNodeComponents; use reth_provider::{ BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider, TransactionsProvider, }; +use reth_rpc_convert::try_into_scroll_tx_info; use reth_rpc_eth_api::{ helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking}, - try_into_scroll_tx_info, FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, - TxInfoMapper, + FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TxInfoMapper, }; use reth_rpc_eth_types::utils::recover_raw_transaction; use reth_scroll_primitives::ScrollReceipt; diff --git a/crates/stages/stages/benches/criterion.rs b/crates/stages/stages/benches/criterion.rs index 33e936b01a4..d755f2fdd04 100644 --- a/crates/stages/stages/benches/criterion.rs +++ b/crates/stages/stages/benches/criterion.rs @@ -114,7 +114,10 @@ fn merkle(c: &mut Criterion, runtime: &Runtime) { let db = setup::txs_testdata(DEFAULT_NUM_BLOCKS); - let stage = MerkleStage::::Both { rebuild_threshold: u64::MAX, incremental_threshold: u64::MAX }; + let stage = MerkleStage::::Both { + rebuild_threshold: u64::MAX, + incremental_threshold: u64::MAX, + }; measure_stage( runtime, &mut group, @@ -125,7 +128,8 @@ fn merkle(c: &mut Criterion, runtime: &Runtime) { "Merkle-incremental".to_string(), ); - let stage = MerkleStage::::Both { rebuild_threshold: 0, incremental_threshold: 0 }; + let stage = + MerkleStage::::Both { rebuild_threshold: 0, incremental_threshold: 0 }; measure_stage( runtime, &mut group, diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index d9a22ada06b..7d5dd69d2bd 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -117,17 +117,20 @@ where pub const fn default_execution_with_consensus( consensus: Arc>, ) -> Self { - Self::Execution { rebuild_threshold: MERKLE_STAGE_DEFAULT_CLEAN_THRESHOLD, + Self::Execution { + rebuild_threshold: MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, incremental_threshold: MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD, -, consensus } + consensus, + } } /// Create new instance of [`MerkleStage::Execution`]. pub const fn new_execution( - rebuild_threshold: u64, incremental_threshold: u64, + rebuild_threshold: u64, + incremental_threshold: u64, consensus: Arc>, ) -> Self { - Self::Execution { rebuild_threshold,incremental_threshold, consensus } + Self::Execution { rebuild_threshold, incremental_threshold, consensus } } /// Create new instance of [`MerkleStage::Unwind`]. @@ -208,7 +211,9 @@ where info!(target: "sync::stages::merkle::unwind", "Stage is always skipped"); return Ok(ExecOutput::done(StageCheckpoint::new(input.target()))); } - Self::Execution { rebuild_threshold, incremental_threshold, .. } => (*rebuild_threshold, *incremental_threshold), + Self::Execution { rebuild_threshold, incremental_threshold, .. } => { + (*rebuild_threshold, *incremental_threshold) + } #[cfg(any(test, feature = "test-utils"))] Self::Both { rebuild_threshold, incremental_threshold } => { (*rebuild_threshold, *incremental_threshold) diff --git a/examples/custom-node/Cargo.toml b/examples/custom-node/Cargo.toml index f43f2eb1c43..dd76b1321aa 100644 --- a/examples/custom-node/Cargo.toml +++ b/examples/custom-node/Cargo.toml @@ -15,6 +15,7 @@ reth-optimism-forks.workspace = true reth-db-api.workspace = true reth-op = { workspace = true, features = ["node", "pool"] } reth-payload-builder.workspace = true +reth-primitives-traits.workspace = true reth-rpc-api.workspace = true reth-rpc-engine-api.workspace = true reth-ethereum = { workspace = true, features = ["node-api", "network", "evm", "pool", "trie", "storage-api"] } diff --git a/examples/custom-node/src/primitives/header.rs b/examples/custom-node/src/primitives/header.rs index 0a832d690c9..acf80bb26e5 100644 --- a/examples/custom-node/src/primitives/header.rs +++ b/examples/custom-node/src/primitives/header.rs @@ -181,6 +181,12 @@ impl reth_db_api::table::Decompress for CustomHeader { } } +impl reth_primitives_traits::block::header::BlockHeaderMut for CustomHeader { + fn extra_data_mut(&mut self) -> &mut Bytes { + &mut self.inner.extra_data + } +} + impl BlockHeader for CustomHeader {} impl RlpBincode for CustomHeader {} From 9c23abbef0ac255aa77bc8900b95c92302ae0dfe Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 9 Jul 2025 02:01:09 +0100 Subject: [PATCH 271/274] lint --- Cargo.lock | 1 - .../rpc-eth-api/src/helpers/transaction.rs | 2 +- crates/scroll/cli/Cargo.toml | 1 - crates/scroll/openvm-compat/Cargo.lock | 216 ++++++++++-------- examples/custom-node/Cargo.toml | 1 + 5 files changed, 119 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11d5c2cffb1..aa682bd2af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10275,7 +10275,6 @@ dependencies = [ "reth-node-metrics", "reth-scroll-chainspec", "reth-scroll-evm", - "reth-scroll-node", "reth-scroll-primitives", "reth-tracing", "scroll-alloy-consensus", diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index c7849011d25..c0c759d400d 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -489,7 +489,7 @@ pub trait EthTransactions: LoadTransaction { fn find_signer( &self, account: &Address, - ) -> Result> + 'static)>, Self::Error> { + ) -> Result> + 'static>, Self::Error> { self.signers() .read() .iter() diff --git a/crates/scroll/cli/Cargo.toml b/crates/scroll/cli/Cargo.toml index b5c79e24940..b603704de06 100644 --- a/crates/scroll/cli/Cargo.toml +++ b/crates/scroll/cli/Cargo.toml @@ -26,7 +26,6 @@ reth-tracing.workspace = true # scroll reth-scroll-chainspec.workspace = true reth-scroll-evm.workspace = true -reth-scroll-node.workspace = true reth-scroll-primitives.workspace = true scroll-alloy-consensus = { workspace = true, optional = true } diff --git a/crates/scroll/openvm-compat/Cargo.lock b/crates/scroll/openvm-compat/Cargo.lock index 64cee04aeec..c5267e11ad0 100644 --- a/crates/scroll/openvm-compat/Cargo.lock +++ b/crates/scroll/openvm-compat/Cargo.lock @@ -22,9 +22,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9cc9d81ace3da457883b0bdf76776e55f1b84219a9e9d55c27ad308548d3f" +checksum = "5674914c2cfdb866c21cb0c09d82374ee39a1395cf512e7515f4c014083b3fff" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -35,9 +35,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bcb57295c4b632b6b3941a089ee82d00ff31ff9eb3eac801bf605ffddc81041" +checksum = "74a694d8be621ee12b45ae23e7f18393b9a1e04f1ba47a0136767cb8c955f7f8" dependencies = [ "alloy-eips", "alloy-primitives", @@ -60,9 +60,9 @@ dependencies = [ [[package]] name = "alloy-consensus-any" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab669be40024565acb719daf1b2a050e6dc065fc0bec6050d97a81cdb860bd7" +checksum = "1647d47f59288584cc3b40eff3e7dde6af8c88a2fca8fe02c22de7b9ab218ffa" dependencies = [ "alloy-consensus", "alloy-eips", @@ -112,9 +112,9 @@ dependencies = [ [[package]] name = "alloy-eips" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f853de9ca1819f54de80de5d03bfc1bb7c9fafcf092b480a654447141bc354d" +checksum = "715ae25d525c567481ba2fc97000415624836d516958b9c3f189f1e267d1d90a" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -132,9 +132,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.10.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "394b09cf3a32773eedf11828987f9c72dfa74545040be0422e3f5f09a2a3fab9" +checksum = "ff5aae4c6dc600734b206b175f3200085ee82dcdaa388760358830a984ca9869" dependencies = [ "alloy-consensus", "alloy-eips", @@ -149,22 +149,23 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8500bcc1037901953771c25cb77e0d4ec0bffd938d93a04715390230d21a612d" +checksum = "696a83af273bfc512e02693bd4b5056c8c57898328bd0ce594013fb864de4dcf" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-serde", "alloy-trie", "serde", + "serde_with", ] [[package]] name = "alloy-hardforks" -version = "0.2.7" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977d2492ce210e34baf7b36afaacea272c96fbe6774c47e23f97d14033c0e94f" +checksum = "819a3620fe125e0fff365363315ee5e24c23169173b19747dfd6deba33db8990" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -175,9 +176,9 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eef189583f4c53d231dd1297b28a675ff842b551fb34715f562868a1937431a" +checksum = "35648c318b4649d2d141d1ed4f6e32c69f4959bdc2f6e44d53c0a333ed615a37" dependencies = [ "alloy-consensus", "alloy-eips", @@ -199,7 +200,7 @@ dependencies = [ "derive_more", "foldhash", "hashbrown 0.15.4", - "indexmap 2.9.0", + "indexmap 2.10.0", "itoa", "k256", "keccak-asm", @@ -237,9 +238,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5b09d86d0c015cb8400c5d1d0483425670bef4fc1260336aea9ef6d4b9540c" +checksum = "3ed717902ec7e7e5b737cf416f29c21f43a4e86db90ff6fddde199f4ed6ea1ac" dependencies = [ "alloy-consensus", "alloy-eips", @@ -251,9 +252,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1826285e4ffc2372a8c061d5cc145858e67a0be3309b768c5b77ddb6b9e6cbc7" +checksum = "c8300d59b0126876a1914102c588f9a4792eb4c754d483a954dc29904ddf79d6" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -271,9 +272,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "906ce0190afeded19cb2e963cb8507c975a7862216b9e74f39bf91ddee6ae74b" +checksum = "8070bc2af2d48969e3aa709ea3ebf1f8316176b91c2132efe33d113f74383a9e" dependencies = [ "alloy-primitives", "serde", @@ -303,7 +304,7 @@ dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.9.0", + "indexmap 2.10.0", "proc-macro-error2", "proc-macro2", "quote", @@ -340,9 +341,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983d99aa81f586cef9dae38443245e585840fcf0fc58b09aee0b1f27aed1d500" +checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -356,9 +357,9 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.12" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75ef8609ea2b31c799b0a56c724dca4c73105c5ccc205d9dfeb1d038df6a1da" +checksum = "472e12600c46b766110edd8382b4804d70188870f064531ee8fd61a35ed18686" dependencies = [ "alloy-primitives", "darling", @@ -820,9 +821,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.27" +version = "1.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" dependencies = [ "jobserver", "libc", @@ -1584,9 +1585,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -1866,13 +1867,14 @@ dependencies = [ [[package]] name = "nybbles" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" +checksum = "675b3a54e5b12af997abc8b6638b0aee51a28caedab70d4967e0d5db3a3f1d06" dependencies = [ "alloy-rlp", - "const-hex", + "cfg-if", "proptest", + "ruint", "serde", "smallvec", ] @@ -1889,9 +1891,9 @@ dependencies = [ [[package]] name = "op-alloy-consensus" -version = "0.17.2" +version = "0.18.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2423a125ef2daa0d15dacc361805a0b6f76d6acfc6e24a1ff6473582087fe75" +checksum = "a8719d9b783b29cfa1cf8d591b894805786b9ab4940adc700a57fd0d5b721cf5" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2340,7 +2342,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reth-chainspec" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -2359,7 +2361,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2376,7 +2378,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.4.8" +version = "1.5.0" dependencies = [ "convert_case", "proc-macro2", @@ -2386,7 +2388,7 @@ dependencies = [ [[package]] name = "reth-db-models" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -2395,7 +2397,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -2406,7 +2408,7 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2420,7 +2422,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2441,7 +2443,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2458,7 +2460,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-evm", "alloy-primitives", @@ -2470,7 +2472,7 @@ dependencies = [ [[package]] name = "reth-execution-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2485,7 +2487,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -2496,7 +2498,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "once_cell", @@ -2508,13 +2510,14 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-genesis", "alloy-primitives", "alloy-rlp", + "alloy-rpc-types-eth", "alloy-trie", "auto_impl", "bytes", @@ -2534,7 +2537,7 @@ dependencies = [ [[package]] name = "reth-prune-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "derive_more", @@ -2543,7 +2546,7 @@ dependencies = [ [[package]] name = "reth-scroll-chainspec" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-consensus", @@ -2566,7 +2569,7 @@ dependencies = [ [[package]] name = "reth-scroll-evm" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2593,7 +2596,7 @@ dependencies = [ [[package]] name = "reth-scroll-forks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-chains", "alloy-primitives", @@ -2605,7 +2608,7 @@ dependencies = [ [[package]] name = "reth-scroll-primitives" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2621,7 +2624,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "reth-trie-common", @@ -2629,7 +2632,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "derive_more", @@ -2639,7 +2642,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2660,7 +2663,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-eips", "alloy-primitives", @@ -2675,7 +2678,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -2696,7 +2699,7 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -2711,7 +2714,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -2726,15 +2729,15 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "1.4.8" +version = "1.5.0" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "24.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "26.0.1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "revm-bytecode", "revm-context", @@ -2751,8 +2754,8 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "5.0.0" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "bitvec", "once_cell", @@ -2763,8 +2766,8 @@ dependencies = [ [[package]] name = "revm-context" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "7.0.1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "cfg-if", "derive-where", @@ -2777,8 +2780,8 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "7.0.1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -2791,8 +2794,8 @@ dependencies = [ [[package]] name = "revm-database" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "6.0.0" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "revm-bytecode", "revm-database-interface", @@ -2802,8 +2805,8 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "6.0.0" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", "revm-primitives", @@ -2812,10 +2815,11 @@ dependencies = [ [[package]] name = "revm-handler" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "7.0.1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", + "derive-where", "revm-bytecode", "revm-context", "revm-context-interface", @@ -2828,10 +2832,11 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "5.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "7.0.1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "auto_impl", + "either", "revm-context", "revm-database-interface", "revm-handler", @@ -2842,8 +2847,8 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "20.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "22.0.1" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -2852,8 +2857,8 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "21.0.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "23.0.0" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -2872,8 +2877,8 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "19.1.0" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "20.0.0" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "alloy-primitives", "num_enum", @@ -2883,7 +2888,7 @@ dependencies = [ [[package]] name = "revm-scroll" version = "0.1.0" -source = "git+https://github.com/scroll-tech/scroll-revm#0195a04190cef78901ed67c7bc3048114034f366" +source = "git+https://github.com/scroll-tech/scroll-revm?branch=feat%2Fv78#c0609bc9e8cb23aba8f560a82e040a49726cf760" dependencies = [ "auto_impl", "enumn", @@ -2895,8 +2900,8 @@ dependencies = [ [[package]] name = "revm-state" -version = "4.0.1" -source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v74#774616019e9562b12cbe1c3f1cdd110793f8084c" +version = "6.0.0" +source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ "bitflags", "revm-bytecode", @@ -3045,9 +3050,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scroll-alloy-consensus" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -3062,7 +3079,7 @@ dependencies = [ [[package]] name = "scroll-alloy-evm" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -3077,7 +3094,7 @@ dependencies = [ [[package]] name = "scroll-alloy-hardforks" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-hardforks", "auto_impl", @@ -3085,7 +3102,7 @@ dependencies = [ [[package]] name = "scroll-alloy-rpc-types" -version = "1.4.8" +version = "1.5.0" dependencies = [ "alloy-consensus", "alloy-eips", @@ -3193,16 +3210,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf65a400f8f66fb7b0552869ad70157166676db75ed8181f8104ea91cf9d0b42" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", - "schemars", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.4", "serde", "serde_derive", "serde_json", @@ -3212,9 +3230,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81679d9ed988d5e9a5e6531dc3f2c28efbd639cbd1dfb628df08edea6004da77" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", @@ -3518,7 +3536,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "toml_datetime", "winnow", ] diff --git a/examples/custom-node/Cargo.toml b/examples/custom-node/Cargo.toml index dd76b1321aa..d190fef9f85 100644 --- a/examples/custom-node/Cargo.toml +++ b/examples/custom-node/Cargo.toml @@ -65,5 +65,6 @@ arbitrary = [ "reth-ethereum/arbitrary", "alloy-rpc-types-engine/arbitrary", "reth-db-api/arbitrary", + "reth-primitives-traits/arbitrary", ] default = [] From cce36a6c27f7c695e1616be45d2cb98d0e20c493 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 9 Jul 2025 11:50:59 +0100 Subject: [PATCH 272/274] lint --- crates/scroll/cli/Cargo.toml | 2 +- crates/scroll/evm/src/execute.rs | 14 +++++++++++++- crates/scroll/primitives/Cargo.toml | 1 - 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/crates/scroll/cli/Cargo.toml b/crates/scroll/cli/Cargo.toml index b603704de06..88e102d194e 100644 --- a/crates/scroll/cli/Cargo.toml +++ b/crates/scroll/cli/Cargo.toml @@ -26,7 +26,7 @@ reth-tracing.workspace = true # scroll reth-scroll-chainspec.workspace = true reth-scroll-evm.workspace = true -reth-scroll-primitives.workspace = true +reth-scroll-primitives = { workspace = true, features = ["reth-codec"] } scroll-alloy-consensus = { workspace = true, optional = true } # misc diff --git a/crates/scroll/evm/src/execute.rs b/crates/scroll/evm/src/execute.rs index 939e4b1c892..caea62f79a4 100644 --- a/crates/scroll/evm/src/execute.rs +++ b/crates/scroll/evm/src/execute.rs @@ -404,8 +404,20 @@ mod tests { // assert oracle contract contains updated bytecode let oracle = bundle.state.get(&L1_GAS_PRICE_ORACLE_ADDRESS).unwrap().clone(); + let oracle_bytecode = oracle.info.unwrap().code.unwrap(); let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); - assert_eq!(oracle.info.unwrap().code.unwrap(), bytecode); + + // Note: Eq operator fails due to the presence of `table_ptr` in the `JumpTable` struct + // therefore we do a manual comparison. + assert_eq!( + bytecode.legacy_jump_table().unwrap().len, + oracle_bytecode.legacy_jump_table().unwrap().len + ); + assert_eq!( + bytecode.legacy_jump_table().unwrap().table, + oracle_bytecode.legacy_jump_table().unwrap().table + ); + assert_eq!(bytecode.bytecode(), oracle_bytecode.bytecode()); // check oracle contract contains storage changeset let mut storage = oracle.storage.into_iter().collect::>(); diff --git a/crates/scroll/primitives/Cargo.toml b/crates/scroll/primitives/Cargo.toml index d9ee3c03a12..be21143bb6a 100644 --- a/crates/scroll/primitives/Cargo.toml +++ b/crates/scroll/primitives/Cargo.toml @@ -60,7 +60,6 @@ std = [ reth-codec = [ "dep:reth-codecs", "std", - "dep:arbitrary", "reth-primitives-traits/reth-codec", "scroll-alloy-consensus/reth-codec", "dep:bytes", From 6cb41f516b1ef0108899f355f3d0daf71f0e73ee Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 9 Jul 2025 12:45:03 +0100 Subject: [PATCH 273/274] lint --- crates/scroll/alloy/evm/Cargo.toml | 1 + crates/scroll/alloy/evm/src/block/curie.rs | 24 ++++++++++++++++---- crates/scroll/alloy/evm/src/block/feynman.rs | 24 ++++++++++++++++---- crates/scroll/alloy/evm/src/block/mod.rs | 12 ++++++++++ crates/scroll/evm/src/execute.rs | 1 + 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/crates/scroll/alloy/evm/Cargo.toml b/crates/scroll/alloy/evm/Cargo.toml index 22b2ebd3a06..a62d0dbaaf3 100644 --- a/crates/scroll/alloy/evm/Cargo.toml +++ b/crates/scroll/alloy/evm/Cargo.toml @@ -42,6 +42,7 @@ reth-scroll-chainspec.workspace = true reth-scroll-evm.workspace = true [features] +default = ["std"] std = [ "alloy-evm/std", "alloy-primitives/std", diff --git a/crates/scroll/alloy/evm/src/block/curie.rs b/crates/scroll/alloy/evm/src/block/curie.rs index 61d873bac53..7e2e853ad42 100644 --- a/crates/scroll/alloy/evm/src/block/curie.rs +++ b/crates/scroll/alloy/evm/src/block/curie.rs @@ -102,7 +102,7 @@ pub(super) fn apply_curie_hard_fork(state: &mut State) -> Resu #[cfg(test)] mod tests { - use super::*; + use super::{super::assert_bytecode_eq, *}; use revm::{ database::{ states::{bundle_state::BundleRetention, plain_account::PlainStorage, StorageSlot}, @@ -155,8 +155,24 @@ mod tests { let expected_oracle_info = AccountInfo { code_hash, code: Some(bytecode.clone()), ..Default::default() }; - assert_eq!(oracle.original_info.unwrap(), oracle_pre_fork); - assert_eq!(oracle.info.unwrap(), expected_oracle_info); + // TODO: revert back to performing equality check on `AccountInfo` once we bump revm > v78 + let oracle_original_info = oracle.original_info.unwrap(); + assert_bytecode_eq( + oracle_original_info.code.as_ref().unwrap(), + oracle_pre_fork.code.as_ref().unwrap(), + ); + assert_eq!(oracle_original_info.balance, oracle_pre_fork.balance); + assert_eq!(oracle_original_info.nonce, oracle_pre_fork.nonce); + assert_eq!(oracle_original_info.code_hash, oracle_pre_fork.code_hash); + + let oracle_post_info = oracle.info.unwrap(); + assert_bytecode_eq( + oracle_post_info.code.as_ref().unwrap(), + expected_oracle_info.code.as_ref().unwrap(), + ); + assert_eq!(oracle_post_info.balance, expected_oracle_info.balance); + assert_eq!(oracle_post_info.nonce, expected_oracle_info.nonce); + assert_eq!(oracle_post_info.code_hash, expected_oracle_info.code_hash); // check oracle storage changeset let mut storage = oracle.storage.into_iter().collect::>(); @@ -172,7 +188,7 @@ mod tests { } // check deployed contract - assert_eq!(bundle.contracts.get(&code_hash).unwrap().clone(), bytecode); + assert_bytecode_eq(bundle.contracts.get(&code_hash).unwrap(), &bytecode); Ok(()) } diff --git a/crates/scroll/alloy/evm/src/block/feynman.rs b/crates/scroll/alloy/evm/src/block/feynman.rs index 4f4467507bb..e815f87cc5f 100644 --- a/crates/scroll/alloy/evm/src/block/feynman.rs +++ b/crates/scroll/alloy/evm/src/block/feynman.rs @@ -90,7 +90,7 @@ pub(super) fn apply_feynman_hard_fork( #[cfg(test)] mod tests { - use super::*; + use super::{super::assert_bytecode_eq, *}; use revm::{ database::{ states::{bundle_state::BundleRetention, plain_account::PlainStorage, StorageSlot}, @@ -158,8 +158,24 @@ mod tests { let expected_oracle_info = AccountInfo { code_hash, code: Some(bytecode.clone()), ..Default::default() }; - assert_eq!(oracle.original_info.unwrap(), oracle_pre_fork); - assert_eq!(oracle.info.unwrap(), expected_oracle_info); + // TODO: revert back to performing equality check on `AccountInfo` once we bump revm > v78 + let oracle_original_info = oracle.original_info.unwrap(); + assert_bytecode_eq( + oracle_original_info.code.as_ref().unwrap(), + oracle_pre_fork.code.as_ref().unwrap(), + ); + assert_eq!(oracle_original_info.balance, oracle_pre_fork.balance); + assert_eq!(oracle_original_info.nonce, oracle_pre_fork.nonce); + assert_eq!(oracle_original_info.code_hash, oracle_pre_fork.code_hash); + + let oracle_post_info = oracle.info.unwrap(); + assert_bytecode_eq( + oracle_post_info.code.as_ref().unwrap(), + expected_oracle_info.code.as_ref().unwrap(), + ); + assert_eq!(oracle_post_info.balance, expected_oracle_info.balance); + assert_eq!(oracle_post_info.nonce, expected_oracle_info.nonce); + assert_eq!(oracle_post_info.code_hash, expected_oracle_info.code_hash); // check oracle storage changeset let mut storage = oracle.storage.into_iter().collect::>(); @@ -175,7 +191,7 @@ mod tests { } // check deployed contract - assert_eq!(bundle.contracts.get(&code_hash).unwrap().clone(), bytecode); + assert_bytecode_eq(bundle.contracts.get(&code_hash).unwrap(), &bytecode); Ok(()) } diff --git a/crates/scroll/alloy/evm/src/block/mod.rs b/crates/scroll/alloy/evm/src/block/mod.rs index ec613d5cd72..054b5a5b96d 100644 --- a/crates/scroll/alloy/evm/src/block/mod.rs +++ b/crates/scroll/alloy/evm/src/block/mod.rs @@ -403,3 +403,15 @@ where ScrollBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) } } + +// TODO: remove this when we bump revm > v78 +/// A helper function that compares asserts that two bytecode instances are equal. +#[cfg(test)] +fn assert_bytecode_eq(expected: &revm::bytecode::Bytecode, actual: &revm::bytecode::Bytecode) { + assert_eq!(expected.legacy_jump_table().unwrap().len, actual.legacy_jump_table().unwrap().len); + assert_eq!( + expected.legacy_jump_table().unwrap().table, + actual.legacy_jump_table().unwrap().table + ); + assert_eq!(expected.bytecode(), actual.bytecode()); +} diff --git a/crates/scroll/evm/src/execute.rs b/crates/scroll/evm/src/execute.rs index caea62f79a4..8648e3d8453 100644 --- a/crates/scroll/evm/src/execute.rs +++ b/crates/scroll/evm/src/execute.rs @@ -407,6 +407,7 @@ mod tests { let oracle_bytecode = oracle.info.unwrap().code.unwrap(); let bytecode = Bytecode::new_raw(CURIE_L1_GAS_PRICE_ORACLE_BYTECODE); + // TODO: update when we bump to revm > v78 // Note: Eq operator fails due to the presence of `table_ptr` in the `JumpTable` struct // therefore we do a manual comparison. assert_eq!( From d2708c3b0d31847283b37bc87a0c406a3111e4a9 Mon Sep 17 00:00:00 2001 From: frisitano Date: Wed, 9 Jul 2025 13:34:53 +0100 Subject: [PATCH 274/274] lint --- crates/scroll/evm/Cargo.toml | 1 - crates/scroll/openvm-compat/Cargo.lock | 243 ++++++++++++++++++++++++- 2 files changed, 235 insertions(+), 9 deletions(-) diff --git a/crates/scroll/evm/Cargo.toml b/crates/scroll/evm/Cargo.toml index e7f5d9c840b..450681ed57c 100644 --- a/crates/scroll/evm/Cargo.toml +++ b/crates/scroll/evm/Cargo.toml @@ -50,7 +50,6 @@ eyre.workspace = true alloy-primitives = { workspace = true, features = ["getrandom"] } [features] -default = ["std"] std = [ "scroll-alloy-consensus/std", "scroll-alloy-evm/std", diff --git a/crates/scroll/openvm-compat/Cargo.lock b/crates/scroll/openvm-compat/Cargo.lock index c5267e11ad0..2c1d1073a97 100644 --- a/crates/scroll/openvm-compat/Cargo.lock +++ b/crates/scroll/openvm-compat/Cargo.lock @@ -52,7 +52,7 @@ dependencies = [ "k256", "once_cell", "rand 0.8.5", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_with", "thiserror", @@ -127,7 +127,7 @@ dependencies = [ "derive_more", "either", "serde", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -140,9 +140,12 @@ dependencies = [ "alloy-eips", "alloy-hardforks", "alloy-primitives", + "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", "derive_more", + "op-alloy-consensus", + "op-revm", "revm", "thiserror", ] @@ -172,6 +175,19 @@ dependencies = [ "alloy-primitives", "auto_impl", "dyn-clone", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15516116086325c157c18261d768a20677f0f699348000ed391d4ad0dcb82530" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", ] [[package]] @@ -329,14 +345,26 @@ dependencies = [ "syn-solidity", ] +[[package]] +name = "alloy-sol-type-parser" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10db1bd7baa35bc8d4a1b07efbf734e73e5ba09f2580fb8cee3483a36087ceb2" +dependencies = [ + "serde", + "winnow", +] + [[package]] name = "alloy-sol-types" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58377025a47d8b8426b3e4846a251f2c1991033b27f517aade368146f6ab1dfe" dependencies = [ + "alloy-json-abi", "alloy-primitives", "alloy-sol-macro", + "serde", ] [[package]] @@ -403,6 +431,7 @@ checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" dependencies = [ "ark-ec", "ark-ff 0.5.0", + "ark-r1cs-std", "ark-std 0.5.0", ] @@ -568,6 +597,35 @@ dependencies = [ "hashbrown 0.15.4", ] +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber", +] + [[package]] name = "ark-serialize" version = "0.3.0" @@ -643,6 +701,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.6" @@ -679,6 +743,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "base16ct" version = "0.2.0" @@ -750,6 +820,15 @@ dependencies = [ "wyz", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1075,7 +1154,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -1348,6 +1427,16 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "group" version = "0.13.0" @@ -1658,7 +1747,7 @@ dependencies = [ "elliptic-curve", "once_cell", "serdect", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1698,6 +1787,52 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -1860,6 +1995,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.104", @@ -1906,6 +2042,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "op-revm" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b97d2b54651fcd2955b454e86b2336c031e17925a127f4c44e2b63b2eeda923" +dependencies = [ + "auto_impl", + "once_cell", + "revm", + "serde", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openvm" version = "1.2.0" @@ -1981,7 +2135,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2529,7 +2683,7 @@ dependencies = [ "revm-primitives", "revm-state", "scroll-alloy-consensus", - "secp256k1", + "secp256k1 0.30.0", "serde", "serde_with", "thiserror", @@ -2776,6 +2930,7 @@ dependencies = [ "revm-database-interface", "revm-primitives", "revm-state", + "serde", ] [[package]] @@ -2790,6 +2945,7 @@ dependencies = [ "revm-database-interface", "revm-primitives", "revm-state", + "serde", ] [[package]] @@ -2797,10 +2953,12 @@ name = "revm-database" version = "6.0.0" source = "git+https://github.com/scroll-tech/revm?branch=feat%2Freth-v78#64e018f80e65d79505591aacec4f35ec46bca5ff" dependencies = [ + "alloy-eips", "revm-bytecode", "revm-database-interface", "revm-primitives", "revm-state", + "serde", ] [[package]] @@ -2811,6 +2969,7 @@ dependencies = [ "auto_impl", "revm-primitives", "revm-state", + "serde", ] [[package]] @@ -2828,6 +2987,7 @@ dependencies = [ "revm-precompile", "revm-primitives", "revm-state", + "serde", ] [[package]] @@ -2843,6 +3003,8 @@ dependencies = [ "revm-interpreter", "revm-primitives", "revm-state", + "serde", + "serde_json", ] [[package]] @@ -2853,6 +3015,7 @@ dependencies = [ "revm-bytecode", "revm-context-interface", "revm-primitives", + "serde", ] [[package]] @@ -2866,13 +3029,17 @@ dependencies = [ "ark-ff 0.5.0", "ark-serialize 0.5.0", "aurora-engine-modexp", + "c-kzg", "cfg-if", "k256", + "libsecp256k1", "once_cell", "p256", "revm-primitives", "ripemd", - "sha2", + "rug", + "secp256k1 0.31.1", + "sha2 0.10.9", ] [[package]] @@ -2896,6 +3063,7 @@ dependencies = [ "revm", "revm-inspector", "revm-primitives", + "serde", ] [[package]] @@ -2938,6 +3106,18 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rug" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "ruint" version = "1.15.0" @@ -3090,6 +3270,8 @@ dependencies = [ "revm-scroll", "scroll-alloy-consensus", "scroll-alloy-hardforks", + "serde", + "zstd", ] [[package]] @@ -3098,6 +3280,7 @@ version = "1.5.0" dependencies = [ "alloy-hardforks", "auto_impl", + "serde", ] [[package]] @@ -3139,10 +3322,21 @@ checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.10.1", "serde", ] +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.1", + "secp256k1-sys 0.11.0", +] + [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -3152,6 +3346,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "0.11.0" @@ -3202,6 +3405,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ + "indexmap 2.10.0", "itoa", "memchr", "ryu", @@ -3250,6 +3454,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3570,6 +3787,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", ] [[package]]

    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