]> Untitled Git - bdk-cli/commitdiff
feat: add redb as an alternative to sqlite
authorcodingp110 <codingp110@gmail.com>
Mon, 14 Jul 2025 17:24:11 +0000 (22:54 +0530)
committercodingp110 <codingp110@gmail.com>
Mon, 25 Aug 2025 17:54:29 +0000 (23:24 +0530)
Cargo.lock
Cargo.toml
src/commands.rs
src/error.rs
src/handlers.rs
src/main.rs
src/persister.rs [new file with mode: 0644]
src/utils.rs

index 365b16c63ee86c3cb00cb27c7380cf1db7b6e5c1..6daa9c6480448a9466031ddafe0314311594c397 100644 (file)
@@ -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]]
index c8695318b9a1b86214c772f512de1343da307b80..a72b7af3fb8f8888d875d51a9465436ab5655fad 100644 (file)
@@ -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 = []
index dbc27b68202e9d9a10f763623401a496b38dd92e..372b03402ff35cefdb38e98d80bf7e60d1b91922 100644 (file)
@@ -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.
index cb757156d89252f32bf46829b8cc53acb181ac17..5f548d91d5f3b087b95afbb870f3bb1fda56a47b 100644 (file)
@@ -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),
 
index 8be2e5705e0b10bda60a092eea99ff573d5b1fd3..71ca5b42f7de8393997b7a05d5001e7a51de24a7 100644 (file)
 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<String, Error> {
             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<String, Error> {
                 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<String, Error> {
             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<String, Error> {
                 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<String, Error> {
         #[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<String, Error> {
                     database_path.clone(),
                 )
                 .await;
-                #[cfg(feature = "sqlite")]
+                #[cfg(any(feature = "sqlite", feature = "redb"))]
                 wallet.persist(&mut persister)?;
 
                 match result {
index 6b3db95eeb8008445ea544b4273ddafd219af5c3..c69aecc7f27465640873bb7355fde1fabe97f164 100644 (file)
@@ -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 (file)
index 0000000..450af5c
--- /dev/null
@@ -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<bdk_wallet::ChangeSet, Self::Error> {
+        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)
+            }
+        }
+    }
+}
index 7acf8521296ee173d3305ed87433fbd9bbe07e68..de05c665ddd2505435f98387710c526de03e19b9 100644 (file)
@@ -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<P: WalletPersister>(
     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<Wallet, Error> {
     let ext_descriptor = wallet_opts.ext_descriptor.clone();