1mod receipts;
2mod set;
3mod static_file;
4mod user;
5
6use crate::{PruneLimiter, PrunerError};
7use alloy_primitives::{BlockNumber, TxNumber};
8use reth_provider::{errors::provider::ProviderResult, BlockReader, PruneCheckpointWriter};
9use reth_prune_types::{PruneCheckpoint, PruneMode, PrunePurpose, PruneSegment, SegmentOutput};
10pub use set::SegmentSet;
11pub use static_file::{
12 Headers as StaticFileHeaders, Receipts as StaticFileReceipts,
13 Transactions as StaticFileTransactions,
14};
15use std::{fmt::Debug, ops::RangeInclusive};
16use tracing::error;
17pub use user::{
18 AccountHistory, Receipts as UserReceipts, ReceiptsByLogs, SenderRecovery, StorageHistory,
19 TransactionLookup,
20};
21
22pub trait Segment<Provider>: Debug + Send + Sync {
30 fn segment(&self) -> PruneSegment;
32
33 fn mode(&self) -> Option<PruneMode>;
35
36 fn purpose(&self) -> PrunePurpose;
38
39 fn prune(&self, provider: &Provider, input: PruneInput) -> Result<SegmentOutput, PrunerError>;
41
42 fn save_checkpoint(
44 &self,
45 provider: &Provider,
46 checkpoint: PruneCheckpoint,
47 ) -> ProviderResult<()>
48 where
49 Provider: PruneCheckpointWriter,
50 {
51 provider.save_prune_checkpoint(self.segment(), checkpoint)
52 }
53}
54
55#[derive(Debug)]
57#[cfg_attr(test, derive(Clone))]
58pub struct PruneInput {
59 pub(crate) previous_checkpoint: Option<PruneCheckpoint>,
60 pub(crate) to_block: BlockNumber,
62 pub(crate) limiter: PruneLimiter,
64}
65
66impl PruneInput {
67 pub(crate) fn get_next_tx_num_range<Provider: BlockReader>(
76 &self,
77 provider: &Provider,
78 ) -> ProviderResult<Option<RangeInclusive<TxNumber>>> {
79 let from_tx_number = self.previous_checkpoint
80 .and_then(|checkpoint| match checkpoint.tx_number {
82 Some(tx_number) => Some(tx_number + 1),
83 _ => {
84 error!(target: "pruner", ?checkpoint, "Expected transaction number in prune checkpoint, found None");
85 None
86 },
87 })
88 .unwrap_or_default();
90
91 let to_tx_number = match provider.block_body_indices(self.to_block)? {
92 Some(body) => {
93 let last_tx = body.last_tx_num();
94 if last_tx + body.tx_count() == 0 {
95 return Ok(None)
99 }
100 last_tx
101 }
102 None => return Ok(None),
103 };
104
105 let range = from_tx_number..=to_tx_number;
106 if range.is_empty() {
107 return Ok(None)
108 }
109
110 Ok(Some(range))
111 }
112
113 pub(crate) fn get_next_block_range(&self) -> Option<RangeInclusive<BlockNumber>> {
122 let from_block = self.get_start_next_block_range();
123 let range = from_block..=self.to_block;
124 if range.is_empty() {
125 return None
126 }
127
128 Some(range)
129 }
130
131 pub(crate) fn get_start_next_block_range(&self) -> u64 {
136 self.previous_checkpoint
137 .and_then(|checkpoint| checkpoint.block_number)
138 .map(|block_number| block_number + 1)
140 .unwrap_or(0)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use alloy_primitives::B256;
149 use reth_provider::{
150 providers::BlockchainProvider,
151 test_utils::{create_test_provider_factory, MockEthProvider},
152 };
153 use reth_testing_utils::generators::{self, random_block_range, BlockRangeParams};
154
155 #[test]
156 fn test_prune_input_get_next_tx_num_range_no_to_block() {
157 let input = PruneInput {
158 previous_checkpoint: None,
159 to_block: 10,
160 limiter: PruneLimiter::default(),
161 };
162
163 let provider = MockEthProvider::default();
165
166 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
168 assert!(range.is_none());
169 }
170
171 #[test]
172 fn test_prune_input_get_next_tx_num_range_no_tx() {
173 let input = PruneInput {
174 previous_checkpoint: None,
175 to_block: 10,
176 limiter: PruneLimiter::default(),
177 };
178
179 let mut rng = generators::rng();
180 let factory = create_test_provider_factory();
181
182 let blocks = random_block_range(
184 &mut rng,
185 0..=10,
186 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() },
187 );
188
189 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
191 for block in &blocks {
192 provider_rw
193 .insert_historical_block(
194 block.clone().try_recover().expect("failed to seal block with senders"),
195 )
196 .expect("failed to insert block");
197 }
198 provider_rw.commit().expect("failed to commit");
199
200 let provider = BlockchainProvider::new(factory).unwrap();
202
203 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
205 assert!(range.is_none());
206 }
207
208 #[test]
209 fn test_prune_input_get_next_tx_num_range_valid() {
210 let input = PruneInput {
212 previous_checkpoint: None,
213 to_block: 10,
214 limiter: PruneLimiter::default(),
215 };
216
217 let mut rng = generators::rng();
218 let factory = create_test_provider_factory();
219
220 let blocks = random_block_range(
222 &mut rng,
223 0..=10,
224 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
225 );
226
227 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
229 for block in &blocks {
230 provider_rw
231 .insert_historical_block(
232 block.clone().try_recover().expect("failed to seal block with senders"),
233 )
234 .expect("failed to insert block");
235 }
236 provider_rw.commit().expect("failed to commit");
237
238 let provider = BlockchainProvider::new(factory).unwrap();
240
241 let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
243
244 let num_txs = blocks.iter().map(|block| block.transaction_count() as u64).sum::<u64>();
246
247 assert_eq!(range, 0..=num_txs - 1);
248 }
249
250 #[test]
251 fn test_prune_input_get_next_tx_checkpoint_without_tx_number() {
252 let input = PruneInput {
254 previous_checkpoint: Some(PruneCheckpoint {
255 block_number: Some(5),
256 tx_number: None,
257 prune_mode: PruneMode::Full,
258 }),
259 to_block: 10,
260 limiter: PruneLimiter::default(),
261 };
262
263 let mut rng = generators::rng();
264 let factory = create_test_provider_factory();
265
266 let blocks = random_block_range(
268 &mut rng,
269 0..=10,
270 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
271 );
272
273 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
275 for block in &blocks {
276 provider_rw
277 .insert_historical_block(
278 block.clone().try_recover().expect("failed to seal block with senders"),
279 )
280 .expect("failed to insert block");
281 }
282 provider_rw.commit().expect("failed to commit");
283
284 let provider = BlockchainProvider::new(factory).unwrap();
286
287 let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
289
290 let num_txs = blocks.iter().map(|block| block.transaction_count() as u64).sum::<u64>();
292
293 assert_eq!(range, 0..=num_txs - 1,);
294 }
295
296 #[test]
297 fn test_prune_input_get_next_tx_empty_range() {
298 let mut rng = generators::rng();
300 let factory = create_test_provider_factory();
301
302 let blocks = random_block_range(
304 &mut rng,
305 0..=10,
306 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
307 );
308
309 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
311 for block in &blocks {
312 provider_rw
313 .insert_historical_block(
314 block.clone().try_recover().expect("failed to seal block with senders"),
315 )
316 .expect("failed to insert block");
317 }
318 provider_rw.commit().expect("failed to commit");
319
320 let provider = BlockchainProvider::new(factory).unwrap();
322
323 let num_txs = blocks.iter().map(|block| block.transaction_count() as u64).sum::<u64>();
326 let max_range = num_txs - 1;
327
328 let input = PruneInput {
330 previous_checkpoint: Some(PruneCheckpoint {
331 block_number: Some(5),
332 tx_number: Some(max_range),
333 prune_mode: PruneMode::Full,
334 }),
335 to_block: 10,
336 limiter: PruneLimiter::default(),
337 };
338
339 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
341 assert!(range.is_none());
342 }
343}