dump.rs
1 use std::collections::BTreeMap; 2 use std::path::PathBuf; 3 4 use anyhow::Context; 5 use erased_serde::Serialize; 6 use fedimint_client::db::ClientConfigKeyPrefix; 7 use fedimint_client::module::init::ClientModuleInitRegistry; 8 use fedimint_core::config::{ClientConfig, CommonModuleInitRegistry, ServerModuleInitRegistry}; 9 use fedimint_core::core::ModuleKind; 10 use fedimint_core::db::{ 11 Database, DatabaseVersionKey, IDatabaseTransactionOpsCore, IDatabaseTransactionOpsCoreTyped, 12 }; 13 use fedimint_core::encoding::Encodable; 14 use fedimint_core::module::registry::ModuleDecoderRegistry; 15 use fedimint_core::push_db_pair_items_no_serde; 16 use fedimint_rocksdb::RocksDbReadOnly; 17 use fedimint_server::config::io::read_server_config; 18 use fedimint_server::config::ServerConfig; 19 use fedimint_server::consensus::db as ConsensusRange; 20 use futures::StreamExt; 21 use ln_gateway::Gateway; 22 use strum::IntoEnumIterator; 23 24 #[derive(Debug, serde::Serialize)] 25 struct SerdeWrapper(#[serde(with = "hex::serde")] Vec<u8>); 26 27 impl SerdeWrapper { 28 fn from_encodable<T: Encodable>(e: T) -> SerdeWrapper { 29 let mut bytes = vec![]; 30 e.consensus_encode(&mut bytes) 31 .expect("Write to vec can't fail"); 32 SerdeWrapper(bytes) 33 } 34 } 35 36 /// Structure to hold the deserialized structs from the database. 37 /// Also includes metadata on which sections of the database to read. 38 pub struct DatabaseDump { 39 serialized: BTreeMap<String, Box<dyn Serialize>>, 40 read_only: Database, 41 modules: Vec<String>, 42 prefixes: Vec<String>, 43 cfg: Option<ServerConfig>, 44 module_inits: ServerModuleInitRegistry, 45 client_cfg: Option<ClientConfig>, 46 client_module_inits: ClientModuleInitRegistry, 47 } 48 49 impl DatabaseDump { 50 pub async fn new( 51 cfg_dir: PathBuf, 52 data_dir: String, 53 password: String, 54 module_inits: ServerModuleInitRegistry, 55 client_module_inits: ClientModuleInitRegistry, 56 modules: Vec<String>, 57 prefixes: Vec<String>, 58 ) -> anyhow::Result<DatabaseDump> { 59 let read_only = match RocksDbReadOnly::open_read_only(data_dir.clone()) { 60 Ok(db) => Database::new(db, Default::default()), 61 Err(_) => { 62 panic!("Error reading RocksDB database. Quitting..."); 63 } 64 }; 65 66 let (server_cfg, client_cfg, decoders) = if let Ok(cfg) = 67 read_server_config(&password, cfg_dir).context("Failed to read server config") 68 { 69 // Successfully read the server's config, that means this database is a server 70 // db 71 let decoders = module_inits 72 .available_decoders(cfg.iter_module_instances()) 73 .unwrap() 74 .with_fallback(); 75 (Some(cfg), None, decoders) 76 } else { 77 // Check if this database is a client database by reading the `ClientConfig` 78 // from the database. 79 let db = match RocksDbReadOnly::open_read_only(data_dir) { 80 Ok(db) => Database::new(db, Default::default()), 81 Err(_) => { 82 panic!("Error reading RocksDB database. Quitting..."); 83 } 84 }; 85 86 let mut dbtx = db.begin_transaction().await; 87 let client_cfg = dbtx 88 .find_by_prefix(&ClientConfigKeyPrefix) 89 .await 90 .next() 91 .await 92 .map(|(_, client_cfg)| client_cfg); 93 94 if let Some(client_cfg) = client_cfg { 95 // Successfully read the client config, that means this database is a client db 96 let kinds = client_cfg.modules.iter().map(|(k, v)| (*k, &v.kind)); 97 let decoders = client_module_inits 98 .available_decoders(kinds) 99 .unwrap() 100 .with_fallback(); 101 (None, Some(client_cfg), decoders) 102 } else { 103 (None, None, ModuleDecoderRegistry::default()) 104 } 105 }; 106 107 Ok(DatabaseDump { 108 serialized: BTreeMap::new(), 109 read_only: read_only.with_decoders(decoders), 110 modules, 111 prefixes, 112 cfg: server_cfg, 113 module_inits, 114 client_module_inits, 115 client_cfg, 116 }) 117 } 118 } 119 120 impl DatabaseDump { 121 /// Prints the contents of the BTreeMap to a pretty JSON string 122 fn print_database(&self) { 123 let json = serde_json::to_string_pretty(&self.serialized).unwrap(); 124 println!("{json}"); 125 } 126 127 async fn serialize_module( 128 &mut self, 129 module_id: &u16, 130 kind: &ModuleKind, 131 inits: CommonModuleInitRegistry, 132 ) -> anyhow::Result<()> { 133 if !self.modules.is_empty() && !self.modules.contains(&kind.to_string()) { 134 return Ok(()); 135 } 136 let mut dbtx = self.read_only.begin_transaction().await; 137 let db_version = dbtx.get_value(&DatabaseVersionKey(*module_id)).await; 138 let mut isolated_dbtx = dbtx.to_ref_with_prefix_module_id(*module_id); 139 140 match inits.get(kind) { 141 None => { 142 tracing::warn!(module_id, %kind, "Detected configuration for unsupported module"); 143 144 let mut module_serialized = BTreeMap::new(); 145 let filtered_prefixes = (0u8..=255).filter(|f| { 146 self.prefixes.is_empty() 147 || self.prefixes.contains(&f.to_string().to_lowercase()) 148 }); 149 150 let isolated_dbtx = &mut isolated_dbtx; 151 152 for prefix in filtered_prefixes { 153 let db_items = isolated_dbtx 154 .raw_find_by_prefix(&[prefix]) 155 .await? 156 .map(|(k, v)| { 157 ( 158 k.consensus_encode_to_hex(), 159 Box::new(v.consensus_encode_to_hex()), 160 ) 161 }) 162 .collect::<BTreeMap<String, Box<_>>>() 163 .await; 164 165 module_serialized.extend(db_items); 166 } 167 self.serialized 168 .insert(format!("{kind}-{module_id}"), Box::new(module_serialized)); 169 } 170 Some(init) => { 171 let mut module_serialized = init 172 .dump_database(&mut isolated_dbtx.to_ref_nc(), self.prefixes.clone()) 173 .await 174 .collect::<BTreeMap<String, _>>(); 175 176 if let Some(db_version) = db_version { 177 module_serialized.insert("Version".to_string(), Box::new(db_version)); 178 } else { 179 module_serialized 180 .insert("Version".to_string(), Box::new("Not Specified".to_string())); 181 } 182 183 self.serialized 184 .insert(format!("{kind}-{module_id}"), Box::new(module_serialized)); 185 } 186 } 187 188 Ok(()) 189 } 190 191 async fn serialize_gateway(&mut self) -> anyhow::Result<()> { 192 let mut dbtx = self.read_only.begin_transaction_nc().await; 193 let mut dbtx = dbtx.to_ref(); 194 let gateway_serialized = Gateway::dump_database(&mut dbtx, self.prefixes.clone()) 195 .await 196 .collect::<BTreeMap<String, _>>(); 197 self.serialized 198 .insert("gateway".to_string(), Box::new(gateway_serialized)); 199 Ok(()) 200 } 201 202 /// Iterates through all the specified ranges in the database and retrieves 203 /// the data for each range. Prints serialized contents at the end. 204 pub async fn dump_database(&mut self) -> anyhow::Result<()> { 205 let cfg = self.cfg.clone(); 206 if let Some(cfg) = cfg { 207 if self.modules.is_empty() || self.modules.contains(&"consensus".to_string()) { 208 self.retrieve_consensus_data().await; 209 } 210 211 for (module_id, module_cfg) in &cfg.consensus.modules { 212 let kind = &module_cfg.kind; 213 self.serialize_module(module_id, kind, self.module_inits.to_common()) 214 .await?; 215 } 216 217 self.print_database(); 218 return Ok(()); 219 } 220 221 if let Some(cfg) = self.client_cfg.clone() { 222 for (module_id, module_cfg) in &cfg.modules { 223 let kind = &module_cfg.kind; 224 let mut modules = Vec::new(); 225 if let Some(module) = self.client_module_inits.get(kind) { 226 modules.push(module.to_dyn_common()); 227 } 228 229 let registry = CommonModuleInitRegistry::from(modules); 230 self.serialize_module(module_id, kind, registry).await?; 231 } 232 233 self.print_database(); 234 return Ok(()); 235 } 236 237 self.serialize_gateway().await?; 238 self.print_database(); 239 240 Ok(()) 241 } 242 243 /// Iterates through each of the prefixes within the consensus range and 244 /// retrieves the corresponding data. 245 async fn retrieve_consensus_data(&mut self) { 246 let mut consensus: BTreeMap<String, Box<dyn Serialize>> = BTreeMap::new(); 247 let mut dbtx = self.read_only.begin_transaction().await; 248 let dbtx = &mut dbtx; 249 let prefix_names = &self.prefixes; 250 251 let filtered_prefixes = ConsensusRange::DbKeyPrefix::iter().filter(|f| { 252 prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase()) 253 }); 254 for table in filtered_prefixes { 255 match table { 256 ConsensusRange::DbKeyPrefix::AcceptedItem => { 257 push_db_pair_items_no_serde!( 258 dbtx, 259 ConsensusRange::AcceptedItemPrefix, 260 ConsensusRange::AcceptedItemKey, 261 fedimint_server::consensus::AcceptedItem, 262 consensus, 263 "Accepted Items" 264 ); 265 } 266 ConsensusRange::DbKeyPrefix::AcceptedTransaction => { 267 push_db_pair_items_no_serde!( 268 dbtx, 269 ConsensusRange::AcceptedTransactionKeyPrefix, 270 ConsensusRange::AcceptedTransactionKey, 271 fedimint_server::consensus::AcceptedTransaction, 272 consensus, 273 "Accepted Transactions" 274 ); 275 } 276 ConsensusRange::DbKeyPrefix::SignedSessionOutcome => { 277 push_db_pair_items_no_serde!( 278 dbtx, 279 ConsensusRange::SignedSessionOutcomePrefix, 280 ConsensusRange::SignedBlockKey, 281 fedimint_server::consensus::SignedBlock, 282 consensus, 283 "Signed Blocks" 284 ); 285 } 286 ConsensusRange::DbKeyPrefix::AlephUnits => { 287 push_db_pair_items_no_serde!( 288 dbtx, 289 ConsensusRange::AlephUnitsPrefix, 290 ConsensusRange::AlephUnitsKey, 291 Vec<u8>, 292 consensus, 293 "Aleph Units" 294 ); 295 } 296 // Module is a global prefix for all module data 297 ConsensusRange::DbKeyPrefix::Module => {} 298 } 299 } 300 301 self.serialized 302 .insert("Consensus".to_string(), Box::new(consensus)); 303 } 304 }