/ fedimint-dbtool / src / dump.rs
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  }