@@ -5398,6 +5398,8 @@ where
53985398 let current_excess_blob_gas_and_price = self . excess_blob_gas_and_price ( ) ;
53995399
54005400 let mut evm_env = self . evm_env . read ( ) . clone ( ) ;
5401+ #[ cfg( feature = "monad" ) ]
5402+ let execution_chain_id = evm_env. cfg_env . chain_id ;
54015403 let hardfork = self . hardfork ( ) ;
54025404
54035405 if evm_env. block_env . basefee == 0 {
@@ -5504,6 +5506,30 @@ where
55045506 self . cheats . consume_next_block_prevrandao ( pending) ;
55055507 }
55065508
5509+ let header = & block_info. block . header ;
5510+ let next_block_base_fee = self . fees . get_next_block_base_fee_per_gas (
5511+ header. gas_used ,
5512+ header. gas_limit ,
5513+ header. base_fee_per_gas . unwrap_or_default ( ) ,
5514+ ) ;
5515+ let next_block_excess_blob_gas = self . fees . get_next_block_blob_excess_gas (
5516+ header. excess_blob_gas . unwrap_or_default ( ) ,
5517+ header. blob_gas_used . unwrap_or_default ( ) ,
5518+ ) ;
5519+ let next_block_blob_fees = BlobExcessGasAndPrice :: new (
5520+ next_block_excess_blob_gas,
5521+ self . fees . blob_params ( ) . update_fraction as u64 ,
5522+ ) ;
5523+
5524+ // Live execution readers acquire the database read lock before cloning the block
5525+ // environment and next-block fee state. Publish the complete snapshot under the
5526+ // database write lock so readers observe either the parent or newly mined state.
5527+ // The database is always the outer lock, and no path holds an environment or fee
5528+ // guard while waiting for it, so these acquisitions cannot form a lock cycle.
5529+ evm_env. block_env . difficulty = U256 :: ZERO ;
5530+ * self . evm_env . write ( ) = evm_env;
5531+ self . fees . set_next_block_fees ( next_block_base_fee, next_block_blob_fees) ;
5532+
55075533 ( block_info, included, invalid, not_yet_valid, block_hash, parent_state)
55085534 } ;
55095535
@@ -5555,7 +5581,7 @@ where
55555581 & mut storage,
55565582 block_hash,
55575583 participants,
5558- evm_env . cfg_env . chain_id ,
5584+ execution_chain_id ,
55595585 hardfork,
55605586 ) ;
55615587 }
@@ -5593,12 +5619,6 @@ where
55935619
55945620 self . time . mark_block_created ( ) ;
55955621
5596- // we intentionally set the difficulty to `0` for newer blocks
5597- evm_env. block_env . difficulty = U256 :: from ( 0 ) ;
5598-
5599- // update env with new values
5600- * self . evm_env . write ( ) = evm_env;
5601-
56025622 let timestamp = utc_from_secs ( header. timestamp ) ;
56035623
56045624 node_info ! ( " Block Number: {}" , block_number) ;
@@ -5614,24 +5634,6 @@ where
56145634
56155635 ( outcome, header, block_hash)
56165636 } ;
5617- let next_block_base_fee = self . fees . get_next_block_base_fee_per_gas (
5618- header. gas_used ,
5619- header. gas_limit ,
5620- header. base_fee_per_gas . unwrap_or_default ( ) ,
5621- ) ;
5622- let next_block_excess_blob_gas = self . fees . get_next_block_blob_excess_gas (
5623- header. excess_blob_gas . unwrap_or_default ( ) ,
5624- header. blob_gas_used . unwrap_or_default ( ) ,
5625- ) ;
5626-
5627- // update next base fee
5628- self . fees . set_base_fee ( next_block_base_fee) ;
5629-
5630- self . fees . set_blob_excess_gas_and_price ( BlobExcessGasAndPrice :: new (
5631- next_block_excess_blob_gas,
5632- self . fees . blob_params ( ) . update_fraction as u64 ,
5633- ) ) ;
5634-
56355637 // notify all listeners
56365638 self . notify_on_new_block ( header. into_inner ( ) , block_hash) ;
56375639
@@ -9420,12 +9422,12 @@ pub use foundry_evm::core::evm::IntoInstructionResult;
94209422#[ cfg( test) ]
94219423mod tests {
94229424 use super :: {
9423- ForkCacheNamespace , ForkCacheSource , StagedForkCacheLease , StagedForkDbUser ,
9424- arbitrum_replay_block_number,
9425+ BlockRequest , FeeDetails , ForkCacheNamespace , ForkCacheSource , InstructionResult , Output ,
9426+ StagedForkCacheLease , StagedForkDbUser , arbitrum_replay_block_number,
94259427 } ;
94269428 use crate :: { NodeConfig , config:: ForkTransactionReplay , spawn} ;
94279429 use alloy_network:: { AnyHeader , AnyRpcBlock , AnyRpcHeader , TransactionBuilder } ;
9428- use alloy_primitives:: { B256 , Bytes , U256 } ;
9430+ use alloy_primitives:: { Address , B256 , Bytes , U256 } ;
94299431 use alloy_provider:: Provider ;
94309432 use alloy_rpc_types:: { Block , BlockTransactions , TransactionRequest , state:: EvmOverrides } ;
94319433 use alloy_serde:: WithOtherFields ;
@@ -9435,7 +9437,7 @@ mod tests {
94359437 hardfork:: { EthereumHardfork , FoundryHardfork } ,
94369438 } ;
94379439 use foundry_evm_networks:: arbitrum;
9438- use std:: sync:: Arc ;
9440+ use std:: sync:: { Arc , mpsc } ;
94399441 use tempfile:: tempdir;
94409442
94419443 fn test_cache_db ( cache_path : std:: path:: PathBuf ) -> BlockchainDb {
@@ -9559,6 +9561,79 @@ mod tests {
95599561 assert_eq ! ( api. backend. time( ) . last_block_wall_time( ) , head_wall_time) ;
95609562 }
95619563
9564+ #[ tokio:: test( flavor = "multi_thread" ) ]
9565+ async fn live_execution_snapshot_is_coherent_during_mining ( ) {
9566+ let ( api, handle) = spawn ( NodeConfig :: test ( ) . with_no_mining ( true ) ) . await ;
9567+ let sender = handle. dev_wallets ( ) . next ( ) . unwrap ( ) . address ( ) ;
9568+ let recipient = Address :: repeat_byte ( 0x11 ) ;
9569+ let contract = Address :: repeat_byte ( 0x22 ) ;
9570+
9571+ // Return the recipient balance followed by NUMBER. Before mining both are zero; after the
9572+ // queued transfer is mined both are one.
9573+ let mut code = vec ! [ 0x73 ] ;
9574+ code. extend_from_slice ( recipient. as_slice ( ) ) ;
9575+ code. extend_from_slice ( & [
9576+ 0x31 , 0x60 , 0x00 , 0x52 , 0x43 , 0x60 , 0x20 , 0x52 , 0x60 , 0x40 , 0x60 , 0x00 , 0xf3 ,
9577+ ] ) ;
9578+ api. anvil_set_code ( contract, code. into ( ) ) . await . unwrap ( ) ;
9579+ api. send_transaction ( WithOtherFields :: new (
9580+ TransactionRequest :: default ( ) . from ( sender) . to ( recipient) . value ( U256 :: from ( 1 ) ) ,
9581+ ) )
9582+ . await
9583+ . unwrap ( ) ;
9584+
9585+ let resolved_head = api. backend . best_number ( ) ;
9586+ let db_guard = api. backend . db . write ( ) . await ;
9587+
9588+ // Polling while holding the database makes the lock queue deterministic: mining owns the
9589+ // first waiter and the call that already resolved `latest` owns the second.
9590+ let mining_api = api. clone ( ) ;
9591+ let mut mining = Box :: pin ( async move { mining_api. mine_one ( ) . await } ) ;
9592+ assert ! ( futures:: poll!( mining. as_mut( ) ) . is_pending( ) ) ;
9593+
9594+ let request = WithOtherFields :: new ( TransactionRequest :: default ( ) . to ( contract) ) ;
9595+ let mut call = Box :: pin ( api. backend . call (
9596+ request,
9597+ FeeDetails :: zero ( ) ,
9598+ Some ( BlockRequest :: Number ( resolved_head) ) ,
9599+ EvmOverrides :: default ( ) ,
9600+ ) ) ;
9601+ assert ! ( futures:: poll!( call. as_mut( ) ) . is_pending( ) ) ;
9602+
9603+ // Pause canonical block publication after the database snapshot is published. A separate
9604+ // thread owns this synchronous lock so the async test does not hold it across an await.
9605+ let backend = api. backend . clone ( ) ;
9606+ let ( storage_locked_tx, storage_locked_rx) = mpsc:: channel ( ) ;
9607+ let ( release_storage_tx, release_storage_rx) = mpsc:: channel ( ) ;
9608+ let storage_thread = std:: thread:: spawn ( move || {
9609+ let storage_guard = backend. blockchain . storage . write ( ) ;
9610+ storage_locked_tx. send ( ( ) ) . unwrap ( ) ;
9611+ release_storage_rx. recv ( ) . unwrap ( ) ;
9612+ drop ( storage_guard) ;
9613+ } ) ;
9614+ storage_locked_rx. recv ( ) . unwrap ( ) ;
9615+ drop ( db_guard) ;
9616+ let mining = tokio:: spawn ( mining) ;
9617+ let ( exit, output, _, _) = call. await . unwrap ( ) ;
9618+
9619+ // `with_pending_block` builds its environment from this snapshot before reading canonical
9620+ // storage. It must already contain the fee derived for block two while mining is paused.
9621+ let pending_env = api. backend . next_evm_env ( ) ;
9622+ assert_eq ! ( pending_env. block_env. number, U256 :: from( 2 ) ) ;
9623+ assert_eq ! ( pending_env. block_env. basefee, 875_175_000 ) ;
9624+
9625+ release_storage_tx. send ( ( ) ) . unwrap ( ) ;
9626+ storage_thread. join ( ) . unwrap ( ) ;
9627+ mining. await . unwrap ( ) . unwrap ( ) ;
9628+
9629+ assert_eq ! ( exit, InstructionResult :: Return ) ;
9630+ let Some ( Output :: Call ( output) ) = output else { panic ! ( "call did not return data" ) } ;
9631+ assert_eq ! ( output. len( ) , 64 ) ;
9632+ let balance = U256 :: from_be_slice ( & output[ ..32 ] ) ;
9633+ let block_number = U256 :: from_be_slice ( & output[ 32 ..] ) ;
9634+ assert_eq ! ( ( balance, block_number) , ( U256 :: from( 1 ) , U256 :: from( 1 ) ) ) ;
9635+ }
9636+
95629637 struct CacheFlushingDb ( BlockchainDb ) ;
95639638
95649639 impl Drop for CacheFlushingDb {
0 commit comments