From 0071d21f67cae48a3f462b3865cd23900b41bfc7 Mon Sep 17 00:00:00 2001 From: malik672 Date: Sat, 10 Jan 2026 15:43:33 +0100 Subject: [PATCH 1/4] impl block_cache --- crates/database/src/states.rs | 2 + .../database/src/states/block_hash_cache.rs | 56 ++++++++++++++ crates/database/src/states/state.rs | 73 ++++++++----------- crates/database/src/states/state_builder.rs | 11 +-- 4 files changed, 96 insertions(+), 46 deletions(-) create mode 100644 crates/database/src/states/block_hash_cache.rs diff --git a/crates/database/src/states.rs b/crates/database/src/states.rs index 49adc0e773..871a1b453d 100644 --- a/crates/database/src/states.rs +++ b/crates/database/src/states.rs @@ -2,6 +2,8 @@ /// Account status tracking. pub mod account_status; +/// Block hash cache. +pub mod block_hash_cache; /// Bundle account representation. pub mod bundle_account; /// Bundle state management. diff --git a/crates/database/src/states/block_hash_cache.rs b/crates/database/src/states/block_hash_cache.rs new file mode 100644 index 0000000000..c94736e7db --- /dev/null +++ b/crates/database/src/states/block_hash_cache.rs @@ -0,0 +1,56 @@ +use primitives::{alloy_primitives::B256, BLOCK_HASH_HISTORY}; + +const BLOCK_HASH_HISTORY_USIZE: usize = BLOCK_HASH_HISTORY as usize; +const BLOCK_HASH_HISTORY_MINUS_ONE: u64 = BLOCK_HASH_HISTORY - 1; + +/// A fixed-size cache for the 256 most recent block hashes. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BlockHashCache { + /// The block number corresponding to index 0 of the `hashes` array. + start_block: u64, + /// A fixed-size array holding the block hashes. + /// Since we only store the most recent 256 block hashes, this array has a length of 256. + /// The reason we store block number alongside its hash is to handle the case where it wraps around, so we can verify the block number. + hashes: [(u64, B256); BLOCK_HASH_HISTORY_USIZE], +} + +impl Default for BlockHashCache { + fn default() -> Self { + Self::new() + } +} + +impl BlockHashCache { + /// Creates a new empty BlockHashCache. + #[inline] + pub const fn new() -> Self { + Self { + start_block: 0, + hashes: [(0, B256::ZERO); BLOCK_HASH_HISTORY_USIZE], + } + } + + /// Inserts a block hash for the given block number. + #[inline] + pub const fn insert(&mut self, block_number: u64, block_hash: B256) { + let index = (block_number % BLOCK_HASH_HISTORY) as usize; + self.hashes[index] = (block_number, block_hash); + if block_number >= self.start_block + BLOCK_HASH_HISTORY { + // this only runs when block_number >= self.start_block + 256 + // Overflow impossible due to the check above + self.start_block = block_number - BLOCK_HASH_HISTORY_MINUS_ONE; + } + } + + /// Retrieves the block hash for the given block number, if it exists in the cache. + #[inline] + pub const fn get(&self, block_number: u64) -> Option { + let index = (block_number % BLOCK_HASH_HISTORY) as usize; + let (stored_block_number, stored_hash) = self.hashes[index]; + if stored_block_number == block_number { + Some(stored_hash) + } else { + None + } + } +} diff --git a/crates/database/src/states/state.rs b/crates/database/src/states/state.rs index 10b7292677..5cf548a750 100644 --- a/crates/database/src/states/state.rs +++ b/crates/database/src/states/state.rs @@ -1,3 +1,5 @@ +use crate::states::block_hash_cache::BlockHashCache; + use super::{ bundle_state::BundleRetention, cache::CacheState, plain_account::PlainStorage, BundleState, CacheAccount, StateBuilder, TransitionAccount, TransitionState, @@ -7,16 +9,12 @@ use database_interface::{ bal::{BalState, EvmDatabaseError}, Database, DatabaseCommit, DatabaseRef, EmptyDB, }; -use primitives::{hash_map, Address, HashMap, StorageKey, StorageValue, B256, BLOCK_HASH_HISTORY}; +use primitives::{hash_map, Address, FixedBytes, HashMap, StorageKey, StorageValue, B256}; use state::{ bal::{alloy::AlloyBal, Bal}, Account, AccountInfo, }; -use std::{ - boxed::Box, - collections::{btree_map, BTreeMap}, - sync::Arc, -}; +use std::{boxed::Box, sync::Arc}; /// Database boxed with a lifetime and Send pub type DBBox<'a, E> = Box + Send + 'a>; @@ -68,7 +66,7 @@ pub struct State { /// This map can be used to give different values for block hashes if in case. /// /// The fork block is different or some blocks are not saved inside database. - pub block_hashes: BTreeMap, + pub block_hashes: BlockHashCache, /// BAL state. /// /// Can contain both the BAL for reads and BAL builder that is used to build BAL. @@ -355,28 +353,21 @@ impl Database for State { } fn block_hash(&mut self, number: u64) -> Result { - match self.block_hashes.entry(number) { - btree_map::Entry::Occupied(entry) => Ok(*entry.get()), - btree_map::Entry::Vacant(entry) => { - let ret = *entry.insert( - self.database - .block_hash(number) - .map_err(EvmDatabaseError::Database)?, - ); - - // Prune all hashes that are older than BLOCK_HASH_HISTORY - let last_block = number.saturating_sub(BLOCK_HASH_HISTORY); - while let Some(entry) = self.block_hashes.first_entry() { - if *entry.key() < last_block { - entry.remove(); - } else { - break; - } - } - - Ok(ret) - } + // Check cache first + if let Some(hash) = self.block_hashes.get(number) { + return Ok(hash); } + + // Not in cache, fetch from database + let hash = self + .database + .block_hash(number) + .map_err(EvmDatabaseError::Database)?; + + // Insert into cache + self.block_hashes.insert(number, hash); + + Ok(hash) } } @@ -495,8 +486,8 @@ impl DatabaseRef for State { } fn block_hash_ref(&self, number: u64) -> Result { - if let Some(entry) = self.block_hashes.get(&number) { - return Ok(*entry); + if let Some(entry) = self.block_hashes.get(number) { + return Ok(FixedBytes(*entry)); } // If not found, load it from database self.database @@ -512,8 +503,7 @@ mod tests { states::{reverts::AccountInfoRevert, StorageSlot}, AccountRevert, AccountStatus, BundleAccount, RevertToSlot, }; - use primitives::{keccak256, U256}; - + use primitives::{keccak256, BLOCK_HASH_HISTORY, U256}; #[test] fn block_hash_cache() { let mut state = State::builder().build(); @@ -526,18 +516,19 @@ mod tests { let block2_hash = keccak256(U256::from(2).to_string().as_bytes()); let block_test_hash = keccak256(U256::from(test_number).to_string().as_bytes()); - assert_eq!( - state.block_hashes, - BTreeMap::from([(1, block1_hash), (2, block2_hash)]) - ); + // Verify blocks 1 and 2 are in cache + assert_eq!(state.block_hashes.get(1), Some(block1_hash)); + assert_eq!(state.block_hashes.get(2), Some(block2_hash)); + // Fetch block beyond BLOCK_HASH_HISTORY + // Block 258 % 256 = 2, so it will overwrite block 2 state.block_hash(test_number).unwrap(); - assert_eq!( - state.block_hashes, - BTreeMap::from([(test_number, block_test_hash), (2, block2_hash)]) - ); - } + // Block 2 should be evicted (wrapped around), but block 1 should still be present + assert_eq!(state.block_hashes.get(1), Some(block1_hash)); + assert_eq!(state.block_hashes.get(2), None); + assert_eq!(state.block_hashes.get(test_number), Some(block_test_hash)); + } /// Checks that if accounts is touched multiple times in the same block, /// then the old values from the first change are preserved and not overwritten. /// diff --git a/crates/database/src/states/state_builder.rs b/crates/database/src/states/state_builder.rs index 3148c271b7..3159e79694 100644 --- a/crates/database/src/states/state_builder.rs +++ b/crates/database/src/states/state_builder.rs @@ -1,10 +1,11 @@ +use crate::states::block_hash_cache::BlockHashCache; + use super::{cache::CacheState, state::DBBox, BundleState, State, TransitionState}; use database_interface::{ bal::BalState, DBErrorMarker, Database, DatabaseRef, EmptyDB, WrapDatabaseRef, }; -use primitives::B256; use state::bal::Bal; -use std::{collections::BTreeMap, sync::Arc}; +use std::sync::Arc; /// Allows building of State and initializing it with different options. #[derive(Clone, Debug, PartialEq, Eq)] @@ -31,7 +32,7 @@ pub struct StateBuilder { /// Default is false. with_background_transition_merge: bool, /// If we want to set different block hashes, - with_block_hashes: BTreeMap, + with_block_hashes: BlockHashCache, /// BAL state. bal_state: BalState, } @@ -62,7 +63,7 @@ impl StateBuilder { with_bundle_prestate: None, with_bundle_update: false, with_background_transition_merge: false, - with_block_hashes: BTreeMap::new(), + with_block_hashes: BlockHashCache::new(), bal_state: BalState::default(), } } @@ -158,7 +159,7 @@ impl StateBuilder { } /// Sets the block hashes for the state. - pub fn with_block_hashes(self, block_hashes: BTreeMap) -> Self { + pub fn with_block_hashes(self, block_hashes: BlockHashCache) -> Self { Self { with_block_hashes: block_hashes, ..self From 20fec1e824d0446783d8aa77981fb867cf2f6e47 Mon Sep 17 00:00:00 2001 From: malik672 Date: Wed, 14 Jan 2026 20:53:59 +0100 Subject: [PATCH 2/4] bump --- crates/database/src/states/block_hash_cache.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/database/src/states/block_hash_cache.rs b/crates/database/src/states/block_hash_cache.rs index c94736e7db..431accf252 100644 --- a/crates/database/src/states/block_hash_cache.rs +++ b/crates/database/src/states/block_hash_cache.rs @@ -1,13 +1,9 @@ use primitives::{alloy_primitives::B256, BLOCK_HASH_HISTORY}; const BLOCK_HASH_HISTORY_USIZE: usize = BLOCK_HASH_HISTORY as usize; -const BLOCK_HASH_HISTORY_MINUS_ONE: u64 = BLOCK_HASH_HISTORY - 1; - /// A fixed-size cache for the 256 most recent block hashes. #[derive(Debug, Clone, PartialEq, Eq)] pub struct BlockHashCache { - /// The block number corresponding to index 0 of the `hashes` array. - start_block: u64, /// A fixed-size array holding the block hashes. /// Since we only store the most recent 256 block hashes, this array has a length of 256. /// The reason we store block number alongside its hash is to handle the case where it wraps around, so we can verify the block number. @@ -21,11 +17,10 @@ impl Default for BlockHashCache { } impl BlockHashCache { - /// Creates a new empty BlockHashCache. + /// Creates a new empty BlockHashCache of length [BLOCK_HASH_HISTORY]. #[inline] pub const fn new() -> Self { Self { - start_block: 0, hashes: [(0, B256::ZERO); BLOCK_HASH_HISTORY_USIZE], } } @@ -35,11 +30,6 @@ impl BlockHashCache { pub const fn insert(&mut self, block_number: u64, block_hash: B256) { let index = (block_number % BLOCK_HASH_HISTORY) as usize; self.hashes[index] = (block_number, block_hash); - if block_number >= self.start_block + BLOCK_HASH_HISTORY { - // this only runs when block_number >= self.start_block + 256 - // Overflow impossible due to the check above - self.start_block = block_number - BLOCK_HASH_HISTORY_MINUS_ONE; - } } /// Retrieves the block hash for the given block number, if it exists in the cache. From 42fd1ba8d1a0e76d76d00125a805acad27864b6e Mon Sep 17 00:00:00 2001 From: malik672 Date: Thu, 15 Jan 2026 12:39:44 +0100 Subject: [PATCH 3/4] bump use Box --- crates/database/src/states/block_hash_cache.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/database/src/states/block_hash_cache.rs b/crates/database/src/states/block_hash_cache.rs index 431accf252..de9a4fd25f 100644 --- a/crates/database/src/states/block_hash_cache.rs +++ b/crates/database/src/states/block_hash_cache.rs @@ -7,7 +7,7 @@ pub struct BlockHashCache { /// A fixed-size array holding the block hashes. /// Since we only store the most recent 256 block hashes, this array has a length of 256. /// The reason we store block number alongside its hash is to handle the case where it wraps around, so we can verify the block number. - hashes: [(u64, B256); BLOCK_HASH_HISTORY_USIZE], + hashes: Box<[(u64, B256); BLOCK_HASH_HISTORY_USIZE]>, } impl Default for BlockHashCache { @@ -19,9 +19,9 @@ impl Default for BlockHashCache { impl BlockHashCache { /// Creates a new empty BlockHashCache of length [BLOCK_HASH_HISTORY]. #[inline] - pub const fn new() -> Self { + pub fn new() -> Self { Self { - hashes: [(0, B256::ZERO); BLOCK_HASH_HISTORY_USIZE], + hashes: Box::new([(0, B256::ZERO); BLOCK_HASH_HISTORY_USIZE]), } } From f6a37696a99c014291235473e6b9410cc05f6eee Mon Sep 17 00:00:00 2001 From: malik672 Date: Thu, 15 Jan 2026 13:05:50 +0100 Subject: [PATCH 4/4] bump use Box --- crates/database/src/states/block_hash_cache.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/database/src/states/block_hash_cache.rs b/crates/database/src/states/block_hash_cache.rs index de9a4fd25f..8d2247c9d0 100644 --- a/crates/database/src/states/block_hash_cache.rs +++ b/crates/database/src/states/block_hash_cache.rs @@ -1,4 +1,5 @@ use primitives::{alloy_primitives::B256, BLOCK_HASH_HISTORY}; +use std::boxed::Box; const BLOCK_HASH_HISTORY_USIZE: usize = BLOCK_HASH_HISTORY as usize; /// A fixed-size cache for the 256 most recent block hashes.