1use crate::{
7 common::SnapRecord, SnapAccountStore, SnapAttemptStore, SnapCatchUpStore, SnapSyncError,
8 SnapWrite,
9};
10use alloy_eips::BlockNumHash;
11use alloy_primitives::{B256, KECCAK256_EMPTY};
12use reth_db_api::{
13 cursor::DbCursorRO,
14 tables,
15 transaction::{DbTx, DbTxMut},
16 RawKey, RawTable,
17};
18use reth_primitives_traits::{AlloyBlockHeader, GotExpected};
19use reth_prune_types::{PruneCheckpoint, PruneSegment};
20use reth_stages_types::{StageCheckpoint, StageId};
21use reth_storage_api::{
22 BlockHashReader, BlockWriter, DBProvider, HeaderProvider, MetadataProvider, MetadataWriter,
23 PruneCheckpointWriter, SnapAttemptId, StageCheckpointReader, StageCheckpointWriter,
24};
25use reth_storage_errors::provider::{ProviderError, RootMismatch};
26use serde::{Deserialize, Serialize};
27use tokio_util::sync::CancellationToken;
28
29pub const DEFAULT_SCAN_CHUNK: u64 = 100_000;
32
33const PUBLISHED_STAGES: [StageId; 11] = [
37 StageId::Bodies,
38 StageId::SenderRecovery,
39 StageId::Execution,
40 StageId::PruneSenderRecovery,
41 StageId::MerkleUnwind,
42 StageId::AccountHashing,
43 StageId::StorageHashing,
44 StageId::TransactionLookup,
45 StageId::IndexStorageHistory,
46 StageId::IndexAccountHistory,
47 StageId::Prune,
48];
49
50pub trait SnapStateVerifier {
54 fn start_trie_rebuild(
59 &self,
60 write: SnapWrite,
61 chunk: u64,
62 cancel: &CancellationToken,
63 ) -> Result<(), SnapSyncError>
64 where
65 Self: MetadataWriter + StageCheckpointWriter + DBProvider;
66
67 fn verify_completeness(
70 &self,
71 write: SnapWrite,
72 chunk: u64,
73 cancel: &CancellationToken,
74 ) -> Result<(), SnapSyncError>
75 where
76 Self: DBProvider;
77
78 fn publish_snap_state(&self, pivot: u64) -> Result<(), SnapSyncError>
90 where
91 Self: BlockWriter + PruneCheckpointWriter + StageCheckpointWriter + DBProvider<Tx: DbTxMut>;
92
93 fn is_trie_rebuild_started(&self, write: SnapWrite) -> Result<bool, SnapSyncError>;
96
97 fn verify_state_root(&self, write: SnapWrite) -> Result<VerifiedSnapState, SnapSyncError>
102 where
103 Self: BlockHashReader + HeaderProvider + MetadataWriter + StageCheckpointReader;
104}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub struct VerifiedSnapState {
109 attempt: SnapAttemptId,
111 target: BlockNumHash,
113 state_root: B256,
115}
116
117impl VerifiedSnapState {
118 pub const fn attempt(&self) -> SnapAttemptId {
120 self.attempt
121 }
122
123 pub const fn target(&self) -> BlockNumHash {
125 self.target
126 }
127
128 pub const fn state_root(&self) -> B256 {
130 self.state_root
131 }
132}
133
134impl<T: MetadataProvider> SnapStateVerifier for T {
135 fn start_trie_rebuild(
136 &self,
137 write: SnapWrite,
138 chunk: u64,
139 cancel: &CancellationToken,
140 ) -> Result<(), SnapSyncError>
141 where
142 Self: MetadataWriter + StageCheckpointWriter + DBProvider,
143 {
144 if self.authorize_snap_write(write)?.pivot().number == 0 {
147 return Err(SnapSyncError::GenesisPivot)
148 }
149 self.verify_completeness(write, chunk, cancel)?;
150 self.save_stage_checkpoint(StageId::MerkleExecute, StageCheckpoint::default())?;
153 self.save_stage_checkpoint_progress(StageId::MerkleExecute, Vec::new())?;
154 StoredRebuild::new(write).write(self)?;
155 Ok(())
156 }
157
158 fn publish_snap_state(&self, pivot: u64) -> Result<(), SnapSyncError>
159 where
160 Self: BlockWriter + PruneCheckpointWriter + StageCheckpointWriter + DBProvider<Tx: DbTxMut>,
161 {
162 if pivot == 0 {
163 return Err(SnapSyncError::GenesisPivot)
164 }
165 self.tx_ref().clear::<tables::BlockBodyIndices>()?;
168 self.tx_ref().clear::<tables::TransactionBlocks>()?;
169 self.tx_ref().clear::<tables::BlockWithdrawals>()?;
173 self.tx_ref().clear::<tables::BlockOmmers>()?;
174 self.tx_ref().clear::<tables::Receipts>()?;
177 self.clear_transaction_lookup()?;
178
179 let checkpoint = StageCheckpoint::new(pivot);
180 for stage in PUBLISHED_STAGES {
181 self.save_stage_checkpoint(stage, checkpoint)?;
182 }
183 let pruned = PruneCheckpoint::pruned_through(pivot);
185 for segment in PruneSegment::variants() {
186 self.save_prune_checkpoint(segment, pruned)?;
187 }
188 Ok(())
189 }
190
191 fn verify_completeness(
192 &self,
193 write: SnapWrite,
194 chunk: u64,
195 cancel: &CancellationToken,
196 ) -> Result<(), SnapSyncError>
197 where
198 Self: DBProvider,
199 {
200 let attempt = self.authorize_snap_write(write)?;
201 let coverage = self.account_coverage(write)?.ok_or(SnapSyncError::NoCoverage)?;
202 if let Some(next) = coverage.next() {
203 return Err(SnapSyncError::IncompleteAccounts { next })
204 }
205 let applied =
206 self.catch_up_progress(write)?.ok_or(SnapSyncError::NoCatchUpProgress)?.applied();
207 if applied != attempt.pivot() {
208 return Err(SnapSyncError::CatchUpBehindPivot {
209 applied: applied.number,
210 pivot: attempt.pivot().number,
211 })
212 }
213 let repairs = self.snap_repairs(write)?;
214 if !repairs.is_empty() {
215 return Err(SnapSyncError::PendingRepairs { accounts: repairs.len() })
216 }
217 ensure_code_present(self.tx_ref(), chunk, cancel)
218 }
219
220 fn is_trie_rebuild_started(&self, write: SnapWrite) -> Result<bool, SnapSyncError> {
221 Ok(StoredRebuild::read(self)?.is_some_and(|stored| stored.write == write))
222 }
223
224 fn verify_state_root(&self, write: SnapWrite) -> Result<VerifiedSnapState, SnapSyncError>
225 where
226 Self: BlockHashReader + HeaderProvider + MetadataWriter + StageCheckpointReader,
227 {
228 let attempt = self.authorize_canonical_snap_write(write)?;
229 let target = attempt.pivot();
230 let header = self
231 .sealed_header(target.number)?
232 .filter(|header| header.hash() == target.hash)
233 .ok_or(SnapSyncError::MissingHeader { block: target.number })?;
234 if header.state_root() != attempt.state_root() {
236 return Err(ProviderError::StateRootMismatch(Box::new(RootMismatch {
237 root: GotExpected { got: attempt.state_root(), expected: header.state_root() },
238 block_number: target.number,
239 block_hash: target.hash,
240 }))
241 .into())
242 }
243 let handed_off = self.is_trie_rebuild_started(write)?;
246 let rebuilt = self.get_stage_checkpoint(StageId::MerkleExecute)?;
247 if !handed_off || rebuilt.map(|checkpoint| checkpoint.block_number) != Some(target.number) {
248 return Err(ProviderError::StateForNumberNotFound(target.number).into())
249 }
250 self.verify_snap_attempt(write)?;
251 Ok(VerifiedSnapState { attempt: attempt.id(), target, state_root: header.state_root() })
252 }
253}
254
255#[derive(Serialize, Deserialize)]
257pub(crate) struct StoredRebuild {
258 version: u32,
260 write: SnapWrite,
262}
263
264impl SnapRecord for StoredRebuild {
265 const KEY: &'static str = "snap_trie_rebuild";
266 const VERSION: u32 = 1;
267}
268
269impl StoredRebuild {
270 const fn new(write: SnapWrite) -> Self {
272 Self { version: Self::VERSION, write }
273 }
274}
275
276fn ensure_code_present(
279 tx: &impl DbTx,
280 chunk: u64,
281 cancel: &CancellationToken,
282) -> Result<(), SnapSyncError> {
283 let chunk = chunk.max(1);
284 let mut cursor = tx.cursor_read::<tables::HashedAccounts>()?;
285 for (scanned, entry) in cursor.walk(None)?.enumerate() {
286 if (scanned as u64).is_multiple_of(chunk) && cancel.is_cancelled() {
287 return Err(SnapSyncError::Cancelled)
288 }
289 let (_, account) = entry?;
290 if let Some(hash) = account.bytecode_hash.filter(|hash| *hash != KECCAK256_EMPTY) &&
292 tx.get::<RawTable<tables::Bytecodes>>(RawKey::new(hash))?.is_none()
293 {
294 return Err(SnapSyncError::MissingCode { hash })
295 }
296 }
297 Ok(())
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use crate::{
304 test_utils::{account, hashed_factory, header, key, state_root},
305 SnapGeneration,
306 };
307 use alloy_consensus::TxLegacy;
308 use alloy_eips::eip4895::{Withdrawal, Withdrawals};
309 use alloy_primitives::{map::B256Map, Address, Bytes, Signature, U256};
310 use reth_db_api::transaction::DbTxMut;
311 use reth_ethereum_primitives::{BlockBody, Receipt, Transaction, TransactionSigned};
312 use reth_primitives_traits::SealedHeader;
313 use reth_provider::{
314 test_utils::{insert_headers, MockNodeTypesWithDB},
315 BlockBodyIndicesProvider, DatabaseProviderFactory, EitherWriter, ProviderFactory,
316 PruneCheckpointReader, StaticFileProviderFactory, StaticFileSegment, StaticFileWriter,
317 StorageSettings, StorageSettingsCache, TransactionsProvider,
318 };
319 use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig};
320 use reth_stages::stages::MerkleStage;
321 use reth_stages_api::{ExecInput, Stage, StageError};
322 use reth_trie_common::{root::storage_root_unsorted, HashedStorage, TrieAccount};
323 use revm::bytecode::Bytecode;
324 use std::collections::BTreeMap;
325
326 type Factory = ProviderFactory<MockNodeTypesWithDB>;
327 type Provider = <Factory as DatabaseProviderFactory>::ProviderRW;
328
329 const CONTRACT: B256 = B256::repeat_byte(0xaa);
330 const SLOT: B256 = B256::repeat_byte(0x55);
331
332 fn code() -> Bytecode {
333 Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00]))
334 }
335
336 fn storage() -> HashedStorage {
337 HashedStorage::from_iter([(SLOT, U256::from(7))])
338 }
339
340 fn accounts() -> Vec<(B256, TrieAccount)> {
342 let mut contract = account(3);
343 contract.storage_root = storage_root_unsorted([(SLOT, U256::from(7))]);
344 contract.code_hash = code().hash_slow();
345 vec![(key(1), account(1)), (key(2), account(2)), (CONTRACT, contract)]
346 }
347
348 fn insert_chain(factory: &Factory, header_root: B256) -> Vec<BlockNumHash> {
350 let mut parent = B256::ZERO;
351 let headers: Vec<_> = (0..=2)
352 .map(|number| {
353 let mut header = header(number, parent, None);
354 header.state_root = header_root;
355 let sealed = SealedHeader::seal_slow(header);
356 parent = sealed.hash();
357 sealed
358 })
359 .collect();
360 insert_headers(factory, &headers);
361 headers.iter().map(|header| BlockNumHash::new(header.number, header.hash())).collect()
362 }
363
364 fn downloaded(header_root: B256, served: usize) -> (Factory, SnapWrite, Vec<BlockNumHash>) {
367 let accounts = accounts();
368 let factory = hashed_factory();
369 let blocks = insert_chain(&factory, header_root);
370 let provider = factory.database_provider_rw().unwrap();
371 let write = provider
372 .start_snap_attempt(SnapGeneration::new(blocks[1], state_root(&accounts)))
373 .unwrap();
374 provider.start_account_coverage(write).unwrap();
375 let targets = (served < accounts.len()).then(|| accounts[served - 1].0);
376 let range =
377 crate::test_utils::verified_range(&accounts, 0..served, B256::ZERO, targets.as_slice());
378 let complete = served == accounts.len();
379 let (storages, bytecodes) = if complete {
380 (B256Map::from_iter([(CONTRACT, storage())]), vec![(code().hash_slow(), code())])
381 } else {
382 Default::default()
383 };
384 provider.commit_account_range(write, &range, storages, bytecodes).unwrap();
385 provider.commit().unwrap();
386 (factory, write, blocks)
387 }
388
389 fn start(provider: &Provider, write: SnapWrite) -> Result<(), SnapSyncError> {
390 provider.start_trie_rebuild(write, 1, &CancellationToken::new())
391 }
392
393 fn run_merkle(provider: &Provider, target: u64) -> Result<(), StageError> {
395 let mut stage = MerkleStage::default_execution();
396 loop {
397 let checkpoint = provider.get_stage_checkpoint(StageId::MerkleExecute).unwrap();
398 let output = stage.execute(provider, ExecInput { target: Some(target), checkpoint })?;
399 provider.save_stage_checkpoint(StageId::MerkleExecute, output.checkpoint).unwrap();
400 if output.done {
401 return Ok(())
402 }
403 }
404 }
405
406 fn merkle_checkpoint(provider: &Provider) -> Option<u64> {
407 provider.get_stage_checkpoint(StageId::MerkleExecute).unwrap().map(|c| c.block_number)
408 }
409
410 #[test]
411 fn complete_state_is_verified_once_the_merkle_stage_reaches_the_pivot() {
412 let root = state_root(&accounts());
413 let (factory, write, blocks) = downloaded(root, accounts().len());
414 let provider = factory.database_provider_rw().unwrap();
415
416 start(&provider, write).unwrap();
417 run_merkle(&provider, 1).unwrap();
418 let verified = provider.verify_state_root(write).unwrap();
419 provider.commit().unwrap();
420
421 assert_eq!((verified.target(), verified.state_root()), (blocks[1], root));
422 let provider = factory.database_provider_rw().unwrap();
423 assert!(provider.snap_attempt().unwrap().unwrap().is_verified());
424 assert!(matches!(provider.verify_state_root(write), Err(SnapSyncError::StaleWrite { .. })));
426 }
427
428 #[test]
429 fn unfinished_accounts_prevent_the_hand_off() {
430 let (factory, write, _) = downloaded(state_root(&accounts()), 1);
431 let provider = factory.database_provider_rw().unwrap();
432
433 assert!(matches!(
434 start(&provider, write),
435 Err(SnapSyncError::IncompleteAccounts { next }) if next == key(2)
436 ));
437 assert_eq!(merkle_checkpoint(&provider), None);
438 }
439
440 #[test]
441 fn missing_code_prevents_the_hand_off() {
442 let (factory, write, _) = downloaded(state_root(&accounts()), accounts().len());
443 let provider = factory.database_provider_rw().unwrap();
444 provider.tx_ref().delete::<tables::Bytecodes>(code().hash_slow(), None).unwrap();
445
446 assert!(matches!(
447 start(&provider, write),
448 Err(SnapSyncError::MissingCode { hash }) if hash == code().hash_slow()
449 ));
450 }
451
452 #[test]
453 fn catch_up_short_of_the_pivot_prevents_the_hand_off() {
454 let root = state_root(&accounts());
455 let (factory, write, blocks) = downloaded(root, accounts().len());
456 let provider = factory.database_provider_rw().unwrap();
457 let write =
458 provider.advance_snap_pivot(write, SnapGeneration::new(blocks[2], root)).unwrap();
459
460 assert!(matches!(
461 start(&provider, write),
462 Err(SnapSyncError::CatchUpBehindPivot { applied: 1, pivot: 2 })
463 ));
464 }
465
466 #[test]
467 fn a_cancelled_session_stops_the_completeness_scan() {
468 let (factory, write, _) = downloaded(state_root(&accounts()), accounts().len());
469 let provider = factory.database_provider_rw().unwrap();
470 let cancel = CancellationToken::new();
471 cancel.cancel();
472
473 assert!(matches!(
474 provider.start_trie_rebuild(write, 1, &cancel),
475 Err(SnapSyncError::Cancelled)
476 ));
477 assert_eq!(merkle_checkpoint(&provider), None);
478 }
479
480 #[test]
481 fn corrupted_state_never_reaches_the_pivot() {
482 let (factory, write, _) = downloaded(state_root(&accounts()), accounts().len());
483 let provider = factory.database_provider_rw().unwrap();
484 start(&provider, write).unwrap();
485 provider.tx_ref().delete::<tables::HashedStorages>(CONTRACT, None).unwrap();
486
487 assert!(matches!(run_merkle(&provider, 1), Err(StageError::Block { .. })));
489 assert!(matches!(
490 provider.verify_state_root(write),
491 Err(SnapSyncError::Provider(ProviderError::StateForNumberNotFound(1)))
492 ));
493 assert!(provider.snap_attempt().unwrap().unwrap().is_unfinished());
494 }
495
496 #[test]
497 fn an_interrupted_rebuild_is_not_verified() {
498 let (factory, write, _) = downloaded(state_root(&accounts()), accounts().len());
499 let provider = factory.database_provider_rw().unwrap();
500 provider.save_stage_checkpoint(StageId::MerkleExecute, StageCheckpoint::new(1)).unwrap();
502
503 start(&provider, write).unwrap();
504
505 assert!(matches!(
506 provider.verify_state_root(write),
507 Err(SnapSyncError::Provider(ProviderError::StateForNumberNotFound(1)))
508 ));
509 run_merkle(&provider, 1).unwrap();
510 provider.verify_state_root(write).unwrap();
511 }
512
513 #[test]
514 fn a_rebuild_for_an_earlier_attempt_is_not_trusted() {
515 let root = state_root(&accounts());
516 let (factory, write, blocks) = downloaded(root, accounts().len());
517 let provider = factory.database_provider_rw().unwrap();
518 start(&provider, write).unwrap();
519 run_merkle(&provider, 1).unwrap();
520
521 let restarted = provider.start_snap_attempt(SnapGeneration::new(blocks[1], root)).unwrap();
523
524 assert_eq!(merkle_checkpoint(&provider), Some(1));
525 assert!(matches!(
526 provider.verify_state_root(restarted),
527 Err(SnapSyncError::Provider(ProviderError::StateForNumberNotFound(1)))
528 ));
529 }
530
531 #[test]
532 fn moving_the_pivot_after_the_hand_off_requires_a_new_one() {
533 let root = state_root(&accounts());
534 let (factory, write, blocks) = downloaded(root, accounts().len());
535 let provider = factory.database_provider_rw().unwrap();
536 start(&provider, write).unwrap();
537 let advanced =
538 provider.advance_snap_pivot(write, SnapGeneration::new(blocks[2], root)).unwrap();
539
540 run_merkle(&provider, 2).unwrap();
541
542 assert!(matches!(
543 provider.verify_state_root(advanced),
544 Err(SnapSyncError::Provider(ProviderError::StateForNumberNotFound(2)))
545 ));
546 }
547
548 #[test]
549 fn a_genesis_pivot_is_not_handed_off() {
550 let root = state_root(&accounts());
551 let (factory, _, blocks) = downloaded(root, accounts().len());
552 let provider = factory.database_provider_rw().unwrap();
553 let write = provider.start_snap_attempt(SnapGeneration::new(blocks[0], root)).unwrap();
554
555 assert!(matches!(start(&provider, write), Err(SnapSyncError::GenesisPivot)));
556 assert_eq!(merkle_checkpoint(&provider), None);
557 }
558
559 #[test]
560 fn publishing_moves_the_covered_stages_and_prunes_the_history_below_the_pivot() {
561 let factory = hashed_factory();
562 let provider = factory.database_provider_rw().unwrap();
563
564 provider.publish_snap_state(7).unwrap();
565
566 let kept = [StageId::Era, StageId::Headers, StageId::MerkleExecute, StageId::Finish];
568 for stage in StageId::ALL {
569 let published = PUBLISHED_STAGES.contains(&stage);
570 assert_ne!(published, kept.contains(&stage), "{stage} must be published or kept");
571 let expected = published.then(|| StageCheckpoint::new(7));
572 assert_eq!(provider.get_stage_checkpoint(stage).unwrap(), expected, "{stage}");
573 }
574
575 for segment in PruneSegment::variants() {
577 let checkpoint = provider.get_prune_checkpoint(segment).unwrap();
578 assert_eq!(checkpoint, Some(PruneCheckpoint::pruned_through(7)), "{segment}");
579 }
580 }
581
582 #[test]
583 fn publishing_refuses_the_genesis_pivot() {
584 let factory = hashed_factory();
585 let provider = factory.database_provider_rw().unwrap();
586
587 assert!(matches!(provider.publish_snap_state(0), Err(SnapSyncError::GenesisPivot)));
588 }
589
590 #[test]
591 fn bodies_downloaded_past_the_pivot_can_be_written_again_after_publishing() {
592 let factory = hashed_factory();
593 insert_chain(&factory, B256::ZERO);
594 let body = BlockBody {
595 withdrawals: Some(Withdrawals::new(vec![Withdrawal::default()])),
596 ..Default::default()
597 };
598 let provider = factory.database_provider_rw().unwrap();
599 provider
600 .append_block_bodies(vec![
601 (0, None),
602 (1, Some(&body)),
603 (2, Some(&body)),
604 (3, Some(&body)),
605 ])
606 .unwrap();
607 provider.commit().unwrap();
608
609 let static_files = factory.static_file_provider();
611 let provider = factory.database_provider_rw().unwrap();
612 provider.publish_snap_state(1).unwrap();
613 static_files.delete_segment(StaticFileSegment::Transactions).unwrap();
614 static_files
615 .latest_writer(StaticFileSegment::Transactions)
616 .unwrap()
617 .ensure_at_block(1)
618 .unwrap();
619 provider.commit().unwrap();
620
621 let provider = factory.database_provider_rw().unwrap();
622 provider.append_block_bodies(vec![(2, Some(&body))]).unwrap();
623 provider.commit().unwrap();
624 }
625
626 #[test]
627 fn published_state_can_unwind_bodies_to_the_pivot() {
628 for tx_count in [0, 2] {
629 let factory = hashed_factory();
630 insert_chain(&factory, B256::ZERO);
631 let provider = factory.database_provider_rw().unwrap();
632 provider.publish_snap_state(1).unwrap();
633 factory
634 .static_file_provider()
635 .latest_writer(StaticFileSegment::Transactions)
636 .unwrap()
637 .ensure_at_block(1)
638 .unwrap();
639 provider.commit().unwrap();
640
641 let body = BlockBody {
642 transactions: (0..tx_count)
643 .map(|nonce| {
644 TransactionSigned::new_unhashed(
645 Transaction::Legacy(TxLegacy { nonce, ..Default::default() }),
646 Signature::test_signature(),
647 )
648 })
649 .collect(),
650 ..Default::default()
651 };
652 let provider = factory.database_provider_rw().unwrap();
653 provider.append_block_bodies(vec![(2, Some(&body))]).unwrap();
654 provider.commit().unwrap();
655
656 let provider = factory.database_provider_rw().unwrap();
657 assert_eq!(provider.block_body_indices(1).unwrap(), None);
658 assert_eq!(provider.next_tx_num_after_block(1).unwrap(), 0);
659 assert_eq!(provider.next_tx_num_after_block(2).unwrap(), tx_count);
660 provider.remove_bodies_above(1).unwrap();
661 provider.commit().unwrap();
662
663 let provider = factory.database_provider_ro().unwrap();
664 assert_eq!(provider.block_body_indices(1).unwrap(), None);
665 assert_eq!(provider.block_body_indices(2).unwrap(), None);
666 let static_files = factory.static_file_provider();
667 assert_eq!(
668 static_files.get_highest_static_file_block(StaticFileSegment::Transactions),
669 Some(1)
670 );
671 assert_eq!(
672 static_files.get_highest_static_file_tx(StaticFileSegment::Transactions),
673 None
674 );
675 }
676 }
677
678 #[test]
679 fn startup_heals_an_interrupted_first_write_after_publishing() {
680 for tx_count in [0, 1] {
682 let factory = hashed_factory();
683 insert_chain(&factory, B256::ZERO);
684 let static_files = factory.static_file_provider();
685 let provider = factory.database_provider_rw().unwrap();
686 provider.publish_snap_state(1).unwrap();
687 static_files
688 .latest_writer(StaticFileSegment::Transactions)
689 .unwrap()
690 .ensure_at_block(1)
691 .unwrap();
692 provider.commit().unwrap();
693
694 let body = BlockBody {
695 transactions: (0..tx_count)
696 .map(|nonce| {
697 TransactionSigned::new_unhashed(
698 Transaction::Legacy(TxLegacy { nonce, ..Default::default() }),
699 Signature::test_signature(),
700 )
701 })
702 .collect(),
703 ..Default::default()
704 };
705
706 let provider = factory.database_provider_rw().unwrap();
708 provider.append_block_bodies(vec![(2, Some(&body))]).unwrap();
709 static_files.commit().unwrap();
710 drop(provider);
711
712 let provider = factory.provider().unwrap();
713 assert_eq!(static_files.check_consistency(&provider).unwrap(), None, "{tx_count}");
714 assert_eq!(
715 static_files.get_highest_static_file_block(StaticFileSegment::Transactions),
716 Some(1),
717 "{tx_count}"
718 );
719 drop(provider);
720
721 let provider = factory.database_provider_rw().unwrap();
722 provider.append_block_bodies(vec![(2, Some(&body))]).unwrap();
723 provider.commit().unwrap();
724 }
725 }
726
727 #[test]
728 fn publishing_clears_transaction_lookups_in_the_active_backend() {
729 for settings in [StorageSettings::v1(), StorageSettings::v2()] {
730 let factory = hashed_factory();
731 factory.set_storage_settings_cache(settings);
732 let old_hash = B256::repeat_byte(0xaa);
733 let new_hash = B256::repeat_byte(0xbb);
734 let insert_lookup = |hash| {
735 let provider = factory.database_provider_rw().unwrap();
736 provider
737 .with_rocksdb_batch(|batch| {
738 let mut writer =
739 EitherWriter::new_transaction_hash_numbers(&provider, batch)?;
740 writer.put_transaction_hash_numbers_batch(vec![(hash, 0)], false)?;
741 Ok(((), writer.into_raw_rocksdb_batch()))
742 })
743 .unwrap();
744 provider.commit().unwrap();
745 };
746 insert_lookup(old_hash);
747 assert_eq!(
748 factory.database_provider_ro().unwrap().transaction_id(old_hash).unwrap(),
749 Some(0)
750 );
751
752 let provider = factory.database_provider_rw().unwrap();
753 provider.publish_snap_state(7).unwrap();
754 provider.commit().unwrap();
755 assert_eq!(
756 factory.database_provider_ro().unwrap().transaction_id(old_hash).unwrap(),
757 None
758 );
759
760 insert_lookup(new_hash);
761 let provider = factory.database_provider_ro().unwrap();
762 assert_eq!(provider.transaction_id(old_hash).unwrap(), None);
763 assert_eq!(provider.transaction_id(new_hash).unwrap(), Some(0));
764 }
765 }
766
767 #[test]
768 fn publishing_resets_database_receipts_before_reusing_transaction_numbers() {
769 let factory = hashed_factory().with_prune_modes(PruneModes {
770 receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([(
771 Address::ZERO,
772 PruneMode::Before(0),
773 )])),
774 ..Default::default()
775 });
776 insert_chain(&factory, B256::ZERO);
777 let body = BlockBody {
778 transactions: (0..2)
779 .map(|nonce| {
780 TransactionSigned::new_unhashed(
781 Transaction::Legacy(TxLegacy { nonce, ..Default::default() }),
782 Signature::test_signature(),
783 )
784 })
785 .collect(),
786 ..Default::default()
787 };
788 let provider = factory.database_provider_rw().unwrap();
789 provider.append_block_bodies(vec![(0, None), (1, Some(&body))]).unwrap();
790 {
791 let mut writer = EitherWriter::new_receipts(&provider, 1).unwrap();
792 assert!(matches!(&writer, EitherWriter::Database(_)));
793 writer.append_receipt(0, &Receipt::default()).unwrap();
794 writer.append_receipt(1, &Receipt::default()).unwrap();
795 }
796 provider.commit().unwrap();
797 let provider = factory.database_provider_rw().unwrap();
798 provider.anchor_pruned_static_files(1).unwrap();
799 provider.publish_snap_state(1).unwrap();
800 provider.commit().unwrap();
801 let provider = factory.database_provider_rw().unwrap();
802 provider.append_block_bodies(vec![(2, Some(&body))]).unwrap();
803 {
804 let mut writer = EitherWriter::new_receipts(&provider, 2).unwrap();
805 writer.append_receipt(0, &Receipt::default()).unwrap();
806 writer.append_receipt(1, &Receipt::default()).unwrap();
807 }
808 provider.commit().unwrap();
809 }
810}