From 1c8a44568b1fc61cace892627dd93ad31f476885 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Fri, 2 Feb 2024 18:05:42 +0100 Subject: [PATCH 1/8] fix: cache block contexts --- .gitignore | 2 + Cargo.lock | 10 ++ crates/edr_provider/Cargo.toml | 1 + crates/edr_provider/src/data.rs | 99 +++++++++++++------ crates/edr_provider/src/requests/debug.rs | 4 +- .../src/requests/eth/blockchain.rs | 2 +- crates/edr_provider/src/requests/eth/call.rs | 2 +- crates/edr_provider/src/requests/eth/state.rs | 6 +- 8 files changed, 87 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index a94644cb64..806f732594 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,5 @@ Brewfile.lock.json # Performance profiling application .clinic + +benchmark-scenarios/ diff --git a/Cargo.lock b/Cargo.lock index b1a364e4ee..e617f77166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1112,6 +1112,7 @@ dependencies = [ "k256", "lazy_static", "log", + "lru", "parking_lot 0.12.1", "paste", "rand", @@ -1941,6 +1942,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +dependencies = [ + "hashbrown 0.14.1", +] + [[package]] name = "matchers" version = "0.1.0" diff --git a/crates/edr_provider/Cargo.toml b/crates/edr_provider/Cargo.toml index ec32ad24e0..8aa30eb12f 100644 --- a/crates/edr_provider/Cargo.toml +++ b/crates/edr_provider/Cargo.toml @@ -23,6 +23,7 @@ sha3 = { version = "0.10.6", default-features = false } thiserror = { version = "1.0.37", default-features = false } tokio = { version = "1.21.2", default-features = false, features = ["macros"] } tracing = { version = "0.1.37", features = ["attributes", "std"] } +lru = "0.12.2" [dev-dependencies] anyhow = "1.0.75" diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index f1630918e2..911a5e75a7 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -8,6 +8,7 @@ use std::{ cmp::Ordering, collections::BTreeMap, fmt::Debug, + num::NonZeroUsize, sync::Arc, time::{Duration, Instant, SystemTime, UNIX_EPOCH}, }; @@ -50,6 +51,7 @@ use gas::gas_used_ratio; use indexmap::IndexMap; use itertools::izip; use lazy_static::lazy_static; +use lru::LruCache; use tokio::runtime; use self::{ @@ -73,6 +75,7 @@ use crate::{ }; const DEFAULT_INITIAL_BASE_FEE_PER_GAS: u64 = 1_000_000_000; +const MAX_CACHED_BLOCK_CONTEXTS: usize = 1000; /// The result of executing an `eth_call`. #[derive(Clone)] @@ -146,6 +149,7 @@ pub struct ProviderData { logger: Box>, impersonated_accounts: HashSet
, subscriber_callback: Box, + block_context_cache: LruCache, } impl ProviderData { @@ -212,6 +216,9 @@ impl ProviderData { logger, impersonated_accounts: HashSet::new(), subscriber_callback, + block_context_cache: LruCache::new( + NonZeroUsize::new(MAX_CACHED_BLOCK_CONTEXTS).expect("constant is non-zero"), + ), }) } @@ -251,7 +258,7 @@ impl ProviderData { } pub fn balance( - &self, + &mut self, address: Address, block_spec: Option<&BlockSpec>, ) -> Result> { @@ -476,7 +483,7 @@ impl ProviderData { #[tracing::instrument(level = "trace", skip(self))] pub fn debug_trace_transaction( - &self, + &mut self, transaction_hash: &B256, trace_config: DebugTraceConfig, ) -> Result> { @@ -531,7 +538,7 @@ impl ProviderData { } pub fn debug_trace_call( - &self, + &mut self, transaction: ExecutableTransaction, block_spec: Option<&BlockSpec>, trace_config: DebugTraceConfig, @@ -546,7 +553,7 @@ impl ProviderData { let result = run_call(RunCallArgs { blockchain, header: block.header(), - state: &state, + state, state_overrides: &StateOverrides::default(), cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), @@ -559,7 +566,7 @@ impl ProviderData { /// Estimate the gas cost of a transaction. Matches Hardhat behavior. pub fn estimate_gas( - &self, + &mut self, transaction: ExecutableTransaction, block_spec: &BlockSpec, ) -> Result> { @@ -584,7 +591,7 @@ impl ProviderData { let result = call::run_call(RunCallArgs { blockchain, header, - state: &state, + state, state_overrides: &state_overrides, cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), @@ -620,7 +627,7 @@ impl ProviderData { let result = gas::check_gas_limit(CheckGasLimitArgs { blockchain, header, - state: &state, + state, state_overrides: &state_overrides, cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), @@ -639,7 +646,7 @@ impl ProviderData { let estimation = gas::binary_search_estimation(BinarySearchEstimationArgs { blockchain, header, - state: &state, + state, state_overrides: &state_overrides, cfg_env: cfg_env.clone(), tx_env: tx_env.clone(), @@ -820,7 +827,7 @@ impl ProviderData { } pub fn get_code( - &self, + &mut self, address: Address, block_spec: Option<&BlockSpec>, ) -> Result> { @@ -865,7 +872,7 @@ impl ProviderData { } pub fn get_storage_at( - &self, + &mut self, address: Address, index: U256, block_spec: Option<&BlockSpec>, @@ -877,7 +884,7 @@ impl ProviderData { } pub fn get_transaction_count( - &self, + &mut self, address: Address, block_spec: Option<&BlockSpec>, ) -> Result> { @@ -1157,7 +1164,7 @@ impl ProviderData { } pub fn nonce( - &self, + &mut self, address: &Address, block_spec: Option<&BlockSpec>, state_overrides: &StateOverrides, @@ -1251,7 +1258,7 @@ impl ProviderData { } pub fn run_call( - &self, + &mut self, transaction: ExecutableTransaction, block_spec: Option<&BlockSpec>, state_overrides: &StateOverrides, @@ -1266,7 +1273,7 @@ impl ProviderData { let execution_result = call::run_call(RunCallArgs { blockchain, header: block.header(), - state: &state, + state, state_overrides, cfg_env, tx_env, @@ -1752,16 +1759,32 @@ impl ProviderData { } fn execute_in_block_context( - &self, + &mut self, block_spec: Option<&BlockSpec>, function: impl FnOnce( &dyn SyncBlockchain, - Arc>, - Box>, + &Arc>, + &Box>, ) -> T, ) -> Result> { - let (context, blockchain) = if let Some(context) = self.context_by_block_spec(block_spec)? { - (context, None) + let maybe_block_number = self + .block_number_by_block_spec(block_spec.unwrap_or(&BlockSpec::Tag(BlockTag::Latest)))?; + if let Some(block_number) = maybe_block_number { + // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but + // we would need &self in the callback to construct the context. + if !self.block_context_cache.contains(&block_number) { + let context = self + .context_by_block_spec(block_spec)? + .expect("If block spec is pending, block number is None"); + self.block_context_cache.push(block_number, context); + } + + let context = self.block_context_cache.get(&block_number).expect( + "We put the context into cache if it wasn't there and we have exclusive \ + access to the cache due to &mut self so it can't have disappeared", + ); + + Ok(function(&*self.blockchain, &context.block, &context.state)) } else { let result = self.mine_pending_block()?; @@ -1777,17 +1800,8 @@ impl ProviderData { state: result.state, }; - (context, Some(blockchain)) - }; - - let blockchain = blockchain - .as_ref() - .map_or(&*self.blockchain, |blockchain| blockchain); - - // Execute function in the requested block context. - let result = function(blockchain, context.block, context.state); - - Ok(result) + Ok(function(&blockchain, &context.block, &context.state)) + } } /// Mine a block at a specific timestamp @@ -2385,7 +2399,7 @@ mod tests { #[test] fn test_local_account_balance() -> anyhow::Result<()> { - let fixture = ProviderTestFixture::new()?; + let mut fixture = ProviderTestFixture::new()?; let account = *fixture .provider_data @@ -2406,7 +2420,7 @@ mod tests { #[test] fn test_local_account_balance_forked() -> anyhow::Result<()> { - let fixture = ProviderTestFixture::new_forked()?; + let mut fixture = ProviderTestFixture::new_forked()?; let account = *fixture .provider_data @@ -2553,6 +2567,27 @@ mod tests { Ok(()) } + // Make sure executing a transaction in a pending block context doesn't panic. + #[test] + fn execute_in_block_context_pending() -> anyhow::Result<()> { + let mut fixture = ProviderTestFixture::new()?; + + let block_spec = Some(BlockSpec::Tag(BlockTag::Pending)); + + let mut value = 0; + let _ = + fixture + .provider_data + .execute_in_block_context(block_spec.as_ref(), |_, _, _| { + value += 1; + Ok::<(), ProviderError>(()) + })?; + + assert_eq!(value, 1); + + Ok(()) + } + #[test] fn chain_id() -> anyhow::Result<()> { let fixture = ProviderTestFixture::new()?; diff --git a/crates/edr_provider/src/requests/debug.rs b/crates/edr_provider/src/requests/debug.rs index 56a1f526c7..89529e8bd7 100644 --- a/crates/edr_provider/src/requests/debug.rs +++ b/crates/edr_provider/src/requests/debug.rs @@ -14,7 +14,7 @@ use crate::{ }; pub fn handle_debug_trace_transaction( - data: &ProviderData, + data: &mut ProviderData, transaction_hash: B256, config: Option, ) -> Result> { @@ -31,7 +31,7 @@ pub fn handle_debug_trace_transaction( } pub fn handle_debug_trace_call( - data: &ProviderData, + data: &mut ProviderData, call_request: CallRequest, block_spec: Option, config: Option, diff --git a/crates/edr_provider/src/requests/eth/blockchain.rs b/crates/edr_provider/src/requests/eth/blockchain.rs index 560fd13901..a4604e2ef3 100644 --- a/crates/edr_provider/src/requests/eth/blockchain.rs +++ b/crates/edr_provider/src/requests/eth/blockchain.rs @@ -19,7 +19,7 @@ pub fn handle_chain_id_request( } pub fn handle_get_transaction_count_request( - data: &ProviderData, + data: &mut ProviderData, address: Address, block_spec: Option, ) -> Result> { diff --git a/crates/edr_provider/src/requests/eth/call.rs b/crates/edr_provider/src/requests/eth/call.rs index f19533dd7c..95b6973e96 100644 --- a/crates/edr_provider/src/requests/eth/call.rs +++ b/crates/edr_provider/src/requests/eth/call.rs @@ -48,7 +48,7 @@ pub fn handle_call_request( } pub(crate) fn resolve_call_request( - data: &ProviderData, + data: &mut ProviderData, request: CallRequest, block_spec: Option<&BlockSpec>, state_overrides: &StateOverrides, diff --git a/crates/edr_provider/src/requests/eth/state.rs b/crates/edr_provider/src/requests/eth/state.rs index e301d5bcbb..2278adcd4d 100644 --- a/crates/edr_provider/src/requests/eth/state.rs +++ b/crates/edr_provider/src/requests/eth/state.rs @@ -7,7 +7,7 @@ use crate::{ }; pub fn handle_get_balance_request( - data: &ProviderData, + data: &mut ProviderData, address: Address, block_spec: Option, ) -> Result> { @@ -19,7 +19,7 @@ pub fn handle_get_balance_request( } pub fn handle_get_code_request( - data: &ProviderData, + data: &mut ProviderData, address: Address, block_spec: Option, ) -> Result> { @@ -31,7 +31,7 @@ pub fn handle_get_code_request( } pub fn handle_get_storage_at_request( - data: &ProviderData, + data: &mut ProviderData, address: Address, index: U256, block_spec: Option, From 28064d839ee82c482b4796a2e837e32a65ab685f Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 8 Feb 2024 00:17:03 -0600 Subject: [PATCH 2/8] fix: add cache to snapshot and re-cache irregular states (#4819) --- crates/edr_evm/src/upcast.rs | 5 - crates/edr_provider/src/data.rs | 158 ++++++++++++++-------------- crates/edr_provider/src/snapshot.rs | 2 + 3 files changed, 81 insertions(+), 84 deletions(-) delete mode 100644 crates/edr_evm/src/upcast.rs diff --git a/crates/edr_evm/src/upcast.rs b/crates/edr_evm/src/upcast.rs deleted file mode 100644 index d24fa6b554..0000000000 --- a/crates/edr_evm/src/upcast.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// Trait for upcasting a super trait object to a sub trait object. -pub trait Upcast { - /// Upcasts the trait object. - fn upcast(&self) -> &T; -} diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 911a5e75a7..abb7652e20 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -4,6 +4,7 @@ mod gas; mod inspector; use std::{ + borrow::Cow, cmp, cmp::Ordering, collections::BTreeMap, @@ -113,11 +114,6 @@ pub enum CreationError { RpcClient(#[from] RpcClientError), } -struct BlockContext { - pub block: Arc>, - pub state: Box>, -} - pub struct ProviderData { runtime_handle: runtime::Handle, initial_config: ProviderConfig, @@ -149,7 +145,7 @@ pub struct ProviderData { logger: Box>, impersonated_accounts: HashSet
, subscriber_callback: Box, - block_context_cache: LruCache, + block_state_cache: LruCache>>, } impl ProviderData { @@ -175,6 +171,12 @@ impl ProviderData { next_block_base_fee_per_gas, } = create_blockchain_and_state(runtime_handle.clone(), &config, genesis_accounts)?; + let mut block_state_cache = LruCache::new( + NonZeroUsize::new(MAX_CACHED_BLOCK_CONTEXTS).expect("constant is non-zero"), + ); + + block_state_cache.push(blockchain.last_block_number(), state.clone()); + let allow_blocks_with_same_timestamp = config.allow_blocks_with_same_timestamp; let allow_unlimited_contract_size = config.allow_unlimited_contract_size; let beneficiary = config.coinbase; @@ -216,9 +218,7 @@ impl ProviderData { logger, impersonated_accounts: HashSet::new(), subscriber_callback, - block_context_cache: LruCache::new( - NonZeroUsize::new(MAX_CACHED_BLOCK_CONTEXTS).expect("constant is non-zero"), - ), + block_state_cache, }) } @@ -948,6 +948,7 @@ impl ProviderData { let snapshot = Snapshot { block_number: self.blockchain.last_block_number(), + block_state_cache: self.block_state_cache.clone(), block_time_offset_seconds: self.block_time_offset_seconds, coinbase: self.beneficiary, irregular_state: self.irregular_state.clone(), @@ -1034,16 +1035,16 @@ impl ProviderData { // Remove outdated filters self.filters.retain(|_, filter| !filter.has_expired()); + self.block_state_cache + .push(block.header().number, result.state.clone()); self.state = result.state; - let result = DebugMineBlockResult { + Ok(DebugMineBlockResult { block: block_and_total_difficulty.block, transaction_results: result.transaction_results, transaction_traces: result.transaction_traces, console_log_inputs: result.console_log_inputs, - }; - - Ok(result) + }) } /// Mines `number_of_blocks` blocks with the provided `interval` between @@ -1222,6 +1223,7 @@ impl ProviderData { if let Some(snapshot) = removed_snapshots.remove(&snapshot_id) { let Snapshot { block_number, + block_state_cache, block_time_offset_seconds, coinbase, irregular_state, @@ -1233,6 +1235,8 @@ impl ProviderData { time, } = snapshot; + self.block_state_cache = block_state_cache; + // We compute a new offset such that: // now + new_offset == snapshot_date + old_offset let duration_since_snapshot = Instant::now().duration_since(time); @@ -1399,7 +1403,8 @@ impl ProviderData { address: Address, balance: U256, ) -> Result<(), ProviderError> { - let account_info = self.state.modify_account( + let mut state = self.state.clone(); + let account_info = state.modify_account( address, AccountModifierFn::new(Box::new(move |account_balance, _, _| { *account_balance = balance; @@ -1414,16 +1419,19 @@ impl ProviderData { }, )?; - let block_number = self.blockchain.last_block_number(); - let state_root = self.state.state_root()?; + let state_root = state.state_root()?; + self.mem_pool.update(&state)?; + + let block_number = self.blockchain.last_block_number(); self.irregular_state .state_override_at_block_number(block_number) .or_insert_with(|| StateOverride::with_state_root(state_root)) .diff .apply_account_change(address, account_info.clone()); - self.mem_pool.update(&self.state)?; + self.block_state_cache.push(block_number, state.clone()); + self.state = state; Ok(()) } @@ -1447,7 +1455,8 @@ impl ProviderData { let default_code = code.clone(); let irregular_code = code.clone(); - let mut account_info = self.state.modify_account( + let mut state = self.state.clone(); + let mut account_info = state.modify_account( address, AccountModifierFn::new(Box::new(move |_, _, account_code| { *account_code = Some(code.clone()); @@ -1466,15 +1475,18 @@ impl ProviderData { // irregular state. account_info.code = Some(irregular_code.clone()); - let block_number = self.blockchain.last_block_number(); - let state_root = self.state.state_root()?; + let state_root = state.state_root()?; + let block_number = self.blockchain.last_block_number(); self.irregular_state .state_override_at_block_number(block_number) .or_insert_with(|| StateOverride::with_state_root(state_root)) .diff .apply_account_change(address, account_info.clone()); + self.block_state_cache.push(block_number, state.clone()); + self.state = state; + Ok(()) } @@ -1557,7 +1569,8 @@ impl ProviderData { }); } - let account_info = self.state.modify_account( + let mut state = self.state.clone(); + let account_info = state.modify_account( address, AccountModifierFn::new(Box::new(move |_, account_nonce, _| *account_nonce = nonce)), &|| { @@ -1570,16 +1583,19 @@ impl ProviderData { }, )?; - let block_number = self.blockchain.last_block_number(); - let state_root = self.state.state_root()?; + let state_root = state.state_root()?; + self.mem_pool.update(&state)?; + + let block_number = self.blockchain.last_block_number(); self.irregular_state .state_override_at_block_number(block_number) .or_insert_with(|| StateOverride::with_state_root(state_root)) .diff .apply_account_change(address, account_info.clone()); - self.mem_pool.update(&self.state)?; + self.block_state_cache.push(block_number, state.clone()); + self.state = state; Ok(()) } @@ -1590,31 +1606,35 @@ impl ProviderData { index: U256, value: U256, ) -> Result<(), ProviderError> { - self.state.set_account_storage_slot(address, index, value)?; + let mut state = self.state.clone(); + state.set_account_storage_slot(address, index, value)?; - let old_value = self.state.set_account_storage_slot(address, index, value)?; + let old_value = state.set_account_storage_slot(address, index, value)?; let slot = StorageSlot::new_changed(old_value, value); - let account_info = self.state.basic(address).and_then(|mut account_info| { + let account_info = state.basic(address).and_then(|mut account_info| { // Retrieve the code if it's not empty. This is needed for the irregular state. if let Some(account_info) = &mut account_info { if account_info.code_hash != KECCAK_EMPTY { - account_info.code = Some(self.state.code_by_hash(account_info.code_hash)?); + account_info.code = Some(state.code_by_hash(account_info.code_hash)?); } } Ok(account_info) })?; - let block_number = self.blockchain.last_block_number(); - let state_root = self.state.state_root()?; + let state_root = state.state_root()?; + let block_number = self.blockchain.last_block_number(); self.irregular_state .state_override_at_block_number(block_number) .or_insert_with(|| StateOverride::with_state_root(state_root)) .diff .apply_storage_change(address, index, slot, account_info); + self.block_state_cache.push(block_number, state.clone()); + self.state = state; + Ok(()) } @@ -1767,25 +1787,37 @@ impl ProviderData { &Box>, ) -> T, ) -> Result> { - let maybe_block_number = self - .block_number_by_block_spec(block_spec.unwrap_or(&BlockSpec::Tag(BlockTag::Latest)))?; - if let Some(block_number) = maybe_block_number { - // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but - // we would need &self in the callback to construct the context. - if !self.block_context_cache.contains(&block_number) { - let context = self - .context_by_block_spec(block_spec)? - .expect("If block spec is pending, block number is None"); - self.block_context_cache.push(block_number, context); - } + let block = if let Some(block_spec) = block_spec { + self.block_by_block_spec(block_spec)? + } else { + Some(self.blockchain.last_block()?) + }; - let context = self.block_context_cache.get(&block_number).expect( - "We put the context into cache if it wasn't there and we have exclusive \ - access to the cache due to &mut self so it can't have disappeared", - ); + if let Some(block) = block { + let block_header = block.header(); + let block_number = block_header.number; + + let contextual_state = if self.block_state_cache.contains(&block_number) { + // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but + // we would need &self in the callback to reference the blockchain. + Cow::Borrowed( + self.block_state_cache + .get(&block_number) + .expect("We checked that the state is in the cache"), + ) + } else { + let state = self + .blockchain + .state_at_block_number(block_number, self.irregular_state.state_overrides())?; - Ok(function(&*self.blockchain, &context.block, &context.state)) + self.block_state_cache.push(block_number, state.clone()); + + Cow::Owned(state) + }; + + Ok(function(&*self.blockchain, &block, &contextual_state)) } else { + // Block spec is pending let result = self.mine_pending_block()?; let blockchain = @@ -1795,12 +1827,7 @@ impl ProviderData { .last_block() .expect("The pending block is the last block"); - let context = BlockContext { - block, - state: result.state, - }; - - Ok(function(&blockchain, &context.block, &context.state)) + Ok(function(&blockchain, &block, &result.state)) } } @@ -1936,33 +1963,6 @@ impl ProviderData { } } - fn context_by_block_spec( - &self, - block_spec: Option<&BlockSpec>, - ) -> Result, ProviderError> { - let block = if let Some(block_spec) = block_spec { - if let Some(block) = self.block_by_block_spec(block_spec)? { - block - } else { - // Block spec is pending - return Ok(None); - } - } else { - self.blockchain.last_block()? - }; - - let block_header = block.header(); - - let contextual_state = self - .blockchain - .state_at_block_number(block_header.number, self.irregular_state.state_overrides())?; - - Ok(Some(BlockContext { - block, - state: contextual_state, - })) - } - fn validate_auto_mine_transaction( &self, transaction: &ExecutableTransaction, diff --git a/crates/edr_provider/src/snapshot.rs b/crates/edr_provider/src/snapshot.rs index 18311fa10a..9bd3497263 100644 --- a/crates/edr_provider/src/snapshot.rs +++ b/crates/edr_provider/src/snapshot.rs @@ -5,9 +5,11 @@ use edr_evm::{ state::{IrregularState, StateError, SyncState}, MemPool, RandomHashGenerator, }; +use lru::LruCache; pub struct Snapshot { pub block_number: u64, + pub block_state_cache: LruCache>>, pub block_time_offset_seconds: i64, pub coinbase: Address, pub irregular_state: IrregularState, From b0441a4c0d0ccbf991008ab3b234be2a9b192381 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Thu, 8 Feb 2024 14:23:23 +0100 Subject: [PATCH 3/8] Don't store state in snapshots --- crates/edr_provider/src/data.rs | 180 +++++++++++------- .../edr_provider/src/requests/eth/blocks.rs | 6 +- crates/edr_provider/src/requests/eth/gas.rs | 2 +- .../src/requests/eth/transactions.rs | 4 +- crates/edr_provider/src/snapshot.rs | 15 +- 5 files changed, 124 insertions(+), 83 deletions(-) diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index abb7652e20..3eab2871f5 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -4,7 +4,6 @@ mod gas; mod inspector; use std::{ - borrow::Cow, cmp, cmp::Ordering, collections::BTreeMap, @@ -76,7 +75,7 @@ use crate::{ }; const DEFAULT_INITIAL_BASE_FEE_PER_GAS: u64 = 1_000_000_000; -const MAX_CACHED_BLOCK_CONTEXTS: usize = 1000; +const MAX_CACHED_STATES: usize = 2048; /// The result of executing an `eth_call`. #[derive(Clone)] @@ -118,7 +117,6 @@ pub struct ProviderData { runtime_handle: runtime::Handle, initial_config: ProviderConfig, blockchain: Box>, - state: Box>, pub irregular_state: IrregularState, mem_pool: MemPool, beneficiary: Address, @@ -145,7 +143,11 @@ pub struct ProviderData { logger: Box>, impersonated_accounts: HashSet
, subscriber_callback: Box, - block_state_cache: LruCache>>, + // We need the Arc to let us avoid returning references to the cache entries which need &mut + // self to get. + block_state_cache: LruCache>>>, + current_state_id: StateId, + block_number_to_state_id: BTreeMap, } impl ProviderData { @@ -171,11 +173,13 @@ impl ProviderData { next_block_base_fee_per_gas, } = create_blockchain_and_state(runtime_handle.clone(), &config, genesis_accounts)?; - let mut block_state_cache = LruCache::new( - NonZeroUsize::new(MAX_CACHED_BLOCK_CONTEXTS).expect("constant is non-zero"), - ); + let mut block_state_cache = + LruCache::new(NonZeroUsize::new(MAX_CACHED_STATES).expect("constant is non-zero")); + let mut block_number_to_state_id = BTreeMap::new(); - block_state_cache.push(blockchain.last_block_number(), state.clone()); + let current_state_id = StateId::default(); + block_state_cache.push(current_state_id, Arc::new(state)); + block_number_to_state_id.insert(blockchain.last_block_number(), current_state_id); let allow_blocks_with_same_timestamp = config.allow_blocks_with_same_timestamp; let allow_unlimited_contract_size = config.allow_unlimited_contract_size; @@ -193,7 +197,6 @@ impl ProviderData { runtime_handle, initial_config: config, blockchain, - state, irregular_state, mem_pool: MemPool::new(block_gas_limit), beneficiary, @@ -219,6 +222,8 @@ impl ProviderData { impersonated_accounts: HashSet::new(), subscriber_callback, block_state_cache, + current_state_id, + block_number_to_state_id, }) } @@ -240,8 +245,12 @@ impl ProviderData { /// Retrieves the last pending nonce of the account corresponding to the /// provided address, if it exists. - pub fn account_next_nonce(&self, address: &Address) -> Result { - mempool::account_next_nonce(&self.mem_pool, &self.state, address) + pub fn account_next_nonce( + &mut self, + address: &Address, + ) -> Result> { + let state = self.current_state()?; + mempool::account_next_nonce(&self.mem_pool, &*state, address).map_err(Into::into) } pub fn accounts(&self) -> impl Iterator { @@ -661,7 +670,7 @@ impl ProviderData { // Matches Hardhat implementation pub fn fee_history( - &self, + &mut self, block_count: u64, newest_block_spec: &BlockSpec, percentiles: Option>, @@ -948,7 +957,7 @@ impl ProviderData { let snapshot = Snapshot { block_number: self.blockchain.last_block_number(), - block_state_cache: self.block_state_cache.clone(), + block_number_to_state_id: self.block_number_to_state_id.clone(), block_time_offset_seconds: self.block_time_offset_seconds, coinbase: self.beneficiary, irregular_state: self.irregular_state.clone(), @@ -956,7 +965,6 @@ impl ProviderData { next_block_base_fee_per_gas: self.next_block_base_fee_per_gas, next_block_timestamp: self.next_block_timestamp, prev_randao_generator: self.prev_randao_generator.clone(), - state: self.state.clone(), time: Instant::now(), }; self.snapshots.insert(id, snapshot); @@ -1035,9 +1043,7 @@ impl ProviderData { // Remove outdated filters self.filters.retain(|_, filter| !filter.has_expired()); - self.block_state_cache - .push(block.header().number, result.state.clone()); - self.state = result.state; + self.add_state_to_cache(result.state, block.header().number); Ok(DebugMineBlockResult { block: block_and_total_difficulty.block, @@ -1177,7 +1183,6 @@ impl ProviderData { || { if matches!(block_spec, Some(BlockSpec::Tag(BlockTag::Pending))) { self.account_next_nonce(address) - .map_err(ProviderError::State) } else { self.execute_in_block_context( block_spec, @@ -1223,7 +1228,7 @@ impl ProviderData { if let Some(snapshot) = removed_snapshots.remove(&snapshot_id) { let Snapshot { block_number, - block_state_cache, + block_number_to_state_id, block_time_offset_seconds, coinbase, irregular_state, @@ -1231,11 +1236,10 @@ impl ProviderData { next_block_base_fee_per_gas, next_block_timestamp, prev_randao_generator, - state, time, } = snapshot; - self.block_state_cache = block_state_cache; + self.block_number_to_state_id = block_number_to_state_id; // We compute a new offset such that: // now + new_offset == snapshot_date + old_offset @@ -1253,7 +1257,6 @@ impl ProviderData { self.next_block_base_fee_per_gas = next_block_base_fee_per_gas; self.next_block_timestamp = next_block_timestamp; self.prev_randao_generator = prev_randao_generator; - self.state = state; true } else { @@ -1403,8 +1406,8 @@ impl ProviderData { address: Address, balance: U256, ) -> Result<(), ProviderError> { - let mut state = self.state.clone(); - let account_info = state.modify_account( + let mut modified_state = (*self.current_state()?).clone(); + let account_info = modified_state.modify_account( address, AccountModifierFn::new(Box::new(move |account_balance, _, _| { *account_balance = balance; @@ -1419,9 +1422,9 @@ impl ProviderData { }, )?; - let state_root = state.state_root()?; + let state_root = modified_state.state_root()?; - self.mem_pool.update(&state)?; + self.mem_pool.update(&modified_state)?; let block_number = self.blockchain.last_block_number(); self.irregular_state @@ -1430,8 +1433,7 @@ impl ProviderData { .diff .apply_account_change(address, account_info.clone()); - self.block_state_cache.push(block_number, state.clone()); - self.state = state; + self.add_state_to_cache(modified_state, block_number); Ok(()) } @@ -1441,8 +1443,9 @@ impl ProviderData { &mut self, gas_limit: u64, ) -> Result<(), ProviderError> { + let state = self.current_state()?; self.mem_pool - .set_block_gas_limit(&self.state, gas_limit) + .set_block_gas_limit(&*state, gas_limit) .map_err(ProviderError::State) } @@ -1455,8 +1458,9 @@ impl ProviderData { let default_code = code.clone(); let irregular_code = code.clone(); - let mut state = self.state.clone(); - let mut account_info = state.modify_account( + // We clone to automatically revert in case of subsequent errors. + let mut modified_state = (*self.current_state()?).clone(); + let mut account_info = modified_state.modify_account( address, AccountModifierFn::new(Box::new(move |_, _, account_code| { *account_code = Some(code.clone()); @@ -1475,7 +1479,7 @@ impl ProviderData { // irregular state. account_info.code = Some(irregular_code.clone()); - let state_root = state.state_root()?; + let state_root = modified_state.state_root()?; let block_number = self.blockchain.last_block_number(); self.irregular_state @@ -1484,8 +1488,7 @@ impl ProviderData { .diff .apply_account_change(address, account_info.clone()); - self.block_state_cache.push(block_number, state.clone()); - self.state = state; + self.add_state_to_cache(modified_state, block_number); Ok(()) } @@ -1558,7 +1561,7 @@ impl ProviderData { } let previous_nonce = self - .state + .current_state()? .basic(address)? .map_or(0, |account| account.nonce); @@ -1569,8 +1572,9 @@ impl ProviderData { }); } - let mut state = self.state.clone(); - let account_info = state.modify_account( + // We clone to automatically revert in case of subsequent errors. + let mut modified_state = (*self.current_state()?).clone(); + let account_info = modified_state.modify_account( address, AccountModifierFn::new(Box::new(move |_, account_nonce, _| *account_nonce = nonce)), &|| { @@ -1583,19 +1587,18 @@ impl ProviderData { }, )?; - let state_root = state.state_root()?; + let state_root = modified_state.state_root()?; - self.mem_pool.update(&state)?; + self.mem_pool.update(&modified_state)?; - let block_number = self.blockchain.last_block_number(); + let block_number = self.last_block_number(); self.irregular_state .state_override_at_block_number(block_number) .or_insert_with(|| StateOverride::with_state_root(state_root)) .diff .apply_account_change(address, account_info.clone()); - self.block_state_cache.push(block_number, state.clone()); - self.state = state; + self.add_state_to_cache(modified_state, block_number); Ok(()) } @@ -1606,7 +1609,8 @@ impl ProviderData { index: U256, value: U256, ) -> Result<(), ProviderError> { - let mut state = self.state.clone(); + // We clone to automatically revert in case of subsequent errors. + let mut state = (*self.current_state()?).clone(); state.set_account_storage_slot(address, index, value)?; let old_value = state.set_account_storage_slot(address, index, value)?; @@ -1632,8 +1636,7 @@ impl ProviderData { .diff .apply_storage_change(address, index, slot, account_info); - self.block_state_cache.push(block_number, state.clone()); - self.state = state; + self.add_state_to_cache(state, block_number); Ok(()) } @@ -1731,8 +1734,9 @@ impl ProviderData { ) -> Result> { let transaction_hash = *transaction.hash(); + let state = self.current_state()?; // Handles validation - self.mem_pool.add_transaction(&self.state, transaction)?; + self.mem_pool.add_transaction(&*state, transaction)?; for (filter_id, filter) in self.filters.iter_mut() { if let FilterData::NewPendingTransactions(events) = &mut filter.data { @@ -1797,23 +1801,7 @@ impl ProviderData { let block_header = block.header(); let block_number = block_header.number; - let contextual_state = if self.block_state_cache.contains(&block_number) { - // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but - // we would need &self in the callback to reference the blockchain. - Cow::Borrowed( - self.block_state_cache - .get(&block_number) - .expect("We checked that the state is in the cache"), - ) - } else { - let state = self - .blockchain - .state_at_block_number(block_number, self.irregular_state.state_overrides())?; - - self.block_state_cache.push(block_number, state.clone()); - - Cow::Owned(state) - }; + let contextual_state = self.get_or_compute_state(block_number)?; Ok(function(&*self.blockchain, &block, &contextual_state)) } else { @@ -1833,7 +1821,7 @@ impl ProviderData { /// Mine a block at a specific timestamp fn mine_block( - &self, + &mut self, timestamp: u64, prevrandao: Option, ) -> Result, ProviderError> { @@ -1841,9 +1829,11 @@ impl ProviderData { let mut inspector = EvmInspector::default(); + let state_to_be_modified = (*self.current_state()?).clone(); + let result = mine_block( &*self.blockchain, - self.state.clone(), + state_to_be_modified, &self.mem_pool, &evm_config, timestamp, @@ -1865,7 +1855,7 @@ impl ProviderData { /// Mines a pending block, without modifying any values. pub fn mine_pending_block( - &self, + &mut self, ) -> Result, ProviderError> { let (block_timestamp, _new_offset) = self.next_block_timestamp(None)?; @@ -1964,10 +1954,10 @@ impl ProviderData { } fn validate_auto_mine_transaction( - &self, + &mut self, transaction: &ExecutableTransaction, ) -> Result<(), ProviderError> { - let next_nonce = self.account_next_nonce(transaction.caller())?; + let next_nonce = { self.account_next_nonce(transaction.caller())? }; match transaction.nonce().cmp(&next_nonce) { Ordering::Less => { @@ -2018,6 +2008,60 @@ impl ProviderData { Ok(()) } + + fn current_state( + &mut self, + ) -> Result>>, ProviderError> { + self.get_or_compute_state(self.last_block_number()) + } + + fn get_or_compute_state( + &mut self, + block_number: u64, + ) -> Result>>, ProviderError> { + if let Some(state_id) = self.block_number_to_state_id.get(&block_number) { + // We cannot use `LruCache::try_get_or_insert`, because it needs &mut self, but + // we would need &self in the callback to reference the blockchain. + if let Some(state) = self.block_state_cache.get(state_id) { + return Ok(state.clone()); + } + }; + + let state = self + .blockchain + .state_at_block_number(block_number, self.irregular_state.state_overrides())?; + let state_id = self.add_state_to_cache(state, block_number); + Ok(self + .block_state_cache + .get(&state_id) + // State must exist, since we just inserted it, and we have exclusive access to + // the cache due to &mut self. + .expect("State must exist") + .clone()) + } + + fn add_state_to_cache( + &mut self, + state: Box>, + block_number: u64, + ) -> StateId { + let state_id = self.current_state_id.increment(); + self.block_state_cache.push(state_id, Arc::new(state)); + self.block_number_to_state_id.insert(block_number, state_id); + state_id + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +#[repr(transparent)] +pub(crate) struct StateId(u64); + +impl StateId { + /// Increment the current state id and return the incremented id. + fn increment(&mut self) -> Self { + self.0 += 1; + *self + } } fn block_time_offset_seconds(config: &ProviderConfig) -> Result { diff --git a/crates/edr_provider/src/requests/eth/blocks.rs b/crates/edr_provider/src/requests/eth/blocks.rs index 82ab6ac121..f4d3f43274 100644 --- a/crates/edr_provider/src/requests/eth/blocks.rs +++ b/crates/edr_provider/src/requests/eth/blocks.rs @@ -41,7 +41,7 @@ pub fn handle_get_block_by_hash_request( } pub fn handle_get_block_by_number_request( - data: &ProviderData, + data: &mut ProviderData, block_spec: PreEip1898BlockSpec, transaction_detail_flag: bool, ) -> Result>, ProviderError> { @@ -74,7 +74,7 @@ pub fn handle_get_block_transaction_count_by_hash_request( } pub fn handle_get_block_transaction_count_by_block_number( - data: &ProviderData, + data: &mut ProviderData, block_spec: PreEip1898BlockSpec, ) -> Result, ProviderError> { Ok(block_by_number(data, &block_spec.into())? @@ -93,7 +93,7 @@ struct BlockByNumberResult { } fn block_by_number( - data: &ProviderData, + data: &mut ProviderData, block_spec: &BlockSpec, ) -> Result, ProviderError> { validate_post_merge_block_tags(data.spec_id(), block_spec)?; diff --git a/crates/edr_provider/src/requests/eth/gas.rs b/crates/edr_provider/src/requests/eth/gas.rs index 8f11a621cc..11a949bf8e 100644 --- a/crates/edr_provider/src/requests/eth/gas.rs +++ b/crates/edr_provider/src/requests/eth/gas.rs @@ -54,7 +54,7 @@ pub fn handle_estimate_gas( } pub fn handle_fee_history( - data: &ProviderData, + data: &mut ProviderData, block_count: U256, newest_block: BlockSpec, reward_percentiles: Option>, diff --git a/crates/edr_provider/src/requests/eth/transactions.rs b/crates/edr_provider/src/requests/eth/transactions.rs index bde48e6d9b..54e8a3f92c 100644 --- a/crates/edr_provider/src/requests/eth/transactions.rs +++ b/crates/edr_provider/src/requests/eth/transactions.rs @@ -40,7 +40,7 @@ pub fn handle_get_transaction_by_block_hash_and_index( } pub fn handle_get_transaction_by_block_spec_and_index( - data: &ProviderData, + data: &mut ProviderData, block_spec: PreEip1898BlockSpec, index: U256, ) -> Result, ProviderError> { @@ -264,7 +264,7 @@ pub fn handle_send_raw_transaction_request( } fn resolve_transaction_request( - data: &ProviderData, + data: &mut ProviderData, transaction_request: EthTransactionRequest, ) -> Result> { const DEFAULT_MAX_PRIORITY_FEE_PER_GAS: u64 = 1_000_000_000; diff --git a/crates/edr_provider/src/snapshot.rs b/crates/edr_provider/src/snapshot.rs index 9bd3497263..e3d46541f3 100644 --- a/crates/edr_provider/src/snapshot.rs +++ b/crates/edr_provider/src/snapshot.rs @@ -1,15 +1,13 @@ -use std::time::Instant; +use std::{collections::BTreeMap, time::Instant}; use edr_eth::{Address, U256}; -use edr_evm::{ - state::{IrregularState, StateError, SyncState}, - MemPool, RandomHashGenerator, -}; -use lru::LruCache; +use edr_evm::{state::IrregularState, MemPool, RandomHashGenerator}; -pub struct Snapshot { +use crate::data::StateId; + +pub(crate) struct Snapshot { pub block_number: u64, - pub block_state_cache: LruCache>>, + pub block_number_to_state_id: BTreeMap, pub block_time_offset_seconds: i64, pub coinbase: Address, pub irregular_state: IrregularState, @@ -17,6 +15,5 @@ pub struct Snapshot { pub next_block_base_fee_per_gas: Option, pub next_block_timestamp: Option, pub prev_randao_generator: RandomHashGenerator, - pub state: Box>, pub time: Instant, } From 89ad114da77919f65393068f1a692d8a5741fdd0 Mon Sep 17 00:00:00 2001 From: Wodann Date: Sun, 11 Feb 2024 01:21:32 +0000 Subject: [PATCH 4/8] fix: failing tests --- Cargo.lock | 1 + crates/edr_evm/Cargo.toml | 1 + crates/edr_evm/src/block/builder.rs | 34 +++++----- crates/edr_evm/src/blockchain/local.rs | 66 +++++++++++++++++++ .../src/blockchain/storage/reservable.rs | 2 +- crates/edr_provider/src/data.rs | 66 ++++++++++++++++--- crates/tools/Cargo.toml | 2 +- 7 files changed, 143 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e617f77166..bd66e72143 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1042,6 +1042,7 @@ name = "edr_evm" version = "0.2.0-dev" dependencies = [ "alloy-rlp", + "anyhow", "async-rwlock", "auto_impl", "cita_trie", diff --git a/crates/edr_evm/Cargo.toml b/crates/edr_evm/Cargo.toml index e77bad0841..2343d2d1f9 100644 --- a/crates/edr_evm/Cargo.toml +++ b/crates/edr_evm/Cargo.toml @@ -30,6 +30,7 @@ tokio = { version = "1.21.2", default-features = false, features = ["macros", "r tracing = { version = "0.1.37", features = ["attributes", "std"], optional = true } [dev-dependencies] +anyhow = "1.0.75" criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support", "html_reports", "plotters"] } lazy_static = "1.4.0" edr_test_utils = { version = "0.2.0-dev", path = "../edr_test_utils" } diff --git a/crates/edr_evm/src/block/builder.rs b/crates/edr_evm/src/block/builder.rs index 0f0fed956f..73fbb964b4 100644 --- a/crates/edr_evm/src/block/builder.rs +++ b/crates/edr_evm/src/block/builder.rs @@ -310,24 +310,22 @@ impl BlockBuilder { StateErrorT: Debug + Send, { for (address, reward) in rewards { - state.modify_account( - address, - AccountModifierFn::new(Box::new(move |balance, _nonce, _code| { - *balance += reward; - })), - &|| { - Ok(AccountInfo { - code: None, - ..AccountInfo::default() - }) - }, - )?; - - let account_info = state - .basic(address)? - .expect("Account must exist after modification"); - - self.state_diff.apply_account_change(address, account_info); + if reward > U256::ZERO { + let account_info = state.modify_account( + address, + AccountModifierFn::new(Box::new(move |balance, _nonce, _code| { + *balance += reward; + })), + &|| { + Ok(AccountInfo { + code: None, + ..AccountInfo::default() + }) + }, + )?; + + self.state_diff.apply_account_change(address, account_info); + } } if let Some(gas_limit) = self.parent_gas_limit { diff --git a/crates/edr_evm/src/blockchain/local.rs b/crates/edr_evm/src/blockchain/local.rs index 0356d46fcc..5dadddc073 100644 --- a/crates/edr_evm/src/blockchain/local.rs +++ b/crates/edr_evm/src/blockchain/local.rs @@ -395,3 +395,69 @@ impl BlockHashRef for LocalBlockchain { .ok_or(BlockchainError::UnknownBlockNumber) } } + +#[cfg(test)] +mod tests { + use edr_eth::{AccountInfo, HashMap}; + use revm::primitives::{Account, AccountStatus}; + + use super::*; + use crate::state::IrregularState; + + #[test] + fn compute_state_after_reserve() -> anyhow::Result<()> { + let address1 = Address::random(); + let accounts = vec![( + address1, + AccountInfo { + balance: U256::from(1_000_000_000u64), + ..AccountInfo::default() + }, + )]; + + let genesis_diff = accounts + .iter() + .map(|(address, info)| { + ( + *address, + Account { + info: info.clone(), + storage: HashMap::new(), + status: AccountStatus::Created | AccountStatus::Touched, + }, + ) + }) + .collect::>() + .into(); + + let mut blockchain = LocalBlockchain::new( + genesis_diff, + 123, + SpecId::SHANGHAI, + 6_000_000, + None, + Some(B256::random()), + None, + Some(BlobGas::default()), + Some(B256::random()), + ) + .unwrap(); + + let irregular_state = IrregularState::default(); + let expected = blockchain.state_at_block_number(0, irregular_state.state_overrides())?; + + blockchain.reserve_blocks(1_000_000_000, 1)?; + + let actual = + blockchain.state_at_block_number(1_000_000_000, irregular_state.state_overrides())?; + + assert_eq!(actual.state_root().unwrap(), expected.state_root().unwrap()); + + for (address, expected) in accounts { + let actual_account = actual.basic(address)?.expect("account should exist"); + assert_eq!(actual_account, expected); + } + + Ok(()) + } +} diff --git a/crates/edr_evm/src/blockchain/storage/reservable.rs b/crates/edr_evm/src/blockchain/storage/reservable.rs index f56105cdcd..77b59ee978 100644 --- a/crates/edr_evm/src/blockchain/storage/reservable.rs +++ b/crates/edr_evm/src/blockchain/storage/reservable.rs @@ -150,7 +150,7 @@ impl ReservableSparseBlockchainStorage { previous_base_fee_per_gas: previous_base_fee, previous_state_root, previous_total_difficulty, - previous_diff_index: self.state_diffs.len(), + previous_diff_index: self.state_diffs.len() - 1, spec_id, }; diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 3eab2871f5..45cb46284a 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -1121,9 +1121,15 @@ impl ProviderData { mine_block_with_interval(self, &mut mined_blocks)?; } } else { + let current_state = (*self.current_state()?).clone(); + self.blockchain .reserve_blocks(remaining_blocks - 1, interval)?; + // Ensure there is a cache entry for the last reserved block, to avoid + // recomputation + self.add_state_to_cache(current_state, self.last_block_number()); + let previous_timestamp = self.blockchain.last_block()?.header().timestamp; let mined_block = self.mine_and_commit_block(Some(previous_timestamp + interval))?; @@ -1610,24 +1616,24 @@ impl ProviderData { value: U256, ) -> Result<(), ProviderError> { // We clone to automatically revert in case of subsequent errors. - let mut state = (*self.current_state()?).clone(); - state.set_account_storage_slot(address, index, value)?; + let mut modified_state = (*self.current_state()?).clone(); + modified_state.set_account_storage_slot(address, index, value)?; - let old_value = state.set_account_storage_slot(address, index, value)?; + let old_value = modified_state.set_account_storage_slot(address, index, value)?; let slot = StorageSlot::new_changed(old_value, value); - let account_info = state.basic(address).and_then(|mut account_info| { + let account_info = modified_state.basic(address).and_then(|mut account_info| { // Retrieve the code if it's not empty. This is needed for the irregular state. if let Some(account_info) = &mut account_info { if account_info.code_hash != KECCAK_EMPTY { - account_info.code = Some(state.code_by_hash(account_info.code_hash)?); + account_info.code = Some(modified_state.code_by_hash(account_info.code_hash)?); } } Ok(account_info) })?; - let state_root = state.state_root()?; + let state_root = modified_state.state_root()?; let block_number = self.blockchain.last_block_number(); self.irregular_state @@ -1636,7 +1642,7 @@ impl ProviderData { .diff .apply_storage_change(address, index, slot, account_info); - self.add_state_to_cache(state, block_number); + self.add_state_to_cache(modified_state, block_number); Ok(()) } @@ -2102,8 +2108,6 @@ fn create_blockchain_and_state( config: &ProviderConfig, mut genesis_accounts: HashMap, ) -> Result { - let mut irregular_state = IrregularState::default(); - let mut prev_randao_generator = RandomHashGenerator::with_seed(edr_defaults::MIX_HASH_SEED); if let Some(fork_config) = &config.fork { @@ -2143,6 +2147,7 @@ fn create_blockchain_and_state( ) .expect("url ok"); + let mut irregular_state = IrregularState::default(); if !genesis_accounts.is_empty() { let genesis_addresses = genesis_accounts.keys().cloned().collect::>(); let genesis_account_infos = tokio::task::block_in_place(|| { @@ -2258,6 +2263,7 @@ fn create_blockchain_and_state( config.initial_parent_beacon_block_root, )?; + let irregular_state = IrregularState::default(); let state = blockchain .state_at_block_number(0, irregular_state.state_overrides()) .expect("Genesis state must exist"); @@ -2704,6 +2710,48 @@ mod tests { Ok(()) } + #[test] + fn mine_and_commit_block_empty() -> anyhow::Result<()> { + let mut fixture = ProviderTestFixture::new()?; + + let result = fixture.provider_data.mine_and_commit_block(None)?; + + let cached_state = fixture + .provider_data + .get_or_compute_state(result.block.header().number)?; + + let calculated_state = fixture.provider_data.blockchain.state_at_block_number( + fixture.provider_data.last_block_number(), + fixture.provider_data.irregular_state.state_overrides(), + )?; + + assert_eq!(cached_state.state_root()?, calculated_state.state_root()?); + + Ok(()) + } + + #[test] + fn mine_and_commit_blocks_empty() -> anyhow::Result<()> { + let mut fixture = ProviderTestFixture::new()?; + + fixture + .provider_data + .mine_and_commit_blocks(1_000_000_000, 1)?; + + let cached_state = fixture + .provider_data + .get_or_compute_state(fixture.provider_data.last_block_number())?; + + let calculated_state = fixture.provider_data.blockchain.state_at_block_number( + fixture.provider_data.last_block_number(), + fixture.provider_data.irregular_state.state_overrides(), + )?; + + assert_eq!(cached_state.state_root()?, calculated_state.state_root()?); + + Ok(()) + } + #[test] fn next_filter_id() -> anyhow::Result<()> { let mut fixture = ProviderTestFixture::new()?; diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index a12f5a0782..e074a0088d 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -anyhow = { version = "1.0.63" } +anyhow = { version = "1.0.75" } cfg-if = "1.0.0" clap = { version = "3.2.20", features = ["derive"] } difference = { version = "2.0.0", default-features = false } From ab8fb1229c092f4499dca3a0ce87be8f54451b47 Mon Sep 17 00:00:00 2001 From: Wodann Date: Mon, 12 Feb 2024 16:07:22 +0000 Subject: [PATCH 5/8] test: disable interval mining test for forked blockchains --- .../provider/interval-mining-provider.ts | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts index bfe746673e..ed419859bb 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts @@ -1,7 +1,6 @@ import { assert } from "chai"; import { rpcQuantityToNumber } from "../../../../src/internal/core/jsonrpc/types/base-types"; -import { ALCHEMY_URL } from "../../../setup"; import { workaroundWindowsCiFailures } from "../../../utils/workaround-windows-ci-failures"; import { setCWD } from "../helpers/cwd"; import { INTERVAL_MINING_PROVIDERS } from "../helpers/providers"; @@ -12,7 +11,6 @@ describe("Interval mining provider", function () { workaroundWindowsCiFailures.call(this, { isFork }); describe(`${name} provider`, function () { - const safeBlockInThePast = 11_200_000; const blockTime = 100; const blockWaitTime = blockTime + 10; @@ -45,40 +43,10 @@ describe("Interval mining provider", function () { }); describe("hardhat_reset", function () { - if (isFork) { - testForkedProviderBehaviour(); - } else { + if (!isFork) { testNormalProviderBehaviour(); } - function testForkedProviderBehaviour() { - it("starts interval mining", async function () { - const firstBlock = await getBlockNumber(); - - await sleep(blockWaitTime); - const secondBlockBeforeReset = await getBlockNumber(); - - await this.provider.send("hardhat_reset", [ - { - forking: { - jsonRpcUrl: ALCHEMY_URL, - blockNumber: safeBlockInThePast, - }, - }, - ]); - - await sleep(blockWaitTime); - const secondBlockAfterReset = await getBlockNumber(); - - await sleep(blockWaitTime); - const thirdBlock = await getBlockNumber(); - - assert.equal(secondBlockBeforeReset, firstBlock + 1); - assert.equal(secondBlockAfterReset, safeBlockInThePast + 1); - assert.equal(thirdBlock, safeBlockInThePast + 2); - }); - } - function testNormalProviderBehaviour() { it("starts interval mining", async function () { const firstBlock = await getBlockNumber(); From 015e31f9b949f4e0325ec471b22500fd4672986e Mon Sep 17 00:00:00 2001 From: Wodann Date: Mon, 12 Feb 2024 19:02:59 +0000 Subject: [PATCH 6/8] fix: interval mining reset --- Cargo.lock | 1 + crates/edr_napi/Cargo.toml | 1 + crates/edr_napi/src/provider.rs | 14 +++---- crates/edr_provider/src/config.rs | 13 +++--- crates/edr_provider/src/data.rs | 6 ++- crates/edr_provider/src/interval.rs | 37 ++++++++++------- crates/edr_provider/src/lib.rs | 40 +++++++++++++------ crates/edr_provider/src/requests/eth/mine.rs | 13 ++++-- .../src/requests/hardhat/state.rs | 10 +---- .../provider/interval-mining-provider.ts | 34 +++++++++++++++- 10 files changed, 114 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd66e72143..470c6b607b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1083,6 +1083,7 @@ dependencies = [ "itertools 0.12.0", "k256", "lazy_static", + "log", "napi", "napi-build", "napi-derive", diff --git a/crates/edr_napi/Cargo.toml b/crates/edr_napi/Cargo.toml index f68ba930eb..883ac5233a 100644 --- a/crates/edr_napi/Cargo.toml +++ b/crates/edr_napi/Cargo.toml @@ -11,6 +11,7 @@ ansi_term = { version = "0.12.1", default-features = false } crossbeam-channel = { version = "0.5.6", default-features = false } itertools = { version = "0.12.0", default-features = false } k256 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa", "pkcs8", "precomputed-tables", "std"] } +log = { version = "0.4.20", default-features = false } # when napi is pinned, be sure to pin napi-derive to the same version # The `async` feature ensures that a tokio runtime is available napi = { version = "2.12.4", default-features = false, features = ["async", "error_anyhow", "napi8", "serde-json"] } diff --git a/crates/edr_napi/src/provider.rs b/crates/edr_napi/src/provider.rs index 4bc8f2dcd1..977a999ede 100644 --- a/crates/edr_napi/src/provider.rs +++ b/crates/edr_napi/src/provider.rs @@ -73,14 +73,12 @@ impl Provider { if let Some((method_name, provider_error)) = reason.provider_error() { // Ignore potential failure of logging, as returning the original error is more // important - let _result = runtime::Handle::current() - .spawn_blocking(move || { - provider.log_failed_deserialization(&method_name, &provider_error) - }) + if let Err(error) = provider + .log_failed_deserialization(&method_name, &provider_error) .await - .map_err(|error| { - napi::Error::new(Status::GenericFailure, error.to_string()) - })?; + { + log::error!("Failed to log deserialization error: {error}"); + } } let data = serde_json::from_str(&json_request).ok(); @@ -107,7 +105,7 @@ impl Provider { }; let response = runtime::Handle::current() - .spawn_blocking(move || provider.handle_request(request)) + .spawn(async move { provider.handle_request(request).await }) .await .map_err(|e| napi::Error::new(Status::GenericFailure, e.to_string()))?; diff --git a/crates/edr_provider/src/config.rs b/crates/edr_provider/src/config.rs index 068f70178b..4811813b4f 100644 --- a/crates/edr_provider/src/config.rs +++ b/crates/edr_provider/src/config.rs @@ -25,14 +25,17 @@ impl IntervalConfig { } } -impl From for IntervalConfig { - fn from(value: OneUsizeOrTwo) -> Self { +impl TryFrom for IntervalConfig { + type Error = (); + + fn try_from(value: OneUsizeOrTwo) -> Result { match value { - OneUsizeOrTwo::One(value) => Self::Fixed(value as u64), - OneUsizeOrTwo::Two([min, max]) => Self::Range { + OneUsizeOrTwo::One(0) => Err(()), + OneUsizeOrTwo::One(value) => Ok(Self::Fixed(value as u64)), + OneUsizeOrTwo::Two([min, max]) => Ok(Self::Range { min: min as u64, max: max as u64, - }, + }), } } } diff --git a/crates/edr_provider/src/data.rs b/crates/edr_provider/src/data.rs index 174c9d3759..224d949301 100644 --- a/crates/edr_provider/src/data.rs +++ b/crates/edr_provider/src/data.rs @@ -70,7 +70,7 @@ use crate::{ pending::BlockchainWithPending, requests::hardhat::rpc_types::{ForkConfig, ForkMetadata}, snapshot::Snapshot, - ProviderConfig, ProviderError, SubscriptionEvent, SubscriptionEventData, + MiningConfig, ProviderConfig, ProviderError, SubscriptionEvent, SubscriptionEventData, SyncSubscriberCallback, }; @@ -1871,6 +1871,10 @@ impl ProviderData { self.mine_block(block_timestamp, prevrandao) } + pub fn mining_config(&self) -> &MiningConfig { + &self.initial_config.mining + } + /// Get the timestamp for the next block. /// Ported from fn next_block_timestamp( diff --git a/crates/edr_provider/src/interval.rs b/crates/edr_provider/src/interval.rs index 5e0cd02949..0a05107fef 100644 --- a/crates/edr_provider/src/interval.rs +++ b/crates/edr_provider/src/interval.rs @@ -1,8 +1,12 @@ use core::fmt::Debug; use std::sync::Arc; -use parking_lot::Mutex; -use tokio::{runtime, sync::oneshot, task::JoinHandle}; +use tokio::{ + runtime, + sync::{oneshot, Mutex}, + task::JoinHandle, + time::Instant, +}; use crate::{data::ProviderData, IntervalConfig, ProviderError}; @@ -27,23 +31,28 @@ impl IntervalMiner { ) -> Self { let (cancellation_sender, mut cancellation_receiver) = oneshot::channel(); let background_task = runtime.spawn(async move { + let mut now = Instant::now(); loop { let delay = config.generate_interval(); + let deadline = now + std::time::Duration::from_millis(delay); tokio::select! { _ = &mut cancellation_receiver => return Ok(()), - _ = tokio::time::sleep(std::time::Duration::from_millis(delay)) => { - let data = data.clone(); + _ = tokio::time::sleep_until(deadline) => { + tokio::select! { + // Check whether the interval miner needs to be destroyed + _ = &mut cancellation_receiver => return Ok(()), + mut data = data.lock() => { + now = Instant::now(); - runtime::Handle::current().spawn_blocking(move ||{ - let mut data = data.lock(); - if let Err(error) = data.interval_mine() { - log::error!("Unexpected error while performing interval mining: {error}"); - return Err(error); - } + if let Err(error) = data.interval_mine() { + log::error!("Unexpected error while performing interval mining: {error}"); + return Err(error); + } - Ok(()) - }).await.expect("Failed to join interval mining task") + Result::<(), ProviderError>::Ok(()) + } + } }, }?; } @@ -70,9 +79,7 @@ impl Drop for IntervalMiner { .send(()) .expect("Failed to send cancellation signal"); - let _result = self - .runtime - .block_on(task) + let _result = tokio::task::block_in_place(move || self.runtime.block_on(task)) .expect("Failed to join interval mininig task"); } } diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index 96f954d62b..a3f1654267 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -20,8 +20,8 @@ use edr_evm::{blockchain::BlockchainError, HashSet}; use lazy_static::lazy_static; use logger::SyncLogger; use parking_lot::Mutex; -use requests::eth::handle_set_interval_mining; -use tokio::runtime; +use requests::{eth::handle_set_interval_mining, hardhat::rpc_types::ResetProviderConfig}; +use tokio::{runtime, sync::Mutex as AsyncMutex}; pub use self::{ config::*, @@ -84,8 +84,9 @@ lazy_static! { /// } /// ``` pub struct Provider { - data: Arc>>, - /// Interval miner runs in the background, if enabled. + data: Arc>>, + /// Interval miner runs in the background, if enabled. It holds the data + /// mutex, so interval_miner: Arc>>>, runtime: runtime::Handle, } @@ -99,7 +100,7 @@ impl Provider { config: ProviderConfig, ) -> Result { let data = ProviderData::new(runtime.clone(), logger, subscriber_callback, config.clone())?; - let data = Arc::new(Mutex::new(data)); + let data = Arc::new(AsyncMutex::new(data)); let interval_miner = config .mining @@ -116,11 +117,11 @@ impl Provider { }) } - pub fn handle_request( + pub async fn handle_request( &self, request: ProviderRequest, ) -> Result> { - let mut data = self.data.lock(); + let mut data = self.data.lock().await; match request { ProviderRequest::Single(request) => self.handle_single_request(&mut data, request), @@ -128,12 +129,12 @@ impl Provider { } } - pub fn log_failed_deserialization( + pub async fn log_failed_deserialization( &self, method_name: &str, error: &ProviderError, ) -> Result<(), ProviderError> { - let mut data = self.data.lock(); + let mut data = self.data.lock().await; data.logger_mut() .print_method_logs(method_name, Some(error)) .map_err(ProviderError::Logger) @@ -362,9 +363,7 @@ impl Provider { MethodInvocation::Mine(number_of_blocks, interval) => { hardhat::handle_mine(data, number_of_blocks, interval).and_then(to_json) } - MethodInvocation::Reset(config) => { - hardhat::handle_reset(data, config).and_then(to_json) - } + MethodInvocation::Reset(config) => self.reset(data, config).and_then(to_json), MethodInvocation::SetBalance(address, balance) => { hardhat::handle_set_balance(data, address, balance).and_then(to_json) } @@ -409,6 +408,23 @@ impl Provider { result } + + fn reset( + &self, + data: &mut ProviderData, + config: Option, + ) -> Result> { + let mut interval_miner = self.interval_miner.lock(); + interval_miner.take(); + + data.reset(config.and_then(|c| c.forking))?; + + *interval_miner = data.mining_config().interval.as_ref().map(|config| { + IntervalMiner::new(self.runtime.clone(), config.clone(), self.data.clone()) + }); + + Ok(true) + } } fn to_json( diff --git a/crates/edr_provider/src/requests/eth/mine.rs b/crates/edr_provider/src/requests/eth/mine.rs index 4c93c2aaa0..8e1644ee84 100644 --- a/crates/edr_provider/src/requests/eth/mine.rs +++ b/crates/edr_provider/src/requests/eth/mine.rs @@ -1,10 +1,11 @@ use core::fmt::Debug; use std::sync::Arc; -use parking_lot::Mutex; -use tokio::runtime; +use tokio::{runtime, sync::Mutex}; -use crate::{data::ProviderData, interval::IntervalMiner, OneUsizeOrTwo, ProviderError}; +use crate::{ + data::ProviderData, interval::IntervalMiner, IntervalConfig, OneUsizeOrTwo, ProviderError, +}; pub fn handle_set_interval_mining( data: Arc>>, @@ -12,7 +13,11 @@ pub fn handle_set_interval_mining( runtime: runtime::Handle, config: OneUsizeOrTwo, ) -> Result> { - *interval_miner = Some(IntervalMiner::new(runtime, config.into(), data.clone())); + let config = IntervalConfig::try_from(config); + + *interval_miner = config + .ok() + .map(|config| IntervalMiner::new(runtime, config, data.clone())); Ok(true) } diff --git a/crates/edr_provider/src/requests/hardhat/state.rs b/crates/edr_provider/src/requests/hardhat/state.rs index 77b77f6b5d..9e6157c1db 100644 --- a/crates/edr_provider/src/requests/hardhat/state.rs +++ b/crates/edr_provider/src/requests/hardhat/state.rs @@ -2,15 +2,7 @@ use core::fmt::Debug; use edr_eth::{Address, Bytes, U256}; -use crate::{data::ProviderData, requests::hardhat::rpc_types::ResetProviderConfig, ProviderError}; - -pub fn handle_reset( - data: &mut ProviderData, - config: Option, -) -> Result> { - data.reset(config.and_then(|c| c.forking))?; - Ok(true) -} +use crate::{data::ProviderData, ProviderError}; pub fn handle_set_balance( data: &mut ProviderData, diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts index ed419859bb..bfe746673e 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/interval-mining-provider.ts @@ -1,6 +1,7 @@ import { assert } from "chai"; import { rpcQuantityToNumber } from "../../../../src/internal/core/jsonrpc/types/base-types"; +import { ALCHEMY_URL } from "../../../setup"; import { workaroundWindowsCiFailures } from "../../../utils/workaround-windows-ci-failures"; import { setCWD } from "../helpers/cwd"; import { INTERVAL_MINING_PROVIDERS } from "../helpers/providers"; @@ -11,6 +12,7 @@ describe("Interval mining provider", function () { workaroundWindowsCiFailures.call(this, { isFork }); describe(`${name} provider`, function () { + const safeBlockInThePast = 11_200_000; const blockTime = 100; const blockWaitTime = blockTime + 10; @@ -43,10 +45,40 @@ describe("Interval mining provider", function () { }); describe("hardhat_reset", function () { - if (!isFork) { + if (isFork) { + testForkedProviderBehaviour(); + } else { testNormalProviderBehaviour(); } + function testForkedProviderBehaviour() { + it("starts interval mining", async function () { + const firstBlock = await getBlockNumber(); + + await sleep(blockWaitTime); + const secondBlockBeforeReset = await getBlockNumber(); + + await this.provider.send("hardhat_reset", [ + { + forking: { + jsonRpcUrl: ALCHEMY_URL, + blockNumber: safeBlockInThePast, + }, + }, + ]); + + await sleep(blockWaitTime); + const secondBlockAfterReset = await getBlockNumber(); + + await sleep(blockWaitTime); + const thirdBlock = await getBlockNumber(); + + assert.equal(secondBlockBeforeReset, firstBlock + 1); + assert.equal(secondBlockAfterReset, safeBlockInThePast + 1); + assert.equal(thirdBlock, safeBlockInThePast + 2); + }); + } + function testNormalProviderBehaviour() { it("starts interval mining", async function () { const firstBlock = await getBlockNumber(); From 954c6a631455faf0ca6537f0afd454c135d82ab4 Mon Sep 17 00:00:00 2001 From: Wodann Date: Mon, 12 Feb 2024 21:24:48 +0000 Subject: [PATCH 7/8] fix: support for remote blob transactions --- .../provider/utils/convertToEdr.ts | 48 +++++++++++++++++-- .../eth/methods/getTransactionByHash.ts | 29 +++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts index cd7c4b7cee..dd429e3736 100644 --- a/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts +++ b/packages/hardhat-core/src/internal/hardhat-network/provider/utils/convertToEdr.ts @@ -44,6 +44,7 @@ import { TracingMessage, SuccessReason, IntervalRange, + Eip4844SignedTransaction, } from "@ignored/edr"; import { fromBigIntLike, toHex } from "../../../util/bigint"; import { HardforkName, hardforkGte } from "../../../util/hardforks"; @@ -672,10 +673,35 @@ export function edrSignedTransactionToEthereumJSTypedTransaction( transaction: | LegacySignedTransaction | Eip2930SignedTransaction - | Eip1559SignedTransaction, + | Eip1559SignedTransaction + | Eip4844SignedTransaction, caller: Address ): TypedTransaction { - if (isEip1559SignedTransaction(transaction)) { + if (isEip4844SignedTransaction(transaction)) { + // TODO: https://github.com/NomicFoundation/edr/issues/289 + // Add proper support for EIP-4844 + const fakeTransaction = new FakeSenderEIP1559Transaction(caller, { + maxPriorityFeePerGas: transaction.maxPriorityFeePerGas, + maxFeePerGas: transaction.maxFeePerGas, + chainId: transaction.chainId, + accessList: transaction.accessList.map((value, _index, _array) => { + return [value.address, value.storageKeys]; + }), + nonce: transaction.nonce, + gasLimit: transaction.gasLimit, + to: transaction.to, + value: transaction.value, + data: transaction.input, + v: BigInt(transaction.oddYParity), + r: transaction.r, + s: transaction.s, + }); + + // Overwrite transaction type + (fakeTransaction as any)._type = 3; + + return fakeTransaction; + } else if (isEip1559SignedTransaction(transaction)) { return new FakeSenderEIP1559Transaction(caller, { maxPriorityFeePerGas: transaction.maxPriorityFeePerGas, maxFeePerGas: transaction.maxFeePerGas, @@ -730,6 +756,7 @@ function isLegacySignedTransaction( | LegacySignedTransaction | Eip2930SignedTransaction | Eip1559SignedTransaction + | Eip4844SignedTransaction ): transaction is LegacySignedTransaction { // Only need to check for one unique field return "signature" in transaction; @@ -740,9 +767,13 @@ function isEip1559SignedTransaction( | LegacySignedTransaction | Eip2930SignedTransaction | Eip1559SignedTransaction + | Eip4844SignedTransaction ): transaction is Eip1559SignedTransaction { // Only need to check for one unique field - return "maxPriorityFeePerGas" in transaction; + return ( + !isEip4844SignedTransaction(transaction) && + "maxPriorityFeePerGas" in transaction + ); } function isEip2930SignedTransaction( @@ -750,6 +781,7 @@ function isEip2930SignedTransaction( | LegacySignedTransaction | Eip2930SignedTransaction | Eip1559SignedTransaction + | Eip4844SignedTransaction ): transaction is Eip2930SignedTransaction { // Only need to check for one unique field return ( @@ -757,6 +789,16 @@ function isEip2930SignedTransaction( ); } +function isEip4844SignedTransaction( + transaction: + | LegacySignedTransaction + | Eip2930SignedTransaction + | Eip1559SignedTransaction + | Eip4844SignedTransaction +): transaction is Eip4844SignedTransaction { + return "maxFeePerBlobGas" in transaction; +} + export function edrRpcDebugTraceToHardhat( rpcDebugTrace: DebugTraceResult ): RpcDebugTraceOutput { diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts index 5d2971d64c..15a3fe917d 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/modules/eth/methods/getTransactionByHash.ts @@ -350,6 +350,35 @@ describe("Eth module", function () { assert.equal(tx.from, "0x84467283e3663522a02574288291a9d0f9c968c2"); }); + it("should get a blob transaction from goerli", async function () { + if (!isFork || ALCHEMY_URL === undefined) { + this.skip(); + } + const goerliUrl = ALCHEMY_URL.replace("mainnet", "goerli"); + + // If "mainnet" is not present the replacement failed so we skip the test + if (goerliUrl === ALCHEMY_URL) { + this.skip(); + } + + await this.provider.send("hardhat_reset", [ + { + forking: { + jsonRpcUrl: goerliUrl, + // Cancun block + blockNumber: 10527489, + }, + }, + ]); + + const tx = await this.provider.send("eth_getTransactionByHash", [ + // blob transaction + "0x0190ab719774b0ed612789072e399157537845383c2d2445a9929784a098a5c9", + ]); + + assert.equal(tx.from, "0xa1d6cf9ed782555a0572cc08380ee3b68a1df449"); + }); + it("should return access list transactions", async function () { const firstBlockNumber = rpcQuantityToNumber( await this.provider.send("eth_blockNumber") From fa34d1aff20693f1b4b340e44c6593dd04bf5f6e Mon Sep 17 00:00:00 2001 From: Wodann Date: Tue, 13 Feb 2024 15:07:34 +0000 Subject: [PATCH 8/8] docs: clarify chance of deadlock for interval miner --- crates/edr_provider/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/edr_provider/src/lib.rs b/crates/edr_provider/src/lib.rs index a3f1654267..1415fa33e3 100644 --- a/crates/edr_provider/src/lib.rs +++ b/crates/edr_provider/src/lib.rs @@ -86,7 +86,8 @@ lazy_static! { pub struct Provider { data: Arc>>, /// Interval miner runs in the background, if enabled. It holds the data - /// mutex, so + /// mutex, so it needs to internally check for cancellation/self-destruction + /// while async-awaiting the lock to avoid a deadlock. interval_miner: Arc>>>, runtime: runtime::Handle, }