1mod manager;
2pub use manager::{
3 StaticFileAccess, StaticFileProvider, StaticFileProviderBuilder, StaticFileWriteCtx,
4 StaticFileWriter,
5};
6
7mod jar;
8pub use jar::StaticFileJarProvider;
9
10mod writer;
11pub use writer::{StaticFileProviderRW, StaticFileProviderRWRefMut};
12
13mod metrics;
14
15#[cfg(test)]
16mod writer_tests;
17
18use reth_nippy_jar::NippyJar;
19use reth_static_file_types::{ChangesetOffsetReader, SegmentHeader, StaticFileSegment};
20use reth_storage_errors::provider::{ProviderError, ProviderResult};
21use std::{io, ops::Deref, sync::Arc};
22
23type LoadedJarRef<'a> =
25 reth_primitives_traits::dashmap::mapref::one::Ref<'a, (u64, StaticFileSegment), LoadedJar>;
26
27#[derive(Debug)]
29pub struct LoadedJar {
30 jar: NippyJar<SegmentHeader>,
31 mmap_handle: Arc<reth_nippy_jar::DataReader>,
32 csoff_reader: Option<ChangesetOffsetReader>,
33}
34
35impl LoadedJar {
36 fn new(jar: NippyJar<SegmentHeader>) -> ProviderResult<Self> {
37 match jar.open_data_reader() {
38 Ok(data_reader) => {
39 let mmap_handle = Arc::new(data_reader);
40
41 let csoff_reader = if jar.user_header().segment().is_change_based() {
42 let csoff_path = jar.data_path().with_extension("csoff");
43 let len = jar.user_header().changeset_offsets_len();
44 match ChangesetOffsetReader::new(&csoff_path, len) {
45 Ok(reader) => Some(reader),
46 Err(err) if err.kind() == io::ErrorKind::NotFound && len == 0 => None,
47 Err(err) => return Err(ProviderError::other(err)),
48 }
49 } else {
50 None
51 };
52
53 Ok(Self { jar, mmap_handle, csoff_reader })
54 }
55 Err(e) => Err(ProviderError::other(e)),
56 }
57 }
58
59 fn mmap_handle(&self) -> Arc<reth_nippy_jar::DataReader> {
61 self.mmap_handle.clone()
62 }
63
64 const fn segment(&self) -> StaticFileSegment {
65 self.jar.user_header().segment()
66 }
67
68 fn size(&self) -> usize {
70 self.mmap_handle.size() + self.mmap_handle.offsets_size()
71 }
72
73 const fn csoff_reader(&self) -> Option<&ChangesetOffsetReader> {
75 self.csoff_reader.as_ref()
76 }
77}
78
79impl Deref for LoadedJar {
80 type Target = NippyJar<SegmentHeader>;
81 fn deref(&self) -> &Self::Target {
82 &self.jar
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::{
90 providers::static_file::manager::StaticFileProviderBuilder,
91 test_utils::create_test_provider_factory, HeaderProvider, StaticFileProviderFactory,
92 };
93 use alloy_consensus::{Header, SignableTransaction, Transaction, TxLegacy};
94 use alloy_primitives::{Address, BlockHash, Signature, TxNumber, B256, U160, U256};
95 use rand::seq::SliceRandom;
96 use reth_db::{
97 models::{AccountBeforeTx, StorageBeforeTx},
98 test_utils::create_test_static_files_dir,
99 };
100 use reth_db_api::{transaction::DbTxMut, CanonicalHeaders, HeaderNumbers, Headers};
101 use reth_ethereum_primitives::{EthPrimitives, Receipt, TransactionSigned};
102 use reth_primitives_traits::Account;
103 use reth_static_file_types::{
104 find_fixed_range, SegmentRangeInclusive, DEFAULT_BLOCKS_PER_STATIC_FILE,
105 };
106 use reth_storage_api::{
107 BlockHashReader, ChangeSetReader, ReceiptProvider, StorageChangeSetReader,
108 TransactionsProvider,
109 };
110 use reth_testing_utils::generators::{self, random_header_range};
111 use std::{collections::BTreeMap, fmt::Debug, fs, ops::Range, path::Path};
112
113 fn assert_eyre<T: PartialEq + Debug>(got: T, expected: T, msg: &str) -> eyre::Result<()> {
114 if got != expected {
115 eyre::bail!("{msg} | got: {got:?} expected: {expected:?}");
116 }
117 Ok(())
118 }
119
120 #[test]
121 fn test_static_files() {
122 let row_count = 100u64;
124 let range = 0..=(row_count - 1);
125
126 let factory = create_test_provider_factory();
128 let static_files_path = tempfile::tempdir().unwrap();
129 let static_file = static_files_path.path().join(
130 StaticFileSegment::Headers
131 .filename(&find_fixed_range(*range.end(), DEFAULT_BLOCKS_PER_STATIC_FILE)),
132 );
133
134 let mut headers = random_header_range(
136 &mut generators::rng(),
137 *range.start()..(*range.end() + 1),
138 B256::random(),
139 );
140
141 let mut provider_rw = factory.provider_rw().unwrap();
142 let tx = provider_rw.tx_mut();
143 for header in headers.clone() {
144 let hash = header.hash();
145
146 tx.put::<CanonicalHeaders>(header.number, hash).unwrap();
147 tx.put::<Headers>(header.number, header.clone_header()).unwrap();
148 tx.put::<HeaderNumbers>(hash, header.number).unwrap();
149 }
150 provider_rw.commit().unwrap();
151
152 {
154 let manager = factory.static_file_provider();
155 let mut writer = manager.latest_writer(StaticFileSegment::Headers).unwrap();
156
157 for header in headers.clone() {
158 let hash = header.hash();
159 writer.append_header(&header.unseal(), &hash).unwrap();
160 }
161 writer.commit().unwrap();
162 }
163
164 {
166 let db_provider = factory.provider().unwrap();
167 let manager = db_provider.static_file_provider();
168 let jar_provider = manager
169 .get_segment_provider_for_block(StaticFileSegment::Headers, 0, Some(&static_file))
170 .unwrap();
171
172 assert!(!headers.is_empty());
173
174 headers.shuffle(&mut generators::rng());
176
177 for header in headers {
178 let header_hash = header.hash();
179 let header = header.unseal();
180
181 assert_eq!(header, db_provider.header(header_hash).unwrap().unwrap());
183 assert_eq!(header, jar_provider.header_by_number(header.number).unwrap().unwrap());
184 }
185 }
186 }
187
188 #[test]
189 fn test_header_truncation() {
190 let (static_dir, _) = create_test_static_files_dir();
191
192 let blocks_per_file = 10; let files_per_range = 3; let file_set_count = 3; let initial_file_count = files_per_range * file_set_count;
196 let tip = blocks_per_file * file_set_count - 1; {
200 let sf_rw: StaticFileProvider<EthPrimitives> =
201 StaticFileProviderBuilder::read_write(&static_dir)
202 .with_blocks_per_file(blocks_per_file)
203 .build()
204 .expect("Failed to build static file provider");
205
206 let mut header_writer = sf_rw.latest_writer(StaticFileSegment::Headers).unwrap();
207
208 let mut header = Header::default();
210 for num in 0..=tip {
211 header.number = num;
212 header_writer.append_header(&header, &BlockHash::default()).unwrap();
213 }
214 header_writer.commit().unwrap();
215 }
216
217 fn prune_and_validate(
219 writer: &mut StaticFileProviderRWRefMut<'_, EthPrimitives>,
220 sf_rw: &StaticFileProvider<EthPrimitives>,
221 static_dir: impl AsRef<Path>,
222 prune_count: u64,
223 expected_tip: Option<u64>,
224 expected_file_count: u64,
225 ) -> eyre::Result<()> {
226 writer.prune_headers(prune_count)?;
227 writer.commit()?;
228
229 assert_eyre(
231 sf_rw.get_highest_static_file_block(StaticFileSegment::Headers),
232 expected_tip,
233 "block mismatch",
234 )?;
235
236 if let Some(id) = expected_tip {
237 assert_eyre(
238 sf_rw.header_by_number(id)?.map(|h| h.number),
239 expected_tip,
240 "header mismatch",
241 )?;
242 }
243
244 assert_eyre(
246 count_files_without_lockfile(static_dir)?,
247 expected_file_count as usize,
248 "file count mismatch",
249 )?;
250
251 Ok(())
252 }
253
254 type PruneCount = u64;
256 type ExpectedTip = u64;
257 type ExpectedFileCount = u64;
258 let mut tmp_tip = tip;
259 let test_cases: Vec<(PruneCount, Option<ExpectedTip>, ExpectedFileCount)> = vec![
260 {
262 tmp_tip -= 1;
263 (1, Some(tmp_tip), initial_file_count)
264 },
265 {
267 tmp_tip -= blocks_per_file - 1;
268 (blocks_per_file - 1, Some(tmp_tip), initial_file_count - files_per_range)
269 },
270 {
273 tmp_tip -= blocks_per_file + 1;
274 (blocks_per_file + 1, Some(tmp_tip), initial_file_count - files_per_range * 2)
275 },
276 {
278 (
279 tmp_tip,
280 Some(0), files_per_range, )
283 },
284 {
286 (
287 1,
288 None, files_per_range, )
291 },
292 ];
293
294 {
296 let sf_rw = StaticFileProviderBuilder::read_write(&static_dir)
297 .with_blocks_per_file(blocks_per_file)
298 .build()
299 .expect("Failed to build static file provider");
300
301 assert_eq!(sf_rw.get_highest_static_file_block(StaticFileSegment::Headers), Some(tip));
302 assert_eq!(
303 count_files_without_lockfile(static_dir.as_ref()).unwrap(),
304 initial_file_count as usize
305 );
306
307 let mut header_writer = sf_rw.latest_writer(StaticFileSegment::Headers).unwrap();
308
309 for (case, (prune_count, expected_tip, expected_file_count)) in
310 test_cases.into_iter().enumerate()
311 {
312 prune_and_validate(
313 &mut header_writer,
314 &sf_rw,
315 &static_dir,
316 prune_count,
317 expected_tip,
318 expected_file_count,
319 )
320 .map_err(|err| eyre::eyre!("Test case {case}: {err}"))
321 .unwrap();
322 }
323 }
324 }
325
326 fn setup_tx_based_scenario(
333 sf_rw: &StaticFileProvider<EthPrimitives>,
334 segment: StaticFileSegment,
335 blocks_per_file: u64,
336 ) {
337 fn setup_block_ranges(
338 writer: &mut StaticFileProviderRWRefMut<'_, EthPrimitives>,
339 sf_rw: &StaticFileProvider<EthPrimitives>,
340 segment: StaticFileSegment,
341 block_range: &Range<u64>,
342 mut tx_count: u64,
343 next_tx_num: &mut u64,
344 ) {
345 let mut receipt = Receipt::default();
346 let mut tx = TxLegacy::default();
347
348 for block in block_range.clone() {
349 writer.increment_block(block).unwrap();
350
351 if tx_count > 0 {
353 match segment {
354 StaticFileSegment::Headers |
355 StaticFileSegment::AccountChangeSets |
356 StaticFileSegment::StorageChangeSets => {
357 panic!("non tx based segment")
358 }
359 StaticFileSegment::Transactions => {
360 tx.nonce = *next_tx_num;
362 let tx: TransactionSigned =
363 tx.clone().into_signed(Signature::test_signature()).into();
364 writer.append_transaction(*next_tx_num, &tx).unwrap();
365 }
366 StaticFileSegment::Receipts => {
367 receipt.cumulative_gas_used = *next_tx_num;
369 writer.append_receipt(*next_tx_num, &receipt).unwrap();
370 }
371 StaticFileSegment::TransactionSenders => {
372 let sender = Address::from(U160::from(*next_tx_num));
374 writer.append_transaction_sender(*next_tx_num, &sender).unwrap();
375 }
376 }
377 *next_tx_num += 1;
378 tx_count -= 1;
379 }
380 }
381 writer.commit().unwrap();
382
383 let expected_block = block_range.end - 1;
385 let expected_tx = if tx_count == 0 { *next_tx_num - 1 } else { *next_tx_num };
386
387 assert_eq!(sf_rw.get_highest_static_file_block(segment), Some(expected_block),);
389 assert_eq!(sf_rw.get_highest_static_file_tx(segment), Some(expected_tx),);
390 }
391
392 let block_ranges = [
394 0..blocks_per_file,
395 blocks_per_file..blocks_per_file * 2,
396 blocks_per_file * 2..blocks_per_file * 3,
397 ];
398
399 let tx_counts = [
400 blocks_per_file - 1, 0, 1, ];
404
405 let mut writer = sf_rw.latest_writer(segment).unwrap();
406 let mut next_tx_num = 0;
407
408 for (block_range, tx_count) in block_ranges.iter().zip(tx_counts.iter()) {
410 setup_block_ranges(
411 &mut writer,
412 sf_rw,
413 segment,
414 block_range,
415 *tx_count,
416 &mut next_tx_num,
417 );
418 }
419
420 let expected_tx_ranges = vec![
422 Some(SegmentRangeInclusive::new(0, 8)),
423 None,
424 Some(SegmentRangeInclusive::new(9, 9)),
425 ];
426
427 block_ranges.iter().zip(expected_tx_ranges).for_each(|(block_range, expected_tx_range)| {
428 assert_eq!(
429 sf_rw
430 .get_segment_provider_for_block(segment, block_range.start, None)
431 .unwrap()
432 .user_header()
433 .tx_range(),
434 expected_tx_range
435 );
436 });
437
438 let expected_tx_index = BTreeMap::from([
440 (8, SegmentRangeInclusive::new(0, 9)),
441 (9, SegmentRangeInclusive::new(20, 29)),
442 ]);
443 assert_eq!(
444 sf_rw.tx_index(segment),
445 (!expected_tx_index.is_empty()).then_some(expected_tx_index),
446 "tx index mismatch",
447 );
448 }
449
450 #[test]
451 fn test_tx_based_truncation() {
452 let segments = [StaticFileSegment::Transactions, StaticFileSegment::Receipts];
453 let blocks_per_file = 10; let files_per_range = 3; let file_set_count = 3; let initial_file_count = files_per_range * file_set_count;
457
458 #[expect(clippy::too_many_arguments)]
459 fn prune_and_validate(
460 sf_rw: &StaticFileProvider<EthPrimitives>,
461 static_dir: impl AsRef<Path>,
462 segment: StaticFileSegment,
463 prune_count: u64,
464 last_block: u64,
465 expected_tx_tip: Option<u64>,
466 expected_file_count: i32,
467 expected_tx_index: BTreeMap<TxNumber, SegmentRangeInclusive>,
468 ) -> eyre::Result<()> {
469 let mut writer = sf_rw.latest_writer(segment)?;
470
471 match segment {
473 StaticFileSegment::Headers |
474 StaticFileSegment::AccountChangeSets |
475 StaticFileSegment::StorageChangeSets => {
476 panic!("non tx based segment")
477 }
478 StaticFileSegment::Transactions => {
479 writer.prune_transactions(prune_count, last_block)?
480 }
481 StaticFileSegment::Receipts => writer.prune_receipts(prune_count, last_block)?,
482 StaticFileSegment::TransactionSenders => {
483 writer.prune_transaction_senders(prune_count, last_block)?
484 }
485 }
486 writer.commit()?;
487
488 assert_eyre(
490 sf_rw.get_highest_static_file_block(segment),
491 Some(last_block),
492 "block mismatch",
493 )?;
494 assert_eyre(sf_rw.get_highest_static_file_tx(segment), expected_tx_tip, "tx mismatch")?;
495
496 if let Some(id) = expected_tx_tip {
499 match segment {
500 StaticFileSegment::Headers |
501 StaticFileSegment::AccountChangeSets |
502 StaticFileSegment::StorageChangeSets => {
503 panic!("non tx based segment")
504 }
505 StaticFileSegment::Transactions => assert_eyre(
506 expected_tx_tip,
507 sf_rw.transaction_by_id(id)?.map(|t| t.nonce()),
508 "tx mismatch",
509 )?,
510 StaticFileSegment::Receipts => assert_eyre(
511 expected_tx_tip,
512 sf_rw.receipt(id)?.map(|r| r.cumulative_gas_used),
513 "receipt mismatch",
514 )?,
515 StaticFileSegment::TransactionSenders => assert_eyre(
516 expected_tx_tip,
517 sf_rw
518 .transaction_sender(id)?
519 .map(|s| u64::try_from(U160::from_be_bytes(s.0.into())).unwrap()),
520 "sender mismatch",
521 )?,
522 }
523 }
524
525 assert_eyre(
527 count_files_without_lockfile(static_dir)?,
528 expected_file_count as usize,
529 "file count mismatch",
530 )?;
531
532 assert_eyre(
534 sf_rw.tx_index(segment).map(|index| index.iter().map(|(k, v)| (*k, *v)).collect()),
535 (!expected_tx_index.is_empty()).then_some(expected_tx_index),
536 "tx index mismatch",
537 )?;
538
539 Ok(())
540 }
541
542 for segment in segments {
543 let (static_dir, _) = create_test_static_files_dir();
544
545 let sf_rw = StaticFileProviderBuilder::read_write(&static_dir)
546 .with_blocks_per_file(blocks_per_file)
547 .build()
548 .expect("Failed to build static file provider");
549
550 setup_tx_based_scenario(&sf_rw, segment, blocks_per_file);
551
552 let sf_rw = StaticFileProviderBuilder::read_write(&static_dir)
553 .with_blocks_per_file(blocks_per_file)
554 .build()
555 .expect("Failed to build static file provider");
556 let highest_tx = sf_rw.get_highest_static_file_tx(segment).unwrap();
557
558 let test_cases = vec![
561 (
566 1,
567 blocks_per_file * 2,
568 Some(highest_tx - 1),
569 initial_file_count,
570 BTreeMap::from([(highest_tx - 1, SegmentRangeInclusive::new(0, 9))]),
571 ),
572 (
576 0,
577 blocks_per_file - 1,
578 Some(highest_tx - 1),
579 files_per_range,
580 BTreeMap::from([(highest_tx - 1, SegmentRangeInclusive::new(0, 9))]),
581 ),
582 (
584 highest_tx - 1,
585 1,
586 Some(0),
587 files_per_range,
588 BTreeMap::from([(0, SegmentRangeInclusive::new(0, 1))]),
589 ),
590 (1, 0, None, files_per_range, BTreeMap::from([])),
592 ];
593
594 for (
596 case,
597 (prune_count, last_block, expected_tx_tip, expected_file_count, expected_tx_index),
598 ) in test_cases.into_iter().enumerate()
599 {
600 prune_and_validate(
601 &sf_rw,
602 &static_dir,
603 segment,
604 prune_count,
605 last_block,
606 expected_tx_tip,
607 expected_file_count,
608 expected_tx_index,
609 )
610 .map_err(|err| eyre::eyre!("Test case {case}: {err}"))
611 .unwrap();
612 }
613 }
614 }
615
616 fn count_files_without_lockfile(path: impl AsRef<Path>) -> eyre::Result<usize> {
618 let is_lockfile =
619 |entry: &fs::DirEntry| entry.path().file_name().is_some_and(|name| name == "lock");
620 let count = fs::read_dir(path)?
621 .filter_map(|entry| entry.ok())
622 .filter(|entry| !is_lockfile(entry))
623 .count();
624
625 Ok(count)
626 }
627
628 #[test]
629 fn test_dynamic_size() -> eyre::Result<()> {
630 let (static_dir, _) = create_test_static_files_dir();
631
632 {
633 let sf_rw: StaticFileProvider<EthPrimitives> =
634 StaticFileProviderBuilder::read_write(&static_dir)
635 .with_blocks_per_file(10)
636 .build()?;
637 let mut header_writer = sf_rw.latest_writer(StaticFileSegment::Headers)?;
638
639 let mut header = Header::default();
640 for num in 0..=15 {
641 header.number = num;
642 header_writer.append_header(&header, &BlockHash::default()).unwrap();
643 }
644 header_writer.commit().unwrap();
645
646 assert_eq!(sf_rw.headers_range(0..=15)?.len(), 16);
647 assert_eq!(
648 sf_rw.expected_block_index(StaticFileSegment::Headers),
649 Some(BTreeMap::from([
650 (9, SegmentRangeInclusive::new(0, 9)),
651 (19, SegmentRangeInclusive::new(10, 19))
652 ])),
653 )
654 }
655
656 {
657 let sf_rw: StaticFileProvider<EthPrimitives> =
658 StaticFileProviderBuilder::read_write(&static_dir)
659 .with_blocks_per_file(5)
660 .build()?;
661 let mut header_writer = sf_rw.latest_writer(StaticFileSegment::Headers)?;
662
663 let mut header = Header::default();
664 for num in 16..=22 {
665 header.number = num;
666 header_writer.append_header(&header, &BlockHash::default()).unwrap();
667 }
668 header_writer.commit().unwrap();
669
670 assert_eq!(sf_rw.headers_range(0..=22)?.len(), 23);
671 assert_eq!(
672 sf_rw.expected_block_index(StaticFileSegment::Headers),
673 Some(BTreeMap::from([
674 (9, SegmentRangeInclusive::new(0, 9)),
675 (19, SegmentRangeInclusive::new(10, 19)),
676 (24, SegmentRangeInclusive::new(20, 24))
677 ]))
678 )
679 }
680
681 {
682 let sf_rw: StaticFileProvider<EthPrimitives> =
683 StaticFileProviderBuilder::read_write(&static_dir)
684 .with_blocks_per_file(15)
685 .build()?;
686 let mut header_writer = sf_rw.latest_writer(StaticFileSegment::Headers)?;
687
688 let mut header = Header::default();
689 for num in 23..=40 {
690 header.number = num;
691 header_writer.append_header(&header, &BlockHash::default()).unwrap();
692 }
693 header_writer.commit().unwrap();
694
695 assert_eq!(sf_rw.headers_range(0..=40)?.len(), 41);
696 assert_eq!(
697 sf_rw.expected_block_index(StaticFileSegment::Headers),
698 Some(BTreeMap::from([
699 (9, SegmentRangeInclusive::new(0, 9)),
700 (19, SegmentRangeInclusive::new(10, 19)),
701 (24, SegmentRangeInclusive::new(20, 24)),
702 (39, SegmentRangeInclusive::new(25, 39)),
703 (54, SegmentRangeInclusive::new(40, 54))
704 ]))
705 )
706 }
707
708 Ok(())
709 }
710
711 #[test]
712 fn test_account_changeset_static_files() {
713 let (static_dir, _) = create_test_static_files_dir();
714
715 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
716 .expect("Failed to create static file provider");
717
718 fn generate_test_changesets(
720 block_num: u64,
721 addresses: Vec<Address>,
722 ) -> Vec<AccountBeforeTx> {
723 addresses
724 .into_iter()
725 .map(|address| AccountBeforeTx {
726 address,
727 info: Some(Account {
728 nonce: block_num,
729 balance: U256::from(block_num * 1000),
730 ..Default::default()
731 }),
732 })
733 .collect()
734 }
735
736 {
738 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
739
740 let test_blocks = 10u64;
742 let addresses_per_block = 5;
743
744 for block_num in 0..test_blocks {
745 let addresses: Vec<Address> = (0..addresses_per_block)
747 .map(|i| {
748 let mut addr = Address::ZERO;
749 addr.0[0] = block_num as u8;
750 addr.0[1] = i as u8;
751 addr
752 })
753 .collect();
754
755 let changeset = generate_test_changesets(block_num, addresses);
756
757 writer.append_account_changeset(changeset, block_num).unwrap();
758 }
759
760 writer.commit().unwrap();
761 }
762
763 {
765 let provider = sf_rw
766 .get_segment_provider_for_block(StaticFileSegment::AccountChangeSets, 5, None)
767 .unwrap();
768
769 let offsets = provider.read_changeset_offsets().unwrap();
771 assert!(offsets.is_some());
772 let offsets = offsets.unwrap();
773 assert_eq!(offsets.len(), 10); for (i, offset) in offsets.iter().enumerate() {
777 assert_eq!(offset.num_changes(), 5, "Block {} should have 5 changes", i);
778 }
779 }
780 }
781
782 #[test]
783 fn test_get_account_before_block() {
784 let (static_dir, _) = create_test_static_files_dir();
785
786 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
787 .expect("Failed to create static file provider");
788
789 let test_address = Address::repeat_byte(1u8);
791 let other_address = Address::repeat_byte(2u8);
792 let missing_address = Address::repeat_byte(3u8);
793
794 {
796 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
797
798 writer
800 .append_account_changeset(
801 vec![
802 AccountBeforeTx {
803 address: test_address,
804 info: None, },
806 AccountBeforeTx { address: other_address, info: None },
807 ],
808 0,
809 )
810 .unwrap();
811
812 writer
814 .append_account_changeset(
815 vec![AccountBeforeTx {
816 address: other_address,
817 info: Some(Account::default()),
818 }],
819 1,
820 )
821 .unwrap();
822
823 writer
825 .append_account_changeset(
826 vec![AccountBeforeTx {
827 address: test_address,
828 info: Some(Account {
829 nonce: 1,
830 balance: U256::from(1000),
831 ..Default::default()
832 }),
833 }],
834 2,
835 )
836 .unwrap();
837
838 writer.commit().unwrap();
839 }
840
841 {
843 let result = sf_rw.get_account_before_block(0, test_address).unwrap();
845 assert!(result.is_some());
846 let account_before = result.unwrap();
847 assert_eq!(account_before.address, test_address);
848 assert!(account_before.info.is_none()); let result = sf_rw.get_account_before_block(2, test_address).unwrap();
852 assert!(result.is_some());
853 let account_before = result.unwrap();
854 assert_eq!(account_before.address, test_address);
855 assert!(account_before.info.is_some());
856 let info = account_before.info.unwrap();
857 assert_eq!(info.nonce, 1);
858 assert_eq!(info.balance, U256::from(1000));
859
860 let result = sf_rw.get_account_before_block(1, test_address).unwrap();
862 assert!(result.is_none()); let result = sf_rw.get_account_before_block(2, missing_address).unwrap();
866 assert!(result.is_none());
867
868 let result = sf_rw.get_account_before_block(1, other_address).unwrap();
870 assert!(result.is_some());
871 let account_before = result.unwrap();
872 assert_eq!(account_before.address, other_address);
873 assert!(account_before.info.is_some());
874 }
875 }
876
877 #[test]
878 fn test_account_changeset_truncation() {
879 let (static_dir, _) = create_test_static_files_dir();
880
881 let blocks_per_file = 10;
882 let files_per_range = 4;
884 let file_set_count = 3;
885 let initial_file_count = files_per_range * file_set_count;
886 let tip = blocks_per_file * file_set_count - 1;
887
888 {
890 let sf_rw: StaticFileProvider<EthPrimitives> =
891 StaticFileProviderBuilder::read_write(&static_dir)
892 .with_blocks_per_file(blocks_per_file)
893 .build()
894 .expect("failed to create static file provider");
895
896 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
897
898 for block_num in 0..=tip {
899 let num_changes = ((block_num % 5) + 1) as usize;
901 let mut changeset = Vec::with_capacity(num_changes);
902
903 for i in 0..num_changes {
904 let mut address = Address::ZERO;
905 address.0[0] = block_num as u8;
906 address.0[1] = i as u8;
907
908 changeset.push(AccountBeforeTx {
909 address,
910 info: Some(Account {
911 nonce: block_num,
912 balance: U256::from(block_num * 1000 + i as u64),
913 ..Default::default()
914 }),
915 });
916 }
917
918 writer.append_account_changeset(changeset, block_num).unwrap();
919 }
920
921 writer.commit().unwrap();
922 }
923
924 fn validate_truncation(
926 sf_rw: &StaticFileProvider<EthPrimitives>,
927 static_dir: impl AsRef<Path>,
928 expected_tip: Option<u64>,
929 expected_file_count: u64,
930 ) -> eyre::Result<()> {
931 let highest_block =
933 sf_rw.get_highest_static_file_block(StaticFileSegment::AccountChangeSets);
934 assert_eyre(highest_block, expected_tip, "block tip mismatch")?;
935
936 assert_eyre(
938 count_files_without_lockfile(static_dir)?,
939 expected_file_count as usize,
940 "file count mismatch",
941 )?;
942
943 if let Some(tip) = expected_tip {
944 let provider = sf_rw.get_segment_provider_for_block(
946 StaticFileSegment::AccountChangeSets,
947 tip,
948 None,
949 )?;
950
951 let offsets = provider.read_changeset_offsets().unwrap();
953 assert!(offsets.is_some(), "Should have changeset offsets");
954 }
955
956 Ok(())
957 }
958
959 let sf_rw = StaticFileProviderBuilder::read_write(&static_dir)
961 .with_blocks_per_file(blocks_per_file)
962 .build()
963 .expect("failed to create static file provider");
964
965 sf_rw.initialize_index().expect("Failed to initialize index");
967
968 {
970 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
971 writer.prune_account_changesets(20).unwrap();
972 writer.commit().unwrap();
973
974 validate_truncation(&sf_rw, &static_dir, Some(20), initial_file_count)
975 .expect("Truncation validation failed");
976 }
977
978 {
980 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
981 writer.prune_account_changesets(9).unwrap();
982 writer.commit().unwrap();
983
984 validate_truncation(&sf_rw, &static_dir, Some(9), files_per_range)
985 .expect("Truncation validation failed");
986 }
987
988 {
990 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
991 writer.prune_account_changesets(0).unwrap();
992 writer.commit().unwrap();
993
994 validate_truncation(&sf_rw, &static_dir, Some(0), files_per_range)
996 .expect("Truncation validation failed");
997 }
998 }
999
1000 #[test]
1001 fn test_changeset_binary_search() {
1002 let (static_dir, _) = create_test_static_files_dir();
1003
1004 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
1005 .expect("Failed to create static file provider");
1006
1007 let block_num = 0u64;
1009 let num_accounts = 100;
1010
1011 let mut addresses: Vec<Address> = Vec::with_capacity(num_accounts);
1012 for i in 0..num_accounts {
1013 let mut addr = Address::ZERO;
1014 addr.0[0] = (i / 256) as u8;
1015 addr.0[1] = (i % 256) as u8;
1016 addresses.push(addr);
1017 }
1018
1019 {
1021 let mut writer = sf_rw.latest_writer(StaticFileSegment::AccountChangeSets).unwrap();
1022
1023 let changeset: Vec<AccountBeforeTx> = addresses
1024 .iter()
1025 .map(|addr| AccountBeforeTx {
1026 address: *addr,
1027 info: Some(Account {
1028 nonce: 1,
1029 balance: U256::from(1000),
1030 ..Default::default()
1031 }),
1032 })
1033 .collect();
1034
1035 writer.append_account_changeset(changeset, block_num).unwrap();
1036 writer.commit().unwrap();
1037 }
1038
1039 {
1041 let result = sf_rw.get_account_before_block(block_num, addresses[0]).unwrap();
1043 assert!(result.is_some());
1044 assert_eq!(result.unwrap().address, addresses[0]);
1045
1046 let result =
1048 sf_rw.get_account_before_block(block_num, addresses[num_accounts - 1]).unwrap();
1049 assert!(result.is_some());
1050 assert_eq!(result.unwrap().address, addresses[num_accounts - 1]);
1051
1052 let mid = num_accounts / 2;
1054 let result = sf_rw.get_account_before_block(block_num, addresses[mid]).unwrap();
1055 assert!(result.is_some());
1056 assert_eq!(result.unwrap().address, addresses[mid]);
1057
1058 let mut missing_addr = Address::ZERO;
1060 missing_addr.0[0] = 255;
1061 missing_addr.0[1] = 255;
1062 let result = sf_rw.get_account_before_block(block_num, missing_addr).unwrap();
1063 assert!(result.is_none());
1064
1065 for i in (0..num_accounts).step_by(10) {
1067 let result = sf_rw.get_account_before_block(block_num, addresses[i]).unwrap();
1068 assert!(result.is_some());
1069 assert_eq!(result.unwrap().address, addresses[i]);
1070 }
1071 }
1072 }
1073
1074 #[test]
1075 fn test_storage_changeset_static_files() {
1076 let (static_dir, _) = create_test_static_files_dir();
1077
1078 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
1079 .expect("Failed to create static file provider");
1080
1081 {
1083 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1084
1085 let test_blocks = 10u64;
1087 let entries_per_block = 5;
1088
1089 for block_num in 0..test_blocks {
1090 let changeset = (0..entries_per_block)
1091 .map(|i| {
1092 let mut addr = Address::ZERO;
1093 addr.0[0] = block_num as u8;
1094 addr.0[1] = i as u8;
1095 StorageBeforeTx {
1096 address: addr,
1097 key: B256::with_last_byte(i as u8),
1098 value: U256::from(block_num * 1000 + i as u64),
1099 }
1100 })
1101 .collect::<Vec<_>>();
1102
1103 writer.append_storage_changeset(changeset, block_num).unwrap();
1104 }
1105
1106 writer.commit().unwrap();
1107 }
1108
1109 {
1111 let provider = sf_rw
1112 .get_segment_provider_for_block(StaticFileSegment::StorageChangeSets, 5, None)
1113 .unwrap();
1114
1115 let offsets = provider.read_changeset_offsets().unwrap();
1117 assert!(offsets.is_some());
1118 let offsets = offsets.unwrap();
1119 assert_eq!(offsets.len(), 10); for (i, offset) in offsets.iter().enumerate() {
1123 assert_eq!(offset.num_changes(), 5, "Block {} should have 5 changes", i);
1124 }
1125 }
1126 }
1127
1128 #[test]
1129 fn test_get_storage_before_block() {
1130 let (static_dir, _) = create_test_static_files_dir();
1131
1132 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
1133 .expect("Failed to create static file provider");
1134
1135 let test_address = Address::repeat_byte(1u8);
1136 let other_address = Address::repeat_byte(2u8);
1137 let missing_address = Address::repeat_byte(3u8);
1138 let test_key = B256::with_last_byte(1);
1139 let other_key = B256::with_last_byte(2);
1140
1141 {
1143 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1144
1145 writer
1147 .append_storage_changeset(
1148 vec![
1149 StorageBeforeTx { address: test_address, key: test_key, value: U256::ZERO },
1150 StorageBeforeTx {
1151 address: other_address,
1152 key: other_key,
1153 value: U256::from(5),
1154 },
1155 ],
1156 0,
1157 )
1158 .unwrap();
1159
1160 writer
1162 .append_storage_changeset(
1163 vec![StorageBeforeTx {
1164 address: other_address,
1165 key: other_key,
1166 value: U256::from(7),
1167 }],
1168 1,
1169 )
1170 .unwrap();
1171
1172 writer
1174 .append_storage_changeset(
1175 vec![StorageBeforeTx {
1176 address: test_address,
1177 key: test_key,
1178 value: U256::from(9),
1179 }],
1180 2,
1181 )
1182 .unwrap();
1183
1184 writer.commit().unwrap();
1185 }
1186
1187 {
1189 let result = sf_rw.get_storage_before_block(0, test_address, test_key).unwrap();
1190 assert!(result.is_some());
1191 let entry = result.unwrap();
1192 assert_eq!(entry.key, test_key);
1193 assert_eq!(entry.value, U256::ZERO);
1194
1195 let result = sf_rw.get_storage_before_block(2, test_address, test_key).unwrap();
1196 assert!(result.is_some());
1197 let entry = result.unwrap();
1198 assert_eq!(entry.key, test_key);
1199 assert_eq!(entry.value, U256::from(9));
1200
1201 let result = sf_rw.get_storage_before_block(1, test_address, test_key).unwrap();
1202 assert!(result.is_none());
1203
1204 let result = sf_rw.get_storage_before_block(2, missing_address, test_key).unwrap();
1205 assert!(result.is_none());
1206
1207 let result = sf_rw.get_storage_before_block(1, other_address, other_key).unwrap();
1208 assert!(result.is_some());
1209 let entry = result.unwrap();
1210 assert_eq!(entry.key, other_key);
1211 }
1212 }
1213
1214 #[test]
1215 fn test_storage_changeset_truncation() {
1216 let (static_dir, _) = create_test_static_files_dir();
1217
1218 let blocks_per_file = 10;
1219 let files_per_range = 4;
1221 let file_set_count = 3;
1222 let initial_file_count = files_per_range * file_set_count;
1223 let tip = blocks_per_file * file_set_count - 1;
1224
1225 {
1227 let sf_rw: StaticFileProvider<EthPrimitives> =
1228 StaticFileProviderBuilder::read_write(&static_dir)
1229 .with_blocks_per_file(blocks_per_file)
1230 .build()
1231 .expect("failed to create static file provider");
1232
1233 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1234
1235 for block_num in 0..=tip {
1236 let num_changes = ((block_num % 5) + 1) as usize;
1237 let mut changeset = Vec::with_capacity(num_changes);
1238
1239 for i in 0..num_changes {
1240 let mut address = Address::ZERO;
1241 address.0[0] = block_num as u8;
1242 address.0[1] = i as u8;
1243
1244 changeset.push(StorageBeforeTx {
1245 address,
1246 key: B256::with_last_byte(i as u8),
1247 value: U256::from(block_num * 1000 + i as u64),
1248 });
1249 }
1250
1251 writer.append_storage_changeset(changeset, block_num).unwrap();
1252 }
1253
1254 writer.commit().unwrap();
1255 }
1256
1257 fn validate_truncation(
1258 sf_rw: &StaticFileProvider<EthPrimitives>,
1259 static_dir: impl AsRef<Path>,
1260 expected_tip: Option<u64>,
1261 expected_file_count: u64,
1262 ) -> eyre::Result<()> {
1263 let highest_block =
1264 sf_rw.get_highest_static_file_block(StaticFileSegment::StorageChangeSets);
1265 assert_eyre(highest_block, expected_tip, "block tip mismatch")?;
1266
1267 assert_eyre(
1268 count_files_without_lockfile(static_dir)?,
1269 expected_file_count as usize,
1270 "file count mismatch",
1271 )?;
1272
1273 if let Some(tip) = expected_tip {
1274 let provider = sf_rw.get_segment_provider_for_block(
1275 StaticFileSegment::StorageChangeSets,
1276 tip,
1277 None,
1278 )?;
1279 let offsets = provider.read_changeset_offsets()?;
1280 assert!(offsets.is_some(), "Should have changeset offsets");
1281 }
1282
1283 Ok(())
1284 }
1285
1286 let sf_rw = StaticFileProviderBuilder::read_write(&static_dir)
1287 .with_blocks_per_file(blocks_per_file)
1288 .build()
1289 .expect("failed to create static file provider");
1290
1291 sf_rw.initialize_index().expect("Failed to initialize index");
1292
1293 {
1295 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1296 writer.prune_storage_changesets(20).unwrap();
1297 writer.commit().unwrap();
1298
1299 validate_truncation(&sf_rw, &static_dir, Some(20), initial_file_count)
1300 .expect("Truncation validation failed");
1301 }
1302
1303 {
1305 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1306 writer.prune_storage_changesets(9).unwrap();
1307 writer.commit().unwrap();
1308
1309 validate_truncation(&sf_rw, &static_dir, Some(9), files_per_range)
1310 .expect("Truncation validation failed");
1311 }
1312
1313 {
1315 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1316 writer.prune_storage_changesets(0).unwrap();
1317 writer.commit().unwrap();
1318
1319 validate_truncation(&sf_rw, &static_dir, Some(0), files_per_range)
1320 .expect("Truncation validation failed");
1321 }
1322 }
1323
1324 #[test]
1325 fn test_storage_changeset_binary_search() {
1326 let (static_dir, _) = create_test_static_files_dir();
1327
1328 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
1329 .expect("Failed to create static file provider");
1330
1331 let block_num = 0u64;
1332 let num_slots = 100;
1333 let address = Address::repeat_byte(4u8);
1334
1335 let mut keys: Vec<B256> = Vec::with_capacity(num_slots);
1336 for i in 0..num_slots {
1337 keys.push(B256::with_last_byte(i as u8));
1338 }
1339
1340 {
1341 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1342 let changeset = keys
1343 .iter()
1344 .enumerate()
1345 .map(|(i, key)| StorageBeforeTx { address, key: *key, value: U256::from(i as u64) })
1346 .collect::<Vec<_>>();
1347
1348 writer.append_storage_changeset(changeset, block_num).unwrap();
1349 writer.commit().unwrap();
1350 }
1351
1352 {
1353 let result = sf_rw.get_storage_before_block(block_num, address, keys[0]).unwrap();
1354 assert!(result.is_some());
1355 let entry = result.unwrap();
1356 assert_eq!(entry.key, keys[0]);
1357 assert_eq!(entry.value, U256::ZERO);
1358
1359 let result =
1360 sf_rw.get_storage_before_block(block_num, address, keys[num_slots - 1]).unwrap();
1361 assert!(result.is_some());
1362 let entry = result.unwrap();
1363 assert_eq!(entry.key, keys[num_slots - 1]);
1364
1365 let mid = num_slots / 2;
1366 let result = sf_rw.get_storage_before_block(block_num, address, keys[mid]).unwrap();
1367 assert!(result.is_some());
1368 let entry = result.unwrap();
1369 assert_eq!(entry.key, keys[mid]);
1370
1371 let missing_key = B256::with_last_byte(255);
1372 let result = sf_rw.get_storage_before_block(block_num, address, missing_key).unwrap();
1373 assert!(result.is_none());
1374
1375 for i in (0..num_slots).step_by(10) {
1376 let result = sf_rw.get_storage_before_block(block_num, address, keys[i]).unwrap();
1377 assert!(result.is_some());
1378 assert_eq!(result.unwrap().key, keys[i]);
1379 }
1380 }
1381 }
1382
1383 #[test]
1384 fn test_last_block_flushed_on_commit() {
1385 let (static_dir, _) = create_test_static_files_dir();
1386
1387 let sf_rw = StaticFileProvider::<EthPrimitives>::read_write(&static_dir)
1388 .expect("Failed to create static file provider");
1389
1390 let address = Address::repeat_byte(5u8);
1391 let key = B256::with_last_byte(1);
1392
1393 {
1396 let mut writer = sf_rw.latest_writer(StaticFileSegment::StorageChangeSets).unwrap();
1397
1398 writer
1400 .append_storage_changeset(
1401 vec![StorageBeforeTx { address, key, value: U256::from(42) }],
1402 0,
1403 )
1404 .unwrap();
1405
1406 writer.commit().unwrap();
1408 }
1409
1410 let highest = sf_rw.get_highest_static_file_block(StaticFileSegment::StorageChangeSets);
1412 assert_eq!(highest, Some(0), "Should have block 0 after commit");
1413
1414 let result = sf_rw.get_storage_before_block(0, address, key).unwrap();
1416 assert!(result.is_some(), "Should be able to read the changeset entry");
1417 let entry = result.unwrap();
1418 assert_eq!(entry.value, U256::from(42));
1419 }
1420
1421 #[test]
1422 fn test_read_headers_while_appending() {
1423 let (static_dir, _) = create_test_static_files_dir();
1424 let sf_rw: StaticFileProvider<EthPrimitives> =
1425 StaticFileProviderBuilder::read_write(&static_dir)
1426 .build()
1427 .expect("Failed to build static file provider");
1428 let hash = |num: u64| B256::with_last_byte(num as u8 + 1);
1429
1430 let mut header_writer = sf_rw.latest_writer(StaticFileSegment::Headers).unwrap();
1431 let mut header = Header::default();
1432 for num in 0..=2 {
1433 header.number = num;
1434 header_writer.append_header(&header, &hash(num)).unwrap();
1435 }
1436 header_writer.commit().unwrap();
1437
1438 header.number = 3;
1441 header_writer.append_header(&header, &hash(3)).unwrap();
1442 header_writer.sync_all().unwrap();
1443
1444 sf_rw.initialize_index().unwrap();
1446
1447 assert_eq!(sf_rw.block_hash(2).unwrap(), Some(hash(2)));
1448 assert_eq!(sf_rw.block_hash(3).unwrap(), None);
1449 }
1450}