#![deny(clippy::pedantic)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::return_self_not_must_use)]
pub mod envs;
use std::path::PathBuf;
use anyhow::Result;
use bytes::Bytes;
use clap::{Parser, Subcommand};
use fedimint_client::module::init::{ClientModuleInit, ClientModuleInitRegistry};
use fedimint_core::config::ServerModuleInitRegistry;
use fedimint_core::db::{IDatabaseTransactionOpsCore, IRawDatabaseExt};
use fedimint_core::module::ServerModuleInit;
use fedimint_core::util::handle_version_hash_command;
use fedimint_ln_client::LightningClientInit;
use fedimint_ln_server::LightningInit;
use fedimint_logging::TracingSetup;
use fedimint_meta_client::MetaClientInit;
use fedimint_meta_server::MetaInit;
use fedimint_mint_client::MintClientInit;
use fedimint_mint_server::MintInit;
use fedimint_wallet_client::WalletClientInit;
use fedimint_wallet_server::WalletInit;
use futures::StreamExt;
use hex::ToHex;
use crate::dump::DatabaseDump;
use crate::envs::{FM_DBTOOL_CONFIG_DIR_ENV, FM_DBTOOL_DATABASE_ENV, FM_PASSWORD_ENV};
mod dump;
#[derive(Debug, Clone, Parser)]
#[command(version)]
struct Options {
#[clap(long, env = FM_DBTOOL_DATABASE_ENV)]
database_dir: String,
#[clap(long, hide = true)]
no_modules: bool,
#[command(subcommand)]
command: DbCommand,
}
#[derive(Debug, Clone, Subcommand)]
enum DbCommand {
List {
#[arg(long, value_parser = hex_parser)]
prefix: Bytes,
},
Write {
#[arg(long, value_parser = hex_parser)]
key: Bytes,
#[arg(long, value_parser = hex_parser)]
value: Bytes,
},
Delete {
#[arg(long, value_parser = hex_parser)]
key: Bytes,
},
DeletePrefix {
#[arg(long, value_parser = hex_parser)]
prefix: Bytes,
},
Dump {
#[clap(long, env = FM_DBTOOL_CONFIG_DIR_ENV)]
cfg_dir: PathBuf,
#[arg(long, env = FM_PASSWORD_ENV)]
password: String,
#[arg(long, required = false)]
modules: Option<String>,
#[arg(long, required = false)]
prefixes: Option<String>,
},
}
fn hex_parser(hex: &str) -> Result<Bytes> {
let bytes: Vec<u8> = hex::FromHex::from_hex(hex)?;
Ok(bytes.into())
}
fn print_kv(key: &[u8], value: &[u8]) {
println!(
"{} {}",
key.encode_hex::<String>(),
value.encode_hex::<String>()
);
}
pub struct FedimintDBTool {
server_module_inits: ServerModuleInitRegistry,
client_module_inits: ClientModuleInitRegistry,
cli_args: Options,
}
impl FedimintDBTool {
pub fn new(version_hash: &str) -> anyhow::Result<Self> {
handle_version_hash_command(version_hash);
TracingSetup::default().init()?;
Ok(Self {
server_module_inits: ServerModuleInitRegistry::new(),
client_module_inits: ClientModuleInitRegistry::new(),
cli_args: Options::parse(),
})
}
pub fn with_server_module_init<T>(mut self, gen: T) -> Self
where
T: ServerModuleInit + 'static + Send + Sync,
{
self.server_module_inits.attach(gen);
self
}
pub fn with_client_module_init<T>(mut self, gen: T) -> Self
where
T: ClientModuleInit + 'static + Send + Sync,
{
self.client_module_inits.attach(gen);
self
}
pub fn with_default_modules_inits(self) -> Self {
self.with_server_module_init(WalletInit)
.with_server_module_init(MintInit)
.with_server_module_init(LightningInit)
.with_server_module_init(fedimint_lnv2_server::LightningInit)
.with_server_module_init(MetaInit)
.with_client_module_init(WalletClientInit::default())
.with_client_module_init(MintClientInit)
.with_client_module_init(LightningClientInit::default())
.with_client_module_init(fedimint_lnv2_client::LightningClientInit::default())
.with_client_module_init(MetaClientInit)
}
pub async fn run(&self) -> anyhow::Result<()> {
let options = &self.cli_args;
match &options.command {
DbCommand::List { prefix } => {
let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database_dir)
.unwrap()
.into_database();
let mut dbtx = rocksdb.begin_transaction().await;
let prefix_iter = dbtx
.raw_find_by_prefix(prefix)
.await?
.collect::<Vec<_>>()
.await;
for (key, value) in prefix_iter {
print_kv(&key, &value);
}
dbtx.commit_tx().await;
}
DbCommand::Write { key, value } => {
let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database_dir)
.unwrap()
.into_database();
let mut dbtx = rocksdb.begin_transaction().await;
dbtx.raw_insert_bytes(key, value)
.await
.expect("Error inserting entry into RocksDb");
dbtx.commit_tx().await;
}
DbCommand::Delete { key } => {
let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database_dir)
.unwrap()
.into_database();
let mut dbtx = rocksdb.begin_transaction().await;
dbtx.raw_remove_entry(key)
.await
.expect("Error removing entry from RocksDb");
dbtx.commit_tx().await;
}
DbCommand::Dump {
cfg_dir,
modules,
prefixes,
password,
} => {
let modules = match modules {
Some(mods) => mods
.split(',')
.map(|s| s.to_string().to_lowercase())
.collect::<Vec<String>>(),
None => Vec::new(),
};
let prefix_names = match prefixes {
Some(db_prefixes) => db_prefixes
.split(',')
.map(|s| s.to_string().to_lowercase())
.collect::<Vec<String>>(),
None => Vec::new(),
};
let (module_inits, client_module_inits) = if options.no_modules {
(
ServerModuleInitRegistry::new(),
ClientModuleInitRegistry::new(),
)
} else {
(
self.server_module_inits.clone(),
self.client_module_inits.clone(),
)
};
let mut dbdump = DatabaseDump::new(
cfg_dir.clone(),
options.database_dir.clone(),
password.to_string(),
module_inits,
client_module_inits,
modules,
prefix_names,
)
.await?;
dbdump.dump_database().await?;
}
DbCommand::DeletePrefix { prefix } => {
let rocksdb = fedimint_rocksdb::RocksDb::open(&options.database_dir)
.unwrap()
.into_database();
let mut dbtx = rocksdb.begin_transaction().await;
dbtx.raw_remove_by_prefix(prefix).await?;
dbtx.commit_tx().await;
}
}
Ok(())
}
}