diff --git a/Cargo.lock b/Cargo.lock index b9de70bfded..6e672b6f684 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9006,7 +9006,6 @@ dependencies = [ "serde", "shellexpand", "strum 0.27.2", - "thiserror 2.0.17", "tokio", "toml", "tracing", @@ -9786,7 +9785,6 @@ dependencies = [ name = "reth-prune" version = "1.8.2" dependencies = [ - "alloy-consensus", "alloy-eips", "alloy-primitives", "assert_matches", diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 5ff2431bb56..dd2e7046b0c 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -33,7 +33,7 @@ pub struct Config { impl Config { /// Sets the pruning configuration. - pub fn set_prune_config(&mut self, prune_config: PruneConfig) { + pub const fn set_prune_config(&mut self, prune_config: PruneConfig) { self.prune = prune_config; } } @@ -451,13 +451,14 @@ impl PruneConfig { } /// Returns whether there is any kind of receipt pruning configuration. - pub fn has_receipts_pruning(&self) -> bool { - self.segments.receipts.is_some() || !self.segments.receipts_log_filter.is_empty() + pub const fn has_receipts_pruning(&self) -> bool { + self.segments.receipts.is_some() } /// Merges another `PruneConfig` into this one, taking values from the other config if and only /// if the corresponding value in this config is not set. pub fn merge(&mut self, other: Self) { + #[expect(deprecated)] let Self { block_interval, segments: @@ -469,7 +470,7 @@ impl PruneConfig { storage_history, bodies_history, merkle_changesets, - receipts_log_filter, + receipts_log_filter: (), }, } = other; @@ -487,10 +488,6 @@ impl PruneConfig { self.segments.bodies_history = self.segments.bodies_history.or(bodies_history); // Merkle changesets is not optional, so we just replace it if provided self.segments.merkle_changesets = merkle_changesets; - - if self.segments.receipts_log_filter.0.is_empty() && !receipts_log_filter.0.is_empty() { - self.segments.receipts_log_filter = receipts_log_filter; - } } } @@ -517,10 +514,9 @@ where mod tests { use super::{Config, EXTENSION}; use crate::PruneConfig; - use alloy_primitives::Address; use reth_network_peers::TrustedPeer; - use reth_prune_types::{PruneMode, PruneModes, ReceiptsLogPruneConfig}; - use std::{collections::BTreeMap, path::Path, str::FromStr, time::Duration}; + use reth_prune_types::{PruneMode, PruneModes}; + use std::{path::Path, str::FromStr, time::Duration}; fn with_tempdir(filename: &str, proc: fn(&std::path::Path)) { let temp_dir = tempfile::tempdir().unwrap(); @@ -1009,10 +1005,8 @@ receipts = 'full' storage_history: Some(PruneMode::Before(5000)), bodies_history: None, merkle_changesets: PruneMode::Before(0), - receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([( - Address::random(), - PruneMode::Full, - )])), + #[expect(deprecated)] + receipts_log_filter: (), }, }; @@ -1026,14 +1020,11 @@ receipts = 'full' storage_history: Some(PruneMode::Distance(3000)), bodies_history: None, merkle_changesets: PruneMode::Distance(10000), - receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([ - (Address::random(), PruneMode::Distance(1000)), - (Address::random(), PruneMode::Before(2000)), - ])), + #[expect(deprecated)] + receipts_log_filter: (), }, }; - let original_filter = config1.segments.receipts_log_filter.clone(); config1.merge(config2); // Check that the configuration has been merged. Any configuration present in config1 @@ -1045,7 +1036,6 @@ receipts = 'full' assert_eq!(config1.segments.account_history, Some(PruneMode::Distance(2000))); assert_eq!(config1.segments.storage_history, Some(PruneMode::Before(5000))); assert_eq!(config1.segments.merkle_changesets, PruneMode::Distance(10000)); - assert_eq!(config1.segments.receipts_log_filter, original_filter); } #[test] diff --git a/crates/exex/exex/src/backfill/factory.rs b/crates/exex/exex/src/backfill/factory.rs index d9a51bc47a7..29734b905e2 100644 --- a/crates/exex/exex/src/backfill/factory.rs +++ b/crates/exex/exex/src/backfill/factory.rs @@ -39,7 +39,7 @@ impl BackfillJobFactory { } /// Sets the prune modes - pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { + pub const fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { self.prune_modes = prune_modes; self } diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index dd3cdbf756d..190cfdc8817 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -406,13 +406,14 @@ impl LaunchContextWith, - // Receipts Log Filter - /// Configure receipts log filter. Format: - /// <`address`>:<`prune_mode`>... where <`prune_mode`> can be 'full', 'distance:<`blocks`>', or - /// 'before:<`block_number`>' - #[arg(long = "prune.receiptslogfilter", value_name = "FILTER_CONFIG", conflicts_with_all = &["receipts_full", "receipts_pre_merge", "receipts_distance", "receipts_before"], value_parser = parse_receipts_log_filter)] - pub receipts_log_filter: Option, + /// Receipts Log Filter + #[arg(long = "prune.receiptslogfilter", value_name = "FILTER_CONFIG", hide = true)] + #[deprecated] + pub receipts_log_filter: Option, // Account History /// Prunes all account history. @@ -130,7 +129,8 @@ impl PruningArgs { // TODO: set default to pre-merge block if available bodies_history: None, merkle_changesets: PruneMode::Distance(MINIMUM_PRUNING_DISTANCE), - receipts_log_filter: Default::default(), + #[expect(deprecated)] + receipts_log_filter: (), }, } } @@ -157,13 +157,14 @@ impl PruningArgs { if let Some(mode) = self.storage_history_prune_mode() { config.segments.storage_history = Some(mode); } - if let Some(receipt_logs) = - self.receipts_log_filter.as_ref().filter(|c| !c.is_empty()).cloned() - { - config.segments.receipts_log_filter = receipt_logs; - // need to remove the receipts segment filter entirely because that takes precedence - // over the logs filter - config.segments.receipts.take(); + + // Log warning if receipts_log_filter is set (deprecated feature) + #[expect(deprecated)] + if self.receipts_log_filter.is_some() { + tracing::warn!( + target: "reth::cli", + "The --prune.receiptslogfilter flag is deprecated and has no effect. It will be removed in a future release." + ); } config.is_default().not().then_some(config) @@ -251,141 +252,3 @@ impl PruningArgs { } } } - -/// Parses `,` separated pruning info into [`ReceiptsLogPruneConfig`]. -pub(crate) fn parse_receipts_log_filter( - value: &str, -) -> Result { - let mut config = BTreeMap::new(); - // Split out each of the filters. - let filters = value.split(','); - for filter in filters { - let parts: Vec<&str> = filter.split(':').collect(); - if parts.len() < 2 { - return Err(ReceiptsLogError::InvalidFilterFormat(filter.to_string())); - } - // Parse the address - let address = parts[0] - .parse::
() - .map_err(|_| ReceiptsLogError::InvalidAddress(parts[0].to_string()))?; - - // Parse the prune mode - let prune_mode = match parts[1] { - "full" => PruneMode::Full, - s if s.starts_with("distance") => { - if parts.len() < 3 { - return Err(ReceiptsLogError::InvalidFilterFormat(filter.to_string())); - } - let distance = - parts[2].parse::().map_err(ReceiptsLogError::InvalidDistance)?; - PruneMode::Distance(distance) - } - s if s.starts_with("before") => { - if parts.len() < 3 { - return Err(ReceiptsLogError::InvalidFilterFormat(filter.to_string())); - } - let block_number = - parts[2].parse::().map_err(ReceiptsLogError::InvalidBlockNumber)?; - PruneMode::Before(block_number) - } - _ => return Err(ReceiptsLogError::InvalidPruneMode(parts[1].to_string())), - }; - config.insert(address, prune_mode); - } - Ok(ReceiptsLogPruneConfig(config)) -} - -#[cfg(test)] -mod tests { - use super::*; - use alloy_primitives::address; - use clap::Parser; - - /// A helper type to parse Args more easily - #[derive(Parser)] - struct CommandParser { - #[command(flatten)] - args: T, - } - - #[test] - fn pruning_args_sanity_check() { - let args = CommandParser::::parse_from([ - "reth", - "--prune.receiptslogfilter", - "0x0000000000000000000000000000000000000003:before:5000000", - ]) - .args; - let mut config = ReceiptsLogPruneConfig::default(); - config.0.insert( - address!("0x0000000000000000000000000000000000000003"), - PruneMode::Before(5000000), - ); - assert_eq!(args.receipts_log_filter, Some(config)); - } - - #[test] - fn parse_receiptslogfilter() { - let default_args = PruningArgs::default(); - let args = CommandParser::::parse_from(["reth"]).args; - assert_eq!(args, default_args); - } - - #[test] - fn test_parse_receipts_log_filter() { - let filter1 = "0x0000000000000000000000000000000000000001:full"; - let filter2 = "0x0000000000000000000000000000000000000002:distance:1000"; - let filter3 = "0x0000000000000000000000000000000000000003:before:5000000"; - let filters = [filter1, filter2, filter3].join(","); - - // Args can be parsed. - let result = parse_receipts_log_filter(&filters); - assert!(result.is_ok()); - let config = result.unwrap(); - assert_eq!(config.0.len(), 3); - - // Check that the args were parsed correctly. - let addr1: Address = "0x0000000000000000000000000000000000000001".parse().unwrap(); - let addr2: Address = "0x0000000000000000000000000000000000000002".parse().unwrap(); - let addr3: Address = "0x0000000000000000000000000000000000000003".parse().unwrap(); - - assert_eq!(config.0.get(&addr1), Some(&PruneMode::Full)); - assert_eq!(config.0.get(&addr2), Some(&PruneMode::Distance(1000))); - assert_eq!(config.0.get(&addr3), Some(&PruneMode::Before(5000000))); - } - - #[test] - fn test_parse_receipts_log_filter_invalid_filter_format() { - let result = parse_receipts_log_filter("invalid_format"); - assert!(matches!(result, Err(ReceiptsLogError::InvalidFilterFormat(_)))); - } - - #[test] - fn test_parse_receipts_log_filter_invalid_address() { - let result = parse_receipts_log_filter("invalid_address:full"); - assert!(matches!(result, Err(ReceiptsLogError::InvalidAddress(_)))); - } - - #[test] - fn test_parse_receipts_log_filter_invalid_prune_mode() { - let result = - parse_receipts_log_filter("0x0000000000000000000000000000000000000000:invalid_mode"); - assert!(matches!(result, Err(ReceiptsLogError::InvalidPruneMode(_)))); - } - - #[test] - fn test_parse_receipts_log_filter_invalid_distance() { - let result = parse_receipts_log_filter( - "0x0000000000000000000000000000000000000000:distance:invalid_distance", - ); - assert!(matches!(result, Err(ReceiptsLogError::InvalidDistance(_)))); - } - - #[test] - fn test_parse_receipts_log_filter_invalid_block_number() { - let result = parse_receipts_log_filter( - "0x0000000000000000000000000000000000000000:before:invalid_block", - ); - assert!(matches!(result, Err(ReceiptsLogError::InvalidBlockNumber(_)))); - } -} diff --git a/crates/prune/prune/Cargo.toml b/crates/prune/prune/Cargo.toml index a2d82c26923..615a793bb89 100644 --- a/crates/prune/prune/Cargo.toml +++ b/crates/prune/prune/Cargo.toml @@ -24,7 +24,6 @@ reth-primitives-traits.workspace = true reth-static-file-types.workspace = true # ethereum -alloy-consensus.workspace = true alloy-eips.workspace = true # metrics diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index 78283710e15..f61aa6bd46d 100644 --- a/crates/prune/prune/src/builder.rs +++ b/crates/prune/prune/src/builder.rs @@ -43,7 +43,7 @@ impl PrunerBuilder { } /// Sets the configuration for every part of the data that can be pruned. - pub fn segments(mut self, segments: PruneModes) -> Self { + pub const fn segments(mut self, segments: PruneModes) -> Self { self.segments = segments; self } diff --git a/crates/prune/prune/src/segments/mod.rs b/crates/prune/prune/src/segments/mod.rs index dc175254453..f0f688a7c86 100644 --- a/crates/prune/prune/src/segments/mod.rs +++ b/crates/prune/prune/src/segments/mod.rs @@ -15,8 +15,8 @@ pub use static_file::{ use std::{fmt::Debug, ops::RangeInclusive}; use tracing::error; pub use user::{ - AccountHistory, MerkleChangeSets, Receipts as UserReceipts, ReceiptsByLogs, SenderRecovery, - StorageHistory, TransactionLookup, + AccountHistory, MerkleChangeSets, Receipts as UserReceipts, SenderRecovery, StorageHistory, + TransactionLookup, }; /// A segment represents a pruning of some portion of the data. diff --git a/crates/prune/prune/src/segments/set.rs b/crates/prune/prune/src/segments/set.rs index 72847219b09..e551a8de9a1 100644 --- a/crates/prune/prune/src/segments/set.rs +++ b/crates/prune/prune/src/segments/set.rs @@ -1,6 +1,6 @@ use crate::segments::{ - AccountHistory, MerkleChangeSets, ReceiptsByLogs, Segment, SenderRecovery, StorageHistory, - TransactionLookup, UserReceipts, + AccountHistory, MerkleChangeSets, Segment, SenderRecovery, StorageHistory, TransactionLookup, + UserReceipts, }; use alloy_eips::eip2718::Encodable2718; use reth_db_api::{table::Value, transaction::DbTxMut}; @@ -61,6 +61,7 @@ where static_file_provider: StaticFileProvider, prune_modes: PruneModes, ) -> Self { + #[expect(deprecated)] let PruneModes { sender_recovery, transaction_lookup, @@ -69,7 +70,7 @@ where storage_history, bodies_history: _, merkle_changesets, - receipts_log_filter, + receipts_log_filter: (), } = prune_modes; Self::default() @@ -87,11 +88,6 @@ where .segment_opt(storage_history.map(StorageHistory::new)) // User receipts .segment_opt(receipts.map(UserReceipts::new)) - // Receipts by logs - .segment_opt( - (!receipts_log_filter.is_empty()) - .then(|| ReceiptsByLogs::new(receipts_log_filter.clone())), - ) // Transaction lookup .segment_opt(transaction_lookup.map(TransactionLookup::new)) // Sender recovery diff --git a/crates/prune/prune/src/segments/user/mod.rs b/crates/prune/prune/src/segments/user/mod.rs index c25bc6bc764..bdbc27f22f0 100644 --- a/crates/prune/prune/src/segments/user/mod.rs +++ b/crates/prune/prune/src/segments/user/mod.rs @@ -2,7 +2,6 @@ mod account_history; mod history; mod merkle_change_sets; mod receipts; -mod receipts_by_logs; mod sender_recovery; mod storage_history; mod transaction_lookup; @@ -10,7 +9,6 @@ mod transaction_lookup; pub use account_history::AccountHistory; pub use merkle_change_sets::MerkleChangeSets; pub use receipts::Receipts; -pub use receipts_by_logs::ReceiptsByLogs; pub use sender_recovery::SenderRecovery; pub use storage_history::StorageHistory; pub use transaction_lookup::TransactionLookup; diff --git a/crates/prune/prune/src/segments/user/receipts_by_logs.rs b/crates/prune/prune/src/segments/user/receipts_by_logs.rs deleted file mode 100644 index 8fd6d1e73a5..00000000000 --- a/crates/prune/prune/src/segments/user/receipts_by_logs.rs +++ /dev/null @@ -1,364 +0,0 @@ -use crate::{ - db_ext::DbTxPruneExt, - segments::{PruneInput, Segment}, - PrunerError, -}; -use alloy_consensus::TxReceipt; -use reth_db_api::{table::Value, tables, transaction::DbTxMut}; -use reth_primitives_traits::NodePrimitives; -use reth_provider::{ - BlockReader, DBProvider, NodePrimitivesProvider, PruneCheckpointWriter, TransactionsProvider, -}; -use reth_prune_types::{ - PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, ReceiptsLogPruneConfig, SegmentOutput, - MINIMUM_PRUNING_DISTANCE, -}; -use tracing::{instrument, trace}; -#[derive(Debug)] -pub struct ReceiptsByLogs { - config: ReceiptsLogPruneConfig, -} - -impl ReceiptsByLogs { - pub const fn new(config: ReceiptsLogPruneConfig) -> Self { - Self { config } - } -} - -impl Segment for ReceiptsByLogs -where - Provider: DBProvider - + PruneCheckpointWriter - + TransactionsProvider - + BlockReader - + NodePrimitivesProvider>, -{ - fn segment(&self) -> PruneSegment { - PruneSegment::ContractLogs - } - - fn mode(&self) -> Option { - None - } - - fn purpose(&self) -> PrunePurpose { - PrunePurpose::User - } - - #[instrument(target = "pruner", skip(self, provider), ret(level = "trace"))] - fn prune(&self, provider: &Provider, input: PruneInput) -> Result { - // 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) - .prune_target_block(input.to_block, PruneSegment::ContractLogs, PrunePurpose::User)? - .map(|(bn, _)| bn) - .unwrap_or_default(); - - // Get status checkpoint from latest run - let mut last_pruned_block = - input.previous_checkpoint.and_then(|checkpoint| checkpoint.block_number); - - let initial_last_pruned_block = last_pruned_block; - - let mut from_tx_number = match initial_last_pruned_block { - Some(block) => provider - .block_body_indices(block)? - .map(|block| block.last_tx_num() + 1) - .unwrap_or(0), - None => 0, - }; - - // Figure out what receipts have already been pruned, so we can have an accurate - // `address_filter` - let address_filter = self.config.group_by_block(input.to_block, last_pruned_block)?; - - // Splits all transactions in different block ranges. Each block range will have its own - // filter address list and will check it while going through the table - // - // Example: - // For an `address_filter` such as: - // { block9: [a1, a2], block20: [a3, a4, a5] } - // - // The following structures will be created in the exact order as showed: - // `block_ranges`: [ - // (block0, block8, 0 addresses), - // (block9, block19, 2 addresses), - // (block20, to_block, 5 addresses) - // ] - // `filtered_addresses`: [a1, a2, a3, a4, a5] - // - // The first range will delete all receipts between block0 - block8 - // The second range will delete all receipts between block9 - 19, except the ones with - // emitter logs from these addresses: [a1, a2]. - // The third range will delete all receipts between block20 - to_block, except the ones with - // emitter logs from these addresses: [a1, a2, a3, a4, a5] - let mut block_ranges = vec![]; - let mut blocks_iter = address_filter.iter().peekable(); - let mut filtered_addresses = vec![]; - - while let Some((start_block, addresses)) = blocks_iter.next() { - filtered_addresses.extend_from_slice(addresses); - - // This will clear all receipts before the first appearance of a contract log or since - // the block after the last pruned one. - if block_ranges.is_empty() { - let init = last_pruned_block.map(|b| b + 1).unwrap_or_default(); - if init < *start_block { - block_ranges.push((init, *start_block - 1, 0)); - } - } - - let end_block = - blocks_iter.peek().map(|(next_block, _)| *next_block - 1).unwrap_or(to_block); - - // Addresses in lower block ranges, are still included in the inclusion list for future - // ranges. - block_ranges.push((*start_block, end_block, filtered_addresses.len())); - } - - trace!( - target: "pruner", - ?block_ranges, - ?filtered_addresses, - "Calculated block ranges and filtered addresses", - ); - - let mut limiter = input.limiter; - - let mut done = true; - let mut pruned = 0; - let mut last_pruned_transaction = None; - for (start_block, end_block, num_addresses) in block_ranges { - let block_range = start_block..=end_block; - - // Calculate the transaction range from this block range - let tx_range_end = match provider.block_body_indices(end_block)? { - Some(body) => body.last_tx_num(), - None => { - trace!( - target: "pruner", - ?block_range, - "No receipts to prune." - ); - continue - } - }; - let tx_range = from_tx_number..=tx_range_end; - - // Delete receipts, except the ones in the inclusion list - let mut last_skipped_transaction = 0; - let deleted; - (deleted, done) = provider.tx_ref().prune_table_with_range::::Receipt, - >>( - tx_range, - &mut limiter, - |(tx_num, receipt)| { - let skip = num_addresses > 0 && - receipt.logs().iter().any(|log| { - filtered_addresses[..num_addresses].contains(&&log.address) - }); - - if skip { - last_skipped_transaction = *tx_num; - } - skip - }, - |row| last_pruned_transaction = Some(row.0), - )?; - - trace!(target: "pruner", %deleted, %done, ?block_range, "Pruned receipts"); - - pruned += deleted; - - // For accurate checkpoints we need to know that we have checked every transaction. - // Example: we reached the end of the range, and the last receipt is supposed to skip - // its deletion. - let last_pruned_transaction = *last_pruned_transaction - .insert(last_pruned_transaction.unwrap_or_default().max(last_skipped_transaction)); - - last_pruned_block = Some( - provider - .transaction_block(last_pruned_transaction)? - .ok_or(PrunerError::InconsistentData("Block for transaction is not found"))? - // If there's more receipts to prune, set the checkpoint block number to - // previous, so we could finish pruning its receipts on the - // next run. - .saturating_sub(if done { 0 } else { 1 }), - ); - - if limiter.is_limit_reached() { - done &= end_block == to_block; - break - } - - from_tx_number = last_pruned_transaction + 1; - } - - // If there are contracts using `PruneMode::Distance(_)` there will be receipts before - // `to_block` that become eligible to be pruned in future runs. Therefore, our checkpoint is - // not actually `to_block`, but the `lowest_block_with_distance` from any contract. - // This ensures that in future pruner runs we can prune all these receipts between the - // previous `lowest_block_with_distance` and the new one using - // `get_next_tx_num_range_from_checkpoint`. - // - // Only applies if we were able to prune everything intended for this run, otherwise the - // checkpoint is the `last_pruned_block`. - let prune_mode_block = self - .config - .lowest_block_with_distance(input.to_block, initial_last_pruned_block)? - .unwrap_or(to_block); - - provider.save_prune_checkpoint( - PruneSegment::ContractLogs, - PruneCheckpoint { - block_number: Some(prune_mode_block.min(last_pruned_block.unwrap_or(u64::MAX))), - tx_number: last_pruned_transaction, - prune_mode: PruneMode::Before(prune_mode_block), - }, - )?; - - let progress = limiter.progress(done); - - Ok(SegmentOutput { progress, pruned, checkpoint: None }) - } -} - -#[cfg(test)] -mod tests { - use crate::segments::{PruneInput, PruneLimiter, ReceiptsByLogs, Segment}; - use alloy_primitives::B256; - use assert_matches::assert_matches; - use reth_db_api::{cursor::DbCursorRO, tables, transaction::DbTx}; - use reth_primitives_traits::InMemorySize; - use reth_provider::{ - DBProvider, DatabaseProviderFactory, PruneCheckpointReader, TransactionsProvider, - }; - use reth_prune_types::{PruneMode, PruneSegment, ReceiptsLogPruneConfig}; - use reth_stages::test_utils::{StorageKind, TestStageDB}; - use reth_testing_utils::generators::{ - self, random_block_range, random_eoa_account, random_log, random_receipt, BlockRangeParams, - }; - use std::collections::BTreeMap; - - #[test] - fn prune_receipts_by_logs() { - reth_tracing::init_test_tracing(); - - let db = TestStageDB::default(); - let mut rng = generators::rng(); - - let tip = 20000; - let blocks = [ - random_block_range( - &mut rng, - 0..=100, - BlockRangeParams { parent: Some(B256::ZERO), tx_count: 1..5, ..Default::default() }, - ), - random_block_range( - &mut rng, - (100 + 1)..=(tip - 100), - BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() }, - ), - random_block_range( - &mut rng, - (tip - 100 + 1)..=tip, - BlockRangeParams { parent: Some(B256::ZERO), tx_count: 1..5, ..Default::default() }, - ), - ] - .concat(); - db.insert_blocks(blocks.iter(), StorageKind::Database(None)).expect("insert blocks"); - - let mut receipts = Vec::new(); - - let (deposit_contract_addr, _) = random_eoa_account(&mut rng); - for block in &blocks { - receipts.reserve_exact(block.body().size()); - for (txi, transaction) in block.body().transactions.iter().enumerate() { - let mut receipt = random_receipt(&mut rng, transaction, Some(1), None); - receipt.logs.push(random_log( - &mut rng, - (txi == (block.transaction_count() - 1)).then_some(deposit_contract_addr), - Some(1), - )); - receipts.push((receipts.len() as u64, receipt)); - } - } - db.insert_receipts(receipts).expect("insert receipts"); - - assert_eq!( - db.table::().unwrap().len(), - blocks.iter().map(|block| block.transaction_count()).sum::() - ); - assert_eq!( - db.table::().unwrap().len(), - db.table::().unwrap().len() - ); - - let run_prune = || { - let provider = db.factory.database_provider_rw().unwrap(); - - let prune_before_block: usize = 20; - let prune_mode = PruneMode::Before(prune_before_block as u64); - let receipts_log_filter = - ReceiptsLogPruneConfig(BTreeMap::from([(deposit_contract_addr, prune_mode)])); - - let limiter = PruneLimiter::default().set_deleted_entries_limit(10); - - let result = ReceiptsByLogs::new(receipts_log_filter).prune( - &provider, - PruneInput { - previous_checkpoint: db - .factory - .provider() - .unwrap() - .get_prune_checkpoint(PruneSegment::ContractLogs) - .unwrap(), - to_block: tip, - limiter, - }, - ); - provider.commit().expect("commit"); - - assert_matches!(result, Ok(_)); - let output = result.unwrap(); - - let (pruned_block, pruned_tx) = db - .factory - .provider() - .unwrap() - .get_prune_checkpoint(PruneSegment::ContractLogs) - .unwrap() - .map(|checkpoint| (checkpoint.block_number.unwrap(), checkpoint.tx_number.unwrap())) - .unwrap_or_default(); - - // All receipts are in the end of the block - let unprunable = pruned_block.saturating_sub(prune_before_block as u64 - 1); - - assert_eq!( - db.table::().unwrap().len(), - blocks.iter().map(|block| block.transaction_count()).sum::() - - ((pruned_tx + 1) - unprunable) as usize - ); - - output.progress.is_finished() - }; - - while !run_prune() {} - - let provider = db.factory.provider().unwrap(); - let mut cursor = provider.tx_ref().cursor_read::().unwrap(); - let walker = cursor.walk(None).unwrap(); - for receipt in walker { - let (tx_num, receipt) = receipt.unwrap(); - - // Either we only find our contract, or the receipt is part of the unprunable receipts - // set by tip - 128 - assert!( - receipt.logs.iter().any(|l| l.address == deposit_contract_addr) || - provider.transaction_block(tx_num).unwrap().unwrap() > tip - 128, - ); - } - } -} diff --git a/crates/prune/types/src/lib.rs b/crates/prune/types/src/lib.rs index 315063278b2..a588693892a 100644 --- a/crates/prune/types/src/lib.rs +++ b/crates/prune/types/src/lib.rs @@ -18,10 +18,6 @@ mod pruner; mod segment; mod target; -use alloc::{collections::BTreeMap, vec::Vec}; -use alloy_primitives::{Address, BlockNumber}; -use core::ops::Deref; - pub use checkpoint::PruneCheckpoint; pub use event::PrunerEvent; pub use mode::PruneMode; @@ -31,300 +27,3 @@ pub use pruner::{ }; pub use segment::{PrunePurpose, PruneSegment, PruneSegmentError}; pub use target::{PruneModes, UnwindTargetPrunedError, MINIMUM_PRUNING_DISTANCE}; - -/// Configuration for pruning receipts not associated with logs emitted by the specified contracts. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] -pub struct ReceiptsLogPruneConfig(pub BTreeMap); - -impl ReceiptsLogPruneConfig { - /// Checks if the configuration is empty - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Given the `tip` block number, consolidates the structure so it can easily be queried for - /// filtering across a range of blocks. - /// - /// Example: - /// - /// `{ addrA: Before(872), addrB: Before(500), addrC: Distance(128) }` - /// - /// for `tip: 1000`, gets transformed to a map such as: - /// - /// `{ 500: [addrB], 872: [addrA, addrC] }` - /// - /// The [`BlockNumber`] key of the new map should be viewed as `PruneMode::Before(block)`, which - /// makes the previous result equivalent to - /// - /// `{ Before(500): [addrB], Before(872): [addrA, addrC] }` - pub fn group_by_block( - &self, - tip: BlockNumber, - pruned_block: Option, - ) -> Result>, PruneSegmentError> { - let mut map = BTreeMap::new(); - let base_block = pruned_block.unwrap_or_default() + 1; - - for (address, mode) in &self.0 { - // Getting `None`, means that there is nothing to prune yet, so we need it to include in - // the BTreeMap (block = 0), otherwise it will be excluded. - // Reminder that this BTreeMap works as an inclusion list that excludes (prunes) all - // other receipts. - // - // Reminder, that we increment because the [`BlockNumber`] key of the new map should be - // viewed as `PruneMode::Before(block)` - let block = base_block.max( - mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)? - .map(|(block, _)| block) - .unwrap_or_default() + - 1, - ); - - map.entry(block).or_insert_with(Vec::new).push(address) - } - Ok(map) - } - - /// Returns the lowest block where we start filtering logs which use `PruneMode::Distance(_)`. - pub fn lowest_block_with_distance( - &self, - tip: BlockNumber, - pruned_block: Option, - ) -> Result, PruneSegmentError> { - let pruned_block = pruned_block.unwrap_or_default(); - let mut lowest = None; - - for mode in self.values() { - if mode.is_distance() && - let Some((block, _)) = - mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)? - { - lowest = Some(lowest.unwrap_or(u64::MAX).min(block)); - } - } - - Ok(lowest.map(|lowest| lowest.max(pruned_block))) - } -} - -impl Deref for ReceiptsLogPruneConfig { - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_group_by_block_empty_config() { - let config = ReceiptsLogPruneConfig(BTreeMap::new()); - let tip = 1000; - let pruned_block = None; - - let result = config.group_by_block(tip, pruned_block).unwrap(); - assert!(result.is_empty(), "The result should be empty when the config is empty"); - } - - #[test] - fn test_group_by_block_single_entry() { - let mut config_map = BTreeMap::new(); - let address = Address::new([1; 20]); - let prune_mode = PruneMode::Before(500); - config_map.insert(address, prune_mode); - - let config = ReceiptsLogPruneConfig(config_map); - // Big tip to have something to prune for the target block - let tip = 3000000; - let pruned_block = Some(400); - - let result = config.group_by_block(tip, pruned_block).unwrap(); - - // Expect one entry with block 500 and the corresponding address - assert_eq!(result.len(), 1); - assert_eq!(result[&500], vec![&address], "Address should be grouped under block 500"); - - // Tip smaller than the target block, so that we have nothing to prune for the block - let tip = 300; - let pruned_block = Some(400); - - let result = config.group_by_block(tip, pruned_block).unwrap(); - - // Expect one entry with block 400 and the corresponding address - assert_eq!(result.len(), 1); - assert_eq!(result[&401], vec![&address], "Address should be grouped under block 400"); - } - - #[test] - fn test_group_by_block_multiple_entries() { - let mut config_map = BTreeMap::new(); - let address1 = Address::new([1; 20]); - let address2 = Address::new([2; 20]); - let prune_mode1 = PruneMode::Before(600); - let prune_mode2 = PruneMode::Before(800); - config_map.insert(address1, prune_mode1); - config_map.insert(address2, prune_mode2); - - let config = ReceiptsLogPruneConfig(config_map); - let tip = 900000; - let pruned_block = Some(400); - - let result = config.group_by_block(tip, pruned_block).unwrap(); - - // Expect two entries: one for block 600 and another for block 800 - assert_eq!(result.len(), 2); - assert_eq!(result[&600], vec![&address1], "Address1 should be grouped under block 600"); - assert_eq!(result[&800], vec![&address2], "Address2 should be grouped under block 800"); - } - - #[test] - fn test_group_by_block_with_distance_prune_mode() { - let mut config_map = BTreeMap::new(); - let address = Address::new([1; 20]); - let prune_mode = PruneMode::Distance(100000); - config_map.insert(address, prune_mode); - - let config = ReceiptsLogPruneConfig(config_map); - let tip = 100100; - // Pruned block is smaller than the target block - let pruned_block = Some(50); - - let result = config.group_by_block(tip, pruned_block).unwrap(); - - // Expect the entry to be grouped under block 100 (tip - distance) - assert_eq!(result.len(), 1); - assert_eq!(result[&101], vec![&address], "Address should be grouped under block 100"); - - let tip = 100100; - // Pruned block is larger than the target block - let pruned_block = Some(800); - - let result = config.group_by_block(tip, pruned_block).unwrap(); - - // Expect the entry to be grouped under block 800 which is larger than tip - distance - assert_eq!(result.len(), 1); - assert_eq!(result[&801], vec![&address], "Address should be grouped under block 800"); - } - - #[test] - fn test_lowest_block_with_distance_empty_config() { - let config = ReceiptsLogPruneConfig(BTreeMap::new()); - let tip = 1000; - let pruned_block = None; - - let result = config.lowest_block_with_distance(tip, pruned_block).unwrap(); - assert_eq!(result, None, "The result should be None when the config is empty"); - } - - #[test] - fn test_lowest_block_with_distance_no_distance_mode() { - let mut config_map = BTreeMap::new(); - let address = Address::new([1; 20]); - let prune_mode = PruneMode::Before(500); - config_map.insert(address, prune_mode); - - let config = ReceiptsLogPruneConfig(config_map); - let tip = 1000; - let pruned_block = None; - - let result = config.lowest_block_with_distance(tip, pruned_block).unwrap(); - assert_eq!(result, None, "The result should be None when there are no Distance modes"); - } - - #[test] - fn test_lowest_block_with_distance_single_entry() { - let mut config_map = BTreeMap::new(); - let address = Address::new([1; 20]); - let prune_mode = PruneMode::Distance(100000); - config_map.insert(address, prune_mode); - - let config = ReceiptsLogPruneConfig(config_map); - - let tip = 100100; - let pruned_block = Some(400); - - // Expect the lowest block to be 400 as 400 > 100100 - 100000 (tip - distance) - assert_eq!( - config.lowest_block_with_distance(tip, pruned_block).unwrap(), - Some(400), - "The lowest block should be 400" - ); - - let tip = 100100; - let pruned_block = Some(50); - - // Expect the lowest block to be 100 as 100 > 50 (pruned block) - assert_eq!( - config.lowest_block_with_distance(tip, pruned_block).unwrap(), - Some(100), - "The lowest block should be 100" - ); - } - - #[test] - fn test_lowest_block_with_distance_multiple_entries_last() { - let mut config_map = BTreeMap::new(); - let address1 = Address::new([1; 20]); - let address2 = Address::new([2; 20]); - let prune_mode1 = PruneMode::Distance(100100); - let prune_mode2 = PruneMode::Distance(100300); - config_map.insert(address1, prune_mode1); - config_map.insert(address2, prune_mode2); - - let config = ReceiptsLogPruneConfig(config_map); - let tip = 200300; - let pruned_block = Some(100); - - // The lowest block should be 200300 - 100300 = 100000: - // - First iteration will return 100200 => 200300 - 100100 = 100200 - // - Second iteration will return 100000 => 200300 - 100300 = 100000 < 100200 - // - Final result is 100000 - assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000)); - } - - #[test] - fn test_lowest_block_with_distance_multiple_entries_first() { - let mut config_map = BTreeMap::new(); - let address1 = Address::new([1; 20]); - let address2 = Address::new([2; 20]); - let prune_mode1 = PruneMode::Distance(100400); - let prune_mode2 = PruneMode::Distance(100300); - config_map.insert(address1, prune_mode1); - config_map.insert(address2, prune_mode2); - - let config = ReceiptsLogPruneConfig(config_map); - let tip = 200300; - let pruned_block = Some(100); - - // The lowest block should be 200300 - 100400 = 99900: - // - First iteration, lowest block is 200300 - 100400 = 99900 - // - Second iteration, lowest block is still 99900 < 200300 - 100300 = 100000 - // - Final result is 99900 - assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(99900)); - } - - #[test] - fn test_lowest_block_with_distance_multiple_entries_pruned_block() { - let mut config_map = BTreeMap::new(); - let address1 = Address::new([1; 20]); - let address2 = Address::new([2; 20]); - let prune_mode1 = PruneMode::Distance(100400); - let prune_mode2 = PruneMode::Distance(100300); - config_map.insert(address1, prune_mode1); - config_map.insert(address2, prune_mode2); - - let config = ReceiptsLogPruneConfig(config_map); - let tip = 200300; - let pruned_block = Some(100000); - - // The lowest block should be 100000 because: - // - Lowest is 200300 - 100400 = 99900 < 200300 - 100300 = 100000 - // - Lowest is compared to the pruned block 100000: 100000 > 99900 - // - Finally the lowest block is 100000 - assert_eq!(config.lowest_block_with_distance(tip, pruned_block).unwrap(), Some(100000)); - } -} diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index 3ff18554a9b..bb61c006cdc 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -2,7 +2,7 @@ use alloy_primitives::BlockNumber; use derive_more::Display; use thiserror::Error; -use crate::{PruneCheckpoint, PruneMode, PruneSegment, ReceiptsLogPruneConfig}; +use crate::{PruneCheckpoint, PruneMode, PruneSegment}; /// Minimum distance from the tip necessary for the node to work correctly: /// 1. Minimum 2 epochs (32 blocks per epoch) required to handle any reorg according to the @@ -99,16 +99,10 @@ pub struct PruneModes { ) )] pub merkle_changesets: PruneMode, - /// Receipts pruning configuration by retaining only those receipts that contain logs emitted - /// by the specified addresses, discarding others. This setting is overridden by `receipts`. - /// - /// The [`BlockNumber`](`crate::BlockNumber`) represents the starting block from which point - /// onwards the receipts are preserved. - #[cfg_attr( - any(test, feature = "serde"), - serde(skip_serializing_if = "ReceiptsLogPruneConfig::is_empty") - )] - pub receipts_log_filter: ReceiptsLogPruneConfig, + /// Receipts log filtering has been deprecated and will be removed in a future release. + #[deprecated] + #[cfg_attr(any(test, feature = "serde"), serde(skip))] + pub receipts_log_filter: (), } impl Default for PruneModes { @@ -121,14 +115,15 @@ impl Default for PruneModes { storage_history: None, bodies_history: None, merkle_changesets: default_merkle_changesets_mode(), - receipts_log_filter: ReceiptsLogPruneConfig::default(), + #[expect(deprecated)] + receipts_log_filter: (), } } } impl PruneModes { /// Sets pruning to all targets. - pub fn all() -> Self { + pub const fn all() -> Self { Self { sender_recovery: Some(PruneMode::Full), transaction_lookup: Some(PruneMode::Full), @@ -137,13 +132,14 @@ impl PruneModes { storage_history: Some(PruneMode::Full), bodies_history: Some(PruneMode::Full), merkle_changesets: PruneMode::Full, - receipts_log_filter: Default::default(), + #[expect(deprecated)] + receipts_log_filter: (), } } /// Returns whether there is any kind of receipt pruning configuration. - pub fn has_receipts_pruning(&self) -> bool { - self.receipts.is_some() || !self.receipts_log_filter.is_empty() + pub const fn has_receipts_pruning(&self) -> bool { + self.receipts.is_some() } /// Returns an error if we can't unwind to the targeted block because the target block is diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index 1666e79baf3..adfc87c5ccc 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -660,7 +660,7 @@ where mod tests { use super::*; use crate::{stages::MERKLE_STAGE_DEFAULT_REBUILD_THRESHOLD, test_utils::TestStageDB}; - use alloy_primitives::{address, hex_literal::hex, keccak256, Address, B256, U256}; + use alloy_primitives::{address, hex_literal::hex, keccak256, B256, U256}; use alloy_rlp::Decodable; use assert_matches::assert_matches; use reth_chainspec::ChainSpecBuilder; @@ -677,9 +677,7 @@ mod tests { DatabaseProviderFactory, ReceiptProvider, StaticFileProviderFactory, }; use reth_prune::PruneModes; - use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig}; use reth_stages_api::StageUnitCheckpoint; - use std::collections::BTreeMap; fn stage() -> ExecutionStage { let evm_config = @@ -896,20 +894,11 @@ mod tests { // If there is a pruning configuration, then it's forced to use the database. // This way we test both cases. let modes = [None, Some(PruneModes::default())]; - let random_filter = ReceiptsLogPruneConfig(BTreeMap::from([( - Address::random(), - PruneMode::Distance(100000), - )])); // Tests node with database and node with static files - for mut mode in modes { + for mode in modes { let mut provider = factory.database_provider_rw().unwrap(); - if let Some(mode) = &mut mode { - // Simulating a full node where we write receipts to database - mode.receipts_log_filter = random_filter.clone(); - } - let mut execution_stage = stage(); provider.set_prune_modes(mode.clone().unwrap_or_default()); @@ -1033,18 +1022,9 @@ mod tests { // If there is a pruning configuration, then it's forced to use the database. // This way we test both cases. let modes = [None, Some(PruneModes::default())]; - let random_filter = ReceiptsLogPruneConfig(BTreeMap::from([( - Address::random(), - PruneMode::Before(100000), - )])); // Tests node with database and node with static files - for mut mode in modes { - if let Some(mode) = &mut mode { - // Simulating a full node where we write receipts to database - mode.receipts_log_filter = random_filter.clone(); - } - + for mode in modes { // Test Execution let mut execution_stage = stage(); provider.set_prune_modes(mode.clone().unwrap_or_default()); diff --git a/crates/static-file/static-file/src/static_file_producer.rs b/crates/static-file/static-file/src/static_file_producer.rs index b6d205a42e1..185fbf7c498 100644 --- a/crates/static-file/static-file/src/static_file_producer.rs +++ b/crates/static-file/static-file/src/static_file_producer.rs @@ -207,19 +207,17 @@ where headers: finalized_block_numbers.headers.and_then(|finalized_block_number| { self.get_static_file_target(highest_static_files.headers, finalized_block_number) }), - // StaticFile receipts only if they're not pruned according to the user configuration - receipts: if self.prune_modes.receipts.is_none() && - self.prune_modes.receipts_log_filter.is_empty() - { - finalized_block_numbers.receipts.and_then(|finalized_block_number| { + receipts: finalized_block_numbers + .receipts + // StaticFile receipts only if they're not pruned according to the user + // configuration + .filter(|_| !self.prune_modes.has_receipts_pruning()) + .and_then(|finalized_block_number| { self.get_static_file_target( highest_static_files.receipts, finalized_block_number, ) - }) - } else { - None - }, + }), transactions: finalized_block_numbers.transactions.and_then(|finalized_block_number| { self.get_static_file_target( highest_static_files.transactions, diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 873b10b0cfc..5d3b5280cda 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -96,7 +96,7 @@ impl ProviderFactory { } /// Sets the pruning configuration for an existing [`ProviderFactory`]. - pub fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { + pub const fn with_prune_modes(mut self, prune_modes: PruneModes) -> Self { self.prune_modes = prune_modes; self } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 31e87b46e62..d5e49d822b2 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -22,7 +22,7 @@ use crate::{ }; use alloy_consensus::{ transaction::{SignerRecoverable, TransactionMeta, TxHashRef}, - BlockHeader, TxReceipt, + BlockHeader, }; use alloy_eips::BlockHashOrNumber; use alloy_primitives::{ @@ -214,7 +214,7 @@ impl DatabaseProvider { #[cfg(feature = "test-utils")] /// Sets the prune modes for provider. - pub fn set_prune_modes(&mut self, prune_modes: PruneModes) { + pub const fn set_prune_modes(&mut self, prune_modes: PruneModes) { self.prune_modes = prune_modes; } } @@ -1621,20 +1621,11 @@ impl StateWriter .then(|| self.static_file_provider.get_writer(first_block, StaticFileSegment::Receipts)) .transpose()?; - let has_contract_log_filter = !self.prune_modes.receipts_log_filter.is_empty(); - let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?; - // All receipts from the last 128 blocks are required for blockchain tree, even with // [`PruneSegment::ContractLogs`]. let prunable_receipts = PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(first_block, tip); - // Prepare set of addresses which logs should not be pruned. - let mut allowed_addresses: HashSet = HashSet::new(); - for (_, addresses) in contract_log_pruner.range(..first_block) { - allowed_addresses.extend(addresses.iter().copied()); - } - for (idx, (receipts, first_tx_index)) in execution_outcome.receipts.iter().zip(block_indices).enumerate() { @@ -1654,21 +1645,8 @@ impl StateWriter continue } - // If there are new addresses to retain after this block number, track them - if let Some(new_addresses) = contract_log_pruner.get(&block_number) { - allowed_addresses.extend(new_addresses.iter().copied()); - } - for (idx, receipt) in receipts.iter().enumerate() { let receipt_idx = first_tx_index + idx as u64; - // Skip writing receipt if log filter is active and it does not have any logs to - // retain - if prunable_receipts && - has_contract_log_filter && - !receipt.logs().iter().any(|log| allowed_addresses.contains(&log.address)) - { - continue - } if let Some(writer) = &mut receipts_static_writer { writer.append_receipt(receipt_idx, receipt)?; diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 1f2ce545bc0..047b8ba0008 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -776,9 +776,6 @@ Pruning: --prune.receipts.before Prune receipts before the specified block number. The specified block number is not pruned - --prune.receiptslogfilter - Configure receipts log filter. Format: <`address`>:<`prune_mode`>... where <`prune_mode`> can be 'full', 'distance:<`blocks`>', or 'before:<`block_number`>' - --prune.accounthistory.full Prunes all account history