/ fedimint-dbtool / src / lib.rs
lib.rs
  1  #![allow(where_clauses_object_safety)] // https://github.com/dtolnay/async-trait/issues/228
  2  pub mod envs;
  3  
  4  use std::path::PathBuf;
  5  
  6  use anyhow::Result;
  7  use bytes::Bytes;
  8  use clap::{Parser, Subcommand};
  9  use fedimint_client::module::init::{ClientModuleInit, ClientModuleInitRegistry};
 10  use fedimint_core::config::ServerModuleInitRegistry;
 11  use fedimint_core::db::{IDatabaseTransactionOpsCore, IRawDatabaseExt};
 12  use fedimint_core::module::ServerModuleInit;
 13  use fedimint_core::util::handle_version_hash_command;
 14  use fedimint_ln_client::LightningClientInit;
 15  use fedimint_ln_server::LightningInit;
 16  use fedimint_logging::TracingSetup;
 17  use fedimint_meta_client::MetaClientInit;
 18  use fedimint_meta_server::MetaInit;
 19  use fedimint_mint_client::MintClientInit;
 20  use fedimint_mint_server::MintInit;
 21  use fedimint_wallet_client::WalletClientInit;
 22  use fedimint_wallet_server::WalletInit;
 23  use futures::StreamExt;
 24  use hex::ToHex;
 25  
 26  use crate::dump::DatabaseDump;
 27  use crate::envs::{FM_DBTOOL_CONFIG_DIR_ENV, FM_DBTOOL_DATABASE_ENV, FM_PASSWORD_ENV};
 28  
 29  mod dump;
 30  
 31  #[derive(Debug, Clone, Parser)]
 32  #[command(version)]
 33  struct Options {
 34      #[clap(long, env = FM_DBTOOL_DATABASE_ENV)]
 35      database: String,
 36  
 37      #[clap(long, hide = true)]
 38      /// Run dbtool like it doesn't know about any module kind. This is a
 39      /// internal option for testing.
 40      no_modules: bool,
 41  
 42      #[command(subcommand)]
 43      command: DbCommand,
 44  }
 45  
 46  /// Tool to inspect and manipulate rocksdb databases. All binary arguments
 47  /// (keys, values) have to be hex encoded.
 48  #[derive(Debug, Clone, Subcommand)]
 49  enum DbCommand {
 50      /// List all key-value pairs where the key begins with `prefix`
 51      List {
 52          #[arg(long, value_parser = hex_parser)]
 53          prefix: Bytes,
 54      },
 55      /// Write a key-value pair to the database, overwriting the previous value
 56      /// if present
 57      Write {
 58          #[arg(long, value_parser = hex_parser)]
 59          key: Bytes,
 60          #[arg(long, value_parser = hex_parser)]
 61          value: Bytes,
 62      },
 63      /// Delete a single entry from the database identified by `key`
 64      Delete {
 65          #[arg(long, value_parser = hex_parser)]
 66          key: Bytes,
 67      },
 68      /// Deletes all keys starting
 69      DeletePrefix {
 70          #[arg(long, value_parser = hex_parser)]
 71          prefix: Bytes,
 72      },
 73      /// Dump a subset of the specified database and serialize the retrieved data
 74      /// to JSON. Module and prefix are used to specify which subset of the
 75      /// database to dump. Password is used to decrypt the server's
 76      /// configuration file. If dumping the client database, the password can
 77      /// be an arbitrary string.
 78      Dump {
 79          #[clap(long, env = FM_DBTOOL_CONFIG_DIR_ENV)]
 80          cfg_dir: PathBuf,
 81          #[arg(long, env = FM_PASSWORD_ENV)]
 82          password: String,
 83          #[arg(long, required = false)]
 84          modules: Option<String>,
 85          #[arg(long, required = false)]
 86          prefixes: Option<String>,
 87      },
 88  }
 89  
 90  fn hex_parser(hex: &str) -> Result<Bytes> {
 91      let bytes: Vec<u8> = hex::FromHex::from_hex(hex)?;
 92      Ok(bytes.into())
 93  }
 94  
 95  fn print_kv(key: &[u8], value: &[u8]) {
 96      println!(
 97          "{} {}",
 98          key.encode_hex::<String>(),
 99          value.encode_hex::<String>()
100      );
101  }
102  
103  pub struct FedimintDBTool {
104      server_module_inits: ServerModuleInitRegistry,
105      client_module_inits: ClientModuleInitRegistry,
106      cli_args: Options,
107  }
108  
109  impl FedimintDBTool {
110      /// Build a new `fedimintdb-tool` with a custom version hash
111      pub fn new(version_hash: &str) -> anyhow::Result<Self> {
112          handle_version_hash_command(version_hash);
113          TracingSetup::default().init()?;
114  
115          Ok(Self {
116              server_module_inits: ServerModuleInitRegistry::new(),
117              client_module_inits: ClientModuleInitRegistry::new(),
118              cli_args: Options::parse(),
119          })
120      }
121  
122      pub fn with_server_module_init<T>(mut self, gen: T) -> Self
123      where
124          T: ServerModuleInit + 'static + Send + Sync,
125      {
126          self.server_module_inits.attach(gen);
127          self
128      }
129  
130      pub fn with_client_module_init<T>(mut self, gen: T) -> Self
131      where
132          T: ClientModuleInit + 'static + Send + Sync,
133      {
134          self.client_module_inits.attach(gen);
135          self
136      }
137  
138      pub fn with_default_modules_inits(self) -> Self {
139          self.with_server_module_init(WalletInit)
140              .with_server_module_init(MintInit)
141              .with_server_module_init(LightningInit)
142              .with_server_module_init(MetaInit)
143              .with_client_module_init(WalletClientInit::default())
144              .with_client_module_init(MintClientInit)
145              .with_client_module_init(LightningClientInit::default())
146              .with_client_module_init(MetaClientInit)
147      }
148  
149      pub async fn run(&self) -> anyhow::Result<()> {
150          let options = &self.cli_args;
151          match &options.command {
152              DbCommand::List { prefix } => {
153                  let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database)
154                      .unwrap()
155                      .into_database();
156                  let mut dbtx = rocksdb.begin_transaction().await;
157                  let prefix_iter = dbtx
158                      .raw_find_by_prefix(prefix)
159                      .await?
160                      .collect::<Vec<_>>()
161                      .await;
162                  for (key, value) in prefix_iter {
163                      print_kv(&key, &value);
164                  }
165                  dbtx.commit_tx().await;
166              }
167              DbCommand::Write { key, value } => {
168                  let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database)
169                      .unwrap()
170                      .into_database();
171                  let mut dbtx = rocksdb.begin_transaction().await;
172                  dbtx.raw_insert_bytes(key, value)
173                      .await
174                      .expect("Error inserting entry into RocksDb");
175                  dbtx.commit_tx().await;
176              }
177              DbCommand::Delete { key } => {
178                  let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database)
179                      .unwrap()
180                      .into_database();
181                  let mut dbtx = rocksdb.begin_transaction().await;
182                  dbtx.raw_remove_entry(key)
183                      .await
184                      .expect("Error removing entry from RocksDb");
185                  dbtx.commit_tx().await;
186              }
187              DbCommand::Dump {
188                  cfg_dir,
189                  modules,
190                  prefixes,
191                  password,
192              } => {
193                  let modules = match modules {
194                      Some(mods) => mods
195                          .split(',')
196                          .map(|s| s.to_string().to_lowercase())
197                          .collect::<Vec<String>>(),
198                      None => Vec::new(),
199                  };
200  
201                  let prefix_names = match prefixes {
202                      Some(db_prefixes) => db_prefixes
203                          .split(',')
204                          .map(|s| s.to_string().to_lowercase())
205                          .collect::<Vec<String>>(),
206                      None => Vec::new(),
207                  };
208  
209                  let (module_inits, client_module_inits) = if options.no_modules {
210                      (
211                          ServerModuleInitRegistry::new(),
212                          ClientModuleInitRegistry::new(),
213                      )
214                  } else {
215                      (
216                          self.server_module_inits.clone(),
217                          self.client_module_inits.clone(),
218                      )
219                  };
220  
221                  let mut dbdump = DatabaseDump::new(
222                      cfg_dir.to_path_buf(),
223                      options.database.to_owned(),
224                      password.to_string(),
225                      module_inits,
226                      client_module_inits,
227                      modules,
228                      prefix_names,
229                  )
230                  .await?;
231                  dbdump.dump_database().await?;
232              }
233              DbCommand::DeletePrefix { prefix } => {
234                  let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database)
235                      .unwrap()
236                      .into_database();
237                  let mut dbtx = rocksdb.begin_transaction().await;
238                  dbtx.raw_remove_by_prefix(prefix).await?;
239                  dbtx.commit_tx().await;
240              }
241          }
242  
243          Ok(())
244      }
245  }