1mod receipts;
2mod set;
3mod user;
4
5use crate::{PruneLimiter, PrunerError};
6use alloy_primitives::{BlockNumber, TxNumber};
7use reth_provider::{
8 errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter, StaticFileProviderFactory,
9};
10use reth_prune_types::{
11 PruneCheckpoint, PruneMode, PruneProgress, PrunePurpose, PruneSegment, SegmentOutput,
12 SegmentOutputCheckpoint,
13};
14use reth_stages_types::StageId;
15use reth_static_file_types::StaticFileSegment;
16pub use set::SegmentSet;
17use std::{fmt::Debug, ops::RangeInclusive};
18use tracing::{debug, error};
19pub use user::{
20 AccountHistory, Bodies, Receipts as UserReceipts, ReceiptsByLogs, SenderRecovery,
21 StorageHistory, TransactionLookup,
22};
23
24pub(crate) fn prune_static_files<Provider>(
33 provider: &Provider,
34 input: PruneInput,
35 segment: StaticFileSegment,
36) -> Result<SegmentOutput, PrunerError>
37where
38 Provider: StaticFileProviderFactory,
39{
40 let deleted_headers =
41 provider.static_file_provider().delete_segment_below_block(segment, input.to_block + 1)?;
42
43 if deleted_headers.is_empty() {
44 return Ok(SegmentOutput {
45 progress: PruneProgress::Finished,
46 pruned: 0,
47 checkpoint: input
48 .previous_checkpoint
49 .map(SegmentOutputCheckpoint::from_prune_checkpoint),
50 })
51 }
52
53 let tx_ranges = deleted_headers.iter().filter_map(|header| header.tx_range());
54
55 let pruned = tx_ranges.clone().map(|range| range.len()).sum::<u64>() as usize;
56
57 let checkpoint_block = deleted_headers
59 .iter()
60 .filter_map(|header| header.block_range())
61 .map(|range| range.end())
62 .max();
63
64 Ok(SegmentOutput {
65 progress: PruneProgress::Finished,
66 pruned,
67 checkpoint: Some(SegmentOutputCheckpoint {
68 block_number: checkpoint_block,
69 tx_number: tx_ranges.map(|range| range.end()).max(),
70 }),
71 })
72}
73
74pub(crate) fn delete_static_files_segment<Provider>(
79 provider: &Provider,
80 input: PruneInput,
81 segment: StaticFileSegment,
82) -> Result<SegmentOutput, PrunerError>
83where
84 Provider: StaticFileProviderFactory,
85{
86 let deleted_headers = provider.static_file_provider().delete_segment(segment)?;
87
88 if deleted_headers.is_empty() {
89 return Ok(SegmentOutput::done())
90 }
91
92 let tx_ranges = deleted_headers.iter().filter_map(|header| header.tx_range());
93
94 let pruned = tx_ranges.clone().map(|range| range.len()).sum::<u64>() as usize;
95
96 Ok(SegmentOutput {
97 progress: PruneProgress::Finished,
98 pruned,
99 checkpoint: Some(SegmentOutputCheckpoint {
100 block_number: Some(input.to_block),
101 tx_number: tx_ranges.map(|range| range.end()).max(),
102 }),
103 })
104}
105
106pub trait Segment<Provider>: Debug + Send + Sync {
114 fn segment(&self) -> PruneSegment;
116
117 fn mode(&self) -> Option<PruneMode>;
119
120 fn purpose(&self) -> PrunePurpose;
122
123 fn prune(&self, provider: &Provider, input: PruneInput) -> Result<SegmentOutput, PrunerError>;
125
126 fn save_checkpoint(
128 &self,
129 provider: &Provider,
130 checkpoint: PruneCheckpoint,
131 ) -> ProviderResult<()>
132 where
133 Provider: PruneCheckpointWriter,
134 {
135 provider.save_prune_checkpoint(self.segment(), checkpoint)
136 }
137
138 fn required_stage(&self) -> Option<StageId> {
143 None
144 }
145}
146
147#[derive(Debug)]
149#[cfg_attr(test, derive(Clone))]
150pub struct PruneInput {
151 pub(crate) previous_checkpoint: Option<PruneCheckpoint>,
152 pub(crate) to_block: BlockNumber,
154 pub(crate) limiter: PruneLimiter,
156}
157
158impl PruneInput {
159 pub(crate) fn get_next_tx_num_range<Provider: BlockReader>(
168 &self,
169 provider: &Provider,
170 ) -> ProviderResult<Option<RangeInclusive<TxNumber>>> {
171 let from_tx_number = self.previous_checkpoint
172 .and_then(|checkpoint| match (checkpoint.tx_number, checkpoint.block_number) {
174 (Some(tx_number), _) => Some(tx_number + 1),
175 (None, Some(_)) => {
177 debug!(target: "pruner", ?checkpoint, "Prune checkpoint has no transaction number, starting from transaction 0");
178 None
179 }
180 (None, None) => {
181 error!(target: "pruner", ?checkpoint, "Expected transaction number in prune checkpoint, found None");
182 None
183 }
184 })
185 .unwrap_or_default();
187
188 let to_tx_number = match provider.block_body_indices(self.to_block)? {
189 Some(body) => {
190 let last_tx = body.last_tx_num();
191 if last_tx + body.tx_count() == 0 {
192 return Ok(None)
196 }
197 last_tx
198 }
199 None => return Ok(None),
200 };
201
202 let range = from_tx_number..=to_tx_number;
203 if range.is_empty() {
204 return Ok(None)
205 }
206
207 Ok(Some(range))
208 }
209
210 pub(crate) fn get_next_block_range(&self) -> Option<RangeInclusive<BlockNumber>> {
219 let from_block = self.get_start_next_block_range();
220 let range = from_block..=self.to_block;
221 if range.is_empty() {
222 return None
223 }
224
225 Some(range)
226 }
227
228 pub(crate) fn get_start_next_block_range(&self) -> u64 {
233 self.previous_checkpoint
234 .and_then(|checkpoint| checkpoint.block_number)
235 .map(|block_number| block_number + 1)
237 .unwrap_or(0)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use alloy_primitives::B256;
246 use reth_provider::{
247 providers::BlockchainProvider,
248 test_utils::{create_test_provider_factory, MockEthProvider},
249 BlockWriter,
250 };
251 use reth_testing_utils::generators::{self, random_block_range, BlockRangeParams};
252
253 #[test]
254 fn test_prune_input_get_next_tx_num_range_no_to_block() {
255 let input = PruneInput {
256 previous_checkpoint: None,
257 to_block: 10,
258 limiter: PruneLimiter::default(),
259 };
260
261 let provider = MockEthProvider::default();
263
264 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
266 assert!(range.is_none());
267 }
268
269 #[test]
270 fn test_prune_input_get_next_tx_num_range_no_tx() {
271 let input = PruneInput {
272 previous_checkpoint: None,
273 to_block: 10,
274 limiter: PruneLimiter::default(),
275 };
276
277 let mut rng = generators::rng();
278 let factory = create_test_provider_factory();
279
280 let blocks = random_block_range(
282 &mut rng,
283 0..=10,
284 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() },
285 );
286
287 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
289 for block in &blocks {
290 provider_rw
291 .insert_block(
292 &block.clone().try_recover().expect("failed to seal block with senders"),
293 )
294 .expect("failed to insert block");
295 }
296 provider_rw.commit().expect("failed to commit");
297
298 let provider = BlockchainProvider::new(factory).unwrap();
300
301 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
303 assert!(range.is_none());
304 }
305
306 #[test]
307 fn test_prune_input_get_next_tx_num_range_valid() {
308 let input = PruneInput {
310 previous_checkpoint: None,
311 to_block: 10,
312 limiter: PruneLimiter::default(),
313 };
314
315 let mut rng = generators::rng();
316 let factory = create_test_provider_factory();
317
318 let blocks = random_block_range(
320 &mut rng,
321 0..=10,
322 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
323 );
324
325 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
327 for block in &blocks {
328 provider_rw
329 .insert_block(
330 &block.clone().try_recover().expect("failed to seal block with senders"),
331 )
332 .expect("failed to insert block");
333 }
334 provider_rw.commit().expect("failed to commit");
335
336 let provider = BlockchainProvider::new(factory).unwrap();
338
339 let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
341
342 let num_txs = blocks.iter().map(|block| block.transaction_count() as u64).sum::<u64>();
344
345 assert_eq!(range, 0..=num_txs - 1);
346 }
347
348 #[test]
349 fn test_prune_input_get_next_tx_checkpoint_without_tx_number() {
350 let input = PruneInput {
352 previous_checkpoint: Some(PruneCheckpoint {
353 block_number: Some(5),
354 tx_number: None,
355 prune_mode: PruneMode::Full,
356 }),
357 to_block: 10,
358 limiter: PruneLimiter::default(),
359 };
360
361 let mut rng = generators::rng();
362 let factory = create_test_provider_factory();
363
364 let blocks = random_block_range(
366 &mut rng,
367 0..=10,
368 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
369 );
370
371 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
373 for block in &blocks {
374 provider_rw
375 .insert_block(
376 &block.clone().try_recover().expect("failed to seal block with senders"),
377 )
378 .expect("failed to insert block");
379 }
380 provider_rw.commit().expect("failed to commit");
381
382 let provider = BlockchainProvider::new(factory).unwrap();
384
385 let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
387
388 let num_txs = blocks.iter().map(|block| block.transaction_count() as u64).sum::<u64>();
390
391 assert_eq!(range, 0..=num_txs - 1,);
392 }
393
394 #[test]
395 fn test_prune_input_get_next_tx_empty_range() {
396 let mut rng = generators::rng();
398 let factory = create_test_provider_factory();
399
400 let blocks = random_block_range(
402 &mut rng,
403 0..=10,
404 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
405 );
406
407 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
409 for block in &blocks {
410 provider_rw
411 .insert_block(
412 &block.clone().try_recover().expect("failed to seal block with senders"),
413 )
414 .expect("failed to insert block");
415 }
416 provider_rw.commit().expect("failed to commit");
417
418 let provider = BlockchainProvider::new(factory).unwrap();
420
421 let num_txs = blocks.iter().map(|block| block.transaction_count() as u64).sum::<u64>();
424 let max_range = num_txs - 1;
425
426 let input = PruneInput {
428 previous_checkpoint: Some(PruneCheckpoint {
429 block_number: Some(5),
430 tx_number: Some(max_range),
431 prune_mode: PruneMode::Full,
432 }),
433 to_block: 10,
434 limiter: PruneLimiter::default(),
435 };
436
437 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
439 assert!(range.is_none());
440 }
441}