fedimint_dbtool/
dump.rs

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
55/// Structure to hold the deserialized structs from the database.
56/// Also includes metadata on which sections of the database to read.
57pub 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            // Successfully read the server's config, that means this database is a server
88            // db
89            let decoders = module_inits
90                .available_decoders(cfg.iter_module_instances())
91                .unwrap()
92                .with_fallback();
93            (Some(cfg), None, decoders)
94        } else {
95            // Check if this database is a client database by reading the `ClientConfig`
96            // from the database.
97
98            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                    // Successfully read the client config, that means this database is a client db
104                    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    /// Prints the contents of the `BTreeMap` to a pretty JSON string
131    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    /// Iterates through all the specified ranges in the database and retrieves
209    /// the data for each range. Prints serialized contents at the end.
210    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    /// Iterates through each of the prefixes within the consensus range and
257    /// retrieves the corresponding data.
258    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            // Module is a global prefix for all module data
320            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}