From 7e43f4bf33f36fcabdd5b3b60010b637441f136a Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 30 Apr 2025 14:17:01 +0100 Subject: [PATCH 1/9] refactor logic to set separate locking --- crates/anvil/src/eth/backend/mem/mod.rs | 12 +++++++++++- crates/anvil/src/eth/backend/mem/storage.rs | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 2f295ed3311b9..dd7a004eb43e7 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -2265,7 +2265,17 @@ impl Backend { .await? .map(|block| (block.header.hash, block)) { - if let Some(state) = self.states.write().get(&block_hash) { + let read_guard = self.states.read(); + let mut state_db = read_guard.get_state(&block_hash); + + let mut write_guard = self.states.write(); + if state_db.is_none() { + state_db = write_guard.get_on_disk_state(&block_hash); + } else { + drop(write_guard); + } + + if let Some(state) = state_db { let block = BlockEnv { number: block_number, coinbase: block.header.beneficiary, diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index e024d97505467..e7b5fdf0e34e2 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -173,6 +173,23 @@ impl InMemoryBlockStates { } } + /// Returns the state for the given `hash` if present + pub fn get_state(&self, hash: &B256) -> Option<&StateDb> { + self.states.get(hash) + } + + /// Returns on-disk state for the given `hash` if present + pub fn get_on_disk_state(&mut self, hash: &B256) -> Option<&StateDb> { + if let Some(state) = self.on_disk_states.get_mut(hash) { + if let Some(cached) = self.disk_cache.read(*hash) { + state.init_from_state_snapshot(cached); + return Some(state); + } + } + + None + } + /// Returns the state for the given `hash` if present pub fn get(&mut self, hash: &B256) -> Option<&StateDb> { self.states.get(hash).or_else(|| { From 8164e265b02f0152f5e5df9cbac311641a441c1f Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 30 Apr 2025 16:17:41 +0100 Subject: [PATCH 2/9] update tests for state cache --- crates/anvil/src/eth/backend/mem/mod.rs | 15 ++++++++++++--- crates/anvil/src/eth/backend/mem/storage.rs | 13 ++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index dd7a004eb43e7..be8fbad649b2a 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -2919,9 +2919,18 @@ impl Backend { pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { // Get the database at the common block let common_state = { - let mut state = self.states.write(); - let state_db = state - .get(&common_block.header.hash_slow()) + let hash = &common_block.header.hash_slow(); + let read_guard = self.states.read(); + let mut state_db = read_guard.get_state(hash); + + let mut write_guard = self.states.write(); + if state_db.is_none() { + state_db = write_guard.get_on_disk_state(hash); + } else { + drop(write_guard); + } + + let state_db = state_db .ok_or(BlockchainError::DataUnavailable)?; let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; db_full.clone() diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index e7b5fdf0e34e2..2c3b33b9b7b10 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -688,7 +688,7 @@ mod tests { assert_eq!(storage.on_disk_states.len(), 1); assert!(storage.on_disk_states.contains_key(&one)); - let loaded = storage.get(&one).unwrap(); + let loaded = storage.get_on_disk_state(&one).unwrap(); let acc = loaded.basic_ref(addr).unwrap().unwrap(); assert_eq!(acc.balance, U256::from(1337u64)); @@ -713,13 +713,20 @@ mod tests { // wait for files to be flushed tokio::time::sleep(std::time::Duration::from_secs(1)).await; - assert_eq!(storage.on_disk_states.len(), num_states - storage.min_in_memory_limit); + let on_disk_states_len = num_states - storage.min_in_memory_limit; + assert_eq!(storage.on_disk_states.len(), on_disk_states_len); assert_eq!(storage.present.len(), storage.min_in_memory_limit); for idx in 0..num_states { let hash = B256::from(U256::from(idx)); let addr = Address::from_word(hash); - let loaded = storage.get(&hash).unwrap(); + + let loaded = if idx < on_disk_states_len { + storage.get_on_disk_state(&hash).unwrap() + } else { + storage.get_state(&hash).unwrap() + }; + let acc = loaded.basic_ref(addr).unwrap().unwrap(); let balance = (idx * 2) as u64; assert_eq!(acc.balance, U256::from(balance)); From 227195c226c655207aaf22e1d71fabd5f5a2e5b3 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Wed, 30 Apr 2025 17:18:48 +0100 Subject: [PATCH 3/9] fix formatting --- crates/anvil/src/eth/backend/mem/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index be8fbad649b2a..78731f2b9188f 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -2930,8 +2930,7 @@ impl Backend { drop(write_guard); } - let state_db = state_db - .ok_or(BlockchainError::DataUnavailable)?; + let state_db = state_db.ok_or(BlockchainError::DataUnavailable)?; let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; db_full.clone() }; From 784dbb40c6a8e1cb3664ea5e813135ec7462b1e2 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 1 May 2025 06:34:11 +0100 Subject: [PATCH 4/9] refactor rollback logic to fix deadlock --- crates/anvil/src/eth/backend/mem/mod.rs | 32 ++++++++++++--------- crates/anvil/src/eth/backend/mem/storage.rs | 8 ++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 78731f2b9188f..bb8a1a9b04d3f 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,7 +1,7 @@ //! In-memory blockchain backend. use self::state::trie_storage; -use super::executor::new_evm_with_inspector_ref; +use super::{db::StateDb, executor::new_evm_with_inspector_ref}; use crate::{ config::PruneStateHistoryConfig, eth::{ @@ -81,7 +81,7 @@ use anvil_core::eth::{ use anvil_rpc::error::RpcError; use chrono::Datelike; use eyre::{Context, Result}; -use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use flate2::{read::GzDecoder, write::{self, GzEncoder}, Compression}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, @@ -99,9 +99,9 @@ use foundry_evm::{ }; use futures::channel::mpsc::{unbounded, UnboundedSender}; use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; -use parking_lot::{Mutex, RwLock}; +use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard}; use revm::{ - db::WrapDatabaseRef, + db::{DbAccount, WrapDatabaseRef}, interpreter::Host, primitives::{BlobExcessGasAndPrice, HashMap, OptimismFields, ResultAndState}, DatabaseCommit, @@ -2920,19 +2920,17 @@ impl Backend { // Get the database at the common block let common_state = { let hash = &common_block.header.hash_slow(); - let read_guard = self.states.read(); - let mut state_db = read_guard.get_state(hash); + let read_guard = self.states.upgradable_read(); + if read_guard.has_state(hash) { + let db = read_guard.get_state(hash); - let mut write_guard = self.states.write(); - if state_db.is_none() { - state_db = write_guard.get_on_disk_state(hash); + return_state_or_throw_err(db).unwrap() } else { - drop(write_guard); - } + let write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let db = write_guard.get_state(hash); - let state_db = state_db.ok_or(BlockchainError::DataUnavailable)?; - let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; - db_full.clone() + return_state_or_throw_err(db).unwrap() + } }; { @@ -2967,6 +2965,12 @@ impl Backend { } } +fn return_state_or_throw_err(db: Option<&StateDb>) -> Result, BlockchainError>{ + let state_db = db.ok_or(BlockchainError::DataUnavailable)?; + let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + Ok(db_full.clone()) +} + /// Get max nonce from transaction pool by address fn get_pool_transactions_nonce( pool_transactions: &[Arc], diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 2c3b33b9b7b10..771d36e4097f6 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -173,6 +173,14 @@ impl InMemoryBlockStates { } } + pub fn has_state(&self, hash: &B256) -> bool { + self.states.contains_key(hash) + } + + pub fn has_on_disk_state(&self, hash: &B256) -> bool { + self.on_disk_states.contains_key(hash) + } + /// Returns the state for the given `hash` if present pub fn get_state(&self, hash: &B256) -> Option<&StateDb> { self.states.get(hash) From f62e4ee2069156cd503a7359dedc15f98ab31362 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 1 May 2025 07:11:25 +0100 Subject: [PATCH 5/9] fix deadlock in with_database_at --- crates/anvil/src/eth/backend/mem/mod.rs | 49 +++++++++++++++---------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index bb8a1a9b04d3f..9088524d05146 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -2265,28 +2265,21 @@ impl Backend { .await? .map(|block| (block.header.hash, block)) { - let read_guard = self.states.read(); - let mut state_db = read_guard.get_state(&block_hash); + let read_guard = self.states.upgradable_read(); - let mut write_guard = self.states.write(); - if state_db.is_none() { - state_db = write_guard.get_on_disk_state(&block_hash); + if read_guard.has_state(&block_hash) { + let state_db = read_guard.get_state(&block_hash); + + if let Some(state) = state_db { + return Ok(get_block_env(state, block_number, block, f)); + } } else { - drop(write_guard); - } + let mut write_guard = RwLockUpgradableReadGuard::upgrade(read_guard); + let state_db = write_guard.get_on_disk_state(&block_hash); - if let Some(state) = state_db { - let block = BlockEnv { - number: block_number, - coinbase: block.header.beneficiary, - timestamp: U256::from(block.header.timestamp), - difficulty: block.header.difficulty, - prevrandao: block.header.mix_hash, - basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), - gas_limit: U256::from(block.header.gas_limit), - ..Default::default() - }; - return Ok(f(Box::new(state), block)); + if let Some(state) = state_db { + return Ok(get_block_env(state, block_number, block, f)); + } } } @@ -2965,6 +2958,24 @@ impl Backend { } } +fn get_block_env(state: &StateDb, block_number: U256, block: AnyRpcBlock, f: F) -> T + where + F: FnOnce(Box, BlockEnv) -> T, +{ + let block = BlockEnv { + number: block_number, + coinbase: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + prevrandao: block.header.mix_hash, + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), + ..Default::default() + }; + + f(Box::new(state), block) +} + fn return_state_or_throw_err(db: Option<&StateDb>) -> Result, BlockchainError>{ let state_db = db.ok_or(BlockchainError::DataUnavailable)?; let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; From 7d6e6f203f0d3f56e1efdc826cdf2f76fbfa5a6d Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 1 May 2025 07:15:56 +0100 Subject: [PATCH 6/9] format --- crates/anvil/src/eth/backend/mem/mod.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 9088524d05146..7f64e0f900bb1 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -81,7 +81,7 @@ use anvil_core::eth::{ use anvil_rpc::error::RpcError; use chrono::Datelike; use eyre::{Context, Result}; -use flate2::{read::GzDecoder, write::{self, GzEncoder}, Compression}; +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, @@ -99,7 +99,7 @@ use foundry_evm::{ }; use futures::channel::mpsc::{unbounded, UnboundedSender}; use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; -use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard, RwLockWriteGuard}; +use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use revm::{ db::{DbAccount, WrapDatabaseRef}, interpreter::Host, @@ -2959,7 +2959,7 @@ impl Backend { } fn get_block_env(state: &StateDb, block_number: U256, block: AnyRpcBlock, f: F) -> T - where +where F: FnOnce(Box, BlockEnv) -> T, { let block = BlockEnv { @@ -2972,11 +2972,16 @@ fn get_block_env(state: &StateDb, block_number: U256, block: AnyRpcBlock, gas_limit: U256::from(block.header.gas_limit), ..Default::default() }; - + f(Box::new(state), block) } -fn return_state_or_throw_err(db: Option<&StateDb>) -> Result, BlockchainError>{ +fn return_state_or_throw_err( + db: Option<&StateDb>, +) -> Result< + HashMap, + BlockchainError, +> { let state_db = db.ok_or(BlockchainError::DataUnavailable)?; let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; Ok(db_full.clone()) From 1b0383feccbfc1854d71e09d0e9300fc633ff9bc Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 1 May 2025 07:23:24 +0100 Subject: [PATCH 7/9] add documentation --- crates/anvil/src/eth/backend/mem/storage.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 771d36e4097f6..f7ded8c8556aa 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -173,15 +173,17 @@ impl InMemoryBlockStates { } } + /// Checks if hash exists in in-memory state pub fn has_state(&self, hash: &B256) -> bool { self.states.contains_key(hash) } + /// Checks if hash exists in on-disk state pub fn has_on_disk_state(&self, hash: &B256) -> bool { self.on_disk_states.contains_key(hash) } - /// Returns the state for the given `hash` if present + /// Returns the in-memory state for the given `hash` if present pub fn get_state(&self, hash: &B256) -> Option<&StateDb> { self.states.get(hash) } From 1f119ad1a826db4d50c23e2da1c76ec7701d94a5 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 1 May 2025 08:10:55 +0100 Subject: [PATCH 8/9] remove redundant get method --- crates/anvil/src/eth/backend/mem/storage.rs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index f7ded8c8556aa..8a3111cc7fbe7 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -200,19 +200,6 @@ impl InMemoryBlockStates { None } - /// Returns the state for the given `hash` if present - pub fn get(&mut self, hash: &B256) -> Option<&StateDb> { - self.states.get(hash).or_else(|| { - if let Some(state) = self.on_disk_states.get_mut(hash) { - if let Some(cached) = self.disk_cache.read(*hash) { - state.init_from_state_snapshot(cached); - return Some(state); - } - } - None - }) - } - /// Sets the maximum number of stats we keep in memory pub fn set_cache_limit(&mut self, limit: usize) { self.in_memory_limit = limit; From 469408724b679c34a974fc4f7856fc44d938c819 Mon Sep 17 00:00:00 2001 From: Nnamdi Aninye Date: Thu, 1 May 2025 08:21:40 +0100 Subject: [PATCH 9/9] remove redundant has_on_disk_state method --- crates/anvil/src/eth/backend/mem/storage.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 8a3111cc7fbe7..a8e6117c57be1 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -178,11 +178,6 @@ impl InMemoryBlockStates { self.states.contains_key(hash) } - /// Checks if hash exists in on-disk state - pub fn has_on_disk_state(&self, hash: &B256) -> bool { - self.on_disk_states.contains_key(hash) - } - /// Returns the in-memory state for the given `hash` if present pub fn get_state(&self, hash: &B256) -> Option<&StateDb> { self.states.get(hash)