Skip to content

Commit 72ff711

Browse files
committed
feat: share sparse trie pipeline with payload builder
1 parent 5736e64 commit 72ff711

7 files changed

Lines changed: 280 additions & 48 deletions

File tree

src/node/engine.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,91 @@ 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 reth_engine_tree::tree::{
131+
payload_processor::PayloadProcessor, precompile_cache::PrecompileCacheMap,
132+
TreeConfig,
133+
};
134+
use reth_provider::providers::{OverlayBuilder, OverlayStateProviderFactory};
135+
use reth_tasks::{Runtime, RuntimeBuilder, RuntimeConfig, TokioConfig};
136+
use reth_trie_db::ChangesetCache;
137+
138+
let chain_spec = Arc::new(ctx.config().chain.clone().as_ref().clone());
139+
let bsc_evm_config = crate::node::evm::config::BscEvmConfig::new(chain_spec);
140+
let tree_config = Arc::new(TreeConfig::default());
141+
let provider = ctx.provider().clone();
142+
143+
// Build a dedicated `reth_tasks::Runtime` for the sparse-trie pools,
144+
// attached to the existing tokio handle so we don't spin up a second tokio
145+
// executor. Rayon pools default to `available_parallelism()`, matching what
146+
// the engine uses for its own PayloadProcessor.
147+
//
148+
// TODO: in a follow-up, share the engine's Runtime instead of constructing
149+
// a parallel one — this currently means two sets of rayon pools competing
150+
// for CPU. Acceptable for a first cut; revisit if perf testing shows
151+
// contention.
152+
let tokio_handle = ctx.task_executor().handle().clone();
153+
let runtime = RuntimeBuilder::new(
154+
RuntimeConfig::default().with_tokio(TokioConfig::existing_handle(tokio_handle)),
155+
)
156+
.build()
157+
.map_err(|e| eyre::eyre!("failed to build sparse-trie Runtime: {e}"))?;
158+
let _: &Runtime = &runtime; // type-check anchor
159+
160+
let payload_processor = std::sync::Arc::new(PayloadProcessor::new(
161+
runtime,
162+
bsc_evm_config,
163+
tree_config.as_ref(),
164+
PrecompileCacheMap::default(),
165+
));
166+
167+
let tree_config_for_closure = tree_config.clone();
168+
let spawn_fn: crate::shared::SparseTrieSpawnFn = std::sync::Arc::new(
169+
move |parent_hash: alloy_primitives::B256,
170+
parent_state_root: alloy_primitives::B256| {
171+
let overlay_factory = OverlayStateProviderFactory::new(
172+
provider.clone(),
173+
OverlayBuilder::<crate::BscPrimitives>::new(
174+
parent_hash,
175+
ChangesetCache::default(),
176+
),
177+
);
178+
Some(payload_processor.spawn_state_root(
179+
overlay_factory,
180+
parent_state_root,
181+
false, // halve_workers
182+
tree_config_for_closure.as_ref(),
183+
))
184+
},
185+
);
186+
187+
if crate::shared::set_sparse_trie_spawn_fn(spawn_fn).is_err() {
188+
tracing::warn!(
189+
"Sparse-trie spawner already registered, keeping existing one"
190+
);
191+
} else {
192+
info!(
193+
"Sparse-trie state-root spawner registered \
194+
(use_sparse_trie_state_root=true, triedb=inactive)"
195+
);
196+
}
197+
}
198+
114199
// Skip mining setup if disabled
115200
if !mining_config.is_mining_enabled() {
116201
info!("Mining is disabled in configuration");

src/node/evm/builder.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,21 @@ where
228228
"Calculated state root using triedb"
229229
);
230230
(new_root, TrieUpdates::default(), Some(new_difflayer))
231-
} else if let Some((root, updates)) = self.precomputed_state_root.take() {
231+
} else if let Some((root, updates)) = self
232+
.ctx
233+
.state_root_precomputed_sink
234+
.as_ref()
235+
.and_then(|sink| sink.lock().unwrap().take())
236+
.or_else(|| self.precomputed_state_root.take())
237+
{
232238
// Fast path: sparse-trie background task already computed the root concurrently
233239
// with execution. See `crate::shared::spawn_sparse_trie_state_root` and reth 2.0
234240
// `--engine.share-sparse-trie-with-payload-builder` semantics for the upstream
235241
// mechanism we mirror.
242+
//
243+
// Preferred source is the sink on `self.ctx` (filled by the payload layer
244+
// post-exec); the field on `Self` is a fallback that `fn finish` populates
245+
// when callers route through the trait method.
236246
tracing::debug!(
237247
target: "bsc::builder",
238248
parent_hash = %self.parent.hash(),

src/node/evm/config.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ pub struct BscNextBlockEnvAttributes {
6969
/// Sink for transporting `turn_length` from builder to payload layer without writing to
7070
/// TURN_LENGTH_CACHE prematurely.
7171
pub turn_length_sink: Option<Arc<Mutex<Option<u8>>>>,
72+
/// Sink for precomputed `(state_root, trie_updates)` from a sparse-trie background
73+
/// task. Filled by payload layer between exec and `finish_with_difflayer` so the
74+
/// builder's MDBX branch can skip the blocking `state_root_with_updates` call. See
75+
/// [`BscBlockExecutionCtx::state_root_precomputed_sink`] for full semantics.
76+
pub state_root_precomputed_sink: Option<
77+
Arc<Mutex<Option<(alloy_primitives::B256, reth_trie_common::updates::TrieUpdates)>>>,
78+
>,
7279
}
7380

7481
impl<H: BlockHeader> BuildPendingEnv<H> for BscNextBlockEnvAttributes {
@@ -79,6 +86,7 @@ impl<H: BlockHeader> BuildPendingEnv<H> for BscNextBlockEnvAttributes {
7986
triedb_prefetcher: None,
8087
validator_cache_sink: None,
8188
turn_length_sink: None,
89+
state_root_precomputed_sink: None,
8290
}
8391
}
8492
}
@@ -135,6 +143,17 @@ pub struct BscBlockExecutionCtx<'a> {
135143
pub validator_cache_sink: Option<ValidatorCacheSink>,
136144
/// Sink for `turn_length` — same lifecycle as `validator_cache_sink`.
137145
pub turn_length_sink: Option<Arc<Mutex<Option<u8>>>>,
146+
/// Sink for a precomputed `(state_root, trie_updates)` from a sparse-trie background
147+
/// task (reth 2.0 mechanism).
148+
///
149+
/// Write direction is **reversed** vs the other sinks: the payload layer fills this
150+
/// **before** calling `finish_with_difflayer`, and the builder's MDBX branch reads
151+
/// it to skip the synchronous `state_root_with_updates` call. `None` in the bid
152+
/// simulator path and when the `--mining.use-sparse-trie-state-root` flag is off,
153+
/// triggering the legacy state-root path.
154+
pub state_root_precomputed_sink: Option<
155+
Arc<Mutex<Option<(alloy_primitives::B256, reth_trie_common::updates::TrieUpdates)>>>,
156+
>,
138157
}
139158

140159
impl<'a> BscBlockExecutionCtx<'a> {
@@ -440,6 +459,7 @@ where
440459
triedb_prefetcher: None,
441460
validator_cache_sink: None,
442461
turn_length_sink: None,
462+
state_root_precomputed_sink: None,
443463
})
444464
}
445465

@@ -466,6 +486,7 @@ where
466486
triedb_prefetcher: attributes.triedb_prefetcher,
467487
validator_cache_sink: attributes.validator_cache_sink,
468488
turn_length_sink: attributes.turn_length_sink,
489+
state_root_precomputed_sink: attributes.state_root_precomputed_sink,
469490
})
470491
}
471492

@@ -541,6 +562,7 @@ where
541562
triedb_prefetcher: None,
542563
validator_cache_sink: None,
543564
turn_length_sink: None,
565+
state_root_precomputed_sink: None,
544566
})
545567
}
546568

src/node/miner/bid_simulator.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,9 @@ where
459459
triedb_prefetcher,
460460
validator_cache_sink: Some(bid_validator_cache_sink.clone()),
461461
turn_length_sink: Some(bid_turn_length_sink.clone()),
462+
// Bid simulation does not run alongside a sparse-trie task —
463+
// builder will fall through to state_root_with_updates.
464+
state_root_precomputed_sink: None,
462465
},
463466
)
464467
.map_err(PayloadBuilderError::other)

src/node/miner/bsc_miner.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ where
733733
// Filled in by BscPayloadJob::start when sparse-trie state-root is enabled
734734
// and the engine has registered a spawner. Falls back to legacy path when None.
735735
state_root_precomputed: std::sync::Arc::new(std::sync::Mutex::new(None)),
736+
trie_handle: std::sync::Arc::new(std::sync::Mutex::new(None)),
736737
};
737738

738739
let parent_hash = mining_ctx.parent_header.hash();

0 commit comments

Comments
 (0)