Skip to content
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `get_internal_address` to allow you to get internal addresses just as you get external addresses.
- added `ensure_addresses_cached` to `Wallet` to let offline wallets load and cache addresses in their database
- Add `is_spent` field to `LocalUtxo`; when we notice that a utxo has been spent we set `is_spent` field to true instead of deleting it from the db.
- Added `Wallet::get_signers()`, `Wallet::get_transaction()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
- Add traits to reuse `Blockchain`s across multiple wallets (`BlockchainFactory` and `StatelessBlockchain`).

### Sync API change

Expand Down Expand Up @@ -437,4 +439,4 @@ final transaction is created by calling `finish` on the builder.
[v0.16.0]: https://github.com/bitcoindevkit/bdk/compare/v0.15.0...v0.16.0
[v0.16.1]: https://github.com/bitcoindevkit/bdk/compare/v0.16.0...v0.16.1
[v0.17.0]: https://github.com/bitcoindevkit/bdk/compare/v0.16.1...v0.17.0
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.17.0...HEAD
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.17.0...HEAD
2 changes: 2 additions & 0 deletions src/blockchain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ impl Blockchain for ElectrumBlockchain {
}
}

impl StatelessBlockchain for ElectrumBlockchain {}

impl GetHeight for ElectrumBlockchain {
fn get_height(&self) -> Result<u32, Error> {
// TODO: unsubscribe when added to the client, or is there a better call to use here?
Expand Down
2 changes: 2 additions & 0 deletions src/blockchain/esplora/reqwest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ impl Blockchain for EsploraBlockchain {
}
}

impl StatelessBlockchain for EsploraBlockchain {}

#[maybe_async]
impl GetHeight for EsploraBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Expand Down
2 changes: 2 additions & 0 deletions src/blockchain/esplora/ureq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ impl Blockchain for EsploraBlockchain {
}
}

impl StatelessBlockchain for EsploraBlockchain {}

impl GetHeight for EsploraBlockchain {
fn get_height(&self) -> Result<u32, Error> {
Ok(self.url_client._get_height()?)
Expand Down
55 changes: 55 additions & 0 deletions src/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,61 @@ pub trait ConfigurableBlockchain: Blockchain + Sized {
fn from_config(config: &Self::Config) -> Result<Self, Error>;
}

/// Trait for blockchains that don't contain any state
///
/// Statless blockchains can be used to sync multiple wallets with different descriptors.
///
/// [`BlockchainFactory`] is automatically implemented for `Arc<T>` where `T` is a stateless
/// blockchain.
pub trait StatelessBlockchain: Blockchain {}

/// Trait for a factory of blockchains that share the underlying connection or configuration
///
/// ## Example
///
/// This example shows how to sync multiple walles and return the sum of their balances
///
/// ```no_run
/// # use bdk::Error;
/// # use bdk::blockchain::*;
/// # use bdk::database::*;
/// # use bdk::wallet::*;
/// fn sum_of_balances<B: BlockchainFactory>(blockchain_factory: B, wallets: &[Wallet<MemoryDatabase>]) -> Result<u64, Error> {
/// Ok(wallets
/// .iter()
/// .map(|w| -> Result<_, Error> {
/// w.sync(&blockchain_factory.build("wallet_1", None)?, SyncOptions::default())?;
/// w.get_balance()
/// })
/// .collect::<Result<Vec<_>, _>>()?
/// .into_iter()
/// .sum())
/// }
/// ```
pub trait BlockchainFactory {
/// The type returned when building a blockchain from this factory
type Inner: Blockchain;

/// Build a new blockchain for the given descriptor checksum
///
/// If `override_skip_blocks` is `None`, the returned blockchain will inherit the number of blocks
/// from the factory. Since it's not possible to override the value to `None`, set it to
/// `Some(0)` to rescan from the genesis.
fn build(
&self,
checksum: &str,
override_skip_blocks: Option<u32>,
) -> Result<Self::Inner, Error>;
}

impl<T: StatelessBlockchain> BlockchainFactory for Arc<T> {
type Inner = Self;

fn build(&self, _checksum: &str, _override_skip_blocks: Option<u32>) -> Result<Self, Error> {
Ok(Arc::clone(self))
}
}

/// Data sent with a progress update over a [`channel`]
pub type ProgressData = (f32, Option<String>);

Expand Down
97 changes: 97 additions & 0 deletions src/blockchain/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,57 @@ fn list_wallet_dir(client: &Client) -> Result<Vec<String>, Error> {
Ok(result.wallets.into_iter().map(|n| n.name).collect())
}

/// Factory of [`RpcBlockchain`] instances, implements [`BlockchainFactory`]
///
/// Internally caches the node url and authentication params and allows getting many different [`RpcBlockchain`]
/// objects for different wallet names and with different rescan heights.
///
/// ## Example
///
/// ```no_run
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let factory = RpcBlockchainFactory {
/// url: "http://127.0.0.1:18332",
/// auth: Auth::Cookie { file: "/home/user/.bitcoin/.cookie".to_string() },
/// network: Network::Testnet,
/// wallet_name_prefix: "prefix-".to_string(),
/// default_skip_blocks: 100_000,
/// };
/// let main_wallet_blockchain = factory.build("main_wallet", Some(200_000))?;
/// # Ok(())
/// ```
#[derive(Debug, Clone)]
pub struct RpcBlockchainFactory {
/// The bitcoin node url
pub url: String,
/// The bitcoin node authentication mechanism
pub auth: Auth,
/// The network we are using (it will be checked the bitcoin node network matches this)
pub network: Network,
/// The prefix used to build the full wallet name for blockchains
pub wallet_name_prefix: String,
/// Default number of blocks to skip which will be inherited by blockchain unless overridden
pub default_skip_blocks: u32,
}

impl BlockchainFactory for RpcBlockchainFactory {
type Inner = RpcBlockchain;

fn build(
&self,
checksum: &str,
override_skip_blocks: Option<u32>,
) -> Result<Self::Inner, Error> {
Ok(RpcBlockchain::from_config(&RpcConfig {
url: self.url.clone(),
auth: self.auth.clone(),
network: self.network,
wallet_name: format!("{}{}", self.wallet_name_prefix, checksum),
skip_blocks: Some(override_skip_blocks.unwrap_or(self.default_skip_blocks)),
})?)
}
}

#[cfg(test)]
#[cfg(feature = "test-rpc")]
crate::bdk_blockchain_tests! {
Expand All @@ -456,3 +507,49 @@ crate::bdk_blockchain_tests! {
RpcBlockchain::from_config(&config).unwrap()
}
}

#[cfg(test)]
#[cfg(feature = "test-rpc")]
mod test {
use super::*;
use crate::blockchain::*;
use crate::testutils::blockchain_tests::TestClient;

use bitcoin::Network;
use bitcoincore_rpc::RpcApi;

#[test]
fn test_rpc_blockchain_factory() {
let test_client = TestClient::default();

let factory = RpcBlockchainFactory {
url: test_client.bitcoind.rpc_url(),
auth: Auth::Cookie {
file: test_client.bitcoind.params.cookie_file.clone(),
},
network: Network::Regtest,
wallet_name_prefix: "prefix-".into(),
default_skip_blocks: 0,
};

let a = factory.build("aaaaaa", None).unwrap();
assert_eq!(a.skip_blocks, Some(0));
assert_eq!(
a.client
.get_wallet_info()
.expect("Node connection is working")
.wallet_name,
"prefix-aaaaaa"
);

let b = factory.build("bbbbbb", Some(100)).unwrap();
assert_eq!(b.skip_blocks, Some(100));
assert_eq!(
a.client
.get_wallet_info()
.expect("Node connection is working")
.wallet_name,
"prefix-bbbbbb"
);
}
}
55 changes: 46 additions & 9 deletions src/descriptor/derived.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,41 @@
// licenses.

//! Derived descriptor keys
//!
//! The [`DerivedDescriptorKey`] type is a wrapper over the standard [`DescriptorPublicKey`] which
//! guarantees that all the extended keys have a fixed derivation path, i.e. all the wildcards have
//! been replaced by actual derivation indexes.
//!
//! The [`AsDerived`] trait provides a quick way to derive descriptors to obtain a
//! `Descriptor<DerivedDescriptorKey>` type. This, in turn, can be used to derive public
//! keys for arbitrary derivation indexes.
//!
//! Combining this with [`Wallet::get_signers`], secret keys can also be derived.
//!
//! # Example
//!
//! ```
//! # use std::str::FromStr;
//! # use bitcoin::secp256k1::Secp256k1;
//! use bdk::descriptor::{AsDerived, DescriptorPublicKey};
//! use bdk::miniscript::{ToPublicKey, TranslatePk, MiniscriptKey};
//!
//! let secp = Secp256k1::gen_new();
//!
//! let key = DescriptorPublicKey::from_str("[aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/*")?;
//! let (descriptor, _, _) = bdk::descriptor!(wpkh(key))?;
//!
//! // derived: wpkh([aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/42)#3ladd0t2
//! let derived = descriptor.as_derived(42, &secp);
//! println!("derived: {}", derived);
//!
//! // with_pks: wpkh(02373ecb54c5e83bd7e0d40adf78b65efaf12fafb13571f0261fc90364eee22e1e)#p4jjgvll
//! let with_pks = derived.translate_pk_infallible(|pk| pk.to_public_key(), |pkh| pkh.to_public_key().to_pubkeyhash());
//! println!("with_pks: {}", with_pks);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers

use std::cmp::Ordering;
use std::fmt;
Expand All @@ -19,10 +54,7 @@ use std::ops::Deref;
use bitcoin::hashes::hash160;
use bitcoin::PublicKey;

pub use miniscript::{
descriptor::KeyMap, descriptor::Wildcard, Descriptor, DescriptorPublicKey, Legacy, Miniscript,
ScriptContext, Segwitv0,
};
use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};

use crate::wallet::utils::SecpCtx;
Expand Down Expand Up @@ -119,14 +151,19 @@ impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
}
}

pub(crate) trait AsDerived {
// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
/// Utilities to derive descriptors
///
/// Check out the [module level] documentation for more.
///
/// [module level]: crate::descriptor::derived
pub trait AsDerived {
/// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
-> Descriptor<DerivedDescriptorKey<'s>>;

// Transform the keys into `DerivedDescriptorKey`.
//
// Panics if the descriptor is not "fixed", i.e. if it's derivable
/// Transform the keys into `DerivedDescriptorKey`.
///
/// Panics if the descriptor is not "fixed", i.e. if it's derivable
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>;
}

Expand Down
12 changes: 6 additions & 6 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,25 @@ use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerpr
use bitcoin::util::psbt;
use bitcoin::{Network, PublicKey, Script, TxOut};

use miniscript::descriptor::{
DescriptorPublicKey, DescriptorType, DescriptorXKey, InnerXKey, Wildcard,
use miniscript::descriptor::{DescriptorType, InnerXKey};
pub use miniscript::{
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
};
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};

use crate::descriptor::policy::BuildSatisfaction;

pub mod checksum;
pub(crate) mod derived;
pub mod derived;
#[doc(hidden)]
pub mod dsl;
pub mod error;
pub mod policy;
pub mod template;

pub use self::checksum::get_checksum;
use self::derived::AsDerived;
pub use self::derived::DerivedDescriptorKey;
pub use self::derived::{AsDerived, DerivedDescriptorKey};
pub use self::error::Error as DescriptorError;
pub use self::policy::Policy;
use self::template::DescriptorTemplateOut;
Expand Down
28 changes: 19 additions & 9 deletions src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ use bitcoin::secp256k1::{self, Secp256k1, Signing};
use bitcoin::util::bip32;
use bitcoin::{Network, PrivateKey, PublicKey};

use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
use miniscript::descriptor::{Descriptor, Wildcard};
pub use miniscript::descriptor::{
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
SortedMultiVec,
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub,
DescriptorXKey, KeyMap, SortedMultiVec,
};
pub use miniscript::ScriptContext;
use miniscript::{Miniscript, Terminal};
Expand Down Expand Up @@ -94,6 +94,13 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
}
}

pub fn as_public(&self, secp: &SecpCtx) -> Result<DescriptorPublicKey, KeyError> {
match self {
DescriptorKey::Public(pk, _, _) => Ok(pk.clone()),
DescriptorKey::Secret(secret, _, _) => Ok(secret.as_public(secp)?),
}
}

// This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
// public because it is effectively called by external crates, once the macros are expanded,
// but since it is not meant to be part of the public api we hide it from the docs.
Expand All @@ -102,18 +109,15 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
self,
secp: &SecpCtx,
) -> Result<(DescriptorPublicKey, KeyMap, ValidNetworks), KeyError> {
let public = self.as_public(secp)?;

match self {
DescriptorKey::Public(public, valid_networks, _) => {
DescriptorKey::Public(_, valid_networks, _) => {
Ok((public, KeyMap::default(), valid_networks))
}
DescriptorKey::Secret(secret, valid_networks, _) => {
let mut key_map = KeyMap::with_capacity(1);

let public = secret
.as_public(secp)
.map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
key_map.insert(public.clone(), secret);

Ok((public, key_map, valid_networks))
}
}
Expand Down Expand Up @@ -891,9 +895,15 @@ pub enum KeyError {
Bip32(bitcoin::util::bip32::Error),
/// Miniscript error
Miniscript(miniscript::Error),
KeyParseError(miniscript::descriptor::DescriptorKeyParseError),
}

impl_error!(miniscript::Error, Miniscript, KeyError);
impl_error!(
miniscript::descriptor::DescriptorKeyParseError,
KeyParseError,
KeyError
);
impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);

impl std::fmt::Display for KeyError {
Expand Down
Loading