fix(prune): add minimum 64 block retention for receipts and bodies (#21520)

This commit is contained in:
joshieDo
2026-01-28 18:10:07 +00:00
committed by GitHub
parent effa0ab4c7
commit 013dfdf8c8
9 changed files with 50 additions and 37 deletions

View File

@@ -5,7 +5,9 @@ use alloy_primitives::{Address, BlockNumber};
use clap::{builder::RangedU64ValueParser, Args};
use reth_chainspec::EthereumHardforks;
use reth_config::config::PruneConfig;
use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_PRUNING_DISTANCE};
use reth_prune_types::{
PruneMode, PruneModes, ReceiptsLogPruneConfig, MINIMUM_UNWIND_SAFE_DISTANCE,
};
use std::{collections::BTreeMap, ops::Not, sync::OnceLock};
/// Global static pruning defaults
@@ -68,9 +70,9 @@ impl Default for DefaultPruningValues {
full_prune_modes: PruneModes {
sender_recovery: Some(PruneMode::Full),
transaction_lookup: None,
receipts: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
account_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
receipts: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)),
account_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)),
storage_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)),
// This field is ignored when full_bodies_history_use_pre_merge is true
bodies_history: None,
receipts_log_filter: Default::default(),
@@ -80,9 +82,9 @@ impl Default for DefaultPruningValues {
sender_recovery: Some(PruneMode::Full),
transaction_lookup: Some(PruneMode::Full),
receipts: Some(PruneMode::Full),
account_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
storage_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
bodies_history: Some(PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)),
account_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)),
storage_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)),
bodies_history: Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)),
receipts_log_filter: Default::default(),
},
}
@@ -93,7 +95,8 @@ impl Default for DefaultPruningValues {
#[derive(Debug, Clone, Args, PartialEq, Eq, Default)]
#[command(next_help_heading = "Pruning")]
pub struct PruningArgs {
/// Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored.
/// Run full node. Only the most recent [`MINIMUM_UNWIND_SAFE_DISTANCE`] block states are
/// stored.
#[arg(long, default_value_t = false, conflicts_with = "minimal")]
pub full: bool,

View File

@@ -11,7 +11,7 @@ use reth_provider::{
};
use reth_prune_types::{
PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, ReceiptsLogPruneConfig, SegmentOutput,
MINIMUM_PRUNING_DISTANCE,
MINIMUM_UNWIND_SAFE_DISTANCE,
};
use tracing::{instrument, trace};
#[derive(Debug)]
@@ -49,8 +49,8 @@ where
fn prune(&self, provider: &Provider, input: PruneInput) -> Result<SegmentOutput, PrunerError> {
// Contract log filtering removes every receipt possible except the ones in the list. So,
// for the other receipts it's as if they had a `PruneMode::Distance()` of
// `MINIMUM_PRUNING_DISTANCE`.
let to_block = PruneMode::Distance(MINIMUM_PRUNING_DISTANCE)
// `MINIMUM_UNWIND_SAFE_DISTANCE`.
let to_block = PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE)
.prune_target_block(input.to_block, PruneSegment::ContractLogs, PrunePurpose::User)?
.map(|(bn, _)| bn)
.unwrap_or_default();

View File

@@ -30,7 +30,9 @@ pub use pruner::{
SegmentOutputCheckpoint,
};
pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError};
pub use target::{PruneModes, UnwindTargetPrunedError, MINIMUM_PRUNING_DISTANCE};
pub use target::{
PruneModes, UnwindTargetPrunedError, MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE,
};
/// Configuration for pruning receipts not associated with logs emitted by the specified contracts.
#[derive(Debug, Clone, PartialEq, Eq, Default)]

View File

@@ -41,8 +41,12 @@ impl PruneMode {
segment: PruneSegment,
purpose: PrunePurpose,
) -> Result<Option<(BlockNumber, Self)>, PruneSegmentError> {
let min_blocks = segment.min_blocks();
let result = match self {
Self::Full if segment.min_blocks() == 0 => Some((tip, *self)),
Self::Full if min_blocks == 0 => Some((tip, *self)),
// For segments with min_blocks > 0, Full mode behaves like Distance(min_blocks)
Self::Full if min_blocks <= tip => Some((tip - min_blocks, *self)),
Self::Full => None, // Nothing to prune yet
Self::Distance(distance) if *distance > tip => None, // Nothing to prune yet
Self::Distance(distance) if *distance >= segment.min_blocks() => {
Some((tip - distance, *self))
@@ -84,9 +88,7 @@ impl PruneMode {
#[cfg(test)]
mod tests {
use crate::{
PruneMode, PrunePurpose, PruneSegment, PruneSegmentError, MINIMUM_PRUNING_DISTANCE,
};
use crate::{PruneMode, PrunePurpose, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE};
use assert_matches::assert_matches;
use serde::Deserialize;
@@ -96,8 +98,8 @@ mod tests {
let segment = PruneSegment::AccountHistory;
let tests = vec![
// MINIMUM_PRUNING_DISTANCE makes this impossible
(PruneMode::Full, Err(PruneSegmentError::Configuration(segment))),
// Full mode with min_blocks > 0 behaves like Distance(min_blocks)
(PruneMode::Full, Ok(Some(tip - segment.min_blocks()))),
// Nothing to prune
(PruneMode::Distance(tip + 1), Ok(None)),
(
@@ -107,12 +109,12 @@ mod tests {
// Nothing to prune
(PruneMode::Before(tip + 1), Ok(None)),
(
PruneMode::Before(tip - MINIMUM_PRUNING_DISTANCE),
Ok(Some(tip - MINIMUM_PRUNING_DISTANCE - 1)),
PruneMode::Before(tip - MINIMUM_UNWIND_SAFE_DISTANCE),
Ok(Some(tip - MINIMUM_UNWIND_SAFE_DISTANCE - 1)),
),
(
PruneMode::Before(tip - MINIMUM_PRUNING_DISTANCE - 1),
Ok(Some(tip - MINIMUM_PRUNING_DISTANCE - 2)),
PruneMode::Before(tip - MINIMUM_UNWIND_SAFE_DISTANCE - 1),
Ok(Some(tip - MINIMUM_UNWIND_SAFE_DISTANCE - 2)),
),
// Nothing to prune
(PruneMode::Before(tip - 1), Ok(None)),
@@ -146,13 +148,13 @@ mod tests {
let tests = vec![
(PruneMode::Distance(tip + 1), 1, !should_prune),
(
PruneMode::Distance(MINIMUM_PRUNING_DISTANCE + 1),
tip - MINIMUM_PRUNING_DISTANCE - 1,
PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE + 1),
tip - MINIMUM_UNWIND_SAFE_DISTANCE - 1,
!should_prune,
),
(
PruneMode::Distance(MINIMUM_PRUNING_DISTANCE + 1),
tip - MINIMUM_PRUNING_DISTANCE - 2,
PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE + 1),
tip - MINIMUM_UNWIND_SAFE_DISTANCE - 2,
should_prune,
),
(PruneMode::Before(tip + 1), 1, should_prune),

View File

@@ -1,6 +1,6 @@
#![allow(deprecated)] // necessary to all defining deprecated `PruneSegment` variants
use crate::MINIMUM_PRUNING_DISTANCE;
use crate::{MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE};
use derive_more::Display;
use strum::{EnumIter, IntoEnumIterator};
use thiserror::Error;
@@ -65,9 +65,10 @@ impl PruneSegment {
/// Returns minimum number of blocks to keep in the database for this segment.
pub const fn min_blocks(&self) -> u64 {
match self {
Self::SenderRecovery | Self::TransactionLookup | Self::Receipts | Self::Bodies => 0,
Self::SenderRecovery | Self::TransactionLookup => 0,
Self::Receipts | Self::Bodies => MINIMUM_DISTANCE,
Self::ContractLogs | Self::AccountHistory | Self::StorageHistory => {
MINIMUM_PRUNING_DISTANCE
MINIMUM_UNWIND_SAFE_DISTANCE
}
#[expect(deprecated)]
#[expect(clippy::match_same_arms)]

View File

@@ -9,7 +9,12 @@ use crate::{PruneCheckpoint, PruneMode, PruneSegment, ReceiptsLogPruneConfig};
/// consensus protocol.
/// 2. Another 10k blocks to have a room for maneuver in case when things go wrong and a manual
/// unwind is required.
pub const MINIMUM_PRUNING_DISTANCE: u64 = 32 * 2 + 10_000;
pub const MINIMUM_UNWIND_SAFE_DISTANCE: u64 = 32 * 2 + 10_000;
/// Minimum blocks to retain for receipts and bodies to ensure reorg safety.
/// This prevents pruning data that may be needed when handling chain reorganizations,
/// specifically when `canonical_block_by_hash` needs to reconstruct `ExecutedBlock` from disk.
pub const MINIMUM_DISTANCE: u64 = 64;
/// Type of history that can be pruned
#[derive(Debug, Error, PartialEq, Eq, Clone)]
@@ -56,7 +61,7 @@ pub struct PruneModes {
any(test, feature = "serde"),
serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_UNWIND_SAFE_DISTANCE, _>"
)
)]
pub account_history: Option<PruneMode>,
@@ -65,7 +70,7 @@ pub struct PruneModes {
any(test, feature = "serde"),
serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_PRUNING_DISTANCE, _>"
deserialize_with = "deserialize_opt_prune_mode_with_min_blocks::<MINIMUM_UNWIND_SAFE_DISTANCE, _>"
)
)]
pub storage_history: Option<PruneMode>,

View File

@@ -54,7 +54,7 @@ use reth_primitives_traits::{
Account, Block as _, BlockBody as _, Bytecode, RecoveredBlock, SealedHeader, StorageEntry,
};
use reth_prune_types::{
PruneCheckpoint, PruneMode, PruneModes, PruneSegment, MINIMUM_PRUNING_DISTANCE,
PruneCheckpoint, PruneMode, PruneModes, PruneSegment, MINIMUM_UNWIND_SAFE_DISTANCE,
};
use reth_stages_types::{StageCheckpoint, StageId};
use reth_static_file_types::StaticFileSegment;
@@ -368,7 +368,7 @@ impl<TX: DbTxMut, N: NodeTypes> DatabaseProvider<TX, N> {
changeset_cache,
pending_rocksdb_batches: Default::default(),
commit_order,
minimum_pruning_distance: MINIMUM_PRUNING_DISTANCE,
minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
metrics: metrics::DatabaseProviderMetrics::default(),
}
}
@@ -958,7 +958,7 @@ impl<TX: DbTx + 'static, N: NodeTypesForProvider> DatabaseProvider<TX, N> {
changeset_cache,
pending_rocksdb_batches: Default::default(),
commit_order: CommitOrder::Normal,
minimum_pruning_distance: MINIMUM_PRUNING_DISTANCE,
minimum_pruning_distance: MINIMUM_UNWIND_SAFE_DISTANCE,
metrics: metrics::DatabaseProviderMetrics::default(),
}
}

View File

@@ -832,7 +832,7 @@ Dev testnet:
Pruning:
--full
Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored
Run full node. Only the most recent [`MINIMUM_UNWIND_SAFE_DISTANCE`] block states are stored
--minimal
Run minimal storage mode with maximum pruning and smaller static files.

View File

@@ -832,7 +832,7 @@ Dev testnet:
Pruning:
--full
Run full node. Only the most recent [`MINIMUM_PRUNING_DISTANCE`] block states are stored
Run full node. Only the most recent [`MINIMUM_UNWIND_SAFE_DISTANCE`] block states are stored
--minimal
Run minimal storage mode with maximum pruning and smaller static files.