1mod ctrl;
2mod event;
3pub use crate::pipeline::ctrl::ControlFlow;
4use crate::{PipelineTarget, StageCheckpoint, StageId};
5use alloy_primitives::{BlockNumber, B256};
6pub use event::*;
7use futures_util::Future;
8use reth_primitives_traits::constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH;
9use reth_provider::{
10 providers::ProviderNodeTypes, BlockHashReader, BlockNumReader, ChainStateBlockReader,
11 ChainStateBlockWriter, DBProvider, DatabaseProviderFactory, ProviderFactory,
12 PruneCheckpointReader, StageCheckpointReader, StageCheckpointWriter, StaticFileProviderFactory,
13 StorageSettingsCache,
14};
15use reth_prune::PrunerBuilder;
16use reth_static_file::StaticFileProducer;
17use reth_tokio_util::{EventSender, EventStream};
18use std::{
19 pin::Pin,
20 time::{Duration, Instant},
21};
22use tokio::sync::watch;
23use tracing::*;
24
25mod builder;
26mod progress;
27mod set;
28
29use crate::{
30 BlockErrorKind, ExecInput, ExecOutput, MetricEvent, MetricEventsSender, PipelineError, Stage,
31 StageError, StageExt, UnwindInput,
32};
33pub use builder::*;
34use progress::*;
35use reth_errors::RethResult;
36pub use set::*;
37
38pub(crate) type BoxedStage<DB> = Box<dyn Stage<DB>>;
40
41pub type PipelineFut<N> = Pin<Box<dyn Future<Output = PipelineWithResult<N>> + Send>>;
44
45pub type PipelineWithResult<N> = (Pipeline<N>, Result<ControlFlow, PipelineError>);
47
48#[cfg_attr(doc, aquamarine::aquamarine)]
49pub struct Pipeline<N: ProviderNodeTypes> {
71 provider_factory: ProviderFactory<N>,
73 stages: Vec<BoxedStage<<ProviderFactory<N> as DatabaseProviderFactory>::ProviderRW>>,
75 max_block: Option<BlockNumber>,
77 static_file_producer: StaticFileProducer<ProviderFactory<N>>,
78 event_sender: EventSender<PipelineEvent>,
80 progress: PipelineProgress,
82 tip_tx: Option<watch::Sender<B256>>,
86 metrics_tx: Option<MetricEventsSender>,
87 fail_on_unwind: bool,
90 last_detached_head_unwind_target: Option<B256>,
93 detached_head_attempts: u64,
96}
97
98impl<N: ProviderNodeTypes> Pipeline<N> {
99 pub fn builder() -> PipelineBuilder<<ProviderFactory<N> as DatabaseProviderFactory>::ProviderRW>
101 {
102 PipelineBuilder::default()
103 }
104
105 pub const fn minimum_block_number(&self) -> Option<u64> {
108 self.progress.minimum_block_number
109 }
110
111 #[track_caller]
113 pub fn set_tip(&self, tip: B256) {
114 let _ = self.tip_tx.as_ref().expect("tip sender is set").send(tip).map_err(|_| {
115 warn!(target: "sync::pipeline", "Chain tip channel closed");
116 });
117 }
118
119 pub fn events(&self) -> EventStream<PipelineEvent> {
121 self.event_sender.new_listener()
122 }
123
124 pub fn stage(
126 &mut self,
127 idx: usize,
128 ) -> &mut dyn Stage<<ProviderFactory<N> as DatabaseProviderFactory>::ProviderRW> {
129 &mut self.stages[idx]
130 }
131}
132
133impl<N: ProviderNodeTypes> Pipeline<N> {
134 pub fn register_metrics(&mut self) -> Result<(), PipelineError> {
136 let Some(metrics_tx) = &mut self.metrics_tx else { return Ok(()) };
137 let provider = self.provider_factory.provider()?;
138
139 for stage in &self.stages {
140 let stage_id = stage.id();
141 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
142 stage_id,
143 checkpoint: provider.get_stage_checkpoint(stage_id)?.unwrap_or_default(),
144 max_block_number: None,
145 elapsed: Duration::default(),
146 });
147 }
148 Ok(())
149 }
150
151 #[track_caller]
154 pub fn run_as_fut(mut self, target: Option<PipelineTarget>) -> PipelineFut<N> {
155 let _ = self.register_metrics();
156 Box::pin(async move {
157 if let Some(target) = target {
159 match target {
160 PipelineTarget::Sync(tip) => self.set_tip(tip),
161 PipelineTarget::Unwind(target) => {
162 if let Err(err) = self.move_to_static_files() {
163 return (self, Err(err.into()))
164 }
165 if let Err(err) = self.unwind(target, None) {
166 return (self, Err(err))
167 }
168 self.progress.update(target);
169
170 return (self, Ok(ControlFlow::Continue { block_number: target }))
171 }
172 }
173 }
174
175 let result = self.run_loop().await;
176 trace!(target: "sync::pipeline", ?target, ?result, "Pipeline finished");
177 (self, result)
178 })
179 }
180
181 pub async fn run(&mut self) -> Result<(), PipelineError> {
184 let _ = self.register_metrics(); loop {
187 let next_action = self.run_loop().await?;
188
189 if next_action.is_unwind() && self.fail_on_unwind {
190 return Err(PipelineError::UnexpectedUnwind)
191 }
192
193 if next_action.should_continue() &&
196 self.progress
197 .minimum_block_number
198 .zip(self.max_block)
199 .is_some_and(|(progress, target)| progress >= target)
200 {
201 trace!(
202 target: "sync::pipeline",
203 ?next_action,
204 minimum_block_number = ?self.progress.minimum_block_number,
205 max_block = ?self.max_block,
206 "Terminating pipeline."
207 );
208 return Ok(())
209 }
210 }
211 }
212
213 pub async fn run_loop(&mut self) -> Result<ControlFlow, PipelineError> {
225 self.run_loop_until(None).await
226 }
227
228 pub async fn run_until(
239 &mut self,
240 last_stage: StageId,
241 target: Option<PipelineTarget>,
242 ) -> Result<ControlFlow, PipelineError> {
243 let _ = self.register_metrics();
244 if !self.stages.iter().any(|stage| stage.id() == last_stage) {
245 return Err(PipelineError::MissingStage(last_stage))
246 }
247
248 match target {
249 Some(PipelineTarget::Sync(tip)) => self.set_tip(tip),
250 Some(PipelineTarget::Unwind(target)) => {
252 self.move_to_static_files()?;
253 self.unwind(target, None)?;
254 self.progress.update(target);
255 return Ok(ControlFlow::Continue { block_number: target })
256 }
257 None => {}
258 }
259
260 self.run_loop_until(Some(last_stage)).await
261 }
262
263 async fn run_loop_until(
265 &mut self,
266 last_stage: Option<StageId>,
267 ) -> Result<ControlFlow, PipelineError> {
268 self.move_to_static_files()?;
269
270 let mut previous_stage = None;
271 for stage_index in 0..self.stages.len() {
272 let stage = &self.stages[stage_index];
273 let stage_id = stage.id();
274
275 trace!(target: "sync::pipeline", stage = %stage_id, "Executing stage");
276 let next = self.execute_stage_to_completion(previous_stage, stage_index).await?;
277
278 trace!(target: "sync::pipeline", stage = %stage_id, ?next, "Completed stage");
279
280 match next {
281 ControlFlow::NoProgress { block_number } => {
282 if let Some(block_number) = block_number {
283 self.progress.update(block_number);
284 }
285 }
286 ControlFlow::Continue { block_number } => self.progress.update(block_number),
287 ControlFlow::Unwind { target, bad_block } => {
288 self.unwind(target, Some(bad_block.block.number))?;
289 return Ok(ControlFlow::Unwind { target, bad_block })
290 }
291 }
292
293 previous_stage = Some(
294 self.provider_factory
295 .provider()?
296 .get_stage_checkpoint(stage_id)?
297 .unwrap_or_default()
298 .block_number,
299 );
300
301 if last_stage == Some(stage_id) {
302 break
303 }
304 }
305
306 Ok(self.progress.next_ctrl())
307 }
308
309 pub fn move_to_static_files(&self) -> RethResult<()> {
326 if self.provider_factory.cached_storage_settings().is_v2() {
327 return Ok(())
328 }
329
330 let lowest_static_file_height =
332 self.static_file_producer.lock().copy_to_static_files()?.min_block_num();
333
334 if let Some(prune_tip) = lowest_static_file_height {
336 let mut pruner = PrunerBuilder::new(Default::default())
339 .delete_limit(usize::MAX)
340 .build_with_provider_factory(self.provider_factory.clone());
341
342 pruner.run(prune_tip)?;
343 }
344
345 Ok(())
346 }
347
348 pub fn unwind(
352 &mut self,
353 to: BlockNumber,
354 bad_block: Option<BlockNumber>,
355 ) -> Result<(), PipelineError> {
356 let (latest_block, prune_modes, checkpoints) = {
358 let provider = self.provider_factory.provider()?;
359 (
360 provider.last_block_number()?,
361 provider.prune_modes_ref().clone(),
362 provider.get_prune_checkpoints()?,
363 )
364 };
365 prune_modes.ensure_unwind_target_unpruned(latest_block, to, &checkpoints)?;
366
367 let unwind_pipeline = self.stages.iter_mut().rev();
369
370 let _locked_sf_producer = self.static_file_producer.lock();
373
374 let mut provider_rw =
375 self.provider_factory.unwind_provider_rw()?.disable_long_read_transaction_safety();
376
377 for stage in unwind_pipeline {
378 let stage_id = stage.id();
379 let span = info_span!("Unwinding", stage = %stage_id);
380 let _enter = span.enter();
381
382 let mut checkpoint = provider_rw.get_stage_checkpoint(stage_id)?.unwrap_or_default();
383 if checkpoint.block_number < to {
384 debug!(
385 target: "sync::pipeline",
386 from = %checkpoint.block_number,
387 %to,
388 "Unwind point too far for stage"
389 );
390 self.event_sender.notify(PipelineEvent::Skipped { stage_id });
391
392 continue
393 }
394
395 info!(
396 target: "sync::pipeline",
397 from = %checkpoint.block_number,
398 %to,
399 ?bad_block,
400 "Starting unwind"
401 );
402 while checkpoint.block_number > to {
403 let unwind_started_at = Instant::now();
404 let input = UnwindInput { checkpoint, unwind_to: to, bad_block };
405 self.event_sender.notify(PipelineEvent::Unwind { stage_id, input });
406
407 let output = stage.unwind(&provider_rw, input);
408 match output {
409 Ok(unwind_output) => {
410 checkpoint = unwind_output.checkpoint;
411 info!(
412 target: "sync::pipeline",
413 stage = %stage_id,
414 unwind_to = to,
415 progress = checkpoint.block_number,
416 done = checkpoint.block_number == to,
417 "Stage unwound"
418 );
419
420 provider_rw.save_stage_checkpoint(stage_id, checkpoint)?;
421
422 self.event_sender
424 .notify(PipelineEvent::Unwound { stage_id, result: unwind_output });
425
426 if let Some(metrics_tx) = &mut self.metrics_tx {
427 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
428 stage_id,
429 checkpoint,
430 max_block_number: None,
433 elapsed: unwind_started_at.elapsed(),
434 });
435 }
436
437 let last_saved_finalized_block_number =
439 provider_rw.last_finalized_block_number()?;
440
441 if last_saved_finalized_block_number.is_none() ||
444 Some(checkpoint.block_number) < last_saved_finalized_block_number
445 {
446 provider_rw.save_finalized_block_number(BlockNumber::from(
447 checkpoint.block_number,
448 ))?;
449 }
450
451 let last_saved_safe_block_number = provider_rw.last_safe_block_number()?;
452
453 if last_saved_safe_block_number.is_none() ||
454 Some(checkpoint.block_number) < last_saved_safe_block_number
455 {
456 provider_rw.save_safe_block_number(BlockNumber::from(
457 checkpoint.block_number,
458 ))?;
459 }
460
461 provider_rw.commit()?;
462
463 stage.post_unwind_commit()?;
464
465 provider_rw = self.provider_factory.unwind_provider_rw()?;
466 }
467 Err(err) => {
468 self.event_sender.notify(PipelineEvent::Error { stage_id });
469
470 return Err(PipelineError::Stage(StageError::Fatal(Box::new(err))))
471 }
472 }
473 }
474 }
475
476 Ok(())
477 }
478
479 async fn execute_stage_to_completion(
480 &mut self,
481 previous_stage: Option<BlockNumber>,
482 stage_index: usize,
483 ) -> Result<ControlFlow, PipelineError> {
484 let total_stages = self.stages.len();
485
486 let stage_id = self.stage(stage_index).id();
487 let mut made_progress = false;
488 let target = self.max_block.or(previous_stage);
489
490 loop {
491 let prev_checkpoint = self.provider_factory.get_stage_checkpoint(stage_id)?;
492
493 let stage_reached_max_block = prev_checkpoint
494 .zip(self.max_block)
495 .is_some_and(|(prev_progress, target)| prev_progress.block_number >= target);
496 if stage_reached_max_block {
497 warn!(
498 target: "sync::pipeline",
499 stage = %stage_id,
500 max_block = self.max_block,
501 prev_block = prev_checkpoint.map(|progress| progress.block_number),
502 "Stage reached target block, skipping."
503 );
504 self.event_sender.notify(PipelineEvent::Skipped { stage_id });
505
506 return Ok(ControlFlow::NoProgress {
508 block_number: prev_checkpoint.map(|progress| progress.block_number),
509 })
510 }
511
512 let exec_input = ExecInput { target, checkpoint: prev_checkpoint };
513
514 self.event_sender.notify(PipelineEvent::Prepare {
515 pipeline_stages_progress: PipelineStagesProgress {
516 current: stage_index + 1,
517 total: total_stages,
518 },
519 stage_id,
520 checkpoint: prev_checkpoint,
521 target,
522 });
523
524 if let Err(err) = self.stage(stage_index).execute_ready(exec_input).await {
525 self.event_sender.notify(PipelineEvent::Error { stage_id });
526 match self.on_stage_error(stage_id, prev_checkpoint, err)? {
527 Some(ctrl) => return Ok(ctrl),
528 None => continue,
529 };
530 }
531
532 let stage_started_at = Instant::now();
533 let provider_rw = self.provider_factory.database_provider_rw()?;
534
535 self.event_sender.notify(PipelineEvent::Run {
536 pipeline_stages_progress: PipelineStagesProgress {
537 current: stage_index + 1,
538 total: total_stages,
539 },
540 stage_id,
541 checkpoint: prev_checkpoint,
542 target,
543 });
544
545 match self.stage(stage_index).execute(&provider_rw, exec_input) {
546 Ok(out @ ExecOutput { checkpoint, done }) => {
547 provider_rw.save_stage_checkpoint(stage_id, checkpoint)?;
549
550 provider_rw.commit()?;
552
553 self.stage(stage_index).post_execute_commit()?;
555
556 self.event_sender.notify(PipelineEvent::Ran {
558 pipeline_stages_progress: PipelineStagesProgress {
559 current: stage_index + 1,
560 total: total_stages,
561 },
562 stage_id,
563 result: out,
564 });
565 if let Some(metrics_tx) = &mut self.metrics_tx {
566 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
567 stage_id,
568 checkpoint,
569 max_block_number: target,
570 elapsed: stage_started_at.elapsed(),
571 });
572 }
573
574 let block_number = checkpoint.block_number;
575 let prev_block_number = prev_checkpoint.unwrap_or_default().block_number;
576 made_progress |= block_number != prev_block_number;
577 if done {
578 return Ok(if made_progress {
579 ControlFlow::Continue { block_number }
580 } else {
581 ControlFlow::NoProgress { block_number: Some(block_number) }
582 })
583 }
584 }
585 Err(err) => {
586 drop(provider_rw);
587 self.event_sender.notify(PipelineEvent::Error { stage_id });
588
589 if let Some(ctrl) = self.on_stage_error(stage_id, prev_checkpoint, err)? {
590 return Ok(ctrl)
591 }
592 }
593 }
594 }
595 }
596
597 fn on_stage_error(
598 &mut self,
599 stage_id: StageId,
600 prev_checkpoint: Option<StageCheckpoint>,
601 err: StageError,
602 ) -> Result<Option<ControlFlow>, PipelineError> {
603 if let StageError::DetachedHead { local_head, header, error } = err {
604 warn!(target: "sync::pipeline", stage = %stage_id, ?local_head, ?header, %error, "Stage encountered detached head");
605
606 if let Some(last_detached_head_unwind_target) = self.last_detached_head_unwind_target {
607 if local_head.block.hash == last_detached_head_unwind_target &&
608 header.block.number == local_head.block.number + 1
609 {
610 self.detached_head_attempts += 1;
611 } else {
612 self.detached_head_attempts = 1;
613 }
614 } else {
615 self.detached_head_attempts = 1;
616 }
617
618 let unwind_to = local_head
620 .block
621 .number
622 .saturating_sub(
623 BEACON_CONSENSUS_REORG_UNWIND_DEPTH.saturating_mul(self.detached_head_attempts),
624 )
625 .max(self.provider_factory.static_file_provider().genesis_block_number());
626
627 self.last_detached_head_unwind_target = self.provider_factory.block_hash(unwind_to)?;
628 Ok(Some(ControlFlow::Unwind { target: unwind_to, bad_block: local_head }))
629 } else if let StageError::Block { block, error } = err {
630 match error {
631 BlockErrorKind::Validation(validation_error) => {
632 error!(
633 target: "sync::pipeline",
634 stage = %stage_id,
635 bad_block = %block.block.number,
636 bad_block_hash = %block.block.hash,
637 "Stage encountered a validation error: {validation_error}"
638 );
639
640 if stage_id == StageId::MerkleExecute {
645 let provider_rw = self.provider_factory.database_provider_rw()?;
646 provider_rw
647 .save_stage_checkpoint_progress(StageId::MerkleExecute, vec![])?;
648 provider_rw.save_stage_checkpoint(
649 StageId::MerkleExecute,
650 prev_checkpoint.unwrap_or_default(),
651 )?;
652
653 provider_rw.commit()?;
654 }
655 }
656 BlockErrorKind::Execution(execution_error) => {
657 error!(
658 target: "sync::pipeline",
659 stage = %stage_id,
660 bad_block = %block.block.number,
661 bad_block_hash = %block.block.hash,
662 "Stage encountered an execution error: {execution_error}"
663 );
664 }
665 }
666
667 Ok(Some(ControlFlow::Unwind {
670 target: prev_checkpoint.unwrap_or_default().block_number,
671 bad_block: block,
672 }))
673 } else if let StageError::MissingStaticFileData { block, segment } = err {
674 error!(
675 target: "sync::pipeline",
676 stage = %stage_id,
677 bad_block = %block.block.number,
678 segment = %segment,
679 "Stage is missing static file data."
680 );
681
682 Ok(Some(ControlFlow::Unwind {
683 target: block.block.number.saturating_sub(1),
684 bad_block: block,
685 }))
686 } else if err.is_fatal() {
687 error!(target: "sync::pipeline", stage = %stage_id, "Stage encountered a fatal error: {err}");
688 Err(err.into())
689 } else {
690 warn!(
693 target: "sync::pipeline",
694 stage = %stage_id,
695 "Stage encountered a non-fatal error: {err}. Retrying..."
696 );
697 Ok(None)
698 }
699 }
700}
701
702impl<N: ProviderNodeTypes> std::fmt::Debug for Pipeline<N> {
703 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
704 f.debug_struct("Pipeline")
705 .field("stages", &self.stages.iter().map(|stage| stage.id()).collect::<Vec<StageId>>())
706 .field("max_block", &self.max_block)
707 .field("event_sender", &self.event_sender)
708 .field("fail_on_unwind", &self.fail_on_unwind)
709 .finish()
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use std::sync::atomic::Ordering;
716
717 use super::*;
718 use crate::{test_utils::TestStage, UnwindOutput};
719 use alloy_eips::{eip1898::BlockWithParent, BlockNumHash};
720 use assert_matches::assert_matches;
721 use reth_consensus::ConsensusError;
722 use reth_errors::ProviderError;
723 use reth_provider::test_utils::{
724 create_test_provider_factory, create_test_provider_factory_with_genesis_block_number,
725 MockNodeTypesWithDB,
726 };
727 use reth_prune::PruneModes;
728 use reth_testing_utils::generators::{self, random_block_with_parent};
729 use tokio_stream::StreamExt;
730
731 #[test]
732 fn record_progress_calculates_outliers() {
733 let mut progress = PipelineProgress::default();
734
735 progress.update(10);
736 assert_eq!(progress.minimum_block_number, Some(10));
737 assert_eq!(progress.maximum_block_number, Some(10));
738
739 progress.update(20);
740 assert_eq!(progress.minimum_block_number, Some(10));
741 assert_eq!(progress.maximum_block_number, Some(20));
742
743 progress.update(1);
744 assert_eq!(progress.minimum_block_number, Some(1));
745 assert_eq!(progress.maximum_block_number, Some(20));
746 }
747
748 #[test]
749 fn progress_ctrl_flow() {
750 let mut progress = PipelineProgress::default();
751
752 assert_eq!(progress.next_ctrl(), ControlFlow::NoProgress { block_number: None });
753
754 progress.update(1);
755 assert_eq!(progress.next_ctrl(), ControlFlow::Continue { block_number: 1 });
756 }
757
758 #[tokio::test]
760 async fn run_until_stops_after_the_named_stage() {
761 let provider_factory = create_test_provider_factory();
762
763 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
764 .add_stage(
765 TestStage::new(StageId::Other("A"))
766 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10)))),
767 )
768 .add_stage(TestStage::new(StageId::Other("B")))
770 .with_max_block(10)
771 .build(
772 provider_factory.clone(),
773 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
774 );
775
776 pipeline.run_until(StageId::Other("A"), None).await.unwrap();
777
778 let provider = provider_factory.provider().unwrap();
779 assert_eq!(
780 provider.get_stage_checkpoint(StageId::Other("A")).unwrap(),
781 Some(StageCheckpoint::new(10))
782 );
783 assert_eq!(provider.get_stage_checkpoint(StageId::Other("B")).unwrap(), None);
784 }
785
786 #[tokio::test]
788 async fn run_until_rejects_an_unknown_stage() {
789 let provider_factory = create_test_provider_factory();
790
791 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
792 .add_stage(TestStage::new(StageId::Other("A")))
793 .build(
794 provider_factory.clone(),
795 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
796 );
797
798 let result = pipeline.run_until(StageId::Other("Z"), None).await;
799
800 assert_matches!(result, Err(PipelineError::MissingStage(StageId::Other("Z"))));
801 }
802
803 #[tokio::test]
805 async fn run_pipeline() {
806 let provider_factory = create_test_provider_factory();
807
808 let stage_a = TestStage::new(StageId::Other("A"))
809 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(20))));
810 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
811 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
812
813 let stage_b = TestStage::new(StageId::Other("B"))
814 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10))));
815 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
816 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
817
818 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
819 .add_stage(stage_a)
820 .add_stage(stage_b)
821 .with_max_block(10)
822 .build(
823 provider_factory.clone(),
824 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
825 );
826 let events = pipeline.events();
827
828 tokio::spawn(async move {
830 pipeline.run().await.unwrap();
831 });
832
833 assert_eq!(
835 events.collect::<Vec<PipelineEvent>>().await,
836 vec![
837 PipelineEvent::Prepare {
838 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
839 stage_id: StageId::Other("A"),
840 checkpoint: None,
841 target: Some(10),
842 },
843 PipelineEvent::Run {
844 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
845 stage_id: StageId::Other("A"),
846 checkpoint: None,
847 target: Some(10),
848 },
849 PipelineEvent::Ran {
850 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
851 stage_id: StageId::Other("A"),
852 result: ExecOutput::done(StageCheckpoint::new(20)),
853 },
854 PipelineEvent::Prepare {
855 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
856 stage_id: StageId::Other("B"),
857 checkpoint: None,
858 target: Some(10),
859 },
860 PipelineEvent::Run {
861 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
862 stage_id: StageId::Other("B"),
863 checkpoint: None,
864 target: Some(10),
865 },
866 PipelineEvent::Ran {
867 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
868 stage_id: StageId::Other("B"),
869 result: ExecOutput::done(StageCheckpoint::new(10)),
870 },
871 ]
872 );
873
874 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
875 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 0);
876
877 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
878 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 0);
879 }
880
881 #[tokio::test]
883 async fn unwind_pipeline() {
884 let provider_factory = create_test_provider_factory();
885
886 let stage_a = TestStage::new(StageId::Other("A"))
887 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(100))))
888 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
889 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
890 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
891
892 let stage_b = TestStage::new(StageId::Other("B"))
893 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10))))
894 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
895 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
896 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
897
898 let stage_c = TestStage::new(StageId::Other("C"))
899 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(20))))
900 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
901 let (stage_c, post_execute_commit_counter_c) = stage_c.with_post_execute_commit_counter();
902 let (stage_c, post_unwind_commit_counter_c) = stage_c.with_post_unwind_commit_counter();
903
904 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
905 .add_stage(stage_a)
906 .add_stage(stage_b)
907 .add_stage(stage_c)
908 .with_max_block(10)
909 .build(
910 provider_factory.clone(),
911 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
912 );
913 let events = pipeline.events();
914
915 tokio::spawn(async move {
917 pipeline.run().await.expect("Could not run pipeline");
919
920 pipeline.unwind(1, None).expect("Could not unwind pipeline");
922 });
923
924 assert_eq!(
926 events.collect::<Vec<PipelineEvent>>().await,
927 vec![
928 PipelineEvent::Prepare {
930 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
931 stage_id: StageId::Other("A"),
932 checkpoint: None,
933 target: Some(10),
934 },
935 PipelineEvent::Run {
936 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
937 stage_id: StageId::Other("A"),
938 checkpoint: None,
939 target: Some(10),
940 },
941 PipelineEvent::Ran {
942 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
943 stage_id: StageId::Other("A"),
944 result: ExecOutput::done(StageCheckpoint::new(100)),
945 },
946 PipelineEvent::Prepare {
947 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
948 stage_id: StageId::Other("B"),
949 checkpoint: None,
950 target: Some(10),
951 },
952 PipelineEvent::Run {
953 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
954 stage_id: StageId::Other("B"),
955 checkpoint: None,
956 target: Some(10),
957 },
958 PipelineEvent::Ran {
959 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
960 stage_id: StageId::Other("B"),
961 result: ExecOutput::done(StageCheckpoint::new(10)),
962 },
963 PipelineEvent::Prepare {
964 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
965 stage_id: StageId::Other("C"),
966 checkpoint: None,
967 target: Some(10),
968 },
969 PipelineEvent::Run {
970 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
971 stage_id: StageId::Other("C"),
972 checkpoint: None,
973 target: Some(10),
974 },
975 PipelineEvent::Ran {
976 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
977 stage_id: StageId::Other("C"),
978 result: ExecOutput::done(StageCheckpoint::new(20)),
979 },
980 PipelineEvent::Unwind {
982 stage_id: StageId::Other("C"),
983 input: UnwindInput {
984 checkpoint: StageCheckpoint::new(20),
985 unwind_to: 1,
986 bad_block: None
987 }
988 },
989 PipelineEvent::Unwound {
990 stage_id: StageId::Other("C"),
991 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
992 },
993 PipelineEvent::Unwind {
994 stage_id: StageId::Other("B"),
995 input: UnwindInput {
996 checkpoint: StageCheckpoint::new(10),
997 unwind_to: 1,
998 bad_block: None
999 }
1000 },
1001 PipelineEvent::Unwound {
1002 stage_id: StageId::Other("B"),
1003 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
1004 },
1005 PipelineEvent::Unwind {
1006 stage_id: StageId::Other("A"),
1007 input: UnwindInput {
1008 checkpoint: StageCheckpoint::new(100),
1009 unwind_to: 1,
1010 bad_block: None
1011 }
1012 },
1013 PipelineEvent::Unwound {
1014 stage_id: StageId::Other("A"),
1015 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
1016 },
1017 ]
1018 );
1019
1020 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
1021 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 1);
1022
1023 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
1024 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 1);
1025
1026 assert_eq!(post_execute_commit_counter_c.load(Ordering::Relaxed), 1);
1027 assert_eq!(post_unwind_commit_counter_c.load(Ordering::Relaxed), 1);
1028 }
1029
1030 #[tokio::test]
1032 async fn unwind_pipeline_with_intermediate_progress() {
1033 let provider_factory = create_test_provider_factory();
1034
1035 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1036 .add_stage(
1037 TestStage::new(StageId::Other("A"))
1038 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(100))))
1039 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(50) })),
1040 )
1041 .add_stage(
1042 TestStage::new(StageId::Other("B"))
1043 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10)))),
1044 )
1045 .with_max_block(10)
1046 .build(
1047 provider_factory.clone(),
1048 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1049 );
1050 let events = pipeline.events();
1051
1052 tokio::spawn(async move {
1054 pipeline.run().await.expect("Could not run pipeline");
1056
1057 pipeline.unwind(50, None).expect("Could not unwind pipeline");
1059 });
1060
1061 assert_eq!(
1063 events.collect::<Vec<PipelineEvent>>().await,
1064 vec![
1065 PipelineEvent::Prepare {
1067 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1068 stage_id: StageId::Other("A"),
1069 checkpoint: None,
1070 target: Some(10),
1071 },
1072 PipelineEvent::Run {
1073 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1074 stage_id: StageId::Other("A"),
1075 checkpoint: None,
1076 target: Some(10),
1077 },
1078 PipelineEvent::Ran {
1079 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1080 stage_id: StageId::Other("A"),
1081 result: ExecOutput::done(StageCheckpoint::new(100)),
1082 },
1083 PipelineEvent::Prepare {
1084 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1085 stage_id: StageId::Other("B"),
1086 checkpoint: None,
1087 target: Some(10),
1088 },
1089 PipelineEvent::Run {
1090 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1091 stage_id: StageId::Other("B"),
1092 checkpoint: None,
1093 target: Some(10),
1094 },
1095 PipelineEvent::Ran {
1096 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1097 stage_id: StageId::Other("B"),
1098 result: ExecOutput::done(StageCheckpoint::new(10)),
1099 },
1100 PipelineEvent::Skipped { stage_id: StageId::Other("B") },
1103 PipelineEvent::Unwind {
1104 stage_id: StageId::Other("A"),
1105 input: UnwindInput {
1106 checkpoint: StageCheckpoint::new(100),
1107 unwind_to: 50,
1108 bad_block: None
1109 }
1110 },
1111 PipelineEvent::Unwound {
1112 stage_id: StageId::Other("A"),
1113 result: UnwindOutput { checkpoint: StageCheckpoint::new(50) },
1114 },
1115 ]
1116 );
1117 }
1118
1119 #[tokio::test]
1132 async fn run_pipeline_with_unwind() {
1133 let provider_factory = create_test_provider_factory();
1134
1135 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1136 .add_stage(
1137 TestStage::new(StageId::Other("A"))
1138 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10))))
1139 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1140 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10)))),
1141 )
1142 .add_stage(
1143 TestStage::new(StageId::Other("B"))
1144 .add_exec(Err(StageError::Block {
1145 block: Box::new(random_block_with_parent(
1146 &mut generators::rng(),
1147 5,
1148 Default::default(),
1149 )),
1150 error: BlockErrorKind::Validation(ConsensusError::BaseFeeMissing),
1151 }))
1152 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1153 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10)))),
1154 )
1155 .with_max_block(10)
1156 .build(
1157 provider_factory.clone(),
1158 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1159 );
1160 let events = pipeline.events();
1161
1162 tokio::spawn(async move {
1164 pipeline.run().await.expect("Could not run pipeline");
1165 });
1166
1167 assert_eq!(
1169 events.collect::<Vec<PipelineEvent>>().await,
1170 vec![
1171 PipelineEvent::Prepare {
1172 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1173 stage_id: StageId::Other("A"),
1174 checkpoint: None,
1175 target: Some(10),
1176 },
1177 PipelineEvent::Run {
1178 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1179 stage_id: StageId::Other("A"),
1180 checkpoint: None,
1181 target: Some(10),
1182 },
1183 PipelineEvent::Ran {
1184 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1185 stage_id: StageId::Other("A"),
1186 result: ExecOutput::done(StageCheckpoint::new(10)),
1187 },
1188 PipelineEvent::Prepare {
1189 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1190 stage_id: StageId::Other("B"),
1191 checkpoint: None,
1192 target: Some(10),
1193 },
1194 PipelineEvent::Run {
1195 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1196 stage_id: StageId::Other("B"),
1197 checkpoint: None,
1198 target: Some(10),
1199 },
1200 PipelineEvent::Error { stage_id: StageId::Other("B") },
1201 PipelineEvent::Unwind {
1202 stage_id: StageId::Other("A"),
1203 input: UnwindInput {
1204 checkpoint: StageCheckpoint::new(10),
1205 unwind_to: 0,
1206 bad_block: Some(5)
1207 }
1208 },
1209 PipelineEvent::Unwound {
1210 stage_id: StageId::Other("A"),
1211 result: UnwindOutput { checkpoint: StageCheckpoint::new(0) },
1212 },
1213 PipelineEvent::Prepare {
1214 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1215 stage_id: StageId::Other("A"),
1216 checkpoint: Some(StageCheckpoint::new(0)),
1217 target: Some(10),
1218 },
1219 PipelineEvent::Run {
1220 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1221 stage_id: StageId::Other("A"),
1222 checkpoint: Some(StageCheckpoint::new(0)),
1223 target: Some(10),
1224 },
1225 PipelineEvent::Ran {
1226 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1227 stage_id: StageId::Other("A"),
1228 result: ExecOutput::done(StageCheckpoint::new(10)),
1229 },
1230 PipelineEvent::Prepare {
1231 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1232 stage_id: StageId::Other("B"),
1233 checkpoint: None,
1234 target: Some(10),
1235 },
1236 PipelineEvent::Run {
1237 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1238 stage_id: StageId::Other("B"),
1239 checkpoint: None,
1240 target: Some(10),
1241 },
1242 PipelineEvent::Ran {
1243 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1244 stage_id: StageId::Other("B"),
1245 result: ExecOutput::done(StageCheckpoint::new(10)),
1246 },
1247 ]
1248 );
1249 }
1250
1251 #[tokio::test]
1253 async fn pipeline_error_handling() {
1254 let provider_factory = create_test_provider_factory();
1256 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1257 .add_stage(
1258 TestStage::new(StageId::Other("NonFatal"))
1259 .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error))))
1260 .add_exec(Ok(ExecOutput::done(StageCheckpoint::new(10)))),
1261 )
1262 .with_max_block(10)
1263 .build(
1264 provider_factory.clone(),
1265 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1266 );
1267 let result = pipeline.run().await;
1268 assert_matches!(result, Ok(()));
1269
1270 let provider_factory = create_test_provider_factory();
1272 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1273 .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err(
1274 StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)),
1275 )))
1276 .build(
1277 provider_factory.clone(),
1278 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1279 );
1280 let result = pipeline.run().await;
1281 assert_matches!(
1282 result,
1283 Err(PipelineError::Stage(StageError::DatabaseIntegrity(
1284 ProviderError::BlockBodyIndicesNotFound(5)
1285 )))
1286 );
1287 }
1288
1289 #[test]
1290 fn detached_head_unwinds_no_lower_than_genesis() {
1291 for genesis in [0, 5] {
1292 let factory = create_test_provider_factory_with_genesis_block_number(genesis);
1293 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1294 .build(factory.clone(), StaticFileProducer::new(factory, PruneModes::default()));
1295 let block = |number| {
1296 Box::new(BlockWithParent {
1297 parent: B256::ZERO,
1298 block: BlockNumHash::new(number, B256::repeat_byte(1)),
1299 })
1300 };
1301 let error = StageError::DetachedHead {
1302 local_head: block(genesis + 1),
1303 header: block(genesis + 2),
1304 error: Box::new(ConsensusError::BaseFeeMissing),
1305 };
1306
1307 let flow = pipeline.on_stage_error(StageId::Headers, None, error).unwrap();
1308
1309 assert_eq!(
1310 flow,
1311 Some(ControlFlow::Unwind { target: genesis, bad_block: block(genesis + 1) })
1312 );
1313 }
1314 }
1315}