1mod context;
16mod handoff;
17mod run;
18mod selection;
19
20pub use handoff::{HandoffOutcome, RebuildOutcome, SnapHandoff};
21pub use selection::{EthereumBackfill, EthereumBackfillSync};
22
23use alloy_consensus::BlockHeader;
24use alloy_primitives::B256;
25use futures::FutureExt;
26use reth_chainspec::EthChainSpec;
27use reth_engine_tree::backfill::{BackfillAction, BackfillEvent, BackfillSync};
28use reth_errors::RethError;
29use reth_network_p2p::{headers::client::HeadersClient, snap::client::SnapClient};
30use reth_provider::{
31 providers::ProviderNodeTypes, ChainSpecProvider, DatabaseProviderFactory, MetadataProvider,
32 ProviderFactory, ProviderResult, StageCheckpointReader,
33};
34use reth_stages::{Pipeline, PipelineError, PipelineTarget, PipelineWithResult, StageId};
35use reth_tasks::Runtime;
36use run::SnapRun;
37use std::task::{ready, Context, Poll};
38use tokio::sync::{oneshot, watch};
39use tokio_util::sync::{CancellationToken, DropGuard};
40
41#[derive(Debug)]
43pub struct SnapBackfillSync<N: ProviderNodeTypes, C> {
44 client: C,
46 provider_factory: ProviderFactory<N>,
47 runtime: Runtime,
48 state: SnapBackfillState<N>,
50 pending_target: Option<PipelineTarget>,
52 finalized: watch::Sender<B256>,
54}
55
56impl<N: ProviderNodeTypes, C> SnapBackfillSync<N, C> {
57 pub fn new(
59 pipeline: Pipeline<N>,
60 client: C,
61 provider_factory: ProviderFactory<N>,
62 runtime: Runtime,
63 ) -> Self {
64 Self {
65 client,
66 provider_factory,
67 runtime,
68 state: SnapBackfillState::Idle(Some(Box::new(pipeline))),
69 pending_target: None,
70 finalized: watch::channel(B256::ZERO).0,
71 }
72 }
73}
74
75impl<N, C> SnapBackfillSync<N, C>
76where
77 N: ProviderNodeTypes,
78 C: SnapClient + HeadersClient + Clone + Unpin + 'static,
79{
80 fn try_spawn(&mut self) -> Option<BackfillEvent> {
82 if !matches!(self.state, SnapBackfillState::Idle(_)) {
83 return None
84 }
85 let target = self.pending_target.take()?;
86 let event = match (self.needs_snap(), target) {
89 (_, PipelineTarget::Sync(hash)) if hash.is_zero() => {
92 BackfillEvent::Finished(Err(PipelineError::Internal(RethError::msg(
93 "snap backfill cannot sync to the zero hash",
94 ))))
95 }
96 (Err(error), _) => {
97 BackfillEvent::Finished(Err(PipelineError::Internal(RethError::other(error))))
98 }
99 (Ok(false), target) => self.spawn_staged(target),
100 (Ok(true), PipelineTarget::Unwind(block)) => {
103 BackfillEvent::Finished(Err(PipelineError::Internal(RethError::msg(format!(
104 "snap backfill cannot unwind to block {block}"
105 )))))
106 }
107 (Ok(true), PipelineTarget::Sync(target)) => self.spawn_snap(target),
108 };
109 Some(event)
110 }
111
112 fn spawn_staged(&mut self, target: PipelineTarget) -> BackfillEvent {
113 let pipeline = self.take_pipeline();
114 let (result_tx, result) = oneshot::channel();
115 self.runtime.spawn_critical_blocking_task("pipeline task", async move {
116 let _ = result_tx.send(pipeline.run_as_fut(Some(target)).await);
117 });
118 self.state = SnapBackfillState::Staged(result);
119 BackfillEvent::Started(target)
120 }
121
122 fn spawn_snap(&mut self, target: B256) -> BackfillEvent {
123 let pipeline = self.take_pipeline();
124 let (result_tx, result) = oneshot::channel();
125 let (targets, target_rx) = watch::channel(target);
126 let stop = CancellationToken::new();
127 let run = SnapRun::new(
128 self.client.clone(),
129 self.provider_factory.clone(),
130 self.runtime.clone(),
131 stop.clone(),
132 self.finalized.subscribe(),
133 );
134 self.runtime.spawn_critical_blocking_task("snap backfill task", async move {
137 let _ = result_tx.send(run.run(*pipeline, target_rx).await);
138 });
139 self.state = SnapBackfillState::Running { _stop: stop.drop_guard(), targets, result };
140 BackfillEvent::Started(PipelineTarget::Sync(target))
141 }
142
143 fn take_pipeline(&mut self) -> Box<Pipeline<N>> {
144 let SnapBackfillState::Idle(pipeline) = &mut self.state else {
145 unreachable!("only an idle backfill spawns a run")
146 };
147 pipeline.take().expect("idle backfill owns its pipeline")
148 }
149
150 fn needs_snap(&self) -> ProviderResult<bool> {
153 let provider = self.provider_factory.database_provider_ro()?;
154 if let Some(attempt) = provider.snap_attempt()? {
155 return Ok(!attempt.is_verified())
156 }
157 let genesis = self.provider_factory.chain_spec().genesis_header().number();
159 Ok(provider
160 .get_stage_checkpoint(StageId::Execution)?
161 .is_none_or(|checkpoint| checkpoint.block_number == genesis))
162 }
163}
164
165impl<N, C> BackfillSync for SnapBackfillSync<N, C>
166where
167 N: ProviderNodeTypes,
168 C: SnapClient + HeadersClient + Clone + Unpin + 'static,
169{
170 fn on_action(&mut self, action: BackfillAction) {
171 match action {
172 BackfillAction::UpdateTarget(hash) | BackfillAction::UpdateFinalized(hash)
174 if hash.is_zero() => {}
175 BackfillAction::Start(target) => self.pending_target = Some(target),
176 BackfillAction::UpdateTarget(hash) => {
177 if let SnapBackfillState::Running { targets, .. } = &self.state {
178 targets.send_if_modified(|current| std::mem::replace(current, hash) != hash);
179 }
180 }
181 BackfillAction::UpdateFinalized(hash) => {
183 self.finalized.send_if_modified(|current| std::mem::replace(current, hash) != hash);
184 }
185 }
186 }
187
188 fn poll(&mut self, cx: &mut Context<'_>) -> Poll<BackfillEvent> {
189 if let Some(event) = self.try_spawn() {
190 return Poll::Ready(event)
191 }
192 let (SnapBackfillState::Running { result, .. } | SnapBackfillState::Staged(result)) =
193 &mut self.state
194 else {
195 return Poll::Pending
196 };
197 let event = match ready!(result.poll_unpin(cx)) {
198 Ok((pipeline, result)) => {
199 self.state = SnapBackfillState::Idle(Some(Box::new(pipeline)));
200 BackfillEvent::Finished(result)
201 }
202 Err(error) => BackfillEvent::TaskDropped(error.to_string()),
203 };
204 Poll::Ready(event)
205 }
206}
207
208#[derive(Debug)]
210enum SnapBackfillState<N: ProviderNodeTypes> {
211 Idle(Option<Box<Pipeline<N>>>),
212 Running {
213 _stop: DropGuard,
216 targets: watch::Sender<B256>,
218 result: oneshot::Receiver<PipelineWithResult<N>>,
220 },
221 Staged(oneshot::Receiver<PipelineWithResult<N>>),
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::snap::run::tests::ServesHeaders;
229 use alloy_consensus::Header;
230 use futures::{future::poll_fn, Stream, StreamExt};
231 use reth_provider::{
232 test_utils::{
233 create_test_provider_factory, create_test_provider_factory_with_genesis_block_number,
234 MockNodeTypesWithDB,
235 },
236 DBProvider, DatabaseProviderFactory, MetadataWriter, StageCheckpointReader,
237 StageCheckpointWriter, StorageSettings, StorageSettingsCache,
238 };
239 use reth_prune::PruneModes;
240 use reth_stages::{
241 ControlFlow, ExecOutput, PipelineEvent, Stage, StageCheckpoint, StageError, StageId,
242 StageSetBuilder,
243 };
244 use reth_stages_api::test_utils::TestStage;
245 use reth_static_file::StaticFileProducer;
246 use std::task::Waker;
247
248 type TestBackfill = SnapBackfillSync<MockNodeTypesWithDB, ServesHeaders>;
249
250 type TestProvider =
251 <ProviderFactory<MockNodeTypesWithDB> as DatabaseProviderFactory>::ProviderRW;
252
253 fn backfill(headers: TestStage) -> (TestBackfill, impl Stream<Item = PipelineEvent> + Unpin) {
255 let (pipeline, factory) = pipeline(headers);
256 let events = pipeline.events();
257 let backfill =
258 SnapBackfillSync::new(pipeline, ServesHeaders::default(), factory, Runtime::test());
259 (backfill, events)
260 }
261
262 pub(super) fn pipeline(
265 headers: TestStage,
266 ) -> (Pipeline<MockNodeTypesWithDB>, ProviderFactory<MockNodeTypesWithDB>) {
267 pipeline_with(headers, watch::channel(B256::ZERO).0)
268 }
269
270 pub(super) fn pipeline_with<S: Stage<TestProvider> + 'static>(
272 headers: S,
273 tip: watch::Sender<B256>,
274 ) -> (Pipeline<MockNodeTypesWithDB>, ProviderFactory<MockNodeTypesWithDB>) {
275 let factory = hashed_factory();
276 (pipeline_on(&factory, StageSetBuilder::default().add_stage(headers), tip), factory)
277 }
278
279 pub(super) fn pipeline_on(
281 factory: &ProviderFactory<MockNodeTypesWithDB>,
282 stages: StageSetBuilder<TestProvider>,
283 tip: watch::Sender<B256>,
284 ) -> Pipeline<MockNodeTypesWithDB> {
285 Pipeline::<MockNodeTypesWithDB>::builder()
286 .add_stages(stages)
287 .with_tip_sender(tip)
288 .build(factory.clone(), StaticFileProducer::new(factory.clone(), PruneModes::default()))
289 }
290
291 pub(super) fn hashed_factory() -> ProviderFactory<MockNodeTypesWithDB> {
293 let factory = create_test_provider_factory();
294 let provider = factory.database_provider_rw().unwrap();
295 provider.write_storage_settings(StorageSettings::v2()).unwrap();
296 provider.commit().unwrap();
297 factory.set_storage_settings_cache(StorageSettings::v2());
298 factory
299 }
300
301 pub(super) fn headers_done(block: u64) -> Result<ExecOutput, StageError> {
302 Ok(ExecOutput { checkpoint: StageCheckpoint::new(block), done: true })
303 }
304
305 fn poll_once(backfill: &mut TestBackfill) -> Poll<BackfillEvent> {
306 backfill.poll(&mut Context::from_waker(Waker::noop()))
307 }
308
309 fn headers_checkpoint(factory: &ProviderFactory<MockNodeTypesWithDB>) -> Option<u64> {
310 let provider = factory.database_provider_ro().unwrap();
311 provider.get_stage_checkpoint(StageId::Headers).unwrap().map(|it| it.block_number)
312 }
313
314 pub(super) async fn headers_reach(
316 events: &mut (impl Stream<Item = PipelineEvent> + Unpin),
317 block: u64,
318 ) {
319 while let Some(event) = events.next().await {
320 if let PipelineEvent::Ran { stage_id: StageId::Headers, result, .. } = event &&
321 result.checkpoint.block_number == block
322 {
323 return
324 }
325 }
326 panic!("the pipeline stopped before headers reached {block}")
327 }
328
329 pub(super) fn target() -> B256 {
330 Header { number: 100, ..Default::default() }.hash_slow()
331 }
332
333 pub(super) fn next_target() -> B256 {
334 Header { number: 101, ..Default::default() }.hash_slow()
335 }
336
337 #[tokio::test]
338 async fn executed_state_backfills_with_the_staged_pipeline() {
339 let headers = TestStage::new(StageId::Headers).add_exec(headers_done(5));
340 let (pipeline, factory) = pipeline(headers);
341 let provider = factory.database_provider_rw().unwrap();
342 provider.save_stage_checkpoint(StageId::Execution, StageCheckpoint::new(42)).unwrap();
343 provider.commit().unwrap();
344 let mut backfill = SnapBackfillSync::new(
345 pipeline,
346 ServesHeaders::default(),
347 factory.clone(),
348 Runtime::test(),
349 );
350
351 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(target())));
353 assert!(matches!(poll_once(&mut backfill), Poll::Ready(BackfillEvent::Started(_))));
354 assert!(matches!(backfill.state, SnapBackfillState::Staged(_)));
355 assert!(matches!(poll_fn(|cx| backfill.poll(cx)).await, BackfillEvent::Finished(Ok(_))));
356
357 assert_eq!(headers_checkpoint(&factory), Some(5));
358 assert!(matches!(backfill.state, SnapBackfillState::Idle(Some(_))));
359 }
360
361 #[test]
362 fn a_genesis_above_block_zero_still_snap_syncs() {
363 let factory = create_test_provider_factory_with_genesis_block_number(5);
364 let provider = factory.database_provider_rw().unwrap();
365 provider.save_stage_checkpoint(StageId::Execution, StageCheckpoint::new(5)).unwrap();
366 provider.commit().unwrap();
367 let (pipeline, _) = pipeline(TestStage::new(StageId::Headers));
369 let backfill =
370 TestBackfill::new(pipeline, ServesHeaders::default(), factory, Runtime::test());
371
372 assert!(backfill.needs_snap().unwrap());
373 }
374
375 #[test]
376 fn a_zero_hash_target_fails_without_starting_a_run() {
377 let (mut backfill, _events) = backfill(TestStage::new(StageId::Headers));
378
379 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(B256::ZERO)));
380
381 assert!(matches!(poll_once(&mut backfill), Poll::Ready(BackfillEvent::Finished(Err(_)))));
382 assert!(matches!(backfill.state, SnapBackfillState::Idle(Some(_))));
383 }
384
385 #[test]
386 fn the_finalized_block_is_kept_while_idle() {
387 let (mut backfill, _events) = backfill(TestStage::new(StageId::Headers));
388 let finalized = backfill.finalized.subscribe();
389
390 backfill.on_action(BackfillAction::UpdateFinalized(target()));
391 backfill.on_action(BackfillAction::UpdateFinalized(B256::ZERO));
392
393 assert_eq!(*finalized.borrow(), target());
394 assert!(poll_once(&mut backfill).is_pending());
395 }
396
397 #[test]
398 fn a_target_update_while_idle_starts_nothing() {
399 let (mut backfill, _events) = backfill(TestStage::new(StageId::Headers));
400
401 backfill.on_action(BackfillAction::UpdateTarget(target()));
402
403 assert!(poll_once(&mut backfill).is_pending());
404 assert!(matches!(backfill.state, SnapBackfillState::Idle(Some(_))));
405 }
406
407 #[tokio::test]
408 async fn an_active_bootstrap_holds_the_pipeline_and_coalesces_targets() {
409 let (mut backfill, mut events) =
410 backfill(TestStage::new(StageId::Headers).add_exec(headers_done(0)));
411
412 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(target())));
413 assert!(matches!(
414 poll_once(&mut backfill),
415 Poll::Ready(BackfillEvent::Started(PipelineTarget::Sync(hash))) if hash == target()
416 ));
417 headers_reach(&mut events, 0).await;
418
419 let SnapBackfillState::Running { targets, .. } = &backfill.state else {
420 panic!("the run owns the pipeline")
421 };
422 let mut observer = targets.subscribe();
423 backfill.on_action(BackfillAction::UpdateTarget(B256::ZERO));
424 assert!(!observer.has_changed().unwrap());
425 backfill.on_action(BackfillAction::UpdateTarget(next_target()));
426 backfill.on_action(BackfillAction::UpdateTarget(next_target()));
427
428 assert!(observer.has_changed().unwrap());
429 assert_eq!(*observer.borrow_and_update(), next_target());
430 assert!(!observer.has_changed().unwrap());
431 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(next_target())));
433 assert!(poll_once(&mut backfill).is_pending());
434 }
435
436 #[tokio::test]
437 async fn a_dropped_backfill_stops_its_run_and_releases_the_pipeline() {
438 let (mut backfill, mut events) =
439 backfill(TestStage::new(StageId::Headers).add_exec(headers_done(0)));
440 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(target())));
441 assert!(poll_once(&mut backfill).is_ready());
442 headers_reach(&mut events, 0).await;
443
444 let SnapBackfillState::Running { _stop, result, .. } =
445 std::mem::replace(&mut backfill.state, SnapBackfillState::Idle(None))
446 else {
447 panic!("the run owns the pipeline")
448 };
449 drop(_stop);
450
451 let (_pipeline, result) = result.await.unwrap();
452 assert!(matches!(result, Ok(ControlFlow::NoProgress { block_number: None })));
453 }
454
455 #[tokio::test]
456 async fn node_shutdown_keeps_committed_progress() {
457 let (mut backfill, mut events) =
458 backfill(TestStage::new(StageId::Headers).add_exec(headers_done(0)));
459 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(target())));
460 assert!(poll_once(&mut backfill).is_ready());
461 headers_reach(&mut events, 0).await;
462
463 backfill.runtime.graceful_shutdown();
464
465 assert!(matches!(poll_fn(|cx| backfill.poll(cx)).await, BackfillEvent::TaskDropped(_)));
467 assert_eq!(headers_checkpoint(&backfill.provider_factory), Some(0));
468 }
469
470 #[tokio::test]
471 async fn a_failed_run_returns_the_pipeline() {
472 let (mut backfill, _events) =
473 backfill(TestStage::new(StageId::Headers).add_exec(Err(StageError::ChannelClosed)));
474 backfill.on_action(BackfillAction::Start(PipelineTarget::Sync(target())));
475 assert!(poll_once(&mut backfill).is_ready());
476
477 assert!(matches!(poll_fn(|cx| backfill.poll(cx)).await, BackfillEvent::Finished(Err(_))));
478 assert!(matches!(backfill.state, SnapBackfillState::Idle(Some(_))));
480 }
481
482 #[tokio::test]
483 async fn an_unwind_target_fails_without_starting_a_run() {
484 let (mut backfill, _events) = backfill(TestStage::new(StageId::Headers));
485
486 backfill.on_action(BackfillAction::Start(PipelineTarget::Unwind(1)));
487
488 assert!(matches!(poll_once(&mut backfill), Poll::Ready(BackfillEvent::Finished(Err(_)))));
489 assert!(matches!(backfill.state, SnapBackfillState::Idle(Some(_))));
490 }
491}