Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/database/src/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
47 changes: 47 additions & 0 deletions crates/database/src/states/block_hash_cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.
#[derive(Debug, Clone, PartialEq, Eq)]
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: Box<[(u64, B256); BLOCK_HASH_HISTORY_USIZE]>,
}

impl Default for BlockHashCache {
fn default() -> Self {
Self::new()
}
}

impl BlockHashCache {
/// Creates a new empty BlockHashCache of length [BLOCK_HASH_HISTORY].
#[inline]
pub fn new() -> Self {
Self {
hashes: Box::new([(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);
}

/// 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<B256> {
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
}
}
}
73 changes: 32 additions & 41 deletions crates/database/src/states/state.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<dyn Database<Error = E> + Send + 'a>;
Expand Down Expand Up @@ -68,7 +66,7 @@ pub struct State<DB> {
/// 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<u64, B256>,
pub block_hashes: BlockHashCache,
/// BAL state.
///
/// Can contain both the BAL for reads and BAL builder that is used to build BAL.
Expand Down Expand Up @@ -355,28 +353,21 @@ impl<DB: Database> Database for State<DB> {
}

fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
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)
}
}

Expand Down Expand Up @@ -495,8 +486,8 @@ impl<DB: DatabaseRef> DatabaseRef for State<DB> {
}

fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
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));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting artefact, can fix it later

}
// If not found, load it from database
self.database
Expand All @@ -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();
Expand All @@ -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.
///
Expand Down
11 changes: 6 additions & 5 deletions crates/database/src/states/state_builder.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -31,7 +32,7 @@ pub struct StateBuilder<DB> {
/// Default is false.
with_background_transition_merge: bool,
/// If we want to set different block hashes,
with_block_hashes: BTreeMap<u64, B256>,
with_block_hashes: BlockHashCache,
/// BAL state.
bal_state: BalState,
}
Expand Down Expand Up @@ -62,7 +63,7 @@ impl<DB: Database> StateBuilder<DB> {
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(),
}
}
Expand Down Expand Up @@ -158,7 +159,7 @@ impl<DB: Database> StateBuilder<DB> {
}

/// Sets the block hashes for the state.
pub fn with_block_hashes(self, block_hashes: BTreeMap<u64, B256>) -> Self {
pub fn with_block_hashes(self, block_hashes: BlockHashCache) -> Self {
Self {
with_block_hashes: block_hashes,
..self
Expand Down
Loading