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, StorageSettingsCache,
13};
14use reth_prune::PrunerBuilder;
15use reth_static_file::StaticFileProducer;
16use reth_tokio_util::{EventSender, EventStream};
17use std::{
18 pin::Pin,
19 time::{Duration, Instant},
20};
21use tokio::sync::watch;
22use tracing::*;
23
24mod builder;
25mod progress;
26mod set;
27
28use crate::{
29 BlockErrorKind, ExecInput, ExecOutput, MetricEvent, MetricEventsSender, PipelineError, Stage,
30 StageError, StageExt, UnwindInput,
31};
32pub use builder::*;
33use progress::*;
34use reth_errors::RethResult;
35pub use set::*;
36
37pub(crate) type BoxedStage<DB> = Box<dyn Stage<DB>>;
39
40pub type PipelineFut<N> = Pin<Box<dyn Future<Output = PipelineWithResult<N>> + Send>>;
43
44pub type PipelineWithResult<N> = (Pipeline<N>, Result<ControlFlow, PipelineError>);
46
47#[cfg_attr(doc, aquamarine::aquamarine)]
48pub struct Pipeline<N: ProviderNodeTypes> {
70 provider_factory: ProviderFactory<N>,
72 stages: Vec<BoxedStage<<ProviderFactory<N> as DatabaseProviderFactory>::ProviderRW>>,
74 max_block: Option<BlockNumber>,
76 static_file_producer: StaticFileProducer<ProviderFactory<N>>,
77 event_sender: EventSender<PipelineEvent>,
79 progress: PipelineProgress,
81 tip_tx: Option<watch::Sender<B256>>,
85 metrics_tx: Option<MetricEventsSender>,
86 fail_on_unwind: bool,
89 last_detached_head_unwind_target: Option<B256>,
92 detached_head_attempts: u64,
95}
96
97impl<N: ProviderNodeTypes> Pipeline<N> {
98 pub fn builder() -> PipelineBuilder<<ProviderFactory<N> as DatabaseProviderFactory>::ProviderRW>
100 {
101 PipelineBuilder::default()
102 }
103
104 pub const fn minimum_block_number(&self) -> Option<u64> {
107 self.progress.minimum_block_number
108 }
109
110 #[track_caller]
112 pub fn set_tip(&self, tip: B256) {
113 let _ = self.tip_tx.as_ref().expect("tip sender is set").send(tip).map_err(|_| {
114 warn!(target: "sync::pipeline", "Chain tip channel closed");
115 });
116 }
117
118 pub fn events(&self) -> EventStream<PipelineEvent> {
120 self.event_sender.new_listener()
121 }
122
123 pub fn stage(
125 &mut self,
126 idx: usize,
127 ) -> &mut dyn Stage<<ProviderFactory<N> as DatabaseProviderFactory>::ProviderRW> {
128 &mut self.stages[idx]
129 }
130}
131
132impl<N: ProviderNodeTypes> Pipeline<N> {
133 pub fn register_metrics(&mut self) -> Result<(), PipelineError> {
135 let Some(metrics_tx) = &mut self.metrics_tx else { return Ok(()) };
136 let provider = self.provider_factory.provider()?;
137
138 for stage in &self.stages {
139 let stage_id = stage.id();
140 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
141 stage_id,
142 checkpoint: provider.get_stage_checkpoint(stage_id)?.unwrap_or_default(),
143 max_block_number: None,
144 elapsed: Duration::default(),
145 });
146 }
147 Ok(())
148 }
149
150 #[track_caller]
153 pub fn run_as_fut(mut self, target: Option<PipelineTarget>) -> PipelineFut<N> {
154 let _ = self.register_metrics();
155 Box::pin(async move {
156 if let Some(target) = target {
158 match target {
159 PipelineTarget::Sync(tip) => self.set_tip(tip),
160 PipelineTarget::Unwind(target) => {
161 if let Err(err) = self.move_to_static_files() {
162 return (self, Err(err.into()))
163 }
164 if let Err(err) = self.unwind(target, None) {
165 return (self, Err(err))
166 }
167 self.progress.update(target);
168
169 return (self, Ok(ControlFlow::Continue { block_number: target }))
170 }
171 }
172 }
173
174 let result = self.run_loop().await;
175 trace!(target: "sync::pipeline", ?target, ?result, "Pipeline finished");
176 (self, result)
177 })
178 }
179
180 pub async fn run(&mut self) -> Result<(), PipelineError> {
183 let _ = self.register_metrics(); loop {
186 let next_action = self.run_loop().await?;
187
188 if next_action.is_unwind() && self.fail_on_unwind {
189 return Err(PipelineError::UnexpectedUnwind)
190 }
191
192 if next_action.should_continue() &&
195 self.progress
196 .minimum_block_number
197 .zip(self.max_block)
198 .is_some_and(|(progress, target)| progress >= target)
199 {
200 trace!(
201 target: "sync::pipeline",
202 ?next_action,
203 minimum_block_number = ?self.progress.minimum_block_number,
204 max_block = ?self.max_block,
205 "Terminating pipeline."
206 );
207 return Ok(())
208 }
209 }
210 }
211
212 pub async fn run_loop(&mut self) -> Result<ControlFlow, PipelineError> {
224 self.move_to_static_files()?;
225
226 let mut previous_stage = None;
227 for stage_index in 0..self.stages.len() {
228 let stage = &self.stages[stage_index];
229 let stage_id = stage.id();
230
231 trace!(target: "sync::pipeline", stage = %stage_id, "Executing stage");
232 let next = self.execute_stage_to_completion(previous_stage, stage_index).await?;
233
234 trace!(target: "sync::pipeline", stage = %stage_id, ?next, "Completed stage");
235
236 match next {
237 ControlFlow::NoProgress { block_number } => {
238 if let Some(block_number) = block_number {
239 self.progress.update(block_number);
240 }
241 }
242 ControlFlow::Continue { block_number } => self.progress.update(block_number),
243 ControlFlow::Unwind { target, bad_block } => {
244 self.unwind(target, Some(bad_block.block.number))?;
245 return Ok(ControlFlow::Unwind { target, bad_block })
246 }
247 }
248
249 previous_stage = Some(
250 self.provider_factory
251 .provider()?
252 .get_stage_checkpoint(stage_id)?
253 .unwrap_or_default()
254 .block_number,
255 );
256 }
257
258 Ok(self.progress.next_ctrl())
259 }
260
261 pub fn move_to_static_files(&self) -> RethResult<()> {
278 if self.provider_factory.cached_storage_settings().is_v2() {
279 return Ok(())
280 }
281
282 let lowest_static_file_height =
284 self.static_file_producer.lock().copy_to_static_files()?.min_block_num();
285
286 if let Some(prune_tip) = lowest_static_file_height {
288 let mut pruner = PrunerBuilder::new(Default::default())
291 .delete_limit(usize::MAX)
292 .build_with_provider_factory(self.provider_factory.clone());
293
294 pruner.run(prune_tip)?;
295 }
296
297 Ok(())
298 }
299
300 pub fn unwind(
304 &mut self,
305 to: BlockNumber,
306 bad_block: Option<BlockNumber>,
307 ) -> Result<(), PipelineError> {
308 let (latest_block, prune_modes, checkpoints) = {
310 let provider = self.provider_factory.provider()?;
311 (
312 provider.last_block_number()?,
313 provider.prune_modes_ref().clone(),
314 provider.get_prune_checkpoints()?,
315 )
316 };
317 prune_modes.ensure_unwind_target_unpruned(latest_block, to, &checkpoints)?;
318
319 let unwind_pipeline = self.stages.iter_mut().rev();
321
322 let _locked_sf_producer = self.static_file_producer.lock();
325
326 let mut provider_rw =
327 self.provider_factory.unwind_provider_rw()?.disable_long_read_transaction_safety();
328
329 for stage in unwind_pipeline {
330 let stage_id = stage.id();
331 let span = info_span!("Unwinding", stage = %stage_id);
332 let _enter = span.enter();
333
334 let mut checkpoint = provider_rw.get_stage_checkpoint(stage_id)?.unwrap_or_default();
335 if checkpoint.block_number < to {
336 debug!(
337 target: "sync::pipeline",
338 from = %checkpoint.block_number,
339 %to,
340 "Unwind point too far for stage"
341 );
342 self.event_sender.notify(PipelineEvent::Skipped { stage_id });
343
344 continue
345 }
346
347 info!(
348 target: "sync::pipeline",
349 from = %checkpoint.block_number,
350 %to,
351 ?bad_block,
352 "Starting unwind"
353 );
354 while checkpoint.block_number > to {
355 let unwind_started_at = Instant::now();
356 let input = UnwindInput { checkpoint, unwind_to: to, bad_block };
357 self.event_sender.notify(PipelineEvent::Unwind { stage_id, input });
358
359 let output = stage.unwind(&provider_rw, input);
360 match output {
361 Ok(unwind_output) => {
362 checkpoint = unwind_output.checkpoint;
363 info!(
364 target: "sync::pipeline",
365 stage = %stage_id,
366 unwind_to = to,
367 progress = checkpoint.block_number,
368 done = checkpoint.block_number == to,
369 "Stage unwound"
370 );
371
372 provider_rw.save_stage_checkpoint(stage_id, checkpoint)?;
373
374 self.event_sender
376 .notify(PipelineEvent::Unwound { stage_id, result: unwind_output });
377
378 if let Some(metrics_tx) = &mut self.metrics_tx {
379 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
380 stage_id,
381 checkpoint,
382 max_block_number: None,
385 elapsed: unwind_started_at.elapsed(),
386 });
387 }
388
389 let last_saved_finalized_block_number =
391 provider_rw.last_finalized_block_number()?;
392
393 if last_saved_finalized_block_number.is_none() ||
396 Some(checkpoint.block_number) < last_saved_finalized_block_number
397 {
398 provider_rw.save_finalized_block_number(BlockNumber::from(
399 checkpoint.block_number,
400 ))?;
401 }
402
403 let last_saved_safe_block_number = provider_rw.last_safe_block_number()?;
404
405 if last_saved_safe_block_number.is_none() ||
406 Some(checkpoint.block_number) < last_saved_safe_block_number
407 {
408 provider_rw.save_safe_block_number(BlockNumber::from(
409 checkpoint.block_number,
410 ))?;
411 }
412
413 provider_rw.commit()?;
414
415 stage.post_unwind_commit()?;
416
417 provider_rw = self.provider_factory.unwind_provider_rw()?;
418 }
419 Err(err) => {
420 self.event_sender.notify(PipelineEvent::Error { stage_id });
421
422 return Err(PipelineError::Stage(StageError::Fatal(Box::new(err))))
423 }
424 }
425 }
426 }
427
428 Ok(())
429 }
430
431 async fn execute_stage_to_completion(
432 &mut self,
433 previous_stage: Option<BlockNumber>,
434 stage_index: usize,
435 ) -> Result<ControlFlow, PipelineError> {
436 let total_stages = self.stages.len();
437
438 let stage_id = self.stage(stage_index).id();
439 let mut made_progress = false;
440 let target = self.max_block.or(previous_stage);
441
442 loop {
443 let prev_checkpoint = self.provider_factory.get_stage_checkpoint(stage_id)?;
444
445 let stage_reached_max_block = prev_checkpoint
446 .zip(self.max_block)
447 .is_some_and(|(prev_progress, target)| prev_progress.block_number >= target);
448 if stage_reached_max_block {
449 warn!(
450 target: "sync::pipeline",
451 stage = %stage_id,
452 max_block = self.max_block,
453 prev_block = prev_checkpoint.map(|progress| progress.block_number),
454 "Stage reached target block, skipping."
455 );
456 self.event_sender.notify(PipelineEvent::Skipped { stage_id });
457
458 return Ok(ControlFlow::NoProgress {
460 block_number: prev_checkpoint.map(|progress| progress.block_number),
461 })
462 }
463
464 let exec_input = ExecInput { target, checkpoint: prev_checkpoint };
465
466 self.event_sender.notify(PipelineEvent::Prepare {
467 pipeline_stages_progress: PipelineStagesProgress {
468 current: stage_index + 1,
469 total: total_stages,
470 },
471 stage_id,
472 checkpoint: prev_checkpoint,
473 target,
474 });
475
476 if let Err(err) = self.stage(stage_index).execute_ready(exec_input).await {
477 self.event_sender.notify(PipelineEvent::Error { stage_id });
478 match self.on_stage_error(stage_id, prev_checkpoint, err)? {
479 Some(ctrl) => return Ok(ctrl),
480 None => continue,
481 };
482 }
483
484 let stage_started_at = Instant::now();
485 let provider_rw = self.provider_factory.database_provider_rw()?;
486
487 self.event_sender.notify(PipelineEvent::Run {
488 pipeline_stages_progress: PipelineStagesProgress {
489 current: stage_index + 1,
490 total: total_stages,
491 },
492 stage_id,
493 checkpoint: prev_checkpoint,
494 target,
495 });
496
497 match self.stage(stage_index).execute(&provider_rw, exec_input) {
498 Ok(out @ ExecOutput { checkpoint, done }) => {
499 provider_rw.save_stage_checkpoint(stage_id, checkpoint)?;
501
502 provider_rw.commit()?;
504
505 self.stage(stage_index).post_execute_commit()?;
507
508 self.event_sender.notify(PipelineEvent::Ran {
510 pipeline_stages_progress: PipelineStagesProgress {
511 current: stage_index + 1,
512 total: total_stages,
513 },
514 stage_id,
515 result: out,
516 });
517 if let Some(metrics_tx) = &mut self.metrics_tx {
518 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
519 stage_id,
520 checkpoint,
521 max_block_number: target,
522 elapsed: stage_started_at.elapsed(),
523 });
524 }
525
526 let block_number = checkpoint.block_number;
527 let prev_block_number = prev_checkpoint.unwrap_or_default().block_number;
528 made_progress |= block_number != prev_block_number;
529 if done {
530 return Ok(if made_progress {
531 ControlFlow::Continue { block_number }
532 } else {
533 ControlFlow::NoProgress { block_number: Some(block_number) }
534 })
535 }
536 }
537 Err(err) => {
538 drop(provider_rw);
539 self.event_sender.notify(PipelineEvent::Error { stage_id });
540
541 if let Some(ctrl) = self.on_stage_error(stage_id, prev_checkpoint, err)? {
542 return Ok(ctrl)
543 }
544 }
545 }
546 }
547 }
548
549 fn on_stage_error(
550 &mut self,
551 stage_id: StageId,
552 prev_checkpoint: Option<StageCheckpoint>,
553 err: StageError,
554 ) -> Result<Option<ControlFlow>, PipelineError> {
555 if let StageError::DetachedHead { local_head, header, error } = err {
556 warn!(target: "sync::pipeline", stage = %stage_id, ?local_head, ?header, %error, "Stage encountered detached head");
557
558 if let Some(last_detached_head_unwind_target) = self.last_detached_head_unwind_target {
559 if local_head.block.hash == last_detached_head_unwind_target &&
560 header.block.number == local_head.block.number + 1
561 {
562 self.detached_head_attempts += 1;
563 } else {
564 self.detached_head_attempts = 1;
565 }
566 } else {
567 self.detached_head_attempts = 1;
568 }
569
570 let unwind_to = local_head
572 .block
573 .number
574 .saturating_sub(
575 BEACON_CONSENSUS_REORG_UNWIND_DEPTH.saturating_mul(self.detached_head_attempts),
576 )
577 .max(1);
578
579 self.last_detached_head_unwind_target = self.provider_factory.block_hash(unwind_to)?;
580 Ok(Some(ControlFlow::Unwind { target: unwind_to, bad_block: local_head }))
581 } else if let StageError::Block { block, error } = err {
582 match error {
583 BlockErrorKind::Validation(validation_error) => {
584 error!(
585 target: "sync::pipeline",
586 stage = %stage_id,
587 bad_block = %block.block.number,
588 bad_block_hash = %block.block.hash,
589 "Stage encountered a validation error: {validation_error}"
590 );
591
592 if stage_id == StageId::MerkleExecute {
597 let provider_rw = self.provider_factory.database_provider_rw()?;
598 provider_rw
599 .save_stage_checkpoint_progress(StageId::MerkleExecute, vec![])?;
600 provider_rw.save_stage_checkpoint(
601 StageId::MerkleExecute,
602 prev_checkpoint.unwrap_or_default(),
603 )?;
604
605 provider_rw.commit()?;
606 }
607 }
608 BlockErrorKind::Execution(execution_error) => {
609 error!(
610 target: "sync::pipeline",
611 stage = %stage_id,
612 bad_block = %block.block.number,
613 bad_block_hash = %block.block.hash,
614 "Stage encountered an execution error: {execution_error}"
615 );
616 }
617 }
618
619 Ok(Some(ControlFlow::Unwind {
622 target: prev_checkpoint.unwrap_or_default().block_number,
623 bad_block: block,
624 }))
625 } else if let StageError::MissingStaticFileData { block, segment } = err {
626 error!(
627 target: "sync::pipeline",
628 stage = %stage_id,
629 bad_block = %block.block.number,
630 segment = %segment,
631 "Stage is missing static file data."
632 );
633
634 Ok(Some(ControlFlow::Unwind {
635 target: block.block.number.saturating_sub(1),
636 bad_block: block,
637 }))
638 } else if err.is_fatal() {
639 error!(target: "sync::pipeline", stage = %stage_id, "Stage encountered a fatal error: {err}");
640 Err(err.into())
641 } else {
642 warn!(
645 target: "sync::pipeline",
646 stage = %stage_id,
647 "Stage encountered a non-fatal error: {err}. Retrying..."
648 );
649 Ok(None)
650 }
651 }
652}
653
654impl<N: ProviderNodeTypes> std::fmt::Debug for Pipeline<N> {
655 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656 f.debug_struct("Pipeline")
657 .field("stages", &self.stages.iter().map(|stage| stage.id()).collect::<Vec<StageId>>())
658 .field("max_block", &self.max_block)
659 .field("event_sender", &self.event_sender)
660 .field("fail_on_unwind", &self.fail_on_unwind)
661 .finish()
662 }
663}
664
665#[cfg(test)]
666mod tests {
667 use std::sync::atomic::Ordering;
668
669 use super::*;
670 use crate::{test_utils::TestStage, UnwindOutput};
671 use assert_matches::assert_matches;
672 use reth_consensus::ConsensusError;
673 use reth_errors::ProviderError;
674 use reth_provider::test_utils::{create_test_provider_factory, MockNodeTypesWithDB};
675 use reth_prune::PruneModes;
676 use reth_testing_utils::generators::{self, random_block_with_parent};
677 use tokio_stream::StreamExt;
678
679 #[test]
680 fn record_progress_calculates_outliers() {
681 let mut progress = PipelineProgress::default();
682
683 progress.update(10);
684 assert_eq!(progress.minimum_block_number, Some(10));
685 assert_eq!(progress.maximum_block_number, Some(10));
686
687 progress.update(20);
688 assert_eq!(progress.minimum_block_number, Some(10));
689 assert_eq!(progress.maximum_block_number, Some(20));
690
691 progress.update(1);
692 assert_eq!(progress.minimum_block_number, Some(1));
693 assert_eq!(progress.maximum_block_number, Some(20));
694 }
695
696 #[test]
697 fn progress_ctrl_flow() {
698 let mut progress = PipelineProgress::default();
699
700 assert_eq!(progress.next_ctrl(), ControlFlow::NoProgress { block_number: None });
701
702 progress.update(1);
703 assert_eq!(progress.next_ctrl(), ControlFlow::Continue { block_number: 1 });
704 }
705
706 #[tokio::test]
708 async fn run_pipeline() {
709 let provider_factory = create_test_provider_factory();
710
711 let stage_a = TestStage::new(StageId::Other("A"))
712 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }));
713 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
714 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
715
716 let stage_b = TestStage::new(StageId::Other("B"))
717 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }));
718 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
719 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
720
721 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
722 .add_stage(stage_a)
723 .add_stage(stage_b)
724 .with_max_block(10)
725 .build(
726 provider_factory.clone(),
727 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
728 );
729 let events = pipeline.events();
730
731 tokio::spawn(async move {
733 pipeline.run().await.unwrap();
734 });
735
736 assert_eq!(
738 events.collect::<Vec<PipelineEvent>>().await,
739 vec![
740 PipelineEvent::Prepare {
741 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
742 stage_id: StageId::Other("A"),
743 checkpoint: None,
744 target: Some(10),
745 },
746 PipelineEvent::Run {
747 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
748 stage_id: StageId::Other("A"),
749 checkpoint: None,
750 target: Some(10),
751 },
752 PipelineEvent::Ran {
753 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
754 stage_id: StageId::Other("A"),
755 result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true },
756 },
757 PipelineEvent::Prepare {
758 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
759 stage_id: StageId::Other("B"),
760 checkpoint: None,
761 target: Some(10),
762 },
763 PipelineEvent::Run {
764 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
765 stage_id: StageId::Other("B"),
766 checkpoint: None,
767 target: Some(10),
768 },
769 PipelineEvent::Ran {
770 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
771 stage_id: StageId::Other("B"),
772 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
773 },
774 ]
775 );
776
777 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
778 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 0);
779
780 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
781 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 0);
782 }
783
784 #[tokio::test]
786 async fn unwind_pipeline() {
787 let provider_factory = create_test_provider_factory();
788
789 let stage_a = TestStage::new(StageId::Other("A"))
790 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }))
791 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
792 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
793 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
794
795 let stage_b = TestStage::new(StageId::Other("B"))
796 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }))
797 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
798 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
799 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
800
801 let stage_c = TestStage::new(StageId::Other("C"))
802 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }))
803 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
804 let (stage_c, post_execute_commit_counter_c) = stage_c.with_post_execute_commit_counter();
805 let (stage_c, post_unwind_commit_counter_c) = stage_c.with_post_unwind_commit_counter();
806
807 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
808 .add_stage(stage_a)
809 .add_stage(stage_b)
810 .add_stage(stage_c)
811 .with_max_block(10)
812 .build(
813 provider_factory.clone(),
814 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
815 );
816 let events = pipeline.events();
817
818 tokio::spawn(async move {
820 pipeline.run().await.expect("Could not run pipeline");
822
823 pipeline.unwind(1, None).expect("Could not unwind pipeline");
825 });
826
827 assert_eq!(
829 events.collect::<Vec<PipelineEvent>>().await,
830 vec![
831 PipelineEvent::Prepare {
833 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
834 stage_id: StageId::Other("A"),
835 checkpoint: None,
836 target: Some(10),
837 },
838 PipelineEvent::Run {
839 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
840 stage_id: StageId::Other("A"),
841 checkpoint: None,
842 target: Some(10),
843 },
844 PipelineEvent::Ran {
845 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
846 stage_id: StageId::Other("A"),
847 result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true },
848 },
849 PipelineEvent::Prepare {
850 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
851 stage_id: StageId::Other("B"),
852 checkpoint: None,
853 target: Some(10),
854 },
855 PipelineEvent::Run {
856 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
857 stage_id: StageId::Other("B"),
858 checkpoint: None,
859 target: Some(10),
860 },
861 PipelineEvent::Ran {
862 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
863 stage_id: StageId::Other("B"),
864 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
865 },
866 PipelineEvent::Prepare {
867 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
868 stage_id: StageId::Other("C"),
869 checkpoint: None,
870 target: Some(10),
871 },
872 PipelineEvent::Run {
873 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
874 stage_id: StageId::Other("C"),
875 checkpoint: None,
876 target: Some(10),
877 },
878 PipelineEvent::Ran {
879 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
880 stage_id: StageId::Other("C"),
881 result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true },
882 },
883 PipelineEvent::Unwind {
885 stage_id: StageId::Other("C"),
886 input: UnwindInput {
887 checkpoint: StageCheckpoint::new(20),
888 unwind_to: 1,
889 bad_block: None
890 }
891 },
892 PipelineEvent::Unwound {
893 stage_id: StageId::Other("C"),
894 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
895 },
896 PipelineEvent::Unwind {
897 stage_id: StageId::Other("B"),
898 input: UnwindInput {
899 checkpoint: StageCheckpoint::new(10),
900 unwind_to: 1,
901 bad_block: None
902 }
903 },
904 PipelineEvent::Unwound {
905 stage_id: StageId::Other("B"),
906 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
907 },
908 PipelineEvent::Unwind {
909 stage_id: StageId::Other("A"),
910 input: UnwindInput {
911 checkpoint: StageCheckpoint::new(100),
912 unwind_to: 1,
913 bad_block: None
914 }
915 },
916 PipelineEvent::Unwound {
917 stage_id: StageId::Other("A"),
918 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
919 },
920 ]
921 );
922
923 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
924 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 1);
925
926 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
927 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 1);
928
929 assert_eq!(post_execute_commit_counter_c.load(Ordering::Relaxed), 1);
930 assert_eq!(post_unwind_commit_counter_c.load(Ordering::Relaxed), 1);
931 }
932
933 #[tokio::test]
935 async fn unwind_pipeline_with_intermediate_progress() {
936 let provider_factory = create_test_provider_factory();
937
938 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
939 .add_stage(
940 TestStage::new(StageId::Other("A"))
941 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }))
942 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(50) })),
943 )
944 .add_stage(
945 TestStage::new(StageId::Other("B"))
946 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
947 )
948 .with_max_block(10)
949 .build(
950 provider_factory.clone(),
951 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
952 );
953 let events = pipeline.events();
954
955 tokio::spawn(async move {
957 pipeline.run().await.expect("Could not run pipeline");
959
960 pipeline.unwind(50, None).expect("Could not unwind pipeline");
962 });
963
964 assert_eq!(
966 events.collect::<Vec<PipelineEvent>>().await,
967 vec![
968 PipelineEvent::Prepare {
970 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
971 stage_id: StageId::Other("A"),
972 checkpoint: None,
973 target: Some(10),
974 },
975 PipelineEvent::Run {
976 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
977 stage_id: StageId::Other("A"),
978 checkpoint: None,
979 target: Some(10),
980 },
981 PipelineEvent::Ran {
982 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
983 stage_id: StageId::Other("A"),
984 result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true },
985 },
986 PipelineEvent::Prepare {
987 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
988 stage_id: StageId::Other("B"),
989 checkpoint: None,
990 target: Some(10),
991 },
992 PipelineEvent::Run {
993 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
994 stage_id: StageId::Other("B"),
995 checkpoint: None,
996 target: Some(10),
997 },
998 PipelineEvent::Ran {
999 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1000 stage_id: StageId::Other("B"),
1001 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1002 },
1003 PipelineEvent::Skipped { stage_id: StageId::Other("B") },
1006 PipelineEvent::Unwind {
1007 stage_id: StageId::Other("A"),
1008 input: UnwindInput {
1009 checkpoint: StageCheckpoint::new(100),
1010 unwind_to: 50,
1011 bad_block: None
1012 }
1013 },
1014 PipelineEvent::Unwound {
1015 stage_id: StageId::Other("A"),
1016 result: UnwindOutput { checkpoint: StageCheckpoint::new(50) },
1017 },
1018 ]
1019 );
1020 }
1021
1022 #[tokio::test]
1035 async fn run_pipeline_with_unwind() {
1036 let provider_factory = create_test_provider_factory();
1037
1038 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1039 .add_stage(
1040 TestStage::new(StageId::Other("A"))
1041 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }))
1042 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1043 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1044 )
1045 .add_stage(
1046 TestStage::new(StageId::Other("B"))
1047 .add_exec(Err(StageError::Block {
1048 block: Box::new(random_block_with_parent(
1049 &mut generators::rng(),
1050 5,
1051 Default::default(),
1052 )),
1053 error: BlockErrorKind::Validation(ConsensusError::BaseFeeMissing),
1054 }))
1055 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1056 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1057 )
1058 .with_max_block(10)
1059 .build(
1060 provider_factory.clone(),
1061 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1062 );
1063 let events = pipeline.events();
1064
1065 tokio::spawn(async move {
1067 pipeline.run().await.expect("Could not run pipeline");
1068 });
1069
1070 assert_eq!(
1072 events.collect::<Vec<PipelineEvent>>().await,
1073 vec![
1074 PipelineEvent::Prepare {
1075 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1076 stage_id: StageId::Other("A"),
1077 checkpoint: None,
1078 target: Some(10),
1079 },
1080 PipelineEvent::Run {
1081 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1082 stage_id: StageId::Other("A"),
1083 checkpoint: None,
1084 target: Some(10),
1085 },
1086 PipelineEvent::Ran {
1087 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1088 stage_id: StageId::Other("A"),
1089 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1090 },
1091 PipelineEvent::Prepare {
1092 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1093 stage_id: StageId::Other("B"),
1094 checkpoint: None,
1095 target: Some(10),
1096 },
1097 PipelineEvent::Run {
1098 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1099 stage_id: StageId::Other("B"),
1100 checkpoint: None,
1101 target: Some(10),
1102 },
1103 PipelineEvent::Error { stage_id: StageId::Other("B") },
1104 PipelineEvent::Unwind {
1105 stage_id: StageId::Other("A"),
1106 input: UnwindInput {
1107 checkpoint: StageCheckpoint::new(10),
1108 unwind_to: 0,
1109 bad_block: Some(5)
1110 }
1111 },
1112 PipelineEvent::Unwound {
1113 stage_id: StageId::Other("A"),
1114 result: UnwindOutput { checkpoint: StageCheckpoint::new(0) },
1115 },
1116 PipelineEvent::Prepare {
1117 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1118 stage_id: StageId::Other("A"),
1119 checkpoint: Some(StageCheckpoint::new(0)),
1120 target: Some(10),
1121 },
1122 PipelineEvent::Run {
1123 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1124 stage_id: StageId::Other("A"),
1125 checkpoint: Some(StageCheckpoint::new(0)),
1126 target: Some(10),
1127 },
1128 PipelineEvent::Ran {
1129 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1130 stage_id: StageId::Other("A"),
1131 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1132 },
1133 PipelineEvent::Prepare {
1134 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1135 stage_id: StageId::Other("B"),
1136 checkpoint: None,
1137 target: Some(10),
1138 },
1139 PipelineEvent::Run {
1140 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1141 stage_id: StageId::Other("B"),
1142 checkpoint: None,
1143 target: Some(10),
1144 },
1145 PipelineEvent::Ran {
1146 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1147 stage_id: StageId::Other("B"),
1148 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1149 },
1150 ]
1151 );
1152 }
1153
1154 #[tokio::test]
1156 async fn pipeline_error_handling() {
1157 let provider_factory = create_test_provider_factory();
1159 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1160 .add_stage(
1161 TestStage::new(StageId::Other("NonFatal"))
1162 .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error))))
1163 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1164 )
1165 .with_max_block(10)
1166 .build(
1167 provider_factory.clone(),
1168 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1169 );
1170 let result = pipeline.run().await;
1171 assert_matches!(result, Ok(()));
1172
1173 let provider_factory = create_test_provider_factory();
1175 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1176 .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err(
1177 StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)),
1178 )))
1179 .build(
1180 provider_factory.clone(),
1181 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1182 );
1183 let result = pipeline.run().await;
1184 assert_matches!(
1185 result,
1186 Err(PipelineError::Stage(StageError::DatabaseIntegrity(
1187 ProviderError::BlockBodyIndicesNotFound(5)
1188 )))
1189 );
1190 }
1191}