@@ -13,6 +13,7 @@ use crate::tree::{
1313 sparse_trie:: SparseTrieTask ,
1414 StateProviderBuilder , TreeConfig ,
1515} ;
16+ use alloy_eips:: eip1898:: BlockWithParent ;
1617use alloy_evm:: { block:: StateChangeSource , ToTxEnv } ;
1718use alloy_primitives:: B256 ;
1819use crossbeam_channel:: Sender as CrossbeamSender ;
@@ -464,6 +465,54 @@ where
464465 cleared_sparse_trie. lock ( ) . replace ( cleared_trie) ;
465466 } ) ;
466467 }
468+
469+ /// Updates the execution cache with the post-execution state from an inserted block.
470+ ///
471+ /// This is used when blocks are inserted directly (e.g., locally built blocks by sequencers)
472+ /// to ensure the cache remains warm for subsequent block execution.
473+ ///
474+ /// The cache enables subsequent blocks to reuse account, storage, and bytecode data without
475+ /// hitting the database, maintaining performance consistency.
476+ pub ( crate ) fn on_inserted_executed_block (
477+ & self ,
478+ block_with_parent : BlockWithParent ,
479+ bundle_state : & BundleState ,
480+ ) {
481+ self . execution_cache . update_with_guard ( |cached| {
482+ if cached. as_ref ( ) . is_some_and ( |c| c. executed_block_hash ( ) != block_with_parent. parent ) {
483+ debug ! (
484+ target: "engine::caching" ,
485+ parent_hash = %block_with_parent. parent,
486+ "Cannot find cache for parent hash, skip updating cache with new state for inserted executed block" ,
487+ ) ;
488+ return ;
489+ }
490+
491+ // Take existing cache (if any) or create fresh caches
492+ let ( caches, cache_metrics) = match cached. take ( ) {
493+ Some ( existing) => {
494+ existing. split ( )
495+ }
496+ None => (
497+ ExecutionCacheBuilder :: default ( ) . build_caches ( self . cross_block_cache_size ) ,
498+ CachedStateMetrics :: zeroed ( ) ,
499+ ) ,
500+ } ;
501+
502+ // Insert the block's bundle state into cache
503+ let new_cache = SavedCache :: new ( block_with_parent. block . hash , caches, cache_metrics) ;
504+ if new_cache. cache ( ) . insert_state ( bundle_state) . is_err ( ) {
505+ * cached = None ;
506+ debug ! ( target: "engine::caching" , "cleared execution cache on update error" ) ;
507+ return ;
508+ }
509+ new_cache. update_metrics ( ) ;
510+
511+ // Replace with the updated cache
512+ * cached = Some ( new_cache) ;
513+ debug ! ( target: "engine::caching" , ?block_with_parent, "Updated execution cache for inserted block" ) ;
514+ } ) ;
515+ }
467516}
468517
469518/// Handle to all the spawned tasks.
@@ -703,6 +752,7 @@ mod tests {
703752 precompile_cache:: PrecompileCacheMap ,
704753 StateProviderBuilder , TreeConfig ,
705754 } ;
755+ use alloy_eips:: eip1898:: { BlockNumHash , BlockWithParent } ;
706756 use alloy_evm:: block:: StateChangeSource ;
707757 use rand:: Rng ;
708758 use reth_chainspec:: ChainSpec ;
@@ -716,6 +766,7 @@ mod tests {
716766 test_utils:: create_test_provider_factory_with_chain_spec,
717767 ChainSpecProvider , HashingWriter ,
718768 } ;
769+ use reth_revm:: db:: BundleState ;
719770 use reth_testing_utils:: generators;
720771 use reth_trie:: { test_utils:: state_root, HashedPostState } ;
721772 use revm_primitives:: { Address , HashMap , B256 , KECCAK_EMPTY , U256 } ;
@@ -793,6 +844,70 @@ mod tests {
793844 assert ! ( new_checkout. is_some( ) , "new checkout should succeed after release and update" ) ;
794845 }
795846
847+ #[ test]
848+ fn on_inserted_executed_block_populates_cache ( ) {
849+ let payload_processor = PayloadProcessor :: new (
850+ WorkloadExecutor :: default ( ) ,
851+ EthEvmConfig :: new ( Arc :: new ( ChainSpec :: default ( ) ) ) ,
852+ & TreeConfig :: default ( ) ,
853+ PrecompileCacheMap :: default ( ) ,
854+ ) ;
855+
856+ let parent_hash = B256 :: from ( [ 1u8 ; 32 ] ) ;
857+ let block_hash = B256 :: from ( [ 10u8 ; 32 ] ) ;
858+ let block_with_parent = BlockWithParent {
859+ block : BlockNumHash { hash : block_hash, number : 1 } ,
860+ parent : parent_hash,
861+ } ;
862+ let bundle_state = BundleState :: default ( ) ;
863+
864+ // Cache should be empty initially
865+ assert ! ( payload_processor. execution_cache. get_cache_for( block_hash) . is_none( ) ) ;
866+
867+ // Update cache with inserted block
868+ payload_processor. on_inserted_executed_block ( block_with_parent, & bundle_state) ;
869+
870+ // Cache should now exist for the block hash
871+ let cached = payload_processor. execution_cache . get_cache_for ( block_hash) ;
872+ assert ! ( cached. is_some( ) ) ;
873+ assert_eq ! ( cached. unwrap( ) . executed_block_hash( ) , block_hash) ;
874+ }
875+
876+ #[ test]
877+ fn on_inserted_executed_block_skips_on_parent_mismatch ( ) {
878+ let payload_processor = PayloadProcessor :: new (
879+ WorkloadExecutor :: default ( ) ,
880+ EthEvmConfig :: new ( Arc :: new ( ChainSpec :: default ( ) ) ) ,
881+ & TreeConfig :: default ( ) ,
882+ PrecompileCacheMap :: default ( ) ,
883+ ) ;
884+
885+ // Setup: populate cache with block 1
886+ let block1_hash = B256 :: from ( [ 1u8 ; 32 ] ) ;
887+ payload_processor
888+ . execution_cache
889+ . update_with_guard ( |slot| * slot = Some ( make_saved_cache ( block1_hash) ) ) ;
890+
891+ // Try to insert block 3 with wrong parent (should skip and keep block 1's cache)
892+ let wrong_parent = B256 :: from ( [ 99u8 ; 32 ] ) ;
893+ let block3_hash = B256 :: from ( [ 3u8 ; 32 ] ) ;
894+ let block_with_parent = BlockWithParent {
895+ block : BlockNumHash { hash : block3_hash, number : 3 } ,
896+ parent : wrong_parent,
897+ } ;
898+ let bundle_state = BundleState :: default ( ) ;
899+
900+ payload_processor. on_inserted_executed_block ( block_with_parent, & bundle_state) ;
901+
902+ // Cache should still be for block 1 (unchanged)
903+ let cached = payload_processor. execution_cache . get_cache_for ( block1_hash) ;
904+ assert ! ( cached. is_some( ) , "Original cache should be preserved" ) ;
905+
906+ // Cache for block 3 should not exist
907+ let cached3 = payload_processor. execution_cache . get_cache_for ( block3_hash) ;
908+ assert ! ( cached3. is_none( ) , "New block cache should not be created on mismatch" ) ;
909+ }
910+
796911 fn create_mock_state_updates ( num_accounts : usize , updates_per_account : usize ) -> Vec < EvmState > {
797912 let mut rng = generators:: rng ( ) ;
798913 let all_addresses: Vec < Address > = ( 0 ..num_accounts) . map ( |_| rng. random ( ) ) . collect ( ) ;
0 commit comments