Skip to main content

sui_tool/db_shell/
mod.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Interactive database shell for the Sui validator database.
5//!
6//! Two operating modes:
7//!
8//! **Direct mode** (`--db-path`): opens RocksDB files directly. Requires the
9//! node to be stopped. Read-only by default; pass `--allow-writes` to enable rm.
10//!
11//! **Proxy mode** (`--admin-url`): delegates all operations to the running
12//! sui-node admin API. Allows write operations because the node owns the DB lock.
13
14pub 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    /// Path to the validator database directory (direct mode, node must be stopped).
62    #[arg(long)]
63    pub db_path: Option<PathBuf>,
64
65    /// Admin API URL of the running sui-node (proxy mode).
66    /// Example: http://127.0.0.1:1337
67    #[arg(long)]
68    pub admin_url: Option<String>,
69
70    /// Initial working directory (default: /).
71    #[arg(long, default_value = "/")]
72    pub start_path: String,
73
74    /// Path to the consensus database directory (direct mode only).
75    /// Enables the /consensus namespace. Typically at `<config_dir>/consensus_db/<key>`.
76    #[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            // CheckpointStore::new already returns Arc<CheckpointStore>.
89            let checkpoint_store = CheckpointStore::new(
90                &db_path.join("checkpoints"),
91                Arc::new(PrunerWatermarks::default()),
92            );
93
94            // CommitteeStore requires a genesis committee to initialize, but we're
95            // opening an existing database so it will already be populated.
96            // We pass a dummy genesis committee; it is only used when the DB is empty.
97            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}