1use super::{
4 context::NodeSnapContext,
5 handoff::{HandoffOutcome, RebuildOutcome, SnapHandoff},
6};
7use alloy_consensus::{BlockHeader, Sealable};
8use alloy_eips::BlockNumHash;
9use alloy_primitives::B256;
10use reth_errors::RethError;
11use reth_network_p2p::{headers::client::HeadersClient, snap::client::SnapClient};
12use reth_provider::{
13 providers::ProviderNodeTypes, BlockHashReader, BlockNumReader, ProviderError, ProviderFactory,
14};
15use reth_snap_sync::{SnapBootstrap, SnapBootstrapOutcome, SnapWrite};
16use reth_stages::{
17 ControlFlow, Pipeline, PipelineError, PipelineTarget, PipelineWithResult, StageId,
18};
19use reth_tasks::Runtime;
20use reth_tracing::tracing::{debug, info, warn};
21use std::{pin::pin, time::Duration};
22use tokio::sync::watch;
23use tokio_util::sync::CancellationToken;
24
25const HEADER_REFRESH: Duration = Duration::from_secs(120);
30
31const HEADER_LOOKUP_RETRY: Duration = Duration::from_secs(1);
33
34const STOPPED: ControlFlow = ControlFlow::NoProgress { block_number: None };
36
37const ROOT_MISMATCH_RETRIES: u32 = 1;
40
41pub(super) struct SnapRun<N: ProviderNodeTypes, C> {
43 client: C,
44 factory: ProviderFactory<N>,
45 runtime: Runtime,
46 header_refresh: Duration,
47 root_mismatch_retries: u32,
49 stop: CancellationToken,
51 finalized: watch::Receiver<B256>,
53}
54
55impl<N: ProviderNodeTypes, C> SnapRun<N, C> {
56 pub(super) const fn new(
57 client: C,
58 factory: ProviderFactory<N>,
59 runtime: Runtime,
60 stop: CancellationToken,
61 finalized: watch::Receiver<B256>,
62 ) -> Self {
63 Self {
64 client,
65 factory,
66 runtime,
67 header_refresh: HEADER_REFRESH,
68 root_mismatch_retries: ROOT_MISMATCH_RETRIES,
69 stop,
70 finalized,
71 }
72 }
73
74 #[cfg(test)]
75 const fn with_header_refresh(mut self, header_refresh: Duration) -> Self {
76 self.header_refresh = header_refresh;
77 self
78 }
79
80 #[cfg(test)]
81 const fn with_root_mismatch_retries(mut self, root_mismatch_retries: u32) -> Self {
82 self.root_mismatch_retries = root_mismatch_retries;
83 self
84 }
85}
86
87impl<N, C> SnapRun<N, C>
88where
89 N: ProviderNodeTypes,
90 C: SnapClient + HeadersClient + Clone + Unpin + 'static,
91{
92 pub(super) async fn run(
94 self,
95 mut pipeline: Pipeline<N>,
96 targets: watch::Receiver<B256>,
97 ) -> PipelineWithResult<N> {
98 let result = self.bootstrap(&mut pipeline, targets).await;
99 (pipeline, result)
100 }
101
102 async fn bootstrap(
105 &self,
106 pipeline: &mut Pipeline<N>,
107 mut targets: watch::Receiver<B256>,
108 ) -> Result<ControlFlow, PipelineError> {
109 let mut mismatches = 0;
110 let verified = loop {
111 match self.catch_up_headers(pipeline, &mut targets).await? {
112 Pass::Stopped => return Ok(STOPPED),
113 Pass::Again | Pass::RootMismatch => continue,
114 Pass::Done => {}
115 }
116 match self.download(&mut targets).await? {
117 SnapBootstrapOutcome::Stopped
119 if self.stop.is_cancelled() || targets.has_changed().is_err() =>
120 {
121 return Ok(STOPPED)
122 }
123 SnapBootstrapOutcome::Stopped => {
124 debug!(target: "sync::snap", "Refreshing headers before resuming snap sync");
125 }
126 SnapBootstrapOutcome::TrieRebuild { write, pivot } => {
127 match self.rebuild_and_hand_off(pipeline, &mut targets, write, pivot).await? {
128 Pass::Stopped => return Ok(STOPPED),
129 Pass::Again => {}
130 Pass::RootMismatch => {
131 mismatches += 1;
132 if mismatches > self.root_mismatch_retries {
133 return Err(PipelineError::Internal(RethError::msg(format!(
134 "snap state root mismatched {mismatches} times, last at block {}",
135 pivot.number
136 ))))
137 }
138 }
139 Pass::Done => break Some(pivot),
140 }
141 }
142 SnapBootstrapOutcome::Verified { pivot } => break Some(pivot),
143 SnapBootstrapOutcome::BeforeBlockAccessLists => break None,
144 }
145 };
146 match verified {
147 Some(pivot) => {
148 info!(target: "sync::snap", ?pivot, "Snap state verified, resuming the pipeline");
149 }
150 None => {
151 info!(target: "sync::snap", "Chain predates block access lists, syncing with the staged pipeline");
152 }
153 }
154 self.finish(pipeline, &mut targets).await
155 }
156
157 async fn download(
159 &self,
160 targets: &mut watch::Receiver<B256>,
161 ) -> Result<SnapBootstrapOutcome, PipelineError> {
162 let run_stop = self.stop.child_token();
163 let context = NodeSnapContext::new(
164 self.factory.clone(),
165 self.client.clone(),
166 targets.clone(),
167 self.finalized.clone(),
168 );
169 let mut session = SnapBootstrap::new(
170 self.client.clone(),
171 self.factory.clone(),
172 self.runtime.clone(),
173 context,
174 )
175 .with_cancellation(run_stop.clone())
176 .with_shutdown(self.stop.clone());
177 let mut run = pin!(session.run());
178 let outcome = tokio::select! {
179 biased;
180 outcome = &mut run => outcome,
181 () = self.refresh_due(targets) => {
182 run_stop.cancel();
184 run.await
185 }
186 };
187 match outcome {
188 Ok(outcome) => Ok(outcome),
189 Err(error) if error.is_fatal() => Err(PipelineError::Internal(RethError::other(error))),
190 Err(error) => {
193 warn!(target: "sync::snap", %error, "Snap sync failed, retrying once forkchoice moves");
194 if let Some(Err(_)) = self.stop.run_until_cancelled(targets.changed()).await {
195 debug!(target: "sync::snap", "Targets channel closed, stopping snap sync");
196 }
197 Ok(SnapBootstrapOutcome::Stopped)
198 }
199 }
200 }
201
202 async fn rebuild_and_hand_off(
205 &self,
206 pipeline: &mut Pipeline<N>,
207 targets: &mut watch::Receiver<B256>,
208 write: SnapWrite,
209 pivot: BlockNumHash,
210 ) -> Result<Pass, PipelineError> {
211 match self.with_handoff(move |handoff, stop| handoff.rebuild(write, stop)).await? {
212 RebuildOutcome::Rebuilt => self.hand_off(pipeline, targets, write, pivot).await,
213 RebuildOutcome::RootMismatch => Ok(Pass::RootMismatch),
214 RebuildOutcome::Stopped => Ok(Pass::Stopped),
215 }
216 }
217
218 async fn hand_off(
221 &self,
222 pipeline: &mut Pipeline<N>,
223 targets: &mut watch::Receiver<B256>,
224 write: SnapWrite,
225 pivot: BlockNumHash,
226 ) -> Result<Pass, PipelineError> {
227 let headers = self.catch_up_headers(pipeline, targets).await?;
228 if headers != Pass::Done {
229 return Ok(headers)
230 }
231 Ok(match self.with_handoff(move |handoff, stop| handoff.hand_off(write, stop)).await? {
232 HandoffOutcome::Completed => Pass::Done,
233 HandoffOutcome::PivotReorged => {
234 info!(target: "sync::snap", ?pivot, "Snap pivot was reorged before the handoff, restarting");
235 Pass::Again
236 }
237 HandoffOutcome::RootMismatch => Pass::RootMismatch,
238 HandoffOutcome::Stopped => Pass::Stopped,
239 })
240 }
241
242 async fn with_handoff<T: Send + 'static>(
244 &self,
245 f: impl FnOnce(SnapHandoff<N>, &CancellationToken) -> Result<T, PipelineError> + Send + 'static,
246 ) -> Result<T, PipelineError> {
247 let handoff = SnapHandoff::new(self.factory.clone());
248 let stop = self.stop.clone();
249 self.runtime
250 .spawn_blocking(move || f(handoff, &stop))
251 .await
252 .map_err(|error| PipelineError::Internal(RethError::other(error)))?
253 }
254
255 async fn finish(
257 &self,
258 pipeline: &mut Pipeline<N>,
259 targets: &mut watch::Receiver<B256>,
260 ) -> Result<ControlFlow, PipelineError> {
261 let Some((target, ahead)) = self.resolve_target(targets).await? else { return Ok(STOPPED) };
262 let target = if ahead { target } else { self.local_head()? };
265 let stages = pipeline.run_until(StageId::Finish, Some(PipelineTarget::Sync(target)));
266 self.stop.run_until_cancelled(stages).await.unwrap_or(Ok(STOPPED))
267 }
268
269 async fn catch_up_headers(
272 &self,
273 pipeline: &mut Pipeline<N>,
274 targets: &mut watch::Receiver<B256>,
275 ) -> Result<Pass, PipelineError> {
276 let Some((target, ahead)) = self.resolve_target(targets).await? else {
277 return Ok(Pass::Stopped)
278 };
279 if !ahead {
280 debug!(target: "sync::snap", %target, "Forkchoice target is not above the local headers, skipping the header pass");
281 return Ok(Pass::Done)
282 }
283 let headers = pipeline.run_until(StageId::Headers, Some(PipelineTarget::Sync(target)));
287 let Some(headers) = self.stop.run_until_cancelled(headers).await else {
288 return Ok(Pass::Stopped)
289 };
290 if headers?.is_unwind() || targets.has_changed().unwrap_or(false) {
291 return Ok(Pass::Again)
292 }
293 Ok(Pass::Done)
294 }
295
296 async fn is_ahead(&self, target: B256) -> Result<Option<bool>, PipelineError> {
300 let head = {
301 let provider = self.factory.provider()?;
302 if provider.block_number(target)?.is_some() {
303 return Ok(Some(false))
304 }
305 provider.last_block_number()?
306 };
307 let header = self.client.get_header(target.into()).await;
308 Ok(header
310 .ok()
311 .and_then(|header| header.into_data())
312 .filter(|header| header.hash_slow() == target)
313 .map(|header| header.number() > head))
314 }
315
316 fn local_head(&self) -> Result<B256, PipelineError> {
318 let provider = self.factory.provider()?;
319 let number = provider.last_block_number()?;
320 Ok(provider.block_hash(number)?.ok_or(ProviderError::HeaderNotFound(number.into()))?)
321 }
322
323 async fn refresh_due(&self, targets: &mut watch::Receiver<B256>) {
326 tokio::time::sleep(self.header_refresh).await;
327 let _ = targets.changed().await;
328 }
329
330 async fn resolve_target(
333 &self,
334 targets: &mut watch::Receiver<B256>,
335 ) -> Result<Option<(B256, bool)>, PipelineError> {
336 loop {
337 let target = *targets.borrow_and_update();
338 let ahead = tokio::select! {
339 biased;
340 () = self.stop.cancelled() => return Ok(None),
341 Ok(()) = targets.changed() => continue,
342 ahead = self.is_ahead(target) => ahead?,
343 };
344 if let Some(ahead) = ahead {
345 return Ok(Some((target, ahead)))
346 }
347 tokio::select! {
348 biased;
349 () = self.stop.cancelled() => return Ok(None),
350 Ok(()) = targets.changed() => {},
351 () = tokio::time::sleep(HEADER_LOOKUP_RETRY) => {},
352 }
353 }
354 }
355}
356
357#[derive(Debug, PartialEq, Eq)]
359enum Pass {
360 Stopped,
362 Again,
364 RootMismatch,
367 Done,
369}
370
371#[cfg(test)]
372pub(crate) mod tests {
373 use super::*;
374 use crate::snap::{
375 handoff::tests::{downloaded_attempt, PIVOT},
376 tests::{
377 hashed_factory, headers_done, headers_reach, next_target, pipeline, pipeline_on,
378 pipeline_with, target,
379 },
380 };
381 use alloy_consensus::Header;
382 use alloy_eips::eip1898::BlockWithParent;
383 use futures::future::{pending, ready, Either, Pending, Ready};
384 use reth_consensus::ConsensusError;
385 use reth_db::{tables, transaction::DbTxMut};
386 use reth_eth_wire_types::snap::{
387 GetAccountRangeMessage, GetBlockAccessListsMessage, GetByteCodesMessage,
388 GetStorageRangesMessage,
389 };
390 use reth_network_p2p::{
391 download::DownloadClient, error::PeerRequestResult, headers::client::HeadersRequest,
392 priority::Priority, NoopFullBlockClient,
393 };
394 use reth_network_peers::{PeerId, WithPeerId};
395 use reth_primitives_traits::{Account, SealedHeader};
396 use reth_provider::{
397 test_utils::{insert_headers, MockNodeTypesWithDB},
398 DBProvider, DatabaseProviderFactory, HeaderProvider, MetadataProvider,
399 };
400 use reth_snap_sync::{SnapAttemptStore, SnapStateVerifier, DEFAULT_SCAN_CHUNK};
401 use reth_stages::{
402 ExecInput, ExecOutput, Stage, StageError, StageSetBuilder, UnwindInput, UnwindOutput,
403 };
404 use reth_stages_api::test_utils::TestStage;
405 use std::{
406 sync::{
407 atomic::{AtomicBool, Ordering},
408 Arc, Mutex,
409 },
410 task::{Context, Poll},
411 };
412
413 #[derive(Clone, Debug)]
416 pub(crate) struct ServesHeaders {
417 headers: Vec<Header>,
418 noop: NoopFullBlockClient,
419 empty_response: Arc<AtomicBool>,
420 pending_target: Option<B256>,
421 }
422
423 impl Default for ServesHeaders {
424 fn default() -> Self {
425 Self {
426 headers: (100..=101)
427 .map(|number| Header { number, ..Default::default() })
428 .collect(),
429 noop: NoopFullBlockClient::default(),
430 empty_response: Arc::default(),
431 pending_target: None,
432 }
433 }
434 }
435
436 impl DownloadClient for ServesHeaders {
437 fn report_bad_message(&self, _peer_id: PeerId) {}
438
439 fn num_connected_peers(&self) -> usize {
440 1
441 }
442 }
443
444 impl HeadersClient for ServesHeaders {
445 type Header = Header;
446 type Output =
447 Either<Ready<PeerRequestResult<Vec<Header>>>, Pending<PeerRequestResult<Vec<Header>>>>;
448
449 fn get_headers_with_priority(
450 &self,
451 request: HeadersRequest,
452 _priority: Priority,
453 ) -> Self::Output {
454 if self.pending_target.is_some_and(|hash| request.start == hash.into()) {
455 return Either::Right(pending())
456 }
457 if self.empty_response.swap(false, Ordering::Relaxed) {
458 return Either::Left(ready(Ok(WithPeerId::new(PeerId::random(), vec![]))))
459 }
460 let requested = self
461 .headers
462 .iter()
463 .find(|header| request.start == header.hash_slow().into())
464 .map_or_else(|| self.headers.clone(), |header| vec![header.clone()]);
465 Either::Left(ready(Ok(WithPeerId::new(PeerId::random(), requested))))
466 }
467 }
468
469 impl SnapClient for ServesHeaders {
470 type Output = <NoopFullBlockClient as SnapClient>::Output;
471
472 fn get_account_range_with_priority(
473 &self,
474 request: GetAccountRangeMessage,
475 priority: Priority,
476 ) -> Self::Output {
477 self.noop.get_account_range_with_priority(request, priority)
478 }
479
480 fn get_storage_ranges(&self, request: GetStorageRangesMessage) -> Self::Output {
481 self.noop.get_storage_ranges(request)
482 }
483
484 fn get_storage_ranges_with_priority(
485 &self,
486 request: GetStorageRangesMessage,
487 priority: Priority,
488 ) -> Self::Output {
489 self.noop.get_storage_ranges_with_priority(request, priority)
490 }
491
492 fn get_byte_codes(&self, request: GetByteCodesMessage) -> Self::Output {
493 self.noop.get_byte_codes(request)
494 }
495
496 fn get_byte_codes_with_priority(
497 &self,
498 request: GetByteCodesMessage,
499 priority: Priority,
500 ) -> Self::Output {
501 self.noop.get_byte_codes_with_priority(request, priority)
502 }
503
504 fn get_block_access_lists_with_priority(
505 &self,
506 request: GetBlockAccessListsMessage,
507 priority: Priority,
508 ) -> Self::Output {
509 self.noop.get_block_access_lists_with_priority(request, priority)
510 }
511 }
512
513 #[derive(Debug)]
516 struct GatedHeaders {
517 gate: watch::Receiver<bool>,
518 attempts: Arc<Mutex<Vec<bool>>>,
519 stage: TestStage,
520 }
521
522 impl<Provider: MetadataProvider> Stage<Provider> for GatedHeaders {
523 fn id(&self) -> StageId {
524 StageId::Headers
525 }
526
527 fn poll_execute_ready(
528 &mut self,
529 cx: &mut Context<'_>,
530 _input: ExecInput,
531 ) -> Poll<Result<(), StageError>> {
532 if *self.gate.borrow() {
533 return Poll::Ready(Ok(()))
534 }
535 cx.waker().wake_by_ref();
536 Poll::Pending
537 }
538
539 fn execute(
540 &mut self,
541 provider: &Provider,
542 input: ExecInput,
543 ) -> Result<ExecOutput, StageError> {
544 self.attempts.lock().unwrap().push(provider.snap_attempt()?.is_some());
545 self.stage.execute(provider, input)
546 }
547
548 fn unwind(
549 &mut self,
550 _provider: &Provider,
551 _input: UnwindInput,
552 ) -> Result<UnwindOutput, StageError> {
553 unreachable!("nothing unwinds in this test")
554 }
555 }
556
557 fn snap_run(
559 factory: &ProviderFactory<MockNodeTypesWithDB>,
560 ) -> (SnapRun<MockNodeTypesWithDB, ServesHeaders>, CancellationToken) {
561 snap_run_with(factory, ServesHeaders::default())
562 }
563
564 fn snap_run_with<C>(
567 factory: &ProviderFactory<MockNodeTypesWithDB>,
568 client: C,
569 ) -> (SnapRun<MockNodeTypesWithDB, C>, CancellationToken) {
570 let stop = CancellationToken::new();
571 let run = SnapRun::new(
572 client,
573 factory.clone(),
574 Runtime::test(),
575 stop.clone(),
576 watch::channel(B256::ZERO).1,
577 )
578 .with_header_refresh(Duration::ZERO);
579 (run, stop)
580 }
581
582 fn handoff_ready(
585 headers: TestStage,
586 ) -> (
587 Pipeline<MockNodeTypesWithDB>,
588 ProviderFactory<MockNodeTypesWithDB>,
589 SnapWrite,
590 BlockNumHash,
591 ) {
592 let (factory, write) = downloaded_attempt();
593 let pivot = factory.sealed_header(PIVOT).unwrap().unwrap().num_hash();
594 let provider = factory.database_provider_rw().unwrap();
595 provider.start_trie_rebuild(write, DEFAULT_SCAN_CHUNK, &CancellationToken::new()).unwrap();
596 provider.commit().unwrap();
597 let pipeline = pipeline_on(
598 &factory,
599 StageSetBuilder::default().add_stage(headers),
600 watch::channel(B256::ZERO).0,
601 );
602 (pipeline, factory, write, pivot)
603 }
604
605 fn detached_head() -> StageError {
607 StageError::DetachedHead {
608 local_head: Box::new(BlockWithParent {
609 parent: B256::ZERO,
610 block: BlockNumHash::default(),
611 }),
612 header: Box::new(BlockWithParent {
613 parent: B256::ZERO,
614 block: BlockNumHash::default(),
615 }),
616 error: Box::new(ConsensusError::BaseFeeMissing),
617 }
618 }
619
620 #[tokio::test]
621 async fn a_new_target_refreshes_headers_then_resumes_until_stopped() {
622 let (pipeline, factory) = pipeline(
623 TestStage::new(StageId::Headers).add_exec(headers_done(0)).add_exec(headers_done(1)),
624 );
625 let (targets, receiver) = watch::channel(target());
626 let (run, stop) = snap_run(&factory);
627 let mut events = pipeline.events();
628 let run = tokio::spawn(run.run(pipeline, receiver));
629 headers_reach(&mut events, 0).await;
630
631 targets.send(next_target()).unwrap();
632
633 headers_reach(&mut events, 1).await;
635 assert!(!run.is_finished());
636
637 stop.cancel();
638 let (_pipeline, result) = run.await.unwrap();
639 assert!(matches!(result, Ok(ControlFlow::NoProgress { block_number: None })));
640 }
641
642 #[tokio::test]
643 async fn a_target_moved_during_headers_is_synced_before_the_bootstrap() {
644 let (open, gate) = watch::channel(false);
645 let attempts = Arc::new(Mutex::new(Vec::new()));
646 let headers = GatedHeaders {
647 gate,
648 attempts: attempts.clone(),
649 stage: TestStage::new(StageId::Headers)
650 .add_exec(headers_done(1))
651 .add_exec(headers_done(2)),
652 };
653 let (tip, _) = watch::channel(B256::ZERO);
654 let mut pipeline_tip = tip.subscribe();
655 let (pipeline, factory) = pipeline_with(headers, tip);
656 let mut parent = B256::ZERO;
658 let stored: Vec<_> = (0..=64)
659 .map(|number| {
660 let header = SealedHeader::seal_slow(Header {
661 number,
662 parent_hash: parent,
663 block_access_list_hash: Some(B256::ZERO),
664 ..Default::default()
665 });
666 parent = header.hash();
667 header
668 })
669 .collect();
670 insert_headers(&factory, &stored);
671 let target = Header { number: 65, parent_hash: parent, ..Default::default() };
673 let next = Header { number: 66, parent_hash: target.hash_slow(), ..Default::default() };
674 let (target_hash, next_hash) = (target.hash_slow(), next.hash_slow());
675 let client = ServesHeaders { headers: vec![target, next], ..Default::default() };
676 let (targets, receiver) = watch::channel(target_hash);
677 let (run, stop) = snap_run_with(&factory, client);
678 let mut events = pipeline.events();
679 let run = tokio::spawn(run.run(pipeline, receiver));
680
681 pipeline_tip.wait_for(|tip| *tip == target_hash).await.unwrap();
683 targets.send(next_hash).unwrap();
684 open.send(true).unwrap();
685
686 tokio::time::timeout(Duration::from_secs(5), headers_reach(&mut events, 2))
687 .await
688 .expect("headers are synced to the moved target");
689 stop.cancel();
690 let (_pipeline, result) = run.await.unwrap();
691 assert!(matches!(result, Ok(ControlFlow::NoProgress { block_number: None })));
692 assert_eq!(*attempts.lock().unwrap(), [false, false]);
694 }
695
696 #[tokio::test]
697 async fn a_detached_head_syncs_headers_again_before_the_bootstrap() {
698 let (pipeline, factory) = pipeline(
699 TestStage::new(StageId::Headers)
700 .add_exec(Err(detached_head()))
701 .add_exec(headers_done(2)),
702 );
703 let (_targets, receiver) = watch::channel(target());
704 let (run, stop) = snap_run(&factory);
705 let mut events = pipeline.events();
706 let run = tokio::spawn(run.run(pipeline, receiver));
707
708 tokio::time::timeout(Duration::from_secs(5), headers_reach(&mut events, 2))
710 .await
711 .expect("headers are synced again after the unwind");
712 stop.cancel();
713 let (_pipeline, result) = run.await.unwrap();
714 assert!(matches!(result, Ok(ControlFlow::NoProgress { block_number: None })));
715 }
716
717 #[tokio::test]
718 async fn the_pipeline_finishes_to_the_latest_target() {
719 let (tip, _) = watch::channel(B256::ZERO);
720 let pipeline_tip = tip.subscribe();
721 let (mut pipeline, factory) =
722 pipeline_with(TestStage::new(StageId::Finish).add_exec(headers_done(1)), tip);
723 let (targets, mut receiver) = watch::channel(target());
724 let (run, _stop) = snap_run(&factory);
725
726 targets.send(next_target()).unwrap();
728 run.finish(&mut pipeline, &mut receiver).await.unwrap();
729
730 assert_eq!(*pipeline_tip.borrow(), next_target());
731 }
732
733 #[tokio::test]
734 async fn a_moved_target_syncs_headers_before_the_handoff() {
735 let (mut pipeline, factory, write, pivot) =
736 handoff_ready(TestStage::new(StageId::Headers).add_exec(headers_done(PIVOT)));
737 let (targets, mut receiver) = watch::channel(target());
738 let (run, _stop) = snap_run(&factory);
739
740 targets.send(next_target()).unwrap();
742 let pass =
743 run.rebuild_and_hand_off(&mut pipeline, &mut receiver, write, pivot).await.unwrap();
744
745 assert_eq!(pass, Pass::Done);
746 assert!(factory.provider().unwrap().snap_attempt().unwrap().unwrap().is_verified());
747 }
748
749 #[tokio::test]
750 async fn a_reorg_during_the_rebuild_syncs_headers_again_without_a_handoff() {
751 let (mut pipeline, factory, write, pivot) =
752 handoff_ready(TestStage::new(StageId::Headers).add_exec(Err(detached_head())));
753 let (_targets, mut receiver) = watch::channel(target());
754 let (run, _stop) = snap_run(&factory);
755
756 let pass =
757 run.rebuild_and_hand_off(&mut pipeline, &mut receiver, write, pivot).await.unwrap();
758
759 assert_eq!(pass, Pass::Again);
760 assert!(!factory.provider().unwrap().snap_attempt().unwrap().unwrap().is_verified());
761 }
762
763 #[tokio::test]
764 async fn a_root_mismatch_starts_a_new_attempt_instead_of_failing() {
765 let (mut pipeline, factory, write, pivot) = handoff_ready(TestStage::new(StageId::Headers));
766 let provider = factory.database_provider_rw().unwrap();
768 provider
769 .tx_ref()
770 .put::<tables::HashedAccounts>(
771 B256::repeat_byte(3),
772 Account { nonce: 1, ..Default::default() },
773 )
774 .unwrap();
775 provider.commit().unwrap();
776 let (_targets, mut receiver) = watch::channel(target());
777 let (run, _stop) = snap_run(&factory);
778
779 let pass =
780 run.rebuild_and_hand_off(&mut pipeline, &mut receiver, write, pivot).await.unwrap();
781
782 assert_eq!(pass, Pass::RootMismatch);
783 assert!(factory.provider().unwrap().active_snap_write().unwrap().is_none());
784 }
785
786 #[tokio::test]
787 async fn repeated_root_mismatches_fail_the_run() {
788 let (mut pipeline, factory, _, _) =
789 handoff_ready(TestStage::new(StageId::Headers).add_exec(headers_done(PIVOT)));
790 let provider = factory.database_provider_rw().unwrap();
792 provider
793 .tx_ref()
794 .put::<tables::HashedAccounts>(
795 B256::repeat_byte(3),
796 Account { nonce: 1, ..Default::default() },
797 )
798 .unwrap();
799 provider.commit().unwrap();
800 let (_targets, receiver) = watch::channel(target());
801 let (run, _stop) = snap_run(&factory);
802
803 let result = run.with_root_mismatch_retries(0).bootstrap(&mut pipeline, receiver).await;
804
805 assert!(matches!(result, Err(PipelineError::Internal(_))), "{result:?}");
806 assert!(factory.provider().unwrap().active_snap_write().unwrap().is_none());
807 }
808
809 #[tokio::test]
810 async fn a_chain_before_block_access_lists_syncs_with_the_staged_pipeline() {
811 let factory = hashed_factory();
812 let stages = StageSetBuilder::default()
813 .add_stage(
814 TestStage::new(StageId::Headers)
815 .add_exec(headers_done(0))
816 .add_exec(headers_done(0)),
817 )
818 .add_stage(TestStage::new(StageId::Finish).add_exec(headers_done(0)));
819 let mut pipeline = pipeline_on(&factory, stages, watch::channel(B256::ZERO).0);
820 insert_headers(&factory, &[SealedHeader::seal_slow(Header::default())]);
821 let (_targets, receiver) = watch::channel(target());
822 let (run, _stop) = snap_run(&factory);
823
824 let result = run.bootstrap(&mut pipeline, receiver).await.unwrap();
826
827 assert_ne!(result, STOPPED);
828 assert!(factory.provider().unwrap().snap_attempt().unwrap().is_none());
829 }
830
831 #[tokio::test]
832 async fn a_sibling_of_the_local_head_skips_the_header_pass() {
833 let (mut pipeline, factory) =
835 pipeline_with(TestStage::new(StageId::Headers), watch::channel(B256::ZERO).0);
836 let (run, _, mut receiver) = sibling_of_head(&factory);
837
838 let pass = run.catch_up_headers(&mut pipeline, &mut receiver).await.unwrap();
839
840 assert_eq!(pass, Pass::Done);
841 }
842
843 #[tokio::test]
844 async fn a_sibling_of_the_local_head_finishes_to_the_local_head() {
845 let factory = hashed_factory();
846 let stages = StageSetBuilder::default()
847 .add_stage(TestStage::new(StageId::Headers).add_exec(headers_done(1)))
848 .add_stage(TestStage::new(StageId::Finish).add_exec(headers_done(1)));
849 let (tip, pipeline_tip) = watch::channel(B256::ZERO);
850 let mut pipeline = pipeline_on(&factory, stages, tip);
851 let (run, head, mut receiver) = sibling_of_head(&factory);
852
853 let result = run.finish(&mut pipeline, &mut receiver).await.unwrap();
854
855 assert_ne!(result, STOPPED);
856 assert_eq!(*pipeline_tip.borrow(), head);
857 }
858
859 #[tokio::test]
860 async fn a_header_other_than_the_target_leaves_it_unresolved() {
861 let factory = hashed_factory();
862 let genesis = Header::default();
863 insert_headers(&factory, &[SealedHeader::seal_slow(genesis.clone())]);
864 let client = ServesHeaders { headers: vec![genesis], ..Default::default() };
866 let (run, _stop) = snap_run_with(&factory, client);
867
868 assert_eq!(run.is_ahead(target()).await.unwrap(), None);
869 }
870
871 #[tokio::test]
872 async fn a_new_target_replaces_an_unresolved_one_before_headers_start() {
873 let (tip, pipeline_tip) = watch::channel(B256::ZERO);
874 let (mut pipeline, factory) =
875 pipeline_with(TestStage::new(StageId::Headers).add_exec(headers_done(1)), tip);
876 let (targets, mut receiver) = watch::channel(B256::repeat_byte(0xff));
877 let (run, _stop) = snap_run(&factory);
878 let mut pass = pin!(run.catch_up_headers(&mut pipeline, &mut receiver));
879
880 assert!(futures::poll!(&mut pass).is_pending());
881 assert_eq!(*pipeline_tip.borrow(), B256::ZERO);
882 targets.send(next_target()).unwrap();
883
884 let pass = tokio::time::timeout(Duration::from_secs(5), pass)
885 .await
886 .expect("the new target resolves without starting the old header pass");
887 assert_eq!(pass.unwrap(), Pass::Done);
888 assert_eq!(*pipeline_tip.borrow(), next_target());
889 }
890
891 #[tokio::test]
892 async fn a_new_target_replaces_an_unresolved_one_before_finishing() {
893 let factory = hashed_factory();
894 let stages = StageSetBuilder::default()
895 .add_stage(TestStage::new(StageId::Headers).add_exec(headers_done(1)))
896 .add_stage(TestStage::new(StageId::Finish).add_exec(headers_done(1)));
897 let (tip, pipeline_tip) = watch::channel(B256::ZERO);
898 let mut pipeline = pipeline_on(&factory, stages, tip);
899 let (targets, mut receiver) = watch::channel(B256::repeat_byte(0xff));
900 let (run, _stop) = snap_run(&factory);
901 let mut finished = pin!(run.finish(&mut pipeline, &mut receiver));
902
903 assert!(futures::poll!(&mut finished).is_pending());
904 assert_eq!(*pipeline_tip.borrow(), B256::ZERO);
905 targets.send(next_target()).unwrap();
906
907 let result = tokio::time::timeout(Duration::from_secs(5), finished)
908 .await
909 .expect("the new target resolves before the pipeline starts");
910 assert_ne!(result.unwrap(), STOPPED);
911 assert_eq!(*pipeline_tip.borrow(), next_target());
912 }
913
914 #[tokio::test]
915 async fn a_new_target_interrupts_a_pending_header_lookup() {
916 let (tip, pipeline_tip) = watch::channel(B256::ZERO);
917 let (mut pipeline, factory) =
918 pipeline_with(TestStage::new(StageId::Headers).add_exec(headers_done(1)), tip);
919 let (targets, mut receiver) = watch::channel(target());
920 let client = ServesHeaders { pending_target: Some(target()), ..Default::default() };
921 let (run, _stop) = snap_run_with(&factory, client);
922 let mut pass = pin!(run.catch_up_headers(&mut pipeline, &mut receiver));
923
924 assert!(futures::poll!(&mut pass).is_pending());
925 assert_eq!(*pipeline_tip.borrow(), B256::ZERO);
926 targets.send(next_target()).unwrap();
927
928 let pass = tokio::time::timeout(Duration::from_secs(5), pass)
929 .await
930 .expect("forkchoice interrupts the pending peer request");
931 assert_eq!(pass.unwrap(), Pass::Done);
932 assert_eq!(*pipeline_tip.borrow(), next_target());
933 }
934
935 #[tokio::test]
936 async fn an_empty_response_retries_a_stale_target_without_starting_headers() {
937 let (tip, pipeline_tip) = watch::channel(B256::ZERO);
938 let (mut pipeline, factory) = pipeline_with(TestStage::new(StageId::Headers), tip);
939 let (run, _, mut receiver) = sibling_of_head(&factory);
940 run.client.empty_response.store(true, Ordering::Relaxed);
941 let mut pass = pin!(run.catch_up_headers(&mut pipeline, &mut receiver));
942
943 assert!(futures::poll!(&mut pass).is_pending());
944 assert_eq!(*pipeline_tip.borrow(), B256::ZERO);
945 let pass = tokio::time::timeout(Duration::from_secs(5), pass)
946 .await
947 .expect("the same target is retried without another forkchoice update");
948
949 assert_eq!(pass.unwrap(), Pass::Done);
950 assert_eq!(*pipeline_tip.borrow(), B256::ZERO);
951 }
952
953 #[tokio::test]
954 async fn stopping_an_unresolved_target_leaves_headers_untouched() {
955 let (tip, pipeline_tip) = watch::channel(B256::ZERO);
956 let (mut pipeline, factory) = pipeline_with(TestStage::new(StageId::Headers), tip);
957 let (_targets, mut receiver) = watch::channel(target());
958 let client = ServesHeaders { pending_target: Some(target()), ..Default::default() };
959 let (run, stop) = snap_run_with(&factory, client);
960 let mut pass = pin!(run.catch_up_headers(&mut pipeline, &mut receiver));
961
962 assert!(futures::poll!(&mut pass).is_pending());
963 stop.cancel();
964
965 assert_eq!(pass.await.unwrap(), Pass::Stopped);
966 assert_eq!(*pipeline_tip.borrow(), B256::ZERO);
967 }
968
969 fn sibling_of_head(
972 factory: &ProviderFactory<MockNodeTypesWithDB>,
973 ) -> (SnapRun<MockNodeTypesWithDB, ServesHeaders>, B256, watch::Receiver<B256>) {
974 let genesis = SealedHeader::seal_slow(Header::default());
975 let head = SealedHeader::seal_slow(Header {
976 number: 1,
977 parent_hash: genesis.hash(),
978 ..Default::default()
979 });
980 insert_headers(factory, &[genesis.clone(), head.clone()]);
981 let sibling =
982 Header { number: 1, parent_hash: genesis.hash(), gas_limit: 1, ..Default::default() };
983 let receiver = watch::channel(sibling.hash_slow()).1;
984 let client = ServesHeaders { headers: vec![sibling], ..Default::default() };
985 (snap_run_with(factory, client).0, head.hash(), receiver)
986 }
987}