1pub mod backend;
15pub mod completion;
16pub mod direct;
17pub mod proxy;
18pub mod shell;
19pub mod vfs;
20
21use anyhow::{Context, bail};
22use clap::Parser;
23use consensus_core::storage::rocksdb_store::RocksDBStore;
24use std::path::PathBuf;
25use std::sync::Arc;
26use sui_core::{
27 authority::{
28 authority_store_pruner::PrunerWatermarks, authority_store_tables::AuthorityPerpetualTables,
29 },
30 checkpoints::CheckpointStore,
31 epoch::committee_store::CommitteeStore,
32};
33use sui_types::committee::Committee;
34
35use self::{backend::Backend, direct::DirectBackend, proxy::ProxyBackend};
36
37#[derive(Parser, Debug)]
38#[command(
39 name = "db-shell",
40 about = "Interactive shell for navigating the validator database",
41 long_about = r#"Interactive shell for navigating the Sui validator database.
42
43Two modes of operation:
44
45 Direct mode (--db-path): opens the database files directly.
46 The node must NOT be running.
47
48 Proxy mode (--admin-url): proxies all operations through the running
49 node's admin API, allowing safe concurrent access.
50
51If both flags are given, proxy mode takes precedence.
52
53Curl-compatible API (proxy mode):
54 curl 'http://127.0.0.1:1337/db-shell/ls?path=/checkpoints/seq&limit=10'
55 curl 'http://127.0.0.1:1337/db-shell/read?path=/checkpoints/seq/1/summary&format=json'
56 curl 'http://127.0.0.1:1337/db-shell/read?path=/checkpoints/seq/1/summary&format=debug'
57 curl 'http://127.0.0.1:1337/db-shell/read?path=/checkpoints/seq/1/summary&format=bcs'
58"#
59)]
60pub struct DbShellArgs {
61 #[arg(long)]
63 pub db_path: Option<PathBuf>,
64
65 #[arg(long)]
68 pub admin_url: Option<String>,
69
70 #[arg(long, default_value = "/")]
72 pub start_path: String,
73
74 #[arg(long)]
77 pub consensus_db_path: Option<PathBuf>,
78}
79
80pub fn run(args: DbShellArgs) -> anyhow::Result<()> {
81 let backend: Arc<dyn Backend> = match (&args.admin_url, &args.db_path) {
82 (Some(url), _) => {
83 eprintln!("Connecting to sui-node admin API at {url}");
84 Arc::new(ProxyBackend::new(url)?)
85 }
86 (None, Some(db_path)) => {
87 eprintln!("Opening database at {}", db_path.display());
88 let checkpoint_store = CheckpointStore::new(
90 &db_path.join("checkpoints"),
91 Arc::new(PrunerWatermarks::default()),
92 );
93
94 let dummy_genesis = Committee::new_simple_test_committee_of_size(0).0;
98 let committee_store = Arc::new(CommitteeStore::new(
99 db_path.join("epochs"),
100 &dummy_genesis,
101 None,
102 ));
103
104 let authority_tables = Arc::new(AuthorityPerpetualTables::open(
105 &db_path.join("store"),
106 None,
107 None,
108 ));
109
110 let consensus_store = if let Some(path) = &args.consensus_db_path {
111 let path_str = path
112 .to_str()
113 .with_context(|| format!("invalid consensus DB path: {}", path.display()))?;
114 eprintln!("Opening consensus database at {path_str}");
115 Some(Arc::new(RocksDBStore::new(path_str)))
116 } else {
117 None
118 };
119
120 Arc::new(DirectBackend {
121 checkpoint_store,
122 committee_store,
123 authority_tables,
124 consensus_store,
125 })
126 }
127 (None, None) => {
128 bail!("specify either --db-path <path> (direct mode) or --admin-url <url> (proxy mode)")
129 }
130 };
131
132 let initial_cwd = vfs::parse_path(&args.start_path)
133 .with_context(|| format!("invalid start path: '{}'", args.start_path))?;
134
135 shell::run_shell(backend, initial_cwd)
136}