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 }