1use crate::{
7 metrics::SnapSyncMetrics, AccountRangeDownload, AccountRangeStep, BlockAccessListCatchUp,
8 BytecodeDownload, BytecodeStep, CatchUpStep, SnapAccountStore, SnapAttemptStore,
9 SnapCatchUpStore, SnapGeneration, SnapPivotPolicy, SnapStateVerifier, SnapSyncError,
10 SnapSyncSession, SnapWrite, StorageRangeDownload, StorageRangeStep, VerifiedRange,
11 DEFAULT_SCAN_CHUNK,
12};
13use alloy_eips::BlockNumHash;
14use reth_db_api::transaction::DbTxMut;
15use reth_network_p2p::{error::RequestError, snap::client::SnapClient};
16use reth_primitives_traits::AlloyBlockHeader;
17use reth_storage_api::{
18 BlockHashReader, DBProvider, DatabaseProviderFactory, HeaderProvider, MetadataProvider,
19 MetadataWriter, StageCheckpointWriter, StateWriter,
20};
21use reth_tasks::Runtime;
22use std::{fmt, future::Future};
23use tokio_util::sync::CancellationToken;
24use tracing::{debug, info};
25
26pub const DEFAULT_RANGES_PER_CHECK: usize = 64;
30
31pub struct SnapBootstrap<C, F, X> {
37 factory: F,
38 runtime: Runtime,
40 context: X,
42 policy: SnapPivotPolicy,
43 session: SnapSyncSession,
45 ranges_per_check: usize,
46 cancel: CancellationToken,
48 shutdown: Option<CancellationToken>,
50 accounts: AccountRangeDownload<C, F>,
51 storage: StorageRangeDownload<C, F>,
52 bytecode: BytecodeDownload<C, F>,
53 catch_up: BlockAccessListCatchUp<C, F>,
54 metrics: SnapSyncMetrics,
55}
56
57impl<C: Clone, F: Clone, X> SnapBootstrap<C, F, X> {
58 pub fn new(client: C, factory: F, runtime: Runtime, context: X) -> Self {
60 let policy = SnapPivotPolicy::default();
61 Self {
62 accounts: AccountRangeDownload::new(client.clone(), factory.clone(), runtime.clone()),
63 storage: StorageRangeDownload::new(client.clone(), factory.clone(), runtime.clone()),
64 bytecode: BytecodeDownload::new(client.clone(), factory.clone(), runtime.clone()),
65 catch_up: BlockAccessListCatchUp::new(client, factory.clone(), runtime.clone()),
66 factory,
67 runtime,
68 context,
69 policy,
70 session: SnapSyncSession::new(policy),
71 ranges_per_check: DEFAULT_RANGES_PER_CHECK,
72 cancel: CancellationToken::new(),
73 shutdown: None,
74 metrics: SnapSyncMetrics::default(),
75 }
76 }
77}
78
79impl<C, F, X> SnapBootstrap<C, F, X> {
80 pub const fn with_policy(mut self, policy: SnapPivotPolicy) -> Self {
82 self.policy = policy;
83 self.session = SnapSyncSession::new(policy);
84 self
85 }
86
87 pub const fn with_ranges_per_check(mut self, ranges: usize) -> Self {
90 self.ranges_per_check = if ranges == 0 { 1 } else { ranges };
91 self
92 }
93
94 pub fn with_cancellation(mut self, cancel: CancellationToken) -> Self {
96 self.cancel = cancel;
97 self
98 }
99
100 pub fn with_shutdown(mut self, shutdown: CancellationToken) -> Self {
104 self.shutdown = Some(shutdown);
105 self
106 }
107}
108
109impl<C, F, X> SnapBootstrap<C, F, X>
110where
111 C: SnapClient + Clone + Unpin,
112 F: DatabaseProviderFactory + Clone + 'static,
113 F::Provider: HeaderProvider + MetadataProvider + BlockHashReader,
114 F::ProviderRW: HeaderProvider
115 + BlockHashReader
116 + MetadataProvider
117 + MetadataWriter
118 + StageCheckpointWriter
119 + StateWriter
120 + DBProvider<Tx: DbTxMut>,
121 X: SnapSyncContext,
122{
123 pub async fn run(&mut self) -> Result<SnapBootstrapOutcome, SnapSyncError> {
128 let mut announced = None;
131 loop {
132 if self.cancel.is_cancelled() {
133 return Ok(SnapBootstrapOutcome::Stopped)
134 }
135 let head = self.context.head()?;
136 let step = match self.resolve(head)? {
137 Resolved::Verified(pivot) => return Ok(SnapBootstrapOutcome::Verified { pivot }),
138 Resolved::BeforeBlockAccessLists => {
139 return Ok(SnapBootstrapOutcome::BeforeBlockAccessLists)
140 }
141 Resolved::Waiting => {
142 if announced.replace(Wait::Pivot) == Some(Wait::Pivot) {
143 debug!(target: "sync::snap", head, "No eligible snap pivot yet, waiting for headers");
144 } else {
145 info!(target: "sync::snap", head, "No eligible snap pivot yet, waiting for headers");
146 }
147 Step::Wait
148 }
149 Resolved::Active(write) => match self.drive(write, head).await {
150 Ok(step) => step,
151 Err(SnapSyncError::Request(RequestError::UnsupportedCapability)) => {
153 if announced.replace(Wait::SnapPeer) == Some(Wait::SnapPeer) {
154 debug!(target: "sync::snap", "No connected peer serves snap/2, waiting for one");
155 } else {
156 info!(target: "sync::snap", "No connected peer serves snap/2, waiting for one");
157 }
158 Step::Wait
159 }
160 Err(error) if error.is_transient() => {
161 debug!(target: "sync::snap", %error, "Waiting for peers or headers");
162 Step::Wait
163 }
164 Err(error) if error.is_reorg() => {
166 debug!(target: "sync::snap", %error, "Snap pivot was reorged");
167 Step::Continue
168 }
169 Err(
171 error @ (SnapSyncError::UnsupportedRecord { .. } |
172 SnapSyncError::UnreadableRecord { .. }),
173 ) => {
174 info!(target: "sync::snap", %error, "Snap progress is unreadable, restarting");
175 Step::Restart
176 }
177 Err(SnapSyncError::Cancelled) => Step::Stop,
178 Err(error) => return Err(error),
179 },
180 };
181 match step {
182 Step::HandedOff(write) => {
183 let pivot = self.pivot()?;
184 info!(target: "sync::snap", ?pivot, "Snap state handed to the trie rebuild");
185 return Ok(SnapBootstrapOutcome::TrieRebuild { write, pivot })
186 }
187 Step::Continue => announced = None,
188 Step::Wait => {
189 if !self.wait(head).await {
190 return Ok(SnapBootstrapOutcome::Stopped)
191 }
192 }
193 Step::Restart => {
194 let provider = self.factory.database_provider_rw()?;
195 provider.abandon_snap_attempt()?;
196 provider.commit()?;
197 self.metrics.restarts.increment(1);
198 self.metrics.clear_attempt();
199 }
200 Step::Stop => return Ok(SnapBootstrapOutcome::Stopped),
201 }
202 }
203 }
204
205 fn resolve(&mut self, head: u64) -> Result<Resolved, SnapSyncError> {
209 let provider = self.factory.database_provider_rw()?;
210 let mut session = SnapSyncSession::new(self.policy);
211 if let Some(attempt) = provider.snap_attempt()? {
212 if attempt.is_verified() {
213 return Ok(Resolved::Verified(attempt.pivot()))
214 }
215 if let Some(write) = provider.active_snap_write()? {
216 session.resume(SnapGeneration::new(attempt.pivot(), attempt.state_root()));
218 self.session = session;
219 return Ok(Resolved::Active(write))
220 }
221 }
222
223 session.select(&provider, head, self.context.finalized()?)?;
224 let Some(generation) = session.start() else {
225 return match provider.sealed_header(head)? {
231 Some(header) if header.block_access_list_hash().is_none() => {
232 Ok(Resolved::BeforeBlockAccessLists)
233 }
234 _ => Ok(Resolved::Waiting),
235 }
236 };
237 let write = provider.start_snap_attempt(generation)?;
238 provider.start_account_coverage(write)?;
239 provider.commit()?;
240 info!(
241 target: "sync::snap",
242 pivot = ?generation.target(),
243 state_root = %generation.state_root(),
244 "Started snap attempt"
245 );
246 self.session = session;
247 Ok(Resolved::Active(write))
248 }
249
250 async fn drive(&mut self, write: SnapWrite, head: u64) -> Result<Step, SnapSyncError> {
253 let (applied, complete) = {
254 let provider = self.factory.database_provider_ro()?;
255 let generation = self.session.target().copied().ok_or(SnapSyncError::NoAttempt)?;
256 self.metrics.pivot_block.set(generation.target().number as f64);
257 let handed_off = provider.is_trie_rebuild_started(write)?;
258 let canonical = generation.is_canonical(&provider)?;
259 if handed_off {
260 if !canonical {
262 info!(target: "sync::snap", pivot = ?generation.target(), "Handed-off snap pivot was reorged, restarting");
263 return Ok(Step::Restart)
264 }
265 self.metrics.set_account_progress(None);
267 self.metrics.applied_block.set(generation.target().number as f64);
268 return Ok(Step::HandedOff(write))
269 }
270 let applied = provider
271 .catch_up_progress(write)?
272 .ok_or(SnapSyncError::NoCatchUpProgress)?
273 .applied();
274 let coverage = provider.account_coverage(write)?;
275 self.metrics.applied_block.set(applied.number as f64);
277 if let Some(coverage) = coverage {
278 self.metrics.set_account_progress(coverage.next());
279 }
280 if !canonical {
281 drop(provider);
282 return self.recover(write, head).await
283 }
284 let complete = coverage.is_some_and(|c| c.is_complete()) &&
286 provider.snap_repairs(write)?.is_empty();
287 (applied, complete)
288 };
289 if complete && applied == self.pivot()? {
292 return self.hand_off(write).await
293 }
294 if !self.policy.is_catchable_from(applied.number, head) {
297 info!(target: "sync::snap", ?applied, head, "Snap catch-up outlived the served block access lists, restarting");
298 return Ok(Step::Restart)
299 }
300 let write = self.advance_pivot(write, head)?;
301 self.metrics.pivot_block.set(self.pivot()?.number as f64);
302 if let Some(step) = self.catch_up(write).await? {
305 return Ok(step)
306 }
307 if let Some(step) = self.repair().await? {
310 return Ok(step)
311 }
312 self.download_ranges(write).await
313 }
314
315 async fn recover(&mut self, write: SnapWrite, head: u64) -> Result<Step, SnapSyncError> {
320 let pivot = self.pivot()?;
321 let provider = self.factory.database_provider_ro()?;
322 let Some(reorg) = provider.snap_reorg(write)? else {
323 info!(target: "sync::snap", ?pivot, "Snap pivot was reorged past its kept blocks, restarting");
324 return Ok(Step::Restart)
325 };
326 let ancestor = reorg.ancestor();
327 let committed = provider
330 .sealed_header(ancestor.number)?
331 .is_some_and(|header| header.block_access_list_hash().is_some());
332 if !committed {
333 info!(target: "sync::snap", ?pivot, ?ancestor, "Snap pivot was reorged across block access list activation, restarting");
334 return Ok(Step::Restart)
335 }
336 let resume = provider
337 .catch_up_progress(write)?
338 .ok_or(SnapSyncError::NoCatchUpProgress)?
339 .resume_after(ancestor)
340 .number;
341 if !self.policy.is_catchable_from(resume, head) {
342 info!(target: "sync::snap", ?pivot, ?ancestor, resume, head, "Orphaned block access lists expired, restarting");
343 return Ok(Step::Restart)
344 }
345 let generation = self.policy.select(&provider, head, self.context.finalized()?)?;
346 let Some(generation) = generation.filter(|g| g.target().number >= ancestor.number) else {
349 return Ok(Step::Wait)
350 };
351 drop(provider);
352 let Some(lists) = self.catch_up.orphaned_lists(reorg.orphaned()).await? else {
354 if !self.policy.awaits_orphaned_lists(ancestor.number, head) {
355 info!(target: "sync::snap", ?pivot, ?ancestor, head, "Orphaned block access lists stayed unavailable, restarting");
356 return Ok(Step::Restart)
357 }
358 debug!(target: "sync::snap", ?pivot, "Orphaned block access lists are unavailable");
359 return Ok(Step::Wait)
360 };
361
362 self.commit_blocking(move |provider| {
364 provider.commit_reorg_recovery(write, ancestor, &lists, generation).map(|_| ())
365 })
366 .await?;
367 info!(target: "sync::snap", ?pivot, ?ancestor, to = ?generation.target(), "Recovered snap state from a pivot reorg");
368 Ok(Step::Continue)
369 }
370
371 fn advance_pivot(&mut self, write: SnapWrite, head: u64) -> Result<SnapWrite, SnapSyncError> {
374 let provider = self.factory.database_provider_rw()?;
375 let from = self.pivot()?;
376 let Some(advanced) =
377 self.session.advance(&provider, write, head, self.context.finalized()?)?
378 else {
379 return Ok(write)
380 };
381 provider.commit()?;
383 info!(target: "sync::snap", ?from, to = ?self.pivot()?, "Advanced snap pivot");
384 Ok(advanced)
385 }
386
387 async fn catch_up(&mut self, write: SnapWrite) -> Result<Option<Step>, SnapSyncError> {
389 let pivot = self.pivot()?.number;
390 loop {
391 match self.catch_up.next(write, pivot).await? {
392 CatchUpStep::Complete => return Ok(None),
393 CatchUpStep::Applied { progress, .. } => {
394 debug!(target: "sync::snap", applied = ?progress.applied(), pivot, "Applied block access lists");
395 self.metrics.applied_block.set(progress.applied().number as f64);
396 }
397 CatchUpStep::Unavailable { peer_id, .. } => {
398 debug!(target: "sync::snap", ?peer_id, pivot, "Peer does not serve the pivot's block access lists");
399 return Ok(Some(Step::Wait))
400 }
401 }
402 if self.cancel.is_cancelled() {
403 return Ok(Some(Step::Stop))
404 }
405 }
406 }
407
408 async fn repair(&mut self) -> Result<Option<Step>, SnapSyncError> {
412 for _ in 0..self.ranges_per_check {
413 let range = match self.accounts.next_repair().await? {
414 None => return Ok(None),
415 Some(AccountRangeStep::Verified(range)) => range,
416 Some(AccountRangeStep::Unavailable { .. }) => return Ok(Some(Step::Wait)),
417 };
418 let Some(slots) = self.storage.repair_slots(&range).await? else {
419 return Ok(Some(Step::Wait))
420 };
421 if let Some(step) = self.download_code(&range).await? {
422 return Ok(Some(step))
423 }
424 let hashed_address = range.origin();
425 let remaining = self.accounts.commit_repair(range, slots).await?;
426 debug!(target: "sync::snap", %hashed_address, remaining, "Committed snap repair batch");
427 if self.cancel.is_cancelled() {
428 return Ok(Some(Step::Stop))
429 }
430 }
431 Ok(Some(Step::Continue))
432 }
433
434 async fn download_ranges(&mut self, write: SnapWrite) -> Result<Step, SnapSyncError> {
436 for _ in 0..self.ranges_per_check {
437 if self.cancel.is_cancelled() {
438 return Ok(Step::Stop)
439 }
440 let range = match self.accounts.next().await? {
441 Some(AccountRangeStep::Verified(range)) => range,
442 Some(AccountRangeStep::Unavailable { origin, peer_id }) => {
443 debug!(target: "sync::snap", ?peer_id, %origin, "Peer does not serve the pivot state");
444 return Ok(Step::Wait)
445 }
446 None => return self.hand_off(write).await,
447 };
448 if let Some(step) = self.download_storage_and_code(&range).await? {
449 return Ok(step)
450 }
451 let coverage = self.accounts.commit(range, Default::default(), Vec::new()).await?;
452 self.metrics.set_account_progress(coverage.next());
453 }
454 Ok(Step::Continue)
455 }
456
457 async fn download_storage_and_code(
460 &mut self,
461 range: &VerifiedRange,
462 ) -> Result<Option<Step>, SnapSyncError> {
463 loop {
464 match self.storage.next(range).await? {
465 StorageRangeStep::Complete => break,
466 StorageRangeStep::Committed(_) => {}
467 StorageRangeStep::Unavailable { peer_id, .. } => {
468 debug!(target: "sync::snap", ?peer_id, "Peer does not serve the pivot's storage");
469 return Ok(Some(Step::Wait))
470 }
471 }
472 if self.cancel.is_cancelled() {
475 return Ok(Some(Step::Stop))
476 }
477 }
478 self.download_code(range).await
479 }
480
481 async fn download_code(
483 &mut self,
484 range: &VerifiedRange,
485 ) -> Result<Option<Step>, SnapSyncError> {
486 loop {
487 match self.bytecode.next(range).await? {
488 BytecodeStep::Complete => return Ok(None),
489 BytecodeStep::Committed { .. } => {}
490 BytecodeStep::Unavailable { peer_id, .. } => {
491 debug!(target: "sync::snap", ?peer_id, "Peer does not serve the pivot's code");
492 return Ok(Some(Step::Wait))
493 }
494 }
495 if self.cancel.is_cancelled() {
496 return Ok(Some(Step::Stop))
497 }
498 }
499 }
500
501 async fn hand_off(&self, write: SnapWrite) -> Result<Step, SnapSyncError> {
504 let cancel = self.shutdown.as_ref().unwrap_or(&self.cancel).clone();
505 self.commit_blocking(move |provider| {
506 provider.start_trie_rebuild(write, DEFAULT_SCAN_CHUNK, &cancel)
507 })
508 .await?;
509 Ok(Step::HandedOff(write))
510 }
511
512 async fn commit_blocking(
514 &self,
515 apply: impl FnOnce(&F::ProviderRW) -> Result<(), SnapSyncError> + Send + 'static,
516 ) -> Result<(), SnapSyncError> {
517 let factory = self.factory.clone();
518 self.runtime
519 .spawn_blocking(move || -> Result<(), SnapSyncError> {
520 let provider = factory.database_provider_rw()?;
521 apply(&provider)?;
522 provider.commit()?;
523 Ok(())
524 })
525 .await
526 .map_err(SnapSyncError::blocking_task)?
527 }
528
529 fn pivot(&self) -> Result<BlockNumHash, SnapSyncError> {
531 self.session.target().map(SnapGeneration::target).ok_or(SnapSyncError::NoAttempt)
532 }
533
534 async fn wait(&mut self, head: u64) -> bool {
536 let Self { cancel, context, .. } = self;
537 cancel.run_until_cancelled(context.wait_for_progress(head)).await.unwrap_or(false)
538 }
539}
540
541impl<C, F, X> fmt::Debug for SnapBootstrap<C, F, X> {
542 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
543 f.debug_struct("SnapBootstrap")
544 .field("policy", &self.policy)
545 .field("session", &self.session)
546 .field("ranges_per_check", &self.ranges_per_check)
547 .finish_non_exhaustive()
548 }
549}
550
551#[derive(Clone, Copy, Debug, Eq, PartialEq)]
553pub enum SnapBootstrapOutcome {
554 TrieRebuild {
557 write: SnapWrite,
559 pivot: BlockNumHash,
561 },
562 Verified {
564 pivot: BlockNumHash,
566 },
567 Stopped,
570 BeforeBlockAccessLists,
572}
573
574pub trait SnapSyncContext: Send {
576 fn head(&self) -> Result<u64, SnapSyncError>;
578
579 fn finalized(&self) -> Result<Option<u64>, SnapSyncError> {
582 Ok(None)
583 }
584
585 fn wait_for_progress(&mut self, head: u64) -> impl Future<Output = bool> + Send;
590}
591
592enum Resolved {
594 Active(SnapWrite),
596 Verified(BlockNumHash),
598 Waiting,
600 BeforeBlockAccessLists,
602}
603
604enum Step {
606 HandedOff(SnapWrite),
607 Continue,
609 Wait,
611 Restart,
613 Stop,
615}
616
617#[derive(Clone, Copy, PartialEq, Eq)]
619enum Wait {
620 Pivot,
621 SnapPeer,
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627 use crate::{
628 common::SnapRecord,
629 reorg::StoredAncestry,
630 test_utils::{
631 account, account_range, hashed_factory, header, key, policy, state_root,
632 storage_ranges, storage_root_of, stored_slots, verified_range, ReorgFactoryExt,
633 ScriptedSnapClient,
634 },
635 StateRepairs, VerifiedSnapState,
636 };
637 use alloy_eip7928::{
638 compute_block_access_list_hash, AccountChanges, BalanceChange, BlockAccessIndex,
639 };
640 use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
641 use metrics_util::debugging::{DebugValue, DebuggingRecorder, Snapshotter};
642 use reth_eth_wire_types::{
643 snap::{AccountRangeMessage, BlockAccessListsMessage},
644 BlockAccessLists,
645 };
646 use reth_network_p2p::{error::PeerRequestResult, snap::client::SnapResponse};
647 use reth_network_peers::{PeerId, WithPeerId};
648 use reth_primitives_traits::{Account, AlloyBlockHeader, Header, SealedHeader};
649 use reth_provider::{
650 test_utils::{insert_headers, MockNodeTypesWithDB},
651 ProviderFactory,
652 };
653 use reth_stages::stages::MerkleStage;
654 use reth_stages_api::{ExecInput, Stage};
655 use reth_stages_types::StageId;
656 use reth_storage_api::{SnapAttemptId, StageCheckpointReader};
657 use reth_trie_common::{HashedPostState, TrieAccount};
658 use std::{
659 cell::RefCell,
660 collections::{HashMap, VecDeque},
661 sync::Arc,
662 };
663
664 type Factory = ProviderFactory<MockNodeTypesWithDB>;
665 type Bootstrap = SnapBootstrap<Arc<ScriptedSnapClient>, Factory, TestContext>;
666
667 const FAR: B256 = B256::repeat_byte(0xaa);
668 const STALE: Address = Address::repeat_byte(0x51);
670 const KEPT: Address = Address::repeat_byte(0x52);
672
673 fn accounts() -> Vec<(B256, TrieAccount)> {
674 vec![(key(1), account(1)), (key(2), account(2)), (FAR, account(3))]
675 }
676
677 fn insert_chain(factory: &Factory, tip: u64, root: B256) {
680 insert_headers(factory, &chain(tip, root));
681 }
682
683 fn empty_lists(request_id: u64, blocks: usize) -> PeerRequestResult<SnapResponse> {
685 lists(request_id, &vec![Vec::new(); blocks], true)
686 }
687
688 fn no_lists(request_id: u64) -> PeerRequestResult<SnapResponse> {
690 lists(request_id, &[Vec::new()], false)
691 }
692
693 fn scripted(
694 factory: &Factory,
695 responses: impl IntoIterator<Item = PeerRequestResult<SnapResponse>>,
696 heads: impl IntoIterator<Item = u64>,
697 ) -> (Arc<ScriptedSnapClient>, Bootstrap) {
698 let client = Arc::new(ScriptedSnapClient::new(responses));
699 let context = TestContext { heads: RefCell::new(heads.into_iter().collect()), waits: 0 };
700 let bootstrap =
701 SnapBootstrap::new(Arc::clone(&client), factory.clone(), Runtime::test(), context)
702 .with_policy(policy());
703 (client, bootstrap)
704 }
705
706 fn downloaded(factory: &Factory) -> SnapWrite {
708 let accounts = accounts();
709 insert_chain(factory, 3, state_root(&accounts));
710 let provider = factory.database_provider_rw().unwrap();
711 let pivot = provider.sealed_header(2).unwrap().unwrap().num_hash();
712 let write =
713 provider.start_snap_attempt(SnapGeneration::new(pivot, state_root(&accounts))).unwrap();
714 provider.start_account_coverage(write).unwrap();
715 let range = verified_range(&accounts, 0..accounts.len(), B256::ZERO, &[]);
716 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
717 provider.commit().unwrap();
718 write
719 }
720
721 fn recorded(mut bootstrap: Bootstrap) -> (Snapshotter, Bootstrap) {
724 let recorder = DebuggingRecorder::new();
725 let snapshotter = recorder.snapshotter();
726 bootstrap.metrics = metrics::with_local_recorder(&recorder, || {
727 SnapSyncMetrics::new_with_labels(Vec::<metrics::Label>::new())
728 });
729 (snapshotter, bootstrap)
730 }
731
732 fn snapshot(snapshotter: &Snapshotter) -> HashMap<String, DebugValue> {
734 snapshotter
735 .snapshot()
736 .into_vec()
737 .into_iter()
738 .map(|(key, _, _, value)| {
739 (key.key().name().trim_start_matches("sync.snap.").to_owned(), value)
740 })
741 .collect()
742 }
743
744 fn attempt_id(factory: &Factory) -> SnapAttemptId {
745 factory.database_provider_ro().unwrap().snap_attempt().unwrap().unwrap().id()
746 }
747
748 struct TestContext {
750 heads: RefCell<VecDeque<u64>>,
751 waits: usize,
752 }
753
754 impl SnapSyncContext for TestContext {
755 fn head(&self) -> Result<u64, SnapSyncError> {
756 let mut heads = self.heads.borrow_mut();
757 let head = if heads.len() > 1 { heads.pop_front() } else { heads.front().copied() };
758 Ok(head.expect("the context is given at least one head"))
759 }
760
761 fn wait_for_progress(&mut self, _head: u64) -> impl Future<Output = bool> + Send {
762 self.waits += 1;
763 std::future::ready(false)
764 }
765 }
766
767 fn chain(tip: u64, root: B256) -> Vec<SealedHeader> {
769 let access_list_hash = compute_block_access_list_hash(&Vec::<AccountChanges>::new());
770 let mut parent = B256::ZERO;
771 (0..=tip)
772 .map(|number| {
773 let mut header = header(number, parent, Some(access_list_hash));
774 header.state_root = root;
775 let sealed = SealedHeader::seal_slow(header);
776 parent = sealed.hash();
777 sealed
778 })
779 .collect()
780 }
781
782 fn branch(
784 parent: &SealedHeader,
785 lists: &[Vec<AccountChanges>],
786 root: B256,
787 ) -> Vec<SealedHeader> {
788 let mut parent = parent.clone();
789 lists
790 .iter()
791 .map(|list| {
792 let access_list_hash = compute_block_access_list_hash(list);
793 let mut header = header(parent.number() + 1, parent.hash(), Some(access_list_hash));
794 header.state_root = root;
795 parent = SealedHeader::seal_slow(header);
796 parent.clone()
797 })
798 .collect()
799 }
800
801 fn lists(
803 request_id: u64,
804 lists: &[Vec<AccountChanges>],
805 served: bool,
806 ) -> PeerRequestResult<SnapResponse> {
807 let block_access_lists =
808 lists.iter().map(|list| served.then(|| alloy_rlp::encode(list).into())).collect();
809 let message = BlockAccessListsMessage {
810 request_id,
811 block_access_lists: BlockAccessLists(block_access_lists),
812 };
813 Ok(WithPeerId::new(PeerId::random(), SnapResponse::BlockAccessLists(message)))
814 }
815
816 fn rebuild_and_verify(factory: &Factory, write: SnapWrite, target: u64) -> VerifiedSnapState {
818 let provider = factory.database_provider_rw().unwrap();
819 let mut stage = MerkleStage::default_execution();
820 loop {
821 let checkpoint = provider.get_stage_checkpoint(StageId::MerkleExecute).unwrap();
822 let output =
823 stage.execute(&provider, ExecInput { target: Some(target), checkpoint }).unwrap();
824 provider.save_stage_checkpoint(StageId::MerkleExecute, output.checkpoint).unwrap();
825 if output.done {
826 break
827 }
828 }
829 provider.verify_state_root(write).unwrap()
830 }
831
832 fn reorg_accounts() -> Vec<(B256, TrieAccount)> {
834 let mut accounts = vec![(keccak256(STALE), account(1)), (keccak256(KEPT), account(2))];
835 accounts.sort_by_key(|(hashed_address, _)| *hashed_address);
836 accounts
837 }
838
839 fn stale_changes() -> AccountChanges {
840 AccountChanges::new(STALE)
841 }
842
843 fn reorged(
847 factory: &Factory,
848 new_lists: [Vec<AccountChanges>; 2],
849 ) -> (SnapAttemptId, Vec<SealedHeader>, Vec<SealedHeader>) {
850 let accounts = reorg_accounts();
851 let root = state_root(&accounts);
852 let shared = chain(2, root);
853 let orphaned = branch(&shared[2], &orphaned_lists(), root);
854 insert_headers(factory, &shared);
855 insert_headers(factory, &orphaned);
856 let provider = factory.database_provider_rw().unwrap();
857 let write =
858 provider.start_snap_attempt(SnapGeneration::new(orphaned[1].num_hash(), root)).unwrap();
859 provider.start_account_coverage(write).unwrap();
860 let range = verified_range(&accounts, 0..2, B256::ZERO, &[]);
861 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
862 let mut credited = Account::from(account(1));
864 credited.balance = U256::from(1_000);
865 let stale = HashedPostState::default().with_accounts([(keccak256(STALE), Some(credited))]);
866 provider.write_hashed_state(&stale.into_sorted()).unwrap();
867 provider.commit().unwrap();
868
869 let [first, second] = new_lists;
870 let new = branch(&shared[2], &[first, second, Vec::new()], root);
871 factory.replace_headers_after(2, &new);
872 (attempt_id(factory), orphaned, new)
873 }
874
875 fn orphaned_lists() -> Vec<Vec<AccountChanges>> {
877 [999, 1_000]
878 .map(|balance| {
879 vec![stale_changes().with_balance_change(BalanceChange::new(
880 BlockAccessIndex::new(1),
881 U256::from(balance),
882 ))]
883 })
884 .to_vec()
885 }
886
887 fn unserved_range(request_id: u64) -> PeerRequestResult<SnapResponse> {
889 let message = AccountRangeMessage { request_id, accounts: Vec::new(), proof: Vec::new() };
890 Ok(WithPeerId::new(PeerId::random(), SnapResponse::AccountRange(message)))
891 }
892
893 fn served_account(
895 request_id: u64,
896 accounts: &[(B256, TrieAccount)],
897 hashed_address: B256,
898 ) -> PeerRequestResult<SnapResponse> {
899 let index = accounts.iter().position(|(key, _)| *key == hashed_address).unwrap();
900 account_range(request_id, accounts, index..index + 1, &[hashed_address])
901 }
902
903 fn hashes(headers: &[SealedHeader]) -> Vec<B256> {
904 headers.iter().map(SealedHeader::hash).collect()
905 }
906
907 async fn recover(new_lists: [Vec<AccountChanges>; 2], repaired: bool) -> Vec<B256> {
910 let accounts = reorg_accounts();
911 let factory = hashed_factory();
912 let (attempt, orphaned, new) = reorged(&factory, new_lists.clone());
913 let mut responses = vec![
914 lists(1, &orphaned_lists(), true),
915 lists(2, &new_lists, true),
917 ];
918 if repaired {
919 responses.push(served_account(1, &accounts, keccak256(STALE)));
920 }
921 let (client, mut bootstrap) = scripted(&factory, responses, [5]);
922
923 let outcome = bootstrap.run().await.unwrap();
924
925 let SnapBootstrapOutcome::TrieRebuild { write, pivot } = outcome else {
926 panic!("the state is repaired: {outcome:?}")
927 };
928 assert_eq!(pivot, new[1].num_hash());
929 assert_eq!(attempt_id(&factory), attempt);
930 assert_eq!(*client.block_requests(), [hashes(&orphaned), hashes(&new[..2])]);
931 let origins = client.origins().clone();
932 let verified = rebuild_and_verify(&factory, write, pivot.number);
934 assert_eq!(verified.state_root(), state_root(&accounts));
935 origins
936 }
937
938 #[tokio::test]
939 async fn downloads_the_state_and_hands_it_to_the_trie_rebuild() {
940 let accounts = accounts();
941 let factory = hashed_factory();
942 insert_chain(&factory, 3, state_root(&accounts));
943 let (client, mut bootstrap) =
944 scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [3]);
945
946 let outcome = bootstrap.run().await.unwrap();
947
948 let SnapBootstrapOutcome::TrieRebuild { write, pivot } = outcome else {
949 panic!("the state is complete: {outcome:?}")
950 };
951 assert_eq!(pivot.number, 2);
952 assert_eq!(*client.origins(), [B256::ZERO]);
953 let provider = factory.database_provider_ro().unwrap();
954 assert!(provider.is_trie_rebuild_started(write).unwrap());
955 }
956
957 #[tokio::test]
958 async fn pending_repairs_follow_the_pivot_once_the_accounts_are_complete() {
959 let accounts = accounts();
960 let root = state_root(&accounts);
961 let factory = hashed_factory();
962 insert_chain(&factory, 8, root);
963 let provider = factory.database_provider_rw().unwrap();
964 let pivot = provider.sealed_header(2).unwrap().unwrap().num_hash();
965 let write = provider.start_snap_attempt(SnapGeneration::new(pivot, root)).unwrap();
966 provider.start_account_coverage(write).unwrap();
967 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
968 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
969 let mut repairs = StateRepairs::default();
970 repairs.insert_account(key(2));
971 provider.schedule_snap_repairs(write, repairs).unwrap();
972 provider.commit().unwrap();
973 let (_, mut bootstrap) = scripted(&factory, [unserved_range(1)], [3]);
975 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
976
977 let responses = [empty_lists(1, 5), account_range(1, &accounts, 1..2, &[key(2)])];
979 let (client, mut bootstrap) = scripted(&factory, responses, [8]);
980 let outcome = bootstrap.run().await.unwrap();
981
982 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
983 panic!("the state is complete: {outcome:?}")
984 };
985 assert_eq!(pivot.number, 7);
986 assert_eq!(*client.origins(), [key(2)]);
987 }
988
989 #[tokio::test]
990 async fn scheduled_repairs_are_fetched_at_the_pivot_before_ranges() {
991 let accounts = accounts();
992 let factory = hashed_factory();
993 insert_chain(&factory, 3, state_root(&accounts));
994 let provider = factory.database_provider_rw().unwrap();
995 let pivot = provider.sealed_header(2).unwrap().unwrap().num_hash();
996 let write =
997 provider.start_snap_attempt(SnapGeneration::new(pivot, state_root(&accounts))).unwrap();
998 provider.start_account_coverage(write).unwrap();
999 let mut repairs = StateRepairs::default();
1000 repairs.insert_account(key(2));
1001 provider.schedule_snap_repairs(write, repairs).unwrap();
1002 provider.commit().unwrap();
1003 let responses =
1005 [account_range(1, &accounts, 1..2, &[key(2)]), account_range(2, &accounts, 0..3, &[])];
1006 let (client, mut bootstrap) = scripted(&factory, responses, [3]);
1007
1008 let outcome = bootstrap.run().await.unwrap();
1009
1010 assert!(matches!(outcome, SnapBootstrapOutcome::TrieRebuild { .. }), "{outcome:?}");
1011 assert_eq!(*client.origins(), [key(2), B256::ZERO]);
1012 let provider = factory.database_provider_ro().unwrap();
1013 assert!(provider.snap_repairs(write).unwrap().is_empty());
1014 }
1015
1016 #[tokio::test]
1017 async fn repairs_yield_to_pivot_checks_between_batches() {
1018 let accounts = accounts();
1019 let root = state_root(&accounts);
1020 let factory = hashed_factory();
1021 insert_chain(&factory, 8, root);
1022 let provider = factory.database_provider_rw().unwrap();
1023 let pivot = provider.sealed_header(2).unwrap().unwrap().num_hash();
1024 let write = provider.start_snap_attempt(SnapGeneration::new(pivot, root)).unwrap();
1025 provider.start_account_coverage(write).unwrap();
1026 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
1027 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
1028 let mut repairs = StateRepairs::default();
1029 repairs.insert_account(key(1));
1030 repairs.insert_account(key(2));
1031 provider.schedule_snap_repairs(write, repairs).unwrap();
1032 provider.commit().unwrap();
1033 let responses = [
1034 account_range(1, &accounts, 0..1, &[key(1)]),
1035 empty_lists(1, 5),
1037 account_range(2, &accounts, 1..2, &[key(2)]),
1038 ];
1039 let (client, bootstrap) = scripted(&factory, responses, [3, 8]);
1040 let mut bootstrap = bootstrap.with_ranges_per_check(1);
1041
1042 let outcome = bootstrap.run().await.unwrap();
1043
1044 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1045 panic!("the state is repaired: {outcome:?}")
1046 };
1047 assert_eq!(pivot.number, 7);
1048 assert_eq!(*client.origins(), [key(1), key(2)]);
1049 assert_eq!(client.block_requests().len(), 1);
1050 }
1051
1052 #[tokio::test]
1053 async fn a_lagging_pivot_advances_before_more_ranges_download() {
1054 let accounts = accounts();
1055 let root = state_root(&accounts);
1056 let factory = hashed_factory();
1057 insert_chain(&factory, 8, root);
1058 let responses = [
1059 account_range(1, &accounts, 0..1, &[key(1)]),
1060 empty_lists(1, 5),
1062 account_range(2, &accounts, 1..3, &[key(2), FAR]),
1063 ];
1064 let (client, bootstrap) = scripted(&factory, responses, [3, 8]);
1066 let (snapshotter, mut bootstrap) = recorded(bootstrap.with_ranges_per_check(1));
1067
1068 let outcome = bootstrap.run().await.unwrap();
1069
1070 let SnapBootstrapOutcome::TrieRebuild { write, pivot } = outcome else {
1071 panic!("the state is complete: {outcome:?}")
1072 };
1073 assert_eq!(pivot.number, 7);
1074 assert_eq!(*client.origins(), [B256::ZERO, key(2)]);
1075 assert_eq!(client.block_requests().len(), 1);
1076 let metrics = snapshot(&snapshotter);
1077 assert_eq!(metrics["pivot_block"], DebugValue::Gauge(7.0.into()));
1078 assert_eq!(metrics["applied_block"], DebugValue::Gauge(7.0.into()));
1079 assert_eq!(metrics["account_progress"], DebugValue::Gauge(1.0.into()));
1080
1081 let verified = rebuild_and_verify(&factory, write, 7);
1083 assert_eq!((verified.target(), verified.state_root()), (pivot, root));
1084 }
1085
1086 #[tokio::test]
1087 async fn peers_without_the_state_wait_and_the_next_run_resumes() {
1088 let accounts = accounts();
1089 let factory = hashed_factory();
1090 insert_chain(&factory, 3, state_root(&accounts));
1091 let responses = [
1092 account_range(1, &accounts, 0..1, &[key(1)]),
1093 Err(RequestError::UnsupportedCapability),
1094 ];
1095 let (_, mut bootstrap) = scripted(&factory, responses, [3]);
1096
1097 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1098 assert_eq!(bootstrap.context.waits, 1);
1099 let attempt = attempt_id(&factory);
1100
1101 let (client, mut resumed) =
1102 scripted(&factory, [account_range(1, &accounts, 1..3, &[key(2), FAR])], [3]);
1103 let outcome = resumed.run().await.unwrap();
1104
1105 assert!(matches!(outcome, SnapBootstrapOutcome::TrieRebuild { .. }));
1106 assert_eq!(attempt_id(&factory), attempt);
1107 assert_eq!(*client.origins(), [key(2)]);
1108 }
1109
1110 #[tokio::test]
1111 async fn no_eligible_pivot_waits_without_starting_an_attempt() {
1112 let factory = hashed_factory();
1113 insert_chain(&factory, 0, state_root(&accounts()));
1114 let (client, mut bootstrap) = scripted(&factory, [], [0]);
1115
1116 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1117
1118 assert_eq!(bootstrap.context.waits, 1);
1119 assert!(client.origins().is_empty());
1120 assert!(factory.database_provider_ro().unwrap().snap_attempt().unwrap().is_none());
1121 }
1122
1123 #[tokio::test]
1124 async fn a_head_before_block_access_lists_falls_back_without_waiting() {
1125 let factory = hashed_factory();
1126 let genesis = header(0, B256::ZERO, None);
1127 insert_headers(&factory, &[SealedHeader::seal_slow(genesis)]);
1128 let (client, mut bootstrap) = scripted(&factory, [], [0]);
1129
1130 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::BeforeBlockAccessLists);
1131
1132 assert_eq!(bootstrap.context.waits, 0);
1133 assert!(client.origins().is_empty());
1134 assert!(factory.database_provider_ro().unwrap().snap_attempt().unwrap().is_none());
1135 }
1136
1137 #[tokio::test]
1138 async fn a_reorged_pivot_without_kept_blocks_restarts_the_attempt() {
1139 let accounts = accounts();
1140 let factory = hashed_factory();
1141 let blocks = chain(3, state_root(&accounts));
1142 insert_headers(&factory, &blocks);
1143 let provider = factory.database_provider_rw().unwrap();
1145 let orphaned = SnapGeneration::new(blocks[2].num_hash(), state_root(&accounts));
1146 provider.start_snap_attempt(orphaned).unwrap();
1147 StoredAncestry::clear(&provider).unwrap();
1148 provider.commit().unwrap();
1149 let mut replaced = chain(3, state_root(&accounts));
1150 replaced[2] = SealedHeader::seal_slow(Header {
1151 extra_data: Bytes::from_static(&[1]),
1152 ..replaced[2].header().clone()
1153 });
1154 replaced[3] = SealedHeader::seal_slow(Header {
1155 parent_hash: replaced[2].hash(),
1156 ..replaced[3].header().clone()
1157 });
1158 factory.replace_headers_after(1, &replaced[2..]);
1159 let orphaned = attempt_id(&factory);
1160 let (_, bootstrap) = scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [3]);
1161 let (snapshotter, mut bootstrap) = recorded(bootstrap);
1162
1163 let outcome = bootstrap.run().await.unwrap();
1164
1165 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1166 panic!("the state is complete: {outcome:?}")
1167 };
1168 assert_ne!(pivot.hash, blocks[2].hash());
1169 assert_ne!(attempt_id(&factory), orphaned);
1170 let metrics = snapshot(&snapshotter);
1171 assert_eq!(metrics["restarts"], DebugValue::Counter(1));
1172 }
1173
1174 #[tokio::test]
1175 async fn a_handed_off_attempt_is_not_downloaded_again() {
1176 let accounts = accounts();
1177 let factory = hashed_factory();
1178 insert_chain(&factory, 3, state_root(&accounts));
1179 let (_, mut bootstrap) = scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [3]);
1180 let first = bootstrap.run().await.unwrap();
1181
1182 let (client, resumed) = scripted(&factory, [], [3]);
1183 let (snapshotter, mut resumed) = recorded(resumed);
1184
1185 assert_eq!(resumed.run().await.unwrap(), first);
1186 assert!(client.origins().is_empty());
1187 let metrics = snapshot(&snapshotter);
1189 assert_eq!(metrics["account_progress"], DebugValue::Gauge(1.0.into()));
1190 assert_eq!(metrics["applied_block"], DebugValue::Gauge(2.0.into()));
1191 }
1192
1193 #[tokio::test]
1194 async fn a_catch_up_stalled_past_the_served_lists_restarts() {
1195 let accounts = accounts();
1196 let factory = hashed_factory();
1197 insert_chain(&factory, 11, state_root(&accounts));
1198 let responses = [account_range(1, &accounts, 0..1, &[key(1)]), no_lists(1)];
1200 let (_, stalled) = scripted(&factory, responses, [3, 8]);
1201 let mut stalled = stalled.with_ranges_per_check(1);
1202 assert_eq!(stalled.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1203 let attempt = attempt_id(&factory);
1204
1205 let (client, mut restarted) =
1207 scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [11]);
1208 let outcome = restarted.run().await.unwrap();
1209
1210 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1211 panic!("the state is complete: {outcome:?}")
1212 };
1213 assert_eq!(pivot.number, 10);
1214 assert_ne!(attempt_id(&factory), attempt);
1215 assert!(client.block_requests().is_empty());
1216 }
1217
1218 #[tokio::test]
1219 async fn complete_state_is_handed_off_past_the_served_lists() {
1220 let accounts = accounts();
1221 let factory = hashed_factory();
1222 insert_chain(&factory, 11, state_root(&accounts));
1223 let provider = factory.database_provider_rw().unwrap();
1225 let pivot = provider.sealed_header(2).unwrap().unwrap();
1226 let generation = SnapGeneration::new(pivot.num_hash(), pivot.state_root);
1227 let downloaded = provider.start_snap_attempt(generation).unwrap();
1228 provider.start_account_coverage(downloaded).unwrap();
1229 let range = verified_range(&accounts, 0..3, B256::ZERO, &[]);
1230 provider.commit_account_range(downloaded, &range, Default::default(), Vec::new()).unwrap();
1231 provider.commit().unwrap();
1232
1233 let (client, resumed) = scripted(&factory, [], [11]);
1235 let (snapshotter, mut resumed) = recorded(resumed);
1236 let outcome = resumed.run().await.unwrap();
1237
1238 assert_eq!(
1239 outcome,
1240 SnapBootstrapOutcome::TrieRebuild { write: downloaded, pivot: pivot.num_hash() }
1241 );
1242 assert!(client.origins().is_empty());
1243 assert!(client.block_requests().is_empty());
1244 let metrics = snapshot(&snapshotter);
1246 assert_eq!(metrics["pivot_block"], DebugValue::Gauge(2.0.into()));
1247 assert_eq!(metrics["account_progress"], DebugValue::Gauge(1.0.into()));
1248 }
1249
1250 #[tokio::test]
1251 async fn unreadable_progress_restarts_the_attempt() {
1252 for record in [br#"{"version":999}"#.as_slice(), b"not json"] {
1254 let accounts = accounts();
1255 let factory = hashed_factory();
1256 insert_chain(&factory, 3, state_root(&accounts));
1257 let (_, mut stopped) =
1258 scripted(&factory, [Err(RequestError::UnsupportedCapability)], [3]);
1259 assert_eq!(stopped.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1260 let provider = factory.database_provider_rw().unwrap();
1262 let stale = provider.active_snap_write().unwrap().unwrap();
1263 provider.write_metadata("snap_account_coverage", record.to_vec()).unwrap();
1264 provider.commit().unwrap();
1265
1266 let (_, mut restarted) =
1267 scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [3]);
1268 let outcome = restarted.run().await.unwrap();
1269
1270 let SnapBootstrapOutcome::TrieRebuild { write, .. } = outcome else {
1271 panic!("the state is complete: {outcome:?}")
1272 };
1273 assert_ne!(write.attempt(), stale.attempt());
1274 let provider = factory.database_provider_ro().unwrap();
1275 assert!(matches!(
1276 provider.authorize_snap_write(stale),
1277 Err(SnapSyncError::StaleWrite { .. })
1278 ));
1279 }
1280 }
1281
1282 #[tokio::test]
1283 async fn cancellation_between_storage_responses_stops_the_run() {
1284 let slots = vec![(key(1), U256::from(11)), (key(2), U256::from(12))];
1285 let mut contract = account(1);
1286 contract.storage_root = storage_root_of(&slots);
1287 let accounts = vec![(key(1), contract)];
1288 let factory = hashed_factory();
1289 insert_chain(&factory, 3, state_root(&accounts));
1290 let cancel = CancellationToken::new();
1291 let on_request = cancel.clone();
1292 let client = Arc::new(
1294 ScriptedSnapClient::new([
1295 account_range(1, &accounts, 0..1, &[]),
1296 storage_ranges(1, &[&slots[..1]], &slots, &[B256::ZERO, key(1)]),
1297 ])
1298 .on_storage_request(move || on_request.cancel()),
1299 );
1300 let context = TestContext { heads: RefCell::new(VecDeque::from([3])), waits: 0 };
1301 let mut bootstrap =
1302 SnapBootstrap::new(Arc::clone(&client), factory.clone(), Runtime::test(), context)
1303 .with_policy(policy())
1304 .with_cancellation(cancel);
1305
1306 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1307 assert_eq!(client.storage_requests().len(), 1);
1308 }
1309
1310 #[tokio::test]
1311 async fn a_new_branch_too_short_for_a_pivot_waits_before_fetching_lists() {
1312 let factory = hashed_factory();
1313 let (attempt, _, _) = reorged(&factory, [Vec::new(), Vec::new()]);
1314 let (client, mut bootstrap) = scripted(&factory, [], [2]);
1316
1317 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1318
1319 assert!(client.block_requests().is_empty());
1320 assert_eq!(attempt_id(&factory), attempt);
1321 }
1322
1323 #[tokio::test]
1324 async fn a_handed_off_pivot_reorged_before_verification_restarts_the_attempt() {
1325 let accounts = accounts();
1326 let root = state_root(&accounts);
1327 let factory = hashed_factory();
1328 let shared = chain(3, root);
1329 insert_headers(&factory, &shared);
1330 let (_, mut bootstrap) = scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [3]);
1331 let SnapBootstrapOutcome::TrieRebuild { pivot: orphaned, .. } =
1332 bootstrap.run().await.unwrap()
1333 else {
1334 panic!("the state is complete")
1335 };
1336 let attempt = attempt_id(&factory);
1337 let new = branch(&shared[1], &[vec![stale_changes()], Vec::new()], root);
1339 factory.replace_headers_after(1, &new);
1340 let (_, mut resumed) = scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [3]);
1341
1342 let outcome = resumed.run().await.unwrap();
1343
1344 let SnapBootstrapOutcome::TrieRebuild { write, pivot } = outcome else {
1345 panic!("the new branch's state is complete: {outcome:?}")
1346 };
1347 assert_ne!(pivot, orphaned);
1348 assert_eq!(pivot, new[0].num_hash());
1349 assert_ne!(attempt_id(&factory), attempt);
1350 let verified = rebuild_and_verify(&factory, write, pivot.number);
1351 assert_eq!(verified.state_root(), root);
1352 }
1353
1354 #[tokio::test]
1355 async fn a_field_only_the_orphaned_branch_changed_is_fetched_again() {
1356 let origins = recover([Vec::new(), Vec::new()], true).await;
1357
1358 assert_eq!(origins, [keccak256(STALE)]);
1360 }
1361
1362 #[tokio::test]
1363 async fn a_field_the_new_branch_changes_too_needs_no_repair() {
1364 let balance = stale_changes()
1365 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::ONE));
1366
1367 let origins = recover([vec![balance], Vec::new()], false).await;
1368
1369 assert!(origins.is_empty());
1370 }
1371
1372 #[tokio::test]
1373 async fn a_second_reorg_repairs_what_the_first_new_branch_changed() {
1374 let accounts = reorg_accounts();
1375 let factory = hashed_factory();
1376 let kept = AccountChanges::new(KEPT)
1377 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::from(7)));
1378 let first_lists = [vec![kept], Vec::new()];
1379 let (attempt, _, first) = reorged(&factory, first_lists.clone());
1380 let responses =
1382 [lists(1, &orphaned_lists(), true), lists(2, &first_lists, true), unserved_range(1)];
1383 let (_, mut bootstrap) = scripted(&factory, responses, [5]);
1384 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1385
1386 let shared = factory.database_provider_ro().unwrap().sealed_header(2).unwrap().unwrap();
1388 let second = branch(&shared, &[Vec::new(), Vec::new(), Vec::new()], state_root(&accounts));
1389 factory.replace_headers_after(2, &second);
1390 let mut responses =
1391 vec![lists(1, &first_lists, true), lists(2, &[Vec::new(), Vec::new()], true)];
1392 let mut repaired = [keccak256(STALE), keccak256(KEPT)];
1394 repaired.sort();
1395 for (id, hashed_address) in (1..).zip(repaired) {
1396 responses.push(served_account(id, &accounts, hashed_address));
1397 }
1398 let (client, mut bootstrap) = scripted(&factory, responses, [5]);
1399
1400 let outcome = bootstrap.run().await.unwrap();
1401
1402 let SnapBootstrapOutcome::TrieRebuild { write, pivot } = outcome else {
1403 panic!("the state is repaired: {outcome:?}")
1404 };
1405 assert_eq!(pivot, second[1].num_hash());
1406 assert_eq!(attempt_id(&factory), attempt);
1407 assert_eq!(client.block_requests()[0], hashes(&first[..2]));
1408 assert_eq!(*client.origins(), repaired);
1409 let verified = rebuild_and_verify(&factory, write, pivot.number);
1410 assert_eq!(verified.state_root(), state_root(&accounts));
1411 }
1412
1413 #[tokio::test]
1414 async fn unserved_orphaned_lists_are_waited_for() {
1415 let accounts = reorg_accounts();
1416 let factory = hashed_factory();
1417 let (attempt, orphaned, new) = reorged(&factory, [Vec::new(), Vec::new()]);
1418 let (_, bootstrap) = scripted(&factory, [lists(1, &orphaned_lists(), false)], [5]);
1419 let (snapshotter, mut bootstrap) = recorded(bootstrap);
1420 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1421 assert_eq!(attempt_id(&factory), attempt);
1422 let metrics = snapshot(&snapshotter);
1423 assert_eq!(metrics["account_progress"], DebugValue::Gauge(1.0.into()));
1424 assert_eq!(metrics["applied_block"], DebugValue::Gauge(4.0.into()));
1425
1426 let responses = [
1428 lists(1, &orphaned_lists(), true),
1429 lists(2, &[Vec::new(), Vec::new()], true),
1430 served_account(1, &accounts, keccak256(STALE)),
1431 ];
1432 let (client, mut bootstrap) = scripted(&factory, responses, [5]);
1433 let outcome = bootstrap.run().await.unwrap();
1434
1435 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1436 panic!("the state is repaired: {outcome:?}")
1437 };
1438 assert_eq!(pivot, new[1].num_hash());
1439 assert_eq!(attempt_id(&factory), attempt);
1440 assert_eq!(client.block_requests()[0], hashes(&orphaned));
1441 }
1442
1443 #[tokio::test]
1444 async fn a_reorg_across_block_access_list_activation_restarts_the_attempt() {
1445 let accounts = accounts();
1446 let root = state_root(&accounts);
1447 let factory = hashed_factory();
1448 let mut shared = chain(1, root);
1449 let mut ancestor = header(2, shared[1].hash(), None);
1451 ancestor.state_root = root;
1452 shared.push(SealedHeader::seal_slow(ancestor));
1453 let orphaned = branch(&shared[2], &orphaned_lists(), root);
1454 insert_headers(&factory, &shared);
1455 insert_headers(&factory, &orphaned);
1456 let provider = factory.database_provider_rw().unwrap();
1457 provider.start_snap_attempt(SnapGeneration::new(orphaned[1].num_hash(), root)).unwrap();
1458 provider.commit().unwrap();
1459 let attempt = attempt_id(&factory);
1460 let new = branch(&shared[2], &[vec![stale_changes()], Vec::new(), Vec::new()], root);
1461 factory.replace_headers_after(2, &new);
1462 let (client, mut bootstrap) =
1463 scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [5]);
1464
1465 let outcome = bootstrap.run().await.unwrap();
1466
1467 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1468 panic!("the new branch's state is complete: {outcome:?}")
1469 };
1470 assert_eq!(pivot, new[1].num_hash());
1471 assert_ne!(attempt_id(&factory), attempt);
1472 assert!(client.block_requests().is_empty());
1473 }
1474
1475 #[tokio::test]
1476 async fn a_catch_up_stalled_below_the_ancestor_restarts_before_fetching_lists() {
1477 let accounts = accounts();
1478 let root = state_root(&accounts);
1479 let factory = hashed_factory();
1480 let shared = chain(2, root);
1481 let orphaned = branch(&shared[2], &orphaned_lists(), root);
1482 insert_headers(&factory, &shared);
1483 insert_headers(&factory, &orphaned);
1484 let provider = factory.database_provider_rw().unwrap();
1486 let write =
1487 provider.start_snap_attempt(SnapGeneration::new(shared[1].num_hash(), root)).unwrap();
1488 provider
1489 .advance_snap_pivot(write, SnapGeneration::new(orphaned[1].num_hash(), root))
1490 .unwrap();
1491 provider.commit().unwrap();
1492 let attempt = attempt_id(&factory);
1493 let mut lists = vec![vec![stale_changes()]];
1494 lists.resize(8, Vec::new());
1495 let new = branch(&shared[2], &lists, root);
1496 factory.replace_headers_after(2, &new);
1497 let (client, mut bootstrap) =
1499 scripted(&factory, [account_range(1, &accounts, 0..3, &[])], [10]);
1500
1501 let outcome = bootstrap.run().await.unwrap();
1502
1503 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1504 panic!("the new branch's state is complete: {outcome:?}")
1505 };
1506 assert_eq!(pivot, new[6].num_hash());
1507 assert_ne!(attempt_id(&factory), attempt);
1508 assert!(client.block_requests().is_empty());
1509 }
1510
1511 #[tokio::test]
1512 async fn orphaned_lists_unserved_past_the_state_window_restart_the_attempt() {
1513 let accounts = accounts();
1514 let root = state_root(&accounts);
1515 let factory = hashed_factory();
1516 let shared = chain(2, root);
1517 let orphaned = branch(&shared[2], &orphaned_lists(), root);
1518 insert_headers(&factory, &shared);
1519 insert_headers(&factory, &orphaned);
1520 let provider = factory.database_provider_rw().unwrap();
1521 provider.start_snap_attempt(SnapGeneration::new(orphaned[1].num_hash(), root)).unwrap();
1522 provider.commit().unwrap();
1523 let attempt = attempt_id(&factory);
1524 let mut new_lists = vec![vec![stale_changes()]];
1525 new_lists.resize(129, Vec::new());
1526 let new = branch(&shared[2], &new_lists, root);
1527 factory.replace_headers_after(2, &new);
1528 let responses =
1531 [lists(1, &orphaned_lists(), false), account_range(1, &accounts, 0..3, &[])];
1532 let (_, bootstrap) = scripted(&factory, responses, [131]);
1533 let mut bootstrap = bootstrap.with_policy(policy().with_history(256));
1534
1535 let outcome = bootstrap.run().await.unwrap();
1536
1537 let SnapBootstrapOutcome::TrieRebuild { pivot, .. } = outcome else {
1538 panic!("the new branch's state is complete: {outcome:?}")
1539 };
1540 assert_eq!(pivot, new[127].num_hash());
1541 assert_ne!(attempt_id(&factory), attempt);
1542 }
1543
1544 #[tokio::test]
1545 async fn a_cancelled_repair_resumes_at_its_pending_slots() {
1546 let slots = vec![(key(1), U256::from(11)), (key(2), U256::from(12))];
1547 let mut contract = account(1);
1548 contract.storage_root = storage_root_of(&slots);
1549 let accounts = vec![(key(1), contract)];
1550 let factory = hashed_factory();
1551 insert_chain(&factory, 3, state_root(&accounts));
1552 let provider = factory.database_provider_rw().unwrap();
1553 let pivot = provider.sealed_header(2).unwrap().unwrap().num_hash();
1554 let write =
1555 provider.start_snap_attempt(SnapGeneration::new(pivot, state_root(&accounts))).unwrap();
1556 provider.start_account_coverage(write).unwrap();
1557 let mut repairs = StateRepairs::default();
1558 repairs.insert_slot(key(1), key(1));
1559 repairs.insert_slot(key(1), key(2));
1560 provider.schedule_snap_repairs(write, repairs).unwrap();
1561 provider.commit().unwrap();
1562 let cancel = CancellationToken::new();
1563 let on_request = cancel.clone();
1564 let client = Arc::new(
1567 ScriptedSnapClient::new([
1568 account_range(1, &accounts, 0..1, &[key(1)]),
1569 storage_ranges(1, &[&slots[..1]], &slots, &[key(1)]),
1570 storage_ranges(2, &[], &[], &[]),
1571 ])
1572 .on_storage_request(move || on_request.cancel()),
1573 );
1574 let context = TestContext { heads: RefCell::new(VecDeque::from([3])), waits: 0 };
1575 let mut bootstrap =
1576 SnapBootstrap::new(Arc::clone(&client), factory.clone(), Runtime::test(), context)
1577 .with_policy(policy())
1578 .with_cancellation(cancel);
1579 assert_eq!(bootstrap.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1580 assert_eq!(client.storage_requests().len(), 2);
1581 let provider = factory.database_provider_ro().unwrap();
1582 let pending = provider.snap_repairs(write).unwrap().slots(key(1)).collect::<Vec<_>>();
1583 assert_eq!(pending, [key(2)]);
1584 assert_eq!(stored_slots(&provider, key(1)), slots[..1]);
1585 drop(provider);
1586
1587 let (client, mut resumed) = scripted(
1588 &factory,
1589 [
1590 account_range(1, &accounts, 0..1, &[key(1)]),
1591 storage_ranges(1, &[&slots[1..]], &slots, &[key(2)]),
1592 Err(RequestError::UnsupportedCapability),
1593 ],
1594 [3],
1595 );
1596 assert_eq!(resumed.run().await.unwrap(), SnapBootstrapOutcome::Stopped);
1597 assert_eq!(*client.storage_requests(), [(vec![key(1)], key(2))]);
1598 let provider = factory.database_provider_ro().unwrap();
1599 assert!(provider.snap_repairs(write).unwrap().is_empty());
1600 assert_eq!(stored_slots(&provider, key(1)), slots);
1601 }
1602
1603 #[tokio::test]
1604 async fn a_cancelled_run_still_finishes_the_hand_off_scan() {
1605 let factory = hashed_factory();
1606 let write = downloaded(&factory);
1607 let cancel = CancellationToken::new();
1608 cancel.cancel();
1609 let (_, bootstrap) = scripted(&factory, [], [3]);
1610 let bootstrap = bootstrap.with_cancellation(cancel).with_shutdown(CancellationToken::new());
1611
1612 assert!(matches!(bootstrap.hand_off(write).await, Ok(Step::HandedOff(_))));
1614
1615 let provider = factory.database_provider_ro().unwrap();
1616 assert!(provider.is_trie_rebuild_started(write).unwrap());
1617 }
1618
1619 #[tokio::test]
1620 async fn shutdown_stops_the_hand_off_scan_without_committing() {
1621 let factory = hashed_factory();
1622 let write = downloaded(&factory);
1623 let shutdown = CancellationToken::new();
1624 shutdown.cancel();
1625 let (_, bootstrap) = scripted(&factory, [], [3]);
1626 let bootstrap = bootstrap.with_shutdown(shutdown);
1627
1628 assert!(matches!(bootstrap.hand_off(write).await, Err(SnapSyncError::Cancelled)));
1629
1630 let provider = factory.database_provider_ro().unwrap();
1631 assert!(!provider.is_trie_rebuild_started(write).unwrap());
1632 }
1633}