1use std::{
12 future::Future,
13 pin::Pin,
14 sync::{Arc, OnceLock},
15};
16
17use alloy_consensus::BlockHeader;
18use arb_payload::ArbEngineTypes;
19use arb_primitives::ArbPrimitives;
20use futures::{FutureExt, StreamExt, stream::FusedStream, stream_select};
21use reth_chainspec::{EthChainSpec, EthereumHardforks};
22use reth_engine_tree::{
23 chain::{ChainEvent, FromOrchestrator},
24 engine::{EngineApiKind, EngineApiRequest, EngineRequestHandler},
25 tree::TreeConfig,
26};
27use reth_engine_util::EngineMessageStreamExt;
28use reth_exex::ExExManagerHandle;
29use reth_network::{NetworkSyncUpdater, SyncState, types::BlockRangeUpdate};
30use reth_network_api::BlockDownloaderProvider;
31use reth_node_api::{
32 BuiltPayload, ConsensusEngineHandle, FullNodeTypes, NodeTypes, NodeTypesWithDBAdapter,
33};
34use reth_node_builder::{
35 AddOns, AddOnsContext, FullNode, LaunchContext, LaunchNode, NodeAdapter,
36 NodeBuilderWithComponents, NodeComponents, NodeComponentsBuilder, NodeHandle, NodeTypesAdapter,
37 common::{Attached, LaunchContextWith, WithConfigs},
38 hooks::NodeHooks,
39 rpc::{EngineShutdown, EngineValidatorAddOn, EngineValidatorBuilder, RethRpcAddOns, RpcHandle},
40 setup::build_networked_pipeline,
41};
42use reth_node_core::{
43 dirs::{ChainPath, DataDirPath},
44 exit::NodeExitFuture,
45 primitives::Head,
46};
47use reth_node_events::node;
48use reth_provider::{
49 BlockNumReader, StorageSettingsCache,
50 providers::{BlockchainProvider, NodeTypesForProvider},
51};
52use reth_tasks::TaskExecutor;
53use reth_tokio_util::EventSender;
54use reth_tracing::tracing::{debug, error, info};
55use reth_trie_db::ChangesetCache;
56use tokio::sync::{mpsc::unbounded_channel, oneshot};
57use tokio_stream::wrappers::UnboundedReceiverStream;
58
59use crate::{
60 engine::{TreeSender, build_arb_engine_orchestrator},
61 error::LauncherError,
62};
63
64static TREE_SENDER: OnceLock<TreeSender<ArbEngineTypes, ArbPrimitives>> = OnceLock::new();
65static ENGINE_HANDLE: OnceLock<ConsensusEngineHandle<ArbEngineTypes>> = OnceLock::new();
66static FLUSH_HANDLE: OnceLock<FlushHandle> = OnceLock::new();
67static PARALLEL_STATE_ROOT_FN: OnceLock<ParallelStateRootFn> = OnceLock::new();
68
69pub enum PersistenceRequest {
71 Flush(FlushRequest),
72 Unwind {
73 target: u64,
74 done: crossbeam_channel::Sender<Result<(), LauncherError>>,
75 },
76}
77
78pub struct FlushRequest {
80 pub blocks: Vec<reth_chain_state::ExecutedBlock<ArbPrimitives>>,
81 pub last_num_hash: alloy_eips::BlockNumHash,
82}
83
84pub struct FlushResult {
86 pub last_num_hash: alloy_eips::BlockNumHash,
87 pub count: usize,
88 pub duration: std::time::Duration,
89}
90
91struct FlushHandle {
93 sender: std::sync::mpsc::Sender<PersistenceRequest>,
94 result_rx: crossbeam_channel::Receiver<FlushResult>,
95 flush_done: Arc<tokio::sync::Notify>,
96}
97
98type ParallelStateRootFn = Box<
100 dyn Fn(
101 Arc<reth_trie_common::TrieInputSorted>,
102 reth_trie_common::prefix_set::TriePrefixSets,
103 )
104 -> Result<(alloy_primitives::B256, reth_trie::updates::TrieUpdates), LauncherError>
105 + Send
106 + Sync,
107>;
108
109pub fn tree_sender() -> Option<&'static TreeSender<ArbEngineTypes, ArbPrimitives>> {
110 TREE_SENDER.get()
111}
112
113pub fn engine_handle() -> Option<&'static ConsensusEngineHandle<ArbEngineTypes>> {
114 ENGINE_HANDLE.get()
115}
116
117pub fn start_flush(request: FlushRequest) {
119 if let Some(handle) = FLUSH_HANDLE.get()
120 && let Err(e) = handle.sender.send(PersistenceRequest::Flush(request))
121 {
122 error!(target: "reth::cli", "Failed to send flush request: {e}");
123 }
124}
125
126pub fn start_unwind(target: u64) -> Option<crossbeam_channel::Receiver<Result<(), LauncherError>>> {
131 let handle = FLUSH_HANDLE.get()?;
132 let (done_tx, done_rx) = crossbeam_channel::bounded(1);
133 if let Err(e) = handle.sender.send(PersistenceRequest::Unwind {
134 target,
135 done: done_tx,
136 }) {
137 error!(target: "reth::cli", "Failed to send unwind request: {e}");
138 return None;
139 }
140 Some(done_rx)
141}
142
143pub fn try_flush_result() -> Option<FlushResult> {
145 FLUSH_HANDLE
146 .get()
147 .and_then(|handle| handle.result_rx.try_recv().ok())
148}
149
150pub fn flush_notifier() -> Option<Arc<tokio::sync::Notify>> {
152 FLUSH_HANDLE.get().map(|handle| handle.flush_done.clone())
153}
154
155pub fn compute_parallel_state_root(
156 overlay: Arc<reth_trie_common::TrieInputSorted>,
157 prefix_sets: reth_trie_common::prefix_set::TriePrefixSets,
158) -> Result<(alloy_primitives::B256, reth_trie::updates::TrieUpdates), LauncherError> {
159 let f = PARALLEL_STATE_ROOT_FN
160 .get()
161 .ok_or(LauncherError::ParallelStateRootNotInitialized)?;
162 f(overlay, prefix_sets)
163}
164
165#[derive(Debug)]
170pub struct ArbEngineLauncher {
171 pub ctx: LaunchContext,
172 pub engine_tree_config: TreeConfig,
173}
174
175impl ArbEngineLauncher {
176 pub const fn new(
177 task_executor: TaskExecutor,
178 data_dir: ChainPath<DataDirPath>,
179 engine_tree_config: TreeConfig,
180 ) -> Self {
181 Self {
182 ctx: LaunchContext::new(task_executor, data_dir),
183 engine_tree_config,
184 }
185 }
186
187 async fn launch_node<T, CB, AO>(
190 self,
191 target: NodeBuilderWithComponents<T, CB, AO>,
192 ) -> eyre::Result<NodeHandle<NodeAdapter<T, CB::Components>, AO>>
193 where
194 T: FullNodeTypes<
195 Types: NodeTypesForProvider<Payload = ArbEngineTypes, Primitives = ArbPrimitives>,
196 Provider = BlockchainProvider<
197 NodeTypesWithDBAdapter<<T as FullNodeTypes>::Types, <T as FullNodeTypes>::DB>,
198 >,
199 >,
200 CB: NodeComponentsBuilder<T>,
201 AO: RethRpcAddOns<NodeAdapter<T, CB::Components>>
202 + EngineValidatorAddOn<NodeAdapter<T, CB::Components>>,
203 {
204 let Self {
205 ctx,
206 engine_tree_config,
207 } = self;
208 let NodeBuilderWithComponents {
209 adapter: NodeTypesAdapter { database },
210 components_builder,
211 add_ons:
212 AddOns {
213 hooks,
214 exexs: installed_exex,
215 add_ons,
216 },
217 config,
218 } = target;
219 let NodeHooks {
220 on_component_initialized,
221 on_node_started,
222 ..
223 } = hooks;
224
225 let changeset_cache = ChangesetCache::new();
226
227 let ctx = ctx
228 .with_configured_globals(engine_tree_config.reserved_cpu_cores())
229 .with_loaded_toml_config(config)?
230 .with_resolved_peers()?
231 .attach(database.clone())
232 .with_adjusted_configs()
233 .with_provider_factory::<_, <CB::Components as NodeComponents<T>>::Evm>(
234 changeset_cache.clone(),
235 )
236 .await?
237 .inspect(|_| {
238 info!(target: "reth::cli", "Database opened");
239 })
240 .with_prometheus_server()
241 .await?
242 .inspect(|this| {
243 debug!(target: "reth::cli", chain=%this.chain_id(), genesis=?this.genesis_hash(), "Initializing genesis");
244 })
245 .with_genesis()?
246 .inspect(
247 |this: &LaunchContextWith<
248 Attached<WithConfigs<<T::Types as NodeTypes>::ChainSpec>, _>,
249 >| {
250 info!(target: "reth::cli", "\n{}", this.chain_spec().display_hardforks());
251 let settings = this.provider_factory().cached_storage_settings();
252 info!(target: "reth::cli", ?settings, "Loaded storage settings");
253 },
254 )
255 .with_metrics_task()
256 .with_blockchain_db::<T, _>(move |provider_factory| {
257 Ok(BlockchainProvider::new(provider_factory)?)
258 })?
259 .with_components(components_builder, on_component_initialized)
260 .await?;
261
262 let maybe_exex_manager_handle = ctx.launch_exex(installed_exex).await?;
263
264 let network_handle = ctx.components().network().clone();
265 let network_client = network_handle.fetch_client().await?;
266 let (consensus_engine_tx, consensus_engine_rx) = unbounded_channel();
267
268 let node_config = ctx.node_config();
269
270 network_handle.update_sync_state(SyncState::Syncing);
271
272 let max_block = ctx.max_block(network_client.clone()).await?;
273
274 let static_file_producer = ctx.static_file_producer();
275 let static_file_producer_events = static_file_producer.lock().events();
276 info!(target: "reth::cli", "StaticFileProducer initialized");
277
278 let consensus = Arc::new(ctx.components().consensus().clone());
279
280 let pipeline = build_networked_pipeline(
281 &ctx.toml_config().stages,
282 network_client.clone(),
283 consensus.clone(),
284 ctx.provider_factory().clone(),
285 ctx.task_executor(),
286 ctx.sync_metrics_tx(),
287 ctx.prune_config(),
288 max_block,
289 static_file_producer,
290 ctx.components().evm_config().clone(),
291 maybe_exex_manager_handle
292 .clone()
293 .unwrap_or_else(ExExManagerHandle::empty),
294 ctx.era_import_source(),
295 )?;
296
297 pipeline.move_to_static_files()?;
298
299 let pipeline_events = pipeline.events();
300
301 let mut pruner_builder = ctx.pruner_builder();
302 if let Some(exex_manager_handle) = &maybe_exex_manager_handle {
303 pruner_builder =
304 pruner_builder.finished_exex_height(exex_manager_handle.finished_height());
305 }
306 let pruner = pruner_builder.build_with_provider_factory(ctx.provider_factory().clone());
307 let pruner_events = pruner.events();
308 info!(target: "reth::cli", prune_config=?ctx.prune_config(), "Pruner initialized");
309
310 let event_sender = EventSender::default();
311
312 let beacon_engine_handle = ConsensusEngineHandle::new(consensus_engine_tx.clone());
313
314 let jwt_secret = ctx.auth_jwt_secret()?;
315
316 let add_ons_ctx = AddOnsContext {
317 node: ctx.node_adapter().clone(),
318 config: ctx.node_config(),
319 beacon_engine_handle: beacon_engine_handle.clone(),
320 jwt_secret,
321 engine_events: event_sender.clone(),
322 };
323 let validator_builder = add_ons.engine_validator_builder();
324
325 let engine_validator = validator_builder
326 .clone()
327 .build_tree_validator(
328 &add_ons_ctx,
329 engine_tree_config.clone(),
330 changeset_cache.clone(),
331 )
332 .await?;
333
334 let consensus_engine_stream = UnboundedReceiverStream::from(consensus_engine_rx)
335 .maybe_skip_fcu(node_config.debug.skip_fcu)
336 .maybe_skip_new_payload(node_config.debug.skip_new_payload)
337 .maybe_reorg(
338 ctx.blockchain_db().clone(),
339 ctx.components().evm_config().clone(),
340 || async {
341 let reorg_cache = ChangesetCache::new();
342 validator_builder
343 .build_tree_validator(&add_ons_ctx, engine_tree_config.clone(), reorg_cache)
344 .await
345 },
346 node_config.debug.reorg_frequency,
347 node_config.debug.reorg_depth,
348 )
349 .await?
350 .maybe_store_messages(node_config.debug.engine_api_store.clone());
351
352 let engine_kind = if ctx.chain_spec().is_optimism() {
353 EngineApiKind::OpStack
354 } else {
355 EngineApiKind::Ethereum
356 };
357
358 {
362 use reth_provider::{DatabaseProviderFactory, SaveBlocksMode};
363 use reth_storage_api::{BlockExecutionWriter, DBProvider};
364
365 let pf = ctx.provider_factory().clone();
366 let (req_tx, req_rx) = std::sync::mpsc::channel::<PersistenceRequest>();
367 let (res_tx, res_rx) = crossbeam_channel::bounded::<FlushResult>(1);
368 let flush_done = Arc::new(tokio::sync::Notify::new());
369 let flush_done_thread = flush_done.clone();
370
371 std::thread::Builder::new()
372 .name("arb-persistence".into())
373 .spawn(move || {
374 while let Ok(req) = req_rx.recv() {
375 match req {
376 PersistenceRequest::Flush(flush) => {
377 let start = std::time::Instant::now();
378 let count = flush.blocks.len();
379 let last = flush.last_num_hash;
380
381 let result = (|| -> Result<(), LauncherError> {
382 let provider_rw = pf.database_provider_rw()?;
383 provider_rw.save_blocks(flush.blocks, SaveBlocksMode::Full)?;
384 provider_rw.commit()?;
385 Ok(())
386 })();
387
388 match result {
389 Ok(()) => {
390 let _ = res_tx.send(FlushResult {
391 last_num_hash: last,
392 count,
393 duration: start.elapsed(),
394 });
395 }
396 Err(e) => {
397 error!(target: "reth::cli", "Background flush failed: {e}");
398 let _ = res_tx.send(FlushResult {
399 last_num_hash: last,
400 count: 0, duration: start.elapsed(),
402 });
403 }
404 }
405 flush_done_thread.notify_one();
406 }
407 PersistenceRequest::Unwind { target, done } => {
408 let start = std::time::Instant::now();
409 let result = (|| -> Result<(), LauncherError> {
410 let provider_rw = pf.database_provider_rw()?;
411 provider_rw.remove_block_and_execution_above(target)?;
412 provider_rw.commit()?;
413 Ok(())
414 })();
415 match &result {
416 Ok(()) => info!(
417 target: "reth::cli",
418 target,
419 duration_ms = start.elapsed().as_millis(),
420 "Persisted unwind complete"
421 ),
422 Err(e) => error!(
423 target: "reth::cli",
424 target,
425 err = %e,
426 "Persisted unwind failed"
427 ),
428 }
429 let _ = done.send(result);
430 }
431 }
432 }
433 })
434 .expect("failed to spawn persistence thread");
435
436 let _ = FLUSH_HANDLE.set(FlushHandle {
437 sender: req_tx,
438 result_rx: res_rx,
439 flush_done,
440 });
441 }
442
443 {
444 use reth_chain_state::{
445 AnchoredTrieInput, ComputedTrieData, DeferredTrieData, LazyOverlay,
446 };
447 use reth_provider::providers::OverlayStateProviderFactory;
448 use reth_trie_parallel::root::ParallelStateRoot;
449
450 let pf = ctx.provider_factory().clone();
451 let runtime = ctx.task_executor().clone();
452 let changeset_cache_for_root = changeset_cache.clone();
453
454 let state_root_fn: ParallelStateRootFn = Box::new(move |overlay, prefix_sets| {
455 let anchor_hash = alloy_primitives::B256::ZERO;
456 let computed = ComputedTrieData {
457 hashed_state: Arc::clone(&overlay.state),
458 trie_updates: Arc::clone(&overlay.nodes),
459 anchored_trie_input: Some(AnchoredTrieInput {
460 anchor_hash,
461 trie_input: overlay,
462 }),
463 };
464 let lazy = LazyOverlay::new(anchor_hash, vec![DeferredTrieData::ready(computed)]);
465 let factory =
466 OverlayStateProviderFactory::new(pf.clone(), changeset_cache_for_root.clone())
467 .with_lazy_overlay(Some(lazy));
468
469 ParallelStateRoot::new(factory, prefix_sets, runtime.clone())
470 .incremental_root_with_updates()
471 .map_err(LauncherError::from)
472 });
473 let _ = PARALLEL_STATE_ROOT_FN.set(state_root_fn);
474 }
475
476 let (mut orchestrator, arb_tree_sender) = build_arb_engine_orchestrator(
477 engine_kind,
478 consensus.clone(),
479 network_client.clone(),
480 Box::pin(consensus_engine_stream),
481 pipeline,
482 ctx.task_executor().clone(),
483 ctx.provider_factory().clone(),
484 ctx.blockchain_db().clone(),
485 pruner,
486 ctx.components().payload_builder_handle().clone(),
487 engine_validator,
488 engine_tree_config,
489 ctx.sync_metrics_tx(),
490 ctx.components().evm_config().clone(),
491 changeset_cache,
492 );
493
494 let _ = TREE_SENDER.set(arb_tree_sender);
495 let _ = ENGINE_HANDLE.set(beacon_engine_handle.clone());
496 info!(target: "reth::cli", "Arbitrum engine tree sender and handle captured");
497
498 info!(target: "reth::cli", "Consensus engine initialized");
499
500 #[allow(clippy::needless_continue)]
501 let events = stream_select!(
502 event_sender.new_listener().map(Into::into),
503 pipeline_events.map(Into::into),
504 ctx.consensus_layer_events(),
505 pruner_events.map(Into::into),
506 static_file_producer_events.map(Into::into),
507 );
508
509 ctx.task_executor().spawn_critical_task(
510 "events task",
511 Box::pin(node::handle_events(
512 Some(Box::new(ctx.components().network().clone())),
513 Some(ctx.head().number),
514 events,
515 )),
516 );
517
518 let RpcHandle {
519 rpc_server_handles,
520 rpc_registry,
521 engine_events,
522 beacon_engine_handle,
523 engine_shutdown: _,
524 } = add_ons.launch_add_ons(add_ons_ctx).await?;
525
526 let (engine_shutdown, shutdown_rx) = EngineShutdown::new();
527
528 let initial_target = ctx.initial_backfill_target()?;
529 let mut built_payloads = ctx
530 .components()
531 .payload_builder_handle()
532 .subscribe()
533 .await
534 .map_err(|e| eyre::eyre!("Failed to subscribe to payload builder events: {:?}", e))?
535 .into_built_payload_stream()
536 .fuse();
537
538 let chainspec = ctx.chain_spec();
539 let provider = ctx.blockchain_db().clone();
540 let (exit, rx) = oneshot::channel();
541 let terminate_after_backfill = ctx.terminate_after_initial_backfill();
542 let startup_sync_state_idle = ctx.node_config().debug.startup_sync_state_idle;
543
544 info!(target: "reth::cli", "Starting consensus engine");
545 let consensus_engine = async move {
546 if let Some(initial_target) = initial_target {
547 debug!(target: "reth::cli", %initial_target, "start backfill sync");
548 orchestrator.start_backfill_sync(initial_target);
549 } else if startup_sync_state_idle {
550 network_handle.update_sync_state(SyncState::Idle);
551 }
552
553 let mut res = Ok(());
554 let mut shutdown_rx = shutdown_rx.fuse();
555
556 loop {
557 tokio::select! {
558 event = orchestrator.next() => {
559 let Some(event) = event else { break };
560 debug!(target: "reth::cli", "Event: {event}");
561 match event {
562 ChainEvent::BackfillSyncFinished => {
563 if terminate_after_backfill {
564 debug!(target: "reth::cli", "Terminating after initial backfill");
565 break
566 }
567 if startup_sync_state_idle {
568 network_handle.update_sync_state(SyncState::Idle);
569 }
570 }
571 ChainEvent::BackfillSyncStarted => {
572 network_handle.update_sync_state(SyncState::Syncing);
573 }
574 ChainEvent::FatalError => {
575 error!(target: "reth::cli", "Fatal error in consensus engine");
576 res = Err(eyre::eyre!("Fatal error in consensus engine"));
577 break
578 }
579 ChainEvent::Handler(ev) => {
580 if let Some(head) = ev.canonical_header() {
581 network_handle.update_sync_state(SyncState::Idle);
582 let head_block = Head {
583 number: head.number(),
584 hash: head.hash(),
585 difficulty: head.difficulty(),
586 timestamp: head.timestamp(),
587 total_difficulty: chainspec.final_paris_total_difficulty()
588 .filter(|_| chainspec.is_paris_active_at_block(head.number()))
589 .unwrap_or_default(),
590 };
591 network_handle.update_status(head_block);
592
593 let updated = BlockRangeUpdate {
594 earliest: provider.earliest_block_number().unwrap_or_default(),
595 latest: head.number(),
596 latest_hash: head.hash(),
597 };
598 network_handle.update_block_range(updated);
599 }
600 event_sender.notify(ev);
601 }
602 }
603 }
604 payload = built_payloads.select_next_some(), if !built_payloads.is_terminated() => {
605 if let Some(executed_block) = payload.executed_block() {
606 debug!(target: "reth::cli", block=?executed_block.recovered_block.num_hash(), "inserting built payload");
607 orchestrator.handler_mut().handler_mut().on_event(EngineApiRequest::InsertExecutedBlock(executed_block.into_executed_payload()).into());
608 }
609 }
610 shutdown_req = &mut shutdown_rx => {
611 if let Ok(req) = shutdown_req {
612 debug!(target: "reth::cli", "received engine shutdown request");
613 orchestrator.handler_mut().handler_mut().on_event(
614 FromOrchestrator::Terminate { tx: req.done_tx }.into()
615 );
616 }
617 }
618 }
619 }
620
621 let _ = exit.send(res);
622 };
623 ctx.task_executor()
624 .spawn_critical_task("consensus engine", Box::pin(consensus_engine));
625
626 let engine_events_for_ethstats = engine_events.new_listener();
627
628 let full_node = FullNode {
629 evm_config: ctx.components().evm_config().clone(),
630 pool: ctx.components().pool().clone(),
631 network: ctx.components().network().clone(),
632 provider: ctx.node_adapter().provider.clone(),
633 payload_builder_handle: ctx.components().payload_builder_handle().clone(),
634 task_executor: ctx.task_executor().clone(),
635 config: ctx.node_config().clone(),
636 data_dir: ctx.data_dir().clone(),
637 add_ons_handle: RpcHandle {
638 rpc_server_handles,
639 rpc_registry,
640 engine_events,
641 beacon_engine_handle,
642 engine_shutdown,
643 },
644 };
645 on_node_started.on_event(FullNode::clone(&full_node))?;
646
647 ctx.spawn_ethstats(engine_events_for_ethstats).await?;
648
649 let handle = NodeHandle {
650 node_exit_future: NodeExitFuture::new(
651 async { rx.await? },
652 full_node.config.debug.terminate,
653 ),
654 node: full_node,
655 };
656
657 Ok(handle)
658 }
659}
660
661impl<T, CB, AO> LaunchNode<NodeBuilderWithComponents<T, CB, AO>> for ArbEngineLauncher
662where
663 T: FullNodeTypes<
664 Types: NodeTypesForProvider<Payload = ArbEngineTypes, Primitives = ArbPrimitives>,
665 Provider = BlockchainProvider<
666 NodeTypesWithDBAdapter<<T as FullNodeTypes>::Types, <T as FullNodeTypes>::DB>,
667 >,
668 >,
669 CB: NodeComponentsBuilder<T> + 'static,
670 AO: RethRpcAddOns<NodeAdapter<T, CB::Components>>
671 + EngineValidatorAddOn<NodeAdapter<T, CB::Components>>
672 + 'static,
673{
674 type Node = NodeHandle<NodeAdapter<T, CB::Components>, AO>;
675 type Future = Pin<Box<dyn Future<Output = eyre::Result<Self::Node>> + Send>>;
676
677 fn launch_node(self, target: NodeBuilderWithComponents<T, CB, AO>) -> Self::Future {
678 Box::pin(self.launch_node(target))
679 }
680}