1use self::db_dump::{StoreName, dump_table, list_tables, table_summary};
5use crate::db_tool::db_dump::{compact, print_table_metadata, prune_checkpoints, prune_objects};
6use anyhow::{anyhow, bail};
7use clap::Parser;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10use sui_core::authority::authority_per_epoch_store::AuthorityEpochTables;
11use sui_core::authority::authority_store_pruner::PrunerWatermarks;
12use sui_core::authority::authority_store_tables::AuthorityPerpetualTables;
13use sui_core::checkpoints::CheckpointStore;
14use sui_types::base_types::{EpochId, ObjectID};
15use sui_types::digests::{CheckpointContentsDigest, TransactionDigest};
16use sui_types::effects::TransactionEffectsAPI;
17use sui_types::messages_checkpoint::{CheckpointDigest, CheckpointSequenceNumber};
18#[cfg(not(tidehunter))]
19use typed_store::rocks::MetricConf;
20use typed_store::rocks::safe_drop_db;
21pub mod db_dump;
22
23#[derive(Parser)]
24#[command(rename_all = "kebab-case")]
25pub enum DbToolCommand {
26 ListTables,
27 Dump(Options),
28 TableSummary(Options),
29 ListDBMetadata(Options),
30 PrintLastConsensusIndex,
31 PrintConsensusCommit(PrintConsensusCommitOptions),
32 PrintTransaction(PrintTransactionOptions),
33 PrintObject(PrintObjectOptions),
34 PrintCheckpoint(PrintCheckpointOptions),
35 PrintCheckpointContent(PrintCheckpointContentOptions),
36 ResetDB,
37 RewindCheckpointExecution(RewindCheckpointExecutionOptions),
38 Compact,
39 PruneObjects,
40 PruneCheckpoints,
41 SetCheckpointWatermark(SetCheckpointWatermarkOptions),
42}
43
44#[derive(Parser)]
45#[command(rename_all = "kebab-case")]
46pub struct Options {
47 #[arg(long = "store", short = 's', value_enum)]
49 store_name: StoreName,
50 #[arg(long = "table-name", short = 't')]
52 table_name: String,
53 #[arg(long = "page-size", short = 'p')]
55 page_size: u16,
56 #[arg(long = "page-num", short = 'n')]
58 page_number: usize,
59
60 #[arg(long = "epoch", short = 'e')]
65 epoch: Option<EpochId>,
66}
67
68#[derive(Parser)]
69#[command(rename_all = "kebab-case")]
70pub struct PrintConsensusCommitOptions {
71 #[arg(long, help = "Sequence number of the consensus commit")]
72 seqnum: u64,
73}
74
75#[derive(Parser)]
76#[command(rename_all = "kebab-case")]
77pub struct PrintTransactionOptions {
78 #[arg(long, help = "The transaction digest to print")]
79 digest: TransactionDigest,
80}
81
82#[derive(Parser)]
83#[command(rename_all = "kebab-case")]
84pub struct PrintObjectOptions {
85 #[arg(long, help = "The object id to print")]
86 id: ObjectID,
87 #[arg(long, help = "The object version to print")]
88 version: Option<u64>,
89}
90
91#[derive(Parser)]
92#[command(rename_all = "kebab-case")]
93pub struct PrintCheckpointOptions {
94 #[arg(long, help = "The checkpoint digest to print")]
95 digest: CheckpointDigest,
96}
97
98#[derive(Parser)]
99#[command(rename_all = "kebab-case")]
100pub struct PrintCheckpointContentOptions {
101 #[arg(
102 long,
103 help = "The checkpoint content digest (NOT the checkpoint digest)"
104 )]
105 digest: CheckpointContentsDigest,
106}
107
108#[derive(Parser)]
109#[command(rename_all = "kebab-case")]
110pub struct RemoveTransactionOptions {
111 #[arg(long, help = "The transaction digest to remove")]
112 digest: TransactionDigest,
113
114 #[arg(long)]
115 confirm: bool,
116
117 #[arg(long = "epoch", short = 'e')]
120 epoch: Option<EpochId>,
121}
122
123#[derive(Parser)]
124#[command(rename_all = "kebab-case")]
125pub struct RemoveObjectLockOptions {
126 #[arg(long, help = "The object ID to remove")]
127 id: ObjectID,
128
129 #[arg(long, help = "The object version to remove")]
130 version: u64,
131
132 #[arg(long)]
133 confirm: bool,
134}
135
136#[derive(Parser)]
137#[command(rename_all = "kebab-case")]
138pub struct RewindCheckpointExecutionOptions {
139 #[arg(long = "epoch")]
140 epoch: EpochId,
141
142 #[arg(long = "checkpoint-sequence-number")]
143 checkpoint_sequence_number: u64,
144}
145
146#[derive(Parser)]
147#[command(rename_all = "kebab-case")]
148pub struct SetCheckpointWatermarkOptions {
149 #[arg(long)]
150 highest_verified: Option<CheckpointSequenceNumber>,
151
152 #[arg(long)]
153 highest_synced: Option<CheckpointSequenceNumber>,
154}
155
156pub async fn execute_db_tool_command(db_path: PathBuf, cmd: DbToolCommand) -> anyhow::Result<()> {
157 match cmd {
158 DbToolCommand::ListTables => print_db_all_tables(db_path),
159 DbToolCommand::Dump(d) => print_all_entries(
160 d.store_name,
161 d.epoch,
162 db_path,
163 &d.table_name,
164 d.page_size,
165 d.page_number,
166 ),
167 DbToolCommand::TableSummary(d) => {
168 print_db_table_summary(d.store_name, d.epoch, db_path, &d.table_name)
169 }
170 DbToolCommand::ListDBMetadata(d) => {
171 print_table_metadata(d.store_name, d.epoch, db_path, &d.table_name)
172 }
173 DbToolCommand::PrintLastConsensusIndex => print_last_consensus_index(&db_path),
174 DbToolCommand::PrintConsensusCommit(d) => print_consensus_commit(&db_path, d),
175 DbToolCommand::PrintTransaction(d) => print_transaction(&db_path, d),
176 DbToolCommand::PrintObject(o) => print_object(&db_path, o),
177 DbToolCommand::PrintCheckpoint(d) => print_checkpoint(&db_path, d),
178 DbToolCommand::PrintCheckpointContent(d) => print_checkpoint_content(&db_path, d),
179 DbToolCommand::ResetDB => reset_db_to_genesis(&db_path).await,
180 DbToolCommand::RewindCheckpointExecution(d) => {
181 rewind_checkpoint_execution(&db_path, d.epoch, d.checkpoint_sequence_number)
182 }
183 DbToolCommand::Compact => compact(db_path),
184 DbToolCommand::PruneObjects => prune_objects(db_path).await,
185 DbToolCommand::PruneCheckpoints => prune_checkpoints(db_path).await,
186 DbToolCommand::SetCheckpointWatermark(d) => set_checkpoint_watermark(&db_path, d),
187 }
188}
189
190pub fn print_db_all_tables(db_path: PathBuf) -> anyhow::Result<()> {
191 list_tables(db_path)?.iter().for_each(|t| println!("{}", t));
192 Ok(())
193}
194
195pub fn print_last_consensus_index(path: &Path) -> anyhow::Result<()> {
196 #[cfg(not(tidehunter))]
197 let epoch_tables = AuthorityEpochTables::open_tables_read_write(
198 path.to_path_buf(),
199 MetricConf::default(),
200 None,
201 None,
202 );
203 #[cfg(tidehunter)]
204 let epoch_tables = AuthorityEpochTables::open_with_path(path);
205 let last_index = epoch_tables.get_last_consensus_index()?;
206 println!("Last consensus index is {:?}", last_index);
207 Ok(())
208}
209
210pub fn print_consensus_commit(
212 _path: &Path,
213 _opt: PrintConsensusCommitOptions,
214) -> anyhow::Result<()> {
215 println!("Printing consensus commit is unimplemented");
216 Ok(())
217}
218
219pub fn print_transaction(path: &Path, opt: PrintTransactionOptions) -> anyhow::Result<()> {
220 let perpetual_db = AuthorityPerpetualTables::open(&path.join("store"), None, None);
221 if let Some((epoch, checkpoint_seq_num)) =
222 perpetual_db.get_checkpoint_sequence_number(&opt.digest)?
223 {
224 println!(
225 "Transaction {:?} executed in epoch {} checkpoint {}",
226 opt.digest, epoch, checkpoint_seq_num
227 );
228 };
229 if let Some(effects) = perpetual_db.get_effects(&opt.digest)? {
230 println!(
231 "Transaction {:?} dependencies: {:#?}",
232 opt.digest,
233 effects.dependencies(),
234 );
235 };
236 Ok(())
237}
238
239pub fn print_object(path: &Path, opt: PrintObjectOptions) -> anyhow::Result<()> {
240 let perpetual_db = AuthorityPerpetualTables::open(&path.join("store"), None, None);
241
242 let obj = if let Some(version) = opt.version {
243 perpetual_db.get_object_by_key_fallible(&opt.id, version.into())?
244 } else {
245 perpetual_db.get_object_fallible(&opt.id)?
246 };
247
248 if let Some(obj) = obj {
249 println!("Object {:?}:\n{:#?}", opt.id, obj);
250 } else {
251 println!("Object {:?} not found", opt.id);
252 }
253
254 Ok(())
255}
256
257pub fn print_checkpoint(path: &Path, opt: PrintCheckpointOptions) -> anyhow::Result<()> {
258 let checkpoint_store = CheckpointStore::new(
259 &path.join("checkpoints"),
260 Arc::new(PrunerWatermarks::default()),
261 );
262 let checkpoint = checkpoint_store
263 .get_checkpoint_by_digest(&opt.digest)?
264 .ok_or(anyhow!(
265 "Checkpoint digest {:?} not found in checkpoint store",
266 opt.digest
267 ))?;
268 println!("Checkpoint: {:?}", checkpoint);
269 drop(checkpoint_store);
270 print_checkpoint_content(
271 path,
272 PrintCheckpointContentOptions {
273 digest: checkpoint.content_digest,
274 },
275 )
276}
277
278pub fn print_checkpoint_content(
279 path: &Path,
280 opt: PrintCheckpointContentOptions,
281) -> anyhow::Result<()> {
282 let checkpoint_store = CheckpointStore::new(
283 &path.join("checkpoints"),
284 Arc::new(PrunerWatermarks::default()),
285 );
286 let contents = checkpoint_store
287 .get_checkpoint_contents(&opt.digest)?
288 .ok_or(anyhow!(
289 "Checkpoint content digest {:?} not found in checkpoint store",
290 opt.digest
291 ))?;
292 println!("Checkpoint content: {:?}", contents);
293 Ok(())
294}
295
296pub async fn reset_db_to_genesis(path: &Path) -> anyhow::Result<()> {
297 safe_drop_db(
327 path.join("store").join("perpetual"),
328 std::time::Duration::from_secs(60),
329 )
330 .await?;
331
332 let checkpoint_db = CheckpointStore::new(
333 &path.join("checkpoints"),
334 Arc::new(PrunerWatermarks::default()),
335 );
336 checkpoint_db.reset_db_for_execution_since_genesis()?;
337
338 Ok(())
339}
340
341pub fn rewind_checkpoint_execution(
345 path: &Path,
346 epoch: EpochId,
347 checkpoint_sequence_number: u64,
348) -> anyhow::Result<()> {
349 let checkpoint_db = CheckpointStore::new(
350 &path.join("checkpoints"),
351 Arc::new(PrunerWatermarks::default()),
352 );
353 let Some(checkpoint) =
354 checkpoint_db.get_checkpoint_by_sequence_number(checkpoint_sequence_number)?
355 else {
356 bail!("Checkpoint {checkpoint_sequence_number} not found!");
357 };
358 if epoch != checkpoint.epoch() {
359 bail!(
360 "Checkpoint {checkpoint_sequence_number} is in epoch {} not {epoch}!",
361 checkpoint.epoch()
362 );
363 }
364
365 let highest_executed_sequence_number = checkpoint_db
366 .get_highest_executed_checkpoint_seq_number()?
367 .unwrap_or_default();
368 if checkpoint_sequence_number > highest_executed_sequence_number {
369 bail!(
370 "Must rewind checkpoint execution to be not later than highest executed ({} > {})!",
371 checkpoint_sequence_number,
372 highest_executed_sequence_number
373 );
374 }
375 checkpoint_db.set_highest_executed_checkpoint_subtle(&checkpoint)?;
376 Ok(())
377}
378
379pub fn print_db_table_summary(
380 store: StoreName,
381 epoch: Option<EpochId>,
382 path: PathBuf,
383 table_name: &str,
384) -> anyhow::Result<()> {
385 let summary = table_summary(store, epoch, path, table_name)?;
386 let quantiles = [25, 50, 75, 90, 99];
387 println!(
388 "Total num keys = {}, total key bytes = {}, total value bytes = {}",
389 summary.num_keys, summary.key_bytes_total, summary.value_bytes_total
390 );
391 println!("Key size distribution:\n");
392 quantiles.iter().for_each(|q| {
393 println!(
394 "p{:?} -> {:?} bytes\n",
395 q,
396 summary.key_hist.value_at_quantile(*q as f64 / 100.0)
397 );
398 });
399 println!("Value size distribution:\n");
400 quantiles.iter().for_each(|q| {
401 println!(
402 "p{:?} -> {:?} bytes\n",
403 q,
404 summary.value_hist.value_at_quantile(*q as f64 / 100.0)
405 );
406 });
407 Ok(())
408}
409
410pub fn print_all_entries(
411 store: StoreName,
412 epoch: Option<EpochId>,
413 path: PathBuf,
414 table_name: &str,
415 page_size: u16,
416 page_number: usize,
417) -> anyhow::Result<()> {
418 for (k, v) in dump_table(store, epoch, path, table_name, page_size, page_number)? {
419 println!("{:>100?}: {:?}", k, v);
420 }
421 Ok(())
422}
423
424pub fn set_checkpoint_watermark(
428 path: &Path,
429 options: SetCheckpointWatermarkOptions,
430) -> anyhow::Result<()> {
431 let checkpoint_db = CheckpointStore::new(
432 &path.join("checkpoints"),
433 Arc::new(PrunerWatermarks::default()),
434 );
435
436 if let Some(highest_verified) = options.highest_verified {
437 let Some(checkpoint) = checkpoint_db.get_checkpoint_by_sequence_number(highest_verified)?
438 else {
439 bail!("Checkpoint {highest_verified} not found");
440 };
441 checkpoint_db.update_highest_verified_checkpoint(&checkpoint)?;
442 }
443 if let Some(highest_synced) = options.highest_synced {
444 let Some(checkpoint) = checkpoint_db.get_checkpoint_by_sequence_number(highest_synced)?
445 else {
446 bail!("Checkpoint {highest_synced} not found");
447 };
448 checkpoint_db.update_highest_synced_checkpoint(&checkpoint)?;
449 }
450 Ok(())
451}