1use std::collections::BTreeMap;
2use std::path::PathBuf;
3
4use anyhow::Context;
5use erased_serde::Serialize;
6use fedimint_client::db::{ClientConfigKey, OperationLogKeyPrefix};
7use fedimint_client::module_init::ClientModuleInitRegistry;
8use fedimint_client_module::oplog::OperationLogEntry;
9use fedimint_core::config::{ClientConfig, CommonModuleInitRegistry};
10use fedimint_core::core::ModuleKind;
11use fedimint_core::db::{
12 Database, DatabaseTransaction, DatabaseVersionKey, IDatabaseTransactionOpsCore,
13 IDatabaseTransactionOpsCoreTyped,
14};
15use fedimint_core::encoding::Encodable;
16use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
17use fedimint_core::push_db_pair_items;
18use fedimint_gateway_server::Gateway;
19use fedimint_rocksdb::RocksDbReadOnly;
20use fedimint_server::config::ServerConfig;
21use fedimint_server::config::io::read_server_config;
22use fedimint_server::consensus::db as consensus_db;
23use fedimint_server::core::{ServerModuleInitRegistry, ServerModuleInitRegistryExt};
24use fedimint_server::db as server_db;
25use fedimint_server::net::api::announcement::ApiAnnouncementPrefix;
26use futures::StreamExt;
27use strum::IntoEnumIterator;
28
29macro_rules! push_db_pair_items_no_serde {
30 ($dbtx:ident, $prefix_type:expr_2021, $key_type:ty, $value_type:ty, $map:ident, $key_literal:literal) => {
31 let db_items = IDatabaseTransactionOpsCoreTyped::find_by_prefix($dbtx, &$prefix_type)
32 .await
33 .map(|(key, val)| {
34 (
35 Encodable::consensus_encode_to_hex(&key),
36 SerdeWrapper::from_encodable(&val),
37 )
38 })
39 .collect::<BTreeMap<_, _>>()
40 .await;
41
42 $map.insert($key_literal.to_string(), Box::new(db_items));
43 };
44}
45
46#[derive(Debug, serde::Serialize)]
47struct SerdeWrapper(#[serde(with = "hex::serde")] Vec<u8>);
48
49impl SerdeWrapper {
50 fn from_encodable<T: Encodable>(e: &T) -> SerdeWrapper {
51 SerdeWrapper(e.consensus_encode_to_vec())
52 }
53}
54
55pub struct DatabaseDump {
58 serialized: BTreeMap<String, Box<dyn Serialize>>,
59 read_only_db: Database,
60 modules: Vec<String>,
61 prefixes: Vec<String>,
62 server_cfg: Option<ServerConfig>,
63 module_inits: ServerModuleInitRegistry,
64 client_cfg: Option<ClientConfig>,
65 client_module_inits: ClientModuleInitRegistry,
66}
67
68impl DatabaseDump {
69 pub async fn new(
70 cfg_dir: PathBuf,
71 data_dir: String,
72 password: String,
73 module_inits: ServerModuleInitRegistry,
74 client_module_inits: ClientModuleInitRegistry,
75 modules: Vec<String>,
76 prefixes: Vec<String>,
77 ) -> anyhow::Result<DatabaseDump> {
78 let Ok(read_only_rocks_db) = RocksDbReadOnly::open_read_only(data_dir.clone()) else {
79 panic!("Error reading RocksDB database. Quitting...");
80 };
81
82 let read_only_db = Database::new(read_only_rocks_db, ModuleRegistry::default());
83
84 let (server_cfg, client_cfg, decoders) = if let Ok(cfg) =
85 read_server_config(&password, &cfg_dir).context("Failed to read server config")
86 {
87 let decoders = module_inits
90 .available_decoders(cfg.iter_module_instances())
91 .unwrap()
92 .with_fallback();
93 (Some(cfg), None, decoders)
94 } else {
95 let mut dbtx = read_only_db.begin_transaction_nc().await;
99 let client_cfg_or = dbtx.get_value(&ClientConfigKey).await;
100
101 match client_cfg_or {
102 Some(client_cfg) => {
103 let kinds = client_cfg.modules.iter().map(|(k, v)| (*k, &v.kind));
105 let decoders = client_module_inits
106 .available_decoders(kinds)
107 .unwrap()
108 .with_fallback();
109 let client_cfg = client_cfg.redecode_raw(&decoders)?;
110 (None, Some(client_cfg), decoders)
111 }
112 _ => (None, None, ModuleDecoderRegistry::default()),
113 }
114 };
115
116 Ok(DatabaseDump {
117 serialized: BTreeMap::new(),
118 read_only_db: read_only_db.with_decoders(decoders),
119 modules,
120 prefixes,
121 server_cfg,
122 module_inits,
123 client_module_inits,
124 client_cfg,
125 })
126 }
127}
128
129impl DatabaseDump {
130 fn print_database(&self) {
132 let json = serde_json::to_string_pretty(&self.serialized).unwrap();
133 println!("{json}");
134 }
135
136 async fn serialize_module(
137 &mut self,
138 module_id: &u16,
139 kind: &ModuleKind,
140 inits: CommonModuleInitRegistry,
141 ) -> anyhow::Result<()> {
142 if !self.modules.is_empty() && !self.modules.contains(&kind.to_string()) {
143 return Ok(());
144 }
145 let mut dbtx = self.read_only_db.begin_transaction_nc().await;
146 let db_version = dbtx.get_value(&DatabaseVersionKey(*module_id)).await;
147 let mut isolated_dbtx = dbtx.to_ref_with_prefix_module_id(*module_id).0;
148
149 match inits.get(kind) {
150 None => {
151 tracing::warn!(module_id, %kind, "Detected configuration for unsupported module");
152
153 let mut module_serialized = BTreeMap::new();
154 let filtered_prefixes = (0u8..=255).filter(|f| {
155 self.prefixes.is_empty()
156 || self.prefixes.contains(&f.to_string().to_lowercase())
157 });
158
159 let isolated_dbtx = &mut isolated_dbtx;
160
161 for prefix in filtered_prefixes {
162 let db_items = isolated_dbtx
163 .raw_find_by_prefix(&[prefix])
164 .await?
165 .map(|(k, v)| {
166 (
167 k.consensus_encode_to_hex(),
168 Box::new(v.consensus_encode_to_hex()),
169 )
170 })
171 .collect::<BTreeMap<String, Box<_>>>()
172 .await;
173
174 module_serialized.extend(db_items);
175 }
176 self.serialized
177 .insert(format!("{kind}-{module_id}"), Box::new(module_serialized));
178 }
179 Some(init) => {
180 let mut module_serialized = init
181 .dump_database(&mut isolated_dbtx.to_ref_nc(), self.prefixes.clone())
182 .await
183 .collect::<BTreeMap<String, _>>();
184
185 if let Some(db_version) = db_version {
186 module_serialized.insert("Version".to_string(), Box::new(db_version));
187 } else {
188 module_serialized
189 .insert("Version".to_string(), Box::new("Not Specified".to_string()));
190 }
191
192 self.serialized
193 .insert(format!("{kind}-{module_id}"), Box::new(module_serialized));
194 }
195 }
196
197 Ok(())
198 }
199
200 async fn serialize_gateway(&mut self) -> anyhow::Result<()> {
201 let mut dbtx = self.read_only_db.begin_transaction_nc().await;
202 let gateway_serialized = Gateway::dump_database(&mut dbtx, self.prefixes.clone()).await;
203 self.serialized
204 .insert("gateway".to_string(), Box::new(gateway_serialized));
205 Ok(())
206 }
207
208 pub async fn dump_database(&mut self) -> anyhow::Result<()> {
211 if let Some(cfg) = self.server_cfg.clone() {
212 if self.modules.is_empty() || self.modules.contains(&"consensus".to_string()) {
213 self.retrieve_consensus_data().await;
214 }
215
216 for (module_id, module_cfg) in &cfg.consensus.modules {
217 let kind = &module_cfg.kind;
218 self.serialize_module(module_id, kind, self.module_inits.to_common())
219 .await?;
220 }
221
222 self.print_database();
223 return Ok(());
224 }
225
226 if let Some(cfg) = self.client_cfg.clone() {
227 self.serialized
228 .insert("Client Config".into(), Box::new(cfg.to_json()));
229
230 for (module_id, module_cfg) in &cfg.modules {
231 let kind = &module_cfg.kind;
232 let mut modules = Vec::new();
233 if let Some(module) = self.client_module_inits.get(kind) {
234 modules.push(module.to_dyn_common());
235 }
236
237 let registry = CommonModuleInitRegistry::from(modules);
238 self.serialize_module(module_id, kind, registry).await?;
239 }
240
241 {
242 let mut dbtx = self.read_only_db.begin_transaction_nc().await;
243 Self::write_serialized_client_operation_log(&mut self.serialized, &mut dbtx).await;
244 }
245
246 self.print_database();
247 return Ok(());
248 }
249
250 self.serialize_gateway().await?;
251 self.print_database();
252
253 Ok(())
254 }
255
256 async fn retrieve_consensus_data(&mut self) {
259 let filtered_prefixes = server_db::DbKeyPrefix::iter().filter(|prefix| {
260 self.prefixes.is_empty() || self.prefixes.contains(&prefix.to_string().to_lowercase())
261 });
262 let mut dbtx = self.read_only_db.begin_transaction_nc().await;
263 let mut consensus: BTreeMap<String, Box<dyn Serialize>> = BTreeMap::new();
264
265 for table in filtered_prefixes {
266 Self::write_serialized_consensus_range(table, &mut dbtx, &mut consensus).await;
267 }
268
269 self.serialized
270 .insert("Consensus".to_string(), Box::new(consensus));
271 }
272
273 async fn write_serialized_consensus_range(
274 table: server_db::DbKeyPrefix,
275 dbtx: &mut DatabaseTransaction<'_>,
276 consensus: &mut BTreeMap<String, Box<dyn Serialize>>,
277 ) {
278 match table {
279 server_db::DbKeyPrefix::AcceptedItem => {
280 push_db_pair_items_no_serde!(
281 dbtx,
282 consensus_db::AcceptedItemPrefix,
283 server_db::AcceptedItemKey,
284 fedimint_server::consensus::AcceptedItem,
285 consensus,
286 "Accepted Items"
287 );
288 }
289 server_db::DbKeyPrefix::AcceptedTransaction => {
290 push_db_pair_items_no_serde!(
291 dbtx,
292 consensus_db::AcceptedTransactionKeyPrefix,
293 server_db::AcceptedTransactionKey,
294 fedimint_server::consensus::AcceptedTransaction,
295 consensus,
296 "Accepted Transactions"
297 );
298 }
299 server_db::DbKeyPrefix::SignedSessionOutcome => {
300 push_db_pair_items_no_serde!(
301 dbtx,
302 consensus_db::SignedSessionOutcomePrefix,
303 server_db::SignedBlockKey,
304 fedimint_server::consensus::SignedBlock,
305 consensus,
306 "Signed Blocks"
307 );
308 }
309 server_db::DbKeyPrefix::AlephUnits => {
310 push_db_pair_items_no_serde!(
311 dbtx,
312 consensus_db::AlephUnitsPrefix,
313 server_db::AlephUnitsKey,
314 Vec<u8>,
315 consensus,
316 "Aleph Units"
317 );
318 }
319 server_db::DbKeyPrefix::Module
321 | server_db::DbKeyPrefix::ServerInfo
322 | server_db::DbKeyPrefix::DatabaseVersion
323 | server_db::DbKeyPrefix::ClientBackup => {}
324 server_db::DbKeyPrefix::ApiAnnouncements => {
325 push_db_pair_items_no_serde!(
326 dbtx,
327 ApiAnnouncementPrefix,
328 ApiAnnouncementKey,
329 fedimint_core::net::api_announcement::SignedApiAnnouncement,
330 consensus,
331 "API Announcements"
332 );
333 }
334 }
335 }
336 async fn write_serialized_client_operation_log(
337 serialized: &mut BTreeMap<String, Box<dyn Serialize>>,
338 dbtx: &mut DatabaseTransaction<'_>,
339 ) {
340 push_db_pair_items!(
341 dbtx,
342 OperationLogKeyPrefix,
343 OperationLogKey,
344 OperationLogEntry,
345 serialized,
346 "Operations"
347 );
348 }
349}