From 7a65d2595d7fb013ce7de52f7ec3cbc7a8ad8e91 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jan 2026 11:35:11 +0100 Subject: [PATCH 01/36] feat(engine-api): add BAL cache for EIP-7928 Introduces an in-memory LRU cache for Block Access Lists (BALs) in the Engine API. BALs are cached when payloads are validated as VALID via newPayload. - Add BalCache with internal Arc for cheap cloning - Store BALs keyed by block hash with block number index for range queries - Implement engine_getBALsByHashV1 and engine_getBALsByRangeV1 - Add metrics for cache inserts/hits/misses Per EIP-7928, the EL should retain BALs for the weak subjectivity period (~3533 epochs). This initial implementation uses a configurable LRU cache (default 1024 entries) as a starting point. --- Cargo.lock | 2 + crates/rpc/rpc-engine-api/Cargo.toml | 2 + crates/rpc/rpc-engine-api/src/bal_cache.rs | 217 ++++++++++++++++++++ crates/rpc/rpc-engine-api/src/engine_api.rs | 105 ++++++++-- crates/rpc/rpc-engine-api/src/lib.rs | 4 + 5 files changed, 315 insertions(+), 15 deletions(-) create mode 100644 crates/rpc/rpc-engine-api/src/bal_cache.rs diff --git a/Cargo.lock b/Cargo.lock index 31ad77a44bb..ae73103ac24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10625,6 +10625,7 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", + "parking_lot", "reth-chainspec", "reth-engine-primitives", "reth-ethereum-engine-primitives", @@ -10642,6 +10643,7 @@ dependencies = [ "reth-tasks", "reth-testing-utils", "reth-transaction-pool", + "schnellru", "serde", "thiserror 2.0.18", "tokio", diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 2702a404196..731e6fd4229 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -41,6 +41,8 @@ metrics.workspace = true async-trait.workspace = true jsonrpsee-core.workspace = true jsonrpsee-types.workspace = true +parking_lot.workspace = true +schnellru.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs new file mode 100644 index 00000000000..2820e9ac31a --- /dev/null +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -0,0 +1,217 @@ +//! Block Access List (BAL) cache for EIP-7928. +//! +//! This module provides an in-memory cache for storing Block Access Lists received via +//! the Engine API. BALs are stored for valid payloads and can be retrieved via +//! `engine_getBALsByHashV1` and `engine_getBALsByRangeV1`. +//! +//! According to EIP-7928, the EL MUST retain BALs for at least the duration of the +//! weak subjectivity period (~3533 epochs) to support synchronization with re-execution. +//! This initial implementation uses a simple in-memory LRU cache with configurable capacity. + +use alloy_primitives::{BlockHash, BlockNumber, Bytes}; +use parking_lot::RwLock; +use reth_metrics::{ + metrics::{Counter, Gauge}, + Metrics, +}; +use schnellru::{ByLength, LruMap}; +use std::{collections::BTreeMap, sync::Arc}; + +/// Default capacity for the BAL cache. +/// +/// This is a conservative default - production deployments should configure based on +/// weak subjectivity period requirements (~3533 epochs ≈ 113,000 blocks). +const DEFAULT_BAL_CACHE_CAPACITY: u32 = 1024; + +/// Cache entry storing a BAL with its block number for range queries. +#[derive(Debug, Clone)] +struct BalEntry { + /// The block number for range-based lookups. + block_number: BlockNumber, + /// The RLP-encoded block access list. + bal: Bytes, +} + +/// In-memory cache for Block Access Lists (BALs). +/// +/// Provides O(1) lookups by block hash and O(log n) range queries by block number. +/// Uses an LRU eviction policy when the cache exceeds capacity. +/// +/// This type is cheaply cloneable as it wraps an `Arc` internally. +#[derive(Debug, Clone)] +pub struct BalCache { + inner: Arc, +} + +#[derive(Debug)] +struct BalCacheInner { + /// LRU cache mapping block hash to BAL entry. + entries: RwLock>, + /// Index mapping block number to block hash for range queries. + /// Uses BTreeMap for efficient range iteration. + block_index: RwLock>, + /// Cache metrics. + metrics: BalCacheMetrics, +} + +impl BalCache { + /// Creates a new BAL cache with the default capacity. + pub fn new() -> Self { + Self::with_capacity(DEFAULT_BAL_CACHE_CAPACITY) + } + + /// Creates a new BAL cache with the specified capacity. + pub fn with_capacity(capacity: u32) -> Self { + Self { + inner: Arc::new(BalCacheInner { + entries: RwLock::new(LruMap::new(ByLength::new(capacity))), + block_index: RwLock::new(BTreeMap::new()), + metrics: BalCacheMetrics::default(), + }), + } + } + + /// Inserts a BAL into the cache. + /// + /// If the cache is at capacity, the least recently used entry will be evicted. + pub fn insert(&self, block_hash: BlockHash, block_number: BlockNumber, bal: Bytes) { + let entry = BalEntry { block_number, bal }; + + let mut entries = self.inner.entries.write(); + let mut block_index = self.inner.block_index.write(); + + // Check if we need to evict an old entry + if entries.len() as u32 >= entries.limiter().max_length() { + // Find and remove the oldest entry from block_index + if let Some((_, evicted_entry)) = entries.iter().next_back() { + block_index.remove(&evicted_entry.block_number); + } + } + + entries.insert(block_hash, entry); + block_index.insert(block_number, block_hash); + + self.inner.metrics.inserts.increment(1); + self.inner.metrics.count.set(entries.len() as f64); + } + + /// Retrieves BALs for the given block hashes. + /// + /// Returns a vector with the same length as `block_hashes`, where each element + /// is `Some(bal)` if found or `None` if not in cache. + pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { + let mut entries = self.inner.entries.write(); + block_hashes + .iter() + .map(|hash| { + let result = entries.get(hash).map(|e| e.bal.clone()); + if result.is_some() { + self.inner.metrics.hits.increment(1); + } else { + self.inner.metrics.misses.increment(1); + } + result + }) + .collect() + } + + /// Retrieves BALs for a range of blocks starting at `start` for `count` blocks. + /// + /// Returns a vector of BALs in block number order. Missing blocks are not included. + pub fn get_by_range(&self, start: BlockNumber, count: u64) -> Vec { + let entries = self.inner.entries.read(); + let block_index = self.inner.block_index.read(); + + let end = start.saturating_add(count); + block_index + .range(start..end) + .filter_map(|(_, hash)| entries.peek(hash).map(|e| e.bal.clone())) + .collect() + } + + /// Returns the number of entries in the cache. + #[cfg(test)] + pub fn len(&self) -> usize { + self.inner.entries.read().len() + } +} + +impl Default for BalCache { + fn default() -> Self { + Self::new() + } +} + +/// Metrics for the BAL cache. +#[derive(Metrics)] +#[metrics(scope = "engine.bal_cache")] +struct BalCacheMetrics { + /// The total number of BALs in the cache. + count: Gauge, + /// The number of cache inserts. + inserts: Counter, + /// The number of cache hits. + hits: Counter, + /// The number of cache misses. + misses: Counter, +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + + #[test] + fn test_insert_and_get_by_hash() { + let cache = BalCache::with_capacity(10); + + let hash1 = B256::random(); + let hash2 = B256::random(); + let bal1 = Bytes::from_static(b"bal1"); + let bal2 = Bytes::from_static(b"bal2"); + + cache.insert(hash1, 1, bal1.clone()); + cache.insert(hash2, 2, bal2.clone()); + + let results = cache.get_by_hashes(&[hash1, hash2, B256::random()]); + assert_eq!(results.len(), 3); + assert_eq!(results[0], Some(bal1)); + assert_eq!(results[1], Some(bal2)); + assert_eq!(results[2], None); + } + + #[test] + fn test_get_by_range() { + let cache = BalCache::with_capacity(10); + + for i in 1..=5 { + let hash = B256::random(); + let bal = Bytes::from(format!("bal{i}").into_bytes()); + cache.insert(hash, i, bal); + } + + let results = cache.get_by_range(2, 3); + assert_eq!(results.len(), 3); + } + + #[test] + fn test_lru_eviction() { + let cache = BalCache::with_capacity(3); + + let hashes: Vec<_> = (0..5).map(|_| B256::random()).collect(); + for (i, hash) in hashes.iter().enumerate() { + cache.insert(*hash, i as u64, Bytes::from_static(b"bal")); + } + + // Only the last 3 should be in cache + assert_eq!(cache.len(), 3); + + // First two should be evicted + let results = cache.get_by_hashes(&hashes[..2]); + assert!(results.iter().all(|r| r.is_none())); + + // Last three should still be there + let results = cache.get_by_hashes(&hashes[2..]); + assert!(results.iter().all(|r| r.is_some())); + } +} diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 5a7b69dd9e1..7324c848ac0 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -1,5 +1,6 @@ use crate::{ - capabilities::EngineCapabilities, metrics::EngineApiMetrics, EngineApiError, EngineApiResult, + bal_cache::BalCache, capabilities::EngineCapabilities, metrics::EngineApiMetrics, + EngineApiError, EngineApiResult, }; use alloy_eips::{ eip1898::BlockHashOrNumber, @@ -21,7 +22,7 @@ use reth_engine_primitives::{ConsensusEngineHandle, EngineApiValidator, EngineTy use reth_network_api::NetworkInfo; use reth_payload_builder::PayloadStore; use reth_payload_primitives::{ - validate_payload_timestamp, EngineApiMessageVersion, MessageValidationKind, + validate_payload_timestamp, EngineApiMessageVersion, ExecutionPayload, MessageValidationKind, PayloadOrAttributes, PayloadTypes, }; use reth_primitives_traits::{Block, BlockBody}; @@ -96,6 +97,38 @@ where validator: Validator, accept_execution_requests_hash: bool, network: impl NetworkInfo + 'static, + ) -> Self { + Self::with_bal_cache( + provider, + chain_spec, + beacon_consensus, + payload_store, + tx_pool, + task_spawner, + client, + capabilities, + validator, + accept_execution_requests_hash, + network, + BalCache::new(), + ) + } + + /// Create new instance of [`EngineApi`] with a custom BAL cache. + #[expect(clippy::too_many_arguments)] + pub fn with_bal_cache( + provider: Provider, + chain_spec: Arc, + beacon_consensus: ConsensusEngineHandle, + payload_store: PayloadStore, + tx_pool: Pool, + task_spawner: Box, + client: ClientVersionV1, + capabilities: EngineCapabilities, + validator: Validator, + accept_execution_requests_hash: bool, + network: impl NetworkInfo + 'static, + bal_cache: BalCache, ) -> Self { let is_syncing = Arc::new(move || network.is_syncing()); let inner = Arc::new(EngineApiInner { @@ -111,10 +144,29 @@ where validator, accept_execution_requests_hash, is_syncing, + bal_cache, }); Self { inner } } + /// Returns a reference to the BAL cache. + pub fn bal_cache(&self) -> &BalCache { + &self.inner.bal_cache + } + + /// Caches the BAL from the payload if the status is valid. + fn maybe_cache_bal(&self, payload: &PayloadT::ExecutionData, status: &PayloadStatus) { + if status.is_valid() { + if let Some(bal) = payload.block_access_list() { + self.inner.bal_cache.insert( + payload.block_hash(), + payload.block_number(), + bal.clone(), + ); + } + } + } + /// Fetches the client version. pub fn get_client_version_v1( &self, @@ -149,7 +201,9 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V1, payload_or_attrs)?; - Ok(self.inner.beacon_consensus.new_payload(payload).await?) + let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; + self.maybe_cache_bal(&payload, &status); + Ok(status) } /// Metered version of `new_payload_v1`. @@ -177,7 +231,10 @@ where self.inner .validator .validate_version_specific_fields(EngineApiMessageVersion::V2, payload_or_attrs)?; - Ok(self.inner.beacon_consensus.new_payload(payload).await?) + + let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; + self.maybe_cache_bal(&payload, &status); + Ok(status) } /// Metered version of `new_payload_v2`. @@ -206,7 +263,9 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V3, payload_or_attrs)?; - Ok(self.inner.beacon_consensus.new_payload(payload).await?) + let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; + self.maybe_cache_bal(&payload, &status); + Ok(status) } /// Metrics version of `new_payload_v3` @@ -236,7 +295,9 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?; - Ok(self.inner.beacon_consensus.new_payload(payload).await?) + let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; + self.maybe_cache_bal(&payload, &status); + Ok(status) } /// Metrics version of `new_payload_v4` @@ -881,6 +942,22 @@ where res } + + /// Retrieves BALs for the given block hashes from the cache. + /// + /// Returns the RLP-encoded BALs for blocks found in the cache. + /// Missing blocks are returned as empty bytes. + pub fn get_bals_by_hash(&self, block_hashes: Vec) -> Vec { + let results = self.inner.bal_cache.get_by_hashes(&block_hashes); + results.into_iter().map(|opt| opt.unwrap_or_default()).collect() + } + + /// Retrieves BALs for a range of blocks from the cache. + /// + /// Returns the RLP-encoded BALs for blocks in the range `[start, start + count)`. + pub fn get_bals_by_range(&self, start: u64, count: u64) -> Vec { + self.inner.bal_cache.get_by_range(start, count) + } } // This is the concrete ethereum engine API implementation. @@ -1172,12 +1249,10 @@ where /// See also async fn get_bals_by_hash_v1( &self, - _block_hashes: Vec, + block_hashes: Vec, ) -> RpcResult> { trace!(target: "rpc::engine", "Serving engine_getBALsByHashV1"); - Err(EngineApiError::EngineObjectValidationError( - reth_payload_primitives::EngineObjectValidationError::UnsupportedFork, - ))? + Ok(self.get_bals_by_hash(block_hashes)) } /// Handler for `engine_getBALsByRangeV1` @@ -1185,13 +1260,11 @@ where /// See also async fn get_bals_by_range_v1( &self, - _start: U64, - _count: U64, + start: U64, + count: U64, ) -> RpcResult> { trace!(target: "rpc::engine", "Serving engine_getBALsByRangeV1"); - Err(EngineApiError::EngineObjectValidationError( - reth_payload_primitives::EngineObjectValidationError::UnsupportedFork, - ))? + Ok(self.get_bals_by_range(start.to(), count.to())) } } @@ -1251,6 +1324,8 @@ struct EngineApiInner bool + Send + Sync>, + /// Cache for Block Access Lists (BALs) per EIP-7928. + bal_cache: BalCache, } #[cfg(test)] diff --git a/crates/rpc/rpc-engine-api/src/lib.rs b/crates/rpc/rpc-engine-api/src/lib.rs index 9ce8d21763b..044517d7ae3 100644 --- a/crates/rpc/rpc-engine-api/src/lib.rs +++ b/crates/rpc/rpc-engine-api/src/lib.rs @@ -12,6 +12,10 @@ /// The Engine API implementation. mod engine_api; +/// Block Access List (BAL) cache for EIP-7928. +mod bal_cache; +pub use bal_cache::BalCache; + /// Engine API capabilities. pub mod capabilities; pub use capabilities::EngineCapabilities; From d750b4976d63490256e5d145e501b70246a01bfa Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jan 2026 11:46:37 +0100 Subject: [PATCH 02/36] fix: address clippy warnings - Collapse nested if statements using let-chains - Add backticks around BTreeMap in doc comment --- crates/rpc/rpc-engine-api/src/bal_cache.rs | 2 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index 2820e9ac31a..c71289f87a8 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -48,7 +48,7 @@ struct BalCacheInner { /// LRU cache mapping block hash to BAL entry. entries: RwLock>, /// Index mapping block number to block hash for range queries. - /// Uses BTreeMap for efficient range iteration. + /// Uses `BTreeMap` for efficient range iteration. block_index: RwLock>, /// Cache metrics. metrics: BalCacheMetrics, diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 7324c848ac0..c114f64a117 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -156,14 +156,10 @@ where /// Caches the BAL from the payload if the status is valid. fn maybe_cache_bal(&self, payload: &PayloadT::ExecutionData, status: &PayloadStatus) { - if status.is_valid() { - if let Some(bal) = payload.block_access_list() { - self.inner.bal_cache.insert( - payload.block_hash(), - payload.block_number(), - bal.clone(), - ); - } + if status.is_valid() && + let Some(bal) = payload.block_access_list() + { + self.inner.bal_cache.insert(payload.block_hash(), payload.block_number(), bal.clone()); } } From 3f50a36191b828a626d4bfaddfbb717e9f33b129 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jan 2026 11:49:29 +0100 Subject: [PATCH 03/36] fix: stop get_by_range at first missing block Ensures caller knows returned BALs correspond to contiguous blocks [start, start + len) --- crates/rpc/rpc-engine-api/src/bal_cache.rs | 40 ++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index c71289f87a8..08a98b1ea04 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -117,16 +117,24 @@ impl BalCache { /// Retrieves BALs for a range of blocks starting at `start` for `count` blocks. /// - /// Returns a vector of BALs in block number order. Missing blocks are not included. + /// Returns a vector of contiguous BALs in block number order, stopping at the first + /// missing block. This ensures the caller knows the returned BALs correspond to + /// blocks `[start, start + len)`. pub fn get_by_range(&self, start: BlockNumber, count: u64) -> Vec { let entries = self.inner.entries.read(); let block_index = self.inner.block_index.read(); - let end = start.saturating_add(count); - block_index - .range(start..end) - .filter_map(|(_, hash)| entries.peek(hash).map(|e| e.bal.clone())) - .collect() + let mut result = Vec::new(); + for block_num in start..start.saturating_add(count) { + let Some(hash) = block_index.get(&block_num) else { + break; + }; + let Some(entry) = entries.peek(hash) else { + break; + }; + result.push(entry.bal.clone()); + } + result } /// Returns the number of entries in the cache. @@ -194,6 +202,26 @@ mod tests { assert_eq!(results.len(), 3); } + #[test] + fn test_get_by_range_stops_at_gap() { + let cache = BalCache::with_capacity(10); + + // Insert blocks 1, 2, 4, 5 (missing block 3) + for i in [1, 2, 4, 5] { + let hash = B256::random(); + let bal = Bytes::from(format!("bal{i}").into_bytes()); + cache.insert(hash, i, bal); + } + + // Requesting range starting at 1 should stop at the gap (block 3) + let results = cache.get_by_range(1, 5); + assert_eq!(results.len(), 2); // Only blocks 1 and 2 + + // Requesting range starting at 4 should return 4 and 5 + let results = cache.get_by_range(4, 3); + assert_eq!(results.len(), 2); + } + #[test] fn test_lru_eviction() { let cache = BalCache::with_capacity(3); From df1413167aa5f9638f62fd47b9fb4b616c2de7a7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jan 2026 12:00:38 +0100 Subject: [PATCH 04/36] perf: clone only BAL bytes instead of entire payload Extract num_hash and BAL before calling new_payload to avoid cloning the entire ExecutionData payload. --- crates/rpc/rpc-engine-api/src/engine_api.rs | 35 +++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index c114f64a117..d59874c2022 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -7,8 +7,9 @@ use alloy_eips::{ eip4844::{BlobAndProofV1, BlobAndProofV2}, eip4895::Withdrawals, eip7685::RequestsOrHash, + BlockNumHash, }; -use alloy_primitives::{BlockHash, BlockNumber, B256, U64}; +use alloy_primitives::{BlockHash, BlockNumber, Bytes, B256, U64}; use alloy_rpc_types_engine::{ CancunPayloadFields, ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1, ExecutionPayloadBodyV1, ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1, @@ -154,12 +155,12 @@ where &self.inner.bal_cache } - /// Caches the BAL from the payload if the status is valid. - fn maybe_cache_bal(&self, payload: &PayloadT::ExecutionData, status: &PayloadStatus) { + /// Caches the BAL if the status is valid. + fn maybe_cache_bal(&self, num_hash: BlockNumHash, bal: Option, status: &PayloadStatus) { if status.is_valid() && - let Some(bal) = payload.block_access_list() + let Some(bal) = bal { - self.inner.bal_cache.insert(payload.block_hash(), payload.block_number(), bal.clone()); + self.inner.bal_cache.insert(num_hash.hash, num_hash.number, bal); } } @@ -197,8 +198,10 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V1, payload_or_attrs)?; - let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; - self.maybe_cache_bal(&payload, &status); + let num_hash = payload.num_hash(); + let bal = payload.block_access_list().cloned(); + let status = self.inner.beacon_consensus.new_payload(payload).await?; + self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -228,8 +231,10 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V2, payload_or_attrs)?; - let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; - self.maybe_cache_bal(&payload, &status); + let num_hash = payload.num_hash(); + let bal = payload.block_access_list().cloned(); + let status = self.inner.beacon_consensus.new_payload(payload).await?; + self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -259,8 +264,10 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V3, payload_or_attrs)?; - let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; - self.maybe_cache_bal(&payload, &status); + let num_hash = payload.num_hash(); + let bal = payload.block_access_list().cloned(); + let status = self.inner.beacon_consensus.new_payload(payload).await?; + self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -291,8 +298,10 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?; - let status = self.inner.beacon_consensus.new_payload(payload.clone()).await?; - self.maybe_cache_bal(&payload, &status); + let num_hash = payload.num_hash(); + let bal = payload.block_access_list().cloned(); + let status = self.inner.beacon_consensus.new_payload(payload).await?; + self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } From 9f5cf847ccb6cfb85b5986a360a8af650020c8d0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jan 2026 12:07:10 +0100 Subject: [PATCH 05/36] refactor: simplify BalCache to use HashMap + BTreeMap Replace LRU-based cache with simpler design: - Use HashMap for O(1) hash lookups - Use BTreeMap as source of truth for eviction - Evict oldest (lowest) block numbers when at capacity - Handle reorgs by removing old hash when block number is replaced This is simpler, more predictable, and removes schnellru dependency. --- Cargo.lock | 1 - crates/rpc/rpc-engine-api/Cargo.toml | 1 - crates/rpc/rpc-engine-api/src/bal_cache.rs | 113 +++++++++++++-------- 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae73103ac24..5a959e3de9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10643,7 +10643,6 @@ dependencies = [ "reth-tasks", "reth-testing-utils", "reth-transaction-pool", - "schnellru", "serde", "thiserror 2.0.18", "tokio", diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 731e6fd4229..4d7f294e917 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -42,7 +42,6 @@ async-trait.workspace = true jsonrpsee-core.workspace = true jsonrpsee-types.workspace = true parking_lot.workspace = true -schnellru.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index 08a98b1ea04..463c01ea0ba 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -6,7 +6,7 @@ //! //! According to EIP-7928, the EL MUST retain BALs for at least the duration of the //! weak subjectivity period (~3533 epochs) to support synchronization with re-execution. -//! This initial implementation uses a simple in-memory LRU cache with configurable capacity. +//! This initial implementation uses a simple in-memory cache with configurable capacity. use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use parking_lot::RwLock; @@ -14,8 +14,10 @@ use reth_metrics::{ metrics::{Counter, Gauge}, Metrics, }; -use schnellru::{ByLength, LruMap}; -use std::{collections::BTreeMap, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; /// Default capacity for the BAL cache. /// @@ -23,19 +25,10 @@ use std::{collections::BTreeMap, sync::Arc}; /// weak subjectivity period requirements (~3533 epochs ≈ 113,000 blocks). const DEFAULT_BAL_CACHE_CAPACITY: u32 = 1024; -/// Cache entry storing a BAL with its block number for range queries. -#[derive(Debug, Clone)] -struct BalEntry { - /// The block number for range-based lookups. - block_number: BlockNumber, - /// The RLP-encoded block access list. - bal: Bytes, -} - /// In-memory cache for Block Access Lists (BALs). /// /// Provides O(1) lookups by block hash and O(log n) range queries by block number. -/// Uses an LRU eviction policy when the cache exceeds capacity. +/// Evicts the oldest (lowest) block numbers when capacity is exceeded. /// /// This type is cheaply cloneable as it wraps an `Arc` internally. #[derive(Debug, Clone)] @@ -45,10 +38,12 @@ pub struct BalCache { #[derive(Debug)] struct BalCacheInner { - /// LRU cache mapping block hash to BAL entry. - entries: RwLock>, + /// Maximum number of entries to store. + capacity: u32, + /// Mapping from block hash to BAL bytes. + entries: RwLock>, /// Index mapping block number to block hash for range queries. - /// Uses `BTreeMap` for efficient range iteration. + /// Uses `BTreeMap` for efficient range iteration and eviction of oldest blocks. block_index: RwLock>, /// Cache metrics. metrics: BalCacheMetrics, @@ -64,7 +59,8 @@ impl BalCache { pub fn with_capacity(capacity: u32) -> Self { Self { inner: Arc::new(BalCacheInner { - entries: RwLock::new(LruMap::new(ByLength::new(capacity))), + capacity, + entries: RwLock::new(HashMap::new()), block_index: RwLock::new(BTreeMap::new()), metrics: BalCacheMetrics::default(), }), @@ -73,22 +69,30 @@ impl BalCache { /// Inserts a BAL into the cache. /// - /// If the cache is at capacity, the least recently used entry will be evicted. + /// If a different hash already exists for this block number (reorg), the old entry + /// is removed first. If the cache is at capacity, the oldest block number is evicted. pub fn insert(&self, block_hash: BlockHash, block_number: BlockNumber, bal: Bytes) { - let entry = BalEntry { block_number, bal }; - let mut entries = self.inner.entries.write(); let mut block_index = self.inner.block_index.write(); - // Check if we need to evict an old entry - if entries.len() as u32 >= entries.limiter().max_length() { - // Find and remove the oldest entry from block_index - if let Some((_, evicted_entry)) = entries.iter().next_back() { - block_index.remove(&evicted_entry.block_number); - } + // If this block number already has a different hash, remove the old entry + if let Some(old_hash) = block_index.get(&block_number) && + *old_hash != block_hash + { + entries.remove(old_hash); + } + + // Evict oldest block if at capacity and this is a new entry + if !entries.contains_key(&block_hash) && + entries.len() as u32 >= self.inner.capacity && + let Some((&oldest_num, &oldest_hash)) = block_index.first_key_value() + { + entries.remove(&oldest_hash); + block_index.remove(&oldest_num); } - entries.insert(block_hash, entry); + entries.insert(block_hash, bal); + block_index.insert(block_number, block_hash); self.inner.metrics.inserts.increment(1); @@ -100,11 +104,11 @@ impl BalCache { /// Returns a vector with the same length as `block_hashes`, where each element /// is `Some(bal)` if found or `None` if not in cache. pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { - let mut entries = self.inner.entries.write(); + let entries = self.inner.entries.read(); block_hashes .iter() .map(|hash| { - let result = entries.get(hash).map(|e| e.bal.clone()); + let result = entries.get(hash).cloned(); if result.is_some() { self.inner.metrics.hits.increment(1); } else { @@ -129,10 +133,10 @@ impl BalCache { let Some(hash) = block_index.get(&block_num) else { break; }; - let Some(entry) = entries.peek(hash) else { + let Some(bal) = entries.get(hash) else { break; }; - result.push(entry.bal.clone()); + result.push(bal.clone()); } result } @@ -223,23 +227,48 @@ mod tests { } #[test] - fn test_lru_eviction() { + fn test_eviction_oldest_first() { let cache = BalCache::with_capacity(3); - let hashes: Vec<_> = (0..5).map(|_| B256::random()).collect(); - for (i, hash) in hashes.iter().enumerate() { - cache.insert(*hash, i as u64, Bytes::from_static(b"bal")); + // Insert blocks 10, 20, 30 + for i in [10, 20, 30] { + let hash = B256::random(); + cache.insert(hash, i, Bytes::from_static(b"bal")); } + assert_eq!(cache.len(), 3); - // Only the last 3 should be in cache + // Insert block 40, should evict block 10 (oldest/lowest) + let hash40 = B256::random(); + cache.insert(hash40, 40, Bytes::from_static(b"bal40")); assert_eq!(cache.len(), 3); - // First two should be evicted - let results = cache.get_by_hashes(&hashes[..2]); - assert!(results.iter().all(|r| r.is_none())); + // Block 10 should be gone, block 20 should still be there + let results = cache.get_by_range(10, 1); + assert_eq!(results.len(), 0); + + let results = cache.get_by_range(20, 1); + assert_eq!(results.len(), 1); + } + + #[test] + fn test_reorg_replaces_hash() { + let cache = BalCache::with_capacity(10); + + let hash1 = B256::random(); + let hash2 = B256::random(); + let bal1 = Bytes::from_static(b"bal1"); + let bal2 = Bytes::from_static(b"bal2"); + + // Insert block 100 with hash1 + cache.insert(hash1, 100, bal1.clone()); + assert_eq!(cache.get_by_hashes(&[hash1])[0], Some(bal1)); + + // Reorg: insert block 100 with hash2 + cache.insert(hash2, 100, bal2.clone()); - // Last three should still be there - let results = cache.get_by_hashes(&hashes[2..]); - assert!(results.iter().all(|r| r.is_some())); + // hash1 should be gone, hash2 should be there + assert_eq!(cache.get_by_hashes(&[hash1])[0], None); + assert_eq!(cache.get_by_hashes(&[hash2])[0], Some(bal2)); + assert_eq!(cache.len(), 1); } } From a0aac13f75507c92002f3b61bbe2158a4c22ef20 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jan 2026 12:36:03 +0100 Subject: [PATCH 06/36] fix: make len() private to satisfy clippy --- crates/rpc/rpc-engine-api/src/bal_cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index 463c01ea0ba..13bbfb2e02b 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -143,7 +143,7 @@ impl BalCache { /// Returns the number of entries in the cache. #[cfg(test)] - pub fn len(&self) -> usize { + fn len(&self) -> usize { self.inner.entries.read().len() } } From 65e27c4c35a534acedd1182297a8c75795573834 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 13:21:53 +0800 Subject: [PATCH 07/36] impl bal storage --- Cargo.lock | 1 + crates/node/builder/src/rpc.rs | 12 +- crates/node/core/src/dirs.rs | 8 + crates/rpc/rpc-engine-api/Cargo.toml | 1 + crates/rpc/rpc-engine-api/src/bal_cache.rs | 24 + .../rpc/rpc-engine-api/src/bal_store/disk.rs | 387 ++++++++++++++++ .../rpc/rpc-engine-api/src/bal_store/mod.rs | 42 ++ crates/rpc/rpc-engine-api/src/engine_api.rs | 427 +++++++++++++++++- crates/rpc/rpc-engine-api/src/lib.rs | 7 + crates/rpc/rpc-engine-api/src/metrics.rs | 23 + 10 files changed, 918 insertions(+), 14 deletions(-) create mode 100644 crates/rpc/rpc-engine-api/src/bal_store/disk.rs create mode 100644 crates/rpc/rpc-engine-api/src/bal_store/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 71bbdcc6021..a1eef9b9dc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10647,6 +10647,7 @@ dependencies = [ "reth-testing-utils", "reth-transaction-pool", "serde", + "tempfile", "thiserror 2.0.18", "tokio", "tracing", diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index c2097ce474c..f975d036b80 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -35,7 +35,9 @@ use reth_rpc_builder::{ config::RethRpcServerConfig, RpcModuleBuilder, RpcRegistryInner, RpcServerConfig, RpcServerHandle, TransportRpcModules, }; -use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; +use reth_rpc_engine_api::{ + capabilities::EngineCapabilities, DiskFileBalStore, DiskFileBalStoreConfig, EngineApi, +}; use reth_rpc_eth_types::{cache::cache_new_blocks_task, EthConfig, EthStateCache}; use reth_tokio_util::EventSender; use reth_tracing::tracing::{debug, info}; @@ -1395,7 +1397,12 @@ where commit: version_metadata().vergen_git_sha.to_string(), }; - Ok(EngineApi::new( + let bal_store = DiskFileBalStore::open( + ctx.config.datadir().balstore(), + DiskFileBalStoreConfig::default(), + )?; + + Ok(EngineApi::with_bal_store( ctx.node.provider().clone(), ctx.config.chain.clone(), ctx.beacon_engine_handle.clone(), @@ -1407,6 +1414,7 @@ where engine_validator, ctx.config.engine.accept_execution_requests_hash, ctx.node.network().clone(), + Arc::new(bal_store), )) } } diff --git a/crates/node/core/src/dirs.rs b/crates/node/core/src/dirs.rs index 815bd8e62f0..0ecbc9790a5 100644 --- a/crates/node/core/src/dirs.rs +++ b/crates/node/core/src/dirs.rs @@ -347,6 +347,14 @@ impl ChainPath { self.data_dir().join("blobstore") } + /// Returns the path to the BAL store directory for this chain where EIP-7928 + /// block access lists are persisted. + /// + /// `//balstore` + pub fn balstore(&self) -> PathBuf { + self.data_dir().join("balstore") + } + /// Returns the path to the local transactions backup file /// /// `//txpool-transactions-backup.rlp` diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 4d7f294e917..5be750788cc 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -56,3 +56,4 @@ alloy-rlp.workspace = true reth-node-ethereum.workspace = true assert_matches.workspace = true +tempfile.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index 13bbfb2e02b..63a9fdf0046 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -8,6 +8,7 @@ //! weak subjectivity period (~3533 epochs) to support synchronization with re-execution. //! This initial implementation uses a simple in-memory cache with configurable capacity. +use crate::bal_store::{BalStore, BalStoreError}; use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use parking_lot::RwLock; use reth_metrics::{ @@ -154,6 +155,29 @@ impl Default for BalCache { } } +impl BalStore for BalCache { + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + BalCache::insert(self, block_hash, block_number, bal); + Ok(()) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError> { + Ok(BalCache::get_by_hashes(self, block_hashes)) + } + + fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { + Ok(BalCache::get_by_range(self, start, count)) + } +} + /// Metrics for the BAL cache. #[derive(Metrics)] #[metrics(scope = "engine.bal_cache")] diff --git a/crates/rpc/rpc-engine-api/src/bal_store/disk.rs b/crates/rpc/rpc-engine-api/src/bal_store/disk.rs new file mode 100644 index 00000000000..51b1c19699e --- /dev/null +++ b/crates/rpc/rpc-engine-api/src/bal_store/disk.rs @@ -0,0 +1,387 @@ +//! Disk-backed BAL store. + +use crate::bal_store::{BalStore, BalStoreError}; +use alloy_primitives::{BlockHash, BlockNumber, Bytes}; +use parking_lot::Mutex; +use std::{ + collections::{BTreeMap, HashMap}, + fs, io, + path::PathBuf, + sync::Arc, +}; +use tracing::debug; + +/// Default maximum retained BAL entries on disk. +/// +/// Roughly aligns with the weak subjectivity period target discussed in EIP-7928. +pub const DEFAULT_MAX_BAL_STORE_ENTRIES: u32 = 113_000; + +/// Configuration for [`DiskFileBalStore`]. +#[derive(Debug, Clone, Copy)] +pub struct DiskFileBalStoreConfig { + /// Maximum number of BAL entries to retain. + pub max_entries: u32, +} + +impl Default for DiskFileBalStoreConfig { + fn default() -> Self { + Self { max_entries: DEFAULT_MAX_BAL_STORE_ENTRIES } + } +} + +impl DiskFileBalStoreConfig { + /// Sets the maximum retained entries. + pub const fn with_max_entries(mut self, max_entries: u32) -> Self { + self.max_entries = max_entries; + self + } +} + +/// A disk-backed BAL store with in-memory indexes. +/// +/// BAL payloads are stored as raw bytes under `/entries/`. +/// Block-number index entries are stored under `/index/` with +/// 32-byte block hash content. +#[derive(Clone, Debug)] +pub struct DiskFileBalStore { + inner: Arc, +} + +#[derive(Debug)] +struct DiskFileBalStoreInner { + root_dir: PathBuf, + entries_dir: PathBuf, + index_dir: PathBuf, + max_entries: u32, + state: Mutex, +} + +#[derive(Debug, Default)] +struct IndexState { + block_to_hash: BTreeMap, + hash_to_block: HashMap, +} + +impl DiskFileBalStore { + /// Opens (or creates) a disk BAL store and rebuilds in-memory indexes. + pub fn open( + bal_dir: impl Into, + opts: DiskFileBalStoreConfig, + ) -> Result { + let root_dir = bal_dir.into(); + let entries_dir = root_dir.join("entries"); + let index_dir = root_dir.join("index"); + + fs::create_dir_all(&entries_dir)?; + fs::create_dir_all(&index_dir)?; + + let inner = DiskFileBalStoreInner { + root_dir, + entries_dir, + index_dir, + max_entries: opts.max_entries, + state: Mutex::new(IndexState::default()), + }; + inner.rebuild_indexes()?; + + Ok(Self { inner: Arc::new(inner) }) + } +} + +impl BalStore for DiskFileBalStore { + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + self.inner.insert(block_hash, block_number, bal) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError> { + self.inner.get_by_hashes(block_hashes) + } + + fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { + self.inner.get_by_range(start, count) + } +} + +impl DiskFileBalStoreInner { + fn rebuild_indexes(&self) -> Result<(), BalStoreError> { + let mut indexed = Vec::new(); + + let dir = match fs::read_dir(&self.index_dir) { + Ok(dir) => dir, + Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(()), + Err(err) => return Err(err.into()), + }; + + for entry in dir { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + debug!(target: "rpc::engine", %err, "Failed to read BAL index dir entry"); + continue; + } + }; + + let path = entry.path(); + let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else { + debug!(target: "rpc::engine", ?path, "Skipping BAL index entry with non-utf8 name"); + continue; + }; + + let Ok(block_number) = file_name.parse::() else { + debug!(target: "rpc::engine", ?path, "Skipping BAL index entry with invalid block number name"); + continue; + }; + + let hash_bytes = match fs::read(&path) { + Ok(bytes) => bytes, + Err(err) => { + debug!(target: "rpc::engine", ?path, %err, "Failed reading BAL index file"); + continue; + } + }; + + if hash_bytes.len() != 32 { + debug!( + target: "rpc::engine", + ?path, + len = hash_bytes.len(), + "Skipping BAL index file with invalid hash length" + ); + continue; + } + + let block_hash = BlockHash::from_slice(&hash_bytes); + if !self.entry_file(block_hash).is_file() { + debug!( + target: "rpc::engine", + block_number, + ?block_hash, + "Skipping BAL index entry pointing to missing BAL payload file" + ); + continue; + } + + indexed.push((block_number, block_hash)); + } + + indexed.sort_unstable_by_key(|(number, _)| *number); + + let mut state = self.state.lock(); + state.block_to_hash.clear(); + state.hash_to_block.clear(); + + for (block_number, block_hash) in indexed { + if let Some(old_number) = state.hash_to_block.insert(block_hash, block_number) { + state.block_to_hash.remove(&old_number); + let _ = self.remove_if_exists(self.index_file(old_number)); + } + state.block_to_hash.insert(block_number, block_hash); + } + + self.evict_over_capacity(&mut state)?; + Ok(()) + } + + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + let mut state = self.state.lock(); + + // If the hash was previously indexed at another number, move the index. + if let Some(old_number) = state.hash_to_block.get(&block_hash).copied() && + old_number != block_number + { + state.block_to_hash.remove(&old_number); + self.remove_if_exists(self.index_file(old_number))?; + } + + // Reorg replacement: remove old hash payload at this number. + if let Some(old_hash) = state.block_to_hash.get(&block_number).copied() && + old_hash != block_hash + { + state.hash_to_block.remove(&old_hash); + self.remove_if_exists(self.entry_file(old_hash))?; + } + + fs::write(self.entry_file(block_hash), bal.as_ref())?; + fs::write(self.index_file(block_number), block_hash.as_slice())?; + + state.block_to_hash.insert(block_number, block_hash); + state.hash_to_block.insert(block_hash, block_number); + + self.evict_over_capacity(&mut state) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError> { + block_hashes + .iter() + .map(|hash| { + let path = self.entry_file(*hash); + match fs::read(path) { + Ok(bytes) => Ok(Some(Bytes::from(bytes))), + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err.into()), + } + }) + .collect() + } + + fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { + let hashes: Vec = { + let state = self.state.lock(); + let mut hashes = Vec::new(); + for number in start..start.saturating_add(count) { + let Some(hash) = state.block_to_hash.get(&number).copied() else { + break; + }; + hashes.push(hash); + } + hashes + }; + + let mut result = Vec::with_capacity(hashes.len()); + for hash in hashes { + match fs::read(self.entry_file(hash)) { + Ok(bytes) => result.push(Bytes::from(bytes)), + Err(err) if err.kind() == io::ErrorKind::NotFound => break, + Err(err) => return Err(err.into()), + } + } + Ok(result) + } + + fn evict_over_capacity(&self, state: &mut IndexState) -> Result<(), BalStoreError> { + while state.block_to_hash.len() as u32 > self.max_entries { + let Some((&oldest_number, &oldest_hash)) = state.block_to_hash.first_key_value() else { + break; + }; + + state.block_to_hash.remove(&oldest_number); + state.hash_to_block.remove(&oldest_hash); + + self.remove_if_exists(self.entry_file(oldest_hash))?; + self.remove_if_exists(self.index_file(oldest_number))?; + } + Ok(()) + } + + fn remove_if_exists(&self, path: PathBuf) -> Result<(), BalStoreError> { + match fs::remove_file(path) { + Ok(()) => Ok(()), + Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()), + Err(err) => Err(err.into()), + } + } + + #[inline] + fn entry_file(&self, block_hash: BlockHash) -> PathBuf { + self.entries_dir.join(format!("{block_hash:x}")) + } + + #[inline] + fn index_file(&self, block_number: BlockNumber) -> PathBuf { + self.index_dir.join(block_number.to_string()) + } +} + +impl std::fmt::Display for DiskFileBalStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DiskFileBalStore({})", self.inner.root_dir.display()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + + fn tmp_store(max_entries: u32) -> (DiskFileBalStore, tempfile::TempDir) { + let dir = tempfile::tempdir().unwrap(); + let store = DiskFileBalStore::open( + dir.path(), + DiskFileBalStoreConfig::default().with_max_entries(max_entries), + ) + .unwrap(); + (store, dir) + } + + #[test] + fn insert_and_get_hashes() { + let (store, _dir) = tmp_store(10); + let h1 = B256::random(); + let h2 = B256::random(); + + store.insert(h1, 1, Bytes::from_static(b"a")).unwrap(); + store.insert(h2, 2, Bytes::from_static(b"b")).unwrap(); + + let got = store.get_by_hashes(&[h1, h2, B256::random()]).unwrap(); + assert_eq!(got, vec![Some(Bytes::from_static(b"a")), Some(Bytes::from_static(b"b")), None]); + } + + #[test] + fn range_stops_on_gap() { + let (store, _dir) = tmp_store(10); + store.insert(B256::random(), 1, Bytes::from_static(b"a")).unwrap(); + store.insert(B256::random(), 2, Bytes::from_static(b"b")).unwrap(); + store.insert(B256::random(), 4, Bytes::from_static(b"d")).unwrap(); + + let got = store.get_by_range(1, 10).unwrap(); + assert_eq!(got.len(), 2); + } + + #[test] + fn reorg_replaces_entry() { + let (store, _dir) = tmp_store(10); + let old_hash = B256::random(); + let new_hash = B256::random(); + + store.insert(old_hash, 42, Bytes::from_static(b"old")).unwrap(); + store.insert(new_hash, 42, Bytes::from_static(b"new")).unwrap(); + + let got_old = store.get_by_hashes(&[old_hash]).unwrap(); + let got_new = store.get_by_hashes(&[new_hash]).unwrap(); + assert_eq!(got_old, vec![None]); + assert_eq!(got_new, vec![Some(Bytes::from_static(b"new"))]); + } + + #[test] + fn evicts_oldest() { + let (store, _dir) = tmp_store(2); + store.insert(B256::random(), 10, Bytes::from_static(b"a")).unwrap(); + store.insert(B256::random(), 20, Bytes::from_static(b"b")).unwrap(); + store.insert(B256::random(), 30, Bytes::from_static(b"c")).unwrap(); + + let got = store.get_by_range(10, 1).unwrap(); + assert!(got.is_empty()); + } + + #[test] + fn recovers_after_restart() { + let dir = tempfile::tempdir().unwrap(); + let h1 = B256::random(); + let h2 = B256::random(); + { + let store = DiskFileBalStore::open(dir.path(), Default::default()).unwrap(); + store.insert(h1, 100, Bytes::from_static(b"a")).unwrap(); + store.insert(h2, 101, Bytes::from_static(b"b")).unwrap(); + } + + let reopened = DiskFileBalStore::open(dir.path(), Default::default()).unwrap(); + let got = reopened.get_by_hashes(&[h1, h2]).unwrap(); + assert_eq!(got, vec![Some(Bytes::from_static(b"a")), Some(Bytes::from_static(b"b"))]); + } +} diff --git a/crates/rpc/rpc-engine-api/src/bal_store/mod.rs b/crates/rpc/rpc-engine-api/src/bal_store/mod.rs new file mode 100644 index 00000000000..0a62b557cd9 --- /dev/null +++ b/crates/rpc/rpc-engine-api/src/bal_store/mod.rs @@ -0,0 +1,42 @@ +//! Storage abstraction for Block Access Lists (BALs). + +use alloy_primitives::{BlockHash, BlockNumber, Bytes}; +use std::io; + +pub mod disk; +pub use disk::{DiskFileBalStore, DiskFileBalStoreConfig, DEFAULT_MAX_BAL_STORE_ENTRIES}; + +/// A store for EIP-7928 Block Access Lists (BALs). +/// +/// The store is keyed by block hash and maintains a block-number index for range queries. +/// Implementations should preserve contiguous-range semantics: +/// queries by range stop at the first missing block. +pub trait BalStore: Send + Sync + 'static { + /// Inserts a BAL for the given block hash and number. + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError>; + + /// Returns BALs for each requested block hash in the same order. + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError>; + + /// Returns contiguous BALs in `[start, start + count)` until the first gap. + fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError>; +} + +/// Error variants that can occur when interacting with a BAL store. +#[derive(Debug, thiserror::Error)] +pub enum BalStoreError { + /// Filesystem I/O error. + #[error("BAL store I/O error: {0}")] + Io(#[from] io::Error), + /// Other implementation-specific error. + #[error(transparent)] + Other(Box), +} diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 660becce72f..bca427a0998 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -1,5 +1,8 @@ use crate::{ - bal_cache::BalCache, capabilities::EngineCapabilities, metrics::EngineApiMetrics, + bal_cache::BalCache, + bal_store::{BalStore, BalStoreError}, + capabilities::EngineCapabilities, + metrics::EngineApiMetrics, EngineApiError, EngineApiResult, }; use alloy_eips::{ @@ -47,6 +50,126 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024; /// The upper limit for blobs in `engine_getBlobsVx`. const MAX_BLOB_LIMIT: usize = 128; +/// The upper limit for BAL requests in `engine_getBALsByHashV1` and +/// `engine_getBALsByRangeV1`. +const MAX_BAL_REQUEST_LIMIT: u64 = 1024; + +#[derive(Clone)] +struct BalProvider { + store: Arc, + cache: BalCache, +} + +impl BalProvider { + fn new(store: Arc, cache: BalCache) -> Self { + Self { store, cache } + } + + fn cache(&self) -> &BalCache { + &self.cache + } + + fn cache_bal( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + self.store.insert(block_hash, block_number, bal.clone())?; + self.cache.insert(block_hash, block_number, bal); + Ok(()) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + metrics: &crate::metrics::BalQueryMetrics, + ) -> Vec> { + let mut results = self.cache.get_by_hashes(block_hashes); + + let mut missing_hashes = Vec::new(); + let mut missing_indices = Vec::new(); + for (idx, result) in results.iter().enumerate() { + if result.is_none() { + missing_indices.push(idx); + missing_hashes.push(block_hashes[idx]); + } + } + + if missing_hashes.is_empty() { + return results; + } + + metrics.store_hash_fallback_requests.increment(1); + match self.store.get_by_hashes(&missing_hashes) { + Ok(store_results) => { + let mut recovered = 0_u64; + let mut still_missing = 0_u64; + + for (missing_idx, store_result) in + missing_indices.into_iter().zip(store_results.into_iter()) + { + if let Some(value) = store_result { + results[missing_idx] = Some(value); + recovered += 1; + } else { + still_missing += 1; + } + } + + if recovered > 0 { + metrics.store_hash_fallback_hits.increment(recovered); + } + if still_missing > 0 { + metrics.store_hash_fallback_misses.increment(still_missing); + } + } + Err(err) => { + metrics.store_hash_fallback_errors.increment(1); + warn!(target: "rpc::engine", ?err, "Failed to retrieve BALs by hash from BAL store"); + } + } + + results + } + + fn get_by_range( + &self, + start: BlockNumber, + count: u64, + metrics: &crate::metrics::BalQueryMetrics, + ) -> Vec { + let mut cache_results = self.cache.get_by_range(start, count); + if cache_results.len() as u64 == count { + return cache_results; + } + + let cached_len = cache_results.len() as u64; + let missing_start = start.saturating_add(cached_len); + let missing_count = count - cached_len; + + metrics.store_range_fallback_requests.increment(1); + match self.store.get_by_range(missing_start, missing_count) { + Ok(mut store_results) => { + let recovered = store_results.len() as u64; + if recovered > 0 { + metrics.store_range_fallback_hits.increment(recovered); + } + if recovered < missing_count { + metrics.store_range_fallback_misses.increment(missing_count - recovered); + } + cache_results.append(&mut store_results); + cache_results + } + Err(err) => { + metrics.store_range_fallback_errors.increment(1); + warn!(target: "rpc::engine", ?err, "Failed to retrieve BALs by range from BAL store"); + cache_results + } + } + } +} + /// The Engine API implementation that grants the Consensus layer access to data and /// functions in the Execution layer that are crucial for the consensus process. /// @@ -130,8 +253,76 @@ where accept_execution_requests_hash: bool, network: impl NetworkInfo + 'static, bal_cache: BalCache, + ) -> Self { + Self::with_bal_store_and_cache( + provider, + chain_spec, + beacon_consensus, + payload_store, + tx_pool, + task_spawner, + client, + capabilities, + validator, + accept_execution_requests_hash, + network, + Arc::new(bal_cache.clone()), + bal_cache, + ) + } + + /// Create new instance of [`EngineApi`] with a custom BAL store. + #[expect(clippy::too_many_arguments)] + pub fn with_bal_store( + provider: Provider, + chain_spec: Arc, + beacon_consensus: ConsensusEngineHandle, + payload_store: PayloadStore, + tx_pool: Pool, + task_spawner: Box, + client: ClientVersionV1, + capabilities: EngineCapabilities, + validator: Validator, + accept_execution_requests_hash: bool, + network: impl NetworkInfo + 'static, + bal_store: Arc, + ) -> Self { + Self::with_bal_store_and_cache( + provider, + chain_spec, + beacon_consensus, + payload_store, + tx_pool, + task_spawner, + client, + capabilities, + validator, + accept_execution_requests_hash, + network, + bal_store, + BalCache::new(), + ) + } + + /// Internal constructor that wires explicit BAL store and cache layers. + #[expect(clippy::too_many_arguments)] + fn with_bal_store_and_cache( + provider: Provider, + chain_spec: Arc, + beacon_consensus: ConsensusEngineHandle, + payload_store: PayloadStore, + tx_pool: Pool, + task_spawner: Box, + client: ClientVersionV1, + capabilities: EngineCapabilities, + validator: Validator, + accept_execution_requests_hash: bool, + network: impl NetworkInfo + 'static, + bal_store: Arc, + bal_cache: BalCache, ) -> Self { let is_syncing = Arc::new(move || network.is_syncing()); + let bal_provider = BalProvider::new(bal_store, bal_cache); let inner = Arc::new(EngineApiInner { provider, chain_spec, @@ -145,14 +336,14 @@ where validator, accept_execution_requests_hash, is_syncing, - bal_cache, + bal_provider, }); Self { inner } } /// Returns a reference to the BAL cache. pub fn bal_cache(&self) -> &BalCache { - &self.inner.bal_cache + self.inner.bal_provider.cache() } /// Caches the BAL if the status is valid. @@ -160,7 +351,16 @@ where if status.is_valid() && let Some(bal) = bal { - self.inner.bal_cache.insert(num_hash.hash, num_hash.number, bal); + if let Err(err) = self.inner.bal_provider.cache_bal(num_hash.hash, num_hash.number, bal) + { + warn!( + target: "rpc::engine", + ?err, + block_hash = ?num_hash.hash, + block_number = num_hash.number, + "Failed to persist BAL into BAL store" + ); + } } } @@ -950,18 +1150,22 @@ where /// Retrieves BALs for the given block hashes from the cache. /// - /// Returns the RLP-encoded BALs for blocks found in the cache. + /// Returns the RLP-encoded BALs for blocks found in the cache or BAL store. /// Missing blocks are returned as empty bytes. pub fn get_bals_by_hash(&self, block_hashes: Vec) -> Vec { - let results = self.inner.bal_cache.get_by_hashes(&block_hashes); - results.into_iter().map(|opt| opt.unwrap_or_default()).collect() + self.inner + .bal_provider + .get_by_hashes(&block_hashes, &self.inner.metrics.bal_metrics) + .into_iter() + .map(|opt| opt.unwrap_or_default()) + .collect() } - /// Retrieves BALs for a range of blocks from the cache. + /// Retrieves BALs for a range of blocks from the cache or BAL store. /// /// Returns the RLP-encoded BALs for blocks in the range `[start, start + count)`. pub fn get_bals_by_range(&self, start: u64, count: u64) -> Vec { - self.inner.bal_cache.get_by_range(start, count) + self.inner.bal_provider.get_by_range(start, count, &self.inner.metrics.bal_metrics) } } @@ -1290,6 +1494,10 @@ where block_hashes: Vec, ) -> RpcResult> { trace!(target: "rpc::engine", "Serving engine_getBALsByHashV1"); + let len = block_hashes.len() as u64; + if len > MAX_BAL_REQUEST_LIMIT { + return Err(EngineApiError::PayloadRequestTooLarge { len }.into()) + } Ok(self.get_bals_by_hash(block_hashes)) } @@ -1302,7 +1510,12 @@ where count: U64, ) -> RpcResult> { trace!(target: "rpc::engine", "Serving engine_getBALsByRangeV1"); - Ok(self.get_bals_by_range(start.to(), count.to())) + let start = start.to(); + let count = count.to(); + if count > MAX_BAL_REQUEST_LIMIT { + return Err(EngineApiError::PayloadRequestTooLarge { len: count }.into()) + } + Ok(self.get_bals_by_range(start, count)) } } @@ -1362,8 +1575,8 @@ struct EngineApiInner bool + Send + Sync>, - /// Cache for Block Access Lists (BALs) per EIP-7928. - bal_cache: BalCache, + /// Block Access List (BAL) provider with cache-first fallback semantics. + bal_provider: BalProvider, } #[cfg(test)] @@ -1652,4 +1865,194 @@ mod tests { assert_eq!(res, expected); } } + + mod bal_queries { + use super::*; + use alloy_rpc_types_engine::ClientCode; + use parking_lot::{Mutex, RwLock}; + use std::{ + collections::{BTreeMap, HashMap}, + future::Future, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + }; + + #[derive(Debug, Clone, Default)] + struct TestBalStore { + by_hash: Arc>>, + by_number: Arc>>, + hash_queries: Arc, + range_queries: Arc>>, + } + + impl TestBalStore { + fn insert_raw(&self, block_hash: BlockHash, block_number: BlockNumber, bal: Bytes) { + self.by_hash.write().insert(block_hash, bal); + self.by_number.write().insert(block_number, block_hash); + } + + fn hash_query_count(&self) -> usize { + self.hash_queries.load(Ordering::Relaxed) + } + + fn range_queries(&self) -> Vec<(BlockNumber, u64)> { + self.range_queries.lock().clone() + } + } + + impl BalStore for TestBalStore { + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + self.insert_raw(block_hash, block_number, bal); + Ok(()) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError> { + self.hash_queries.fetch_add(1, Ordering::Relaxed); + let by_hash = self.by_hash.read(); + Ok(block_hashes.iter().map(|hash| by_hash.get(hash).cloned()).collect()) + } + + fn get_by_range( + &self, + start: BlockNumber, + count: u64, + ) -> Result, BalStoreError> { + self.range_queries.lock().push((start, count)); + let by_hash = self.by_hash.read(); + let by_number = self.by_number.read(); + let mut result = Vec::new(); + + for block_number in start..start.saturating_add(count) { + let Some(hash) = by_number.get(&block_number) else { + break; + }; + let Some(bal) = by_hash.get(hash) else { + break; + }; + result.push(bal.clone()); + } + Ok(result) + } + } + + fn setup_engine_api_with_store( + bal_store: Arc, + ) -> EngineApi< + Arc, + EthEngineTypes, + NoopTransactionPool, + EthereumEngineValidator, + ChainSpec, + > { + let chain_spec: Arc = MAINNET.clone(); + let provider = Arc::new(MockEthProvider::default()); + let payload_store = spawn_test_payload_service(); + let (to_engine, _) = unbounded_channel(); + + EngineApi::with_bal_store( + provider, + chain_spec.clone(), + ConsensusEngineHandle::new(to_engine), + payload_store.into(), + NoopTransactionPool::default(), + Box::::default(), + ClientVersionV1 { + code: ClientCode::RH, + name: "Reth".to_string(), + version: "v0.0.0-test".to_string(), + commit: "test".to_string(), + }, + EngineCapabilities::default(), + EthereumEngineValidator::new(chain_spec), + false, + NoopNetwork::default(), + bal_store, + ) + } + + fn run_with_tokio_runtime(test: impl Future) { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to build tokio runtime for test"); + runtime.block_on(test); + } + + #[test] + fn by_hash_uses_partial_store_fallback() { + run_with_tokio_runtime(async { + let store = TestBalStore::default(); + let hash1 = B256::random(); + let hash2 = B256::random(); + let hash3 = B256::random(); + let bal1 = Bytes::from_static(b"bal1"); + let bal2 = Bytes::from_static(b"bal2"); + + store.insert_raw(hash2, 2, bal2.clone()); + let api = setup_engine_api_with_store(Arc::new(store.clone())); + api.bal_cache().insert(hash1, 1, bal1.clone()); + + let results = api.get_bals_by_hash(vec![hash1, hash2, hash3]); + assert_eq!(results, vec![bal1, bal2, Bytes::new()]); + assert_eq!(store.hash_query_count(), 1); + }); + } + + #[test] + fn by_range_fallback_queries_only_missing_suffix() { + run_with_tokio_runtime(async { + let store = TestBalStore::default(); + let hash1 = B256::random(); + let hash2 = B256::random(); + let hash3 = B256::random(); + let hash4 = B256::random(); + let hash5 = B256::random(); + + let bal1 = Bytes::from_static(b"bal1"); + let bal2 = Bytes::from_static(b"bal2"); + let bal3 = Bytes::from_static(b"bal3"); + let bal4 = Bytes::from_static(b"bal4"); + let bal5 = Bytes::from_static(b"bal5"); + + store.insert_raw(hash3, 3, bal3.clone()); + store.insert_raw(hash4, 4, bal4.clone()); + store.insert_raw(hash5, 5, bal5.clone()); + + let api = setup_engine_api_with_store(Arc::new(store.clone())); + api.bal_cache().insert(hash1, 1, bal1.clone()); + api.bal_cache().insert(hash2, 2, bal2.clone()); + + let results = api.get_bals_by_range(1, 5); + assert_eq!(results, vec![bal1, bal2, bal3, bal4, bal5]); + assert_eq!(store.range_queries(), vec![(3, 3)]); + }); + } + + #[test] + fn bal_requests_too_large() { + run_with_tokio_runtime(async { + let (_, api) = setup_engine_api(); + + let hashes = vec![B256::ZERO; (MAX_BAL_REQUEST_LIMIT + 1) as usize]; + let err = api.get_bals_by_hash_v1(hashes).await.unwrap_err(); + assert_eq!(err.code(), crate::error::REQUEST_TOO_LARGE_CODE); + + let err = api + .get_bals_by_range_v1(U64::from(1_u64), U64::from(MAX_BAL_REQUEST_LIMIT + 1)) + .await + .unwrap_err(); + assert_eq!(err.code(), crate::error::REQUEST_TOO_LARGE_CODE); + }); + } + } } diff --git a/crates/rpc/rpc-engine-api/src/lib.rs b/crates/rpc/rpc-engine-api/src/lib.rs index 044517d7ae3..677d764df7b 100644 --- a/crates/rpc/rpc-engine-api/src/lib.rs +++ b/crates/rpc/rpc-engine-api/src/lib.rs @@ -16,6 +16,13 @@ mod engine_api; mod bal_cache; pub use bal_cache::BalCache; +/// Block Access List (BAL) storage abstraction and implementations. +pub mod bal_store; +pub use bal_store::{ + BalStore, BalStoreError, DiskFileBalStore, DiskFileBalStoreConfig, + DEFAULT_MAX_BAL_STORE_ENTRIES, +}; + /// Engine API capabilities. pub mod capabilities; pub use capabilities::EngineCapabilities; diff --git a/crates/rpc/rpc-engine-api/src/metrics.rs b/crates/rpc/rpc-engine-api/src/metrics.rs index d2ec2d3e083..12b746e662b 100644 --- a/crates/rpc/rpc-engine-api/src/metrics.rs +++ b/crates/rpc/rpc-engine-api/src/metrics.rs @@ -8,6 +8,8 @@ pub(crate) struct EngineApiMetrics { pub(crate) latency: EngineApiLatencyMetrics, /// Blob-related metrics pub(crate) blob_metrics: BlobMetrics, + /// BAL query metrics. + pub(crate) bal_metrics: BalQueryMetrics, } /// Beacon consensus engine latency metrics. @@ -66,3 +68,24 @@ pub(crate) struct BlobMetrics { /// Number of times getBlobsV2 responded with “miss” pub(crate) get_blobs_requests_failure_total: Counter, } + +#[derive(Metrics)] +#[metrics(scope = "engine.rpc.bal")] +pub(crate) struct BalQueryMetrics { + /// Number of by-hash queries that required store fallback. + pub(crate) store_hash_fallback_requests: Counter, + /// Number of BAL entries recovered from store during by-hash fallback. + pub(crate) store_hash_fallback_hits: Counter, + /// Number of BAL entries still missing after by-hash fallback. + pub(crate) store_hash_fallback_misses: Counter, + /// Number of store errors during by-hash fallback. + pub(crate) store_hash_fallback_errors: Counter, + /// Number of by-range queries that required store fallback. + pub(crate) store_range_fallback_requests: Counter, + /// Number of BAL entries recovered from store during by-range fallback. + pub(crate) store_range_fallback_hits: Counter, + /// Number of BAL entries still missing after by-range fallback. + pub(crate) store_range_fallback_misses: Counter, + /// Number of store errors during by-range fallback. + pub(crate) store_range_fallback_errors: Counter, +} From 130c88094c5e8dc022308dd988cf5a5e1c45c5f0 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 16:18:25 +0800 Subject: [PATCH 08/36] add comments --- crates/rpc/rpc-engine-api/src/engine_api.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index bca427a0998..ac91d260d80 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -69,6 +69,10 @@ impl BalProvider { &self.cache } + // Persist first: store is the source of truth. We only populate the in-memory cache if + // durability succeeds, so cache visibility cannot outlive failed persistence. + // `Bytes` is consumed by each insert call, so we clone once for store and move the original + // into cache. fn cache_bal( &self, block_hash: BlockHash, @@ -80,6 +84,9 @@ impl BalProvider { Ok(()) } + // Intent: serve BAL hash queries with cache-first latency while preserving + // request-order semantics and filling only missing entries from durable storage. + // Cache-first lookup: keep request order and fill only cache misses from durable storage. fn get_by_hashes( &self, block_hashes: &[BlockHash], @@ -87,6 +94,7 @@ impl BalProvider { ) -> Vec> { let mut results = self.cache.get_by_hashes(block_hashes); + // Collect missing positions so store fallback can patch holes in-place. let mut missing_hashes = Vec::new(); let mut missing_indices = Vec::new(); for (idx, result) in results.iter().enumerate() { @@ -133,6 +141,9 @@ impl BalProvider { results } + // Intent: serve contiguous BAL range queries with cache-first latency and + // append-only fallback from store, so the returned slice remains ordered and gap-safe. + // Cache range reads are contiguous and stop at the first gap. fn get_by_range( &self, start: BlockNumber, @@ -144,6 +155,7 @@ impl BalProvider { return cache_results; } + // Only the missing suffix can be queried from store, which avoids re-reading cached prefix. let cached_len = cache_results.len() as u64; let missing_start = start.saturating_add(cached_len); let missing_count = count - cached_len; From 0e2ea8caa9c2be3276669edbdd3e28cd4a827bd6 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 16:57:41 +0800 Subject: [PATCH 09/36] re-org structs --- crates/rpc/rpc-engine-api/src/bal_store/disk.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/bal_store/disk.rs b/crates/rpc/rpc-engine-api/src/bal_store/disk.rs index 51b1c19699e..a48efdd2786 100644 --- a/crates/rpc/rpc-engine-api/src/bal_store/disk.rs +++ b/crates/rpc/rpc-engine-api/src/bal_store/disk.rs @@ -47,6 +47,12 @@ pub struct DiskFileBalStore { inner: Arc, } +#[derive(Debug, Default)] +struct IndexState { + block_to_hash: BTreeMap, + hash_to_block: HashMap, +} + #[derive(Debug)] struct DiskFileBalStoreInner { root_dir: PathBuf, @@ -56,12 +62,6 @@ struct DiskFileBalStoreInner { state: Mutex, } -#[derive(Debug, Default)] -struct IndexState { - block_to_hash: BTreeMap, - hash_to_block: HashMap, -} - impl DiskFileBalStore { /// Opens (or creates) a disk BAL store and rebuilds in-memory indexes. pub fn open( From 6d1bbc9a5f4fa7c9efbd6210e346b0cee44aef35 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 18:51:23 +0800 Subject: [PATCH 10/36] refactor --- Cargo.lock | 13 +++++ Cargo.toml | 2 + crates/net/bal-store/Cargo.toml | 22 +++++++++ .../bal_store => net/bal-store/src}/disk.rs | 14 +++--- crates/net/bal-store/src/lib.rs | 42 +++++++++++++++++ crates/node/builder/Cargo.toml | 1 + crates/node/builder/src/rpc.rs | 5 +- crates/rpc/rpc-engine-api/Cargo.toml | 1 + .../rpc/rpc-engine-api/src/bal_store/mod.rs | 47 +++---------------- 9 files changed, 96 insertions(+), 51 deletions(-) create mode 100644 crates/net/bal-store/Cargo.toml rename crates/{rpc/rpc-engine-api/src/bal_store => net/bal-store/src}/disk.rs (95%) create mode 100644 crates/net/bal-store/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a1eef9b9dc5..15d8a8aac80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7677,6 +7677,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-bal-store" +version = "1.10.2" +dependencies = [ + "alloy-primitives", + "parking_lot", + "tempfile", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "reth-basic-payload-builder" version = "1.10.2" @@ -9358,6 +9369,7 @@ dependencies = [ "jsonrpsee", "parking_lot", "rayon", + "reth-bal-store", "reth-basic-payload-builder", "reth-chain-state", "reth-chainspec", @@ -10629,6 +10641,7 @@ dependencies = [ "jsonrpsee-types", "metrics", "parking_lot", + "reth-bal-store", "reth-chainspec", "reth-engine-primitives", "reth-ethereum-engine-primitives", diff --git a/Cargo.toml b/Cargo.toml index bc5df15fb5e..26d1dd3fdfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "crates/exex/test-utils/", "crates/exex/types/", "crates/metrics/", + "crates/net/bal-store/", "crates/net/banlist/", "crates/net/discv4/", "crates/net/discv5/", @@ -398,6 +399,7 @@ reth-ipc = { path = "crates/rpc/ipc" } reth-libmdbx = { path = "crates/storage/libmdbx-rs" } reth-mdbx-sys = { path = "crates/storage/libmdbx-rs/mdbx-sys" } reth-metrics = { path = "crates/metrics" } +reth-bal-store = { path = "crates/net/bal-store" } reth-net-banlist = { path = "crates/net/banlist" } reth-net-nat = { path = "crates/net/nat" } reth-network = { path = "crates/net/network" } diff --git a/crates/net/bal-store/Cargo.toml b/crates/net/bal-store/Cargo.toml new file mode 100644 index 00000000000..39bf1a51b2e --- /dev/null +++ b/crates/net/bal-store/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "reth-bal-store" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Shared BAL storage abstractions and implementations" + +[lints] +workspace = true + +[dependencies] +alloy-primitives.workspace = true +parking_lot.workspace = true +thiserror.workspace = true +tracing.workspace = true + +[dev-dependencies] +alloy-primitives = { workspace = true, features = ["rand"] } +tempfile.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/bal_store/disk.rs b/crates/net/bal-store/src/disk.rs similarity index 95% rename from crates/rpc/rpc-engine-api/src/bal_store/disk.rs rename to crates/net/bal-store/src/disk.rs index a48efdd2786..83424d7383c 100644 --- a/crates/rpc/rpc-engine-api/src/bal_store/disk.rs +++ b/crates/net/bal-store/src/disk.rs @@ -1,6 +1,6 @@ //! Disk-backed BAL store. -use crate::bal_store::{BalStore, BalStoreError}; +use crate::{BalStore, BalStoreError}; use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use parking_lot::Mutex; use std::{ @@ -124,33 +124,33 @@ impl DiskFileBalStoreInner { let entry = match entry { Ok(entry) => entry, Err(err) => { - debug!(target: "rpc::engine", %err, "Failed to read BAL index dir entry"); + debug!(target: "bal::store", %err, "Failed to read BAL index dir entry"); continue; } }; let path = entry.path(); let Some(file_name) = path.file_name().and_then(|f| f.to_str()) else { - debug!(target: "rpc::engine", ?path, "Skipping BAL index entry with non-utf8 name"); + debug!(target: "bal::store", ?path, "Skipping BAL index entry with non-utf8 name"); continue; }; let Ok(block_number) = file_name.parse::() else { - debug!(target: "rpc::engine", ?path, "Skipping BAL index entry with invalid block number name"); + debug!(target: "bal::store", ?path, "Skipping BAL index entry with invalid block number name"); continue; }; let hash_bytes = match fs::read(&path) { Ok(bytes) => bytes, Err(err) => { - debug!(target: "rpc::engine", ?path, %err, "Failed reading BAL index file"); + debug!(target: "bal::store", ?path, %err, "Failed reading BAL index file"); continue; } }; if hash_bytes.len() != 32 { debug!( - target: "rpc::engine", + target: "bal::store", ?path, len = hash_bytes.len(), "Skipping BAL index file with invalid hash length" @@ -161,7 +161,7 @@ impl DiskFileBalStoreInner { let block_hash = BlockHash::from_slice(&hash_bytes); if !self.entry_file(block_hash).is_file() { debug!( - target: "rpc::engine", + target: "bal::store", block_number, ?block_hash, "Skipping BAL index entry pointing to missing BAL payload file" diff --git a/crates/net/bal-store/src/lib.rs b/crates/net/bal-store/src/lib.rs new file mode 100644 index 00000000000..283870694f0 --- /dev/null +++ b/crates/net/bal-store/src/lib.rs @@ -0,0 +1,42 @@ +//! Storage abstractions and implementations for EIP-7928 Block Access Lists (BALs). + +use alloy_primitives::{BlockHash, BlockNumber, Bytes}; +use std::io; + +pub mod disk; +pub use disk::{DiskFileBalStore, DiskFileBalStoreConfig, DEFAULT_MAX_BAL_STORE_ENTRIES}; + +/// A store for EIP-7928 Block Access Lists (BALs). +/// +/// The store is keyed by block hash and maintains a block-number index for range queries. +/// Implementations should preserve contiguous-range semantics: +/// queries by range stop at the first missing block. +pub trait BalStore: Send + Sync + 'static { + /// Inserts a BAL for the given block hash and number. + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError>; + + /// Returns BALs for each requested block hash in the same order. + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError>; + + /// Returns contiguous BALs in `[start, start + count)` until the first gap. + fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError>; +} + +/// Error variants that can occur when interacting with a BAL store. +#[derive(Debug, thiserror::Error)] +pub enum BalStoreError { + /// Filesystem I/O error. + #[error("BAL store I/O error: {0}")] + Io(#[from] io::Error), + /// Other implementation-specific error. + #[error(transparent)] + Other(Box), +} diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index 82753d943f1..ddbd50f0b7c 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -38,6 +38,7 @@ reth-node-api.workspace = true reth-node-core.workspace = true reth-node-events.workspace = true reth-node-metrics.workspace = true +reth-bal-store.workspace = true reth-payload-builder.workspace = true reth-primitives-traits.workspace = true reth-provider.workspace = true diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index f975d036b80..584496dd260 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -13,6 +13,7 @@ use alloy_rpc_types::engine::ClientVersionV1; use alloy_rpc_types_engine::ExecutionData; use jsonrpsee::{core::middleware::layer::Either, RpcModule}; use parking_lot::Mutex; +use reth_bal_store::{DiskFileBalStore, DiskFileBalStoreConfig}; use reth_chain_state::CanonStateSubscriptions; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks, Hardforks}; use reth_node_api::{ @@ -35,9 +36,7 @@ use reth_rpc_builder::{ config::RethRpcServerConfig, RpcModuleBuilder, RpcRegistryInner, RpcServerConfig, RpcServerHandle, TransportRpcModules, }; -use reth_rpc_engine_api::{ - capabilities::EngineCapabilities, DiskFileBalStore, DiskFileBalStoreConfig, EngineApi, -}; +use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; use reth_rpc_eth_types::{cache::cache_new_blocks_task, EthConfig, EthStateCache}; use reth_tokio_util::EventSender; use reth_tracing::tracing::{debug, info}; diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 5be750788cc..8d3d3772a63 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -24,6 +24,7 @@ reth-engine-primitives.workspace = true reth-transaction-pool.workspace = true reth-primitives-traits.workspace = true reth-network-api.workspace = true +reth-bal-store.workspace = true # ethereum alloy-eips.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/bal_store/mod.rs b/crates/rpc/rpc-engine-api/src/bal_store/mod.rs index 0a62b557cd9..9e890793f83 100644 --- a/crates/rpc/rpc-engine-api/src/bal_store/mod.rs +++ b/crates/rpc/rpc-engine-api/src/bal_store/mod.rs @@ -1,42 +1,7 @@ -//! Storage abstraction for Block Access Lists (BALs). +//! Backward-compatible re-exports for BAL store types. +//! +//! The concrete BAL store implementation lives in `reth-bal-store` so both the +//! Engine API and networking stack can share the same abstraction without +//! introducing a `network -> rpc` dependency. -use alloy_primitives::{BlockHash, BlockNumber, Bytes}; -use std::io; - -pub mod disk; -pub use disk::{DiskFileBalStore, DiskFileBalStoreConfig, DEFAULT_MAX_BAL_STORE_ENTRIES}; - -/// A store for EIP-7928 Block Access Lists (BALs). -/// -/// The store is keyed by block hash and maintains a block-number index for range queries. -/// Implementations should preserve contiguous-range semantics: -/// queries by range stop at the first missing block. -pub trait BalStore: Send + Sync + 'static { - /// Inserts a BAL for the given block hash and number. - fn insert( - &self, - block_hash: BlockHash, - block_number: BlockNumber, - bal: Bytes, - ) -> Result<(), BalStoreError>; - - /// Returns BALs for each requested block hash in the same order. - fn get_by_hashes( - &self, - block_hashes: &[BlockHash], - ) -> Result>, BalStoreError>; - - /// Returns contiguous BALs in `[start, start + count)` until the first gap. - fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError>; -} - -/// Error variants that can occur when interacting with a BAL store. -#[derive(Debug, thiserror::Error)] -pub enum BalStoreError { - /// Filesystem I/O error. - #[error("BAL store I/O error: {0}")] - Io(#[from] io::Error), - /// Other implementation-specific error. - #[error(transparent)] - Other(Box), -} +pub use reth_bal_store::*; From ddd6fb03b52874a3f30792bd5308b71a55c46902 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 19:59:10 +0800 Subject: [PATCH 11/36] share disk-backed BAL store across engine and network --- Cargo.lock | 4 +- crates/net/bal-store/src/lib.rs | 2 + crates/net/bal-store/src/noop.rs | 33 +++++++++++++ crates/net/network/Cargo.toml | 1 + crates/net/network/src/builder.rs | 13 +++++- crates/net/network/src/eth_requests.rs | 49 +++++++++++++++++-- crates/node/api/Cargo.toml | 1 + crates/node/api/src/node.rs | 13 +++++- crates/node/builder/src/builder/mod.rs | 13 +++++- crates/node/builder/src/launch/common.rs | 16 +++++++ crates/node/builder/src/launch/engine.rs | 1 + crates/node/builder/src/rpc.rs | 11 ++--- crates/rpc/rpc-engine-api/src/bal_cache.rs | 4 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 52 --------------------- 14 files changed, 142 insertions(+), 71 deletions(-) create mode 100644 crates/net/bal-store/src/noop.rs diff --git a/Cargo.lock b/Cargo.lock index aba6f67fd29..8d041601235 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7579,7 +7579,7 @@ dependencies = [ [[package]] name = "reth-bal-store" -version = "1.10.2" +version = "1.11.0" dependencies = [ "alloy-primitives", "parking_lot", @@ -9096,6 +9096,7 @@ dependencies = [ "rand 0.8.5", "rand 0.9.2", "rayon", + "reth-bal-store", "reth-chainspec", "reth-consensus", "reth-discv4", @@ -9235,6 +9236,7 @@ version = "1.11.0" dependencies = [ "alloy-rpc-types-engine", "eyre", + "reth-bal-store", "reth-basic-payload-builder", "reth-consensus", "reth-db-api", diff --git a/crates/net/bal-store/src/lib.rs b/crates/net/bal-store/src/lib.rs index 283870694f0..1109b348aac 100644 --- a/crates/net/bal-store/src/lib.rs +++ b/crates/net/bal-store/src/lib.rs @@ -4,7 +4,9 @@ use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use std::io; pub mod disk; +mod noop; pub use disk::{DiskFileBalStore, DiskFileBalStoreConfig, DEFAULT_MAX_BAL_STORE_ENTRIES}; +pub use noop::NoopBalStore; /// A store for EIP-7928 Block Access Lists (BALs). /// diff --git a/crates/net/bal-store/src/noop.rs b/crates/net/bal-store/src/noop.rs new file mode 100644 index 00000000000..872db1a1c12 --- /dev/null +++ b/crates/net/bal-store/src/noop.rs @@ -0,0 +1,33 @@ +//! No-op BAL store implementation. + +use crate::{BalStore, BalStoreError}; +use alloy_primitives::{BlockHash, BlockNumber, Bytes}; + +/// A no-op BAL store. +/// +/// This implementation never persists data and always returns cache-miss semantics: +/// hash lookups return `None` and range lookups return an empty result. +#[derive(Clone, Debug, Default)] +pub struct NoopBalStore; + +impl BalStore for NoopBalStore { + fn insert( + &self, + _block_hash: BlockHash, + _block_number: BlockNumber, + _bal: Bytes, + ) -> Result<(), BalStoreError> { + Ok(()) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError> { + Ok(vec![None; block_hashes.len()]) + } + + fn get_by_range(&self, _start: BlockNumber, _count: u64) -> Result, BalStoreError> { + Ok(Vec::new()) + } +} diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 9eb7718e5a5..8cf354e3013 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -34,6 +34,7 @@ reth-tokio-util.workspace = true reth-consensus.workspace = true reth-network-peers = { workspace = true, features = ["net"] } reth-network-types.workspace = true +reth-bal-store.workspace = true # ethereum alloy-consensus.workspace = true diff --git a/crates/net/network/src/builder.rs b/crates/net/network/src/builder.rs index 8c8ebd8f1e5..afc25039437 100644 --- a/crates/net/network/src/builder.rs +++ b/crates/net/network/src/builder.rs @@ -11,9 +11,11 @@ use crate::{ }, NetworkHandle, NetworkManager, }; +use reth_bal_store::{BalStore, NoopBalStore}; use reth_eth_wire::{EthNetworkPrimitives, NetworkPrimitives}; use reth_network_api::test_utils::PeersHandleProvider; use reth_transaction_pool::TransactionPool; +use std::sync::Arc; use tokio::sync::mpsc; /// We set the max channel capacity of the `EthRequestHandler` to 256 @@ -63,12 +65,21 @@ impl NetworkBuilder { pub fn request_handler( self, client: Client, + ) -> NetworkBuilder, N> { + self.request_handler_with_bal_store(client, Arc::new(NoopBalStore)) + } + + /// Creates a new [`EthRequestHandler`] with a custom BAL store and wires it to the network. + pub fn request_handler_with_bal_store( + self, + client: Client, + bal_store: Arc, ) -> NetworkBuilder, N> { let Self { mut network, transactions, .. } = self; let (tx, rx) = mpsc::channel(ETH_REQUEST_CHANNEL_CAPACITY); network.set_eth_request_handler(tx); let peers = network.handle().peers_handle().clone(); - let request_handler = EthRequestHandler::new(client, peers, rx); + let request_handler = EthRequestHandler::with_bal_store(client, peers, rx, bal_store); NetworkBuilder { network, request_handler, transactions } } diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 1b28af56a9b..59d82f1b272 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -9,6 +9,7 @@ use alloy_eips::BlockHashOrNumber; use alloy_primitives::Bytes; use alloy_rlp::Encodable; use futures::StreamExt; +use reth_bal_store::{BalStore, NoopBalStore}; use reth_eth_wire::{ BlockAccessLists, BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockAccessLists, GetBlockBodies, GetBlockHeaders, GetNodeData, GetReceipts, GetReceipts70, HeadersDirection, @@ -20,13 +21,16 @@ use reth_network_peers::PeerId; use reth_primitives_traits::Block; use reth_storage_api::{BlockReader, HeaderProvider}; use std::{ + fmt, future::Future, pin::Pin, + sync::Arc, task::{Context, Poll}, time::Duration, }; use tokio::sync::{mpsc::Receiver, oneshot}; use tokio_stream::wrappers::ReceiverStream; +use tracing::debug; // Limits: @@ -52,7 +56,6 @@ pub const SOFT_RESPONSE_LIMIT: usize = 2 * 1024 * 1024; /// Manages eth related requests on top of the p2p network. /// /// This can be spawned to another task and is supposed to be run as background service. -#[derive(Debug)] #[must_use = "Manager does nothing unless polled."] pub struct EthRequestHandler { /// The client type that can interact with the chain. @@ -63,18 +66,37 @@ pub struct EthRequestHandler { peers: PeersHandle, /// Incoming request from the [`NetworkManager`](crate::NetworkManager). incoming_requests: ReceiverStream>, + /// Shared BAL store used to serve `GetBlockAccessLists` requests. + bal_store: Arc, /// Metrics for the eth request handler. metrics: EthRequestHandlerMetrics, } +impl fmt::Debug for EthRequestHandler { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EthRequestHandler").finish_non_exhaustive() + } +} + // === impl EthRequestHandler === impl EthRequestHandler { - /// Create a new instance + /// Create a new instance with a no-op BAL store. pub fn new(client: C, peers: PeersHandle, incoming: Receiver>) -> Self { + Self::with_bal_store(client, peers, incoming, Arc::new(NoopBalStore)) + } + + /// Create a new instance with an explicit BAL store. + pub fn with_bal_store( + client: C, + peers: PeersHandle, + incoming: Receiver>, + bal_store: Arc, + ) -> Self { Self { client, peers, incoming_requests: ReceiverStream::new(incoming), + bal_store, metrics: Default::default(), } } @@ -291,7 +313,28 @@ where request: GetBlockAccessLists, response: oneshot::Sender>, ) { - let access_lists = request.0.into_iter().map(|_| Bytes::new()).collect(); + let block_hashes = request.0; + let requested_len = block_hashes.len(); + let mut access_lists = match self.bal_store.get_by_hashes(&block_hashes) { + Ok(bals) => bals.into_iter().map(|bal| bal.unwrap_or_else(Bytes::new)).collect(), + Err(err) => { + debug!( + target: "net::eth", + %err, + requested_len, + "Failed to read BALs from store, returning empty BALs" + ); + vec![Bytes::new(); requested_len] + } + }; + + // Enforce one response entry per requested hash. + if access_lists.len() < requested_len { + access_lists.resize(requested_len, Bytes::new()); + } else if access_lists.len() > requested_len { + access_lists.truncate(requested_len); + } + let _ = response.send(Ok(BlockAccessLists(access_lists))); } diff --git a/crates/node/api/Cargo.toml b/crates/node/api/Cargo.toml index 7bd6196318d..8b810ddccd2 100644 --- a/crates/node/api/Cargo.toml +++ b/crates/node/api/Cargo.toml @@ -17,6 +17,7 @@ reth-db-api.workspace = true reth-consensus.workspace = true reth-evm.workspace = true reth-provider.workspace = true +reth-bal-store.workspace = true reth-engine-primitives.workspace = true reth-transaction-pool.workspace = true reth-payload-builder.workspace = true diff --git a/crates/node/api/src/node.rs b/crates/node/api/src/node.rs index d4e6aa4030f..bbad3fc5591 100644 --- a/crates/node/api/src/node.rs +++ b/crates/node/api/src/node.rs @@ -2,6 +2,7 @@ use crate::PayloadTypes; use alloy_rpc_types_engine::JwtSecret; +use reth_bal_store::BalStore; use reth_basic_payload_builder::PayloadBuilder; use reth_consensus::FullConsensus; use reth_db_api::{database_metrics::DatabaseMetrics, Database}; @@ -15,7 +16,7 @@ use reth_provider::FullProvider; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use std::{fmt::Debug, future::Future, marker::PhantomData}; +use std::{fmt, fmt::Debug, future::Future, marker::PhantomData, sync::Arc}; /// A helper trait that is downstream of the [`NodeTypes`] trait and adds stateful /// components to the node. @@ -103,7 +104,7 @@ pub trait FullNodeComponents: FullNodeTypes + Clone + 'static { } /// Context passed to [`NodeAddOns::launch_add_ons`], -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct AddOnsContext<'a, N: FullNodeComponents> { /// Node with all configured components. pub node: N, @@ -115,6 +116,14 @@ pub struct AddOnsContext<'a, N: FullNodeComponents> { pub engine_events: EventSender::Primitives>>, /// JWT secret for the node. pub jwt_secret: JwtSecret, + /// Shared BAL store used by both Engine API and network request handling. + pub bal_store: Arc, +} + +impl fmt::Debug for AddOnsContext<'_, N> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("AddOnsContext").finish_non_exhaustive() + } } /// Customizable node add-on types. diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 09f08ad82b3..c051e96e137 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -11,6 +11,7 @@ use crate::{ }; use alloy_eips::eip4844::env_settings::EnvKzgSettings; use futures::Future; +use reth_bal_store::BalStore; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_db_api::{database::Database, database_metrics::DatabaseMetrics}; use reth_exex::ExExContext; @@ -736,6 +737,8 @@ pub struct BuilderContext { pub(crate) executor: TaskExecutor, /// Config container pub(crate) config_container: WithConfigs<::ChainSpec>, + /// Shared BAL store used by network handlers and RPC Engine API. + pub(crate) bal_store: Arc, } impl BuilderContext { @@ -745,8 +748,9 @@ impl BuilderContext { provider: Node::Provider, executor: TaskExecutor, config_container: WithConfigs<::ChainSpec>, + bal_store: Arc, ) -> Self { - Self { head, provider, executor, config_container } + Self { head, provider, executor, config_container, bal_store } } /// Returns the configured provider to interact with the blockchain. @@ -781,6 +785,11 @@ impl BuilderContext { &self.executor } + /// Returns the shared BAL store. + pub fn bal_store(&self) -> &Arc { + &self.bal_store + } + /// Returns the chain spec of the node. pub fn chain_spec(&self) -> Arc<::ChainSpec> { self.provider().chain_spec() @@ -900,7 +909,7 @@ impl BuilderContext { { let (handle, network, txpool, eth) = builder .transactions_with_policies(pool, tx_config, propagation_policy, announcement_policy) - .request_handler(self.provider().clone()) + .request_handler_with_bal_store(self.provider().clone(), self.bal_store().clone()) .split_with_handle(); self.executor.spawn_critical_blocking_task("p2p txpool", Box::pin(txpool)); diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 1cc3ed125f7..32c719aabbf 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -38,6 +38,7 @@ use alloy_eips::eip2124::Head; use alloy_primitives::{BlockNumber, B256}; use eyre::Context; use rayon::ThreadPoolBuilder; +use reth_bal_store::{BalStore, DiskFileBalStore, DiskFileBalStoreConfig}; use reth_chainspec::{Chain, EthChainSpec, EthereumHardforks}; use reth_config::{config::EtlConfig, PruneConfig}; use reth_consensus::noop::NoopConsensus; @@ -787,12 +788,20 @@ where { // fetch the head block from the database let head = self.lookup_head()?; + let bal_store = Arc::new( + DiskFileBalStore::open( + self.node_config().datadir().balstore(), + DiskFileBalStoreConfig::default(), + ) + .wrap_err("failed to open BAL store")?, + ) as Arc; let builder_ctx = BuilderContext::new( head, self.blockchain_db().clone(), self.task_executor().clone(), self.configs().clone(), + bal_store.clone(), ); debug!(target: "reth::cli", "creating components"); @@ -816,6 +825,7 @@ where }, node_adapter, head, + bal_store, }; let ctx = LaunchContextWith { @@ -1003,6 +1013,11 @@ where &self.node_adapter().components } + /// Returns the shared BAL store instance. + pub fn bal_store(&self) -> &Arc { + &self.right().bal_store + } + /// Launches ExEx (Execution Extensions) and returns the ExEx manager handle. #[allow(clippy::type_complexity)] pub async fn launch_exex( @@ -1234,6 +1249,7 @@ where db_provider_container: WithMeteredProvider>, node_adapter: NodeAdapter, head: Head, + bal_store: Arc, } /// Returns the metrics hooks for the node. diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 31a21d16684..209747e4f7e 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -187,6 +187,7 @@ impl EngineNodeLauncher { beacon_engine_handle: beacon_engine_handle.clone(), jwt_secret, engine_events: event_sender.clone(), + bal_store: ctx.bal_store().clone(), }; let validator_builder = add_ons.engine_validator_builder(); diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 2a44940b1ae..a72f0cf664e 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -13,7 +13,6 @@ use alloy_rpc_types::engine::ClientVersionV1; use alloy_rpc_types_engine::ExecutionData; use jsonrpsee::{core::middleware::layer::Either, RpcModule}; use parking_lot::Mutex; -use reth_bal_store::{DiskFileBalStore, DiskFileBalStoreConfig}; use reth_chain_state::CanonStateSubscriptions; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks, Hardforks}; use reth_node_api::{ @@ -981,7 +980,8 @@ where let Self { eth_api_builder, engine_api_builder, hooks, .. } = self; let engine_api = engine_api_builder.build_engine_api(&ctx).await?; - let AddOnsContext { node, config, beacon_engine_handle, jwt_secret, engine_events } = ctx; + let AddOnsContext { node, config, beacon_engine_handle, jwt_secret, engine_events, .. } = + ctx; info!(target: "reth::cli", "Engine API handler initialized"); @@ -1398,11 +1398,6 @@ where commit: version_metadata().vergen_git_sha.to_string(), }; - let bal_store = DiskFileBalStore::open( - ctx.config.datadir().balstore(), - DiskFileBalStoreConfig::default(), - )?; - Ok(EngineApi::with_bal_store( ctx.node.provider().clone(), ctx.config.chain.clone(), @@ -1415,7 +1410,7 @@ where engine_validator, ctx.config.engine.accept_execution_requests_hash, ctx.node.network().clone(), - Arc::new(bal_store), + ctx.bal_store.clone(), )) } } diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index 63a9fdf0046..541dac184e8 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -1,8 +1,8 @@ //! Block Access List (BAL) cache for EIP-7928. //! //! This module provides an in-memory cache for storing Block Access Lists received via -//! the Engine API. BALs are stored for valid payloads and can be retrieved via -//! `engine_getBALsByHashV1` and `engine_getBALsByRangeV1`. +//! the Engine API. BALs are stored for valid payloads and can be retrieved through +//! Engine API BAL query paths that read from the cache/store provider. //! //! According to EIP-7928, the EL MUST retain BALs for at least the duration of the //! weak subjectivity period (~3533 epochs) to support synchronization with re-execution. diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 73f545a91e2..6cd62cf4604 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -51,10 +51,6 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024; /// The upper limit for blobs in `engine_getBlobsVx`. const MAX_BLOB_LIMIT: usize = 128; -/// The upper limit for BAL requests in `engine_getBALsByHashV1` and -/// `engine_getBALsByRangeV1`. -const MAX_BAL_REQUEST_LIMIT: u64 = 1024; - #[derive(Clone)] struct BalProvider { store: Arc, @@ -1579,37 +1575,6 @@ where trace!(target: "rpc::engine", "Serving engine_getBlobsV3"); Ok(self.get_blobs_v3_metered(versioned_hashes)?) } - /// Handler for `engine_getBALsByHashV1` - /// - /// See also - async fn get_bals_by_hash_v1( - &self, - block_hashes: Vec, - ) -> RpcResult> { - trace!(target: "rpc::engine", "Serving engine_getBALsByHashV1"); - let len = block_hashes.len() as u64; - if len > MAX_BAL_REQUEST_LIMIT { - return Err(EngineApiError::PayloadRequestTooLarge { len }.into()) - } - Ok(self.get_bals_by_hash(block_hashes)) - } - - /// Handler for `engine_getBALsByRangeV1` - /// - /// See also - async fn get_bals_by_range_v1( - &self, - start: U64, - count: U64, - ) -> RpcResult> { - trace!(target: "rpc::engine", "Serving engine_getBALsByRangeV1"); - let start = start.to(); - let count = count.to(); - if count > MAX_BAL_REQUEST_LIMIT { - return Err(EngineApiError::PayloadRequestTooLarge { len: count }.into()) - } - Ok(self.get_bals_by_range(start, count)) - } } impl IntoEngineApiRpcModule @@ -2130,22 +2095,5 @@ mod tests { assert_eq!(store.range_queries(), vec![(3, 3)]); }); } - - #[test] - fn bal_requests_too_large() { - run_with_tokio_runtime(async { - let (_, api) = setup_engine_api(); - - let hashes = vec![B256::ZERO; (MAX_BAL_REQUEST_LIMIT + 1) as usize]; - let err = api.get_bals_by_hash_v1(hashes).await.unwrap_err(); - assert_eq!(err.code(), crate::error::REQUEST_TOO_LARGE_CODE); - - let err = api - .get_bals_by_range_v1(U64::from(1_u64), U64::from(MAX_BAL_REQUEST_LIMIT + 1)) - .await - .unwrap_err(); - assert_eq!(err.code(), crate::error::REQUEST_TOO_LARGE_CODE); - }); - } } } From f7fb79b39bd767dc8f4aced7d303d6c3337950cf Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 20:04:01 +0800 Subject: [PATCH 12/36] add comments --- crates/net/bal-store/src/disk.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/crates/net/bal-store/src/disk.rs b/crates/net/bal-store/src/disk.rs index 83424d7383c..fc4b4994d75 100644 --- a/crates/net/bal-store/src/disk.rs +++ b/crates/net/bal-store/src/disk.rs @@ -89,6 +89,7 @@ impl DiskFileBalStore { } impl BalStore for DiskFileBalStore { + /// Delegates insertion to the shared inner implementation. fn insert( &self, block_hash: BlockHash, @@ -98,6 +99,7 @@ impl BalStore for DiskFileBalStore { self.inner.insert(block_hash, block_number, bal) } + /// Delegates by-hash reads to the shared inner implementation. fn get_by_hashes( &self, block_hashes: &[BlockHash], @@ -105,12 +107,17 @@ impl BalStore for DiskFileBalStore { self.inner.get_by_hashes(block_hashes) } + /// Delegates contiguous range reads to the shared inner implementation. fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { self.inner.get_by_range(start, count) } } impl DiskFileBalStoreInner { + /// Rebuilds in-memory indexes from disk. + /// + /// Malformed index files or orphaned entries are skipped instead of failing startup, then + /// capacity eviction is re-applied to reestablish retention guarantees. fn rebuild_indexes(&self) -> Result<(), BalStoreError> { let mut indexed = Vec::new(); @@ -190,6 +197,10 @@ impl DiskFileBalStoreInner { Ok(()) } + /// Persists a BAL payload and updates both in-memory and on-disk indexes. + /// + /// This also handles reorg/relocation semantics by removing stale index or payload files + /// that conflict with the new `(block_number, block_hash)` mapping. fn insert( &self, block_hash: BlockHash, @@ -223,6 +234,9 @@ impl DiskFileBalStoreInner { self.evict_over_capacity(&mut state) } + /// Returns BAL bytes for each requested hash in request order. + /// + /// Missing files are mapped to `None`; other filesystem errors are surfaced. fn get_by_hashes( &self, block_hashes: &[BlockHash], @@ -240,6 +254,10 @@ impl DiskFileBalStoreInner { .collect() } + /// Returns contiguous BAL bytes for `[start, start + count)`. + /// + /// The method first resolves the contiguous hash sequence from the in-memory block index, + /// then reads payload bytes from disk and stops at the first missing payload file. fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { let hashes: Vec = { let state = self.state.lock(); @@ -264,6 +282,9 @@ impl DiskFileBalStoreInner { Ok(result) } + /// Evicts oldest block-number entries until the configured capacity is satisfied. + /// + /// Eviction removes both entry payload files and index files to keep disk and memory in sync. fn evict_over_capacity(&self, state: &mut IndexState) -> Result<(), BalStoreError> { while state.block_to_hash.len() as u32 > self.max_entries { let Some((&oldest_number, &oldest_hash)) = state.block_to_hash.first_key_value() else { @@ -279,6 +300,9 @@ impl DiskFileBalStoreInner { Ok(()) } + /// Deletes a file if it exists. + /// + /// `NotFound` is treated as success to simplify idempotent cleanup paths. fn remove_if_exists(&self, path: PathBuf) -> Result<(), BalStoreError> { match fs::remove_file(path) { Ok(()) => Ok(()), @@ -287,11 +311,13 @@ impl DiskFileBalStoreInner { } } + /// Returns the payload file path for a block hash. #[inline] fn entry_file(&self, block_hash: BlockHash) -> PathBuf { self.entries_dir.join(format!("{block_hash:x}")) } + /// Returns the index file path for a block number. #[inline] fn index_file(&self, block_number: BlockNumber) -> PathBuf { self.index_dir.join(block_number.to_string()) From 465fcf3da2627aeaac635cbb0fd02979b96e0d39 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 20:23:05 +0800 Subject: [PATCH 13/36] fix clippy --- crates/rpc/rpc-engine-api/src/bal_cache.rs | 6 +++--- crates/rpc/rpc-engine-api/src/engine_api.rs | 22 ++++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/rpc/rpc-engine-api/src/bal_cache.rs index 541dac184e8..ce7af6a3900 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/rpc/rpc-engine-api/src/bal_cache.rs @@ -162,7 +162,7 @@ impl BalStore for BalCache { block_number: BlockNumber, bal: Bytes, ) -> Result<(), BalStoreError> { - BalCache::insert(self, block_hash, block_number, bal); + Self::insert(self, block_hash, block_number, bal); Ok(()) } @@ -170,11 +170,11 @@ impl BalStore for BalCache { &self, block_hashes: &[BlockHash], ) -> Result>, BalStoreError> { - Ok(BalCache::get_by_hashes(self, block_hashes)) + Ok(Self::get_by_hashes(self, block_hashes)) } fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { - Ok(BalCache::get_by_range(self, start, count)) + Ok(Self::get_by_range(self, start, count)) } } diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 6cd62cf4604..20f433e9c3b 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -62,7 +62,7 @@ impl BalProvider { Self { store, cache } } - fn cache(&self) -> &BalCache { + const fn cache(&self) -> &BalCache { &self.cache } @@ -358,18 +358,16 @@ where /// Caches the BAL if the status is valid. fn maybe_cache_bal(&self, num_hash: BlockNumHash, bal: Option, status: &PayloadStatus) { if status.is_valid() && - let Some(bal) = bal + let Some(bal) = bal && + let Err(err) = self.inner.bal_provider.cache_bal(num_hash.hash, num_hash.number, bal) { - if let Err(err) = self.inner.bal_provider.cache_bal(num_hash.hash, num_hash.number, bal) - { - warn!( - target: "rpc::engine", - ?err, - block_hash = ?num_hash.hash, - block_number = num_hash.number, - "Failed to persist BAL into BAL store" - ); - } + warn!( + target: "rpc::engine", + ?err, + block_hash = ?num_hash.hash, + block_number = num_hash.number, + "Failed to persist BAL into BAL store" + ); } } From 0b0199e7d96d3016f2d5e0c55674b24aeda34026 Mon Sep 17 00:00:00 2001 From: Karl Date: Sat, 14 Feb 2026 20:59:31 +0800 Subject: [PATCH 14/36] comment outdated --- crates/net/network/src/eth_requests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 59d82f1b272..8df3a08e63f 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -306,7 +306,10 @@ where /// Handles [`GetBlockAccessLists`] queries. /// - /// For now this returns one empty BAL per requested hash. + /// Returns one BAL entry per requested hash in request order. + /// + /// BAL data is loaded from the configured store; missing entries (or store failures) are + /// returned as empty bytes to preserve response cardinality. fn on_block_access_lists_request( &self, _peer_id: PeerId, From 12fbb98a279c08e142ace18ff71074abd365bb26 Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 19 Feb 2026 22:14:40 +0800 Subject: [PATCH 15/36] apply mattese suggestions --- crates/net/bal-store/src/disk.rs | 240 +++++++++++++++++--- crates/net/bal-store/src/lib.rs | 9 +- crates/node/builder/src/rpc.rs | 2 +- crates/rpc/rpc-builder/tests/it/utils.rs | 3 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 9 +- 5 files changed, 220 insertions(+), 43 deletions(-) diff --git a/crates/net/bal-store/src/disk.rs b/crates/net/bal-store/src/disk.rs index fc4b4994d75..51fc9655126 100644 --- a/crates/net/bal-store/src/disk.rs +++ b/crates/net/bal-store/src/disk.rs @@ -2,7 +2,7 @@ use crate::{BalStore, BalStoreError}; use alloy_primitives::{BlockHash, BlockNumber, Bytes}; -use parking_lot::Mutex; +use parking_lot::{Mutex, RwLock}; use std::{ collections::{BTreeMap, HashMap}, fs, io, @@ -15,17 +15,24 @@ use tracing::debug; /// /// Roughly aligns with the weak subjectivity period target discussed in EIP-7928. pub const DEFAULT_MAX_BAL_STORE_ENTRIES: u32 = 113_000; +/// Default number of recent BALs retained in memory to avoid repeated disk reads. +pub const DEFAULT_RECENT_BAL_CACHE_ENTRIES: u32 = 1024; /// Configuration for [`DiskFileBalStore`]. #[derive(Debug, Clone, Copy)] pub struct DiskFileBalStoreConfig { /// Maximum number of BAL entries to retain. pub max_entries: u32, + /// Number of most-recent BAL entries retained in memory. + pub recent_cache_entries: u32, } impl Default for DiskFileBalStoreConfig { fn default() -> Self { - Self { max_entries: DEFAULT_MAX_BAL_STORE_ENTRIES } + Self { + max_entries: DEFAULT_MAX_BAL_STORE_ENTRIES, + recent_cache_entries: DEFAULT_RECENT_BAL_CACHE_ENTRIES, + } } } @@ -35,6 +42,12 @@ impl DiskFileBalStoreConfig { self.max_entries = max_entries; self } + + /// Sets the number of recent entries retained in memory. + pub const fn with_recent_cache_entries(mut self, recent_cache_entries: u32) -> Self { + self.recent_cache_entries = recent_cache_entries; + self + } } /// A disk-backed BAL store with in-memory indexes. @@ -53,6 +66,49 @@ struct IndexState { hash_to_block: HashMap, } +#[derive(Debug)] +struct RecentBalCache { + capacity: u32, + entries: BTreeMap, +} + +impl RecentBalCache { + fn new(capacity: u32) -> Self { + Self { capacity, entries: BTreeMap::new() } + } + + fn insert(&mut self, block_hash: BlockHash, block_number: BlockNumber, bal: Bytes) { + if self.capacity == 0 { + return; + } + + // Keep a single slot per hash within the recent window, then write the latest position. + self.remove_by_hash(block_hash); + self.entries.insert(block_number, (block_hash, bal)); + + while self.entries.len() as u32 > self.capacity { + let Some((&oldest_number, _)) = self.entries.first_key_value() else { + break; + }; + self.entries.remove(&oldest_number); + } + } + + fn get(&self, block_number: BlockNumber, block_hash: BlockHash) -> Option { + self.entries.get(&block_number).and_then(|(cached_hash, bal)| { + if *cached_hash == block_hash { + Some(bal.clone()) + } else { + None + } + }) + } + + fn remove_by_hash(&mut self, block_hash: BlockHash) { + self.entries.retain(|_, (cached_hash, _)| *cached_hash != block_hash); + } +} + #[derive(Debug)] struct DiskFileBalStoreInner { root_dir: PathBuf, @@ -60,6 +116,7 @@ struct DiskFileBalStoreInner { index_dir: PathBuf, max_entries: u32, state: Mutex, + recent_cache: RwLock, } impl DiskFileBalStore { @@ -81,6 +138,7 @@ impl DiskFileBalStore { index_dir, max_entries: opts.max_entries, state: Mutex::new(IndexState::default()), + recent_cache: RwLock::new(RecentBalCache::new(opts.recent_cache_entries)), }; inner.rebuild_indexes()?; @@ -208,21 +266,23 @@ impl DiskFileBalStoreInner { bal: Bytes, ) -> Result<(), BalStoreError> { let mut state = self.state.lock(); + let mut removed_hashes = Vec::new(); // If the hash was previously indexed at another number, move the index. - if let Some(old_number) = state.hash_to_block.get(&block_hash).copied() && - old_number != block_number - { - state.block_to_hash.remove(&old_number); - self.remove_if_exists(self.index_file(old_number))?; + if let Some(old_number) = state.hash_to_block.get(&block_hash).copied() { + if old_number != block_number { + state.block_to_hash.remove(&old_number); + self.remove_if_exists(self.index_file(old_number))?; + } } // Reorg replacement: remove old hash payload at this number. - if let Some(old_hash) = state.block_to_hash.get(&block_number).copied() && - old_hash != block_hash - { - state.hash_to_block.remove(&old_hash); - self.remove_if_exists(self.entry_file(old_hash))?; + if let Some(old_hash) = state.block_to_hash.get(&block_number).copied() { + if old_hash != block_hash { + state.hash_to_block.remove(&old_hash); + self.remove_if_exists(self.entry_file(old_hash))?; + removed_hashes.push(old_hash); + } } fs::write(self.entry_file(block_hash), bal.as_ref())?; @@ -231,7 +291,16 @@ impl DiskFileBalStoreInner { state.block_to_hash.insert(block_number, block_hash); state.hash_to_block.insert(block_hash, block_number); - self.evict_over_capacity(&mut state) + self.evict_over_capacity(&mut state)?; + drop(state); + + let mut recent_cache = self.recent_cache.write(); + for old_hash in removed_hashes { + recent_cache.remove_by_hash(old_hash); + } + recent_cache.insert(block_hash, block_number, bal); + + Ok(()) } /// Returns BAL bytes for each requested hash in request order. @@ -241,44 +310,107 @@ impl DiskFileBalStoreInner { &self, block_hashes: &[BlockHash], ) -> Result>, BalStoreError> { - block_hashes - .iter() - .map(|hash| { - let path = self.entry_file(*hash); - match fs::read(path) { - Ok(bytes) => Ok(Some(Bytes::from(bytes))), - Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None), - Err(err) => Err(err.into()), + let block_numbers: Vec> = { + let state = self.state.lock(); + block_hashes.iter().map(|hash| state.hash_to_block.get(hash).copied()).collect() + }; + + let mut results = vec![None; block_hashes.len()]; + let mut misses = Vec::new(); + + { + let recent_cache = self.recent_cache.read(); + for (idx, hash) in block_hashes.iter().copied().enumerate() { + if let Some(block_number) = block_numbers[idx] { + if let Some(bal) = recent_cache.get(block_number, hash) { + results[idx] = Some(bal); + continue; + } } - }) - .collect() + misses.push((idx, hash, block_numbers[idx])); + } + } + + if misses.is_empty() { + return Ok(results); + } + + let mut recovered = Vec::new(); + for (idx, hash, block_number) in misses { + let path = self.entry_file(hash); + match fs::read(path) { + Ok(bytes) => { + let bal = Bytes::from(bytes); + results[idx] = Some(bal.clone()); + if let Some(block_number) = block_number { + recovered.push((hash, block_number, bal)); + } + } + Err(err) if err.kind() == io::ErrorKind::NotFound => {} + Err(err) => return Err(err.into()), + } + } + + if !recovered.is_empty() { + let mut recent_cache = self.recent_cache.write(); + for (hash, block_number, bal) in recovered { + recent_cache.insert(hash, block_number, bal); + } + } + + Ok(results) } /// Returns contiguous BAL bytes for `[start, start + count)`. /// /// The method first resolves the contiguous hash sequence from the in-memory block index, - /// then reads payload bytes from disk and stops at the first missing payload file. + /// then serves from hot cache first and falls back to disk, stopping at the first missing + /// payload file. fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { - let hashes: Vec = { + let numbered_hashes: Vec<(BlockNumber, BlockHash)> = { let state = self.state.lock(); let mut hashes = Vec::new(); for number in start..start.saturating_add(count) { let Some(hash) = state.block_to_hash.get(&number).copied() else { break; }; - hashes.push(hash); + hashes.push((number, hash)); } hashes }; - let mut result = Vec::with_capacity(hashes.len()); - for hash in hashes { + let mut result = Vec::with_capacity(numbered_hashes.len()); + { + let recent_cache = self.recent_cache.read(); + for (block_number, hash) in &numbered_hashes { + let Some(bal) = recent_cache.get(*block_number, *hash) else { + break; + }; + result.push(bal); + } + } + + let cached_prefix_len = result.len(); + let mut recovered = Vec::new(); + for (block_number, hash) in numbered_hashes.into_iter().skip(cached_prefix_len) { match fs::read(self.entry_file(hash)) { - Ok(bytes) => result.push(Bytes::from(bytes)), + Ok(bytes) => { + let bal = Bytes::from(bytes); + recovered.push((hash, block_number, bal.clone())); + result.push(bal); + } Err(err) if err.kind() == io::ErrorKind::NotFound => break, Err(err) => return Err(err.into()), } } + + if !recovered.is_empty() { + let mut recent_cache = self.recent_cache.write(); + for (hash, block_number, bal) in recovered { + recent_cache.insert(hash, block_number, bal); + } + } + Ok(result) } @@ -286,6 +418,7 @@ impl DiskFileBalStoreInner { /// /// Eviction removes both entry payload files and index files to keep disk and memory in sync. fn evict_over_capacity(&self, state: &mut IndexState) -> Result<(), BalStoreError> { + let mut evicted_hashes = Vec::new(); while state.block_to_hash.len() as u32 > self.max_entries { let Some((&oldest_number, &oldest_hash)) = state.block_to_hash.first_key_value() else { break; @@ -293,10 +426,19 @@ impl DiskFileBalStoreInner { state.block_to_hash.remove(&oldest_number); state.hash_to_block.remove(&oldest_hash); + evicted_hashes.push(oldest_hash); self.remove_if_exists(self.entry_file(oldest_hash))?; self.remove_if_exists(self.index_file(oldest_number))?; } + + if !evicted_hashes.is_empty() { + let mut recent_cache = self.recent_cache.write(); + for hash in evicted_hashes { + recent_cache.remove_by_hash(hash); + } + } + Ok(()) } @@ -335,16 +477,22 @@ mod tests { use super::*; use alloy_primitives::B256; - fn tmp_store(max_entries: u32) -> (DiskFileBalStore, tempfile::TempDir) { + fn tmp_store_with_config( + config: DiskFileBalStoreConfig, + ) -> (DiskFileBalStore, tempfile::TempDir) { let dir = tempfile::tempdir().unwrap(); - let store = DiskFileBalStore::open( - dir.path(), - DiskFileBalStoreConfig::default().with_max_entries(max_entries), - ) - .unwrap(); + let store = DiskFileBalStore::open(dir.path(), config).unwrap(); (store, dir) } + fn tmp_store(max_entries: u32) -> (DiskFileBalStore, tempfile::TempDir) { + tmp_store_with_config( + DiskFileBalStoreConfig::default() + .with_max_entries(max_entries) + .with_recent_cache_entries(DEFAULT_RECENT_BAL_CACHE_ENTRIES), + ) + } + #[test] fn insert_and_get_hashes() { let (store, _dir) = tmp_store(10); @@ -410,4 +558,26 @@ mod tests { let got = reopened.get_by_hashes(&[h1, h2]).unwrap(); assert_eq!(got, vec![Some(Bytes::from_static(b"a")), Some(Bytes::from_static(b"b"))]); } + + #[test] + fn recent_cache_keeps_last_blocks() { + let (store, _dir) = tmp_store_with_config( + DiskFileBalStoreConfig::default().with_max_entries(10).with_recent_cache_entries(2), + ); + + let h1 = B256::random(); + let h2 = B256::random(); + let h3 = B256::random(); + + store.insert(h1, 1, Bytes::from_static(b"a")).unwrap(); + store.insert(h2, 2, Bytes::from_static(b"b")).unwrap(); + store.insert(h3, 3, Bytes::from_static(b"c")).unwrap(); + + fs::remove_file(store.inner.entry_file(h1)).unwrap(); + fs::remove_file(store.inner.entry_file(h2)).unwrap(); + fs::remove_file(store.inner.entry_file(h3)).unwrap(); + + let got = store.get_by_hashes(&[h1, h2, h3]).unwrap(); + assert_eq!(got, vec![None, Some(Bytes::from_static(b"b")), Some(Bytes::from_static(b"c"))]); + } } diff --git a/crates/net/bal-store/src/lib.rs b/crates/net/bal-store/src/lib.rs index 1109b348aac..ca3ef63483a 100644 --- a/crates/net/bal-store/src/lib.rs +++ b/crates/net/bal-store/src/lib.rs @@ -1,11 +1,14 @@ //! Storage abstractions and implementations for EIP-7928 Block Access Lists (BALs). use alloy_primitives::{BlockHash, BlockNumber, Bytes}; -use std::io; +use std::{fmt::Debug, io}; pub mod disk; mod noop; -pub use disk::{DiskFileBalStore, DiskFileBalStoreConfig, DEFAULT_MAX_BAL_STORE_ENTRIES}; +pub use disk::{ + DiskFileBalStore, DiskFileBalStoreConfig, DEFAULT_MAX_BAL_STORE_ENTRIES, + DEFAULT_RECENT_BAL_CACHE_ENTRIES, +}; pub use noop::NoopBalStore; /// A store for EIP-7928 Block Access Lists (BALs). @@ -13,7 +16,7 @@ pub use noop::NoopBalStore; /// The store is keyed by block hash and maintains a block-number index for range queries. /// Implementations should preserve contiguous-range semantics: /// queries by range stop at the first missing block. -pub trait BalStore: Send + Sync + 'static { +pub trait BalStore: Debug + Send + Sync + 'static { /// Inserts a BAL for the given block hash and number. fn insert( &self, diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index a72f0cf664e..6019c5b30ed 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1398,7 +1398,7 @@ where commit: version_metadata().vergen_git_sha.to_string(), }; - Ok(EngineApi::with_bal_store( + Ok(EngineApi::new( ctx.node.provider().clone(), ctx.config.chain.clone(), ctx.beacon_engine_handle.clone(), diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index b41951b7227..774bc1c4430 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -16,7 +16,7 @@ use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerConfig, AuthServerHandle}, RpcModuleBuilder, RpcServerConfig, RpcServerHandle, TransportRpcModuleConfig, }; -use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; +use reth_rpc_engine_api::{bal_store::NoopBalStore, capabilities::EngineCapabilities, EngineApi}; use reth_rpc_layer::JwtSecret; use reth_rpc_server_types::RpcModuleSelection; use reth_tasks::TokioTaskExecutor; @@ -55,6 +55,7 @@ pub async fn launch_auth(secret: JwtSecret) -> AuthServerHandle { EthereumEngineValidator::new(MAINNET.clone()), false, NoopNetwork::default(), + std::sync::Arc::new(NoopBalStore), ); let module = AuthRpcModule::new(engine_api); module.start_server(config).await.unwrap() diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 20f433e9c3b..591533caf4c 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -230,8 +230,9 @@ where validator: Validator, accept_execution_requests_hash: bool, network: impl NetworkInfo + 'static, + bal_store: Arc, ) -> Self { - Self::with_bal_cache( + Self::with_bal_store_and_cache( provider, chain_spec, beacon_consensus, @@ -243,6 +244,7 @@ where validator, accept_execution_requests_hash, network, + bal_store, BalCache::new(), ) } @@ -296,7 +298,7 @@ where network: impl NetworkInfo + 'static, bal_store: Arc, ) -> Self { - Self::with_bal_store_and_cache( + Self::new( provider, chain_spec, beacon_consensus, @@ -309,7 +311,6 @@ where accept_execution_requests_hash, network, bal_store, - BalCache::new(), ) } @@ -1688,6 +1689,7 @@ mod tests { EthereumEngineValidator::new(chain_spec.clone()), false, NoopNetwork::default(), + Arc::new(crate::bal_store::NoopBalStore), ); let handle = EngineApiTestHandle { chain_spec, provider, from_api: engine_rx }; (handle, api) @@ -1793,6 +1795,7 @@ mod tests { EthereumEngineValidator::new(chain_spec), false, TestNetworkInfo { syncing: true }, + Arc::new(crate::bal_store::NoopBalStore), ); let res = api.get_blobs_v3_metered(vec![B256::ZERO]); From f0279531e5fc9451afb92744abaab2234f9d33c2 Mon Sep 17 00:00:00 2001 From: Karl Date: Thu, 19 Feb 2026 22:36:49 +0800 Subject: [PATCH 16/36] fix clippy --- crates/net/bal-store/src/disk.rs | 44 +++++++++++++++----------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/crates/net/bal-store/src/disk.rs b/crates/net/bal-store/src/disk.rs index 51fc9655126..5e31c799842 100644 --- a/crates/net/bal-store/src/disk.rs +++ b/crates/net/bal-store/src/disk.rs @@ -73,7 +73,7 @@ struct RecentBalCache { } impl RecentBalCache { - fn new(capacity: u32) -> Self { + const fn new(capacity: u32) -> Self { Self { capacity, entries: BTreeMap::new() } } @@ -95,13 +95,9 @@ impl RecentBalCache { } fn get(&self, block_number: BlockNumber, block_hash: BlockHash) -> Option { - self.entries.get(&block_number).and_then(|(cached_hash, bal)| { - if *cached_hash == block_hash { - Some(bal.clone()) - } else { - None - } - }) + self.entries + .get(&block_number) + .and_then(|(cached_hash, bal)| (*cached_hash == block_hash).then(|| bal.clone())) } fn remove_by_hash(&mut self, block_hash: BlockHash) { @@ -269,20 +265,20 @@ impl DiskFileBalStoreInner { let mut removed_hashes = Vec::new(); // If the hash was previously indexed at another number, move the index. - if let Some(old_number) = state.hash_to_block.get(&block_hash).copied() { - if old_number != block_number { - state.block_to_hash.remove(&old_number); - self.remove_if_exists(self.index_file(old_number))?; - } + if let Some(old_number) = + state.hash_to_block.get(&block_hash).copied().filter(|n| *n != block_number) + { + state.block_to_hash.remove(&old_number); + self.remove_if_exists(self.index_file(old_number))?; } // Reorg replacement: remove old hash payload at this number. - if let Some(old_hash) = state.block_to_hash.get(&block_number).copied() { - if old_hash != block_hash { - state.hash_to_block.remove(&old_hash); - self.remove_if_exists(self.entry_file(old_hash))?; - removed_hashes.push(old_hash); - } + if let Some(old_hash) = + state.block_to_hash.get(&block_number).copied().filter(|h| *h != block_hash) + { + state.hash_to_block.remove(&old_hash); + self.remove_if_exists(self.entry_file(old_hash))?; + removed_hashes.push(old_hash); } fs::write(self.entry_file(block_hash), bal.as_ref())?; @@ -321,11 +317,11 @@ impl DiskFileBalStoreInner { { let recent_cache = self.recent_cache.read(); for (idx, hash) in block_hashes.iter().copied().enumerate() { - if let Some(block_number) = block_numbers[idx] { - if let Some(bal) = recent_cache.get(block_number, hash) { - results[idx] = Some(bal); - continue; - } + if let Some(bal) = + block_numbers[idx].and_then(|block_number| recent_cache.get(block_number, hash)) + { + results[idx] = Some(bal); + continue; } misses.push((idx, hash, block_numbers[idx])); } From 2de4d06398274d9dbfa64ba8b0a1fd166b70e0ff Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 20 Feb 2026 01:30:18 +0800 Subject: [PATCH 17/36] fix api inconsistency --- crates/rpc/rpc-engine-api/src/engine_api.rs | 36 ++++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 591533caf4c..fb4db4dddb8 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -33,7 +33,7 @@ use reth_payload_primitives::{ use reth_primitives_traits::{Block, BlockBody}; use reth_rpc_api::{EngineApiServer, IntoEngineApiRpcModule}; use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory}; -use reth_tasks::TaskSpawner; +use reth_tasks::{Runtime, TaskSpawner}; use reth_transaction_pool::TransactionPool; use std::{ sync::Arc, @@ -51,6 +51,31 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024; /// The upper limit for blobs in `engine_getBlobsVx`. const MAX_BLOB_LIMIT: usize = 128; +pub trait IntoEngineTaskSpawner { + fn into_task_spawner(self) -> Box; +} + +impl IntoEngineTaskSpawner for Box { + fn into_task_spawner(self) -> Box { + self + } +} + +impl IntoEngineTaskSpawner for Box +where + T: TaskSpawner + 'static, +{ + fn into_task_spawner(self) -> Box { + self + } +} + +impl IntoEngineTaskSpawner for Runtime { + fn into_task_spawner(self) -> Box { + Box::new(self) + } +} + #[derive(Clone)] struct BalProvider { store: Arc, @@ -224,7 +249,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: Box, + task_spawner: impl IntoEngineTaskSpawner, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -257,7 +282,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: Box, + task_spawner: impl IntoEngineTaskSpawner, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -290,7 +315,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: Box, + task_spawner: impl IntoEngineTaskSpawner, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -322,7 +347,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: Box, + task_spawner: impl IntoEngineTaskSpawner, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -331,6 +356,7 @@ where bal_store: Arc, bal_cache: BalCache, ) -> Self { + let task_spawner = task_spawner.into_task_spawner(); let is_syncing = Arc::new(move || network.is_syncing()); let bal_provider = BalProvider::new(bal_store, bal_cache); let inner = Arc::new(EngineApiInner { From 81f304c0bd55e595b2e5572d8f8f35ae9b629dab Mon Sep 17 00:00:00 2001 From: Karl Date: Fri, 20 Feb 2026 01:48:25 +0800 Subject: [PATCH 18/36] shrink --- crates/node/builder/src/rpc.rs | 2 +- crates/rpc/rpc-builder/tests/it/utils.rs | 4 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 46 +++++---------------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 6019c5b30ed..0265aca7696 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1404,7 +1404,7 @@ where ctx.beacon_engine_handle.clone(), PayloadStore::new(ctx.node.payload_builder_handle().clone()), ctx.node.pool().clone(), - Box::new(ctx.node.task_executor().clone()), + ctx.node.task_executor().clone(), client, EngineCapabilities::default(), engine_validator, diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index 774bc1c4430..7086ef5beb9 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -19,7 +19,7 @@ use reth_rpc_builder::{ use reth_rpc_engine_api::{bal_store::NoopBalStore, capabilities::EngineCapabilities, EngineApi}; use reth_rpc_layer::JwtSecret; use reth_rpc_server_types::RpcModuleSelection; -use reth_tasks::TokioTaskExecutor; +use reth_tasks::{Runtime, TokioTaskExecutor}; use reth_transaction_pool::{ noop::NoopTransactionPool, test_utils::{TestPool, TestPoolBuilder}, @@ -49,7 +49,7 @@ pub async fn launch_auth(secret: JwtSecret) -> AuthServerHandle { beacon_engine_handle, spawn_test_payload_service().into(), NoopTransactionPool::default(), - Box::::default(), + Runtime::test(), client, EngineCapabilities::default(), EthereumEngineValidator::new(MAINNET.clone()), diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index fb4db4dddb8..8a638bf8049 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -33,7 +33,7 @@ use reth_payload_primitives::{ use reth_primitives_traits::{Block, BlockBody}; use reth_rpc_api::{EngineApiServer, IntoEngineApiRpcModule}; use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory}; -use reth_tasks::{Runtime, TaskSpawner}; +use reth_tasks::Runtime; use reth_transaction_pool::TransactionPool; use std::{ sync::Arc, @@ -51,31 +51,6 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024; /// The upper limit for blobs in `engine_getBlobsVx`. const MAX_BLOB_LIMIT: usize = 128; -pub trait IntoEngineTaskSpawner { - fn into_task_spawner(self) -> Box; -} - -impl IntoEngineTaskSpawner for Box { - fn into_task_spawner(self) -> Box { - self - } -} - -impl IntoEngineTaskSpawner for Box -where - T: TaskSpawner + 'static, -{ - fn into_task_spawner(self) -> Box { - self - } -} - -impl IntoEngineTaskSpawner for Runtime { - fn into_task_spawner(self) -> Box { - Box::new(self) - } -} - #[derive(Clone)] struct BalProvider { store: Arc, @@ -249,7 +224,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: impl IntoEngineTaskSpawner, + task_spawner: Runtime, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -282,7 +257,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: impl IntoEngineTaskSpawner, + task_spawner: Runtime, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -315,7 +290,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: impl IntoEngineTaskSpawner, + task_spawner: Runtime, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -347,7 +322,7 @@ where beacon_consensus: ConsensusEngineHandle, payload_store: PayloadStore, tx_pool: Pool, - task_spawner: impl IntoEngineTaskSpawner, + task_spawner: Runtime, client: ClientVersionV1, capabilities: EngineCapabilities, validator: Validator, @@ -356,7 +331,6 @@ where bal_store: Arc, bal_cache: BalCache, ) -> Self { - let task_spawner = task_spawner.into_task_spawner(); let is_syncing = Arc::new(move || network.is_syncing()); let bal_provider = BalProvider::new(bal_store, bal_cache); let inner = Arc::new(EngineApiInner { @@ -1644,7 +1618,7 @@ struct EngineApiInner, /// For spawning and executing async tasks - task_spawner: Box, + task_spawner: Runtime, /// The latency and response type metrics for engine api calls metrics: EngineApiMetrics, /// Identification of the execution client used by the consensus client @@ -1677,7 +1651,7 @@ mod tests { use reth_node_ethereum::EthereumEngineValidator; use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_provider::test_utils::MockEthProvider; - use reth_tasks::TokioTaskExecutor; + use reth_tasks::Runtime; use reth_transaction_pool::noop::NoopTransactionPool; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; @@ -1702,7 +1676,7 @@ mod tests { let provider = Arc::new(MockEthProvider::default()); let payload_store = spawn_test_payload_service(); let (to_engine, engine_rx) = unbounded_channel(); - let task_executor = Box::::default(); + let task_executor = Runtime::test(); let api = EngineApi::new( provider.clone(), chain_spec.clone(), @@ -1810,7 +1784,7 @@ mod tests { ConsensusEngineHandle::new(to_engine), payload_store.into(), NoopTransactionPool::default(), - Box::::default(), + Runtime::test(), ClientVersionV1 { code: ClientCode::RH, name: "Reth".to_string(), @@ -2050,7 +2024,7 @@ mod tests { ConsensusEngineHandle::new(to_engine), payload_store.into(), NoopTransactionPool::default(), - Box::::default(), + Runtime::test(), ClientVersionV1 { code: ClientCode::RH, name: "Reth".to_string(), From fb963726108a9f1c693de3b9aae6e5b83c1b7756 Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:13:02 +0530 Subject: [PATCH 19/36] chore: modified rpcs --- crates/rpc/rpc-engine-api/src/engine_api.rs | 42 +++++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index ce6d75a1514..7d1df419c5b 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -924,12 +924,21 @@ where start: BlockNumber, count: u64, ) -> EngineApiResult { - self.get_payload_bodies_by_range_with(start, count, |block| ExecutionPayloadBodyV2 { - transactions: block.body().encoded_2718_transactions(), - withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner), - block_access_list: None, - }) - .await + let mut bodies = self + .get_payload_bodies_by_range_with(start, count, |block| ExecutionPayloadBodyV2 { + transactions: block.body().encoded_2718_transactions(), + withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner), + block_access_list: None, + }) + .await?; + let bals = + self.inner.bal_provider.get_by_range(start, count, &self.inner.metrics.bal_metrics); + for (body_opt, bal) in bodies.iter_mut().zip(bals.into_iter()) { + if let Some(body) = body_opt.as_mut() { + body.block_access_list = Some(bal); + } + } + return Ok(bodies) } /// Metrics version of `get_payload_bodies_by_range_v2` @@ -1012,12 +1021,21 @@ where &self, hashes: Vec, ) -> EngineApiResult { - self.get_payload_bodies_by_hash_with(hashes, |block| ExecutionPayloadBodyV2 { - transactions: block.body().encoded_2718_transactions(), - withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner), - block_access_list: None, - }) - .await + let mut bodies = self + .get_payload_bodies_by_hash_with(hashes.clone(), |block| ExecutionPayloadBodyV2 { + transactions: block.body().encoded_2718_transactions(), + withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner), + block_access_list: None, + }) + .await?; + + let bals = self.inner.bal_provider.get_by_hashes(&hashes, &self.inner.metrics.bal_metrics); + for (body_opt, bal) in bodies.iter_mut().zip(bals.into_iter()) { + if let Some(body) = body_opt.as_mut() { + body.block_access_list = bal; + } + } + return Ok(bodies) } /// Metrics version of `get_payload_bodies_by_hash_v2` From ac723b20db57fa010c9871a3c7921047abc8744e Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Thu, 5 Mar 2026 12:32:21 +0530 Subject: [PATCH 20/36] fix --- Cargo.lock | 740 +++++++++++++++++++++++++++++------------------------ 1 file changed, 402 insertions(+), 338 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1558982e949..c59a46eeb6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ab1b2f1b48a7e6b3597cb2afae04f93879fb69d71e39736b5663d7366b23f2" +checksum = "cc2db5c583aaef0255aa63a4fe827f826090142528bba48d1bf4119b62780cad" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "alloy-eip7928" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3231de68d5d6e75332b7489cfcc7f4dfabeba94d990a10e4b923af0e6623540" +checksum = "f8222b1d88f9a6d03be84b0f5e76bb60cd83991b43ad8ab6477f0e4a7809b98d" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e414aa37b335ad2acb78a95814c59d137d53139b412f87aed1e10e2d862cd49" +checksum = "e9dbe713da0c737d9e5e387b0ba790eb98b14dd207fe53eef50e19a5a8ec3dac" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -420,9 +420,9 @@ dependencies = [ [[package]] name = "alloy-node-bindings" -version = "1.6.3" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce6930ce52e43b0768dc99ceeff5cb9e673e8c9f87d926914cd028b2e3f7233" +checksum = "091dc8117d84de3a9ac7ec97f2c4d83987e24d485b478d26aa1ec455d7d52f7d" dependencies = [ "alloy-genesis", "alloy-hardforks 0.2.13", @@ -454,9 +454,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b1483f8c2562bf35f0270b697d5b5fe8170464e935bd855a4c5eaf6f89b354" +checksum = "de3b431b4e72cd8bd0ec7a50b4be18e73dab74de0dba180eef171055e5d5926e" dependencies = [ "alloy-rlp", "arbitrary", @@ -466,7 +466,7 @@ dependencies = [ "derive_more", "fixed-cache", "foldhash 0.2.0", - "getrandom 0.4.1", + "getrandom 0.4.2", "hashbrown 0.16.1", "indexmap 2.13.0", "itoa", @@ -569,7 +569,7 @@ checksum = "ce8849c74c9ca0f5a03da1c865e3eb6f768df816e67dd3721a398a8a7e398011" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -810,23 +810,23 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4b64c8146291f750c3f391dff2dd40cf896f7e2b253417a31e342aa7265baa" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9df903674682f9bae8d43fdea535ab48df2d6a8cb5104ca29c58ada22ef67b3" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" dependencies = [ "alloy-sol-macro-input", "const-hex", @@ -836,15 +836,15 @@ dependencies = [ "proc-macro2", "quote", "sha3", - "syn 2.0.115", + "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-macro-input" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737b8a959f527a86e07c44656db237024a32ae9b97d449f788262a547e8aa136" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" dependencies = [ "const-hex", "dunce", @@ -852,15 +852,15 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28e6e86c6d2db52654b65a5a76b4f57eae5a32a7f0aa2222d1dbdb74e2cb8e0" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" dependencies = [ "serde", "winnow", @@ -868,9 +868,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf7effe4ab0a4f52c865959f790036e61a7983f68b13b75d7fbcedf20b753ce" +checksum = "64612d29379782a5dde6f4b6570d9c756d734d760c0c94c254d361e678a6591f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -984,7 +984,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1054,9 +1054,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "approx" @@ -1078,7 +1078,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1220,7 +1220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1258,7 +1258,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1347,7 +1347,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1397,9 +1397,9 @@ dependencies = [ [[package]] name = "asn1_der" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" +checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" [[package]] name = "assert_matches" @@ -1420,9 +1420,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", @@ -1463,7 +1463,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1474,7 +1474,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1488,6 +1488,15 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1512,7 +1521,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1613,33 +1622,13 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "bincode_derive", - "serde", - "unty", -] - -[[package]] -name = "bincode_derive" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" -dependencies = [ - "virtue", -] - [[package]] name = "bindgen" version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -1648,7 +1637,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1690,9 +1679,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "arbitrary", "serde_core", @@ -1770,7 +1759,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc119a5ad34c3f459062a96907f53358989b173d104258891bb74f95d93747e8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "boa_interner", "boa_macros", "boa_string", @@ -1787,7 +1776,7 @@ checksum = "e637ec52ea66d76b0ca86180c259d6c7bb6e6a6e14b2f36b85099306d8b00cc3" dependencies = [ "aligned-vec", "arrayvec", - "bitflags 2.10.0", + "bitflags 2.11.0", "boa_ast", "boa_gc", "boa_interner", @@ -1869,7 +1858,7 @@ dependencies = [ "cow-utils", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "synstructure", ] @@ -1879,7 +1868,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02f99bf5b684f0de946378fcfe5f38c3a0fbd51cbf83a0f39ff773a0e218541f" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "boa_ast", "boa_interner", "boa_macros", @@ -1925,7 +1914,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -1981,9 +1970,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byte-slice-cast" @@ -2008,7 +1997,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2125,9 +2114,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -2164,9 +2153,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -2226,9 +2215,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -2236,9 +2225,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -2255,7 +2244,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2273,6 +2262,15 @@ dependencies = [ "cc", ] +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "codspeed" version = "4.3.0" @@ -2462,9 +2460,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "brotli", "compression-core", @@ -2512,9 +2510,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" dependencies = [ "cfg-if", "cpufeatures", @@ -2698,7 +2696,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", @@ -2785,7 +2783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.31.1", + "nix 0.31.2", "windows-sys 0.61.2", ] @@ -2813,7 +2811,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2870,7 +2868,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2885,7 +2883,7 @@ dependencies = [ "quote", "serde", "strsim", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2898,7 +2896,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2909,7 +2907,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2920,7 +2918,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2931,7 +2929,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -2973,7 +2971,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3005,9 +3003,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -3032,7 +3030,7 @@ checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3043,7 +3041,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3064,7 +3062,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3074,7 +3072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3096,7 +3094,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.115", + "syn 2.0.117", "unicode-xid", ] @@ -3183,11 +3181,11 @@ dependencies = [ [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "block2", "libc", "objc2", @@ -3201,7 +3199,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3248,7 +3246,7 @@ checksum = "1ec431cd708430d5029356535259c5d645d60edd3d39c54e5eea9782d46caa7d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3300,7 +3298,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3372,6 +3370,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encode_unicode" version = "1.0.0" @@ -3407,7 +3417,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3427,7 +3437,7 @@ checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3447,7 +3457,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3529,7 +3539,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -3541,7 +3551,7 @@ dependencies = [ "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -4050,7 +4060,7 @@ checksum = "6dc7a9cb3326bafb80642c5ce99b39a2c0702d4bfa8ee8a3e773791a6cbe2407" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -4123,9 +4133,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -4138,9 +4148,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -4161,15 +4171,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -4178,9 +4188,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -4212,26 +4222,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -4245,9 +4255,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -4257,7 +4267,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -4326,20 +4335,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -4366,7 +4375,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "libgit2-sys", "log", @@ -4472,6 +4481,15 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -4536,6 +4554,20 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version 0.4.1", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -4973,7 +5005,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -5046,7 +5078,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -5092,7 +5124,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -5106,9 +5138,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bf2b0e0785c5394a7392f66d7c4fb9c653633c29b27a932280da3cb344c66a" +checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69" dependencies = [ "doctest-file", "futures-core", @@ -5142,9 +5174,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" @@ -5257,9 +5289,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -5369,7 +5401,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -5480,9 +5512,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -5531,9 +5563,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.180" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libgit2-sys" @@ -5595,13 +5627,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", - "redox_syscall 0.7.1", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -5622,9 +5655,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" dependencies = [ "cc", "libc", @@ -5638,7 +5671,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -5659,9 +5692,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -5776,7 +5809,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -5800,7 +5833,7 @@ checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -5820,9 +5853,9 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -5854,7 +5887,7 @@ checksum = "161ab904c2c62e7bda0f7562bf22f96440ca35ff79e66c800cbac298f2f4f5ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -5987,14 +6020,14 @@ checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] name = "moka" -version = "0.12.13" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" +checksum = "85f8024e1c8e71c778968af91d43700ce1d11b219d127d79fb2934153b82b42b" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -6060,7 +6093,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -6068,11 +6101,11 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.1" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225e7cfe711e0ba79a68baeddb2982723e4235247aefce1482f2f16c27865b66" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -6094,7 +6127,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "fsevent-sys", "inotify", "kqueue", @@ -6112,7 +6145,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -6243,7 +6276,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -6272,9 +6305,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", ] @@ -6285,7 +6318,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -6566,9 +6599,9 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "p256" @@ -6619,7 +6652,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -6734,7 +6767,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -6748,29 +6781,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -6794,6 +6827,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plain_hasher" version = "0.2.3" @@ -6849,6 +6888,19 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -6904,7 +6956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -6929,9 +6981,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -6955,7 +7007,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -6973,7 +7025,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "chrono", "flate2", "procfs-core", @@ -6986,7 +7038,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "chrono", "hex", ] @@ -6999,7 +7051,7 @@ checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.10.0", + "bitflags 2.11.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -7028,7 +7080,7 @@ checksum = "fb6dc647500e84a25a85b100e76c85b8ace114c209432dc174f20aac11d4ed6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -7051,7 +7103,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -7141,9 +7193,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -7154,6 +7206,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -7283,9 +7341,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.3.0" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84816e4c99c467e92cf984ee6328caa976dfecd33a673544489d79ca2caaefe5" +checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" dependencies = [ "rand 0.9.2", "rustversion", @@ -7309,7 +7367,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "compact_str", "hashbrown 0.16.1", "indoc", @@ -7341,7 +7399,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.16.1", "indoc", "instability", @@ -7360,7 +7418,7 @@ version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -7395,16 +7453,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -7435,7 +7493,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -7463,9 +7521,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "regress" @@ -7582,7 +7640,7 @@ dependencies = [ [[package]] name = "reth-bal-store" -version = "1.11.0" +version = "1.11.1" dependencies = [ "alloy-primitives", "parking_lot", @@ -7675,7 +7733,7 @@ dependencies = [ "csv", "ctrlc", "eyre", - "nix 0.31.1", + "nix 0.31.2", "reth-chainspec", "reth-cli-runner", "reth-cli-util", @@ -7902,7 +7960,7 @@ dependencies = [ "proc-macro2", "quote", "similar-asserts", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -8807,7 +8865,7 @@ dependencies = [ "alloy-evm", "alloy-primitives", "arbitrary", - "bincode 1.3.3", + "bincode", "derive_more", "rand 0.9.2", "reth-ethereum-primitives", @@ -8901,7 +8959,7 @@ dependencies = [ "alloy-eips", "alloy-primitives", "arbitrary", - "bincode 1.3.3", + "bincode", "rand 0.9.2", "reth-chain-state", "reth-ethereum-primitives", @@ -8979,7 +9037,7 @@ dependencies = [ name = "reth-libmdbx" version = "1.11.1" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "crossbeam-queue", "dashmap", @@ -9176,7 +9234,7 @@ name = "reth-nippy-jar" version = "1.11.1" dependencies = [ "anyhow", - "bincode 1.3.3", + "bincode", "derive_more", "lz4_flex", "memmap2", @@ -9576,7 +9634,7 @@ dependencies = [ "alloy-trie", "arbitrary", "auto_impl", - "bincode 1.3.3", + "bincode", "byteorder", "bytes", "dashmap", @@ -10125,7 +10183,7 @@ dependencies = [ "alloy-primitives", "alloy-rlp", "assert_matches", - "bincode 1.3.3", + "bincode", "eyre", "futures-util", "itertools 0.14.0", @@ -10427,7 +10485,7 @@ dependencies = [ "aquamarine", "assert_matches", "auto_impl", - "bitflags 2.10.0", + "bitflags 2.11.0", "futures", "futures-util", "metrics", @@ -10512,7 +10570,7 @@ dependencies = [ "alloy-trie", "arbitrary", "arrayvec", - "bincode 1.3.3", + "bincode", "bytes", "derive_more", "hash-db", @@ -10835,7 +10893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29404707763da607e5d6e4771cb203998c28159279c2f64cc32de08d2814651" dependencies = [ "alloy-eip7928", - "bitflags 2.10.0", + "bitflags 2.11.0", "revm-bytecode", "revm-primitives", "serde", @@ -10978,7 +11036,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.115", + "syn 2.0.117", "unicode-ident", ] @@ -11067,11 +11125,11 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -11080,9 +11138,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -11306,11 +11364,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation", "core-foundation-sys", "libc", @@ -11319,9 +11377,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -11418,7 +11476,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -11469,9 +11527,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "base64 0.22.1", "chrono", @@ -11488,14 +11546,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.16.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -11635,9 +11693,9 @@ dependencies = [ [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", @@ -11653,9 +11711,9 @@ checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "sketches-ddsketch" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" [[package]] name = "slab" @@ -11743,6 +11801,15 @@ dependencies = [ "sha1", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.3" @@ -11799,7 +11866,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -11821,9 +11888,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.115" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -11832,14 +11899,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.5.6" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8658017776544996edc21c8c7cc8bb4f13db60955382f4bac25dc6303b38438" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -11859,14 +11926,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] name = "sysinfo" -version = "0.38.1" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5792d209c2eac902426c0c4a166c9f72147db453af548cf9bf3242644c4d4fe3" +checksum = "d03c61d2a49c649a15c407338afe7accafde9dac869995dccb73e5f7ef7d9034" dependencies = [ "libc", "memchr", @@ -11907,12 +11974,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand 2.3.0", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", @@ -11936,7 +12003,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -11947,15 +12014,15 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "test-case-core", ] [[package]] name = "test-fuzz" -version = "7.2.5" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11e5c77910b1d5b469a342be541cf44933f0ad2c4b8d5acb32ee46697fd60546" +checksum = "bdcc9e8244ec7140f52b55bb67ff77fe5e10f1e7651a9d7ca1187555344a212d" dependencies = [ "serde", "serde_combinators", @@ -11966,35 +12033,35 @@ dependencies = [ [[package]] name = "test-fuzz-internal" -version = "7.2.5" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25f2f0ee315b130411a98570dd128dfe344bfaa0a28bf33d38f4a1fe85f39b" +checksum = "b36ec8e4151160eb1b1d42d4691c24b5a6f3891c75d41afb343346801ab60ce5" dependencies = [ - "bincode 2.0.1", "cargo_metadata 0.19.2", + "postcard", "serde", ] [[package]] name = "test-fuzz-macro" -version = "7.2.5" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c03ba0a9e3e4032f94d71c85e149af147843c6f212e4ca4383542d606b04a6" +checksum = "bd45ef045619b976f1efa036aee72b6a80dc359d800e11f9d636332ebf6655f9" dependencies = [ - "darling 0.21.3", + "darling 0.23.0", "heck", "itertools 0.14.0", "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] name = "test-fuzz-runtime" -version = "7.2.5" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a4ac481aa983d386e857a7be0006c2f0ef26e0c5326bbc7262f73c2891b91d" +checksum = "91e243f304ded602493cfd77bee2f627f376bec4ea185cf8e9674b6150ea3837" dependencies = [ "hex", "num-traits", @@ -12035,7 +12102,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -12046,7 +12113,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -12055,7 +12122,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2210811179577da3d54eb69ab0b50490ee40491a25d95b8c6011ba40771cb721" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "libc", "log", @@ -12184,9 +12251,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -12201,13 +12268,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -12284,7 +12351,7 @@ dependencies = [ "indexmap 2.13.0", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -12299,23 +12366,32 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap 2.13.0", - "toml_datetime", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.7+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "247eaa3197818b831697600aadf81514e577e0cba5eab10f7e064e78ae154df1" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -12328,9 +12404,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tonic" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a286e33f82f8a1ee2df63f4fa35c0becf4a85a0cb03091a15fd7bf0b402dc94a" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "base64 0.22.1", @@ -12354,9 +12430,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c55a2d6a14174563de34409c9f92ff981d006f56da9c6ecd40d9d4a31500b0" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", "prost", @@ -12391,7 +12467,7 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -12458,7 +12534,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -12652,7 +12728,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -12763,9 +12839,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -12818,12 +12894,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - [[package]] name = "url" version = "2.5.8" @@ -12863,11 +12933,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -12931,12 +13001,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "virtue" -version = "0.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" - [[package]] name = "visibility" version = "0.1.1" @@ -12945,7 +13009,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -13014,9 +13078,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -13027,9 +13091,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -13041,9 +13105,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13051,22 +13115,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -13112,7 +13176,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap 2.13.0", "semver 1.0.27", @@ -13134,9 +13198,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -13324,7 +13388,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -13335,7 +13399,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -13770,7 +13834,7 @@ dependencies = [ "heck", "indexmap 2.13.0", "prettyplease", - "syn 2.0.115", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -13786,7 +13850,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -13798,7 +13862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap 2.13.0", "log", "serde", @@ -13909,28 +13973,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -13950,7 +14014,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", "synstructure", ] @@ -13971,7 +14035,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] @@ -14005,7 +14069,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.115", + "syn 2.0.117", ] [[package]] From ca9b12dc2f806e9915c6a089b3ee1cec15b65b55 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Wed, 11 Mar 2026 19:25:50 +0530 Subject: [PATCH 21/36] add bal provider to blockchainprovider --- Cargo.lock | 1 + .../rpc/rpc-engine-api/src/bal_store/mod.rs | 7 - crates/rpc/rpc-engine-api/src/engine_api.rs | 267 ------------------ crates/storage/provider/Cargo.toml | 16 +- .../provider/src/providers/bal_provider.rs} | 156 +++++++--- .../src/providers/blockchain_provider.rs | 28 +- crates/storage/provider/src/providers/mod.rs | 3 + 7 files changed, 146 insertions(+), 332 deletions(-) delete mode 100644 crates/rpc/rpc-engine-api/src/bal_store/mod.rs rename crates/{rpc/rpc-engine-api/src/bal_cache.rs => storage/provider/src/providers/bal_provider.rs} (67%) diff --git a/Cargo.lock b/Cargo.lock index 1e2d0a5c3f2..a4c2ccc13cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9626,6 +9626,7 @@ dependencies = [ "parking_lot", "rand 0.9.2", "rayon", + "reth-bal-store", "reth-chain-state", "reth-chainspec", "reth-codecs", diff --git a/crates/rpc/rpc-engine-api/src/bal_store/mod.rs b/crates/rpc/rpc-engine-api/src/bal_store/mod.rs deleted file mode 100644 index 9e890793f83..00000000000 --- a/crates/rpc/rpc-engine-api/src/bal_store/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Backward-compatible re-exports for BAL store types. -//! -//! The concrete BAL store implementation lives in `reth-bal-store` so both the -//! Engine API and networking stack can share the same abstraction without -//! introducing a `network -> rpc` dependency. - -pub use reth_bal_store::*; diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 61021fb69a9..f11b44c6648 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -55,134 +55,6 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024; /// The upper limit for blobs in `engine_getBlobsVx`. const MAX_BLOB_LIMIT: usize = 128; -#[derive(Clone)] -struct BalProvider { - store: Arc, - cache: BalCache, -} - -impl BalProvider { - fn new(store: Arc, cache: BalCache) -> Self { - Self { store, cache } - } - - const fn cache(&self) -> &BalCache { - &self.cache - } - - // Persist first: store is the source of truth. We only populate the in-memory cache if - // durability succeeds, so cache visibility cannot outlive failed persistence. - // `Bytes` is consumed by each insert call, so we clone once for store and move the original - // into cache. - fn cache_bal( - &self, - block_hash: BlockHash, - block_number: BlockNumber, - bal: Bytes, - ) -> Result<(), BalStoreError> { - self.store.insert(block_hash, block_number, bal.clone())?; - self.cache.insert(block_hash, block_number, bal); - Ok(()) - } - - // Intent: serve BAL hash queries with cache-first latency while preserving - // request-order semantics and filling only missing entries from durable storage. - // Cache-first lookup: keep request order and fill only cache misses from durable storage. - fn get_by_hashes( - &self, - block_hashes: &[BlockHash], - metrics: &crate::metrics::BalQueryMetrics, - ) -> Vec> { - let mut results = self.cache.get_by_hashes(block_hashes); - - // Collect missing positions so store fallback can patch holes in-place. - let mut missing_hashes = Vec::new(); - let mut missing_indices = Vec::new(); - for (idx, result) in results.iter().enumerate() { - if result.is_none() { - missing_indices.push(idx); - missing_hashes.push(block_hashes[idx]); - } - } - - if missing_hashes.is_empty() { - return results; - } - - metrics.store_hash_fallback_requests.increment(1); - match self.store.get_by_hashes(&missing_hashes) { - Ok(store_results) => { - let mut recovered = 0_u64; - let mut still_missing = 0_u64; - - for (missing_idx, store_result) in - missing_indices.into_iter().zip(store_results.into_iter()) - { - if let Some(value) = store_result { - results[missing_idx] = Some(value); - recovered += 1; - } else { - still_missing += 1; - } - } - - if recovered > 0 { - metrics.store_hash_fallback_hits.increment(recovered); - } - if still_missing > 0 { - metrics.store_hash_fallback_misses.increment(still_missing); - } - } - Err(err) => { - metrics.store_hash_fallback_errors.increment(1); - warn!(target: "rpc::engine", ?err, "Failed to retrieve BALs by hash from BAL store"); - } - } - - results - } - - // Intent: serve contiguous BAL range queries with cache-first latency and - // append-only fallback from store, so the returned slice remains ordered and gap-safe. - // Cache range reads are contiguous and stop at the first gap. - fn get_by_range( - &self, - start: BlockNumber, - count: u64, - metrics: &crate::metrics::BalQueryMetrics, - ) -> Vec { - let mut cache_results = self.cache.get_by_range(start, count); - if cache_results.len() as u64 == count { - return cache_results; - } - - // Only the missing suffix can be queried from store, which avoids re-reading cached prefix. - let cached_len = cache_results.len() as u64; - let missing_start = start.saturating_add(cached_len); - let missing_count = count - cached_len; - - metrics.store_range_fallback_requests.increment(1); - match self.store.get_by_range(missing_start, missing_count) { - Ok(mut store_results) => { - let recovered = store_results.len() as u64; - if recovered > 0 { - metrics.store_range_fallback_hits.increment(recovered); - } - if recovered < missing_count { - metrics.store_range_fallback_misses.increment(missing_count - recovered); - } - cache_results.append(&mut store_results); - cache_results - } - Err(err) => { - metrics.store_range_fallback_errors.increment(1); - warn!(target: "rpc::engine", ?err, "Failed to retrieve BALs by range from BAL store"); - cache_results - } - } - } -} - /// The Engine API implementation that grants the Consensus layer access to data and /// functions in the Execution layer that are crucial for the consensus process. /// @@ -234,109 +106,8 @@ where validator: Validator, accept_execution_requests_hash: bool, network: impl NetworkInfo + 'static, - bal_store: Arc, - ) -> Self { - Self::with_bal_store_and_cache( - provider, - chain_spec, - beacon_consensus, - payload_store, - tx_pool, - task_spawner, - client, - capabilities, - validator, - accept_execution_requests_hash, - network, - bal_store, - BalCache::new(), - ) - } - - /// Create new instance of [`EngineApi`] with a custom BAL cache. - #[expect(clippy::too_many_arguments)] - pub fn with_bal_cache( - provider: Provider, - chain_spec: Arc, - beacon_consensus: ConsensusEngineHandle, - payload_store: PayloadStore, - tx_pool: Pool, - task_spawner: Runtime, - client: ClientVersionV1, - capabilities: EngineCapabilities, - validator: Validator, - accept_execution_requests_hash: bool, - network: impl NetworkInfo + 'static, - bal_cache: BalCache, - ) -> Self { - Self::with_bal_store_and_cache( - provider, - chain_spec, - beacon_consensus, - payload_store, - tx_pool, - task_spawner, - client, - capabilities, - validator, - accept_execution_requests_hash, - network, - Arc::new(bal_cache.clone()), - bal_cache, - ) - } - - /// Create new instance of [`EngineApi`] with a custom BAL store. - #[expect(clippy::too_many_arguments)] - pub fn with_bal_store( - provider: Provider, - chain_spec: Arc, - beacon_consensus: ConsensusEngineHandle, - payload_store: PayloadStore, - tx_pool: Pool, - task_spawner: Runtime, - client: ClientVersionV1, - capabilities: EngineCapabilities, - validator: Validator, - accept_execution_requests_hash: bool, - network: impl NetworkInfo + 'static, - bal_store: Arc, - ) -> Self { - Self::new( - provider, - chain_spec, - beacon_consensus, - payload_store, - tx_pool, - task_spawner, - client, - capabilities, - validator, - accept_execution_requests_hash, - network, - bal_store, - ) - } - - /// Internal constructor that wires explicit BAL store and cache layers. - #[expect(clippy::too_many_arguments)] - fn with_bal_store_and_cache( - provider: Provider, - chain_spec: Arc, - beacon_consensus: ConsensusEngineHandle, - payload_store: PayloadStore, - tx_pool: Pool, - task_spawner: Runtime, - client: ClientVersionV1, - capabilities: EngineCapabilities, - validator: Validator, - accept_execution_requests_hash: bool, - network: impl NetworkInfo + 'static, - bal_store: Arc, - bal_cache: BalCache, ) -> Self { let is_syncing = Arc::new(move || network.is_syncing()); - let bal_provider = BalProvider::new(bal_store, bal_cache); let inner = Arc::new(EngineApiInner { provider, chain_spec, @@ -350,32 +121,10 @@ where validator, accept_execution_requests_hash, is_syncing, - bal_provider, }); Self { inner } } - /// Returns a reference to the BAL cache. - pub fn bal_cache(&self) -> &BalCache { - self.inner.bal_provider.cache() - } - - /// Caches the BAL if the status is valid. - fn maybe_cache_bal(&self, num_hash: BlockNumHash, bal: Option, status: &PayloadStatus) { - if status.is_valid() && - let Some(bal) = bal && - let Err(err) = self.inner.bal_provider.cache_bal(num_hash.hash, num_hash.number, bal) - { - warn!( - target: "rpc::engine", - ?err, - block_hash = ?num_hash.hash, - block_number = num_hash.number, - "Failed to persist BAL into BAL store" - ); - } - } - /// Fetches the client version. pub fn get_client_version_v1( &self, @@ -410,10 +159,7 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V1, payload_or_attrs)?; - let num_hash = payload.num_hash(); - let bal = payload.block_access_list().cloned(); let status = self.inner.beacon_consensus.new_payload(payload).await?; - self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -443,10 +189,7 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V2, payload_or_attrs)?; - let num_hash = payload.num_hash(); - let bal = payload.block_access_list().cloned(); let status = self.inner.beacon_consensus.new_payload(payload).await?; - self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -476,10 +219,7 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V3, payload_or_attrs)?; - let num_hash = payload.num_hash(); - let bal = payload.block_access_list().cloned(); let status = self.inner.beacon_consensus.new_payload(payload).await?; - self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -510,10 +250,7 @@ where .validator .validate_version_specific_fields(EngineApiMessageVersion::V4, payload_or_attrs)?; - let num_hash = payload.num_hash(); - let bal = payload.block_access_list().cloned(); let status = self.inner.beacon_consensus.new_payload(payload).await?; - self.maybe_cache_bal(num_hash, bal, &status); Ok(status) } @@ -1829,8 +1566,6 @@ struct EngineApiInner bool + Send + Sync>, - /// Block Access List (BAL) provider with cache-first fallback semantics. - bal_provider: BalProvider, } #[cfg(test)] @@ -1889,7 +1624,6 @@ mod tests { EthereumEngineValidator::new(chain_spec.clone()), false, NoopNetwork::default(), - Arc::new(crate::bal_store::NoopBalStore), ); let handle = EngineApiTestHandle { chain_spec, provider, from_api: engine_rx }; (handle, api) @@ -1995,7 +1729,6 @@ mod tests { EthereumEngineValidator::new(chain_spec), false, TestNetworkInfo { syncing: true }, - Arc::new(crate::bal_store::NoopBalStore), ); let res = api.get_blobs_v3_metered(vec![B256::ZERO]); diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index f5e96631258..28538ae0c90 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -16,7 +16,11 @@ workspace = true reth-chainspec.workspace = true reth-execution-types.workspace = true reth-ethereum-primitives = { workspace = true, features = ["reth-codec"] } -reth-primitives-traits = { workspace = true, features = ["reth-codec", "secp256k1", "dashmap"] } +reth-primitives-traits = { workspace = true, features = [ + "reth-codec", + "secp256k1", + "dashmap", +] } reth-errors.workspace = true reth-storage-errors.workspace = true reth-storage-api = { workspace = true, features = ["std", "db-api"] } @@ -33,6 +37,7 @@ reth-chain-state.workspace = true reth-node-types.workspace = true reth-static-file-types = { workspace = true, features = ["std"] } reth-fs-util.workspace = true +reth-bal-store.workspace = true # ethereum alloy-eips.workspace = true @@ -51,7 +56,9 @@ metrics.workspace = true # misc itertools.workspace = true -notify = { workspace = true, default-features = false, features = ["macos_fsevent"] } +notify = { workspace = true, default-features = false, features = [ + "macos_fsevent", +] } parking_lot.workspace = true strum.workspace = true eyre.workspace = true @@ -69,7 +76,10 @@ rocksdb = { workspace = true, features = ["jemalloc"], optional = true } [dev-dependencies] reth-db = { workspace = true, features = ["test-utils"] } -reth-primitives-traits = { workspace = true, features = ["arbitrary", "test-utils"] } +reth-primitives-traits = { workspace = true, features = [ + "arbitrary", + "test-utils", +] } reth-chain-state = { workspace = true, features = ["test-utils"] } reth-trie = { workspace = true, features = ["test-utils"] } reth-testing-utils.workspace = true diff --git a/crates/rpc/rpc-engine-api/src/bal_cache.rs b/crates/storage/provider/src/providers/bal_provider.rs similarity index 67% rename from crates/rpc/rpc-engine-api/src/bal_cache.rs rename to crates/storage/provider/src/providers/bal_provider.rs index ce7af6a3900..c702068ed1c 100644 --- a/crates/rpc/rpc-engine-api/src/bal_cache.rs +++ b/crates/storage/provider/src/providers/bal_provider.rs @@ -8,17 +8,14 @@ //! weak subjectivity period (~3533 epochs) to support synchronization with re-execution. //! This initial implementation uses a simple in-memory cache with configurable capacity. -use crate::bal_store::{BalStore, BalStoreError}; use alloy_primitives::{BlockHash, BlockNumber, Bytes}; use parking_lot::RwLock; -use reth_metrics::{ - metrics::{Counter, Gauge}, - Metrics, -}; +use reth_bal_store::{BalStore, BalStoreError}; use std::{ collections::{BTreeMap, HashMap}, sync::Arc, }; +use tracing::warn; /// Default capacity for the BAL cache. /// @@ -46,8 +43,6 @@ struct BalCacheInner { /// Index mapping block number to block hash for range queries. /// Uses `BTreeMap` for efficient range iteration and eviction of oldest blocks. block_index: RwLock>, - /// Cache metrics. - metrics: BalCacheMetrics, } impl BalCache { @@ -63,7 +58,6 @@ impl BalCache { capacity, entries: RwLock::new(HashMap::new()), block_index: RwLock::new(BTreeMap::new()), - metrics: BalCacheMetrics::default(), }), } } @@ -93,11 +87,7 @@ impl BalCache { } entries.insert(block_hash, bal); - block_index.insert(block_number, block_hash); - - self.inner.metrics.inserts.increment(1); - self.inner.metrics.count.set(entries.len() as f64); } /// Retrieves BALs for the given block hashes. @@ -106,18 +96,7 @@ impl BalCache { /// is `Some(bal)` if found or `None` if not in cache. pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { let entries = self.inner.entries.read(); - block_hashes - .iter() - .map(|hash| { - let result = entries.get(hash).cloned(); - if result.is_some() { - self.inner.metrics.hits.increment(1); - } else { - self.inner.metrics.misses.increment(1); - } - result - }) - .collect() + block_hashes.iter().map(|hash| entries.get(hash).cloned()).collect() } /// Retrieves BALs for a range of blocks starting at `start` for `count` blocks. @@ -178,18 +157,112 @@ impl BalStore for BalCache { } } -/// Metrics for the BAL cache. -#[derive(Metrics)] -#[metrics(scope = "engine.bal_cache")] -struct BalCacheMetrics { - /// The total number of BALs in the cache. - count: Gauge, - /// The number of cache inserts. - inserts: Counter, - /// The number of cache hits. - hits: Counter, - /// The number of cache misses. - misses: Counter, +/// Provides access to Block Access Lists (BALs). +/// +/// `BalProvider` acts as a thin abstraction over: +/// - a **durable store** (`BalStore`) which is the source of truth +/// - an **in-memory cache** (`BalCache`) for fast access. +/// +/// Reads are cache-first with fallback to the store. +/// Writes are store-first to ensure durability before cache visibility. +#[derive(Clone, Debug)] +pub struct BalProvider { + /// Persistent storage backend for BALs. + store: Arc, + /// In-memory cache for recently accessed BALs. + cache: BalCache, +} + +impl Default for BalProvider { + fn default() -> Self { + let cache = BalCache::new(); + Self { store: Arc::new(BalCache::new()), cache } + } +} + +impl BalProvider { + fn new(store: Arc, cache: BalCache) -> Self { + Self { store, cache } + } + + const fn cache(&self) -> &BalCache { + &self.cache + } + + // Persist first: store is the source of truth. We only populate the in-memory cache if + // durability succeeds, so cache visibility cannot outlive failed persistence. + // `Bytes` is consumed by each insert call, so we clone once for store and move the original + // into cache. + fn cache_bal( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + self.store.insert(block_hash, block_number, bal.clone())?; + self.cache.insert(block_hash, block_number, bal); + Ok(()) + } + + // Cache-first lookup: keep request order and fill only cache misses from durable storage. + fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { + let mut results = self.cache.get_by_hashes(block_hashes); + + // Collect missing positions so store fallback can patch holes in-place. + let mut missing_hashes = Vec::new(); + let mut missing_indices = Vec::new(); + for (idx, result) in results.iter().enumerate() { + if result.is_none() { + missing_indices.push(idx); + missing_hashes.push(block_hashes[idx]); + } + } + + if missing_hashes.is_empty() { + return results; + } + + match self.store.get_by_hashes(&missing_hashes) { + Ok(store_results) => { + for (missing_idx, store_result) in + missing_indices.into_iter().zip(store_results.into_iter()) + { + if let Some(value) = store_result { + results[missing_idx] = Some(value); + } + } + } + Err(err) => { + warn!(target: "rpc::engine", ?err, "Failed to retrieve BALs by hash from BAL store"); + } + } + + results + } + + // Cache range reads are contiguous and stop at the first gap. + // Only the missing suffix is queried from store to avoid re-reading cached prefix. + fn get_by_range(&self, start: BlockNumber, count: u64) -> Vec { + let mut cache_results = self.cache.get_by_range(start, count); + if cache_results.len() as u64 == count { + return cache_results; + } + + let cached_len = cache_results.len() as u64; + let missing_start = start.saturating_add(cached_len); + let missing_count = count - cached_len; + + match self.store.get_by_range(missing_start, missing_count) { + Ok(mut store_results) => { + cache_results.append(&mut store_results); + cache_results + } + Err(err) => { + warn!(target: "rpc::engine", ?err, "Failed to retrieve BALs by range from BAL store"); + cache_results + } + } + } } #[cfg(test)] @@ -234,18 +307,15 @@ mod tests { fn test_get_by_range_stops_at_gap() { let cache = BalCache::with_capacity(10); - // Insert blocks 1, 2, 4, 5 (missing block 3) for i in [1, 2, 4, 5] { let hash = B256::random(); let bal = Bytes::from(format!("bal{i}").into_bytes()); cache.insert(hash, i, bal); } - // Requesting range starting at 1 should stop at the gap (block 3) let results = cache.get_by_range(1, 5); - assert_eq!(results.len(), 2); // Only blocks 1 and 2 + assert_eq!(results.len(), 2); - // Requesting range starting at 4 should return 4 and 5 let results = cache.get_by_range(4, 3); assert_eq!(results.len(), 2); } @@ -254,19 +324,16 @@ mod tests { fn test_eviction_oldest_first() { let cache = BalCache::with_capacity(3); - // Insert blocks 10, 20, 30 for i in [10, 20, 30] { let hash = B256::random(); cache.insert(hash, i, Bytes::from_static(b"bal")); } assert_eq!(cache.len(), 3); - // Insert block 40, should evict block 10 (oldest/lowest) let hash40 = B256::random(); cache.insert(hash40, 40, Bytes::from_static(b"bal40")); assert_eq!(cache.len(), 3); - // Block 10 should be gone, block 20 should still be there let results = cache.get_by_range(10, 1); assert_eq!(results.len(), 0); @@ -283,14 +350,11 @@ mod tests { let bal1 = Bytes::from_static(b"bal1"); let bal2 = Bytes::from_static(b"bal2"); - // Insert block 100 with hash1 cache.insert(hash1, 100, bal1.clone()); assert_eq!(cache.get_by_hashes(&[hash1])[0], Some(bal1)); - // Reorg: insert block 100 with hash2 cache.insert(hash2, 100, bal2.clone()); - // hash1 should be gone, hash2 should be there assert_eq!(cache.get_by_hashes(&[hash1])[0], None); assert_eq!(cache.get_by_hashes(&[hash2])[0], Some(bal2)); assert_eq!(cache.len(), 1); diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index a9cf4c38f42..cb813c5ef9e 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -1,6 +1,6 @@ use crate::{ providers::{ - ConsistentProvider, ProviderNodeTypes, RocksDBProvider, StaticFileProvider, + BalProvider, ConsistentProvider, ProviderNodeTypes, RocksDBProvider, StaticFileProvider, StaticFileProviderRWRefMut, }, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, @@ -15,6 +15,7 @@ use alloy_consensus::transaction::TransactionMeta; use alloy_eips::{BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag}; use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash, TxNumber, B256}; use alloy_rpc_types_engine::ForkchoiceState; +use reth_bal_store::BalStore; use reth_chain_state::{ BlockState, CanonicalInMemoryState, ForkChoiceNotifications, ForkChoiceSubscriptions, MemoryOverlayStateProvider, PersistedBlockNotifications, PersistedBlockSubscriptions, @@ -50,6 +51,8 @@ pub struct BlockchainProvider { /// Tracks the chain info wrt forkchoice updates and in memory canonical /// state. pub(crate) canonical_in_memory_state: CanonicalInMemoryState, + /// + pub(crate) bal_provider: BalProvider, } impl Clone for BlockchainProvider { @@ -57,6 +60,7 @@ impl Clone for BlockchainProvider { Self { database: self.database.clone(), canonical_in_memory_state: self.canonical_in_memory_state.clone(), + bal_provider: self.bal_provider.clone(), } } } @@ -64,13 +68,17 @@ impl Clone for BlockchainProvider { impl BlockchainProvider { /// Create a new [`BlockchainProvider`] using only the storage, fetching the latest /// header from the database to initialize the provider. - pub fn new(storage: ProviderFactory) -> ProviderResult { + pub fn new(storage: ProviderFactory, bal_provider: BalProvider) -> ProviderResult { let provider = storage.provider()?; let best = provider.chain_info()?; match provider.header_by_number(best.best_number)? { Some(header) => { drop(provider); - Ok(Self::with_latest(storage, SealedHeader::new(header, best.best_hash))?) + Ok(Self::with_latest( + storage, + SealedHeader::new(header, best.best_hash), + bal_provider, + )?) } None => Err(ProviderError::HeaderNotFound(best.best_number.into())), } @@ -84,6 +92,7 @@ impl BlockchainProvider { pub fn with_latest( storage: ProviderFactory, latest: SealedHeader>, + bal_provider: BalProvider, ) -> ProviderResult { let provider = storage.provider()?; let finalized_header = provider @@ -108,6 +117,7 @@ impl BlockchainProvider { finalized_header, safe_header, ), + bal_provider, }) } @@ -796,7 +806,7 @@ impl StateReader for BlockchainProvider { #[cfg(test)] mod tests { use crate::{ - providers::BlockchainProvider, + providers::{BalProvider, BlockchainProvider}, test_utils::{ create_test_provider_factory, create_test_provider_factory_with_chain_spec, MockNodeTypesWithDB, @@ -927,7 +937,7 @@ mod tests { provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; // Insert the rest of the blocks and receipts into the in-memory state let chain = NewCanonicalChain::Commit { @@ -1052,7 +1062,7 @@ mod tests { provider_rw.commit()?; // Create a new provider - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; // Useful blocks let first_db_block = database_blocks.first().unwrap(); @@ -1150,7 +1160,7 @@ mod tests { provider_rw.commit()?; // Create a new provider - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; // First in memory block let first_in_mem_block = in_memory_blocks.first().unwrap(); @@ -1364,7 +1374,7 @@ mod tests { provider_rw.insert_block(&block_1)?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; // Subscribe twice for canonical state updates. let in_memory_state = provider.canonical_in_memory_state(); @@ -1716,7 +1726,7 @@ mod tests { )?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let in_memory_changesets = in_memory_changesets.into_iter().next().unwrap(); let chain = NewCanonicalChain::Commit { diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 9b8af6de663..03482665587 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -26,6 +26,9 @@ pub use state::{ mod consistent_view; pub use consistent_view::{ConsistentDbView, ConsistentViewError}; +mod bal_provider; +pub use bal_provider::*; + mod blockchain_provider; pub use blockchain_provider::BlockchainProvider; From fa747dbbe64d8f496424e8f5d0e2496464011603 Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Wed, 11 Mar 2026 19:47:54 +0530 Subject: [PATCH 22/36] chore: compilation error and BalStore impl --- .../tree/src/tree/payload_processor/mod.rs | 2 +- crates/exex/exex/src/backfill/job.rs | 18 ++++++++----- crates/exex/exex/src/backfill/stream.rs | 11 +++++--- crates/exex/exex/src/manager.rs | 18 +++++++------ crates/exex/exex/src/notifications.rs | 15 +++++------ crates/exex/test-utils/src/lib.rs | 2 +- crates/prune/prune/src/segments/mod.rs | 10 ++++---- .../provider/src/providers/bal_provider.rs | 8 +++--- .../src/providers/blockchain_provider.rs | 25 +++++++++++++++++++ .../provider/src/providers/consistent.rs | 23 +++++++++-------- examples/rpc-db/src/main.rs | 2 +- 11 files changed, 86 insertions(+), 48 deletions(-) diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 175496181de..866186bc1e1 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -1448,7 +1448,7 @@ mod tests { PrecompileCacheMap::default(), ); - let provider_factory = BlockchainProvider::new(factory).unwrap(); + let provider_factory = BlockchainProvider::new(factory, BalProvider::default()).unwrap(); let mut handle = payload_processor.spawn( ExecutionEnv::test_default(), diff --git a/crates/exex/exex/src/backfill/job.rs b/crates/exex/exex/src/backfill/job.rs index f819ff7169d..101877a07b3 100644 --- a/crates/exex/exex/src/backfill/job.rs +++ b/crates/exex/exex/src/backfill/job.rs @@ -253,7 +253,8 @@ mod tests { use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::crypto::secp256k1::public_key_to_address; use reth_provider::{ - providers::BlockchainProvider, test_utils::create_test_provider_factory_with_chain_spec, + providers::{BalProvider, BlockchainProvider}, + test_utils::create_test_provider_factory_with_chain_spec, }; use reth_testing_utils::generators; @@ -270,7 +271,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; let blocks_and_execution_outputs = blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?; @@ -306,7 +308,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; let blocks_and_execution_outcomes = blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?; @@ -356,7 +359,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; // Execute blocks via LatestStateProvider (pipeline-style) and commit to DB. // This mirrors what the pipeline's ExecutionStage does. @@ -420,7 +424,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; let pipeline_results = blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?; @@ -463,7 +468,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; let blocks_and_execution_outputs = blocks_and_execution_outputs(provider_factory, chain_spec, key_pair)?; diff --git a/crates/exex/exex/src/backfill/stream.rs b/crates/exex/exex/src/backfill/stream.rs index 9d50737f5aa..0d253a9f07d 100644 --- a/crates/exex/exex/src/backfill/stream.rs +++ b/crates/exex/exex/src/backfill/stream.rs @@ -259,7 +259,7 @@ mod tests { crypto::secp256k1::public_key_to_address, Block as _, NodePrimitives, }; use reth_provider::{ - providers::{BlockchainProvider, ProviderNodeTypes}, + providers::{BalProvider, BlockchainProvider, ProviderNodeTypes}, test_utils::create_test_provider_factory_with_chain_spec, ProviderFactory, }; @@ -281,7 +281,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; // Create first 2 blocks let blocks_and_execution_outcomes = @@ -318,7 +319,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; // Create first 2 blocks let (blocks, execution_outcome) = @@ -421,7 +423,8 @@ mod tests { let executor = EthEvmConfig::ethereum(chain_spec.clone()); let provider_factory = create_test_provider_factory_with_chain_spec(chain_spec.clone()); init_genesis(&provider_factory)?; - let blockchain_db = BlockchainProvider::new(provider_factory.clone())?; + let blockchain_db = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; // Create and commit 4 blocks let blocks = create_blocks(&chain_spec, key_pair, 4)?; diff --git a/crates/exex/exex/src/manager.rs b/crates/exex/exex/src/manager.rs index c8a1b2c4ba6..dbbcedee4e5 100644 --- a/crates/exex/exex/src/manager.rs +++ b/crates/exex/exex/src/manager.rs @@ -687,8 +687,9 @@ mod tests { use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::RecoveredBlock; use reth_provider::{ - providers::BlockchainProvider, test_utils::create_test_provider_factory, BlockReader, - BlockWriter, Chain, DBProvider, DatabaseProviderFactory, TransactionVariant, + providers::{BalProvider, BlockchainProvider}, + test_utils::create_test_provider_factory, + BlockReader, BlockWriter, Chain, DBProvider, DatabaseProviderFactory, TransactionVariant, }; use reth_testing_utils::generators::{self, random_block, BlockParams}; @@ -1119,7 +1120,7 @@ mod tests { async fn exex_handle_new() { let provider_factory = create_test_provider_factory(); init_genesis(&provider_factory).unwrap(); - let provider = BlockchainProvider::new(provider_factory).unwrap(); + let provider = BlockchainProvider::new(provider_factory, BalProvider::default()).unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let wal = Wal::new(temp_dir.path()).unwrap(); @@ -1174,7 +1175,7 @@ mod tests { async fn test_notification_if_finished_height_gt_chain_tip() { let provider_factory = create_test_provider_factory(); init_genesis(&provider_factory).unwrap(); - let provider = BlockchainProvider::new(provider_factory).unwrap(); + let provider = BlockchainProvider::new(provider_factory, BalProvider::default()).unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let wal = Wal::new(temp_dir.path()).unwrap(); @@ -1224,7 +1225,7 @@ mod tests { async fn test_sends_chain_reorged_notification() { let provider_factory = create_test_provider_factory(); init_genesis(&provider_factory).unwrap(); - let provider = BlockchainProvider::new(provider_factory).unwrap(); + let provider = BlockchainProvider::new(provider_factory, BalProvider::default()).unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let wal = Wal::new(temp_dir.path()).unwrap(); @@ -1267,7 +1268,7 @@ mod tests { async fn test_sends_chain_reverted_notification() { let provider_factory = create_test_provider_factory(); init_genesis(&provider_factory).unwrap(); - let provider = BlockchainProvider::new(provider_factory).unwrap(); + let provider = BlockchainProvider::new(provider_factory, BalProvider::default()).unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let wal = Wal::new(temp_dir.path()).unwrap(); @@ -1327,7 +1328,7 @@ mod tests { provider_rw.insert_block(&block).unwrap(); provider_rw.commit().unwrap(); - let provider = BlockchainProvider::new(provider_factory).unwrap(); + let provider = BlockchainProvider::new(provider_factory, BalProvider::default()).unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let wal = Wal::new(temp_dir.path()).unwrap(); @@ -1428,7 +1429,8 @@ mod tests { let wal = Wal::new(temp_dir.path()).unwrap(); let provider_factory = create_test_provider_factory(); init_genesis(&provider_factory).unwrap(); - let provider = BlockchainProvider::new(provider_factory.clone()).unwrap(); + let provider = + BlockchainProvider::new(provider_factory.clone(), BalProvider::default()).unwrap(); // 1. Setup Manager with Capacity = 1 let (exex_handle, _, mut notifications) = ExExHandle::new( diff --git a/crates/exex/exex/src/notifications.rs b/crates/exex/exex/src/notifications.rs index b5124e8ce0e..001ffd38c29 100644 --- a/crates/exex/exex/src/notifications.rs +++ b/crates/exex/exex/src/notifications.rs @@ -529,8 +529,9 @@ mod tests { use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::Block as _; use reth_provider::{ - providers::BlockchainProvider, test_utils::create_test_provider_factory, BlockWriter, - Chain, DBProvider, DatabaseProviderFactory, + providers::{BalProvider, BlockchainProvider}, + test_utils::create_test_provider_factory, + BlockWriter, Chain, DBProvider, DatabaseProviderFactory, }; use reth_testing_utils::generators::{self, random_block, BlockParams}; use std::collections::BTreeMap; @@ -549,7 +550,7 @@ mod tests { .block(genesis_hash.into())? .ok_or_else(|| eyre::eyre!("genesis block not found"))?; - let provider = BlockchainProvider::new(provider_factory.clone())?; + let provider = BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; let node_head_block = random_block( &mut rng, @@ -623,7 +624,7 @@ mod tests { .block(genesis_hash.into())? .ok_or_else(|| eyre::eyre!("genesis block not found"))?; - let provider = BlockchainProvider::new(provider_factory)?; + let provider = BlockchainProvider::new(provider_factory, BalProvider::default())?; let node_head = BlockNumHash { number: genesis_block.number, hash: genesis_hash }; let exex_head = ExExHead { block: node_head }; @@ -677,7 +678,7 @@ mod tests { .block(genesis_hash.into())? .ok_or_else(|| eyre::eyre!("genesis block not found"))?; - let provider = BlockchainProvider::new(provider_factory)?; + let provider = BlockchainProvider::new(provider_factory, BalProvider::default())?; let node_head_block = random_block( &mut rng, @@ -768,7 +769,7 @@ mod tests { .block(genesis_hash.into())? .ok_or_else(|| eyre::eyre!("genesis block not found"))?; - let provider = BlockchainProvider::new(provider_factory)?; + let provider = BlockchainProvider::new(provider_factory, BalProvider::default())?; let exex_head_block = random_block( &mut rng, @@ -853,7 +854,7 @@ mod tests { .block(genesis_hash.into())? .ok_or_else(|| eyre::eyre!("genesis block not found"))?; - let provider = BlockchainProvider::new(provider_factory.clone())?; + let provider = BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; // Insert block 1 into the DB so there's something to backfill let node_head_block = random_block( diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index 900a985eb32..958a14f2872 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -256,7 +256,7 @@ pub async fn test_exex_context_with_chain_spec( )?; let genesis_hash = init_genesis(&provider_factory)?; - let provider = BlockchainProvider::new(provider_factory.clone())?; + let provider = BlockchainProvider::new(provider_factory.clone(), BalProvider::default())?; let runtime = Runtime::test(); let network_manager = NetworkManager::new( diff --git a/crates/prune/prune/src/segments/mod.rs b/crates/prune/prune/src/segments/mod.rs index 429ee60f24e..de3896fbed0 100644 --- a/crates/prune/prune/src/segments/mod.rs +++ b/crates/prune/prune/src/segments/mod.rs @@ -239,7 +239,7 @@ mod tests { use super::*; use alloy_primitives::B256; use reth_provider::{ - providers::BlockchainProvider, + providers::{BalProvider, BlockchainProvider}, test_utils::{create_test_provider_factory, MockEthProvider}, BlockWriter, }; @@ -291,7 +291,7 @@ mod tests { provider_rw.commit().expect("failed to commit"); // Create a new provider - let provider = BlockchainProvider::new(factory).unwrap(); + let provider = BlockchainProvider::new(factory, BalProvider::default()).unwrap(); // Since there are no transactions, expected None let range = input.get_next_tx_num_range(&provider).expect("Expected range"); @@ -329,7 +329,7 @@ mod tests { provider_rw.commit().expect("failed to commit"); // Create a new provider - let provider = BlockchainProvider::new(factory).unwrap(); + let provider = BlockchainProvider::new(factory, BalProvider::default()).unwrap(); // Get the next tx number range let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap(); @@ -375,7 +375,7 @@ mod tests { provider_rw.commit().expect("failed to commit"); // Create a new provider - let provider = BlockchainProvider::new(factory).unwrap(); + let provider = BlockchainProvider::new(factory, BalProvider::default()).unwrap(); // Fetch the range and check if it is correct let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap(); @@ -411,7 +411,7 @@ mod tests { provider_rw.commit().expect("failed to commit"); // Create a new provider - let provider = BlockchainProvider::new(factory).unwrap(); + let provider = BlockchainProvider::new(factory, BalProvider::default()).unwrap(); // Get the last tx number // Calculate the total number of transactions diff --git a/crates/storage/provider/src/providers/bal_provider.rs b/crates/storage/provider/src/providers/bal_provider.rs index c702068ed1c..a886489bb77 100644 --- a/crates/storage/provider/src/providers/bal_provider.rs +++ b/crates/storage/provider/src/providers/bal_provider.rs @@ -185,7 +185,7 @@ impl BalProvider { Self { store, cache } } - const fn cache(&self) -> &BalCache { + pub const fn cache(&self) -> &BalCache { &self.cache } @@ -193,7 +193,7 @@ impl BalProvider { // durability succeeds, so cache visibility cannot outlive failed persistence. // `Bytes` is consumed by each insert call, so we clone once for store and move the original // into cache. - fn cache_bal( + pub fn cache_bal( &self, block_hash: BlockHash, block_number: BlockNumber, @@ -205,7 +205,7 @@ impl BalProvider { } // Cache-first lookup: keep request order and fill only cache misses from durable storage. - fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { + pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { let mut results = self.cache.get_by_hashes(block_hashes); // Collect missing positions so store fallback can patch holes in-place. @@ -242,7 +242,7 @@ impl BalProvider { // Cache range reads are contiguous and stop at the first gap. // Only the missing suffix is queried from store to avoid re-reading cached prefix. - fn get_by_range(&self, start: BlockNumber, count: u64) -> Vec { + pub fn get_by_range(&self, start: BlockNumber, count: u64) -> Vec { let mut cache_results = self.cache.get_by_range(start, count); if cache_results.len() as u64 == count { return cache_results; diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index cb813c5ef9e..7940611c6b4 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -802,6 +802,31 @@ impl StateReader for BlockchainProvider { StateReader::get_state(&self.consistent_provider()?, block) } } +impl BalStore for BlockchainProvider { + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: alloy_primitives::Bytes, + ) -> Result<(), reth_bal_store::BalStoreError> { + Ok(self.bal_provider.cache().insert(block_hash, block_number, bal)) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, reth_bal_store::BalStoreError> { + Ok(self.bal_provider.get_by_hashes(block_hashes)) + } + + fn get_by_range( + &self, + start: BlockNumber, + count: u64, + ) -> Result, reth_bal_store::BalStoreError> { + Ok(self.bal_provider.get_by_range(start, count)) + } +} #[cfg(test)] mod tests { diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index ba03ae37b25..8c29a8337e0 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -1702,8 +1702,9 @@ impl StateReader for ConsistentProvider { #[cfg(test)] mod tests { use crate::{ - providers::blockchain_provider::BlockchainProvider, - test_utils::create_test_provider_factory, BlockWriter, + providers::{blockchain_provider::BlockchainProvider, BalProvider}, + test_utils::create_test_provider_factory, + BlockWriter, }; use alloy_eips::BlockHashOrNumber; use alloy_primitives::B256; @@ -1783,7 +1784,7 @@ mod tests { provider_rw.commit()?; // Create a new provider - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let consistent_provider = provider.consistent_provider()?; // Useful blocks @@ -1894,7 +1895,7 @@ mod tests { provider_rw.commit()?; // Create a new provider - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let consistent_provider = provider.consistent_provider()?; // First in memory block @@ -2008,7 +2009,7 @@ mod tests { )?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let in_memory_changesets = in_memory_changesets.into_iter().next().unwrap(); let chain = NewCanonicalChain::Commit { @@ -2132,7 +2133,7 @@ mod tests { provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let consistent_provider = provider.consistent_provider()?; let outcome = @@ -2299,7 +2300,7 @@ mod tests { provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let consistent_provider = provider.consistent_provider()?; let outcome = @@ -2364,7 +2365,7 @@ mod tests { )?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let in_mem_block = in_memory_blocks.first().unwrap(); let senders = in_mem_block.senders().expect("failed to recover senders"); @@ -2462,7 +2463,7 @@ mod tests { )?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let in_mem_block = in_memory_blocks.first().unwrap(); let senders = in_mem_block.senders().expect("failed to recover senders"); @@ -2564,7 +2565,7 @@ mod tests { )?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let in_mem_block = in_memory_blocks.first().unwrap(); let senders = in_mem_block.senders().expect("failed to recover senders"); @@ -2660,7 +2661,7 @@ mod tests { )?; provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let in_mem_block = in_memory_blocks.first().unwrap(); let senders = in_mem_block.senders().expect("failed to recover senders"); diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index 1ff8e0f3e18..eeecc014e43 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -61,7 +61,7 @@ async fn main() -> eyre::Result<()> { // 2. Set up the blockchain provider using only the database provider and a noop for the tree to // satisfy trait bounds. Tree is not used in this example since we are only operating on the // disk and don't handle new blocks/live sync etc, which is done by the blockchain tree. - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let rpc_builder = RpcModuleBuilder::default() .with_provider(provider.clone()) From 124242f1d2138a87d066e51ffa595eb2cd72e345 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Wed, 11 Mar 2026 19:49:28 +0530 Subject: [PATCH 23/36] docs --- .../provider/src/providers/bal_provider.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/storage/provider/src/providers/bal_provider.rs b/crates/storage/provider/src/providers/bal_provider.rs index a886489bb77..b0f3ae0760e 100644 --- a/crates/storage/provider/src/providers/bal_provider.rs +++ b/crates/storage/provider/src/providers/bal_provider.rs @@ -181,18 +181,20 @@ impl Default for BalProvider { } impl BalProvider { - fn new(store: Arc, cache: BalCache) -> Self { + /// Creates a new `BalProvider` with the given store and cache. + pub fn new(store: Arc, cache: BalCache) -> Self { Self { store, cache } } + /// Returns a reference to the in-memory cache. pub const fn cache(&self) -> &BalCache { &self.cache } - // Persist first: store is the source of truth. We only populate the in-memory cache if - // durability succeeds, so cache visibility cannot outlive failed persistence. - // `Bytes` is consumed by each insert call, so we clone once for store and move the original - // into cache. + /// Persist first: store is the source of truth. We only populate the in-memory cache if + /// durability succeeds, so cache visibility cannot outlive failed persistence. + /// `Bytes` is consumed by each insert call, so we clone once for store and move the original + /// into cache. pub fn cache_bal( &self, block_hash: BlockHash, @@ -204,7 +206,7 @@ impl BalProvider { Ok(()) } - // Cache-first lookup: keep request order and fill only cache misses from durable storage. + /// Cache-first lookup: keep request order and fill only cache misses from durable storage. pub fn get_by_hashes(&self, block_hashes: &[BlockHash]) -> Vec> { let mut results = self.cache.get_by_hashes(block_hashes); @@ -240,8 +242,8 @@ impl BalProvider { results } - // Cache range reads are contiguous and stop at the first gap. - // Only the missing suffix is queried from store to avoid re-reading cached prefix. + /// Cache range reads are contiguous and stop at the first gap. + /// Only the missing suffix is queried from store to avoid re-reading cached prefix. pub fn get_by_range(&self, start: BlockNumber, count: u64) -> Vec { let mut cache_results = self.cache.get_by_range(start, count); if cache_results.len() as u64 == count { From abb489a786e5842f2e4990dac104113381893f73 Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:22:34 +0530 Subject: [PATCH 24/36] chore: added BalStore to provider of engine api --- crates/rpc/rpc-engine-api/src/engine_api.rs | 47 +++++++++++---------- crates/rpc/rpc-engine-api/src/error.rs | 7 +++ crates/rpc/rpc-engine-api/src/lib.rs | 10 ----- crates/rpc/rpc-engine-api/src/metrics.rs | 44 +++++++++---------- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index f11b44c6648..52df1f213f6 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -1,18 +1,13 @@ use crate::{ - bal_cache::BalCache, - bal_store::{BalStore, BalStoreError}, - capabilities::EngineCapabilities, - metrics::EngineApiMetrics, - EngineApiError, EngineApiResult, + capabilities::EngineCapabilities, metrics::EngineApiMetrics, EngineApiError, EngineApiResult, }; use alloy_eips::{ eip1898::BlockHashOrNumber, eip4844::{BlobAndProofV1, BlobAndProofV2}, eip4895::Withdrawals, eip7685::RequestsOrHash, - BlockNumHash, }; -use alloy_primitives::{BlockHash, BlockNumber, Bytes, B256, U64}; +use alloy_primitives::{BlockHash, BlockNumber, B256, U64}; use alloy_rpc_types_engine::{ CancunPayloadFields, ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1, ExecutionPayloadBodiesV2, ExecutionPayloadBodyV1, ExecutionPayloadBodyV2, @@ -23,12 +18,13 @@ use alloy_rpc_types_engine::{ use async_trait::async_trait; use jsonrpsee_core::{server::RpcModule, RpcResult}; +use reth_bal_store::{BalStore, BalStoreError}; use reth_chainspec::EthereumHardforks; use reth_engine_primitives::{ConsensusEngineHandle, EngineApiValidator, EngineTypes}; use reth_network_api::NetworkInfo; use reth_payload_builder::PayloadStore; use reth_payload_primitives::{ - validate_payload_timestamp, EngineApiMessageVersion, ExecutionPayload, MessageValidationKind, + validate_payload_timestamp, EngineApiMessageVersion, MessageValidationKind, PayloadOrAttributes, PayloadTypes, }; use reth_primitives_traits::{Block, BlockBody}; @@ -336,7 +332,7 @@ where impl EngineApi where - Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, + Provider: HeaderProvider + BlockReader + StateProviderFactory + BalStore + 'static, EngineT: EngineTypes, Pool: TransactionPool + 'static, Validator: EngineApiValidator, @@ -750,8 +746,7 @@ where block_access_list: None, }) .await?; - let bals = - self.inner.bal_provider.get_by_range(start, count, &self.inner.metrics.bal_metrics); + let bals = self.inner.provider.get_by_range(start, count)?; for (body_opt, bal) in bodies.iter_mut().zip(bals.into_iter()) { if let Some(body) = body_opt.as_mut() { body.block_access_list = Some(bal); @@ -848,10 +843,10 @@ where }) .await?; - let bals = self.inner.bal_provider.get_by_hashes(&hashes, &self.inner.metrics.bal_metrics); + let bals = self.get_bals_by_hash(hashes)?; for (body_opt, bal) in bodies.iter_mut().zip(bals.into_iter()) { if let Some(body) = body_opt.as_mut() { - body.block_access_list = bal; + body.block_access_list = Some(bal); } } return Ok(bodies) @@ -1075,20 +1070,28 @@ where /// /// Returns the RLP-encoded BALs for blocks found in the cache or BAL store. /// Missing blocks are returned as empty bytes. - pub fn get_bals_by_hash(&self, block_hashes: Vec) -> Vec { - self.inner - .bal_provider - .get_by_hashes(&block_hashes, &self.inner.metrics.bal_metrics) + pub fn get_bals_by_hash( + &self, + block_hashes: Vec, + ) -> Result, BalStoreError> { + Ok(self + .inner + .provider + .get_by_hashes(&block_hashes)? .into_iter() .map(|opt| opt.unwrap_or_default()) - .collect() + .collect()) } /// Retrieves BALs for a range of blocks from the cache or BAL store. /// /// Returns the RLP-encoded BALs for blocks in the range `[start, start + count)`. - pub fn get_bals_by_range(&self, start: u64, count: u64) -> Vec { - self.inner.bal_provider.get_by_range(start, count, &self.inner.metrics.bal_metrics) + pub fn get_bals_by_range( + &self, + start: u64, + count: u64, + ) -> Result, BalStoreError> { + self.inner.provider.get_by_range(start, count) } } @@ -1097,7 +1100,7 @@ where impl EngineApiServer for EngineApi where - Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, + Provider: HeaderProvider + BlockReader + StateProviderFactory + BalStore + 'static, EngineT: EngineTypes, Pool: TransactionPool + 'static, Validator: EngineApiValidator, @@ -1466,7 +1469,7 @@ where impl RethEngineApiServer for EngineApi where - Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static, + Provider: HeaderProvider + BlockReader + StateProviderFactory + BalStore + 'static, EngineT: EngineTypes, Pool: TransactionPool + 'static, Validator: EngineApiValidator, diff --git a/crates/rpc/rpc-engine-api/src/error.rs b/crates/rpc/rpc-engine-api/src/error.rs index 8d2b678a9f5..c744a2e4410 100644 --- a/crates/rpc/rpc-engine-api/src/error.rs +++ b/crates/rpc/rpc-engine-api/src/error.rs @@ -6,6 +6,7 @@ use alloy_rpc_types_engine::{ use jsonrpsee_types::error::{ INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE, INVALID_PARAMS_MSG, SERVER_ERROR_MSG, }; +use reth_bal_store::BalStoreError; use reth_engine_primitives::{BeaconForkChoiceUpdateError, BeaconOnNewPayloadError}; use reth_payload_builder_primitives::PayloadBuilderError; use reth_payload_primitives::{EngineObjectValidationError, VersionSpecificValidationError}; @@ -207,6 +208,12 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { } } +impl From for EngineApiError { + fn from(err: BalStoreError) -> Self { + EngineApiError::Internal(Box::new(err)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-engine-api/src/lib.rs b/crates/rpc/rpc-engine-api/src/lib.rs index 1332115e6ed..6d3a8695fad 100644 --- a/crates/rpc/rpc-engine-api/src/lib.rs +++ b/crates/rpc/rpc-engine-api/src/lib.rs @@ -12,16 +12,6 @@ /// The Engine API implementation. mod engine_api; -/// Block Access List (BAL) cache for EIP-7928. -mod bal_cache; -pub use bal_cache::BalCache; - -/// Block Access List (BAL) storage abstraction and implementations. -pub mod bal_store; -pub use bal_store::{ - BalStore, BalStoreError, DiskFileBalStore, DiskFileBalStoreConfig, - DEFAULT_MAX_BAL_STORE_ENTRIES, -}; /// Reth-specific engine API extensions. mod reth_engine_api; diff --git a/crates/rpc/rpc-engine-api/src/metrics.rs b/crates/rpc/rpc-engine-api/src/metrics.rs index 1ac1bf0d8f9..d22cfc573f4 100644 --- a/crates/rpc/rpc-engine-api/src/metrics.rs +++ b/crates/rpc/rpc-engine-api/src/metrics.rs @@ -8,8 +8,8 @@ pub(crate) struct EngineApiMetrics { pub(crate) latency: EngineApiLatencyMetrics, /// Blob-related metrics pub(crate) blob_metrics: BlobMetrics, - /// BAL query metrics. - pub(crate) bal_metrics: BalQueryMetrics, + // BAL query metrics. + //pub(crate) bal_metrics: BalQueryMetrics, } /// Beacon consensus engine latency metrics. @@ -79,23 +79,23 @@ pub(crate) struct BlobMetrics { pub(crate) get_blobs_requests_failure_total: Counter, } -#[derive(Metrics)] -#[metrics(scope = "engine.rpc.bal")] -pub(crate) struct BalQueryMetrics { - /// Number of by-hash queries that required store fallback. - pub(crate) store_hash_fallback_requests: Counter, - /// Number of BAL entries recovered from store during by-hash fallback. - pub(crate) store_hash_fallback_hits: Counter, - /// Number of BAL entries still missing after by-hash fallback. - pub(crate) store_hash_fallback_misses: Counter, - /// Number of store errors during by-hash fallback. - pub(crate) store_hash_fallback_errors: Counter, - /// Number of by-range queries that required store fallback. - pub(crate) store_range_fallback_requests: Counter, - /// Number of BAL entries recovered from store during by-range fallback. - pub(crate) store_range_fallback_hits: Counter, - /// Number of BAL entries still missing after by-range fallback. - pub(crate) store_range_fallback_misses: Counter, - /// Number of store errors during by-range fallback. - pub(crate) store_range_fallback_errors: Counter, -} +// #[derive(Metrics)] +// #[metrics(scope = "engine.rpc.bal")] +// pub(crate) struct BalQueryMetrics { +// /// Number of by-hash queries that required store fallback. +// pub(crate) store_hash_fallback_requests: Counter, +// /// Number of BAL entries recovered from store during by-hash fallback. +// pub(crate) store_hash_fallback_hits: Counter, +// /// Number of BAL entries still missing after by-hash fallback. +// pub(crate) store_hash_fallback_misses: Counter, +// /// Number of store errors during by-hash fallback. +// pub(crate) store_hash_fallback_errors: Counter, +// /// Number of by-range queries that required store fallback. +// pub(crate) store_range_fallback_requests: Counter, +// /// Number of BAL entries recovered from store during by-range fallback. +// pub(crate) store_range_fallback_hits: Counter, +// /// Number of BAL entries still missing after by-range fallback. +// pub(crate) store_range_fallback_misses: Counter, +// /// Number of store errors during by-range fallback. +// pub(crate) store_range_fallback_errors: Counter, +// } From 66fbb63b600f906bea34a993032513ffa3cd3ea0 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Wed, 11 Mar 2026 20:36:34 +0530 Subject: [PATCH 25/36] add bal_provider in launch --- Cargo.lock | 1 + crates/net/bal-store/src/lib.rs | 2 +- crates/node/api/src/node.rs | 5 +--- crates/node/builder/src/launch/engine.rs | 8 +++-- crates/node/builder/src/rpc.rs | 1 - .../provider/src/providers/bal_provider.rs | 8 ++++- .../storage/provider/src/test_utils/mock.rs | 29 +++++++++++++++++++ crates/storage/provider/src/traits/full.rs | 3 ++ crates/storage/storage-api/Cargo.toml | 6 ++-- crates/storage/storage-api/src/noop.rs | 27 +++++++++++++++++ 10 files changed, 76 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4c2ccc13cf..92276a910bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10284,6 +10284,7 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", "auto_impl", + "reth-bal-store", "reth-chainspec", "reth-db-api", "reth-db-models", diff --git a/crates/net/bal-store/src/lib.rs b/crates/net/bal-store/src/lib.rs index ca3ef63483a..182ba09d8b8 100644 --- a/crates/net/bal-store/src/lib.rs +++ b/crates/net/bal-store/src/lib.rs @@ -16,7 +16,7 @@ pub use noop::NoopBalStore; /// The store is keyed by block hash and maintains a block-number index for range queries. /// Implementations should preserve contiguous-range semantics: /// queries by range stop at the first missing block. -pub trait BalStore: Debug + Send + Sync + 'static { +pub trait BalStore: Send + Sync + 'static { /// Inserts a BAL for the given block hash and number. fn insert( &self, diff --git a/crates/node/api/src/node.rs b/crates/node/api/src/node.rs index 55a8bbf13f2..113404a6fbc 100644 --- a/crates/node/api/src/node.rs +++ b/crates/node/api/src/node.rs @@ -2,7 +2,6 @@ use crate::PayloadTypes; use alloy_rpc_types_engine::JwtSecret; -use reth_bal_store::BalStore; use reth_basic_payload_builder::PayloadBuilder; use reth_consensus::FullConsensus; use reth_db_api::{database_metrics::DatabaseMetrics, Database}; @@ -16,7 +15,7 @@ use reth_provider::FullProvider; use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use std::{fmt, fmt::Debug, future::Future, marker::PhantomData, sync::Arc}; +use std::{fmt, fmt::Debug, future::Future, marker::PhantomData}; /// A helper trait that is downstream of the [`NodeTypes`] trait and adds stateful /// components to the node. @@ -116,8 +115,6 @@ pub struct AddOnsContext<'a, N: FullNodeComponents> { pub engine_events: EventSender::Primitives>>, /// JWT secret for the node. pub jwt_secret: JwtSecret, - /// Shared BAL store used by both Engine API and network request handling. - pub bal_store: Arc, } impl fmt::Debug for AddOnsContext<'_, N> { diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index d6851675245..c34e4d69843 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -31,7 +31,7 @@ use reth_node_core::{ }; use reth_node_events::node; use reth_provider::{ - providers::{BlockchainProvider, NodeTypesForProvider}, + providers::{BalProvider, BlockchainProvider, NodeTypesForProvider}, BlockNumReader, StorageSettingsCache, }; use reth_tasks::TaskExecutor; @@ -90,6 +90,9 @@ impl EngineNodeLauncher { // Create changeset cache that will be shared across the engine let changeset_cache = ChangesetCache::new(); + // Create the bal cache + let bal_provider = BalProvider::default(); + // setup the launch context let ctx = ctx .with_configured_globals(engine_tree_config.reserved_cpu_cores()) @@ -120,7 +123,7 @@ impl EngineNodeLauncher { // passing FullNodeTypes as type parameter here so that we can build // later the components. .with_blockchain_db::(move |provider_factory| { - Ok(BlockchainProvider::new(provider_factory)?) + Ok(BlockchainProvider::new(provider_factory,bal_provider)?) })? .with_components(components_builder, on_component_initialized).await?; @@ -187,7 +190,6 @@ impl EngineNodeLauncher { beacon_engine_handle: beacon_engine_handle.clone(), jwt_secret, engine_events: event_sender.clone(), - bal_store: ctx.bal_store().clone(), }; let validator_builder = add_ons.engine_validator_builder(); diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 05f48be53d4..9da3f4eb144 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1406,7 +1406,6 @@ where engine_validator, ctx.config.engine.accept_execution_requests_hash, ctx.node.network().clone(), - ctx.bal_store.clone(), )) } } diff --git a/crates/storage/provider/src/providers/bal_provider.rs b/crates/storage/provider/src/providers/bal_provider.rs index b0f3ae0760e..d59a4fda6d2 100644 --- a/crates/storage/provider/src/providers/bal_provider.rs +++ b/crates/storage/provider/src/providers/bal_provider.rs @@ -165,7 +165,7 @@ impl BalStore for BalCache { /// /// Reads are cache-first with fallback to the store. /// Writes are store-first to ensure durability before cache visibility. -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct BalProvider { /// Persistent storage backend for BALs. store: Arc, @@ -173,6 +173,12 @@ pub struct BalProvider { cache: BalCache, } +impl std::fmt::Debug for BalProvider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BalProvider").field("cache", &self.cache).finish_non_exhaustive() + } +} + impl Default for BalProvider { fn default() -> Self { let cache = BalCache::new(); diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index c8c6e80e217..d4e59a3b750 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -17,6 +17,7 @@ use alloy_primitives::{ Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, TxNumber, B256, U256, }; use parking_lot::Mutex; +use reth_bal_store::BalStore; use reth_chain_state::{CanonStateNotifications, CanonStateSubscriptions}; use reth_chainspec::{ChainInfo, EthChainSpec}; use reth_db::transaction::DbTx; @@ -1071,6 +1072,34 @@ impl NodePrimitivesProvider type Primitives = T; } +impl BalStore + for MockEthProvider +{ + fn insert( + &self, + _block_hash: BlockHash, + _block_number: BlockNumber, + _bal: Bytes, + ) -> Result<(), reth_bal_store::BalStoreError> { + Ok(()) + } + + fn get_by_hashes( + &self, + _block_hashes: &[BlockHash], + ) -> Result>, reth_bal_store::BalStoreError> { + Ok(Vec::new()) + } + + fn get_by_range( + &self, + _start: BlockNumber, + _count: u64, + ) -> Result, reth_bal_store::BalStoreError> { + Ok(Vec::new()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/storage/provider/src/traits/full.rs b/crates/storage/provider/src/traits/full.rs index 928ab55a568..e49c492bd91 100644 --- a/crates/storage/provider/src/traits/full.rs +++ b/crates/storage/provider/src/traits/full.rs @@ -6,6 +6,7 @@ use crate::{ RocksDBProviderFactory, StageCheckpointReader, StateProviderFactory, StateReader, StaticFileProviderFactory, }; +use reth_bal_store::BalStore; use reth_chain_state::{ CanonStateSubscriptions, ForkChoiceSubscriptions, PersistedBlockSubscriptions, }; @@ -42,6 +43,7 @@ pub trait FullProvider: + ForkChoiceSubscriptions
> + PersistedBlockSubscriptions + StageCheckpointReader + + BalStore + Clone + Debug + Unpin @@ -77,6 +79,7 @@ impl FullProvider for T where + ForkChoiceSubscriptions
> + PersistedBlockSubscriptions + StageCheckpointReader + + BalStore + Clone + Debug + Unpin diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index d83b1346c71..c1e0aee8f45 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -24,6 +24,7 @@ reth-storage-errors.workspace = true reth-trie-common.workspace = true revm-database.workspace = true reth-ethereum-primitives.workspace = true +reth-bal-store.workspace = true # ethereum alloy-eips.workspace = true @@ -56,10 +57,7 @@ std = [ "serde_json?/std", ] -db-api = [ - "dep:reth-db-api", - "dep:serde_json", -] +db-api = ["dep:reth-db-api", "dep:serde_json"] serde = [ "reth-ethereum-primitives/serde", diff --git a/crates/storage/storage-api/src/noop.rs b/crates/storage/storage-api/src/noop.rs index ee51b2458bc..98c836f2b25 100644 --- a/crates/storage/storage-api/src/noop.rs +++ b/crates/storage/storage-api/src/noop.rs @@ -22,6 +22,7 @@ use core::{ marker::PhantomData, ops::{RangeBounds, RangeInclusive}, }; +use reth_bal_store::BalStore; use reth_chainspec::{ChainInfo, ChainSpecProvider, EthChainSpec, MAINNET}; #[cfg(feature = "db-api")] use reth_db_api::mock::{DatabaseMock, TxMock}; @@ -704,3 +705,29 @@ impl StorageSettingsCache for NoopProvid fn set_storage_settings_cache(&self, _settings: reth_db_api::models::StorageSettings) {} } + +impl BalStore for NoopProvider { + fn insert( + &self, + _block_hash: BlockHash, + _block_number: BlockNumber, + _bal: Bytes, + ) -> Result<(), reth_bal_store::BalStoreError> { + Ok(()) + } + + fn get_by_hashes( + &self, + _block_hashes: &[BlockHash], + ) -> Result>, reth_bal_store::BalStoreError> { + Ok(Vec::new()) + } + + fn get_by_range( + &self, + _start: BlockNumber, + _count: u64, + ) -> Result, reth_bal_store::BalStoreError> { + Ok(Vec::new()) + } +} From c6db746ba9bce199eb897e007b23c007e52a6e3a Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:52:39 +0530 Subject: [PATCH 26/36] chore: compilation fix --- crates/net/bal-store/src/disk.rs | 22 +++ crates/rpc/rpc-engine-api/src/engine_api.rs | 173 -------------------- 2 files changed, 22 insertions(+), 173 deletions(-) diff --git a/crates/net/bal-store/src/disk.rs b/crates/net/bal-store/src/disk.rs index 5e31c799842..d31264120de 100644 --- a/crates/net/bal-store/src/disk.rs +++ b/crates/net/bal-store/src/disk.rs @@ -468,6 +468,28 @@ impl std::fmt::Display for DiskFileBalStore { } } +impl BalStore for Arc { + fn insert( + &self, + block_hash: BlockHash, + block_number: BlockNumber, + bal: Bytes, + ) -> Result<(), BalStoreError> { + (**self).insert(block_hash, block_number, bal) + } + + fn get_by_hashes( + &self, + block_hashes: &[BlockHash], + ) -> Result>, BalStoreError> { + (**self).get_by_hashes(block_hashes) + } + + fn get_by_range(&self, start: BlockNumber, count: u64) -> Result, BalStoreError> { + (**self).get_by_range(start, count) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 52df1f213f6..dad52e814c7 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -1973,177 +1973,4 @@ mod tests { assert_eq!(res, expected); } } - - mod bal_queries { - use super::*; - use alloy_rpc_types_engine::ClientCode; - use parking_lot::{Mutex, RwLock}; - use std::{ - collections::{BTreeMap, HashMap}, - future::Future, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, - }; - - #[derive(Debug, Clone, Default)] - struct TestBalStore { - by_hash: Arc>>, - by_number: Arc>>, - hash_queries: Arc, - range_queries: Arc>>, - } - - impl TestBalStore { - fn insert_raw(&self, block_hash: BlockHash, block_number: BlockNumber, bal: Bytes) { - self.by_hash.write().insert(block_hash, bal); - self.by_number.write().insert(block_number, block_hash); - } - - fn hash_query_count(&self) -> usize { - self.hash_queries.load(Ordering::Relaxed) - } - - fn range_queries(&self) -> Vec<(BlockNumber, u64)> { - self.range_queries.lock().clone() - } - } - - impl BalStore for TestBalStore { - fn insert( - &self, - block_hash: BlockHash, - block_number: BlockNumber, - bal: Bytes, - ) -> Result<(), BalStoreError> { - self.insert_raw(block_hash, block_number, bal); - Ok(()) - } - - fn get_by_hashes( - &self, - block_hashes: &[BlockHash], - ) -> Result>, BalStoreError> { - self.hash_queries.fetch_add(1, Ordering::Relaxed); - let by_hash = self.by_hash.read(); - Ok(block_hashes.iter().map(|hash| by_hash.get(hash).cloned()).collect()) - } - - fn get_by_range( - &self, - start: BlockNumber, - count: u64, - ) -> Result, BalStoreError> { - self.range_queries.lock().push((start, count)); - let by_hash = self.by_hash.read(); - let by_number = self.by_number.read(); - let mut result = Vec::new(); - - for block_number in start..start.saturating_add(count) { - let Some(hash) = by_number.get(&block_number) else { - break; - }; - let Some(bal) = by_hash.get(hash) else { - break; - }; - result.push(bal.clone()); - } - Ok(result) - } - } - - fn setup_engine_api_with_store( - bal_store: Arc, - ) -> EngineApi< - Arc, - EthEngineTypes, - NoopTransactionPool, - EthereumEngineValidator, - ChainSpec, - > { - let chain_spec: Arc = MAINNET.clone(); - let provider = Arc::new(MockEthProvider::default()); - let payload_store = spawn_test_payload_service(); - let (to_engine, _) = unbounded_channel(); - - EngineApi::with_bal_store( - provider, - chain_spec.clone(), - ConsensusEngineHandle::new(to_engine), - payload_store.into(), - NoopTransactionPool::default(), - Runtime::test(), - ClientVersionV1 { - code: ClientCode::RH, - name: "Reth".to_string(), - version: "v0.0.0-test".to_string(), - commit: "test".to_string(), - }, - EngineCapabilities::default(), - EthereumEngineValidator::new(chain_spec), - false, - NoopNetwork::default(), - bal_store, - ) - } - - fn run_with_tokio_runtime(test: impl Future) { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to build tokio runtime for test"); - runtime.block_on(test); - } - - #[test] - fn by_hash_uses_partial_store_fallback() { - run_with_tokio_runtime(async { - let store = TestBalStore::default(); - let hash1 = B256::random(); - let hash2 = B256::random(); - let hash3 = B256::random(); - let bal1 = Bytes::from_static(b"bal1"); - let bal2 = Bytes::from_static(b"bal2"); - - store.insert_raw(hash2, 2, bal2.clone()); - let api = setup_engine_api_with_store(Arc::new(store.clone())); - api.bal_cache().insert(hash1, 1, bal1.clone()); - - let results = api.get_bals_by_hash(vec![hash1, hash2, hash3]); - assert_eq!(results, vec![bal1, bal2, Bytes::new()]); - assert_eq!(store.hash_query_count(), 1); - }); - } - - #[test] - fn by_range_fallback_queries_only_missing_suffix() { - run_with_tokio_runtime(async { - let store = TestBalStore::default(); - let hash1 = B256::random(); - let hash2 = B256::random(); - let hash3 = B256::random(); - let hash4 = B256::random(); - let hash5 = B256::random(); - - let bal1 = Bytes::from_static(b"bal1"); - let bal2 = Bytes::from_static(b"bal2"); - let bal3 = Bytes::from_static(b"bal3"); - let bal4 = Bytes::from_static(b"bal4"); - let bal5 = Bytes::from_static(b"bal5"); - - store.insert_raw(hash3, 3, bal3.clone()); - store.insert_raw(hash4, 4, bal4.clone()); - store.insert_raw(hash5, 5, bal5.clone()); - - let api = setup_engine_api_with_store(Arc::new(store.clone())); - api.bal_cache().insert(hash1, 1, bal1.clone()); - api.bal_cache().insert(hash2, 2, bal2.clone()); - - let results = api.get_bals_by_range(1, 5); - assert_eq!(results, vec![bal1, bal2, bal3, bal4, bal5]); - assert_eq!(store.range_queries(), vec![(3, 3)]); - }); - } - } } From 8db4432d72144ae5ac4c66737678d77c7af8a745 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Wed, 11 Mar 2026 20:55:04 +0530 Subject: [PATCH 27/36] fix tests --- Cargo.lock | 2 -- crates/engine/tree/src/tree/payload_processor/mod.rs | 2 +- crates/node/api/Cargo.toml | 1 - crates/rpc/rpc-builder/tests/it/utils.rs | 3 +-- crates/rpc/rpc-engine-api/Cargo.toml | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92276a910bf..0fc3899deca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9203,7 +9203,6 @@ version = "1.11.0" dependencies = [ "alloy-rpc-types-engine", "eyre", - "reth-bal-store", "reth-basic-payload-builder", "reth-consensus", "reth-db-api", @@ -9972,7 +9971,6 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", - "parking_lot", "reth-bal-store", "reth-chainspec", "reth-engine-primitives", diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 866186bc1e1..c5162c672c7 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -1190,7 +1190,7 @@ mod tests { use reth_evm_ethereum::EthEvmConfig; use reth_primitives_traits::{Account, Recovered, StorageEntry}; use reth_provider::{ - providers::{BlockchainProvider, OverlayStateProviderFactory}, + providers::{BalProvider, BlockchainProvider, OverlayStateProviderFactory}, test_utils::create_test_provider_factory_with_chain_spec, ChainSpecProvider, HashingWriter, }; diff --git a/crates/node/api/Cargo.toml b/crates/node/api/Cargo.toml index 8b810ddccd2..7bd6196318d 100644 --- a/crates/node/api/Cargo.toml +++ b/crates/node/api/Cargo.toml @@ -17,7 +17,6 @@ reth-db-api.workspace = true reth-consensus.workspace = true reth-evm.workspace = true reth-provider.workspace = true -reth-bal-store.workspace = true reth-engine-primitives.workspace = true reth-transaction-pool.workspace = true reth-payload-builder.workspace = true diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index 6a118c37b8b..5bbca0a5918 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -16,7 +16,7 @@ use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerConfig, AuthServerHandle}, RpcModuleBuilder, RpcServerConfig, RpcServerHandle, TransportRpcModuleConfig, }; -use reth_rpc_engine_api::{bal_store::NoopBalStore, capabilities::EngineCapabilities, EngineApi}; +use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; use reth_rpc_layer::JwtSecret; use reth_rpc_server_types::RpcModuleSelection; use reth_tasks::Runtime; @@ -55,7 +55,6 @@ pub async fn launch_auth(secret: JwtSecret) -> AuthServerHandle { EthereumEngineValidator::new(MAINNET.clone()), false, NoopNetwork::default(), - std::sync::Arc::new(NoopBalStore), ); let module = AuthRpcModule::new(engine_api); module.start_server(config).await.unwrap() diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index 9195654b62d..50ce49360fc 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -43,7 +43,6 @@ metrics.workspace = true async-trait.workspace = true jsonrpsee-core.workspace = true jsonrpsee-types.workspace = true -parking_lot.workspace = true serde.workspace = true thiserror.workspace = true tracing.workspace = true From eb847ae71263e927b7c47bc6433f1518c7e9d33e Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:28:04 +0530 Subject: [PATCH 28/36] chore: inserted in bal store only if validation is successful --- Cargo.lock | 1 + crates/engine/tree/Cargo.toml | 1 + crates/engine/tree/src/tree/payload_validator.rs | 6 ++++++ crates/exex/test-utils/src/lib.rs | 2 +- examples/rpc-db/src/main.rs | 2 +- 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fc3899deca..4e9f46fcaeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8367,6 +8367,7 @@ dependencies = [ "rand 0.8.5", "rand 0.9.2", "rayon", + "reth-bal-store", "reth-chain-state", "reth-chainspec", "reth-consensus", diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index 5c5cf853953..4af3338b855 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] # reth +reth-bal-store.workspace = true reth-chain-state = { workspace = true, features = ["rayon"] } reth-chainspec = { workspace = true, optional = true } reth-consensus.workspace = true diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index aa1ea214cba..be43a006a82 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -16,6 +16,7 @@ use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash}; use alloy_evm::Evm; use alloy_primitives::{map::B256Set, B256}; use alloy_rlp::Decodable; +use reth_bal_store::BalStore; #[cfg(feature = "trie-debug")] use reth_trie_sparse::debug_recorder::TrieDebugRecorder; @@ -178,6 +179,7 @@ where + StateProviderFactory + StateReader + HashedPostStateProvider + + BalStore + Clone + 'static, Evm: ConfigureEvm + 'static, @@ -993,6 +995,9 @@ where ConsensusError::BlockAccessListHashMismatch((got, expected).into()), )); } + let bal_bytes: alloy_primitives::Bytes = + alloy_rlp::encode(built_bal.unwrap_or_default()).into(); + let _ = self.provider.insert(input.hash(), input.num_hash().number, bal_bytes); } let output = BlockExecutionOutput { result, state: db.take_bundle() }; @@ -1986,6 +1991,7 @@ where + ChangeSetReader + BlockNumReader + HashedPostStateProvider + + BalStore + Clone + 'static, N: NodePrimitives, diff --git a/crates/exex/test-utils/src/lib.rs b/crates/exex/test-utils/src/lib.rs index 958a14f2872..8f42f9b09ff 100644 --- a/crates/exex/test-utils/src/lib.rs +++ b/crates/exex/test-utils/src/lib.rs @@ -52,7 +52,7 @@ use reth_node_ethereum::{ use reth_payload_builder::noop::NoopPayloadBuilderService; use reth_primitives_traits::{Block as _, RecoveredBlock}; use reth_provider::{ - providers::{BlockchainProvider, RocksDBProvider, StaticFileProvider}, + providers::{BalProvider, BlockchainProvider, RocksDBProvider, StaticFileProvider}, BlockReader, EthStorage, ProviderFactory, }; use reth_tasks::Runtime; diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index eeecc014e43..63efa604a37 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -24,7 +24,7 @@ use reth_ethereum::{ pool::noop::NoopTransactionPool, provider::{ db::{mdbx::DatabaseArguments, open_db_read_only, ClientVersion, DatabaseEnv}, - providers::{BlockchainProvider, RocksDBProvider, StaticFileProvider}, + providers::{BalProvider, BlockchainProvider, RocksDBProvider, StaticFileProvider}, ProviderFactory, }, rpc::{ From 76737d5491f97d7865f4091f4e329cb07e250ee0 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Wed, 11 Mar 2026 22:08:29 +0530 Subject: [PATCH 29/36] dprint --- crates/net/network/Cargo.toml | 1 - crates/storage/provider/Cargo.toml | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 93a78ecf434..f3f1654f7b5 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -36,7 +36,6 @@ reth-network-peers = { workspace = true, features = ["net"] } reth-network-types = { workspace = true, features = ["serde"] } reth-bal-store.workspace = true - # ethereum alloy-consensus.workspace = true alloy-eips.workspace = true diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 28538ae0c90..a58a5ed9c46 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -16,11 +16,7 @@ workspace = true reth-chainspec.workspace = true reth-execution-types.workspace = true reth-ethereum-primitives = { workspace = true, features = ["reth-codec"] } -reth-primitives-traits = { workspace = true, features = [ - "reth-codec", - "secp256k1", - "dashmap", -] } +reth-primitives-traits = { workspace = true, features = ["reth-codec", "secp256k1", "dashmap"] } reth-errors.workspace = true reth-storage-errors.workspace = true reth-storage-api = { workspace = true, features = ["std", "db-api"] } @@ -56,9 +52,7 @@ metrics.workspace = true # misc itertools.workspace = true -notify = { workspace = true, default-features = false, features = [ - "macos_fsevent", -] } +notify = { workspace = true, default-features = false, features = ["macos_fsevent"] } parking_lot.workspace = true strum.workspace = true eyre.workspace = true @@ -76,10 +70,7 @@ rocksdb = { workspace = true, features = ["jemalloc"], optional = true } [dev-dependencies] reth-db = { workspace = true, features = ["test-utils"] } -reth-primitives-traits = { workspace = true, features = [ - "arbitrary", - "test-utils", -] } +reth-primitives-traits = { workspace = true, features = ["arbitrary", "test-utils"] } reth-chain-state = { workspace = true, features = ["test-utils"] } reth-trie = { workspace = true, features = ["test-utils"] } reth-testing-utils.workspace = true From 357c97317cd80b5df5e99ea7bc0ccba07838a427 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Wed, 11 Mar 2026 22:47:52 +0530 Subject: [PATCH 30/36] fix --- crates/storage/provider/src/providers/consistent.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index 8c29a8337e0..0eafc4cf23e 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -2219,7 +2219,7 @@ mod tests { provider_rw.commit()?; - let provider = BlockchainProvider::new(factory)?; + let provider = BlockchainProvider::new(factory, BalProvider::default())?; let consistent_provider = provider.consistent_provider()?; let outcome = From bd7c577247bdfd2212231360f76c51c655b4ca7c Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Thu, 12 Mar 2026 10:49:31 +0530 Subject: [PATCH 31/36] chore: clippy --- .github/scripts/hive/expected_failures.yaml | 4 ++-- crates/storage/provider/src/providers/bal_provider.rs | 4 +--- crates/storage/provider/src/providers/blockchain_provider.rs | 5 +++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/scripts/hive/expected_failures.yaml b/.github/scripts/hive/expected_failures.yaml index 7046f5011e2..49b1c8c4a34 100644 --- a/.github/scripts/hive/expected_failures.yaml +++ b/.github/scripts/hive/expected_failures.yaml @@ -21,10 +21,10 @@ engine-withdrawals: [] engine-api: [] engine-cancun: - - Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth) + # - Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth) # the test fails with older versions of the code for which it passed before, probably related to changes # in hive or its dependencies - - Blob Transaction Ordering, Multiple Clients (Cancun) (reth) + # - Blob Transaction Ordering, Multiple Clients (Cancun) (reth) sync: [] diff --git a/crates/storage/provider/src/providers/bal_provider.rs b/crates/storage/provider/src/providers/bal_provider.rs index d59a4fda6d2..58c5a3f439d 100644 --- a/crates/storage/provider/src/providers/bal_provider.rs +++ b/crates/storage/provider/src/providers/bal_provider.rs @@ -232,9 +232,7 @@ impl BalProvider { match self.store.get_by_hashes(&missing_hashes) { Ok(store_results) => { - for (missing_idx, store_result) in - missing_indices.into_iter().zip(store_results.into_iter()) - { + for (missing_idx, store_result) in missing_indices.into_iter().zip(store_results) { if let Some(value) = store_result { results[missing_idx] = Some(value); } diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 7940611c6b4..53d6ddd7919 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -51,7 +51,7 @@ pub struct BlockchainProvider { /// Tracks the chain info wrt forkchoice updates and in memory canonical /// state. pub(crate) canonical_in_memory_state: CanonicalInMemoryState, - /// + /// Bal Provider used to access the the Bal Cache pub(crate) bal_provider: BalProvider, } @@ -809,7 +809,8 @@ impl BalStore for BlockchainProvider { block_number: BlockNumber, bal: alloy_primitives::Bytes, ) -> Result<(), reth_bal_store::BalStoreError> { - Ok(self.bal_provider.cache().insert(block_hash, block_number, bal)) + self.bal_provider.cache().insert(block_hash, block_number, bal); + Ok(()) } fn get_by_hashes( From 7f3700f1b5272079b1dae2fc192908e1472c60a7 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Thu, 12 Mar 2026 11:11:10 +0530 Subject: [PATCH 32/36] clippy --- crates/rpc/rpc-engine-api/src/engine_api.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index dad52e814c7..dbbb0fe5677 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -747,12 +747,12 @@ where }) .await?; let bals = self.inner.provider.get_by_range(start, count)?; - for (body_opt, bal) in bodies.iter_mut().zip(bals.into_iter()) { + for (body_opt, bal) in bodies.iter_mut().zip(bals) { if let Some(body) = body_opt.as_mut() { body.block_access_list = Some(bal); } } - return Ok(bodies) + Ok(bodies) } /// Metrics version of `get_payload_bodies_by_range_v2` @@ -844,12 +844,12 @@ where .await?; let bals = self.get_bals_by_hash(hashes)?; - for (body_opt, bal) in bodies.iter_mut().zip(bals.into_iter()) { + for (body_opt, bal) in bodies.iter_mut().zip(bals) { if let Some(body) = body_opt.as_mut() { body.block_access_list = Some(bal); } } - return Ok(bodies) + Ok(bodies) } /// Metrics version of `get_payload_bodies_by_hash_v2` From c2b4e6c0638b1d2e1094dd9bf2995250005e1163 Mon Sep 17 00:00:00 2001 From: Soubhik Singha Mahapatra Date: Thu, 12 Mar 2026 12:26:02 +0530 Subject: [PATCH 33/36] clippy --- .github/scripts/hive/expected_failures.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/hive/expected_failures.yaml b/.github/scripts/hive/expected_failures.yaml index 49b1c8c4a34..19e326440f6 100644 --- a/.github/scripts/hive/expected_failures.yaml +++ b/.github/scripts/hive/expected_failures.yaml @@ -20,7 +20,7 @@ engine-withdrawals: [] engine-api: [] -engine-cancun: +engine-cancun: [] # - Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth) # the test fails with older versions of the code for which it passed before, probably related to changes # in hive or its dependencies From 43104e99d1f3dddac17ccae5e2933551704fbfe9 Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:10:59 +0530 Subject: [PATCH 34/36] fixes --- crates/rpc/rpc-engine-api/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc-engine-api/src/error.rs b/crates/rpc/rpc-engine-api/src/error.rs index c744a2e4410..71eaafe3cf8 100644 --- a/crates/rpc/rpc-engine-api/src/error.rs +++ b/crates/rpc/rpc-engine-api/src/error.rs @@ -210,7 +210,7 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { impl From for EngineApiError { fn from(err: BalStoreError) -> Self { - EngineApiError::Internal(Box::new(err)) + Self::Internal(Box::new(err)) } } From caba51b006d3a3906dd26ae6d11f554a29ca24bb Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:51:05 +0530 Subject: [PATCH 35/36] fixes --- crates/engine/tree/src/tree/payload_validator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 630ee0926ad..2fd41eff569 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -1399,7 +1399,7 @@ where if built_bal.is_some() { let bal_bytes: alloy_primitives::Bytes = alloy_rlp::encode(built_bal.unwrap_or_default()).into(); - let _ = self.provider.insert(input.hash(), input.num_hash().number, bal_bytes); + let _ = self.provider.insert(block.hash(), block.num_hash().number, bal_bytes); } drop(_enter); From c8b55d058b99e0edc016b3b65031a5797efd8d9d Mon Sep 17 00:00:00 2001 From: Ishika Choudhury <117741714+Rimeeeeee@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:17:58 +0530 Subject: [PATCH 36/36] fixes --- crates/engine/tree/src/tree/payload_validator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index 2fd41eff569..3f088658a72 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -1389,7 +1389,7 @@ where block, output, receipt_root_bloom, - built_bal, + built_bal.clone(), true, ) { // call post-block hook