1use 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#[derive(Debug, Parser)]
13pub struct Command {
14 #[command(subcommand)]
15 command: Subcommands,
16}
17
18impl Command {
19 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 {
35 #[arg(long, value_enum)]
37 segment: Option<SegmentArg>,
38 },
39 Set(SetArgs),
45}
46
47#[derive(Debug, Args)]
49pub struct SetArgs {
50 #[arg(long, value_enum)]
52 segment: SegmentArg,
53
54 #[arg(long)]
56 block_number: Option<u64>,
57
58 #[arg(long)]
60 tx_number: Option<u64>,
61
62 #[arg(long, value_enum)]
64 mode: PruneModeArg,
65
66 #[arg(long, required_if_eq_any([("mode", "distance"), ("mode", "before")]))]
68 mode_value: Option<u64>,
69}
70
71#[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#[derive(Debug, Clone, Copy, ValueEnum)]
100#[clap(rename_all = "kebab-case")]
101pub enum PruneModeArg {
102 Full,
104 Distance,
106 Before,
108}
109
110impl Command {
111 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 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}