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.run_loop_until(None).await
225 }
226
227 pub async fn run_until(
238 &mut self,
239 last_stage: StageId,
240 target: Option<PipelineTarget>,
241 ) -> Result<ControlFlow, PipelineError> {
242 let _ = self.register_metrics();
243 if !self.stages.iter().any(|stage| stage.id() == last_stage) {
244 return Err(PipelineError::MissingStage(last_stage))
245 }
246
247 match target {
248 Some(PipelineTarget::Sync(tip)) => self.set_tip(tip),
249 Some(PipelineTarget::Unwind(target)) => {
251 self.move_to_static_files()?;
252 self.unwind(target, None)?;
253 self.progress.update(target);
254 return Ok(ControlFlow::Continue { block_number: target })
255 }
256 None => {}
257 }
258
259 self.run_loop_until(Some(last_stage)).await
260 }
261
262 async fn run_loop_until(
264 &mut self,
265 last_stage: Option<StageId>,
266 ) -> Result<ControlFlow, PipelineError> {
267 self.move_to_static_files()?;
268
269 let mut previous_stage = None;
270 for stage_index in 0..self.stages.len() {
271 let stage = &self.stages[stage_index];
272 let stage_id = stage.id();
273
274 trace!(target: "sync::pipeline", stage = %stage_id, "Executing stage");
275 let next = self.execute_stage_to_completion(previous_stage, stage_index).await?;
276
277 trace!(target: "sync::pipeline", stage = %stage_id, ?next, "Completed stage");
278
279 match next {
280 ControlFlow::NoProgress { block_number } => {
281 if let Some(block_number) = block_number {
282 self.progress.update(block_number);
283 }
284 }
285 ControlFlow::Continue { block_number } => self.progress.update(block_number),
286 ControlFlow::Unwind { target, bad_block } => {
287 self.unwind(target, Some(bad_block.block.number))?;
288 return Ok(ControlFlow::Unwind { target, bad_block })
289 }
290 }
291
292 previous_stage = Some(
293 self.provider_factory
294 .provider()?
295 .get_stage_checkpoint(stage_id)?
296 .unwrap_or_default()
297 .block_number,
298 );
299
300 if last_stage == Some(stage_id) {
301 break
302 }
303 }
304
305 Ok(self.progress.next_ctrl())
306 }
307
308 pub fn move_to_static_files(&self) -> RethResult<()> {
325 if self.provider_factory.cached_storage_settings().is_v2() {
326 return Ok(())
327 }
328
329 let lowest_static_file_height =
331 self.static_file_producer.lock().copy_to_static_files()?.min_block_num();
332
333 if let Some(prune_tip) = lowest_static_file_height {
335 let mut pruner = PrunerBuilder::new(Default::default())
338 .delete_limit(usize::MAX)
339 .build_with_provider_factory(self.provider_factory.clone());
340
341 pruner.run(prune_tip)?;
342 }
343
344 Ok(())
345 }
346
347 pub fn unwind(
351 &mut self,
352 to: BlockNumber,
353 bad_block: Option<BlockNumber>,
354 ) -> Result<(), PipelineError> {
355 let (latest_block, prune_modes, checkpoints) = {
357 let provider = self.provider_factory.provider()?;
358 (
359 provider.last_block_number()?,
360 provider.prune_modes_ref().clone(),
361 provider.get_prune_checkpoints()?,
362 )
363 };
364 prune_modes.ensure_unwind_target_unpruned(latest_block, to, &checkpoints)?;
365
366 let unwind_pipeline = self.stages.iter_mut().rev();
368
369 let _locked_sf_producer = self.static_file_producer.lock();
372
373 let mut provider_rw =
374 self.provider_factory.unwind_provider_rw()?.disable_long_read_transaction_safety();
375
376 for stage in unwind_pipeline {
377 let stage_id = stage.id();
378 let span = info_span!("Unwinding", stage = %stage_id);
379 let _enter = span.enter();
380
381 let mut checkpoint = provider_rw.get_stage_checkpoint(stage_id)?.unwrap_or_default();
382 if checkpoint.block_number < to {
383 debug!(
384 target: "sync::pipeline",
385 from = %checkpoint.block_number,
386 %to,
387 "Unwind point too far for stage"
388 );
389 self.event_sender.notify(PipelineEvent::Skipped { stage_id });
390
391 continue
392 }
393
394 info!(
395 target: "sync::pipeline",
396 from = %checkpoint.block_number,
397 %to,
398 ?bad_block,
399 "Starting unwind"
400 );
401 while checkpoint.block_number > to {
402 let unwind_started_at = Instant::now();
403 let input = UnwindInput { checkpoint, unwind_to: to, bad_block };
404 self.event_sender.notify(PipelineEvent::Unwind { stage_id, input });
405
406 let output = stage.unwind(&provider_rw, input);
407 match output {
408 Ok(unwind_output) => {
409 checkpoint = unwind_output.checkpoint;
410 info!(
411 target: "sync::pipeline",
412 stage = %stage_id,
413 unwind_to = to,
414 progress = checkpoint.block_number,
415 done = checkpoint.block_number == to,
416 "Stage unwound"
417 );
418
419 provider_rw.save_stage_checkpoint(stage_id, checkpoint)?;
420
421 self.event_sender
423 .notify(PipelineEvent::Unwound { stage_id, result: unwind_output });
424
425 if let Some(metrics_tx) = &mut self.metrics_tx {
426 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
427 stage_id,
428 checkpoint,
429 max_block_number: None,
432 elapsed: unwind_started_at.elapsed(),
433 });
434 }
435
436 let last_saved_finalized_block_number =
438 provider_rw.last_finalized_block_number()?;
439
440 if last_saved_finalized_block_number.is_none() ||
443 Some(checkpoint.block_number) < last_saved_finalized_block_number
444 {
445 provider_rw.save_finalized_block_number(BlockNumber::from(
446 checkpoint.block_number,
447 ))?;
448 }
449
450 let last_saved_safe_block_number = provider_rw.last_safe_block_number()?;
451
452 if last_saved_safe_block_number.is_none() ||
453 Some(checkpoint.block_number) < last_saved_safe_block_number
454 {
455 provider_rw.save_safe_block_number(BlockNumber::from(
456 checkpoint.block_number,
457 ))?;
458 }
459
460 provider_rw.commit()?;
461
462 stage.post_unwind_commit()?;
463
464 provider_rw = self.provider_factory.unwind_provider_rw()?;
465 }
466 Err(err) => {
467 self.event_sender.notify(PipelineEvent::Error { stage_id });
468
469 return Err(PipelineError::Stage(StageError::Fatal(Box::new(err))))
470 }
471 }
472 }
473 }
474
475 Ok(())
476 }
477
478 async fn execute_stage_to_completion(
479 &mut self,
480 previous_stage: Option<BlockNumber>,
481 stage_index: usize,
482 ) -> Result<ControlFlow, PipelineError> {
483 let total_stages = self.stages.len();
484
485 let stage_id = self.stage(stage_index).id();
486 let mut made_progress = false;
487 let target = self.max_block.or(previous_stage);
488
489 loop {
490 let prev_checkpoint = self.provider_factory.get_stage_checkpoint(stage_id)?;
491
492 let stage_reached_max_block = prev_checkpoint
493 .zip(self.max_block)
494 .is_some_and(|(prev_progress, target)| prev_progress.block_number >= target);
495 if stage_reached_max_block {
496 warn!(
497 target: "sync::pipeline",
498 stage = %stage_id,
499 max_block = self.max_block,
500 prev_block = prev_checkpoint.map(|progress| progress.block_number),
501 "Stage reached target block, skipping."
502 );
503 self.event_sender.notify(PipelineEvent::Skipped { stage_id });
504
505 return Ok(ControlFlow::NoProgress {
507 block_number: prev_checkpoint.map(|progress| progress.block_number),
508 })
509 }
510
511 let exec_input = ExecInput { target, checkpoint: prev_checkpoint };
512
513 self.event_sender.notify(PipelineEvent::Prepare {
514 pipeline_stages_progress: PipelineStagesProgress {
515 current: stage_index + 1,
516 total: total_stages,
517 },
518 stage_id,
519 checkpoint: prev_checkpoint,
520 target,
521 });
522
523 if let Err(err) = self.stage(stage_index).execute_ready(exec_input).await {
524 self.event_sender.notify(PipelineEvent::Error { stage_id });
525 match self.on_stage_error(stage_id, prev_checkpoint, err)? {
526 Some(ctrl) => return Ok(ctrl),
527 None => continue,
528 };
529 }
530
531 let stage_started_at = Instant::now();
532 let provider_rw = self.provider_factory.database_provider_rw()?;
533
534 self.event_sender.notify(PipelineEvent::Run {
535 pipeline_stages_progress: PipelineStagesProgress {
536 current: stage_index + 1,
537 total: total_stages,
538 },
539 stage_id,
540 checkpoint: prev_checkpoint,
541 target,
542 });
543
544 match self.stage(stage_index).execute(&provider_rw, exec_input) {
545 Ok(out @ ExecOutput { checkpoint, done }) => {
546 provider_rw.save_stage_checkpoint(stage_id, checkpoint)?;
548
549 provider_rw.commit()?;
551
552 self.stage(stage_index).post_execute_commit()?;
554
555 self.event_sender.notify(PipelineEvent::Ran {
557 pipeline_stages_progress: PipelineStagesProgress {
558 current: stage_index + 1,
559 total: total_stages,
560 },
561 stage_id,
562 result: out,
563 });
564 if let Some(metrics_tx) = &mut self.metrics_tx {
565 let _ = metrics_tx.send(MetricEvent::StageCheckpoint {
566 stage_id,
567 checkpoint,
568 max_block_number: target,
569 elapsed: stage_started_at.elapsed(),
570 });
571 }
572
573 let block_number = checkpoint.block_number;
574 let prev_block_number = prev_checkpoint.unwrap_or_default().block_number;
575 made_progress |= block_number != prev_block_number;
576 if done {
577 return Ok(if made_progress {
578 ControlFlow::Continue { block_number }
579 } else {
580 ControlFlow::NoProgress { block_number: Some(block_number) }
581 })
582 }
583 }
584 Err(err) => {
585 drop(provider_rw);
586 self.event_sender.notify(PipelineEvent::Error { stage_id });
587
588 if let Some(ctrl) = self.on_stage_error(stage_id, prev_checkpoint, err)? {
589 return Ok(ctrl)
590 }
591 }
592 }
593 }
594 }
595
596 fn on_stage_error(
597 &mut self,
598 stage_id: StageId,
599 prev_checkpoint: Option<StageCheckpoint>,
600 err: StageError,
601 ) -> Result<Option<ControlFlow>, PipelineError> {
602 if let StageError::DetachedHead { local_head, header, error } = err {
603 warn!(target: "sync::pipeline", stage = %stage_id, ?local_head, ?header, %error, "Stage encountered detached head");
604
605 if let Some(last_detached_head_unwind_target) = self.last_detached_head_unwind_target {
606 if local_head.block.hash == last_detached_head_unwind_target &&
607 header.block.number == local_head.block.number + 1
608 {
609 self.detached_head_attempts += 1;
610 } else {
611 self.detached_head_attempts = 1;
612 }
613 } else {
614 self.detached_head_attempts = 1;
615 }
616
617 let unwind_to = local_head
619 .block
620 .number
621 .saturating_sub(
622 BEACON_CONSENSUS_REORG_UNWIND_DEPTH.saturating_mul(self.detached_head_attempts),
623 )
624 .max(1);
625
626 self.last_detached_head_unwind_target = self.provider_factory.block_hash(unwind_to)?;
627 Ok(Some(ControlFlow::Unwind { target: unwind_to, bad_block: local_head }))
628 } else if let StageError::Block { block, error } = err {
629 match error {
630 BlockErrorKind::Validation(validation_error) => {
631 error!(
632 target: "sync::pipeline",
633 stage = %stage_id,
634 bad_block = %block.block.number,
635 bad_block_hash = %block.block.hash,
636 "Stage encountered a validation error: {validation_error}"
637 );
638
639 if stage_id == StageId::MerkleExecute {
644 let provider_rw = self.provider_factory.database_provider_rw()?;
645 provider_rw
646 .save_stage_checkpoint_progress(StageId::MerkleExecute, vec![])?;
647 provider_rw.save_stage_checkpoint(
648 StageId::MerkleExecute,
649 prev_checkpoint.unwrap_or_default(),
650 )?;
651
652 provider_rw.commit()?;
653 }
654 }
655 BlockErrorKind::Execution(execution_error) => {
656 error!(
657 target: "sync::pipeline",
658 stage = %stage_id,
659 bad_block = %block.block.number,
660 bad_block_hash = %block.block.hash,
661 "Stage encountered an execution error: {execution_error}"
662 );
663 }
664 }
665
666 Ok(Some(ControlFlow::Unwind {
669 target: prev_checkpoint.unwrap_or_default().block_number,
670 bad_block: block,
671 }))
672 } else if let StageError::MissingStaticFileData { block, segment } = err {
673 error!(
674 target: "sync::pipeline",
675 stage = %stage_id,
676 bad_block = %block.block.number,
677 segment = %segment,
678 "Stage is missing static file data."
679 );
680
681 Ok(Some(ControlFlow::Unwind {
682 target: block.block.number.saturating_sub(1),
683 bad_block: block,
684 }))
685 } else if err.is_fatal() {
686 error!(target: "sync::pipeline", stage = %stage_id, "Stage encountered a fatal error: {err}");
687 Err(err.into())
688 } else {
689 warn!(
692 target: "sync::pipeline",
693 stage = %stage_id,
694 "Stage encountered a non-fatal error: {err}. Retrying..."
695 );
696 Ok(None)
697 }
698 }
699}
700
701impl<N: ProviderNodeTypes> std::fmt::Debug for Pipeline<N> {
702 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
703 f.debug_struct("Pipeline")
704 .field("stages", &self.stages.iter().map(|stage| stage.id()).collect::<Vec<StageId>>())
705 .field("max_block", &self.max_block)
706 .field("event_sender", &self.event_sender)
707 .field("fail_on_unwind", &self.fail_on_unwind)
708 .finish()
709 }
710}
711
712#[cfg(test)]
713mod tests {
714 use std::sync::atomic::Ordering;
715
716 use super::*;
717 use crate::{test_utils::TestStage, UnwindOutput};
718 use assert_matches::assert_matches;
719 use reth_consensus::ConsensusError;
720 use reth_errors::ProviderError;
721 use reth_provider::test_utils::{create_test_provider_factory, MockNodeTypesWithDB};
722 use reth_prune::PruneModes;
723 use reth_testing_utils::generators::{self, random_block_with_parent};
724 use tokio_stream::StreamExt;
725
726 #[test]
727 fn record_progress_calculates_outliers() {
728 let mut progress = PipelineProgress::default();
729
730 progress.update(10);
731 assert_eq!(progress.minimum_block_number, Some(10));
732 assert_eq!(progress.maximum_block_number, Some(10));
733
734 progress.update(20);
735 assert_eq!(progress.minimum_block_number, Some(10));
736 assert_eq!(progress.maximum_block_number, Some(20));
737
738 progress.update(1);
739 assert_eq!(progress.minimum_block_number, Some(1));
740 assert_eq!(progress.maximum_block_number, Some(20));
741 }
742
743 #[test]
744 fn progress_ctrl_flow() {
745 let mut progress = PipelineProgress::default();
746
747 assert_eq!(progress.next_ctrl(), ControlFlow::NoProgress { block_number: None });
748
749 progress.update(1);
750 assert_eq!(progress.next_ctrl(), ControlFlow::Continue { block_number: 1 });
751 }
752
753 #[tokio::test]
755 async fn run_until_stops_after_the_named_stage() {
756 let provider_factory = create_test_provider_factory();
757
758 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
759 .add_stage(
760 TestStage::new(StageId::Other("A"))
761 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
762 )
763 .add_stage(TestStage::new(StageId::Other("B")))
765 .with_max_block(10)
766 .build(
767 provider_factory.clone(),
768 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
769 );
770
771 pipeline.run_until(StageId::Other("A"), None).await.unwrap();
772
773 let provider = provider_factory.provider().unwrap();
774 assert_eq!(
775 provider.get_stage_checkpoint(StageId::Other("A")).unwrap(),
776 Some(StageCheckpoint::new(10))
777 );
778 assert_eq!(provider.get_stage_checkpoint(StageId::Other("B")).unwrap(), None);
779 }
780
781 #[tokio::test]
783 async fn run_until_rejects_an_unknown_stage() {
784 let provider_factory = create_test_provider_factory();
785
786 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
787 .add_stage(TestStage::new(StageId::Other("A")))
788 .build(
789 provider_factory.clone(),
790 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
791 );
792
793 let result = pipeline.run_until(StageId::Other("Z"), None).await;
794
795 assert_matches!(result, Err(PipelineError::MissingStage(StageId::Other("Z"))));
796 }
797
798 #[tokio::test]
800 async fn run_pipeline() {
801 let provider_factory = create_test_provider_factory();
802
803 let stage_a = TestStage::new(StageId::Other("A"))
804 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }));
805 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
806 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
807
808 let stage_b = TestStage::new(StageId::Other("B"))
809 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }));
810 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
811 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
812
813 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
814 .add_stage(stage_a)
815 .add_stage(stage_b)
816 .with_max_block(10)
817 .build(
818 provider_factory.clone(),
819 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
820 );
821 let events = pipeline.events();
822
823 tokio::spawn(async move {
825 pipeline.run().await.unwrap();
826 });
827
828 assert_eq!(
830 events.collect::<Vec<PipelineEvent>>().await,
831 vec![
832 PipelineEvent::Prepare {
833 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
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: 2 },
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: 2 },
846 stage_id: StageId::Other("A"),
847 result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true },
848 },
849 PipelineEvent::Prepare {
850 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
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: 2 },
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: 2 },
863 stage_id: StageId::Other("B"),
864 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
865 },
866 ]
867 );
868
869 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
870 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 0);
871
872 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
873 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 0);
874 }
875
876 #[tokio::test]
878 async fn unwind_pipeline() {
879 let provider_factory = create_test_provider_factory();
880
881 let stage_a = TestStage::new(StageId::Other("A"))
882 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }))
883 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
884 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
885 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
886
887 let stage_b = TestStage::new(StageId::Other("B"))
888 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }))
889 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
890 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
891 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
892
893 let stage_c = TestStage::new(StageId::Other("C"))
894 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }))
895 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
896 let (stage_c, post_execute_commit_counter_c) = stage_c.with_post_execute_commit_counter();
897 let (stage_c, post_unwind_commit_counter_c) = stage_c.with_post_unwind_commit_counter();
898
899 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
900 .add_stage(stage_a)
901 .add_stage(stage_b)
902 .add_stage(stage_c)
903 .with_max_block(10)
904 .build(
905 provider_factory.clone(),
906 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
907 );
908 let events = pipeline.events();
909
910 tokio::spawn(async move {
912 pipeline.run().await.expect("Could not run pipeline");
914
915 pipeline.unwind(1, None).expect("Could not unwind pipeline");
917 });
918
919 assert_eq!(
921 events.collect::<Vec<PipelineEvent>>().await,
922 vec![
923 PipelineEvent::Prepare {
925 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
926 stage_id: StageId::Other("A"),
927 checkpoint: None,
928 target: Some(10),
929 },
930 PipelineEvent::Run {
931 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
932 stage_id: StageId::Other("A"),
933 checkpoint: None,
934 target: Some(10),
935 },
936 PipelineEvent::Ran {
937 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
938 stage_id: StageId::Other("A"),
939 result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true },
940 },
941 PipelineEvent::Prepare {
942 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
943 stage_id: StageId::Other("B"),
944 checkpoint: None,
945 target: Some(10),
946 },
947 PipelineEvent::Run {
948 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
949 stage_id: StageId::Other("B"),
950 checkpoint: None,
951 target: Some(10),
952 },
953 PipelineEvent::Ran {
954 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
955 stage_id: StageId::Other("B"),
956 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
957 },
958 PipelineEvent::Prepare {
959 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
960 stage_id: StageId::Other("C"),
961 checkpoint: None,
962 target: Some(10),
963 },
964 PipelineEvent::Run {
965 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
966 stage_id: StageId::Other("C"),
967 checkpoint: None,
968 target: Some(10),
969 },
970 PipelineEvent::Ran {
971 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
972 stage_id: StageId::Other("C"),
973 result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true },
974 },
975 PipelineEvent::Unwind {
977 stage_id: StageId::Other("C"),
978 input: UnwindInput {
979 checkpoint: StageCheckpoint::new(20),
980 unwind_to: 1,
981 bad_block: None
982 }
983 },
984 PipelineEvent::Unwound {
985 stage_id: StageId::Other("C"),
986 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
987 },
988 PipelineEvent::Unwind {
989 stage_id: StageId::Other("B"),
990 input: UnwindInput {
991 checkpoint: StageCheckpoint::new(10),
992 unwind_to: 1,
993 bad_block: None
994 }
995 },
996 PipelineEvent::Unwound {
997 stage_id: StageId::Other("B"),
998 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
999 },
1000 PipelineEvent::Unwind {
1001 stage_id: StageId::Other("A"),
1002 input: UnwindInput {
1003 checkpoint: StageCheckpoint::new(100),
1004 unwind_to: 1,
1005 bad_block: None
1006 }
1007 },
1008 PipelineEvent::Unwound {
1009 stage_id: StageId::Other("A"),
1010 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
1011 },
1012 ]
1013 );
1014
1015 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
1016 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 1);
1017
1018 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
1019 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 1);
1020
1021 assert_eq!(post_execute_commit_counter_c.load(Ordering::Relaxed), 1);
1022 assert_eq!(post_unwind_commit_counter_c.load(Ordering::Relaxed), 1);
1023 }
1024
1025 #[tokio::test]
1027 async fn unwind_pipeline_with_intermediate_progress() {
1028 let provider_factory = create_test_provider_factory();
1029
1030 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1031 .add_stage(
1032 TestStage::new(StageId::Other("A"))
1033 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }))
1034 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(50) })),
1035 )
1036 .add_stage(
1037 TestStage::new(StageId::Other("B"))
1038 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1039 )
1040 .with_max_block(10)
1041 .build(
1042 provider_factory.clone(),
1043 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1044 );
1045 let events = pipeline.events();
1046
1047 tokio::spawn(async move {
1049 pipeline.run().await.expect("Could not run pipeline");
1051
1052 pipeline.unwind(50, None).expect("Could not unwind pipeline");
1054 });
1055
1056 assert_eq!(
1058 events.collect::<Vec<PipelineEvent>>().await,
1059 vec![
1060 PipelineEvent::Prepare {
1062 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1063 stage_id: StageId::Other("A"),
1064 checkpoint: None,
1065 target: Some(10),
1066 },
1067 PipelineEvent::Run {
1068 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1069 stage_id: StageId::Other("A"),
1070 checkpoint: None,
1071 target: Some(10),
1072 },
1073 PipelineEvent::Ran {
1074 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1075 stage_id: StageId::Other("A"),
1076 result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true },
1077 },
1078 PipelineEvent::Prepare {
1079 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1080 stage_id: StageId::Other("B"),
1081 checkpoint: None,
1082 target: Some(10),
1083 },
1084 PipelineEvent::Run {
1085 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1086 stage_id: StageId::Other("B"),
1087 checkpoint: None,
1088 target: Some(10),
1089 },
1090 PipelineEvent::Ran {
1091 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1092 stage_id: StageId::Other("B"),
1093 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1094 },
1095 PipelineEvent::Skipped { stage_id: StageId::Other("B") },
1098 PipelineEvent::Unwind {
1099 stage_id: StageId::Other("A"),
1100 input: UnwindInput {
1101 checkpoint: StageCheckpoint::new(100),
1102 unwind_to: 50,
1103 bad_block: None
1104 }
1105 },
1106 PipelineEvent::Unwound {
1107 stage_id: StageId::Other("A"),
1108 result: UnwindOutput { checkpoint: StageCheckpoint::new(50) },
1109 },
1110 ]
1111 );
1112 }
1113
1114 #[tokio::test]
1127 async fn run_pipeline_with_unwind() {
1128 let provider_factory = create_test_provider_factory();
1129
1130 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1131 .add_stage(
1132 TestStage::new(StageId::Other("A"))
1133 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }))
1134 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1135 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1136 )
1137 .add_stage(
1138 TestStage::new(StageId::Other("B"))
1139 .add_exec(Err(StageError::Block {
1140 block: Box::new(random_block_with_parent(
1141 &mut generators::rng(),
1142 5,
1143 Default::default(),
1144 )),
1145 error: BlockErrorKind::Validation(ConsensusError::BaseFeeMissing),
1146 }))
1147 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1148 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1149 )
1150 .with_max_block(10)
1151 .build(
1152 provider_factory.clone(),
1153 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1154 );
1155 let events = pipeline.events();
1156
1157 tokio::spawn(async move {
1159 pipeline.run().await.expect("Could not run pipeline");
1160 });
1161
1162 assert_eq!(
1164 events.collect::<Vec<PipelineEvent>>().await,
1165 vec![
1166 PipelineEvent::Prepare {
1167 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1168 stage_id: StageId::Other("A"),
1169 checkpoint: None,
1170 target: Some(10),
1171 },
1172 PipelineEvent::Run {
1173 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1174 stage_id: StageId::Other("A"),
1175 checkpoint: None,
1176 target: Some(10),
1177 },
1178 PipelineEvent::Ran {
1179 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1180 stage_id: StageId::Other("A"),
1181 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1182 },
1183 PipelineEvent::Prepare {
1184 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1185 stage_id: StageId::Other("B"),
1186 checkpoint: None,
1187 target: Some(10),
1188 },
1189 PipelineEvent::Run {
1190 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1191 stage_id: StageId::Other("B"),
1192 checkpoint: None,
1193 target: Some(10),
1194 },
1195 PipelineEvent::Error { stage_id: StageId::Other("B") },
1196 PipelineEvent::Unwind {
1197 stage_id: StageId::Other("A"),
1198 input: UnwindInput {
1199 checkpoint: StageCheckpoint::new(10),
1200 unwind_to: 0,
1201 bad_block: Some(5)
1202 }
1203 },
1204 PipelineEvent::Unwound {
1205 stage_id: StageId::Other("A"),
1206 result: UnwindOutput { checkpoint: StageCheckpoint::new(0) },
1207 },
1208 PipelineEvent::Prepare {
1209 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1210 stage_id: StageId::Other("A"),
1211 checkpoint: Some(StageCheckpoint::new(0)),
1212 target: Some(10),
1213 },
1214 PipelineEvent::Run {
1215 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1216 stage_id: StageId::Other("A"),
1217 checkpoint: Some(StageCheckpoint::new(0)),
1218 target: Some(10),
1219 },
1220 PipelineEvent::Ran {
1221 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1222 stage_id: StageId::Other("A"),
1223 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1224 },
1225 PipelineEvent::Prepare {
1226 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1227 stage_id: StageId::Other("B"),
1228 checkpoint: None,
1229 target: Some(10),
1230 },
1231 PipelineEvent::Run {
1232 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1233 stage_id: StageId::Other("B"),
1234 checkpoint: None,
1235 target: Some(10),
1236 },
1237 PipelineEvent::Ran {
1238 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1239 stage_id: StageId::Other("B"),
1240 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1241 },
1242 ]
1243 );
1244 }
1245
1246 #[tokio::test]
1248 async fn pipeline_error_handling() {
1249 let provider_factory = create_test_provider_factory();
1251 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1252 .add_stage(
1253 TestStage::new(StageId::Other("NonFatal"))
1254 .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error))))
1255 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1256 )
1257 .with_max_block(10)
1258 .build(
1259 provider_factory.clone(),
1260 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1261 );
1262 let result = pipeline.run().await;
1263 assert_matches!(result, Ok(()));
1264
1265 let provider_factory = create_test_provider_factory();
1267 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1268 .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err(
1269 StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)),
1270 )))
1271 .build(
1272 provider_factory.clone(),
1273 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1274 );
1275 let result = pipeline.run().await;
1276 assert_matches!(
1277 result,
1278 Err(PipelineError::Stage(StageError::DatabaseIntegrity(
1279 ProviderError::BlockBodyIndicesNotFound(5)
1280 )))
1281 );
1282 }
1283}