@@ -111,6 +111,134 @@ where
111111 MiningConfig :: from_env ( )
112112 } ;
113113
114+ // Register the sparse-trie state-root spawner, if enabled.
115+ //
116+ // We construct a long-lived `PayloadProcessor` keyed to a fresh `BscEvmConfig`
117+ // (built from chain_spec — same source as the rest of the BSC pipeline). For
118+ // each build job, the registered closure constructs a one-shot
119+ // `OverlayStateProviderFactory` anchored at the parent block hash and calls
120+ // `spawn_state_root` to get a `StateRootHandle`.
121+ //
122+ // `TreeConfig::default()` is used here as a workaround — the engine-launch
123+ // `TreeConfig` (which honors `--engine.*` CLI flags) is not reachable from
124+ // `BscPayloadServiceBuilder`. Default values are reasonable for the sparse-trie
125+ // background task; revisit if performance testing shows we need CLI-tunable
126+ // worker counts or cache sizes.
127+ if mining_config. use_sparse_trie_state_root
128+ && !rust_eth_triedb:: triedb_manager:: is_triedb_active ( )
129+ {
130+ use alloy_consensus:: BlockHeader ;
131+ use reth_chain_state:: LazyOverlay ;
132+ use reth_engine_tree:: tree:: {
133+ payload_processor:: PayloadProcessor , precompile_cache:: PrecompileCacheMap ,
134+ TreeConfig ,
135+ } ;
136+ use reth_provider:: providers:: { OverlayBuilder , OverlayStateProviderFactory } ;
137+ use reth_tasks:: { Runtime , RuntimeBuilder , RuntimeConfig , TokioConfig } ;
138+ use reth_trie_db:: ChangesetCache ;
139+
140+ let chain_spec = Arc :: new ( ctx. config ( ) . chain . clone ( ) . as_ref ( ) . clone ( ) ) ;
141+ let bsc_evm_config = crate :: node:: evm:: config:: BscEvmConfig :: new ( chain_spec) ;
142+ let tree_config = Arc :: new ( TreeConfig :: default ( ) ) ;
143+ let provider = ctx. provider ( ) . clone ( ) ;
144+
145+ // Build a dedicated `reth_tasks::Runtime` for the sparse-trie pools,
146+ // attached to the existing tokio handle so we don't spin up a second tokio
147+ // executor. Rayon pools default to `available_parallelism()`, matching what
148+ // the engine uses for its own PayloadProcessor.
149+ //
150+ // TODO: in a follow-up, share the engine's Runtime instead of constructing
151+ // a parallel one — this currently means two sets of rayon pools competing
152+ // for CPU. Acceptable for a first cut; revisit if perf testing shows
153+ // contention.
154+ let tokio_handle = ctx. task_executor ( ) . handle ( ) . clone ( ) ;
155+ let runtime = RuntimeBuilder :: new (
156+ RuntimeConfig :: default ( ) . with_tokio ( TokioConfig :: existing_handle ( tokio_handle) ) ,
157+ )
158+ . build ( )
159+ . map_err ( |e| eyre:: eyre!( "failed to build sparse-trie Runtime: {e}" ) ) ?;
160+ let _: & Runtime = & runtime; // type-check anchor
161+
162+ let payload_processor = std:: sync:: Arc :: new ( PayloadProcessor :: new (
163+ runtime,
164+ bsc_evm_config,
165+ tree_config. as_ref ( ) ,
166+ PrecompileCacheMap :: default ( ) ,
167+ ) ) ;
168+
169+ let tree_config_for_closure = tree_config. clone ( ) ;
170+ let spawn_fn: crate :: shared:: SparseTrieSpawnFn = std:: sync:: Arc :: new (
171+ move |parent_hash : alloy_primitives:: B256 ,
172+ parent_state_root : alloy_primitives:: B256 | {
173+ // Walk the in-memory canonical chain to find the on-disk anchor.
174+ // Without this, proof workers fail with `BlockHashNotFound` whenever
175+ // the parent hasn't been persisted yet (the common case during fast
176+ // block production with last_persisted_number lagging head).
177+ //
178+ // Mirrors `payload_validator::get_parent_lazy_overlay` from upstream
179+ // reth, which the engine's own PayloadProcessor uses.
180+ //
181+ // `canonical_in_memory_state` is published from main.rs after launch
182+ // (it's only reachable on the concrete BlockchainProvider). If for
183+ // some reason it's not yet set (race during early startup), fall back
184+ // to anchoring at parent_hash directly — proof workers will fail and
185+ // builder will use the synchronous state_root_with_updates path.
186+ let ( anchor_hash, lazy_overlay) = if let Some ( cim) =
187+ crate :: shared:: get_canonical_in_memory_state ( )
188+ {
189+ match cim. state_by_hash ( parent_hash) {
190+ Some ( state) => {
191+ // chain() yields newest-to-oldest including self, exactly
192+ // the order LazyOverlay::new requires.
193+ let blocks: Vec < ExecutedBlock < crate :: BscPrimitives > > =
194+ state. chain ( ) . map ( |bs| bs. block ( ) ) . collect ( ) ;
195+ // Anchor = parent of the oldest in-memory block (= on-disk tip).
196+ let anchor = blocks
197+ . last ( )
198+ . map ( |b| b. recovered_block ( ) . parent_hash ( ) )
199+ . unwrap_or ( parent_hash) ;
200+ ( anchor, Some ( LazyOverlay :: new ( blocks) ) )
201+ }
202+ None => {
203+ // Parent already persisted — anchor directly, no overlay.
204+ ( parent_hash, None )
205+ }
206+ }
207+ } else {
208+ ( parent_hash, None )
209+ } ;
210+
211+ let overlay_builder = OverlayBuilder :: < crate :: BscPrimitives > :: new (
212+ anchor_hash,
213+ ChangesetCache :: default ( ) ,
214+ )
215+ . with_lazy_overlay ( lazy_overlay) ;
216+
217+ let overlay_factory = OverlayStateProviderFactory :: new (
218+ provider. clone ( ) ,
219+ overlay_builder,
220+ ) ;
221+ Some ( payload_processor. spawn_state_root (
222+ overlay_factory,
223+ parent_state_root,
224+ false , // halve_workers
225+ tree_config_for_closure. as_ref ( ) ,
226+ ) )
227+ } ,
228+ ) ;
229+
230+ if crate :: shared:: set_sparse_trie_spawn_fn ( spawn_fn) . is_err ( ) {
231+ tracing:: warn!(
232+ "Sparse-trie spawner already registered, keeping existing one"
233+ ) ;
234+ } else {
235+ info ! (
236+ "Sparse-trie state-root spawner registered \
237+ (use_sparse_trie_state_root=true, triedb=inactive)"
238+ ) ;
239+ }
240+ }
241+
114242 // Skip mining setup if disabled
115243 if !mining_config. is_mining_enabled ( ) {
116244 info ! ( "Mining is disabled in configuration" ) ;
0 commit comments