Skip to content

Commit 7bcbcf0

Browse files
committed
feat: add redb as an alternative to sqlite
1 parent a46aa9d commit 7bcbcf0

File tree

8 files changed

+186
-23
lines changed

8 files changed

+186
-23
lines changed

Cargo.lock

Lines changed: 68 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bdk_bitcoind_rpc = { version = "0.20.0", optional = true }
2626
bdk_electrum = { version = "0.23.0", optional = true }
2727
bdk_esplora = { version = "0.22.0", features = ["async-https", "tokio"], optional = true }
2828
bdk_kyoto = { version = "0.11.0", optional = true }
29+
bdk_redb = { git = "https://github.com/110CodingP/bdk_redb", optional = true }
2930
shlex = { version = "1.3.0", optional = true }
3031
tracing = "0.1.41"
3132
tracing-subscriber = "0.3.19"
@@ -38,12 +39,13 @@ repl = ["shlex"]
3839

3940
# Available database options
4041
sqlite = ["bdk_wallet/rusqlite"]
42+
redb = ["bdk_redb"]
4143

4244
# Available blockchain client options
4345
cbf = ["bdk_kyoto"]
4446
electrum = ["bdk_electrum"]
4547
esplora = ["bdk_esplora"]
46-
rpc = ["bdk_bitcoind_rpc"]
48+
rpc = ["bdk_bitcoind_rpc"]
4749

4850
# Use this to consensus verify transactions at sync time
4951
verify = []

src/commands.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ pub enum DatabaseType {
126126
/// Sqlite database
127127
#[cfg(feature = "sqlite")]
128128
Sqlite,
129+
/// Redb database
130+
#[cfg(feature = "redb")]
131+
Redb,
129132
}
130133

131134
#[cfg(any(
@@ -169,7 +172,7 @@ pub struct WalletOpts {
169172
))]
170173
#[arg(env = "CLIENT_TYPE", short = 'c', long, value_enum, required = true)]
171174
pub client_type: ClientType,
172-
#[cfg(feature = "sqlite")]
175+
#[cfg(any(feature = "sqlite", feature = "redb"))]
173176
#[arg(env = "DATABASE_TYPE", short = 'd', long, value_enum, required = true)]
174177
pub database_type: DatabaseType,
175178
/// Sets the server url.

src/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,18 @@ pub enum BDKCliError {
5757
#[error("PsbtError: {0}")]
5858
PsbtError(#[from] bdk_wallet::bitcoin::psbt::Error),
5959

60+
#[cfg(feature = "sqlite")]
6061
#[error("Rusqlite error: {0}")]
6162
RusqliteError(#[from] bdk_wallet::rusqlite::Error),
6263

64+
#[cfg(feature = "redb")]
65+
#[error("Redb StoreError: {0}")]
66+
RedbStoreError(#[from] bdk_redb::error::StoreError),
67+
68+
#[cfg(feature = "redb")]
69+
#[error("Redb dabtabase error: {0}")]
70+
RedbDatabaseError(#[from] bdk_redb::redb::DatabaseError),
71+
6372
#[error("Serde json error: {0}")]
6473
SerdeJson(#[from] serde_json::Error),
6574

src/handlers.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
use crate::commands::OfflineWalletSubCommand::*;
1414
use crate::commands::*;
1515
use crate::error::BDKCliError as Error;
16+
#[cfg(any(feature = "sqlite", feature = "redb"))]
17+
use crate::persister::Persister;
1618
#[cfg(feature = "cbf")]
1719
use crate::utils::BlockchainClient::KyotoClient;
1820
use crate::utils::*;
21+
#[cfg(feature = "redb")]
22+
use bdk_redb::Store as RedbStore;
1923
use bdk_wallet::bip39::{Language, Mnemonic};
2024
use bdk_wallet::bitcoin::Network;
2125
use bdk_wallet::bitcoin::bip32::{DerivationPath, KeySource};
@@ -51,6 +55,8 @@ use crate::utils::BlockchainClient::Electrum;
5155
#[cfg(feature = "cbf")]
5256
use bdk_kyoto::{Info, LightClient};
5357
use bdk_wallet::bitcoin::base64::prelude::*;
58+
#[cfg(feature = "redb")]
59+
use std::sync::Arc;
5460
#[cfg(feature = "cbf")]
5561
use tokio::select;
5662
#[cfg(any(
@@ -751,15 +757,28 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
751757
let home_dir = prepare_home_dir(cli_opts.datadir)?;
752758
let wallet_name = &wallet_opts.wallet;
753759
let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
754-
#[cfg(feature = "sqlite")]
760+
761+
#[cfg(any(feature = "sqlite", feature = "redb"))]
755762
let result = {
756-
let mut persister = match &wallet_opts.database_type {
763+
let mut persister: Persister = match &wallet_opts.database_type {
757764
#[cfg(feature = "sqlite")]
758765
DatabaseType::Sqlite => {
759766
let db_file = database_path.join("wallet.sqlite");
760767
let connection = Connection::open(db_file)?;
761768
log::debug!("Sqlite database opened successfully");
762-
connection
769+
Persister::Connection(connection)
770+
}
771+
#[cfg(feature = "redb")]
772+
DatabaseType::Redb => {
773+
let db = Arc::new(bdk_redb::redb::Database::create(
774+
home_dir.join("wallet.redb"),
775+
)?);
776+
let store = RedbStore::new(
777+
db,
778+
wallet_name.as_deref().unwrap_or("wallet").to_string(),
779+
)?;
780+
log::debug!("Redb database opened successfully");
781+
Persister::RedbStore(store)
763782
}
764783
};
765784

@@ -776,7 +795,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
776795
wallet.persist(&mut persister)?;
777796
result
778797
};
779-
#[cfg(not(any(feature = "sqlite")))]
798+
#[cfg(not(any(feature = "sqlite", feature = "redb")))]
780799
let result = {
781800
let wallet = new_wallet(network, &wallet_opts)?;
782801
let blockchain_client =
@@ -792,18 +811,30 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
792811
subcommand: WalletSubCommand::OfflineWalletSubCommand(offline_subcommand),
793812
} => {
794813
let network = cli_opts.network;
795-
#[cfg(feature = "sqlite")]
814+
#[cfg(any(feature = "sqlite", feature = "redb"))]
796815
let result = {
797816
let home_dir = prepare_home_dir(cli_opts.datadir)?;
798817
let wallet_name = &wallet_opts.wallet;
799-
let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
800-
let mut persister = match &wallet_opts.database_type {
818+
let mut persister: Persister = match &wallet_opts.database_type {
801819
#[cfg(feature = "sqlite")]
802820
DatabaseType::Sqlite => {
821+
let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
803822
let db_file = database_path.join("wallet.sqlite");
804823
let connection = Connection::open(db_file)?;
805824
log::debug!("Sqlite database opened successfully");
806-
connection
825+
Persister::Connection(connection)
826+
}
827+
#[cfg(feature = "redb")]
828+
DatabaseType::Redb => {
829+
let db = Arc::new(bdk_redb::redb::Database::create(
830+
home_dir.join("wallet.redb"),
831+
)?);
832+
let store = RedbStore::new(
833+
db,
834+
wallet_name.as_deref().unwrap_or("wallet").to_string(),
835+
)?;
836+
log::debug!("Redb database opened successfully");
837+
Persister::RedbStore(store)
807838
}
808839
};
809840

@@ -816,7 +847,7 @@ pub(crate) async fn handle_command(cli_opts: CliOpts) -> Result<String, Error> {
816847
wallet.persist(&mut persister)?;
817848
result
818849
};
819-
#[cfg(not(any(feature = "sqlite")))]
850+
#[cfg(not(any(feature = "sqlite", feature = "redb")))]
820851
let result = {
821852
let mut wallet = new_wallet(network, &wallet_opts)?;
822853
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> {
840871
#[cfg(feature = "repl")]
841872
CliSubCommand::Repl { wallet_opts } => {
842873
let network = cli_opts.network;
843-
#[cfg(feature = "sqlite")]
874+
#[cfg(any(feature = "sqlite", feature = "redb"))]
844875
let (mut wallet, mut persister) = {
845876
let wallet_name = &wallet_opts.wallet;
846877

847878
let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
848879

849-
let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
850-
851-
let mut persister = match &wallet_opts.database_type {
880+
let mut persister: Persister = match &wallet_opts.database_type {
852881
#[cfg(feature = "sqlite")]
853882
DatabaseType::Sqlite => {
883+
let database_path = prepare_wallet_db_dir(wallet_name, &home_dir)?;
854884
let db_file = database_path.join("wallet.sqlite");
855885
let connection = Connection::open(db_file)?;
856886
log::debug!("Sqlite database opened successfully");
857-
connection
887+
Persister::Connection(connection)
888+
}
889+
#[cfg(feature = "redb")]
890+
DatabaseType::Redb => {
891+
let db = Arc::new(bdk_redb::redb::Database::create(
892+
home_dir.join("wallet.redb"),
893+
)?);
894+
let store = RedbStore::new(
895+
db,
896+
wallet_name.as_deref().unwrap_or("wallet").to_string(),
897+
)?;
898+
log::debug!("Redb database opened successfully");
899+
Persister::RedbStore(store)
858900
}
859901
};
860902
let wallet = new_persisted_wallet(network, &mut persister, &wallet_opts)?;
861903
(wallet, persister)
862904
};
863-
#[cfg(not(any(feature = "sqlite")))]
905+
#[cfg(not(any(feature = "sqlite", feature = "redb")))]
864906
let mut wallet = new_wallet(network, &wallet_opts)?;
865907
let home_dir = prepare_home_dir(cli_opts.datadir.clone())?;
866908
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> {
880922
database_path.clone(),
881923
)
882924
.await;
883-
#[cfg(feature = "sqlite")]
925+
#[cfg(any(feature = "sqlite", feature = "redb"))]
884926
wallet.persist(&mut persister)?;
885927

886928
match result {

src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
mod commands;
1414
mod error;
1515
mod handlers;
16+
#[cfg(any(feature = "sqlite", feature = "redb"))]
17+
mod persister;
1618
mod utils;
1719

1820
use bdk_wallet::bitcoin::Network;

src/persister.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use crate::error::BDKCliError;
2+
use bdk_wallet::WalletPersister;
3+
4+
pub enum Persister {
5+
#[cfg(feature = "sqlite")]
6+
Connection(bdk_wallet::rusqlite::Connection),
7+
#[cfg(feature = "redb")]
8+
RedbStore(bdk_redb::Store),
9+
}
10+
11+
impl WalletPersister for Persister {
12+
type Error = BDKCliError;
13+
14+
fn initialize(persister: &mut Self) -> Result<bdk_wallet::ChangeSet, Self::Error> {
15+
match persister {
16+
#[cfg(feature = "sqlite")]
17+
Persister::Connection(connection) => {
18+
WalletPersister::initialize(connection).map_err(BDKCliError::from)
19+
}
20+
#[cfg(feature = "redb")]
21+
Persister::RedbStore(store) => {
22+
WalletPersister::initialize(store).map_err(BDKCliError::from)
23+
}
24+
}
25+
}
26+
27+
fn persist(persister: &mut Self, changeset: &bdk_wallet::ChangeSet) -> Result<(), Self::Error> {
28+
match persister {
29+
#[cfg(feature = "sqlite")]
30+
Persister::Connection(connection) => {
31+
WalletPersister::persist(connection, changeset).map_err(BDKCliError::from)
32+
}
33+
#[cfg(feature = "redb")]
34+
Persister::RedbStore(store) => {
35+
WalletPersister::persist(store, changeset).map_err(BDKCliError::from)
36+
}
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)