1use super::{
2 manager::StaticFileProviderInner, metrics::StaticFileProviderMetrics, StaticFileProvider,
3};
4use crate::providers::static_file::metrics::StaticFileProviderOperation;
5use alloy_consensus::BlockHeader;
6use alloy_primitives::{BlockHash, BlockNumber, TxNumber, U256};
7use parking_lot::{lock_api::RwLockWriteGuard, RawRwLock, RwLock};
8use reth_codecs::Compact;
9use reth_db::models::{AccountBeforeTx, StorageBeforeTx};
10use reth_db_api::models::CompactU256;
11use reth_nippy_jar::{NippyJar, NippyJarError, NippyJarWriter, CONFIG_FILE_EXTENSION};
12use reth_node_types::NodePrimitives;
13use reth_primitives_traits::FastInstant as Instant;
14use reth_static_file_types::{
15 ChangesetOffset, ChangesetOffsetReader, ChangesetOffsetWriter, SegmentHeader,
16 SegmentRangeInclusive, StaticFileSegment,
17};
18use reth_storage_errors::provider::{ProviderError, ProviderResult, StaticFileWriterError};
19use std::{
20 borrow::Borrow,
21 cmp::Ordering,
22 fmt::Debug,
23 fs::OpenOptions,
24 path::{Path, PathBuf},
25 sync::{Arc, Weak},
26};
27use tracing::{debug, instrument};
28
29#[derive(Debug, Clone, Copy)]
31enum PruneStrategy {
32 Headers {
34 num_blocks: u64,
36 },
37 Transactions {
39 num_rows: u64,
41 last_block: BlockNumber,
43 },
44 Receipts {
46 num_rows: u64,
48 last_block: BlockNumber,
50 },
51 TransactionSenders {
53 num_rows: u64,
55 last_block: BlockNumber,
57 },
58 AccountChangeSets {
60 last_block: BlockNumber,
62 },
63 StorageChangeSets {
65 last_block: BlockNumber,
67 },
68}
69
70#[derive(Debug)]
75pub(crate) struct StaticFileWriters<N> {
76 headers: RwLock<Option<StaticFileProviderRW<N>>>,
77 transactions: RwLock<Option<StaticFileProviderRW<N>>>,
78 receipts: RwLock<Option<StaticFileProviderRW<N>>>,
79 transaction_senders: RwLock<Option<StaticFileProviderRW<N>>>,
80 account_change_sets: RwLock<Option<StaticFileProviderRW<N>>>,
81 storage_change_sets: RwLock<Option<StaticFileProviderRW<N>>>,
82}
83
84impl<N> Default for StaticFileWriters<N> {
85 fn default() -> Self {
86 Self {
87 headers: Default::default(),
88 transactions: Default::default(),
89 receipts: Default::default(),
90 transaction_senders: Default::default(),
91 account_change_sets: Default::default(),
92 storage_change_sets: Default::default(),
93 }
94 }
95}
96
97impl<N: NodePrimitives> StaticFileWriters<N> {
98 pub(crate) fn get_or_create(
99 &self,
100 segment: StaticFileSegment,
101 create_fn: impl FnOnce() -> ProviderResult<StaticFileProviderRW<N>>,
102 ) -> ProviderResult<StaticFileProviderRWRefMut<'_, N>> {
103 let mut write_guard = match segment {
104 StaticFileSegment::Headers => self.headers.write(),
105 StaticFileSegment::Transactions => self.transactions.write(),
106 StaticFileSegment::Receipts => self.receipts.write(),
107 StaticFileSegment::TransactionSenders => self.transaction_senders.write(),
108 StaticFileSegment::AccountChangeSets => self.account_change_sets.write(),
109 StaticFileSegment::StorageChangeSets => self.storage_change_sets.write(),
110 };
111
112 if write_guard.is_none() {
113 *write_guard = Some(create_fn()?);
114 }
115
116 Ok(StaticFileProviderRWRefMut(write_guard))
117 }
118
119 pub(crate) fn remove(&self, segment: StaticFileSegment) {
121 let mut write_guard = match segment {
122 StaticFileSegment::Headers => self.headers.write(),
123 StaticFileSegment::Transactions => self.transactions.write(),
124 StaticFileSegment::Receipts => self.receipts.write(),
125 StaticFileSegment::TransactionSenders => self.transaction_senders.write(),
126 StaticFileSegment::AccountChangeSets => self.account_change_sets.write(),
127 StaticFileSegment::StorageChangeSets => self.storage_change_sets.write(),
128 };
129
130 *write_guard = None;
131 }
132
133 #[instrument(
134 name = "StaticFileWriters::commit",
135 level = "debug",
136 target = "providers::static_file",
137 skip_all
138 )]
139 pub(crate) fn commit(&self) -> ProviderResult<()> {
140 debug!(target: "providers::static_file", "Committing all static file segments");
141
142 for writer_lock in [
143 &self.headers,
144 &self.transactions,
145 &self.receipts,
146 &self.transaction_senders,
147 &self.account_change_sets,
148 &self.storage_change_sets,
149 ] {
150 let mut writer = writer_lock.write();
151 if let Some(writer) = writer.as_mut() {
152 writer.commit()?;
153 }
154 }
155
156 debug!(target: "providers::static_file", "Committed all static file segments");
157 Ok(())
158 }
159
160 pub(crate) fn has_unwind_queued(&self) -> bool {
161 for writer_lock in [
162 &self.headers,
163 &self.transactions,
164 &self.receipts,
165 &self.transaction_senders,
166 &self.account_change_sets,
167 &self.storage_change_sets,
168 ] {
169 let writer = writer_lock.read();
170 if let Some(writer) = writer.as_ref() &&
171 writer.will_prune_on_commit()
172 {
173 return true
174 }
175 }
176 false
177 }
178
179 #[instrument(
184 name = "StaticFileWriters::finalize",
185 level = "debug",
186 target = "providers::static_file",
187 skip_all
188 )]
189 pub(crate) fn finalize(&self) -> ProviderResult<()> {
190 debug!(target: "providers::static_file", "Finalizing all static file segments into disk");
191
192 for writer_lock in [
193 &self.headers,
194 &self.transactions,
195 &self.receipts,
196 &self.transaction_senders,
197 &self.account_change_sets,
198 &self.storage_change_sets,
199 ] {
200 let mut writer = writer_lock.write();
201 if let Some(writer) = writer.as_mut() {
202 writer.finalize()?;
203 }
204 }
205
206 debug!(target: "providers::static_file", "Finalized all static file segments into disk");
207 Ok(())
208 }
209}
210
211#[derive(Debug)]
213pub struct StaticFileProviderRWRefMut<'a, N>(
214 pub(crate) RwLockWriteGuard<'a, RawRwLock, Option<StaticFileProviderRW<N>>>,
215);
216
217impl<N> std::ops::DerefMut for StaticFileProviderRWRefMut<'_, N> {
218 fn deref_mut(&mut self) -> &mut Self::Target {
219 self.0.as_mut().expect("static file writer provider should be init")
221 }
222}
223
224impl<N> std::ops::Deref for StaticFileProviderRWRefMut<'_, N> {
225 type Target = StaticFileProviderRW<N>;
226
227 fn deref(&self) -> &Self::Target {
228 self.0.as_ref().expect("static file writer provider should be init")
230 }
231}
232
233#[derive(Debug)]
234pub struct StaticFileProviderRW<N> {
236 reader: Weak<StaticFileProviderInner<N>>,
241 writer: NippyJarWriter<SegmentHeader>,
243 data_path: PathBuf,
245 buf: Vec<u8>,
247 metrics: Option<Arc<StaticFileProviderMetrics>>,
249 prune_on_commit: Option<PruneStrategy>,
251 synced: bool,
253 changeset_offsets: Option<ChangesetOffsetWriter>,
255 current_changeset_offset: Option<ChangesetOffset>,
257}
258
259impl<N: NodePrimitives> StaticFileProviderRW<N> {
260 pub fn new(
265 segment: StaticFileSegment,
266 block: BlockNumber,
267 reader: Weak<StaticFileProviderInner<N>>,
268 metrics: Option<Arc<StaticFileProviderMetrics>>,
269 ) -> ProviderResult<Self> {
270 let (writer, data_path) = Self::open(segment, block, reader.clone(), metrics.clone())?;
271
272 let mut writer = Self {
274 writer,
275 data_path,
276 buf: Vec::with_capacity(100),
277 reader,
278 metrics,
279 prune_on_commit: None,
280 synced: false,
281 changeset_offsets: None,
282 current_changeset_offset: None,
283 };
284
285 writer.ensure_end_range_consistency()?;
288
289 if segment.is_change_based() {
291 writer.heal_changeset_sidecar()?;
292 }
293
294 Ok(writer)
295 }
296
297 fn open(
298 segment: StaticFileSegment,
299 block: u64,
300 reader: Weak<StaticFileProviderInner<N>>,
301 metrics: Option<Arc<StaticFileProviderMetrics>>,
302 ) -> ProviderResult<(NippyJarWriter<SegmentHeader>, PathBuf)> {
303 let start = Instant::now();
304
305 let static_file_provider = Self::upgrade_provider_to_strong_reference(&reader);
306
307 let block_range = static_file_provider.find_fixed_range(segment, block);
308 let (jar, path) = match static_file_provider.get_segment_provider_for_block(
309 segment,
310 block_range.start(),
311 None,
312 ) {
313 Ok(provider) => (
314 NippyJar::load(provider.data_path()).map_err(ProviderError::other)?,
315 provider.data_path().into(),
316 ),
317 Err(ProviderError::MissingStaticFileBlock(_, _)) => {
318 let path = static_file_provider.directory().join(segment.filename(&block_range));
319 (create_jar(segment, &path, block_range), path)
320 }
321 Err(err) => return Err(err),
322 };
323
324 let result = match NippyJarWriter::new(jar) {
325 Ok(writer) => Ok((writer, path)),
326 Err(NippyJarError::FrozenJar) => {
327 Err(ProviderError::FinalizedStaticFile(segment, block))
329 }
330 Err(e) => Err(ProviderError::other(e)),
331 }?;
332
333 if let Some(metrics) = &metrics {
334 metrics.record_segment_operation(
335 segment,
336 StaticFileProviderOperation::OpenWriter,
337 Some(start.elapsed()),
338 );
339 }
340
341 Ok(result)
342 }
343
344 fn ensure_end_range_consistency(&mut self) -> ProviderResult<()> {
353 let expected_rows = if self.user_header().segment().is_headers() {
355 self.user_header().block_len().unwrap_or_default()
356 } else {
357 self.user_header().tx_len().unwrap_or_default()
358 };
359 let actual_rows = self.writer.rows() as u64;
360 let pruned_rows = expected_rows.saturating_sub(actual_rows);
361 if pruned_rows > 0 {
362 self.user_header_mut().prune(pruned_rows);
363 }
364
365 debug!(
366 target: "providers::static_file",
367 segment = ?self.writer.user_header().segment(),
368 path = ?self.data_path,
369 pruned_rows,
370 "Ensuring end range consistency"
371 );
372
373 self.writer.commit().map_err(ProviderError::other)?;
374
375 self.update_index()?;
377 Ok(())
378 }
379
380 pub const fn will_prune_on_commit(&self) -> bool {
382 self.prune_on_commit.is_some()
383 }
384
385 fn heal_changeset_sidecar(&mut self) -> ProviderResult<()> {
393 let csoff_path = self.data_path.with_extension("csoff");
394
395 let header_claims_blocks = self.writer.user_header().changeset_offsets_len();
397 let actual_nippy_rows = self.writer.rows() as u64;
398
399 let actual_sidecar_blocks = if csoff_path.exists() {
401 let file_len = reth_fs_util::metadata(&csoff_path).map_err(ProviderError::other)?.len();
402 let aligned_len = file_len - (file_len % 16);
404 aligned_len / 16
405 } else {
406 0
407 };
408
409 if header_claims_blocks == 0 && actual_sidecar_blocks == 0 {
411 self.changeset_offsets =
412 Some(ChangesetOffsetWriter::new(&csoff_path, 0).map_err(ProviderError::other)?);
413 return Ok(());
414 }
415
416 let valid_blocks = if actual_sidecar_blocks > 0 {
418 let reader = ChangesetOffsetReader::new(&csoff_path, actual_sidecar_blocks)
419 .map_err(ProviderError::other)?;
420
421 let mut valid = 0u64;
424 for i in 0..actual_sidecar_blocks {
425 if let Some(offset) = reader.get(i).map_err(ProviderError::other)? {
426 if offset.offset() + offset.num_changes() <= actual_nippy_rows {
427 valid = i + 1;
428 } else {
429 break;
431 }
432 }
433 }
434 valid
435 } else {
436 0
437 };
438
439 let correct_blocks = valid_blocks.min(header_claims_blocks);
442
443 let mut needs_header_commit = false;
445
446 if correct_blocks != header_claims_blocks || actual_sidecar_blocks != correct_blocks {
447 tracing::warn!(
448 target: "reth::static_file",
449 path = %csoff_path.display(),
450 header_claims = header_claims_blocks,
451 sidecar_has = actual_sidecar_blocks,
452 valid_blocks = correct_blocks,
453 actual_rows = actual_nippy_rows,
454 "Three-way healing: syncing header, sidecar, and NippyJar state"
455 );
456
457 if actual_sidecar_blocks > correct_blocks {
459 let file = OpenOptions::new()
460 .write(true)
461 .open(&csoff_path)
462 .map_err(ProviderError::other)?;
463 file.set_len(correct_blocks * 16).map_err(ProviderError::other)?;
464 file.sync_all().map_err(ProviderError::other)?;
465
466 tracing::debug!(
467 target: "reth::static_file",
468 "Truncated sidecar from {} to {} blocks",
469 actual_sidecar_blocks,
470 correct_blocks
471 );
472 }
473
474 if correct_blocks < header_claims_blocks {
476 let blocks_removed = header_claims_blocks - correct_blocks;
479 self.writer.user_header_mut().prune(blocks_removed);
480
481 tracing::debug!(
482 target: "reth::static_file",
483 "Updated header: removed {} blocks (changeset_offsets_len: {} -> {})",
484 blocks_removed,
485 header_claims_blocks,
486 correct_blocks
487 );
488
489 needs_header_commit = true;
490 }
491 } else {
492 tracing::debug!(
493 target: "reth::static_file",
494 path = %csoff_path.display(),
495 blocks = correct_blocks,
496 "Changeset sidecar consistent, no healing needed"
497 );
498 }
499
500 let csoff_writer = ChangesetOffsetWriter::new(&csoff_path, correct_blocks)
502 .map_err(ProviderError::other)?;
503
504 self.changeset_offsets = Some(csoff_writer);
505
506 if needs_header_commit {
508 self.writer.commit().map_err(ProviderError::other)?;
509
510 tracing::info!(
511 target: "reth::static_file",
512 path = %csoff_path.display(),
513 blocks = correct_blocks,
514 "Committed healed changeset offset header"
515 );
516 }
517
518 Ok(())
519 }
520
521 fn flush_current_changeset_offset(&mut self) -> ProviderResult<()> {
529 if !self.writer.user_header().segment().is_change_based() {
530 return Ok(());
531 }
532
533 if let Some(offset) = self.current_changeset_offset.take() &&
534 let Some(writer) = &mut self.changeset_offsets
535 {
536 writer.append(&offset).map_err(ProviderError::other)?;
537 }
538 Ok(())
539 }
540
541 pub fn sync_all(&mut self) -> ProviderResult<()> {
548 if self.prune_on_commit.is_some() {
549 return Err(StaticFileWriterError::FinalizeWithPruneQueued.into());
550 }
551
552 self.flush_current_changeset_offset()?;
554 if let Some(writer) = &mut self.changeset_offsets {
555 writer.sync().map_err(ProviderError::other)?;
556 self.writer.user_header_mut().set_changeset_offsets_len(writer.len());
558 }
559
560 if self.writer.is_dirty() {
561 self.writer.sync_all().map_err(ProviderError::other)?;
562 }
563 self.synced = true;
564 Ok(())
565 }
566
567 #[instrument(
573 name = "StaticFileProviderRW::finalize",
574 level = "debug",
575 target = "providers::static_file",
576 skip_all
577 )]
578 pub fn finalize(&mut self) -> ProviderResult<()> {
579 if self.prune_on_commit.is_some() {
580 return Err(StaticFileWriterError::FinalizeWithPruneQueued.into());
581 }
582 if self.writer.is_dirty() {
583 if !self.synced {
584 self.sync_all()?;
587 }
588
589 self.writer.finalize().map_err(ProviderError::other)?;
590 self.update_index()?;
591 }
592 self.synced = false;
593 Ok(())
594 }
595
596 #[instrument(
598 name = "StaticFileProviderRW::commit",
599 level = "debug",
600 target = "providers::static_file",
601 skip_all
602 )]
603 pub fn commit(&mut self) -> ProviderResult<()> {
604 let start = Instant::now();
605
606 if let Some(strategy) = self.prune_on_commit.take() {
608 debug!(
609 target: "providers::static_file",
610 segment = ?self.writer.user_header().segment(),
611 "Pruning data on commit"
612 );
613 match strategy {
614 PruneStrategy::Headers { num_blocks } => self.prune_header_data(num_blocks)?,
615 PruneStrategy::Transactions { num_rows, last_block } => {
616 self.prune_transaction_data(num_rows, last_block)?
617 }
618 PruneStrategy::Receipts { num_rows, last_block } => {
619 self.prune_receipt_data(num_rows, last_block)?
620 }
621 PruneStrategy::TransactionSenders { num_rows, last_block } => {
622 self.prune_transaction_sender_data(num_rows, last_block)?
623 }
624 PruneStrategy::AccountChangeSets { last_block } => {
625 self.prune_account_changeset_data(last_block)?
626 }
627 PruneStrategy::StorageChangeSets { last_block } => {
628 self.prune_storage_changeset_data(last_block)?
629 }
630 }
631 }
632
633 self.flush_current_changeset_offset()?;
636 if let Some(writer) = &mut self.changeset_offsets {
637 writer.sync().map_err(ProviderError::other)?;
638 self.writer.user_header_mut().set_changeset_offsets_len(writer.len());
640 }
641
642 if self.writer.is_dirty() {
643 debug!(
644 target: "providers::static_file",
645 segment = ?self.writer.user_header().segment(),
646 "Committing writer to disk"
647 );
648
649 self.writer.commit().map_err(ProviderError::other)?;
651
652 if let Some(metrics) = &self.metrics {
653 metrics.record_segment_operation(
654 self.writer.user_header().segment(),
655 StaticFileProviderOperation::CommitWriter,
656 Some(start.elapsed()),
657 );
658 }
659
660 debug!(
661 target: "providers::static_file",
662 segment = ?self.writer.user_header().segment(),
663 path = ?self.data_path,
664 duration = ?start.elapsed(),
665 "Committed writer to disk"
666 );
667
668 self.update_index()?;
669 }
670
671 if self.apply_pending_truncation()? {
676 self.update_index()?;
677 }
678
679 Ok(())
680 }
681
682 fn apply_pending_truncation(&mut self) -> ProviderResult<bool> {
686 let mut truncated = self.writer.apply_pending_truncation().map_err(ProviderError::other)?;
687 if let Some(writer) = &mut self.changeset_offsets {
688 truncated |= writer.apply_pending_truncation().map_err(ProviderError::other)?;
689 }
690 Ok(truncated)
691 }
692
693 #[cfg(feature = "test-utils")]
697 pub fn commit_without_sync_all(&mut self) -> ProviderResult<()> {
698 let start = Instant::now();
699
700 debug!(
701 target: "providers::static_file",
702 segment = ?self.writer.user_header().segment(),
703 "Committing writer to disk (without sync)"
704 );
705
706 self.writer.commit_without_sync_all().map_err(ProviderError::other)?;
708
709 if let Some(metrics) = &self.metrics {
710 metrics.record_segment_operation(
711 self.writer.user_header().segment(),
712 StaticFileProviderOperation::CommitWriter,
713 Some(start.elapsed()),
714 );
715 }
716
717 debug!(
718 target: "providers::static_file",
719 segment = ?self.writer.user_header().segment(),
720 path = ?self.data_path,
721 duration = ?start.elapsed(),
722 "Committed writer to disk (without sync)"
723 );
724
725 self.update_index()?;
726
727 Ok(())
728 }
729
730 fn update_index(&self) -> ProviderResult<()> {
732 let segment = self.writer.user_header().segment();
733
734 let segment_max_block = self
742 .writer
743 .user_header()
744 .block_range()
745 .as_ref()
746 .map(|block_range| block_range.end())
747 .or_else(|| {
748 let expected_start = self.writer.user_header().expected_block_start();
749 if expected_start <= self.reader().genesis_block_number() {
750 return None;
751 }
752
753 let prev_block = expected_start - 1;
754 let prev_range = self.reader().find_fixed_range(segment, prev_block);
755 let prev_path = self.reader().directory().join(segment.filename(&prev_range));
756 (prev_path.exists() && prev_path.with_extension(CONFIG_FILE_EXTENSION).exists())
757 .then_some(prev_block)
758 });
759
760 self.reader().update_index(segment, segment_max_block)
761 }
762
763 pub fn ensure_at_block(&mut self, advance_to: BlockNumber) -> ProviderResult<()> {
768 let current_block = if let Some(current_block_number) = self.current_block_number() {
769 current_block_number
770 } else {
771 let first_block = self.writer.user_header().expected_block_start();
775 self.increment_block(first_block)?;
776 first_block
777 };
778
779 match current_block.cmp(&advance_to) {
780 Ordering::Less => {
781 for block in current_block + 1..=advance_to {
782 self.increment_block(block)?;
783 }
784 }
785 Ordering::Equal => {}
786 Ordering::Greater => {
787 return Err(ProviderError::UnexpectedStaticFileBlockNumber(
788 self.writer.user_header().segment(),
789 current_block,
790 advance_to,
791 ));
792 }
793 }
794
795 Ok(())
796 }
797
798 pub fn initialize_pruned_anchor(&mut self, block: BlockNumber) -> ProviderResult<()> {
802 let header = self.user_header();
803 if header.segment().is_headers() ||
804 header.block_range().is_some() ||
805 self.writer.rows() != 0
806 {
807 return Err(ProviderError::other(StaticFileWriterError::new(
808 "pruned anchor requires a fresh non-header segment",
809 )))
810 }
811 if !header.expected_block_range().contains(block) {
812 return Err(ProviderError::other(StaticFileWriterError::new(
813 "pruned anchor is outside the static file range",
814 )))
815 }
816 self.writer.user_header_mut().set_block_range(block, block);
817 if self.user_header().segment().is_change_based() {
818 self.writer.user_header_mut().set_changeset_offsets_len(1);
819 self.current_changeset_offset = Some(ChangesetOffset::new(0, 0));
820 }
821 Ok(())
822 }
823
824 pub fn increment_block(&mut self, expected_block_number: BlockNumber) -> ProviderResult<()> {
827 let segment = self.writer.user_header().segment();
828
829 self.check_next_block_number(expected_block_number)?;
830
831 let start = Instant::now();
832 if let Some(last_block) = self.writer.user_header().block_end() {
833 if last_block == self.writer.user_header().expected_block_end() {
835 self.commit()?;
837
838 let (writer, data_path) =
840 Self::open(segment, last_block + 1, self.reader.clone(), self.metrics.clone())?;
841 self.writer = writer;
842 self.data_path = data_path.clone();
843
844 if segment.is_change_based() {
846 let csoff_path = data_path.with_extension("csoff");
847 self.changeset_offsets = Some(
848 ChangesetOffsetWriter::new(&csoff_path, 0).map_err(ProviderError::other)?,
849 );
850 }
851
852 *self.writer.user_header_mut() = SegmentHeader::new(
853 self.reader().find_fixed_range(segment, last_block + 1),
854 None,
855 None,
856 segment,
857 );
858 }
859 }
860
861 self.writer.user_header_mut().increment_block();
862
863 if segment.is_change_based() {
865 if let Some(offset) = self.current_changeset_offset.take() &&
867 let Some(writer) = &mut self.changeset_offsets
868 {
869 writer.append(&offset).map_err(ProviderError::other)?;
870 }
871 let new_offset = self.writer.rows() as u64;
873 self.current_changeset_offset = Some(ChangesetOffset::new(new_offset, 0));
874 }
875
876 if let Some(metrics) = &self.metrics {
877 metrics.record_segment_operation(
878 segment,
879 StaticFileProviderOperation::IncrementBlock,
880 Some(start.elapsed()),
881 );
882 }
883
884 Ok(())
885 }
886
887 pub fn current_block_number(&self) -> Option<u64> {
889 self.writer.user_header().block_end()
890 }
891
892 pub fn next_block_number(&self) -> u64 {
894 self.writer
898 .user_header()
899 .block_end()
900 .map(|b| b + 1)
901 .unwrap_or_else(|| self.writer.user_header().expected_block_start())
902 }
903
904 fn check_next_block_number(&self, expected_block_number: u64) -> ProviderResult<()> {
907 let next_static_file_block = self.next_block_number();
908
909 if expected_block_number != next_static_file_block {
910 return Err(ProviderError::UnexpectedStaticFileBlockNumber(
911 self.writer.user_header().segment(),
912 expected_block_number,
913 next_static_file_block,
914 ))
915 }
916 Ok(())
917 }
918
919 fn truncate_changesets(&mut self, last_block: u64) -> ProviderResult<()> {
925 let segment = self.writer.user_header().segment();
926 debug_assert!(segment.is_change_based());
927
928 let current_block_end = self
930 .writer
931 .user_header()
932 .block_end()
933 .ok_or(ProviderError::MissingStaticFileBlock(segment, 0))?;
934
935 if current_block_end <= last_block {
937 return Ok(())
938 }
939
940 self.ensure_above_pruned_anchor(last_block)?;
941
942 let mut expected_block_start = self.writer.user_header().expected_block_start();
944 while last_block < expected_block_start && expected_block_start > 0 {
945 self.delete_current_and_open_previous()?;
946 expected_block_start = self.writer.user_header().expected_block_start();
947 }
948
949 let block_start = self.writer.user_header().block_start().unwrap_or(expected_block_start);
951
952 let blocks_to_keep =
954 if last_block >= block_start { last_block - block_start + 1 } else { 0 };
955
956 let csoff_path = self.data_path.with_extension("csoff");
958 let changeset_offsets_len = self.writer.user_header().changeset_offsets_len();
959
960 self.flush_current_changeset_offset()?;
962
963 let rows_to_keep = if blocks_to_keep == 0 {
964 0
965 } else if blocks_to_keep >= changeset_offsets_len {
966 self.writer.rows() as u64
968 } else {
969 let reader = ChangesetOffsetReader::new(&csoff_path, changeset_offsets_len)
973 .map_err(ProviderError::other)?;
974 if let Some(next_offset) = reader.get(blocks_to_keep).map_err(ProviderError::other)? {
975 next_offset.offset()
976 } else {
977 self.writer.rows() as u64
979 }
980 };
981
982 let total_rows = self.writer.rows() as u64;
983 let rows_to_delete = total_rows.saturating_sub(rows_to_keep);
984
985 if rows_to_delete > 0 {
986 let current_block_end = self
988 .writer
989 .user_header()
990 .block_end()
991 .ok_or(ProviderError::MissingStaticFileBlock(segment, 0))?;
992 let blocks_to_remove = current_block_end - last_block;
993
994 self.writer.user_header_mut().prune(blocks_to_remove);
996
997 self.writer
999 .prune_rows_deferred(rows_to_delete as usize)
1000 .map_err(ProviderError::other)?;
1001 }
1002
1003 self.writer.user_header_mut().set_block_range(block_start, last_block);
1005
1006 self.writer.user_header_mut().sync_changeset_offsets();
1008
1009 if let Some(writer) = &mut self.changeset_offsets {
1011 writer.truncate_deferred(blocks_to_keep);
1012 }
1013
1014 self.current_changeset_offset = None;
1016
1017 self.commit()?;
1019
1020 Ok(())
1021 }
1022
1023 fn truncate(&mut self, num_rows: u64, last_block: Option<u64>) -> ProviderResult<()> {
1031 if let Some(last_block) = last_block {
1032 self.ensure_above_pruned_anchor(last_block)?;
1033 }
1034 let mut remaining_rows = num_rows;
1035 let segment = self.writer.user_header().segment();
1036 while remaining_rows > 0 {
1037 let len = if segment.is_block_based() {
1038 self.writer.user_header().block_len().unwrap_or_default()
1039 } else {
1040 self.writer.user_header().tx_len().unwrap_or_default()
1041 };
1042
1043 if remaining_rows >= len {
1044 let block_start = self.writer.user_header().expected_block_start();
1047
1048 if block_start != 0 &&
1054 (segment.is_headers() || last_block.is_some_and(|b| b < block_start))
1055 {
1056 self.delete_current_and_open_previous()?;
1057 } else {
1058 self.writer.user_header_mut().prune(len);
1060 self.writer.prune_rows_deferred(len as usize).map_err(ProviderError::other)?;
1061 break
1062 }
1063
1064 remaining_rows -= len;
1065 } else {
1066 self.writer.user_header_mut().prune(remaining_rows);
1068
1069 self.writer
1071 .prune_rows_deferred(remaining_rows as usize)
1072 .map_err(ProviderError::other)?;
1073 remaining_rows = 0;
1074 }
1075 }
1076
1077 if let Some(last_block) = last_block {
1079 let mut expected_block_start = self.writer.user_header().expected_block_start();
1080
1081 while last_block < expected_block_start {
1085 self.delete_current_and_open_previous()?;
1086 expected_block_start = self.writer.user_header().expected_block_start();
1087 }
1088 let block_start =
1090 self.writer.user_header().block_start().unwrap_or(expected_block_start);
1091 self.writer.user_header_mut().set_block_range(block_start, last_block);
1092 }
1093
1094 self.commit()?;
1096
1097 Ok(())
1098 }
1099
1100 fn delete_current_and_open_previous(&mut self) -> Result<(), ProviderError> {
1103 let segment = self.user_header().segment();
1104 let current_path = self.data_path.clone();
1105 let (previous_writer, data_path) = Self::open(
1106 segment,
1107 self.writer.user_header().expected_block_start() - 1,
1108 self.reader.clone(),
1109 self.metrics.clone(),
1110 )?;
1111 self.writer = previous_writer;
1112 self.writer.set_dirty();
1113 self.data_path = data_path.clone();
1114
1115 if segment.is_change_based() {
1117 let csoff_path = current_path.with_extension("csoff");
1118 if csoff_path.exists() {
1119 std::fs::remove_file(&csoff_path).map_err(ProviderError::other)?;
1120 }
1121 let new_csoff_path = data_path.with_extension("csoff");
1123 let committed_len = self.writer.user_header().changeset_offsets_len();
1124 self.changeset_offsets = Some(
1125 ChangesetOffsetWriter::new(&new_csoff_path, committed_len)
1126 .map_err(ProviderError::other)?,
1127 );
1128 }
1129
1130 self.current_changeset_offset = None;
1132
1133 NippyJar::<SegmentHeader>::load(¤t_path)
1134 .map_err(ProviderError::other)?
1135 .delete()
1136 .map_err(ProviderError::other)?;
1137 Ok(())
1138 }
1139
1140 fn ensure_above_pruned_anchor(&self, last_block: BlockNumber) -> ProviderResult<()> {
1143 let header = self.writer.user_header();
1144 let lowest = self.reader().get_lowest_range_start(header.segment());
1145 let current = header
1148 .block_start()
1149 .filter(|start| lowest.is_none() || *start > header.expected_block_start());
1150 if let Some(anchor) = current.into_iter().chain(lowest).find(|start| last_block < *start) {
1151 return Err(StaticFileWriterError::UnwindBelowPrunedAnchor {
1152 segment: header.segment(),
1153 target: last_block,
1154 anchor,
1155 }
1156 .into())
1157 }
1158 Ok(())
1159 }
1160
1161 fn append_column<T: Compact>(&mut self, column: T) -> ProviderResult<()> {
1163 self.buf.clear();
1164 column.to_compact(&mut self.buf);
1165
1166 self.writer.append_column(Some(Ok(&self.buf))).map_err(ProviderError::other)?;
1167 Ok(())
1168 }
1169
1170 fn append_with_tx_number<V: Compact>(
1172 &mut self,
1173 tx_num: TxNumber,
1174 value: V,
1175 ) -> ProviderResult<()> {
1176 if let Some(range) = self.writer.user_header().tx_range() {
1177 let next_tx = range.end() + 1;
1178 if next_tx != tx_num {
1179 return Err(ProviderError::UnexpectedStaticFileTxNumber(
1180 self.writer.user_header().segment(),
1181 tx_num,
1182 next_tx,
1183 ))
1184 }
1185 self.writer.user_header_mut().increment_tx();
1186 } else {
1187 self.writer.user_header_mut().set_tx_range(tx_num, tx_num);
1188 }
1189
1190 self.append_column(value)?;
1191
1192 Ok(())
1193 }
1194
1195 fn append_change<V: Compact>(&mut self, change: &V) -> ProviderResult<()> {
1197 if let Some(ref mut offset) = self.current_changeset_offset {
1198 offset.increment_num_changes();
1199 }
1200 self.append_column(change)?;
1201 Ok(())
1202 }
1203
1204 pub fn append_header(&mut self, header: &N::BlockHeader, hash: &BlockHash) -> ProviderResult<()>
1209 where
1210 N::BlockHeader: Compact,
1211 {
1212 self.append_header_with_td(header, U256::ZERO, hash)
1213 }
1214
1215 pub fn append_header_with_td(
1220 &mut self,
1221 header: &N::BlockHeader,
1222 total_difficulty: U256,
1223 hash: &BlockHash,
1224 ) -> ProviderResult<()>
1225 where
1226 N::BlockHeader: Compact,
1227 {
1228 let start = Instant::now();
1229 self.ensure_no_queued_prune()?;
1230
1231 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Headers);
1232
1233 self.increment_block(header.number())?;
1234
1235 self.append_column(header)?;
1236 self.append_column(CompactU256::from(total_difficulty))?;
1237 self.append_column(hash)?;
1238
1239 if let Some(metrics) = &self.metrics {
1240 metrics.record_segment_operation(
1241 StaticFileSegment::Headers,
1242 StaticFileProviderOperation::Append,
1243 Some(start.elapsed()),
1244 );
1245 }
1246
1247 Ok(())
1248 }
1249
1250 pub fn append_header_direct(
1253 &mut self,
1254 header: &N::BlockHeader,
1255 total_difficulty: U256,
1256 hash: &BlockHash,
1257 ) -> ProviderResult<()>
1258 where
1259 N::BlockHeader: Compact,
1260 {
1261 let start = Instant::now();
1262 self.ensure_no_queued_prune()?;
1263
1264 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Headers);
1265
1266 self.append_column(header)?;
1267 self.append_column(CompactU256::from(total_difficulty))?;
1268 self.append_column(hash)?;
1269
1270 if let Some(metrics) = &self.metrics {
1271 metrics.record_segment_operation(
1272 StaticFileSegment::Headers,
1273 StaticFileProviderOperation::Append,
1274 Some(start.elapsed()),
1275 );
1276 }
1277
1278 Ok(())
1279 }
1280
1281 pub fn append_transaction(&mut self, tx_num: TxNumber, tx: &N::SignedTx) -> ProviderResult<()>
1286 where
1287 N::SignedTx: Compact,
1288 {
1289 let start = Instant::now();
1290 self.ensure_no_queued_prune()?;
1291
1292 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Transactions);
1293 self.append_with_tx_number(tx_num, tx)?;
1294
1295 if let Some(metrics) = &self.metrics {
1296 metrics.record_segment_operation(
1297 StaticFileSegment::Transactions,
1298 StaticFileProviderOperation::Append,
1299 Some(start.elapsed()),
1300 );
1301 }
1302
1303 Ok(())
1304 }
1305
1306 pub fn append_receipt(&mut self, tx_num: TxNumber, receipt: &N::Receipt) -> ProviderResult<()>
1311 where
1312 N::Receipt: Compact,
1313 {
1314 let start = Instant::now();
1315 self.ensure_no_queued_prune()?;
1316
1317 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Receipts);
1318 self.append_with_tx_number(tx_num, receipt)?;
1319
1320 if let Some(metrics) = &self.metrics {
1321 metrics.record_segment_operation(
1322 StaticFileSegment::Receipts,
1323 StaticFileProviderOperation::Append,
1324 Some(start.elapsed()),
1325 );
1326 }
1327
1328 Ok(())
1329 }
1330
1331 pub fn append_receipts<I, R>(&mut self, receipts: I) -> ProviderResult<()>
1333 where
1334 I: Iterator<Item = Result<(TxNumber, R), ProviderError>>,
1335 R: Borrow<N::Receipt>,
1336 N::Receipt: Compact,
1337 {
1338 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Receipts);
1339
1340 let mut receipts_iter = receipts.into_iter().peekable();
1341 if receipts_iter.peek().is_none() {
1343 return Ok(());
1344 }
1345
1346 let start = Instant::now();
1347 self.ensure_no_queued_prune()?;
1348
1349 let mut count: u64 = 0;
1351
1352 for receipt_result in receipts_iter {
1353 let (tx_num, receipt) = receipt_result?;
1354 self.append_with_tx_number(tx_num, receipt.borrow())?;
1355 count += 1;
1356 }
1357
1358 if let Some(metrics) = &self.metrics {
1359 metrics.record_segment_operations(
1360 StaticFileSegment::Receipts,
1361 StaticFileProviderOperation::Append,
1362 count,
1363 Some(start.elapsed()),
1364 );
1365 }
1366
1367 Ok(())
1368 }
1369
1370 pub fn append_transaction_sender(
1375 &mut self,
1376 tx_num: TxNumber,
1377 sender: &alloy_primitives::Address,
1378 ) -> ProviderResult<()> {
1379 let start = Instant::now();
1380 self.ensure_no_queued_prune()?;
1381
1382 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::TransactionSenders);
1383 self.append_with_tx_number(tx_num, sender)?;
1384
1385 if let Some(metrics) = &self.metrics {
1386 metrics.record_segment_operation(
1387 StaticFileSegment::TransactionSenders,
1388 StaticFileProviderOperation::Append,
1389 Some(start.elapsed()),
1390 );
1391 }
1392
1393 Ok(())
1394 }
1395
1396 pub fn append_transaction_senders<I>(&mut self, senders: I) -> ProviderResult<()>
1398 where
1399 I: Iterator<Item = (TxNumber, alloy_primitives::Address)>,
1400 {
1401 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::TransactionSenders);
1402
1403 let mut senders_iter = senders.into_iter().peekable();
1404 if senders_iter.peek().is_none() {
1406 return Ok(());
1407 }
1408
1409 let start = Instant::now();
1410 self.ensure_no_queued_prune()?;
1411
1412 let mut count: u64 = 0;
1414 for (tx_num, sender) in senders_iter {
1415 self.append_with_tx_number(tx_num, sender)?;
1416 count += 1;
1417 }
1418
1419 if let Some(metrics) = &self.metrics {
1420 metrics.record_segment_operations(
1421 StaticFileSegment::TransactionSenders,
1422 StaticFileProviderOperation::Append,
1423 count,
1424 Some(start.elapsed()),
1425 );
1426 }
1427
1428 Ok(())
1429 }
1430
1431 pub fn append_account_changeset(
1437 &mut self,
1438 mut changeset: Vec<AccountBeforeTx>,
1439 block_number: u64,
1440 ) -> ProviderResult<()> {
1441 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::AccountChangeSets);
1442 let start = Instant::now();
1443
1444 self.increment_block(block_number)?;
1445 self.ensure_no_queued_prune()?;
1446
1447 changeset.sort_by_key(|change| change.address);
1449
1450 let mut count: u64 = 0;
1451
1452 for change in changeset {
1453 self.append_change(&change)?;
1454 count += 1;
1455 }
1456
1457 if let Some(metrics) = &self.metrics {
1458 metrics.record_segment_operations(
1459 StaticFileSegment::AccountChangeSets,
1460 StaticFileProviderOperation::Append,
1461 count,
1462 Some(start.elapsed()),
1463 );
1464 }
1465
1466 Ok(())
1467 }
1468
1469 pub fn begin_account_changeset(&mut self, block_number: u64) -> ProviderResult<()> {
1474 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::AccountChangeSets);
1475
1476 self.increment_block(block_number)?;
1477 self.ensure_no_queued_prune()
1478 }
1479
1480 pub fn append_account_changeset_entry(
1484 &mut self,
1485 change: AccountBeforeTx,
1486 ) -> ProviderResult<()> {
1487 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::AccountChangeSets);
1488 if self.current_changeset_offset.is_none() {
1489 return Err(ProviderError::other(StaticFileWriterError::new(
1490 "account changeset stream must be started before appending entries",
1491 )))
1492 }
1493
1494 self.append_change(&change)
1495 }
1496
1497 pub fn append_storage_changeset(
1501 &mut self,
1502 mut changeset: Vec<StorageBeforeTx>,
1503 block_number: u64,
1504 ) -> ProviderResult<()> {
1505 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::StorageChangeSets);
1506 let start = Instant::now();
1507
1508 self.increment_block(block_number)?;
1509 self.ensure_no_queued_prune()?;
1510
1511 changeset.sort_by_key(|change| (change.address, change.key));
1513
1514 let mut count: u64 = 0;
1515 for change in changeset {
1516 self.append_change(&change)?;
1517 count += 1;
1518 }
1519
1520 if let Some(metrics) = &self.metrics {
1521 metrics.record_segment_operations(
1522 StaticFileSegment::StorageChangeSets,
1523 StaticFileProviderOperation::Append,
1524 count,
1525 Some(start.elapsed()),
1526 );
1527 }
1528
1529 Ok(())
1530 }
1531
1532 pub fn begin_storage_changeset(&mut self, block_number: u64) -> ProviderResult<()> {
1538 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::StorageChangeSets);
1539
1540 self.increment_block(block_number)?;
1541 self.ensure_no_queued_prune()
1542 }
1543
1544 pub fn append_storage_changeset_entry(
1548 &mut self,
1549 change: StorageBeforeTx,
1550 ) -> ProviderResult<()> {
1551 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::StorageChangeSets);
1552 if self.current_changeset_offset.is_none() {
1553 return Err(ProviderError::other(StaticFileWriterError::new(
1554 "storage changeset stream must be started before appending entries",
1555 )))
1556 }
1557
1558 self.append_change(&change)
1559 }
1560
1561 pub fn prune_transactions(
1565 &mut self,
1566 to_delete: u64,
1567 last_block: BlockNumber,
1568 ) -> ProviderResult<()> {
1569 debug_assert_eq!(self.writer.user_header().segment(), StaticFileSegment::Transactions);
1570 self.queue_prune(PruneStrategy::Transactions { num_rows: to_delete, last_block })
1571 }
1572
1573 pub fn prune_receipts(
1577 &mut self,
1578 to_delete: u64,
1579 last_block: BlockNumber,
1580 ) -> ProviderResult<()> {
1581 debug_assert_eq!(self.writer.user_header().segment(), StaticFileSegment::Receipts);
1582 self.queue_prune(PruneStrategy::Receipts { num_rows: to_delete, last_block })
1583 }
1584
1585 pub fn prune_transaction_senders(
1589 &mut self,
1590 to_delete: u64,
1591 last_block: BlockNumber,
1592 ) -> ProviderResult<()> {
1593 debug_assert_eq!(
1594 self.writer.user_header().segment(),
1595 StaticFileSegment::TransactionSenders
1596 );
1597 self.queue_prune(PruneStrategy::TransactionSenders { num_rows: to_delete, last_block })
1598 }
1599
1600 pub fn prune_headers(&mut self, to_delete: u64) -> ProviderResult<()> {
1602 debug_assert_eq!(self.writer.user_header().segment(), StaticFileSegment::Headers);
1603 self.queue_prune(PruneStrategy::Headers { num_blocks: to_delete })
1604 }
1605
1606 pub fn prune_account_changesets(&mut self, last_block: u64) -> ProviderResult<()> {
1608 debug_assert_eq!(self.writer.user_header().segment(), StaticFileSegment::AccountChangeSets);
1609 self.queue_prune(PruneStrategy::AccountChangeSets { last_block })
1610 }
1611
1612 pub fn prune_storage_changesets(&mut self, last_block: u64) -> ProviderResult<()> {
1614 debug_assert_eq!(self.writer.user_header().segment(), StaticFileSegment::StorageChangeSets);
1615 self.queue_prune(PruneStrategy::StorageChangeSets { last_block })
1616 }
1617
1618 fn queue_prune(&mut self, strategy: PruneStrategy) -> ProviderResult<()> {
1620 self.ensure_no_queued_prune()?;
1621 match strategy {
1623 PruneStrategy::Transactions { last_block, .. } |
1624 PruneStrategy::Receipts { last_block, .. } |
1625 PruneStrategy::TransactionSenders { last_block, .. } |
1626 PruneStrategy::AccountChangeSets { last_block } |
1627 PruneStrategy::StorageChangeSets { last_block } => {
1628 self.ensure_above_pruned_anchor(last_block)?;
1629 }
1630 PruneStrategy::Headers { .. } => {}
1631 }
1632 self.prune_on_commit = Some(strategy);
1633 Ok(())
1634 }
1635
1636 fn ensure_no_queued_prune(&self) -> ProviderResult<()> {
1638 if self.prune_on_commit.is_some() {
1639 return Err(ProviderError::other(StaticFileWriterError::new(
1640 "Pruning should be committed before appending or pruning more data",
1641 )));
1642 }
1643 Ok(())
1644 }
1645
1646 fn prune_transaction_data(
1648 &mut self,
1649 to_delete: u64,
1650 last_block: BlockNumber,
1651 ) -> ProviderResult<()> {
1652 let start = Instant::now();
1653
1654 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Transactions);
1655
1656 self.truncate(to_delete, Some(last_block))?;
1657
1658 if let Some(metrics) = &self.metrics {
1659 metrics.record_segment_operation(
1660 StaticFileSegment::Transactions,
1661 StaticFileProviderOperation::Prune,
1662 Some(start.elapsed()),
1663 );
1664 }
1665
1666 Ok(())
1667 }
1668
1669 fn prune_account_changeset_data(&mut self, last_block: BlockNumber) -> ProviderResult<()> {
1671 let start = Instant::now();
1672
1673 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::AccountChangeSets);
1674
1675 self.truncate_changesets(last_block)?;
1676
1677 if let Some(metrics) = &self.metrics {
1678 metrics.record_segment_operation(
1679 StaticFileSegment::AccountChangeSets,
1680 StaticFileProviderOperation::Prune,
1681 Some(start.elapsed()),
1682 );
1683 }
1684
1685 Ok(())
1686 }
1687
1688 fn prune_storage_changeset_data(&mut self, last_block: BlockNumber) -> ProviderResult<()> {
1690 let start = Instant::now();
1691
1692 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::StorageChangeSets);
1693
1694 self.truncate_changesets(last_block)?;
1695
1696 if let Some(metrics) = &self.metrics {
1697 metrics.record_segment_operation(
1698 StaticFileSegment::StorageChangeSets,
1699 StaticFileProviderOperation::Prune,
1700 Some(start.elapsed()),
1701 );
1702 }
1703
1704 Ok(())
1705 }
1706
1707 fn prune_receipt_data(
1709 &mut self,
1710 to_delete: u64,
1711 last_block: BlockNumber,
1712 ) -> ProviderResult<()> {
1713 let start = Instant::now();
1714
1715 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Receipts);
1716
1717 self.truncate(to_delete, Some(last_block))?;
1718
1719 if let Some(metrics) = &self.metrics {
1720 metrics.record_segment_operation(
1721 StaticFileSegment::Receipts,
1722 StaticFileProviderOperation::Prune,
1723 Some(start.elapsed()),
1724 );
1725 }
1726
1727 Ok(())
1728 }
1729
1730 fn prune_transaction_sender_data(
1732 &mut self,
1733 to_delete: u64,
1734 last_block: BlockNumber,
1735 ) -> ProviderResult<()> {
1736 let start = Instant::now();
1737
1738 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::TransactionSenders);
1739
1740 self.truncate(to_delete, Some(last_block))?;
1741
1742 if let Some(metrics) = &self.metrics {
1743 metrics.record_segment_operation(
1744 StaticFileSegment::TransactionSenders,
1745 StaticFileProviderOperation::Prune,
1746 Some(start.elapsed()),
1747 );
1748 }
1749
1750 Ok(())
1751 }
1752
1753 fn prune_header_data(&mut self, to_delete: u64) -> ProviderResult<()> {
1755 let start = Instant::now();
1756
1757 debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Headers);
1758
1759 self.truncate(to_delete, None)?;
1760
1761 if let Some(metrics) = &self.metrics {
1762 metrics.record_segment_operation(
1763 StaticFileSegment::Headers,
1764 StaticFileProviderOperation::Prune,
1765 Some(start.elapsed()),
1766 );
1767 }
1768
1769 Ok(())
1770 }
1771
1772 pub fn reader(&self) -> StaticFileProvider<N> {
1774 Self::upgrade_provider_to_strong_reference(&self.reader)
1775 }
1776
1777 fn upgrade_provider_to_strong_reference(
1786 provider: &Weak<StaticFileProviderInner<N>>,
1787 ) -> StaticFileProvider<N> {
1788 provider.upgrade().map(StaticFileProvider).expect("StaticFileProvider is dropped")
1789 }
1790
1791 pub const fn user_header(&self) -> &SegmentHeader {
1793 self.writer.user_header()
1794 }
1795
1796 pub const fn user_header_mut(&mut self) -> &mut SegmentHeader {
1798 self.writer.user_header_mut()
1799 }
1800
1801 #[cfg(any(test, feature = "test-utils"))]
1803 pub const fn set_block_range(&mut self, block_range: std::ops::RangeInclusive<BlockNumber>) {
1804 self.writer.user_header_mut().set_block_range(*block_range.start(), *block_range.end())
1805 }
1806
1807 #[cfg(any(test, feature = "test-utils"))]
1809 pub const fn inner(&mut self) -> &mut NippyJarWriter<SegmentHeader> {
1810 &mut self.writer
1811 }
1812}
1813
1814fn create_jar(
1815 segment: StaticFileSegment,
1816 path: &Path,
1817 expected_block_range: SegmentRangeInclusive,
1818) -> NippyJar<SegmentHeader> {
1819 let mut jar = NippyJar::new(
1820 segment.columns(),
1821 path,
1822 SegmentHeader::new(expected_block_range, None, None, segment),
1823 );
1824
1825 if segment.is_headers() {
1828 jar = jar.with_lz4();
1829 }
1830
1831 jar
1832}