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_primitives_traits::BlockBody;
150 use reth_provider::{
151 providers::BlockchainProvider2,
152 test_utils::{create_test_provider_factory, MockEthProvider},
153 };
154 use reth_testing_utils::generators::{self, random_block_range, BlockRangeParams};
155
156 #[test]
157 fn test_prune_input_get_next_tx_num_range_no_to_block() {
158 let input = PruneInput {
159 previous_checkpoint: None,
160 to_block: 10,
161 limiter: PruneLimiter::default(),
162 };
163
164 let provider = MockEthProvider::default();
166
167 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
169 assert!(range.is_none());
170 }
171
172 #[test]
173 fn test_prune_input_get_next_tx_num_range_no_tx() {
174 let input = PruneInput {
175 previous_checkpoint: None,
176 to_block: 10,
177 limiter: PruneLimiter::default(),
178 };
179
180 let mut rng = generators::rng();
181 let factory = create_test_provider_factory();
182
183 let blocks = random_block_range(
185 &mut rng,
186 0..=10,
187 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() },
188 );
189
190 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
192 for block in &blocks {
193 provider_rw
194 .insert_historical_block(
195 block.clone().seal_with_senders().expect("failed to seal block with senders"),
196 )
197 .expect("failed to insert block");
198 }
199 provider_rw.commit().expect("failed to commit");
200
201 let provider = BlockchainProvider2::new(factory).unwrap();
203
204 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
206 assert!(range.is_none());
207 }
208
209 #[test]
210 fn test_prune_input_get_next_tx_num_range_valid() {
211 let input = PruneInput {
213 previous_checkpoint: None,
214 to_block: 10,
215 limiter: PruneLimiter::default(),
216 };
217
218 let mut rng = generators::rng();
219 let factory = create_test_provider_factory();
220
221 let blocks = random_block_range(
223 &mut rng,
224 0..=10,
225 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
226 );
227
228 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
230 for block in &blocks {
231 provider_rw
232 .insert_historical_block(
233 block.clone().seal_with_senders().expect("failed to seal block with senders"),
234 )
235 .expect("failed to insert block");
236 }
237 provider_rw.commit().expect("failed to commit");
238
239 let provider = BlockchainProvider2::new(factory).unwrap();
241
242 let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
244
245 let num_txs =
247 blocks.iter().map(|block| block.body.transactions().len() as u64).sum::<u64>();
248
249 assert_eq!(range, 0..=num_txs - 1);
250 }
251
252 #[test]
253 fn test_prune_input_get_next_tx_checkpoint_without_tx_number() {
254 let input = PruneInput {
256 previous_checkpoint: Some(PruneCheckpoint {
257 block_number: Some(5),
258 tx_number: None,
259 prune_mode: PruneMode::Full,
260 }),
261 to_block: 10,
262 limiter: PruneLimiter::default(),
263 };
264
265 let mut rng = generators::rng();
266 let factory = create_test_provider_factory();
267
268 let blocks = random_block_range(
270 &mut rng,
271 0..=10,
272 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
273 );
274
275 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
277 for block in &blocks {
278 provider_rw
279 .insert_historical_block(
280 block.clone().seal_with_senders().expect("failed to seal block with senders"),
281 )
282 .expect("failed to insert block");
283 }
284 provider_rw.commit().expect("failed to commit");
285
286 let provider = BlockchainProvider2::new(factory).unwrap();
288
289 let range = input.get_next_tx_num_range(&provider).expect("Expected range").unwrap();
291
292 let num_txs =
294 blocks.iter().map(|block| block.body.transactions().len() as u64).sum::<u64>();
295
296 assert_eq!(range, 0..=num_txs - 1,);
297 }
298
299 #[test]
300 fn test_prune_input_get_next_tx_empty_range() {
301 let mut rng = generators::rng();
303 let factory = create_test_provider_factory();
304
305 let blocks = random_block_range(
307 &mut rng,
308 0..=10,
309 BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..5, ..Default::default() },
310 );
311
312 let provider_rw = factory.provider_rw().expect("failed to get provider_rw");
314 for block in &blocks {
315 provider_rw
316 .insert_historical_block(
317 block.clone().seal_with_senders().expect("failed to seal block with senders"),
318 )
319 .expect("failed to insert block");
320 }
321 provider_rw.commit().expect("failed to commit");
322
323 let provider = BlockchainProvider2::new(factory).unwrap();
325
326 let num_txs =
329 blocks.iter().map(|block| block.body.transactions().len() as u64).sum::<u64>();
330 let max_range = num_txs - 1;
331
332 let input = PruneInput {
334 previous_checkpoint: Some(PruneCheckpoint {
335 block_number: Some(5),
336 tx_number: Some(max_range),
337 prune_mode: PruneMode::Full,
338 }),
339 to_block: 10,
340 limiter: PruneLimiter::default(),
341 };
342
343 let range = input.get_next_tx_num_range(&provider).expect("Expected range");
345 assert!(range.is_none());
346 }
347}