Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = { version = "0.1.0", optional = true }
shlex = { version = "1.3.0", optional = true }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
Expand All @@ -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 = []
Expand Down
5 changes: 4 additions & 1 deletion src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ pub enum DatabaseType {
/// Sqlite database
#[cfg(feature = "sqlite")]
Sqlite,
/// Redb database
#[cfg(feature = "redb")]
Redb,
}

#[cfg(any(
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
74 changes: 58 additions & 16 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
}
};

Expand All @@ -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 =
Expand All @@ -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)
}
};

Expand All @@ -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)?
Expand All @@ -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)?;
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
40 changes: 40 additions & 0 deletions src/persister.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::error::BDKCliError;
use bdk_wallet::WalletPersister;

// Types of Persistence backends supported by bdk-cli
pub(crate) 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)
}
}
}
}
Loading
Loading