mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-02-19 03:04:27 -05:00
fix(prune): add minimum 64 block retention for receipts and bodies (#21520)
This commit is contained in:
@@ -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,
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user