Skip to main content

reth_cli_commands/db/
prune_checkpoints.rs

1//! `reth db prune-checkpoints` command for viewing and setting prune checkpoint values.
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4use reth_db_common::DbTool;
5use reth_provider::{providers::ProviderNodeTypes, DBProvider, DatabaseProviderFactory};
6use reth_prune_types::{PruneCheckpoint, PruneMode, PruneSegment};
7use reth_storage_api::{PruneCheckpointReader, PruneCheckpointWriter};
8
9use crate::common::AccessRights;
10
11/// `reth db prune-checkpoints` subcommand
12#[derive(Debug, Parser)]
13pub struct Command {
14    #[command(subcommand)]
15    command: Subcommands,
16}
17
18impl Command {
19    /// Returns database access rights required for the command.
20    pub fn access_rights(&self) -> AccessRights {
21        match &self.command {
22            Subcommands::Get { .. } => AccessRights::RO,
23            Subcommands::Set(_) => AccessRights::RW,
24        }
25    }
26}
27
28#[derive(Debug, Subcommand)]
29enum Subcommands {
30    /// Get prune checkpoint(s) from database.
31    ///
32    /// Shows the current prune progress for each segment, including the highest
33    /// pruned block/tx number and the active prune mode.
34    Get {
35        /// Specific segment to query. If omitted, shows all segments.
36        #[arg(long, value_enum)]
37        segment: Option<SegmentArg>,
38    },
39    /// Set a prune checkpoint for a segment.
40    ///
41    /// WARNING: Manually setting checkpoints can cause data inconsistencies.
42    /// Only use this if you know what you're doing (e.g., recovering from a
43    /// corrupted checkpoint or forcing a re-prune from a specific block).
44    Set(SetArgs),
45}
46
47/// Arguments for the `set` subcommand
48#[derive(Debug, Args)]
49pub struct SetArgs {
50    /// The prune segment to update
51    #[arg(long, value_enum)]
52    segment: SegmentArg,
53
54    /// Highest pruned block number
55    #[arg(long)]
56    block_number: Option<u64>,
57
58    /// Highest pruned transaction number
59    #[arg(long)]
60    tx_number: Option<u64>,
61
62    /// Prune mode to write: full, distance, or before
63    #[arg(long, value_enum)]
64    mode: PruneModeArg,
65
66    /// Value for distance or before mode (required unless mode is full)
67    #[arg(long, required_if_eq_any([("mode", "distance"), ("mode", "before")]))]
68    mode_value: Option<u64>,
69}
70
71/// CLI-friendly prune segment names (excludes deprecated variants)
72#[derive(Debug, Clone, Copy, ValueEnum)]
73#[clap(rename_all = "kebab-case")]
74pub enum SegmentArg {
75    SenderRecovery,
76    TransactionLookup,
77    Receipts,
78    ContractLogs,
79    AccountHistory,
80    StorageHistory,
81    Bodies,
82}
83
84impl From<SegmentArg> for PruneSegment {
85    fn from(arg: SegmentArg) -> Self {
86        match arg {
87            SegmentArg::SenderRecovery => Self::SenderRecovery,
88            SegmentArg::TransactionLookup => Self::TransactionLookup,
89            SegmentArg::Receipts => Self::Receipts,
90            SegmentArg::ContractLogs => Self::ContractLogs,
91            SegmentArg::AccountHistory => Self::AccountHistory,
92            SegmentArg::StorageHistory => Self::StorageHistory,
93            SegmentArg::Bodies => Self::Bodies,
94        }
95    }
96}
97
98/// CLI-friendly prune mode
99#[derive(Debug, Clone, Copy, ValueEnum)]
100#[clap(rename_all = "kebab-case")]
101pub enum PruneModeArg {
102    /// Prune all blocks
103    Full,
104    /// Keep the last N blocks (requires --mode-value)
105    Distance,
106    /// Prune blocks before a specific block number (requires --mode-value)
107    Before,
108}
109
110impl Command {
111    /// Execute the command
112    pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
113        match self.command {
114            Subcommands::Get { segment } => Self::get(tool, segment),
115            Subcommands::Set(args) => Self::set(tool, args),
116        }
117    }
118
119    fn get<N: ProviderNodeTypes>(
120        tool: &DbTool<N>,
121        segment: Option<SegmentArg>,
122    ) -> eyre::Result<()> {
123        let provider = tool.provider_factory.provider()?;
124
125        match segment {
126            Some(seg) => {
127                let segment: PruneSegment = seg.into();
128                match provider.get_prune_checkpoint(segment)? {
129                    Some(checkpoint) => print_checkpoint(segment, &checkpoint),
130                    None => println!("No checkpoint found for {segment}"),
131                }
132            }
133            None => {
134                let mut checkpoints = provider.get_prune_checkpoints()?;
135                checkpoints.sort_by_key(|(seg, _)| *seg);
136                if checkpoints.is_empty() {
137                    println!("No prune checkpoints found.");
138                } else {
139                    println!(
140                        "{:<25} {:>15} {:>15} {:>20}",
141                        "Segment", "Block Number", "Tx Number", "Prune Mode"
142                    );
143                    println!("{}", "-".repeat(80));
144                    for (segment, checkpoint) in &checkpoints {
145                        println!(
146                            "{:<25} {:>15} {:>15} {:>20}",
147                            segment.to_string(),
148                            fmt_opt(checkpoint.block_number),
149                            fmt_opt(checkpoint.tx_number),
150                            fmt_mode(&checkpoint.prune_mode),
151                        );
152                    }
153                }
154            }
155        }
156
157        Ok(())
158    }
159
160    fn set<N: ProviderNodeTypes>(tool: &DbTool<N>, args: SetArgs) -> eyre::Result<()> {
161        eyre::ensure!(
162            args.block_number.is_some() || args.tx_number.is_some(),
163            "at least one of --block-number or --tx-number must be provided"
164        );
165
166        let prune_mode = match args.mode {
167            PruneModeArg::Full => PruneMode::Full,
168            PruneModeArg::Distance => PruneMode::Distance(
169                args.mode_value
170                    .ok_or_else(|| eyre::eyre!("--mode-value is required for distance mode"))?,
171            ),
172            PruneModeArg::Before => PruneMode::Before(
173                args.mode_value
174                    .ok_or_else(|| eyre::eyre!("--mode-value is required for before mode"))?,
175            ),
176        };
177
178        let segment: PruneSegment = args.segment.into();
179        let checkpoint = PruneCheckpoint {
180            block_number: args.block_number,
181            tx_number: args.tx_number,
182            prune_mode,
183        };
184
185        let provider_rw = tool.provider_factory.database_provider_rw()?;
186
187        // Show previous value if any
188        if let Some(prev) = provider_rw.get_prune_checkpoint(segment)? {
189            println!("Previous checkpoint for {segment}:");
190            print_checkpoint(segment, &prev);
191            println!();
192        }
193
194        provider_rw.save_prune_checkpoint(segment, checkpoint)?;
195        provider_rw.commit()?;
196
197        println!("Updated checkpoint for {segment}:");
198        print_checkpoint(segment, &checkpoint);
199
200        Ok(())
201    }
202}
203
204fn print_checkpoint(segment: PruneSegment, checkpoint: &PruneCheckpoint) {
205    println!("  Segment:      {segment}");
206    println!("  Block Number: {}", fmt_opt(checkpoint.block_number));
207    println!("  Tx Number:    {}", fmt_opt(checkpoint.tx_number));
208    println!("  Prune Mode:   {}", fmt_mode(&checkpoint.prune_mode));
209}
210
211fn fmt_opt(val: Option<u64>) -> String {
212    val.map_or("-".to_string(), |n| n.to_string())
213}
214
215fn fmt_mode(mode: &PruneMode) -> String {
216    match mode {
217        PruneMode::Full => "Full".to_string(),
218        PruneMode::Distance(d) => format!("Distance({d})"),
219        PruneMode::Before(b) => format!("Before({b})"),
220    }
221}