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.clone(),
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 "Stage encountered a validation error: {validation_error}"
589 );
590
591 if stage_id == StageId::MerkleExecute {
596 let provider_rw = self.provider_factory.database_provider_rw()?;
597 provider_rw
598 .save_stage_checkpoint_progress(StageId::MerkleExecute, vec![])?;
599 provider_rw.save_stage_checkpoint(
600 StageId::MerkleExecute,
601 prev_checkpoint.unwrap_or_default(),
602 )?;
603
604 provider_rw.commit()?;
605 }
606 }
607 BlockErrorKind::Execution(execution_error) => {
608 error!(
609 target: "sync::pipeline",
610 stage = %stage_id,
611 bad_block = %block.block.number,
612 "Stage encountered an execution error: {execution_error}"
613 );
614 }
615 }
616
617 Ok(Some(ControlFlow::Unwind {
620 target: prev_checkpoint.unwrap_or_default().block_number,
621 bad_block: block,
622 }))
623 } else if let StageError::MissingStaticFileData { block, segment } = err {
624 error!(
625 target: "sync::pipeline",
626 stage = %stage_id,
627 bad_block = %block.block.number,
628 segment = %segment,
629 "Stage is missing static file data."
630 );
631
632 Ok(Some(ControlFlow::Unwind {
633 target: block.block.number.saturating_sub(1),
634 bad_block: block,
635 }))
636 } else if err.is_fatal() {
637 error!(target: "sync::pipeline", stage = %stage_id, "Stage encountered a fatal error: {err}");
638 Err(err.into())
639 } else {
640 warn!(
643 target: "sync::pipeline",
644 stage = %stage_id,
645 "Stage encountered a non-fatal error: {err}. Retrying..."
646 );
647 Ok(None)
648 }
649 }
650}
651
652impl<N: ProviderNodeTypes> std::fmt::Debug for Pipeline<N> {
653 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
654 f.debug_struct("Pipeline")
655 .field("stages", &self.stages.iter().map(|stage| stage.id()).collect::<Vec<StageId>>())
656 .field("max_block", &self.max_block)
657 .field("event_sender", &self.event_sender)
658 .field("fail_on_unwind", &self.fail_on_unwind)
659 .finish()
660 }
661}
662
663#[cfg(test)]
664mod tests {
665 use std::sync::atomic::Ordering;
666
667 use super::*;
668 use crate::{test_utils::TestStage, UnwindOutput};
669 use assert_matches::assert_matches;
670 use reth_consensus::ConsensusError;
671 use reth_errors::ProviderError;
672 use reth_provider::test_utils::{create_test_provider_factory, MockNodeTypesWithDB};
673 use reth_prune::PruneModes;
674 use reth_testing_utils::generators::{self, random_block_with_parent};
675 use tokio_stream::StreamExt;
676
677 #[test]
678 fn record_progress_calculates_outliers() {
679 let mut progress = PipelineProgress::default();
680
681 progress.update(10);
682 assert_eq!(progress.minimum_block_number, Some(10));
683 assert_eq!(progress.maximum_block_number, Some(10));
684
685 progress.update(20);
686 assert_eq!(progress.minimum_block_number, Some(10));
687 assert_eq!(progress.maximum_block_number, Some(20));
688
689 progress.update(1);
690 assert_eq!(progress.minimum_block_number, Some(1));
691 assert_eq!(progress.maximum_block_number, Some(20));
692 }
693
694 #[test]
695 fn progress_ctrl_flow() {
696 let mut progress = PipelineProgress::default();
697
698 assert_eq!(progress.next_ctrl(), ControlFlow::NoProgress { block_number: None });
699
700 progress.update(1);
701 assert_eq!(progress.next_ctrl(), ControlFlow::Continue { block_number: 1 });
702 }
703
704 #[tokio::test]
706 async fn run_pipeline() {
707 let provider_factory = create_test_provider_factory();
708
709 let stage_a = TestStage::new(StageId::Other("A"))
710 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }));
711 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
712 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
713
714 let stage_b = TestStage::new(StageId::Other("B"))
715 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }));
716 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
717 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
718
719 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
720 .add_stage(stage_a)
721 .add_stage(stage_b)
722 .with_max_block(10)
723 .build(
724 provider_factory.clone(),
725 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
726 );
727 let events = pipeline.events();
728
729 tokio::spawn(async move {
731 pipeline.run().await.unwrap();
732 });
733
734 assert_eq!(
736 events.collect::<Vec<PipelineEvent>>().await,
737 vec![
738 PipelineEvent::Prepare {
739 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
740 stage_id: StageId::Other("A"),
741 checkpoint: None,
742 target: Some(10),
743 },
744 PipelineEvent::Run {
745 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
746 stage_id: StageId::Other("A"),
747 checkpoint: None,
748 target: Some(10),
749 },
750 PipelineEvent::Ran {
751 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
752 stage_id: StageId::Other("A"),
753 result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true },
754 },
755 PipelineEvent::Prepare {
756 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
757 stage_id: StageId::Other("B"),
758 checkpoint: None,
759 target: Some(10),
760 },
761 PipelineEvent::Run {
762 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
763 stage_id: StageId::Other("B"),
764 checkpoint: None,
765 target: Some(10),
766 },
767 PipelineEvent::Ran {
768 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
769 stage_id: StageId::Other("B"),
770 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
771 },
772 ]
773 );
774
775 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
776 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 0);
777
778 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
779 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 0);
780 }
781
782 #[tokio::test]
784 async fn unwind_pipeline() {
785 let provider_factory = create_test_provider_factory();
786
787 let stage_a = TestStage::new(StageId::Other("A"))
788 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }))
789 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
790 let (stage_a, post_execute_commit_counter_a) = stage_a.with_post_execute_commit_counter();
791 let (stage_a, post_unwind_commit_counter_a) = stage_a.with_post_unwind_commit_counter();
792
793 let stage_b = TestStage::new(StageId::Other("B"))
794 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }))
795 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
796 let (stage_b, post_execute_commit_counter_b) = stage_b.with_post_execute_commit_counter();
797 let (stage_b, post_unwind_commit_counter_b) = stage_b.with_post_unwind_commit_counter();
798
799 let stage_c = TestStage::new(StageId::Other("C"))
800 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }))
801 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) }));
802 let (stage_c, post_execute_commit_counter_c) = stage_c.with_post_execute_commit_counter();
803 let (stage_c, post_unwind_commit_counter_c) = stage_c.with_post_unwind_commit_counter();
804
805 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
806 .add_stage(stage_a)
807 .add_stage(stage_b)
808 .add_stage(stage_c)
809 .with_max_block(10)
810 .build(
811 provider_factory.clone(),
812 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
813 );
814 let events = pipeline.events();
815
816 tokio::spawn(async move {
818 pipeline.run().await.expect("Could not run pipeline");
820
821 pipeline.unwind(1, None).expect("Could not unwind pipeline");
823 });
824
825 assert_eq!(
827 events.collect::<Vec<PipelineEvent>>().await,
828 vec![
829 PipelineEvent::Prepare {
831 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
832 stage_id: StageId::Other("A"),
833 checkpoint: None,
834 target: Some(10),
835 },
836 PipelineEvent::Run {
837 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
838 stage_id: StageId::Other("A"),
839 checkpoint: None,
840 target: Some(10),
841 },
842 PipelineEvent::Ran {
843 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 3 },
844 stage_id: StageId::Other("A"),
845 result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true },
846 },
847 PipelineEvent::Prepare {
848 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
849 stage_id: StageId::Other("B"),
850 checkpoint: None,
851 target: Some(10),
852 },
853 PipelineEvent::Run {
854 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
855 stage_id: StageId::Other("B"),
856 checkpoint: None,
857 target: Some(10),
858 },
859 PipelineEvent::Ran {
860 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 3 },
861 stage_id: StageId::Other("B"),
862 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
863 },
864 PipelineEvent::Prepare {
865 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
866 stage_id: StageId::Other("C"),
867 checkpoint: None,
868 target: Some(10),
869 },
870 PipelineEvent::Run {
871 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
872 stage_id: StageId::Other("C"),
873 checkpoint: None,
874 target: Some(10),
875 },
876 PipelineEvent::Ran {
877 pipeline_stages_progress: PipelineStagesProgress { current: 3, total: 3 },
878 stage_id: StageId::Other("C"),
879 result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true },
880 },
881 PipelineEvent::Unwind {
883 stage_id: StageId::Other("C"),
884 input: UnwindInput {
885 checkpoint: StageCheckpoint::new(20),
886 unwind_to: 1,
887 bad_block: None
888 }
889 },
890 PipelineEvent::Unwound {
891 stage_id: StageId::Other("C"),
892 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
893 },
894 PipelineEvent::Unwind {
895 stage_id: StageId::Other("B"),
896 input: UnwindInput {
897 checkpoint: StageCheckpoint::new(10),
898 unwind_to: 1,
899 bad_block: None
900 }
901 },
902 PipelineEvent::Unwound {
903 stage_id: StageId::Other("B"),
904 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
905 },
906 PipelineEvent::Unwind {
907 stage_id: StageId::Other("A"),
908 input: UnwindInput {
909 checkpoint: StageCheckpoint::new(100),
910 unwind_to: 1,
911 bad_block: None
912 }
913 },
914 PipelineEvent::Unwound {
915 stage_id: StageId::Other("A"),
916 result: UnwindOutput { checkpoint: StageCheckpoint::new(1) },
917 },
918 ]
919 );
920
921 assert_eq!(post_execute_commit_counter_a.load(Ordering::Relaxed), 1);
922 assert_eq!(post_unwind_commit_counter_a.load(Ordering::Relaxed), 1);
923
924 assert_eq!(post_execute_commit_counter_b.load(Ordering::Relaxed), 1);
925 assert_eq!(post_unwind_commit_counter_b.load(Ordering::Relaxed), 1);
926
927 assert_eq!(post_execute_commit_counter_c.load(Ordering::Relaxed), 1);
928 assert_eq!(post_unwind_commit_counter_c.load(Ordering::Relaxed), 1);
929 }
930
931 #[tokio::test]
933 async fn unwind_pipeline_with_intermediate_progress() {
934 let provider_factory = create_test_provider_factory();
935
936 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
937 .add_stage(
938 TestStage::new(StageId::Other("A"))
939 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }))
940 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(50) })),
941 )
942 .add_stage(
943 TestStage::new(StageId::Other("B"))
944 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
945 )
946 .with_max_block(10)
947 .build(
948 provider_factory.clone(),
949 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
950 );
951 let events = pipeline.events();
952
953 tokio::spawn(async move {
955 pipeline.run().await.expect("Could not run pipeline");
957
958 pipeline.unwind(50, None).expect("Could not unwind pipeline");
960 });
961
962 assert_eq!(
964 events.collect::<Vec<PipelineEvent>>().await,
965 vec![
966 PipelineEvent::Prepare {
968 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
969 stage_id: StageId::Other("A"),
970 checkpoint: None,
971 target: Some(10),
972 },
973 PipelineEvent::Run {
974 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
975 stage_id: StageId::Other("A"),
976 checkpoint: None,
977 target: Some(10),
978 },
979 PipelineEvent::Ran {
980 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
981 stage_id: StageId::Other("A"),
982 result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true },
983 },
984 PipelineEvent::Prepare {
985 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
986 stage_id: StageId::Other("B"),
987 checkpoint: None,
988 target: Some(10),
989 },
990 PipelineEvent::Run {
991 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
992 stage_id: StageId::Other("B"),
993 checkpoint: None,
994 target: Some(10),
995 },
996 PipelineEvent::Ran {
997 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
998 stage_id: StageId::Other("B"),
999 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1000 },
1001 PipelineEvent::Skipped { stage_id: StageId::Other("B") },
1004 PipelineEvent::Unwind {
1005 stage_id: StageId::Other("A"),
1006 input: UnwindInput {
1007 checkpoint: StageCheckpoint::new(100),
1008 unwind_to: 50,
1009 bad_block: None
1010 }
1011 },
1012 PipelineEvent::Unwound {
1013 stage_id: StageId::Other("A"),
1014 result: UnwindOutput { checkpoint: StageCheckpoint::new(50) },
1015 },
1016 ]
1017 );
1018 }
1019
1020 #[tokio::test]
1033 async fn run_pipeline_with_unwind() {
1034 let provider_factory = create_test_provider_factory();
1035
1036 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1037 .add_stage(
1038 TestStage::new(StageId::Other("A"))
1039 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }))
1040 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1041 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1042 )
1043 .add_stage(
1044 TestStage::new(StageId::Other("B"))
1045 .add_exec(Err(StageError::Block {
1046 block: Box::new(random_block_with_parent(
1047 &mut generators::rng(),
1048 5,
1049 Default::default(),
1050 )),
1051 error: BlockErrorKind::Validation(ConsensusError::BaseFeeMissing),
1052 }))
1053 .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) }))
1054 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1055 )
1056 .with_max_block(10)
1057 .build(
1058 provider_factory.clone(),
1059 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1060 );
1061 let events = pipeline.events();
1062
1063 tokio::spawn(async move {
1065 pipeline.run().await.expect("Could not run pipeline");
1066 });
1067
1068 assert_eq!(
1070 events.collect::<Vec<PipelineEvent>>().await,
1071 vec![
1072 PipelineEvent::Prepare {
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::Run {
1079 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1080 stage_id: StageId::Other("A"),
1081 checkpoint: None,
1082 target: Some(10),
1083 },
1084 PipelineEvent::Ran {
1085 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1086 stage_id: StageId::Other("A"),
1087 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1088 },
1089 PipelineEvent::Prepare {
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::Run {
1096 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1097 stage_id: StageId::Other("B"),
1098 checkpoint: None,
1099 target: Some(10),
1100 },
1101 PipelineEvent::Error { stage_id: StageId::Other("B") },
1102 PipelineEvent::Unwind {
1103 stage_id: StageId::Other("A"),
1104 input: UnwindInput {
1105 checkpoint: StageCheckpoint::new(10),
1106 unwind_to: 0,
1107 bad_block: Some(5)
1108 }
1109 },
1110 PipelineEvent::Unwound {
1111 stage_id: StageId::Other("A"),
1112 result: UnwindOutput { checkpoint: StageCheckpoint::new(0) },
1113 },
1114 PipelineEvent::Prepare {
1115 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1116 stage_id: StageId::Other("A"),
1117 checkpoint: Some(StageCheckpoint::new(0)),
1118 target: Some(10),
1119 },
1120 PipelineEvent::Run {
1121 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1122 stage_id: StageId::Other("A"),
1123 checkpoint: Some(StageCheckpoint::new(0)),
1124 target: Some(10),
1125 },
1126 PipelineEvent::Ran {
1127 pipeline_stages_progress: PipelineStagesProgress { current: 1, total: 2 },
1128 stage_id: StageId::Other("A"),
1129 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1130 },
1131 PipelineEvent::Prepare {
1132 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1133 stage_id: StageId::Other("B"),
1134 checkpoint: None,
1135 target: Some(10),
1136 },
1137 PipelineEvent::Run {
1138 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1139 stage_id: StageId::Other("B"),
1140 checkpoint: None,
1141 target: Some(10),
1142 },
1143 PipelineEvent::Ran {
1144 pipeline_stages_progress: PipelineStagesProgress { current: 2, total: 2 },
1145 stage_id: StageId::Other("B"),
1146 result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true },
1147 },
1148 ]
1149 );
1150 }
1151
1152 #[tokio::test]
1154 async fn pipeline_error_handling() {
1155 let provider_factory = create_test_provider_factory();
1157 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1158 .add_stage(
1159 TestStage::new(StageId::Other("NonFatal"))
1160 .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error))))
1161 .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })),
1162 )
1163 .with_max_block(10)
1164 .build(
1165 provider_factory.clone(),
1166 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1167 );
1168 let result = pipeline.run().await;
1169 assert_matches!(result, Ok(()));
1170
1171 let provider_factory = create_test_provider_factory();
1173 let mut pipeline = Pipeline::<MockNodeTypesWithDB>::builder()
1174 .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err(
1175 StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)),
1176 )))
1177 .build(
1178 provider_factory.clone(),
1179 StaticFileProducer::new(provider_factory.clone(), PruneModes::default()),
1180 );
1181 let result = pipeline.run().await;
1182 assert_matches!(
1183 result,
1184 Err(PipelineError::Stage(StageError::DatabaseIntegrity(
1185 ProviderError::BlockBodyIndicesNotFound(5)
1186 )))
1187 );
1188 }
1189}