1use crate::{
6 common::DownloadContext, CatchUpProgress, SnapAttemptStore, SnapCatchUpStore, SnapSyncError,
7 SnapWrite,
8};
9use alloy_eips::{eip7928::bal::DecodedBal, BlockNumHash};
10use alloy_primitives::Sealable;
11use reth_db_api::transaction::DbTxMut;
12use reth_downloaders::snap::{BlockAccessListDownloader, BlockAccessListOutcome};
13use reth_eth_wire_types::snap::GetBlockAccessListsMessage;
14use reth_network_p2p::snap::client::SnapClient;
15use reth_network_peers::PeerId;
16use reth_primitives_traits::{AlloyBlockHeader, SealedHeader};
17use reth_storage_api::{
18 BlockHashReader, DBProvider, DatabaseProviderFactory, HeaderProvider, MetadataProvider,
19 MetadataWriter, StateWriter,
20};
21use reth_tasks::Runtime;
22use std::fmt;
23
24pub const DEFAULT_BAL_RESPONSE_BYTES: u64 = 2 * 1024 * 1024;
26
27pub const DEFAULT_CATCH_UP_BLOCKS: u64 = 28;
30
31pub struct BlockAccessListCatchUp<C, F> {
33 context: DownloadContext<C, F>,
34 max_blocks: u64,
36}
37
38impl<C, F> BlockAccessListCatchUp<C, F> {
39 pub const fn new(client: C, factory: F, runtime: Runtime) -> Self {
41 let mut context = DownloadContext::new(client, factory, runtime);
42 context.set_response_bytes(DEFAULT_BAL_RESPONSE_BYTES);
43 Self { context, max_blocks: DEFAULT_CATCH_UP_BLOCKS }
44 }
45
46 pub const fn with_response_bytes(mut self, response_bytes: u64) -> Self {
48 self.context.set_response_bytes(response_bytes);
49 self
50 }
51
52 pub const fn with_max_blocks(mut self, max_blocks: u64) -> Self {
54 self.max_blocks = if max_blocks == 0 { 1 } else { max_blocks };
55 self
56 }
57}
58
59impl<C, F> BlockAccessListCatchUp<C, F>
60where
61 C: SnapClient + Clone + Unpin,
62 F: DatabaseProviderFactory + Clone + 'static,
63 F::Provider: HeaderProvider + MetadataProvider + BlockHashReader,
64 F::ProviderRW:
65 BlockHashReader + MetadataProvider + MetadataWriter + StateWriter + DBProvider<Tx: DbTxMut>,
66{
67 pub async fn next(
73 &mut self,
74 write: SnapWrite,
75 target: u64,
76 ) -> Result<CatchUpStep, SnapSyncError> {
77 let max_blocks = self.max_blocks;
78 let headers = self
79 .context
80 .read(move |provider| pending_headers(provider, write, target, max_blocks))
81 .await?;
82 if headers.is_empty() {
83 return Ok(CatchUpStep::Complete)
84 }
85
86 let verified = match self.request(&headers).await? {
87 BlockAccessListOutcome::Verified(verified) => verified,
88 BlockAccessListOutcome::Unavailable { peer_id } => {
89 return Ok(CatchUpStep::Unavailable { peer_id })
90 }
91 };
92
93 let peer_id = verified.peer_id();
94 let applied = verified
97 .into_block_access_lists()
98 .into_iter()
99 .zip(&headers)
100 .map_while(|((_, list), header)| {
101 let block = BlockNumHash::new(header.number(), header.hash());
102 list.map(|list| (block, header.parent_hash(), list))
103 })
104 .collect::<Vec<_>>();
105 if applied.is_empty() {
106 return Ok(CatchUpStep::Unavailable { peer_id })
107 }
108
109 let blocks = applied.len();
110 let progress = self
111 .context
112 .commit(move |provider| {
113 let mut progress = None;
114 for (block, parent, list) in applied {
115 progress = Some(provider.commit_block_access_list(
116 write,
117 block,
118 parent,
119 list.as_bal(),
120 )?);
121 }
122 Ok(progress.expect("the applied run is not empty"))
123 })
124 .await?;
125 Ok(CatchUpStep::Applied { progress, blocks })
126 }
127
128 pub async fn orphaned_lists<H: AlloyBlockHeader + Sealable>(
134 &mut self,
135 headers: &[SealedHeader<H>],
136 ) -> Result<Option<Vec<DecodedBal>>, SnapSyncError> {
137 let mut lists = Vec::with_capacity(headers.len());
138 while lists.len() < headers.len() {
139 let end = headers.len().min(lists.len() + self.max_blocks as usize);
140 let BlockAccessListOutcome::Verified(verified) =
141 self.request(&headers[lists.len()..end]).await?
142 else {
143 return Ok(None)
144 };
145 let served = lists.len();
146 lists
147 .extend(verified.into_block_access_lists().into_iter().map_while(|(_, list)| list));
148 if lists.len() == served {
149 return Ok(None)
150 }
151 }
152 Ok(Some(lists))
153 }
154
155 async fn request<H: AlloyBlockHeader + Sealable>(
157 &mut self,
158 headers: &[SealedHeader<H>],
159 ) -> Result<BlockAccessListOutcome, SnapSyncError> {
160 let request = GetBlockAccessListsMessage {
161 request_id: self.context.next_request_id(),
162 block_hashes: headers.iter().map(SealedHeader::hash).collect(),
163 response_bytes: self.context.response_bytes(),
164 };
165 let downloader = BlockAccessListDownloader::new(
166 self.context.client().clone(),
167 request,
168 headers,
169 self.context.runtime().clone(),
170 )?;
171 Ok(downloader.await?)
172 }
173}
174
175impl<C, F> fmt::Debug for BlockAccessListCatchUp<C, F> {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 f.debug_struct("BlockAccessListCatchUp")
178 .field("context", &self.context)
179 .field("max_blocks", &self.max_blocks)
180 .finish()
181 }
182}
183
184#[derive(Debug)]
186pub enum CatchUpStep {
187 Applied {
189 progress: CatchUpProgress,
191 blocks: usize,
193 },
194 Unavailable {
196 peer_id: PeerId,
198 },
199 Complete,
201}
202
203fn pending_headers<P: HeaderProvider + MetadataProvider + BlockHashReader>(
209 provider: &P,
210 write: SnapWrite,
211 target: u64,
212 max_blocks: u64,
213) -> Result<Vec<SealedHeader<P::Header>>, SnapSyncError> {
214 let progress = provider.catch_up_progress(write)?.ok_or(SnapSyncError::NoCatchUpProgress)?;
215 let applied = progress.applied();
216 let canonical = provider
217 .sealed_header(applied.number)?
218 .ok_or(SnapSyncError::MissingHeader { block: applied.number })?;
219 if canonical.hash() != applied.hash {
220 return Err(SnapSyncError::ForkedBlock { expected: applied.hash, got: canonical.hash() })
221 }
222 let target = target.min(provider.authorize_canonical_snap_write(write)?.pivot().number);
224 if target <= applied.number {
225 return Ok(Vec::new())
226 }
227
228 let end = target.min(applied.number.saturating_add(max_blocks));
229 let headers = provider.sealed_headers_range(progress.next()..=end)?;
230 if headers.is_empty() {
231 return Err(SnapSyncError::MissingHeader { block: progress.next() })
232 }
233 for (number, header) in (progress.next()..=end).zip(&headers) {
236 if header.number() != number {
237 return Err(SnapSyncError::MissingHeader { block: number })
238 }
239 }
240 Ok(headers)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::{
247 test_utils::{
248 account, hashed_factory, key, state_root, verified_range, BalChain, ScriptedSnapClient,
249 SnapStateSnapshot,
250 },
251 SnapAccountStore, SnapGeneration,
252 };
253 use alloy_eip7928::{AccountChanges, BalanceChange, BlockAccessIndex};
254 use alloy_primitives::{keccak256, Address, B256, U256};
255 use reth_db_api::{cursor::DbCursorRO, tables, transaction::DbTx};
256 use reth_network_p2p::{error::PeerRequestResult, snap::client::SnapResponse};
257 use reth_provider::{
258 test_utils::{insert_headers, MockNodeTypesWithDB},
259 ProviderFactory,
260 };
261 use reth_trie_common::{TrieAccount, EMPTY_ROOT_HASH};
262 use std::sync::Arc;
263
264 type Factory = ProviderFactory<MockNodeTypesWithDB>;
265 type CatchUp = BlockAccessListCatchUp<Arc<ScriptedSnapClient>, Factory>;
266
267 const CHANGED: Address = Address::repeat_byte(0xaa);
269 const PIVOT: u64 = 2;
271
272 fn accounts() -> Vec<(B256, TrieAccount)> {
273 let mut accounts = vec![(key(1), account(1)), (keccak256(CHANGED), account(2))];
274 accounts.sort_by_key(|(hashed_address, _)| *hashed_address);
275 accounts
276 }
277
278 fn credit(balance: u64) -> Vec<AccountChanges> {
280 vec![AccountChanges::new(CHANGED)
281 .with_balance_change(BalanceChange::new(BlockAccessIndex::new(1), U256::from(balance)))]
282 }
283
284 fn chain() -> BalChain {
286 BalChain::new(PIVOT, [credit(10), credit(20), credit(30)])
287 }
288
289 fn started(chain: &BalChain, accounts: &[(B256, TrieAccount)]) -> (Factory, SnapWrite) {
292 let factory = hashed_factory();
293 insert_headers(&factory, &chain.headers);
294 let provider = factory.database_provider_rw().unwrap();
295 let write = provider.start_snap_attempt(chain.generation(state_root(accounts))).unwrap();
296 provider.start_account_coverage(write).unwrap();
297 let range = verified_range(accounts, 0..accounts.len(), B256::ZERO, &[]);
298 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
299 let tip = SnapGeneration::new(chain.tip(), state_root(accounts));
300 let write = provider.advance_snap_pivot(write, tip).unwrap();
301 provider.commit().unwrap();
302 (factory, write)
303 }
304
305 fn pivot_reorg_fixture() -> (Factory, SnapWrite, BalChain, BalChain) {
309 let chain = BalChain::new(PIVOT, [Vec::new(), credit(30)]);
310 let replacement = BalChain::new(PIVOT, [Vec::new(), Vec::new()]);
311 assert_eq!(chain.block(1), replacement.block(1));
312 assert_ne!(chain.tip(), replacement.tip());
313 let mut accounts = accounts();
314 let factory = hashed_factory();
315 insert_headers(&factory, &chain.headers);
316 let provider = factory.database_provider_rw().unwrap();
317 let write = provider.start_snap_attempt(chain.generation(state_root(&accounts))).unwrap();
318 provider.start_account_coverage(write).unwrap();
319 accounts.iter_mut().find(|(key, _)| *key == keccak256(CHANGED)).unwrap().1.balance =
320 U256::from(30);
321 let write = provider
322 .advance_snap_pivot(write, SnapGeneration::new(chain.tip(), state_root(&accounts)))
323 .unwrap();
324 let range = verified_range(&accounts, 0..accounts.len(), B256::ZERO, &[]);
325 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
326 provider.commit().unwrap();
327 (factory, write, chain, replacement)
328 }
329
330 fn catch_up(
331 responses: impl IntoIterator<Item = PeerRequestResult<SnapResponse>>,
332 factory: Factory,
333 ) -> (Arc<ScriptedSnapClient>, CatchUp) {
334 catch_up_with(responses, factory, DEFAULT_CATCH_UP_BLOCKS)
335 }
336
337 fn catch_up_with(
338 responses: impl IntoIterator<Item = PeerRequestResult<SnapResponse>>,
339 factory: Factory,
340 max_blocks: u64,
341 ) -> (Arc<ScriptedSnapClient>, CatchUp) {
342 let client = Arc::new(ScriptedSnapClient::new(responses));
343 let catch_up = BlockAccessListCatchUp::new(Arc::clone(&client), factory, Runtime::test());
344 (client, catch_up.with_max_blocks(max_blocks))
345 }
346
347 async fn applied(catch_up: &mut CatchUp, write: SnapWrite, target: u64) -> CatchUpProgress {
348 match catch_up.next(write, target).await.unwrap() {
349 CatchUpStep::Applied { progress, .. } => progress,
350 step => panic!("expected an application, got {step:?}"),
351 }
352 }
353
354 fn balance(factory: &Factory) -> U256 {
355 let provider = factory.database_provider_ro().unwrap();
356 provider
357 .tx_ref()
358 .get::<tables::HashedAccounts>(keccak256(CHANGED))
359 .unwrap()
360 .unwrap()
361 .balance
362 }
363
364 fn accounts_with(
366 balance: u64,
367 extra: impl IntoIterator<Item = (B256, TrieAccount)>,
368 ) -> Vec<(B256, TrieAccount)> {
369 let mut accounts: Vec<_> = accounts().into_iter().chain(extra).collect();
370 accounts.iter_mut().find(|(key, _)| *key == keccak256(CHANGED)).unwrap().1.balance =
371 U256::from(balance);
372 accounts.sort_by_key(|(hashed_address, _)| *hashed_address);
373 accounts
374 }
375
376 fn downloaded_root(factory: &Factory) -> B256 {
379 let provider = factory.database_provider_ro().unwrap();
380 let mut cursor = provider.tx_ref().cursor_read::<tables::HashedAccounts>().unwrap();
381 let accounts: Vec<_> = cursor
382 .walk(None)
383 .unwrap()
384 .map(|entry| {
385 let (hashed_address, account) = entry.unwrap();
386 (hashed_address, account.into_trie_account(EMPTY_ROOT_HASH))
387 })
388 .collect();
389 state_root(&accounts)
390 }
391
392 fn partially_downloaded(
394 chain: &BalChain,
395 accounts: &[(B256, TrieAccount)],
396 served: usize,
397 ) -> (Factory, SnapWrite) {
398 let factory = hashed_factory();
399 insert_headers(&factory, &chain.headers);
400 let provider = factory.database_provider_rw().unwrap();
401 let write = provider.start_snap_attempt(chain.generation(state_root(accounts))).unwrap();
402 provider.start_account_coverage(write).unwrap();
403 let range =
404 verified_range(accounts, 0..served, B256::ZERO, &[B256::ZERO, accounts[served - 1].0]);
405 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
406 provider.commit().unwrap();
407 (factory, write)
408 }
409
410 fn advance(
412 factory: &Factory,
413 write: SnapWrite,
414 chain: &BalChain,
415 nth: usize,
416 accounts: &[(B256, TrieAccount)],
417 ) -> SnapWrite {
418 let provider = factory.database_provider_rw().unwrap();
419 let generation = SnapGeneration::new(chain.block(nth), state_root(accounts));
420 let write = provider.advance_snap_pivot(write, generation).unwrap();
421 provider.commit().unwrap();
422 write
423 }
424
425 fn download_rest(factory: &Factory, write: SnapWrite, accounts: &[(B256, TrieAccount)]) {
427 let provider = factory.database_provider_rw().unwrap();
428 let origin = provider.account_coverage(write).unwrap().unwrap().next().unwrap();
429 let served = accounts.iter().position(|(key, _)| *key >= origin).unwrap()..accounts.len();
430 let range = verified_range(accounts, served, origin, &[origin, accounts.last().unwrap().0]);
431 provider.commit_account_range(write, &range, Default::default(), Vec::new()).unwrap();
432 provider.commit().unwrap();
433 }
434
435 #[tokio::test]
436 async fn every_list_a_response_carries_is_applied_in_block_order() {
437 let chain = chain();
438 let (factory, write) = started(&chain, &accounts());
439 let (client, mut catch_up) =
440 catch_up([chain.response(1, [Some(1), Some(2), Some(3)])], factory.clone());
441
442 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
443
444 assert_eq!(progress.applied(), chain.block(3));
445 assert_eq!(balance(&factory), U256::from(30));
447 assert_eq!(
448 *client.block_requests(),
449 [(1..=3).map(|nth| chain.block(nth).hash).collect::<Vec<_>>()]
450 );
451 assert!(matches!(catch_up.next(write, PIVOT + 3).await.unwrap(), CatchUpStep::Complete));
452 }
453
454 #[tokio::test]
455 async fn a_list_a_peer_leaves_out_holds_back_the_blocks_after_it() {
456 let chain = chain();
457 let (factory, write) = started(&chain, &accounts());
458 let (client, mut catch_up) = catch_up(
459 [chain.response(1, [Some(1), None, Some(3)]), chain.response(2, [Some(2), Some(3)])],
460 factory.clone(),
461 );
462
463 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
465 assert_eq!(progress.applied(), chain.block(1));
466 assert_eq!(balance(&factory), U256::from(10));
467
468 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
469 assert_eq!(progress.applied(), chain.block(3));
470 assert_eq!(balance(&factory), U256::from(30));
471 assert_eq!(client.block_requests()[1], [chain.block(2).hash, chain.block(3).hash]);
473 }
474
475 #[tokio::test]
476 async fn a_response_cut_short_leaves_the_rest_pending() {
477 let chain = chain();
478 let (factory, write) = started(&chain, &accounts());
479 let (client, mut catch_up) = catch_up(
481 [chain.response(1, [Some(1), Some(2)]), chain.response(2, [Some(3)])],
482 factory.clone(),
483 );
484
485 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
486 assert_eq!(progress.applied(), chain.block(2));
487
488 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
489 assert_eq!(progress.applied(), chain.block(3));
490 assert_eq!(balance(&factory), U256::from(30));
491 assert_eq!(client.block_requests()[1], [chain.block(3).hash]);
493 }
494
495 #[tokio::test]
496 async fn a_reply_repeating_an_applied_list_cannot_apply_it_again() {
497 let chain = chain();
498 let (factory, write) = started(&chain, &accounts());
499 let duplicate = std::iter::repeat_with(|| chain.response(2, [Some(1)])).take(4);
501 let (_, mut catch_up) = catch_up_with(
502 std::iter::once(chain.response(1, [Some(1)])).chain(duplicate),
503 factory.clone(),
504 1,
505 );
506 applied(&mut catch_up, write, PIVOT + 3).await;
507
508 let repeated = catch_up.next(write, PIVOT + 3).await;
511
512 assert!(matches!(repeated, Err(SnapSyncError::Request(_))));
513 assert_eq!(balance(&factory), U256::from(10));
514 }
515
516 #[tokio::test]
517 async fn a_peer_holding_no_list_for_the_next_block_applies_nothing() {
518 let chain = chain();
519 let (factory, write) = started(&chain, &accounts());
520 let (_, mut catch_up) = catch_up([chain.response(1, [None, None, None])], factory.clone());
521
522 let step = catch_up.next(write, PIVOT + 3).await.unwrap();
523
524 assert!(matches!(step, CatchUpStep::Unavailable { .. }));
525 assert_eq!(balance(&factory), U256::from(1));
526 }
527
528 #[tokio::test]
529 async fn a_block_that_changes_nothing_is_still_carried_past() {
530 let chain = BalChain::new(PIVOT, [Vec::new()]);
531 let (factory, write) = started(&chain, &accounts());
532 let (_, mut catch_up) = catch_up([chain.response(1, [Some(1)])], factory.clone());
533
534 let progress = applied(&mut catch_up, write, PIVOT + 1).await;
535
536 assert_eq!(progress.applied(), chain.block(1));
537 assert_eq!(balance(&factory), U256::from(1));
538 }
539
540 #[tokio::test]
541 async fn only_the_blocks_a_request_can_carry_are_asked_for() {
542 let chain = chain();
543 let (factory, write) = started(&chain, &accounts());
544 let (client, mut catch_up) = catch_up_with([chain.response(1, [Some(1)])], factory, 1);
545
546 applied(&mut catch_up, write, PIVOT + 3).await;
547
548 assert_eq!(*client.block_requests(), [vec![chain.block(1).hash]]);
549 }
550
551 #[tokio::test]
552 async fn an_unbounded_request_asks_for_every_block_through_the_target() {
553 let chain = chain();
554 let (factory, write) = started(&chain, &accounts());
555 let (client, mut catch_up) =
556 catch_up_with([chain.response(1, [Some(1), Some(2), Some(3)])], factory, u64::MAX);
557
558 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
559
560 assert_eq!(progress.applied(), chain.block(3));
561 assert_eq!(
562 *client.block_requests(),
563 [(1..=3).map(|nth| chain.block(nth).hash).collect::<Vec<_>>()]
564 );
565 }
566
567 #[tokio::test]
568 async fn a_target_already_applied_needs_no_request() {
569 let chain = chain();
570 let (factory, write) = started(&chain, &accounts());
571 let (client, mut catch_up) = catch_up([], factory);
572
573 assert!(matches!(catch_up.next(write, PIVOT).await.unwrap(), CatchUpStep::Complete));
574 assert!(client.block_requests().is_empty());
575 }
576
577 #[tokio::test]
578 async fn a_target_past_the_pivot_is_held_at_it() {
579 let chain = chain();
580 let factory = hashed_factory();
581 insert_headers(&factory, &chain.headers);
582 let provider = factory.database_provider_rw().unwrap();
583 let write = provider.start_snap_attempt(chain.generation(state_root(&accounts()))).unwrap();
584 provider.commit().unwrap();
585 let (client, mut catch_up) = catch_up([], factory);
586
587 assert!(matches!(catch_up.next(write, PIVOT + 3).await.unwrap(), CatchUpStep::Complete));
588 assert!(client.block_requests().is_empty());
589 }
590
591 #[tokio::test]
592 async fn an_applied_block_the_canonical_chain_no_longer_holds_is_reported() {
593 let chain = chain();
594 let accounts = accounts();
595 let reorged = BalChain::new(0, [credit(1), credit(2), credit(3)]);
597 let factory = hashed_factory();
598 insert_headers(&factory, &reorged.headers);
599 let provider = factory.database_provider_rw().unwrap();
600 let write = provider.start_snap_attempt(chain.generation(state_root(&accounts))).unwrap();
601 provider.start_account_coverage(write).unwrap();
602 provider.commit().unwrap();
603 let (_, mut catch_up) = catch_up([], factory);
604
605 let forked = catch_up.next(write, PIVOT + 3).await;
606
607 assert!(matches!(forked, Err(SnapSyncError::ForkedBlock { .. })));
608 }
609
610 #[tokio::test]
611 async fn a_pivot_the_canonical_chain_no_longer_holds_is_reported() {
612 let (factory, write, chain, replacement) = pivot_reorg_fixture();
613 let before = SnapStateSnapshot::read(&factory.database_provider_ro().unwrap());
614 replacement.replace_tip(&factory);
615 let (client, mut catch_up) = catch_up([], factory.clone());
616
617 for target in [PIVOT, PIVOT + 2] {
619 assert!(matches!(
620 catch_up.next(write, target).await,
621 Err(SnapSyncError::NonCanonicalBlock { block, hash })
622 if block == PIVOT + 2 && hash == chain.tip().hash
623 ));
624 }
625 assert!(client.block_requests().is_empty());
626 let provider = factory.database_provider_ro().unwrap();
627 assert_eq!(provider.block_hash(PIVOT + 1).unwrap(), Some(chain.block(1).hash));
629 assert_eq!(provider.block_hash(PIVOT).unwrap(), Some(chain.block(0).hash));
630 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied(), chain.block(0));
631 assert_eq!(SnapStateSnapshot::read(&provider), before);
632 }
633
634 #[tokio::test]
635 async fn a_pivot_reorg_during_a_bal_request_changes_nothing() {
636 let (factory, write, chain, replacement) = pivot_reorg_fixture();
637 let before = SnapStateSnapshot::read(&factory.database_provider_ro().unwrap());
638 let reorg_factory = factory.clone();
639 let replacement_hash = replacement.tip().hash;
640 let client = Arc::new(
641 ScriptedSnapClient::new([chain.response(1, [Some(1)])])
642 .on_block_request(move || replacement.replace_tip(&reorg_factory)),
643 );
644 let mut catch_up =
645 BlockAccessListCatchUp::new(client.clone(), factory.clone(), Runtime::test());
646
647 assert!(matches!(
650 catch_up.next(write, PIVOT + 2).await,
651 Err(SnapSyncError::NonCanonicalBlock { block, hash })
652 if block == PIVOT + 2 && hash == chain.tip().hash
653 ));
654
655 assert_eq!(*client.block_requests(), [vec![chain.block(1).hash, chain.tip().hash]]);
656 let provider = factory.database_provider_ro().unwrap();
657 assert_eq!(provider.block_hash(PIVOT + 1).unwrap(), Some(chain.block(1).hash));
658 assert_eq!(provider.block_hash(PIVOT + 2).unwrap(), Some(replacement_hash));
659 assert_eq!(provider.catch_up_progress(write).unwrap().unwrap().applied(), chain.block(0));
660 assert_eq!(SnapStateSnapshot::read(&provider), before);
661 }
662
663 #[tokio::test]
664 async fn a_reply_to_an_earlier_request_is_ignored() {
665 let chain = chain();
666 let (factory, write) = started(&chain, &accounts());
667 let stale = chain.response(99, [Some(1), Some(2), Some(3)]);
669 let (_, mut catch_up) =
670 catch_up([stale, chain.response(1, [Some(1), Some(2), Some(3)])], factory.clone());
671
672 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
674
675 assert_eq!(progress.applied(), chain.block(3));
676 assert_eq!(balance(&factory), U256::from(30));
677 }
678
679 #[tokio::test]
680 async fn downloads_split_across_pivots_converge_on_the_latest_pivot_state() {
681 const FAR: B256 = B256::repeat_byte(0xfe);
682 let chain = chain();
683 let at = |balance| accounts_with(balance, [(FAR, account(3))]);
684 assert_eq!(at(1).last().unwrap().0, FAR);
685 let (factory, write) = partially_downloaded(&chain, &at(1), 2);
687 let (_, mut catch_up) =
688 catch_up([chain.response(1, [Some(1)]), chain.response(2, [Some(2)])], factory.clone());
689
690 let write = advance(&factory, write, &chain, 1, &at(10));
691 applied(&mut catch_up, write, PIVOT + 3).await;
692 assert_eq!(balance(&factory), U256::from(10));
693 let write = advance(&factory, write, &chain, 2, &at(20));
694
695 let provider = factory.database_provider_rw().unwrap();
697 let origin = provider.account_coverage(write).unwrap().unwrap().next().unwrap();
698 let stale = verified_range(&at(10), 2..3, origin, &[origin, FAR]);
699 assert!(matches!(
700 provider.commit_account_range(write, &stale, Default::default(), Vec::new()),
701 Err(SnapSyncError::RootMismatch { .. })
702 ));
703 drop(provider);
704 download_rest(&factory, write, &at(20));
705
706 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
708 assert_eq!(progress.applied(), chain.block(2));
709 assert!(matches!(catch_up.next(write, PIVOT + 3).await.unwrap(), CatchUpStep::Complete));
710 assert_eq!(downloaded_root(&factory), state_root(&at(20)));
711 }
712
713 #[tokio::test]
714 async fn an_account_lists_reach_before_its_range_is_taken_from_the_new_root() {
715 let chain = chain();
716 let at = |balance| accounts_with(balance, []);
717 assert_eq!(at(1)[1].0, keccak256(CHANGED));
719 let (factory, write) = partially_downloaded(&chain, &at(1), 1);
720 let write = advance(&factory, write, &chain, 2, &at(20));
721 let (_, mut catch_up) = catch_up([chain.response(1, [Some(1), Some(2)])], factory.clone());
722
723 let progress = applied(&mut catch_up, write, PIVOT + 3).await;
725 assert_eq!(progress.applied(), chain.block(2));
726 let provider = factory.database_provider_ro().unwrap();
727 assert_eq!(
728 provider.tx_ref().get::<tables::HashedAccounts>(keccak256(CHANGED)).unwrap(),
729 None
730 );
731 drop(provider);
732
733 download_rest(&factory, write, &at(20));
734
735 assert_eq!(balance(&factory), U256::from(20));
736 assert_eq!(downloaded_root(&factory), state_root(&at(20)));
737 }
738
739 #[tokio::test]
740 async fn orphaned_lists_are_fetched_in_order_across_requests() {
741 let chain = chain();
742 let orphaned = &chain.headers[PIVOT as usize + 1..];
743 let responses = [chain.response(1, [Some(1), Some(2)]), chain.response(2, [Some(3)])];
744 let (client, mut catch_up) = catch_up_with(responses, hashed_factory(), 2);
745
746 let lists = catch_up.orphaned_lists(orphaned).await.unwrap().unwrap();
747
748 let lists: Vec<_> = lists.iter().map(|list| list.as_bal().to_vec()).collect();
749 assert_eq!(lists, [credit(10), credit(20), credit(30)]);
750 let hashes: Vec<_> = orphaned.iter().map(SealedHeader::hash).collect();
751 assert_eq!(*client.block_requests(), [hashes[..2].to_vec(), hashes[2..].to_vec()]);
752 }
753
754 #[tokio::test]
755 async fn a_missing_orphaned_list_fetches_nothing() {
756 let chain = chain();
757 let orphaned = &chain.headers[PIVOT as usize + 1..];
758 let responses = [chain.response(1, [Some(1), Some(2), None]), chain.response(2, [None])];
760 let (client, mut catch_up) = catch_up_with(responses, hashed_factory(), 3);
761
762 assert!(catch_up.orphaned_lists(orphaned).await.unwrap().is_none());
763 assert_eq!(client.block_requests().len(), 2);
764 }
765
766 #[tokio::test]
767 async fn a_response_cut_short_keeps_its_lists_and_fetches_the_rest() {
768 let chain = chain();
769 let orphaned = &chain.headers[PIVOT as usize + 1..];
770 let responses = [chain.response(1, [Some(1)]), chain.response(2, [Some(2), Some(3)])];
772 let (client, mut catch_up) = catch_up_with(responses, hashed_factory(), 3);
773
774 let lists = catch_up.orphaned_lists(orphaned).await.unwrap().unwrap();
775
776 let lists: Vec<_> = lists.iter().map(|list| list.as_bal().to_vec()).collect();
777 assert_eq!(lists, [credit(10), credit(20), credit(30)]);
778 let hashes: Vec<_> = orphaned.iter().map(SealedHeader::hash).collect();
779 assert_eq!(*client.block_requests(), [hashes.clone(), hashes[1..].to_vec()]);
780 }
781}