mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-19 03:04:27 -05:00
fix(cli): delete all static files when PruneModes::Full is configured (#21647)
This commit is contained in:
@@ -54,6 +54,38 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Deletes ALL static file jars for a given segment.
|
||||
///
|
||||
/// This is used for `PruneMode::Full` where all data should be removed, including the highest jar.
|
||||
/// Unlike [`prune_static_files`], this does not preserve the most recent jar.
|
||||
pub(crate) fn delete_static_files_segment<Provider>(
|
||||
provider: &Provider,
|
||||
input: PruneInput,
|
||||
segment: StaticFileSegment,
|
||||
) -> Result<SegmentOutput, PrunerError>
|
||||
where
|
||||
Provider: StaticFileProviderFactory,
|
||||
{
|
||||
let deleted_headers = provider.static_file_provider().delete_segment(segment)?;
|
||||
|
||||
if deleted_headers.is_empty() {
|
||||
return Ok(SegmentOutput::done())
|
||||
}
|
||||
|
||||
let tx_ranges = deleted_headers.iter().filter_map(|header| header.tx_range());
|
||||
|
||||
let pruned = tx_ranges.clone().map(|range| range.len()).sum::<u64>() as usize;
|
||||
|
||||
Ok(SegmentOutput {
|
||||
progress: PruneProgress::Finished,
|
||||
pruned,
|
||||
checkpoint: Some(SegmentOutputCheckpoint {
|
||||
block_number: Some(input.to_block),
|
||||
tx_number: tx_ranges.map(|range| range.end()).max(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// A segment represents a pruning of some portion of the data.
|
||||
///
|
||||
/// Segments are called from [`Pruner`](crate::Pruner) with the following lifecycle:
|
||||
|
||||
@@ -49,6 +49,16 @@ where
|
||||
fn prune(&self, provider: &Provider, input: PruneInput) -> Result<SegmentOutput, PrunerError> {
|
||||
if EitherWriterDestination::senders(provider).is_static_file() {
|
||||
debug!(target: "pruner", "Pruning transaction senders from static files.");
|
||||
|
||||
if self.mode.is_full() {
|
||||
debug!(target: "pruner", "PruneMode::Full: deleting all transaction senders static files.");
|
||||
return segments::delete_static_files_segment(
|
||||
provider,
|
||||
input,
|
||||
StaticFileSegment::TransactionSenders,
|
||||
)
|
||||
}
|
||||
|
||||
return segments::prune_static_files(
|
||||
provider,
|
||||
input,
|
||||
|
||||
@@ -37,14 +37,15 @@ use reth_primitives_traits::{
|
||||
AlloyBlockHeader as _, BlockBody as _, RecoveredBlock, SealedHeader, SignedTransaction,
|
||||
StorageEntry,
|
||||
};
|
||||
use reth_prune_types::PruneSegment;
|
||||
use reth_stages_types::PipelineTarget;
|
||||
use reth_static_file_types::{
|
||||
find_fixed_range, HighestStaticFiles, SegmentHeader, SegmentRangeInclusive, StaticFileMap,
|
||||
StaticFileSegment, DEFAULT_BLOCKS_PER_STATIC_FILE,
|
||||
};
|
||||
use reth_storage_api::{
|
||||
BlockBodyIndicesProvider, ChangeSetReader, DBProvider, StorageChangeSetReader,
|
||||
StorageSettingsCache,
|
||||
BlockBodyIndicesProvider, ChangeSetReader, DBProvider, PruneCheckpointReader,
|
||||
StorageChangeSetReader, StorageSettingsCache,
|
||||
};
|
||||
use reth_storage_errors::provider::{ProviderError, ProviderResult, StaticFileWriterError};
|
||||
use std::{
|
||||
@@ -985,6 +986,34 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
/// Deletes ALL static file jars for the given segment, including the highest one.
|
||||
///
|
||||
/// CAUTION: destructive. Deletes all files on disk for this segment.
|
||||
///
|
||||
/// This is used for `PruneMode::Full` where all data should be removed.
|
||||
///
|
||||
/// Returns a list of `SegmentHeader`s from the deleted jars.
|
||||
pub fn delete_segment(&self, segment: StaticFileSegment) -> ProviderResult<Vec<SegmentHeader>> {
|
||||
let mut deleted_headers = Vec::new();
|
||||
|
||||
while let Some(block_height) = self.get_highest_static_file_block(segment) {
|
||||
debug!(
|
||||
target: "provider::static_file",
|
||||
?segment,
|
||||
?block_height,
|
||||
"Deleting static file jar"
|
||||
);
|
||||
|
||||
let header = self.delete_jar(segment, block_height).inspect_err(|err| {
|
||||
warn!(target: "provider::static_file", ?segment, %block_height, ?err, "Failed to delete static file jar")
|
||||
})?;
|
||||
|
||||
deleted_headers.push(header);
|
||||
}
|
||||
|
||||
Ok(deleted_headers)
|
||||
}
|
||||
|
||||
/// Given a segment and block range it returns a cached
|
||||
/// [`StaticFileJarProvider`]. TODO(joshie): we should check the size and pop N if there's too
|
||||
/// many.
|
||||
@@ -1293,6 +1322,7 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
Provider: DBProvider
|
||||
+ BlockReader
|
||||
+ StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ ChainSpecProvider
|
||||
+ StorageSettingsCache,
|
||||
N: NodePrimitives<Receipt: Value, BlockHeader: Value, SignedTx: Value>,
|
||||
@@ -1452,7 +1482,7 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
/// database checkpoints or prune against them.
|
||||
pub fn check_file_consistency<Provider>(&self, provider: &Provider) -> ProviderResult<()>
|
||||
where
|
||||
Provider: DBProvider + ChainSpecProvider + StorageSettingsCache,
|
||||
Provider: DBProvider + ChainSpecProvider + StorageSettingsCache + PruneCheckpointReader,
|
||||
{
|
||||
info!(target: "reth::cli", "Healing static file inconsistencies.");
|
||||
|
||||
@@ -1469,7 +1499,7 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
provider: &'a Provider,
|
||||
) -> impl Iterator<Item = StaticFileSegment> + 'a
|
||||
where
|
||||
Provider: DBProvider + ChainSpecProvider + StorageSettingsCache,
|
||||
Provider: DBProvider + ChainSpecProvider + StorageSettingsCache + PruneCheckpointReader,
|
||||
{
|
||||
StaticFileSegment::iter()
|
||||
.filter(move |segment| self.should_check_segment(provider, *segment))
|
||||
@@ -1481,7 +1511,7 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
segment: StaticFileSegment,
|
||||
) -> bool
|
||||
where
|
||||
Provider: DBProvider + ChainSpecProvider + StorageSettingsCache,
|
||||
Provider: DBProvider + ChainSpecProvider + StorageSettingsCache + PruneCheckpointReader,
|
||||
{
|
||||
match segment {
|
||||
StaticFileSegment::Headers | StaticFileSegment::Transactions => true,
|
||||
@@ -1506,7 +1536,17 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
true
|
||||
}
|
||||
StaticFileSegment::TransactionSenders => {
|
||||
!EitherWriterDestination::senders(provider).is_database()
|
||||
if EitherWriterDestination::senders(provider).is_database() {
|
||||
debug!(target: "reth::providers::static_file", ?segment, "Skipping senders segment: senders stored in database");
|
||||
return false;
|
||||
}
|
||||
|
||||
if Self::is_segment_fully_pruned(provider, PruneSegment::SenderRecovery) {
|
||||
debug!(target: "reth::providers::static_file", ?segment, "Skipping senders segment: fully pruned");
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
if EitherWriter::account_changesets_destination(provider).is_database() {
|
||||
@@ -1525,6 +1565,20 @@ impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the given prune segment has a checkpoint with
|
||||
/// [`reth_prune_types::PruneMode::Full`], indicating all data for this segment has been
|
||||
/// intentionally deleted.
|
||||
fn is_segment_fully_pruned<Provider>(provider: &Provider, segment: PruneSegment) -> bool
|
||||
where
|
||||
Provider: PruneCheckpointReader,
|
||||
{
|
||||
provider
|
||||
.get_prune_checkpoint(segment)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some_and(|checkpoint| checkpoint.prune_mode.is_full())
|
||||
}
|
||||
|
||||
/// Checks consistency of the latest static file segment and throws an error if at fault.
|
||||
/// Read-only.
|
||||
pub fn check_segment_consistency(&self, segment: StaticFileSegment) -> ProviderResult<()> {
|
||||
|
||||
Reference in New Issue
Block a user