1use crate::download::{
2 manifest::{ComponentManifest, ComponentSelection, SnapshotComponentType, SnapshotManifest},
3 SelectionPreset,
4};
5use reth_chainspec::{EthereumHardfork, EthereumHardforks};
6use reth_config::config::{BlocksPerFileConfig, Config, PruneConfig, StaticFilesConfig};
7use reth_db::tables;
8use reth_db_api::transaction::{DbTx, DbTxMut};
9use reth_node_core::args::DefaultPruningValues;
10use reth_prune_types::{
11 PruneCheckpoint, PruneMode, PruneSegment, MINIMUM_DISTANCE, MINIMUM_UNWIND_SAFE_DISTANCE,
12};
13use reth_stages_types::StageCheckpoint;
14use std::{collections::BTreeMap, path::Path};
15use tracing::info;
16
17pub fn write_config(config: &Config, data_dir: &Path) -> eyre::Result<bool> {
21 let config_path = data_dir.join("reth.toml");
22
23 if config_path.exists() {
24 info!(target: "reth::cli",
25 path = ?config_path,
26 "reth.toml already exists, skipping config generation"
27 );
28 return Ok(false);
29 }
30
31 let toml_str = toml::to_string_pretty(config)?;
32 reth_fs_util::write(&config_path, toml_str)?;
33
34 info!(target: "reth::cli",
35 path = ?config_path,
36 "Generated reth.toml based on downloaded components"
37 );
38
39 Ok(true)
40}
41
42pub(crate) fn write_prune_checkpoints_tx<Tx>(
44 tx: &Tx,
45 config: &Config,
46 selections: &BTreeMap<SnapshotComponentType, ComponentSelection>,
47 snapshot_block: u64,
48) -> eyre::Result<()>
49where
50 Tx: DbTx + DbTxMut,
51{
52 let segments = &config.prune.segments;
53
54 let checkpoints: Vec<(PruneSegment, PruneMode)> = [
56 (PruneSegment::SenderRecovery, segments.sender_recovery),
57 (PruneSegment::TransactionLookup, segments.transaction_lookup),
58 (PruneSegment::Receipts, segments.receipts),
59 (PruneSegment::AccountHistory, segments.account_history),
60 (PruneSegment::StorageHistory, segments.storage_history),
61 (PruneSegment::Bodies, segments.bodies_history),
62 ]
63 .into_iter()
64 .filter_map(|(segment, mode)| mode.map(|m| (segment, m)))
65 .collect();
66
67 if checkpoints.is_empty() {
68 return Ok(());
69 }
70
71 let tx_number =
73 tx.get::<tables::BlockBodyIndices>(snapshot_block)?.map(|indices| indices.last_tx_num());
74
75 for (segment, prune_mode) in &checkpoints {
76 let (block_number, tx_number) = match segment {
77 PruneSegment::AccountHistory | PruneSegment::StorageHistory => {
80 let component = if *segment == PruneSegment::AccountHistory {
81 SnapshotComponentType::AccountChangesets
82 } else {
83 SnapshotComponentType::StorageChangesets
84 };
85 let target = match selections.get(&component) {
86 Some(ComponentSelection::All) => None,
87 Some(ComponentSelection::Distance(distance)) => {
88 snapshot_block.checked_sub(*distance)
89 }
90 Some(ComponentSelection::Since(block)) => {
91 block.checked_sub(1).map(|target| target.min(snapshot_block))
92 }
93 Some(ComponentSelection::None) | None => Some(snapshot_block),
94 };
95 let Some(target) = target else { continue };
96 let existing = tx
98 .get::<tables::PruneCheckpoints>(*segment)?
99 .and_then(|checkpoint| checkpoint.block_number);
100 (existing.map_or(target, |existing| existing.max(target)), None)
101 }
102 _ => (snapshot_block, tx_number),
103 };
104 let checkpoint = PruneCheckpoint {
105 block_number: Some(block_number),
106 tx_number,
107 prune_mode: *prune_mode,
108 };
109
110 tx.put::<tables::PruneCheckpoints>(*segment, checkpoint)?;
111
112 info!(target: "reth::cli",
113 segment = %segment,
114 block = block_number,
115 tx = ?tx_number,
116 mode = ?prune_mode,
117 "Set prune checkpoint"
118 );
119 }
120
121 Ok(())
122}
123
124const INDEX_STAGE_IDS: [&str; 3] =
127 ["TransactionLookup", "IndexAccountHistory", "IndexStorageHistory"];
128
129const INDEX_PRUNE_SEGMENTS: [PruneSegment; 3] =
131 [PruneSegment::TransactionLookup, PruneSegment::AccountHistory, PruneSegment::StorageHistory];
132
133pub(crate) fn reset_index_stage_checkpoints_tx<Tx>(tx: &Tx) -> eyre::Result<()>
146where
147 Tx: DbTx + DbTxMut,
148{
149 for stage_id in INDEX_STAGE_IDS {
150 tx.put::<tables::StageCheckpoints>(stage_id.to_string(), StageCheckpoint::default())?;
151
152 tx.delete::<tables::StageCheckpointProgresses>(stage_id.to_string(), None)?;
154
155 info!(target: "reth::cli", stage = stage_id, "Reset stage checkpoint to block 0");
156 }
157
158 for segment in INDEX_PRUNE_SEGMENTS {
161 tx.delete::<tables::PruneCheckpoints>(segment, None)?;
162 }
163
164 Ok(())
165}
166
167pub(crate) fn config_for_selections(
172 selections: &BTreeMap<SnapshotComponentType, ComponentSelection>,
173 manifest: &SnapshotManifest,
174 preset: Option<SelectionPreset>,
175 chain_spec: Option<&impl EthereumHardforks>,
176) -> Config {
177 let selection_for = |ty| selections.get(&ty).copied().unwrap_or(ComponentSelection::None);
178
179 let tx_sel = selection_for(SnapshotComponentType::Transactions);
180 let senders_sel = selection_for(SnapshotComponentType::TransactionSenders);
181 let receipt_sel = selection_for(SnapshotComponentType::Receipts);
182 let account_cs_sel = selection_for(SnapshotComponentType::AccountChangesets);
183 let storage_cs_sel = selection_for(SnapshotComponentType::StorageChangesets);
184
185 let is_archive = [tx_sel, senders_sel, receipt_sel, account_cs_sel, storage_cs_sel]
187 .iter()
188 .all(|s| *s == ComponentSelection::All);
189
190 let blocks_per_file = |ty: SnapshotComponentType| -> Option<u64> {
192 match manifest.component(ty)? {
193 ComponentManifest::Chunked(c) => Some(c.blocks_per_file),
194 ComponentManifest::Single(_) => None,
195 }
196 };
197 let static_files = StaticFilesConfig {
198 blocks_per_file: BlocksPerFileConfig {
199 headers: blocks_per_file(SnapshotComponentType::Headers),
200 transactions: blocks_per_file(SnapshotComponentType::Transactions),
201 receipts: blocks_per_file(SnapshotComponentType::Receipts),
202 transaction_senders: blocks_per_file(SnapshotComponentType::TransactionSenders),
203 account_change_sets: blocks_per_file(SnapshotComponentType::AccountChangesets),
204 storage_change_sets: blocks_per_file(SnapshotComponentType::StorageChangesets),
205 },
206 };
207
208 if matches!(preset, Some(SelectionPreset::Archive)) {
209 return Config { static_files, ..Default::default() };
210 }
211
212 if matches!(preset, Some(SelectionPreset::Minimal)) {
213 return Config {
214 prune: PruneConfig {
215 segments: DefaultPruningValues::get_global().minimal_prune_modes.clone(),
216 ..Default::default()
217 },
218 static_files,
219 ..Default::default()
220 };
221 }
222
223 if matches!(preset, Some(SelectionPreset::Full)) {
224 let defaults = DefaultPruningValues::get_global();
225 let mut segments = defaults.full_prune_modes.clone();
226
227 if defaults.full_bodies_history_use_pre_merge {
228 segments.bodies_history = chain_spec.and_then(|chain_spec| {
229 chain_spec
230 .ethereum_fork_activation(EthereumHardfork::Paris)
231 .block_number()
232 .map(PruneMode::Before)
233 });
234 }
235
236 return Config {
237 prune: PruneConfig { segments, ..Default::default() },
238 static_files,
239 ..Default::default()
240 };
241 }
242
243 if is_archive {
244 return Config { static_files, ..Default::default() };
245 }
246
247 let mut config = Config::default();
248 let mut prune = PruneConfig::default();
249
250 if senders_sel != ComponentSelection::All {
251 prune.segments.sender_recovery = Some(PruneMode::Full);
252 }
253 prune.segments.transaction_lookup = Some(PruneMode::Full);
254
255 if let Some(mode) = selection_to_prune_mode(tx_sel, Some(MINIMUM_UNWIND_SAFE_DISTANCE)) {
256 prune.segments.bodies_history = Some(mode);
257 }
258
259 if let Some(mode) = selection_to_prune_mode(receipt_sel, Some(MINIMUM_DISTANCE)) {
260 prune.segments.receipts = Some(mode);
261 }
262
263 if let Some(mode) = selection_to_prune_mode(account_cs_sel, Some(MINIMUM_UNWIND_SAFE_DISTANCE))
264 {
265 prune.segments.account_history = Some(mode);
266 }
267
268 if let Some(mode) = selection_to_prune_mode(storage_cs_sel, Some(MINIMUM_UNWIND_SAFE_DISTANCE))
269 {
270 prune.segments.storage_history = Some(mode);
271 }
272
273 config.prune = prune;
274 config.static_files = static_files;
275 config
276}
277
278fn selection_to_prune_mode(
284 sel: ComponentSelection,
285 min_distance: Option<u64>,
286) -> Option<PruneMode> {
287 match sel {
288 ComponentSelection::All => None,
289 ComponentSelection::Distance(d) => {
290 Some(PruneMode::Distance(min_distance.map_or(d, |min| d.max(min))))
291 }
292 ComponentSelection::Since(block) => Some(PruneMode::Before(block)),
293 ComponentSelection::None => Some(min_distance.map_or(PruneMode::Full, PruneMode::Distance)),
294 }
295}
296
297pub(crate) fn describe_prune_config(config: &Config) -> Vec<String> {
299 let segments = &config.prune.segments;
300
301 [
302 ("sender_recovery", segments.sender_recovery),
303 ("transaction_lookup", segments.transaction_lookup),
304 ("bodies_history", segments.bodies_history),
305 ("receipts", segments.receipts),
306 ("account_history", segments.account_history),
307 ("storage_history", segments.storage_history),
308 ]
309 .into_iter()
310 .filter_map(|(name, mode)| mode.map(|m| format!("{name}={}", format_mode(&m))))
311 .collect()
312}
313
314fn format_mode(mode: &PruneMode) -> String {
316 match mode {
317 PruneMode::Full => "\"full\"".to_string(),
318 PruneMode::Distance(d) => format!("{{ distance = {d} }}"),
319 PruneMode::Before(b) => format!("{{ before = {b} }}"),
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326 use reth_db::Database;
327
328 fn empty_manifest() -> SnapshotManifest {
330 SnapshotManifest {
331 block: 0,
332 chain_id: 1,
333 storage_version: 2,
334 timestamp: 0,
335 base_url: None,
336 reth_version: None,
337 components: BTreeMap::new(),
338 extensions: Default::default(),
339 }
340 }
341
342 #[test]
343 fn write_prune_checkpoints_sets_all_segments() {
344 let dir = tempfile::tempdir().unwrap();
345 let db = reth_db::init_db(dir.path(), reth_db::mdbx::DatabaseArguments::default()).unwrap();
346
347 let mut selections = BTreeMap::new();
348 selections.insert(SnapshotComponentType::State, ComponentSelection::All);
349 selections.insert(SnapshotComponentType::Headers, ComponentSelection::All);
350 let config = config_for_selections(
351 &selections,
352 &empty_manifest(),
353 None,
354 None::<&reth_chainspec::ChainSpec>,
355 );
356 let snapshot_block = 21_000_000;
357
358 {
359 let tx = db.tx_mut().unwrap();
360 write_prune_checkpoints_tx(&tx, &config, &selections, snapshot_block).unwrap();
361 tx.commit().unwrap();
362 }
363
364 let tx = db.tx().unwrap();
366 for segment in [
367 PruneSegment::SenderRecovery,
368 PruneSegment::TransactionLookup,
369 PruneSegment::Receipts,
370 PruneSegment::AccountHistory,
371 PruneSegment::StorageHistory,
372 PruneSegment::Bodies,
373 ] {
374 let checkpoint = tx
375 .get::<tables::PruneCheckpoints>(segment)
376 .unwrap()
377 .unwrap_or_else(|| panic!("expected checkpoint for {segment}"));
378 assert_eq!(checkpoint.block_number, Some(snapshot_block));
379 assert_eq!(checkpoint.tx_number, None);
381 }
382 }
383
384 fn history_checkpoint(snapshot_block: u64, distance: u64) -> Option<PruneCheckpoint> {
386 let dir = tempfile::tempdir().unwrap();
387 let db = reth_db::init_db(dir.path(), reth_db::mdbx::DatabaseArguments::default()).unwrap();
388 let selections = BTreeMap::from([(
389 SnapshotComponentType::AccountChangesets,
390 ComponentSelection::Distance(distance),
391 )]);
392 let config = config_for_selections(
393 &selections,
394 &empty_manifest(),
395 None,
396 None::<&reth_chainspec::ChainSpec>,
397 );
398
399 let tx = db.tx_mut().unwrap();
400 write_prune_checkpoints_tx(&tx, &config, &selections, snapshot_block).unwrap();
401 tx.commit().unwrap();
402
403 db.tx().unwrap().get::<tables::PruneCheckpoints>(PruneSegment::AccountHistory).unwrap()
404 }
405
406 #[test]
407 fn write_prune_checkpoints_sets_history_to_retained_window_start() {
408 let checkpoint = history_checkpoint(21_000_000, 500_000).unwrap();
409 assert_eq!(checkpoint.block_number, Some(20_500_000));
410 assert_eq!(checkpoint.prune_mode, PruneMode::Distance(500_000));
411 }
412
413 #[test]
414 fn write_prune_checkpoints_skips_history_shorter_than_distance() {
415 assert_eq!(history_checkpoint(100_000, 500_000), None);
416 }
417
418 #[test]
419 fn write_prune_checkpoints_archive_no_checkpoints() {
420 let dir = tempfile::tempdir().unwrap();
421 let db = reth_db::init_db(dir.path(), reth_db::mdbx::DatabaseArguments::default()).unwrap();
422
423 let mut selections = BTreeMap::new();
425 for ty in SnapshotComponentType::ALL {
426 selections.insert(ty, ComponentSelection::All);
427 }
428 let config = config_for_selections(
429 &selections,
430 &empty_manifest(),
431 None,
432 None::<&reth_chainspec::ChainSpec>,
433 );
434
435 {
436 let tx = db.tx_mut().unwrap();
437 write_prune_checkpoints_tx(&tx, &config, &selections, 21_000_000).unwrap();
438 tx.commit().unwrap();
439 }
440
441 let tx = db.tx().unwrap();
442 for segment in [PruneSegment::SenderRecovery, PruneSegment::TransactionLookup] {
443 assert!(
444 tx.get::<tables::PruneCheckpoints>(segment).unwrap().is_none(),
445 "expected no checkpoint for {segment} on archive node"
446 );
447 }
448 }
449
450 #[test]
451 fn selections_all_no_pruning() {
452 let mut selections = BTreeMap::new();
453 for ty in SnapshotComponentType::ALL {
454 selections.insert(ty, ComponentSelection::All);
455 }
456 let config = config_for_selections(
457 &selections,
458 &empty_manifest(),
459 None,
460 None::<&reth_chainspec::ChainSpec>,
461 );
462 assert_eq!(config.prune.segments.transaction_lookup, None);
464 assert_eq!(config.prune.segments.sender_recovery, None);
465 assert_eq!(config.prune.segments.bodies_history, None);
466 assert_eq!(config.prune.segments.receipts, None);
467 assert_eq!(config.prune.segments.account_history, None);
468 assert_eq!(config.prune.segments.storage_history, None);
469 }
470
471 #[test]
472 fn selections_none_clamps_to_minimum_distance() {
473 let mut selections = BTreeMap::new();
474 selections.insert(SnapshotComponentType::State, ComponentSelection::All);
475 selections.insert(SnapshotComponentType::Headers, ComponentSelection::All);
476 let config = config_for_selections(
477 &selections,
478 &empty_manifest(),
479 None,
480 None::<&reth_chainspec::ChainSpec>,
481 );
482 assert_eq!(config.prune.segments.transaction_lookup, Some(PruneMode::Full));
483 assert_eq!(config.prune.segments.sender_recovery, Some(PruneMode::Full));
484 assert_eq!(
486 config.prune.segments.bodies_history,
487 Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE))
488 );
489 assert_eq!(config.prune.segments.receipts, Some(PruneMode::Distance(MINIMUM_DISTANCE)));
490 assert_eq!(
491 config.prune.segments.account_history,
492 Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE))
493 );
494 assert_eq!(
495 config.prune.segments.storage_history,
496 Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE))
497 );
498 }
499
500 #[test]
501 fn selections_distance_maps_bodies_history() {
502 let mut selections = BTreeMap::new();
503 selections.insert(SnapshotComponentType::State, ComponentSelection::All);
504 selections.insert(SnapshotComponentType::Headers, ComponentSelection::All);
505 selections
506 .insert(SnapshotComponentType::Transactions, ComponentSelection::Distance(10_064));
507 selections.insert(SnapshotComponentType::Receipts, ComponentSelection::None);
508 selections
509 .insert(SnapshotComponentType::AccountChangesets, ComponentSelection::Distance(10_064));
510 selections
511 .insert(SnapshotComponentType::StorageChangesets, ComponentSelection::Distance(10_064));
512 let config = config_for_selections(
513 &selections,
514 &empty_manifest(),
515 None,
516 None::<&reth_chainspec::ChainSpec>,
517 );
518
519 assert_eq!(config.prune.segments.transaction_lookup, Some(PruneMode::Full));
520 assert_eq!(config.prune.segments.sender_recovery, Some(PruneMode::Full));
521 assert_eq!(config.prune.segments.bodies_history, Some(PruneMode::Distance(10_064)));
523 assert_eq!(config.prune.segments.receipts, Some(PruneMode::Distance(MINIMUM_DISTANCE)));
524 assert_eq!(config.prune.segments.account_history, Some(PruneMode::Distance(10_064)));
525 assert_eq!(config.prune.segments.storage_history, Some(PruneMode::Distance(10_064)));
526 }
527
528 #[test]
529 fn selections_since_maps_to_before_prune_mode() {
530 let mut selections = BTreeMap::new();
531 selections.insert(SnapshotComponentType::State, ComponentSelection::All);
532 selections.insert(SnapshotComponentType::Headers, ComponentSelection::All);
533 selections
534 .insert(SnapshotComponentType::Transactions, ComponentSelection::Since(15_537_394));
535 selections.insert(SnapshotComponentType::Receipts, ComponentSelection::Since(15_537_394));
536 selections.insert(
537 SnapshotComponentType::AccountChangesets,
538 ComponentSelection::Since(15_537_394),
539 );
540 selections.insert(
541 SnapshotComponentType::StorageChangesets,
542 ComponentSelection::Since(15_537_394),
543 );
544
545 let config = config_for_selections(
546 &selections,
547 &empty_manifest(),
548 None,
549 None::<&reth_chainspec::ChainSpec>,
550 );
551
552 assert_eq!(config.prune.segments.bodies_history, Some(PruneMode::Before(15_537_394)));
553 assert_eq!(config.prune.segments.receipts, Some(PruneMode::Before(15_537_394)));
554 assert_eq!(config.prune.segments.account_history, Some(PruneMode::Before(15_537_394)));
555 assert_eq!(config.prune.segments.storage_history, Some(PruneMode::Before(15_537_394)));
556 }
557
558 #[test]
559 fn full_preset_matches_default_full_prune_config() {
560 let mut selections = BTreeMap::new();
561 selections.insert(SnapshotComponentType::State, ComponentSelection::All);
562 selections.insert(SnapshotComponentType::Headers, ComponentSelection::All);
563 selections
564 .insert(SnapshotComponentType::Transactions, ComponentSelection::Distance(500_000));
565 selections.insert(SnapshotComponentType::Receipts, ComponentSelection::Distance(10_064));
566
567 let chain_spec = reth_chainspec::MAINNET.clone();
568 let config = config_for_selections(
569 &selections,
570 &empty_manifest(),
571 Some(SelectionPreset::Full),
572 Some(chain_spec.as_ref()),
573 );
574
575 assert_eq!(config.prune.segments.sender_recovery, Some(PruneMode::Full));
576 assert_eq!(config.prune.segments.transaction_lookup, None);
577 assert_eq!(
578 config.prune.segments.receipts,
579 Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE))
580 );
581 assert_eq!(
582 config.prune.segments.account_history,
583 Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE))
584 );
585 assert_eq!(
586 config.prune.segments.storage_history,
587 Some(PruneMode::Distance(MINIMUM_UNWIND_SAFE_DISTANCE))
588 );
589
590 let paris_block = chain_spec
591 .ethereum_fork_activation(EthereumHardfork::Paris)
592 .block_number()
593 .expect("mainnet Paris block should be known");
594 assert_eq!(config.prune.segments.bodies_history, Some(PruneMode::Before(paris_block)));
595 }
596
597 #[test]
598 fn minimal_preset_matches_default_minimal_prune_config() {
599 let config = config_for_selections(
600 &BTreeMap::new(),
601 &empty_manifest(),
602 Some(SelectionPreset::Minimal),
603 None::<&reth_chainspec::ChainSpec>,
604 );
605
606 assert_eq!(&config.prune.segments, &DefaultPruningValues::get_global().minimal_prune_modes);
607 }
608
609 #[test]
610 fn describe_selections_all_no_pruning() {
611 let mut selections = BTreeMap::new();
612 for ty in SnapshotComponentType::ALL {
613 selections.insert(ty, ComponentSelection::All);
614 }
615 let config = config_for_selections(
616 &selections,
617 &empty_manifest(),
618 None,
619 None::<&reth_chainspec::ChainSpec>,
620 );
621 let desc = describe_prune_config(&config);
622 assert!(desc.is_empty());
624 }
625
626 #[test]
627 fn describe_selections_with_distances() {
628 let mut selections = BTreeMap::new();
629 selections.insert(SnapshotComponentType::State, ComponentSelection::All);
630 selections.insert(SnapshotComponentType::Headers, ComponentSelection::All);
631 selections
632 .insert(SnapshotComponentType::Transactions, ComponentSelection::Distance(10_064));
633 selections.insert(SnapshotComponentType::Receipts, ComponentSelection::None);
634 let config = config_for_selections(
635 &selections,
636 &empty_manifest(),
637 None,
638 None::<&reth_chainspec::ChainSpec>,
639 );
640 let desc = describe_prune_config(&config);
641 assert!(desc.contains(&"sender_recovery=\"full\"".to_string()));
642 assert!(desc.contains(&"bodies_history={ distance = 10064 }".to_string()));
644 assert!(desc.contains(&"receipts={ distance = 64 }".to_string()));
645 }
646
647 #[test]
648 fn reset_index_stage_checkpoints_clears_only_rocksdb_index_stages() {
649 let dir = tempfile::tempdir().unwrap();
650 let db = reth_db::init_db(dir.path(), reth_db::mdbx::DatabaseArguments::default()).unwrap();
651
652 let tip_checkpoint = StageCheckpoint::new(24_500_000);
654 {
655 let tx = db.tx_mut().unwrap();
656 for stage_id in INDEX_STAGE_IDS {
657 tx.put::<tables::StageCheckpoints>(stage_id.to_string(), tip_checkpoint).unwrap();
658 }
659 for segment in INDEX_PRUNE_SEGMENTS {
660 tx.put::<tables::PruneCheckpoints>(
661 segment,
662 PruneCheckpoint {
663 block_number: Some(24_500_000),
664 tx_number: None,
665 prune_mode: PruneMode::Full,
666 },
667 )
668 .unwrap();
669 }
670
671 tx.put::<tables::StageCheckpoints>("SenderRecovery".to_string(), tip_checkpoint)
673 .unwrap();
674 tx.put::<tables::PruneCheckpoints>(
675 PruneSegment::SenderRecovery,
676 PruneCheckpoint {
677 block_number: Some(24_500_000),
678 tx_number: None,
679 prune_mode: PruneMode::Full,
680 },
681 )
682 .unwrap();
683 tx.commit().unwrap();
684 }
685
686 {
688 let tx = db.tx_mut().unwrap();
689 reset_index_stage_checkpoints_tx(&tx).unwrap();
690 tx.commit().unwrap();
691 }
692
693 let tx = db.tx().unwrap();
695 for stage_id in INDEX_STAGE_IDS {
696 let checkpoint = tx
697 .get::<tables::StageCheckpoints>(stage_id.to_string())
698 .unwrap()
699 .expect("checkpoint should exist");
700 assert_eq!(checkpoint.block_number, 0, "stage {stage_id} should be reset to block 0");
701 }
702
703 for segment in INDEX_PRUNE_SEGMENTS {
705 assert!(
706 tx.get::<tables::PruneCheckpoints>(segment).unwrap().is_none(),
707 "prune checkpoint for {segment} should be deleted"
708 );
709 }
710
711 let sender_stage_checkpoint = tx
713 .get::<tables::StageCheckpoints>("SenderRecovery".to_string())
714 .unwrap()
715 .expect("sender checkpoint should exist");
716 assert_eq!(sender_stage_checkpoint.block_number, tip_checkpoint.block_number);
717
718 let sender_prune_checkpoint = tx
719 .get::<tables::PruneCheckpoints>(PruneSegment::SenderRecovery)
720 .unwrap()
721 .expect("sender prune checkpoint should exist");
722 assert_eq!(sender_prune_checkpoint.block_number, Some(tip_checkpoint.block_number));
723 }
724
725 #[test]
726 fn history_checkpoints_do_not_advertise_undownloaded_changesets() {
727 let snapshot_block = 21_000_000;
728 for (selection, expected) in [
729 (None, Some(snapshot_block)),
730 (Some(ComponentSelection::None), Some(snapshot_block)),
731 (Some(ComponentSelection::Distance(1)), Some(snapshot_block - 1)),
732 (Some(ComponentSelection::Since(snapshot_block)), Some(snapshot_block - 1)),
733 (Some(ComponentSelection::Since(0)), None),
734 (Some(ComponentSelection::All), None),
735 ] {
736 let dir = tempfile::tempdir().unwrap();
737 let db =
738 reth_db::init_db(dir.path(), reth_db::mdbx::DatabaseArguments::default()).unwrap();
739 let mut selections = BTreeMap::from([
740 (SnapshotComponentType::State, ComponentSelection::All),
741 (SnapshotComponentType::Headers, ComponentSelection::All),
742 (SnapshotComponentType::RocksdbIndices, ComponentSelection::All),
743 ]);
744 if let Some(selection) = selection {
745 selections.insert(SnapshotComponentType::AccountChangesets, selection);
746 selections.insert(SnapshotComponentType::StorageChangesets, selection);
747 }
748 let config = config_for_selections(
749 &selections,
750 &empty_manifest(),
751 None,
752 None::<&reth_chainspec::ChainSpec>,
753 );
754 assert!(!super::super::should_reset_index_stage_checkpoints(&selections));
755 let tx = db.tx_mut().unwrap();
756 write_prune_checkpoints_tx(&tx, &config, &selections, snapshot_block).unwrap();
757 let mut checkpoints = Vec::new();
758 for segment in [PruneSegment::AccountHistory, PruneSegment::StorageHistory] {
759 let checkpoint = tx.get::<tables::PruneCheckpoints>(segment).unwrap();
760 assert_eq!(checkpoint.and_then(|checkpoint| checkpoint.block_number), expected);
761 if let Some(checkpoint) = checkpoint {
762 checkpoints.push((segment, checkpoint));
763 }
764 }
765 if let Some(boundary) = expected {
766 assert!(config
767 .prune
768 .segments
769 .ensure_unwind_target_unpruned(snapshot_block, boundary - 1, &checkpoints,)
770 .is_err());
771 assert!(config
772 .prune
773 .segments
774 .ensure_unwind_target_unpruned(snapshot_block, boundary, &checkpoints,)
775 .is_ok());
776 }
777 }
778 }
779
780 #[test]
781 fn history_checkpoints_preserve_source_pruning_boundary() {
782 let snapshot_block = 21_000_000;
783 let selected_boundary = 20_500_000;
784 for selection in [
785 ComponentSelection::Distance(snapshot_block - selected_boundary),
786 ComponentSelection::Since(selected_boundary + 1),
787 ] {
788 for existing_boundary in [None, Some(20_000_000), Some(20_750_000)] {
789 let dir = tempfile::tempdir().unwrap();
790 let db = reth_db::init_db(dir.path(), reth_db::mdbx::DatabaseArguments::default())
791 .unwrap();
792 let selections = BTreeMap::from([
793 (SnapshotComponentType::AccountChangesets, selection),
794 (SnapshotComponentType::StorageChangesets, selection),
795 (SnapshotComponentType::RocksdbIndices, ComponentSelection::All),
796 ]);
797 let config = config_for_selections(
798 &selections,
799 &empty_manifest(),
800 None,
801 None::<&reth_chainspec::ChainSpec>,
802 );
803 let tx = db.tx_mut().unwrap();
804 for segment in [PruneSegment::AccountHistory, PruneSegment::StorageHistory] {
805 tx.put::<tables::PruneCheckpoints>(
806 segment,
807 PruneCheckpoint {
808 block_number: existing_boundary,
809 tx_number: Some(100),
810 prune_mode: PruneMode::Full,
811 },
812 )
813 .unwrap();
814 }
815
816 write_prune_checkpoints_tx(&tx, &config, &selections, snapshot_block).unwrap();
817
818 let expected = existing_boundary.unwrap_or_default().max(selected_boundary);
819 for (segment, mode) in [
820 (PruneSegment::AccountHistory, config.prune.segments.account_history),
821 (PruneSegment::StorageHistory, config.prune.segments.storage_history),
822 ] {
823 assert_eq!(
824 tx.get::<tables::PruneCheckpoints>(segment).unwrap(),
825 Some(PruneCheckpoint {
826 block_number: Some(expected),
827 tx_number: None,
828 prune_mode: mode.unwrap(),
829 }),
830 );
831 }
832 }
833 }
834 }
835}