Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2cd3179
add Metadata table and StorageSettings type
joshieDo Oct 29, 2025
e295a4c
initialize storage_settings on provider factory
joshieDo Oct 29, 2025
b4e32d6
update factory cached settings on init_genesis
joshieDo Oct 29, 2025
5cd4a58
ci
joshieDo Oct 29, 2025
62b4443
update doctests
joshieDo Oct 29, 2025
eefe390
add cached storagesettings to databaseprovider
joshieDo Oct 29, 2025
40a3cb2
add cached_storage_settings to StorageSettingsCache
joshieDo Oct 29, 2025
6429250
use serde_json for storage settings
joshieDo Oct 29, 2025
57ba2fb
on new nodes always write receipts to static files
joshieDo Oct 29, 2025
678c38f
Merge remote-tracking branch 'origin/main' into joshie/receipts-alway…
joshieDo Nov 6, 2025
218d978
Merge remote-tracking branch 'origin/main' into joshie/receipts-alway…
joshieDo Nov 7, 2025
7122e42
Merge remote-tracking branch 'origin/main' into joshie/receipts-alway…
joshieDo Nov 12, 2025
159ab62
add EitherWriter::receipts_destination
joshieDo Nov 13, 2025
ffa9181
allow to skip writing receipts to static files if none have been writ…
joshieDo Nov 13, 2025
4aabf62
add prunable tests
joshieDo Nov 13, 2025
b0d0eec
clippy
joshieDo Nov 13, 2025
6aac3d0
Merge remote-tracking branch 'origin/main' into joshie/receipts-alway…
joshieDo Nov 13, 2025
7686c77
init_genesis with settings + --static-files.receipts
joshieDo Nov 13, 2025
e73918b
doc update
joshieDo Nov 13, 2025
cffb466
clippy & book
joshieDo Nov 13, 2025
ac8fcdb
make fn const
joshieDo Nov 13, 2025
4f03c04
feat: prune receipts from static files (#19401)
joshieDo Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions crates/cli/commands/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use reth_cli::chainspec::ChainSpecParser;
use reth_config::{config::EtlConfig, Config};
use reth_consensus::noop::NoopConsensus;
use reth_db::{init_db, open_db_read_only, DatabaseEnv};
use reth_db_common::init::init_genesis;
use reth_db_common::init::init_genesis_with_settings;
use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader};
use reth_eth_wire::NetPrimitivesFor;
use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
Expand All @@ -17,7 +17,7 @@ use reth_node_builder::{
Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter,
};
use reth_node_core::{
args::{DatabaseArgs, DatadirArgs},
args::{DatabaseArgs, DatadirArgs, StaticFilesArgs},
dirs::{ChainPath, DataDirPath},
};
use reth_provider::{
Expand Down Expand Up @@ -57,6 +57,10 @@ pub struct EnvironmentArgs<C: ChainSpecParser> {
/// All database related arguments
#[command(flatten)]
pub db: DatabaseArgs,

/// All static files related arguments
#[command(flatten)]
pub static_files: StaticFilesArgs,
}

impl<C: ChainSpecParser> EnvironmentArgs<C> {
Expand Down Expand Up @@ -106,7 +110,7 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
let provider_factory = self.create_provider_factory(&config, db, sfp)?;
if access.is_read_write() {
debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
init_genesis(&provider_factory)?;
init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?;
}

Ok(Environment { config, provider_factory, data_dir })
Expand All @@ -126,7 +130,6 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
where
C: ChainSpecParser<ChainSpec = N::ChainSpec>,
{
let has_receipt_pruning = config.prune.has_receipts_pruning();
let prune_modes = config.prune.segments.clone();
let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
db,
Expand All @@ -136,9 +139,8 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
.with_prune_modes(prune_modes.clone());

// Check for consistency between database and static files.
if let Some(unwind_target) = factory
.static_file_provider()
.check_consistency(&factory.provider()?, has_receipt_pruning)?
if let Some(unwind_target) =
factory.static_file_provider().check_consistency(&factory.provider()?)?
{
if factory.db_ref().is_read_only()? {
warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
Expand Down
19 changes: 11 additions & 8 deletions crates/node/builder/src/launch/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use reth_chainspec::{Chain, EthChainSpec, EthereumHardforks};
use reth_config::{config::EtlConfig, PruneConfig};
use reth_consensus::noop::NoopConsensus;
use reth_db_api::{database::Database, database_metrics::DatabaseMetrics};
use reth_db_common::init::{init_genesis, InitStorageError};
use reth_db_common::init::{init_genesis_with_settings, InitStorageError};
use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader};
use reth_engine_local::MiningMode;
use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
Expand Down Expand Up @@ -482,13 +482,10 @@ where
ProviderFactory::new(self.right().clone(), self.chain_spec(), static_file_provider)?
.with_prune_modes(self.prune_modes());

let has_receipt_pruning = self.toml_config().prune.has_receipts_pruning();

// Check for consistency between database and static files. If it fails, it unwinds to
// the first block that's consistent between database and static files.
if let Some(unwind_target) = factory
.static_file_provider()
.check_consistency(&factory.provider()?, has_receipt_pruning)?
if let Some(unwind_target) =
factory.static_file_provider().check_consistency(&factory.provider()?)?
{
// Highly unlikely to happen, and given its destructive nature, it's better to panic
// instead.
Expand Down Expand Up @@ -627,13 +624,19 @@ where

/// Convenience function to [`Self::init_genesis`]
pub fn with_genesis(self) -> Result<Self, InitStorageError> {
init_genesis(self.provider_factory())?;
init_genesis_with_settings(
self.provider_factory(),
self.node_config().static_files.to_settings(),
)?;
Ok(self)
}

/// Write the genesis block and state if it has not already been written
pub fn init_genesis(&self) -> Result<B256, InitStorageError> {
init_genesis(self.provider_factory())
init_genesis_with_settings(
self.provider_factory(),
self.node_config().static_files.to_settings(),
)
}

/// Creates a new `WithMeteredProvider` container and attaches it to the
Expand Down
1 change: 1 addition & 0 deletions crates/node/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ reth-cli-util.workspace = true
reth-db = { workspace = true, features = ["mdbx"] }
reth-storage-errors.workspace = true
reth-storage-api = { workspace = true, features = ["std", "db-api"] }
reth-provider.workspace = true
reth-network = { workspace = true, features = ["serde"] }
reth-network-p2p.workspace = true
reth-rpc-eth-types.workspace = true
Expand Down
19 changes: 19 additions & 0 deletions crates/node/core/src/args/static_files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use clap::Args;
use reth_config::config::{BlocksPerFileConfig, StaticFilesConfig};
use reth_provider::StorageSettings;

/// Parameters for static files configuration
#[derive(Debug, Args, PartialEq, Eq, Default, Clone, Copy)]
Expand All @@ -18,6 +19,15 @@ pub struct StaticFilesArgs {
/// Number of blocks per file for the receipts segment.
#[arg(long = "static-files.blocks-per-file.receipts")]
pub blocks_per_file_receipts: Option<u64>,

/// Store receipts in static files instead of the database.
///
/// When enabled, receipts will be written to static files on disk instead of the database.
///
/// Note: This setting can only be configured at genesis initialization. Once
/// the node has been initialized, changing this flag requires re-syncing from scratch.
#[arg(long = "static-files.receipts")]
pub receipts: bool,
}

impl StaticFilesArgs {
Expand All @@ -34,4 +44,13 @@ impl StaticFilesArgs {
},
}
}

/// Converts the static files arguments into [`StorageSettings`].
pub const fn to_settings(&self) -> StorageSettings {
if self.receipts {
StorageSettings::new().with_receipts_in_static_files()
} else {
StorageSettings::legacy()
}
Comment on lines +50 to +54
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: think it's nicer to be able to do

Suggested change
if self.receipts {
StorageSettings::new().with_receipts_in_static_files()
} else {
StorageSettings::legacy()
}
StorageSettings::legacy().with_receipts_in_static_files(self.receipts)

}
}
6 changes: 4 additions & 2 deletions crates/prune/prune/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use reth_primitives_traits::NodePrimitives;
use reth_provider::{
providers::StaticFileProvider, BlockReader, ChainStateBlockReader, DBProvider,
DatabaseProviderFactory, NodePrimitivesProvider, PruneCheckpointReader, PruneCheckpointWriter,
StaticFileProviderFactory,
StaticFileProviderFactory, StorageSettingsCache,
};
use reth_prune_types::PruneModes;
use std::time::Duration;
Expand Down Expand Up @@ -80,6 +80,7 @@ impl PrunerBuilder {
+ PruneCheckpointReader
+ BlockReader<Transaction: Encodable2718>
+ ChainStateBlockReader
+ StorageSettingsCache
+ StaticFileProviderFactory<
Primitives: NodePrimitives<SignedTx: Value, Receipt: Value, BlockHeader: Value>,
>,
Expand Down Expand Up @@ -112,7 +113,8 @@ impl PrunerBuilder {
+ BlockReader<Transaction: Encodable2718>
+ ChainStateBlockReader
+ PruneCheckpointWriter
+ PruneCheckpointReader,
+ PruneCheckpointReader
+ StorageSettingsCache,
{
let segments = SegmentSet::<Provider>::from_components(static_file_provider, self.segments);

Expand Down
43 changes: 41 additions & 2 deletions crates/prune/prune/src/segments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ mod user;

use crate::{PruneLimiter, PrunerError};
use alloy_primitives::{BlockNumber, TxNumber};
use reth_provider::{errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter};
use reth_prune_types::{PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput};
use reth_provider::{
errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter, StaticFileProviderFactory,
};
use reth_prune_types::{
PruneCheckpoint, PruneMode, PruneProgress, PrunePurpose, PruneSegment, SegmentOutput,
SegmentOutputCheckpoint,
};
use reth_static_file_types::StaticFileSegment;
pub use set::SegmentSet;
use std::{fmt::Debug, ops::RangeInclusive};
use tracing::error;
Expand All @@ -14,6 +20,39 @@ pub use user::{
SenderRecovery, StorageHistory, TransactionLookup,
};

/// Prunes data from static files for a given segment.
///
/// This is a generic helper function used by both receipts and bodies pruning
/// when data is stored in static files.
pub(crate) fn prune_static_files<Provider>(
provider: &Provider,
input: PruneInput,
segment: StaticFileSegment,
) -> Result<SegmentOutput, PrunerError>
where
Provider: StaticFileProviderFactory,
{
let deleted_headers =
provider.static_file_provider().delete_segment_below_block(segment, input.to_block + 1)?;

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:
Expand Down
33 changes: 27 additions & 6 deletions crates/prune/prune/src/segments/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@
//! - [`crate::segments::user::Receipts`] is responsible for pruning receipts according to the
//! user-configured settings (for example, on a full node or with a custom prune config)

use crate::{db_ext::DbTxPruneExt, segments::PruneInput, PrunerError};
use crate::{
db_ext::DbTxPruneExt,
segments::{self, PruneInput},
PrunerError,
};
use reth_db_api::{table::Value, tables, transaction::DbTxMut};
use reth_primitives_traits::NodePrimitives;
use reth_provider::{
errors::provider::ProviderResult, BlockReader, DBProvider, NodePrimitivesProvider,
PruneCheckpointWriter, TransactionsProvider,
errors::provider::ProviderResult, BlockReader, DBProvider, EitherWriter,
NodePrimitivesProvider, PruneCheckpointWriter, StaticFileProviderFactory, StorageSettingsCache,
TransactionsProvider,
};
use reth_prune_types::{PruneCheckpoint, PruneSegment, SegmentOutput, SegmentOutputCheckpoint};
use tracing::trace;
use reth_static_file_types::StaticFileSegment;
use tracing::{debug, trace};

pub(crate) fn prune<Provider>(
provider: &Provider,
Expand All @@ -21,8 +27,17 @@ where
Provider: DBProvider<Tx: DbTxMut>
+ TransactionsProvider
+ BlockReader
+ StorageSettingsCache
+ StaticFileProviderFactory
+ NodePrimitivesProvider<Primitives: NodePrimitives<Receipt: Value>>,
{
if EitherWriter::receipts_destination(provider).is_static_file() {
debug!(target: "pruner", "Pruning receipts from static files.");
return segments::prune_static_files(provider, input, StaticFileSegment::Receipts)
}
debug!(target: "pruner", "Pruning receipts from database.");

// Original database implementation for when receipts are not on static files (old nodes)
let tx_range = match input.get_next_tx_num_range(provider)? {
Some(range) => range,
None => {
Expand Down Expand Up @@ -98,8 +113,14 @@ mod tests {
use std::ops::Sub;

#[test]
fn prune() {
let db = TestStageDB::default();
fn prune_legacy() {
let mut db = TestStageDB::default();
// Configure the factory to use database for receipts by enabling receipt pruning.
// This ensures EitherWriter::receipts_destination returns Database instead of StaticFile.
db.factory = db.factory.with_prune_modes(reth_prune_types::PruneModes {
receipts: Some(PruneMode::Full),
..Default::default()
});
let mut rng = generators::rng();

let blocks = random_block_range(
Expand Down
5 changes: 3 additions & 2 deletions crates/prune/prune/src/segments/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use reth_db_api::{table::Value, transaction::DbTxMut};
use reth_primitives_traits::NodePrimitives;
use reth_provider::{
providers::StaticFileProvider, BlockReader, ChainStateBlockReader, DBProvider,
PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory,
PruneCheckpointReader, PruneCheckpointWriter, StaticFileProviderFactory, StorageSettingsCache,
};
use reth_prune_types::PruneModes;

Expand Down Expand Up @@ -51,7 +51,8 @@ where
+ PruneCheckpointWriter
+ PruneCheckpointReader
+ BlockReader<Transaction: Encodable2718>
+ ChainStateBlockReader,
+ ChainStateBlockReader
+ StorageSettingsCache,
{
/// Creates a [`SegmentSet`] from an existing components, such as [`StaticFileProvider`] and
/// [`PruneModes`].
Expand Down
27 changes: 3 additions & 24 deletions crates/prune/prune/src/segments/user/bodies.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use crate::{
segments::{PruneInput, Segment},
segments::{self, PruneInput, Segment},
PrunerError,
};
use reth_provider::{BlockReader, StaticFileProviderFactory};
use reth_prune_types::{
PruneMode, PruneProgress, PrunePurpose, PruneSegment, SegmentOutput, SegmentOutputCheckpoint,
};
use reth_prune_types::{PruneMode, PrunePurpose, PruneSegment, SegmentOutput};
use reth_static_file_types::StaticFileSegment;

/// Segment responsible for pruning transactions in static files.
Expand Down Expand Up @@ -40,26 +38,7 @@ where
}

fn prune(&self, provider: &Provider, input: PruneInput) -> Result<SegmentOutput, PrunerError> {
let deleted_headers = provider
.static_file_provider()
.delete_segment_below_block(StaticFileSegment::Transactions, input.to_block + 1)?;

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(),
}),
})
segments::prune_static_files(provider, input, StaticFileSegment::Transactions)
}
}

Expand Down
4 changes: 3 additions & 1 deletion crates/prune/prune/src/segments/user/receipts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use reth_db_api::{table::Value, transaction::DbTxMut};
use reth_primitives_traits::NodePrimitives;
use reth_provider::{
errors::provider::ProviderResult, BlockReader, DBProvider, NodePrimitivesProvider,
PruneCheckpointWriter, TransactionsProvider,
PruneCheckpointWriter, StaticFileProviderFactory, StorageSettingsCache, TransactionsProvider,
};
use reth_prune_types::{PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput};
use tracing::instrument;
Expand All @@ -28,6 +28,8 @@ where
+ PruneCheckpointWriter
+ TransactionsProvider
+ BlockReader
+ StorageSettingsCache
+ StaticFileProviderFactory
+ NodePrimitivesProvider<Primitives: NodePrimitives<Receipt: Value>>,
{
fn segment(&self) -> PruneSegment {
Expand Down
Loading