From: codingp110 Date: Mon, 14 Jul 2025 17:24:11 +0000 (+0530) Subject: feat: add redb as an alternative to sqlite X-Git-Tag: v2.0.0~5^2~1 X-Git-Url: http://internal-gitweb-vhost/?a=commitdiff_plain;h=7bcbcf05d38f95f8a2b047fdd57eb320a065339e;p=bdk-cli feat: add redb as an alternative to sqlite --- diff --git a/Cargo.lock b/Cargo.lock index 365b16c..6daa9c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -185,6 +185,7 @@ dependencies = [ "bdk_electrum", "bdk_esplora", "bdk_kyoto", + "bdk_redb", "bdk_wallet", "clap", "dirs", @@ -265,6 +266,19 @@ dependencies = [ "kyoto-cbf", ] +[[package]] +name = "bdk_redb" +version = "0.1.0" +source = "git+https://github.com/110CodingP/bdk_redb#9985249c3f22489ccaf4ae09138fae950eca774b" +dependencies = [ + "bdk_chain", + "bdk_wallet", + "ciborium", + "redb", + "tempfile", + "thiserror", +] + [[package]] name = "bdk_wallet" version = "2.1.0" @@ -509,6 +523,33 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b4b0fc281743d80256607bd65e8beedc42cb0787ea119c85b81b4c0eab85e5f" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -591,6 +632,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + [[package]] name = "dirs" version = "6.0.0" @@ -878,6 +925,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1663,6 +1720,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "redb" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cef6a6d3a65ea334d6cdfb31fa2525c20184b7aa7bd1ad1e2e37502610d4609f" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.5.17" @@ -1798,7 +1864,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c869531..a72b7af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ bdk_bitcoind_rpc = { version = "0.20.0", optional = true } bdk_electrum = { version = "0.23.0", optional = true } bdk_esplora = { version = "0.22.0", features = ["async-https", "tokio"], optional = true } bdk_kyoto = { version = "0.11.0", optional = true } +bdk_redb = { git = "https://github.com/110CodingP/bdk_redb", optional = true } shlex = { version = "1.3.0", optional = true } tracing = "0.1.41" tracing-subscriber = "0.3.19" @@ -38,12 +39,13 @@ repl = ["shlex"] # Available database options sqlite = ["bdk_wallet/rusqlite"] +redb = ["bdk_redb"] # Available blockchain client options cbf = ["bdk_kyoto"] electrum = ["bdk_electrum"] esplora = ["bdk_esplora"] -rpc = ["bdk_bitcoind_rpc"] +rpc = ["bdk_bitcoind_rpc"] # Use this to consensus verify transactions at sync time verify = [] diff --git a/src/commands.rs b/src/commands.rs index dbc27b6..372b034 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -126,6 +126,9 @@ pub enum DatabaseType { /// Sqlite database #[cfg(feature = "sqlite")] Sqlite, + /// Redb database + #[cfg(feature = "redb")] + Redb, } #[cfg(any( @@ -169,7 +172,7 @@ pub struct WalletOpts { ))] #[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum, required = true)] pub client_type: ClientType, - #[cfg(feature = "sqlite")] + #[cfg(any(feature = "sqlite", feature = "redb"))] #[arg(env = "DATABASE_TYPE", short = 'd', long, value_enum, required = true)] pub database_type: DatabaseType, /// Sets the server url. diff --git a/src/error.rs b/src/error.rs index cb75715..5f548d9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -57,9 +57,18 @@ pub enum BDKCliError { #[error("PsbtError: {0}")] PsbtError(#[from] bdk_wallet::bitcoin::psbt::Error), + #[cfg(feature = "sqlite")] #[error("Rusqlite error: {0}")] RusqliteError(#[from] bdk_wallet::rusqlite::Error), + #[cfg(feature = "redb")] + #[error("Redb StoreError: {0}")] + RedbStoreError(#[from] bdk_redb::error::StoreError), + + #[cfg(feature = "redb")] + #[error("Redb dabtabase error: {0}")] + RedbDatabaseError(#[from] bdk_redb::redb::DatabaseError), + #[error("Serde json error: {0}")] SerdeJson(#[from] serde_json::Error), diff --git a/src/handlers.rs b/src/handlers.rs index 8be2e57..71ca5b4 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -13,9 +13,13 @@ use crate::commands::OfflineWalletSubCommand::*; use crate::commands::*; use crate::error::BDKCliError as Error; +#[cfg(any(feature = "sqlite", feature = "redb"))] +use crate::persister::Persister; #[cfg(feature = "cbf")] use crate::utils::BlockchainClient::KyotoClient; use crate::utils::*; +#[cfg(feature = "redb")] +use bdk_redb::Store as RedbStore; use bdk_wallet::bip39::{Language, Mnemonic}; use bdk_wallet::bitcoin::Network; use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource}; @@ -51,6 +55,8 @@ use crate::utils::BlockchainClient::Electrum; #[cfg(feature = "cbf")] use bdk_kyoto::{Info, LightClient}; use bdk_wallet::bitcoin::base64::prelude::*; +#[cfg(feature = "redb")] +use std::sync::Arc; #[cfg(feature = "cbf")] use tokio::select; #[cfg(any( @@ -751,15 +757,28 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { let home_dir = prepare_home_dir(cli_opts.datadir)?; let wallet_name = &wallet_opts.wallet; let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?; - #[cfg(feature = "sqlite")] + + #[cfg(any(feature = "sqlite", feature = "redb"))] let result = { - let mut persister = match &wallet_opts.database_type { + let mut persister: Persister = match &wallet_opts.database_type { #[cfg(feature = "sqlite")] DatabaseType::Sqlite => { let db_file = database_path.join("wallet.sqlite"); let connection = Connection::open(db_file)?; log::debug!("Sqlite database opened successfully"); - connection + Persister::Connection(connection) + } + #[cfg(feature = "redb")] + DatabaseType::Redb => { + let db = Arc::new(bdk_redb::redb::Database::create( + home_dir.join("wallet.redb"), + )?); + let store = RedbStore::new( + db, + wallet_name.as_deref().unwrap_or("wallet").to_string(), + )?; + log::debug!("Redb database opened successfully"); + Persister::RedbStore(store) } }; @@ -776,7 +795,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { wallet.persist(&mut persister)?; result }; - #[cfg(not(any(feature = "sqlite")))] + #[cfg(not(any(feature = "sqlite", feature = "redb")))] let result = { let wallet = new_wallet(network, &wallet_opts)?; let blockchain_client = @@ -792,18 +811,30 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand), } => { let network = cli_opts.network; - #[cfg(feature = "sqlite")] + #[cfg(any(feature = "sqlite", feature = "redb"))] let result = { let home_dir = prepare_home_dir(cli_opts.datadir)?; let wallet_name = &wallet_opts.wallet; - let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?; - let mut persister = match &wallet_opts.database_type { + let mut persister: Persister = match &wallet_opts.database_type { #[cfg(feature = "sqlite")] DatabaseType::Sqlite => { + let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?; let db_file = database_path.join("wallet.sqlite"); let connection = Connection::open(db_file)?; log::debug!("Sqlite database opened successfully"); - connection + Persister::Connection(connection) + } + #[cfg(feature = "redb")] + DatabaseType::Redb => { + let db = Arc::new(bdk_redb::redb::Database::create( + home_dir.join("wallet.redb"), + )?); + let store = RedbStore::new( + db, + wallet_name.as_deref().unwrap_or("wallet").to_string(), + )?; + log::debug!("Redb database opened successfully"); + Persister::RedbStore(store) } }; @@ -816,7 +847,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { wallet.persist(&mut persister)?; result }; - #[cfg(not(any(feature = "sqlite")))] + #[cfg(not(any(feature = "sqlite", feature = "redb")))] let result = { let mut wallet = new_wallet(network, &wallet_opts)?; handle_offline_wallet_subcommand(&mut wallet, &wallet_opts, offline_subcommand)? @@ -840,27 +871,38 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { #[cfg(feature = "repl")] CliSubCommand::Repl { wallet_opts } => { let network = cli_opts.network; - #[cfg(feature = "sqlite")] + #[cfg(any(feature = "sqlite", feature = "redb"))] let (mut wallet, mut persister) = { let wallet_name = &wallet_opts.wallet; let home_dir = prepare_home_dir(cli_opts.datadir.clone())?; - let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?; - - let mut persister = match &wallet_opts.database_type { + let mut persister: Persister = match &wallet_opts.database_type { #[cfg(feature = "sqlite")] DatabaseType::Sqlite => { + let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?; let db_file = database_path.join("wallet.sqlite"); let connection = Connection::open(db_file)?; log::debug!("Sqlite database opened successfully"); - connection + Persister::Connection(connection) + } + #[cfg(feature = "redb")] + DatabaseType::Redb => { + let db = Arc::new(bdk_redb::redb::Database::create( + home_dir.join("wallet.redb"), + )?); + let store = RedbStore::new( + db, + wallet_name.as_deref().unwrap_or("wallet").to_string(), + )?; + log::debug!("Redb database opened successfully"); + Persister::RedbStore(store) } }; let wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?; (wallet, persister) }; - #[cfg(not(any(feature = "sqlite")))] + #[cfg(not(any(feature = "sqlite", feature = "redb")))] let mut wallet = new_wallet(network, &wallet_opts)?; let home_dir = prepare_home_dir(cli_opts.datadir.clone())?; let database_path = prepare_wallet_db_dir(&wallet_opts.wallet, &home_dir)?; @@ -880,7 +922,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result { database_path.clone(), ) .await; - #[cfg(feature = "sqlite")] + #[cfg(any(feature = "sqlite", feature = "redb"))] wallet.persist(&mut persister)?; match result { diff --git a/src/main.rs b/src/main.rs index 6b3db95..c69aecc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,8 @@ mod commands; mod error; mod handlers; +#[cfg(any(feature = "sqlite", feature = "redb"))] +mod persister; mod utils; use bdk_wallet::bitcoin::Network; diff --git a/src/persister.rs b/src/persister.rs new file mode 100644 index 0000000..450af5c --- /dev/null +++ b/src/persister.rs @@ -0,0 +1,39 @@ +use crate::error::BDKCliError; +use bdk_wallet::WalletPersister; + +pub enum Persister { + #[cfg(feature = "sqlite")] + Connection(bdk_wallet::rusqlite::Connection), + #[cfg(feature = "redb")] + RedbStore(bdk_redb::Store), +} + +impl WalletPersister for Persister { + type Error = BDKCliError; + + fn initialize(persister: &mut Self) -> Result { + match persister { + #[cfg(feature = "sqlite")] + Persister::Connection(connection) => { + WalletPersister::initialize(connection).map_err(BDKCliError::from) + } + #[cfg(feature = "redb")] + Persister::RedbStore(store) => { + WalletPersister::initialize(store).map_err(BDKCliError::from) + } + } + } + + fn persist(persister: &mut Self, changeset: &bdk_wallet::ChangeSet) -> Result<(), Self::Error> { + match persister { + #[cfg(feature = "sqlite")] + Persister::Connection(connection) => { + WalletPersister::persist(connection, changeset).map_err(BDKCliError::from) + } + #[cfg(feature = "redb")] + Persister::RedbStore(store) => { + WalletPersister::persist(store, changeset).map_err(BDKCliError::from) + } + } + } +} diff --git a/src/utils.rs b/src/utils.rs index 7acf852..de05c66 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -33,7 +33,7 @@ use bdk_wallet::bitcoin::{Address, Network, OutPoint, ScriptBuf}; use crate::commands::ClientType; use bdk_wallet::Wallet; -#[cfg(feature = "sqlite")] +#[cfg(any(feature = "sqlite", feature = "redb"))] use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister}; /// Parse the recipient (Address,Amount) argument from cli input. @@ -214,7 +214,7 @@ pub(crate) fn new_blockchain_client( Ok(client) } -#[cfg(feature = "sqlite")] +#[cfg(any(feature = "sqlite", feature = "redb"))] /// Create a new persisted wallet from given wallet configuration options. pub(crate) fn new_persisted_wallet( network: Network, @@ -271,7 +271,7 @@ where Ok(wallet) } -#[cfg(not(any(feature = "sqlite",)))] +#[cfg(not(any(feature = "sqlite", feature = "redb")))] /// Create a new non-persisted wallet from given wallet configuration options. pub(crate) fn new_wallet(network: Network, wallet_opts: &WalletOpts) -> Result { let ext_descriptor = wallet_opts.ext_descriptor.clone();