refactor(wallet)!: Make Wallet require a change descriptor

All `Wallet` constructors are modified to require a change
descriptor, where previously it was optional. Additionally
we enforce uniqueness of the change descriptor to avoid
ambiguity when deriving scripts and ensure the wallet will
always have two distinct keystores.

Notable changes

* Add error DescriptorError::ExternalAndInternalAreTheSame
* Remove error CreateTxError::ChangePolicyDescriptor
* No longer rely on `map_keychain`
This commit is contained in:
valued mammal 2024-03-26 21:57:10 -04:00
parent 8eef350bd0
commit 9d954cf7d2
No known key found for this signature in database
16 changed files with 393 additions and 341 deletions

View File

@ -20,7 +20,7 @@
//! //!
//! # let mut wallet = Wallet::new_no_persist( //! # let mut wallet = Wallet::new_no_persist(
//! # "", //! # "",
//! # None, //! # "",
//! # Network::Testnet, //! # Network::Testnet,
//! # )?; //! # )?;
//! # //! #

View File

@ -74,7 +74,8 @@ fn main() {
let db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store"); let db = bdk_file_store::Store::<ChangeSet>::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store");
let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)"; let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
let mut wallet = Wallet::new_or_load(descriptor, None, db, Network::Testnet).expect("create or load wallet"); let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
let mut wallet = Wallet::new_or_load(descriptor, change_descriptor, db, Network::Testnet).expect("create or load wallet");
// Insert a single `TxOut` at `OutPoint` into the wallet. // Insert a single `TxOut` at `OutPoint` into the wallet.
let _ = wallet.insert_txout(outpoint, txout); let _ = wallet.insert_txout(outpoint, txout);

View File

@ -32,21 +32,52 @@ use bdk_wallet::{KeychainKind, Wallet};
/// This example demonstrates the interaction between a bdk wallet and miniscript policy. /// This example demonstrates the interaction between a bdk wallet and miniscript policy.
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
// We start with a generic miniscript policy string // We start with a miniscript policy string
let policy_str = "or(10@thresh(4,pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)),1@and(older(4209713),thresh(2,pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068))))"; let policy_str = "or(
10@thresh(4,
pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec),pk(02a27c8b850a00f67da3499b60562673dcf5fdfb82b7e17652a7ac54416812aefd),pk(03e618ec5f384d6e19ca9ebdb8e2119e5bef978285076828ce054e55c4daf473e2)
),1@and(
older(4209713),
thresh(2,
pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),pk(033841045a531e1adf9910a6ec279589a90b3b8a904ee64ffd692bd08a8996c1aa),pk(02aebf2d10b040eb936a6f02f44ee82f8b34f5c1ccb20ff3949c2b28206b7c1068)
)
)
)"
.replace(&[' ', '\n', '\t'][..], "");
println!("Compiling policy: \n{}", policy_str); println!("Compiling policy: \n{}", policy_str);
// Parse the string as a [`Concrete`] type miniscript policy. // Parse the string as a [`Concrete`] type miniscript policy.
let policy = Concrete::<String>::from_str(policy_str)?; let policy = Concrete::<String>::from_str(&policy_str)?;
// Create a `wsh` type descriptor from the policy. // Create a `wsh` type descriptor from the policy.
// `policy.compile()` returns the resulting miniscript from the policy. // `policy.compile()` returns the resulting miniscript from the policy.
let descriptor = Descriptor::new_wsh(policy.compile()?)?; let descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string();
println!("Compiled into following Descriptor: \n{}", descriptor); println!("Compiled into Descriptor: \n{}", descriptor);
// Create a new wallet from this descriptor // Do the same for another (internal) keychain
let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?; let policy_str = "or(
10@thresh(2,
pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0),pk(025f05815e3a1a8a83bfbb03ce016c9a2ee31066b98f567f6227df1d76ec4bd143),pk(025625f41e4a065efc06d5019cbbd56fe8c07595af1231e7cbc03fafb87ebb71ec)
),1@and(
pk(03deae92101c790b12653231439f27b8897264125ecb2f46f48278603102573165),
older(12960)
)
)"
.replace(&[' ', '\n', '\t'][..], "");
println!("Compiling internal policy: \n{}", policy_str);
let policy = Concrete::<String>::from_str(&policy_str)?;
let internal_descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string();
println!(
"Compiled into internal Descriptor: \n{}",
internal_descriptor
);
// Create a new wallet from descriptors
let mut wallet = Wallet::new_no_persist(&descriptor, &internal_descriptor, Network::Regtest)?;
println!( println!(
"First derived address from the descriptor: \n{}", "First derived address from the descriptor: \n{}",

View File

@ -42,6 +42,8 @@ pub enum Error {
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
/// Hex decoding error /// Hex decoding error
Hex(bitcoin::hex::HexToBytesError), Hex(bitcoin::hex::HexToBytesError),
/// The provided wallet descriptors are identical
ExternalAndInternalAreTheSame,
} }
impl From<crate::keys::KeyError> for Error { impl From<crate::keys::KeyError> for Error {
@ -79,6 +81,9 @@ impl fmt::Display for Error {
Self::Pk(err) => write!(f, "Key-related error: {}", err), Self::Pk(err) => write!(f, "Key-related error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err), Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
Self::Hex(err) => write!(f, "Hex decoding error: {}", err), Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
Self::ExternalAndInternalAreTheSame => {
write!(f, "External and internal descriptors are the same")
}
} }
} }
} }

View File

@ -77,9 +77,12 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
/// # use bdk_wallet::KeychainKind; /// # use bdk_wallet::KeychainKind;
/// use bdk_wallet::template::P2Pkh; /// use bdk_wallet::template::P2Pkh;
/// ///
/// let key = /// let key_external =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?; /// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
/// let mut wallet =
/// Wallet::new_no_persist(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet /// wallet
@ -107,9 +110,15 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
/// # use bdk_wallet::KeychainKind; /// # use bdk_wallet::KeychainKind;
/// use bdk_wallet::template::P2Wpkh_P2Sh; /// use bdk_wallet::template::P2Wpkh_P2Sh;
/// ///
/// let key = /// let key_external =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?; /// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
/// let mut wallet = Wallet::new_no_persist(
/// P2Wpkh_P2Sh(key_external),
/// P2Wpkh_P2Sh(key_internal),
/// Network::Testnet,
/// )?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet /// wallet
@ -138,9 +147,12 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
/// # use bdk_wallet::KeychainKind; /// # use bdk_wallet::KeychainKind;
/// use bdk_wallet::template::P2Wpkh; /// use bdk_wallet::template::P2Wpkh;
/// ///
/// let key = /// let key_external =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?; /// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
/// let mut wallet =
/// Wallet::new_no_persist(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet /// wallet
@ -168,9 +180,12 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
/// # use bdk_wallet::KeychainKind; /// # use bdk_wallet::KeychainKind;
/// use bdk_wallet::template::P2TR; /// use bdk_wallet::template::P2TR;
/// ///
/// let key = /// let key_external =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?; /// let key_internal =
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
/// let mut wallet =
/// Wallet::new_no_persist(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
/// ///
/// assert_eq!( /// assert_eq!(
/// wallet /// wallet
@ -205,7 +220,7 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip44(key.clone(), KeychainKind::External), /// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)), /// Bip44(key, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -242,7 +257,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// Bip44Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -278,7 +293,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip49(key.clone(), KeychainKind::External), /// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)), /// Bip49(key, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -315,7 +330,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// Bip49Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -351,7 +366,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip84(key.clone(), KeychainKind::External), /// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)), /// Bip84(key, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -388,7 +403,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// Bip84Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -424,7 +439,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip86(key.clone(), KeychainKind::External), /// Bip86(key.clone(), KeychainKind::External),
/// Some(Bip86(key, KeychainKind::Internal)), /// Bip86(key, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///
@ -461,7 +476,7 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new_no_persist( /// let mut wallet = Wallet::new_no_persist(
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External), /// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)), /// Bip86Public(key, fingerprint, KeychainKind::Internal),
/// Network::Testnet, /// Network::Testnet,
/// )?; /// )?;
/// ///

View File

@ -90,8 +90,6 @@ pub enum CreateTxError {
NoUtxosSelected, NoUtxosSelected,
/// Output created is under the dust limit, 546 satoshis /// Output created is under the dust limit, 546 satoshis
OutputBelowDustLimit(usize), OutputBelowDustLimit(usize),
/// The `change_policy` was set but the wallet does not have a change_descriptor
ChangePolicyDescriptor,
/// There was an error with coin selection /// There was an error with coin selection
CoinSelection(coin_selection::Error), CoinSelection(coin_selection::Error),
/// Wallet's UTXO set is not enough to cover recipient's requested plus fee /// Wallet's UTXO set is not enough to cover recipient's requested plus fee
@ -177,12 +175,6 @@ impl fmt::Display for CreateTxError {
CreateTxError::OutputBelowDustLimit(limit) => { CreateTxError::OutputBelowDustLimit(limit) => {
write!(f, "Output below the dust limit: {}", limit) write!(f, "Output below the dust limit: {}", limit)
} }
CreateTxError::ChangePolicyDescriptor => {
write!(
f,
"The `change_policy` can be set only if the wallet has a change_descriptor"
)
}
CreateTxError::CoinSelection(e) => e.fmt(f), CreateTxError::CoinSelection(e) => e.fmt(f),
CreateTxError::InsufficientFunds { needed, available } => { CreateTxError::InsufficientFunds { needed, available } => {
write!( write!(

View File

@ -31,7 +31,7 @@
//! let import = FullyNodedExport::from_str(import)?; //! let import = FullyNodedExport::from_str(import)?;
//! let wallet = Wallet::new_no_persist( //! let wallet = Wallet::new_no_persist(
//! &import.descriptor(), //! &import.descriptor(),
//! import.change_descriptor().as_ref(), //! &import.change_descriptor().expect("change descriptor"),
//! Network::Testnet, //! Network::Testnet,
//! )?; //! )?;
//! # Ok::<_, Box<dyn std::error::Error>>(()) //! # Ok::<_, Box<dyn std::error::Error>>(())
@ -44,7 +44,7 @@
//! # use bdk_wallet::*; //! # use bdk_wallet::*;
//! let wallet = Wallet::new_no_persist( //! let wallet = Wallet::new_no_persist(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
//! Network::Testnet, //! Network::Testnet,
//! )?; //! )?;
//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap(); //! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
@ -142,9 +142,7 @@ impl FullyNodedExport {
blockheight, blockheight,
}; };
let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() { let change_descriptor = {
false => None,
true => {
let descriptor = wallet let descriptor = wallet
.get_descriptor_for_keychain(KeychainKind::Internal) .get_descriptor_for_keychain(KeychainKind::Internal)
.to_string_with_secret( .to_string_with_secret(
@ -153,8 +151,8 @@ impl FullyNodedExport {
.as_key_map(wallet.secp_ctx()), .as_key_map(wallet.secp_ctx()),
); );
Some(remove_checksum(descriptor)) Some(remove_checksum(descriptor))
}
}; };
if export.change_descriptor() != change_descriptor { if export.change_descriptor() != change_descriptor {
return Err("Incompatible change descriptor"); return Err("Incompatible change descriptor");
} }
@ -223,11 +221,7 @@ mod test {
use super::*; use super::*;
use crate::wallet::Wallet; use crate::wallet::Wallet;
fn get_test_wallet( fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
descriptor: &str,
change_descriptor: Option<&str>,
network: Network,
) -> Wallet {
let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap(); let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
let transaction = Transaction { let transaction = Transaction {
input: vec![], input: vec![],
@ -258,7 +252,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin); let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
assert_eq!(export.descriptor(), descriptor); assert_eq!(export.descriptor(), descriptor);
@ -270,13 +264,14 @@ mod test {
#[test] #[test]
#[should_panic(expected = "Incompatible change descriptor")] #[should_panic(expected = "Incompatible change descriptor")]
fn test_export_no_change() { fn test_export_no_change() {
// This wallet explicitly doesn't have a change descriptor. It should be impossible to // The wallet's change descriptor has no wildcard. It should be impossible to
// export, because exporting this kind of external descriptor normally implies the // export, because exporting this kind of external descriptor normally implies the
// existence of an internal descriptor // existence of a compatible internal descriptor
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/0)";
let wallet = get_test_wallet(descriptor, None, Network::Bitcoin); let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
} }
@ -289,7 +284,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin); let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
} }
@ -306,7 +301,7 @@ mod test {
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\ [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))"; ))";
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet); let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet);
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
assert_eq!(export.descriptor(), descriptor); assert_eq!(export.descriptor(), descriptor);
@ -319,7 +314,7 @@ mod test {
fn test_export_tr() { fn test_export_tr() {
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)"; let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet); let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet);
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
assert_eq!(export.descriptor(), descriptor); assert_eq!(export.descriptor(), descriptor);
assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
@ -332,7 +327,7 @@ mod test {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin); let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}"); assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");

View File

@ -166,7 +166,7 @@ impl Wallet {
/// Creates a wallet that does not persist data. /// Creates a wallet that does not persist data.
pub fn new_no_persist<E: IntoWalletDescriptor>( pub fn new_no_persist<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
network: Network, network: Network,
) -> Result<Self, DescriptorError> { ) -> Result<Self, DescriptorError> {
Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e { Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
@ -179,7 +179,7 @@ impl Wallet {
/// Creates a wallet that does not persist data, with a custom genesis hash. /// Creates a wallet that does not persist data, with a custom genesis hash.
pub fn new_no_persist_with_genesis_hash<E: IntoWalletDescriptor>( pub fn new_no_persist_with_genesis_hash<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
network: Network, network: Network,
genesis_hash: BlockHash, genesis_hash: BlockHash,
) -> Result<Self, crate::descriptor::DescriptorError> { ) -> Result<Self, crate::descriptor::DescriptorError> {
@ -398,7 +398,7 @@ impl Wallet {
/// Initialize an empty [`Wallet`]. /// Initialize an empty [`Wallet`].
pub fn new<E: IntoWalletDescriptor>( pub fn new<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
db: impl PersistBackend<ChangeSet> + Send + Sync + 'static, db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
) -> Result<Self, NewError> { ) -> Result<Self, NewError> {
@ -412,7 +412,7 @@ impl Wallet {
/// for syncing from alternative networks. /// for syncing from alternative networks.
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>( pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static, mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
genesis_hash: BlockHash, genesis_hash: BlockHash,
@ -524,7 +524,8 @@ impl Wallet {
.indexer .indexer
.keychains_added .keychains_added
.get(&KeychainKind::Internal) .get(&KeychainKind::Internal)
.cloned(); .ok_or(LoadError::MissingDescriptor)?
.clone();
let (signers, change_signers) = let (signers, change_signers) =
create_signers(&mut index, &secp, descriptor, change_descriptor, network) create_signers(&mut index, &secp, descriptor, change_descriptor, network)
@ -551,7 +552,7 @@ impl Wallet {
/// This method will fail if the loaded [`Wallet`] has different parameters to those provided. /// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
pub fn new_or_load<E: IntoWalletDescriptor>( pub fn new_or_load<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
db: impl PersistBackend<ChangeSet> + Send + Sync + 'static, db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
) -> Result<Self, NewOrLoadError> { ) -> Result<Self, NewOrLoadError> {
@ -573,7 +574,7 @@ impl Wallet {
/// useful for syncing from alternative networks. /// useful for syncing from alternative networks.
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>( pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static, mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
network: Network, network: Network,
genesis_hash: BlockHash, genesis_hash: BlockHash,
@ -639,26 +640,23 @@ impl Wallet {
}); });
} }
let expected_change_descriptor = if let Some(c) = change_descriptor { let (expected_change_descriptor, expected_change_descriptor_keymap) =
Some( change_descriptor
c.into_wallet_descriptor(&wallet.secp, network) .into_wallet_descriptor(&wallet.secp, network)
.map_err(NewOrLoadError::Descriptor)?, .map_err(NewOrLoadError::Descriptor)?;
)
} else {
None
};
let wallet_change_descriptor = let wallet_change_descriptor =
wallet.public_descriptor(KeychainKind::Internal).cloned(); wallet.public_descriptor(KeychainKind::Internal).cloned();
if wallet_change_descriptor != Some(expected_change_descriptor.clone()) {
match (expected_change_descriptor, wallet_change_descriptor) { return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
(Some((expected_descriptor, expected_keymap)), Some(wallet_descriptor)) got: wallet_change_descriptor,
if wallet_descriptor == expected_descriptor => keychain: KeychainKind::Internal,
{ });
}
// if expected change descriptor has private keys add them as new signers // if expected change descriptor has private keys add them as new signers
if !expected_keymap.is_empty() { if !expected_change_descriptor_keymap.is_empty() {
let signer_container = SignersContainer::build( let signer_container = SignersContainer::build(
expected_keymap, expected_change_descriptor_keymap,
&expected_descriptor, &expected_change_descriptor,
&wallet.secp, &wallet.secp,
); );
signer_container.signers().into_iter().for_each(|signer| { signer_container.signers().into_iter().for_each(|signer| {
@ -669,15 +667,6 @@ impl Wallet {
) )
}); });
} }
}
(None, None) => (),
(_, wallet_descriptor) => {
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
got: wallet_descriptor,
keychain: KeychainKind::Internal,
});
}
}
Ok(wallet) Ok(wallet)
} }
@ -717,12 +706,11 @@ impl Wallet {
/// This panics when the caller requests for an address of derivation index greater than the /// This panics when the caller requests for an address of derivation index greater than the
/// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index. /// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo { pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
let keychain = self.map_keychain(keychain);
let mut spk_iter = self let mut spk_iter = self
.indexed_graph .indexed_graph
.index .index
.unbounded_spk_iter(&keychain) .unbounded_spk_iter(&keychain)
.expect("Must exist (we called map_keychain)"); .expect("keychain must exist");
if !spk_iter.descriptor().has_wildcard() { if !spk_iter.descriptor().has_wildcard() {
index = 0; index = 0;
} }
@ -748,12 +736,11 @@ impl Wallet {
/// ///
/// If writing to persistent storage fails. /// If writing to persistent storage fails.
pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> { pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
let keychain = self.map_keychain(keychain);
let ((index, spk), index_changeset) = self let ((index, spk), index_changeset) = self
.indexed_graph .indexed_graph
.index .index
.reveal_next_spk(&keychain) .reveal_next_spk(&keychain)
.expect("Must exist (we called map_keychain)"); .expect("keychain must exist");
self.persist self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
@ -780,12 +767,11 @@ impl Wallet {
keychain: KeychainKind, keychain: KeychainKind,
index: u32, index: u32,
) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> { ) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
let keychain = self.map_keychain(keychain);
let (spk_iter, index_changeset) = self let (spk_iter, index_changeset) = self
.indexed_graph .indexed_graph
.index .index
.reveal_to_target(&keychain, index) .reveal_to_target(&keychain, index)
.expect("must exist (we called map_keychain)"); .expect("keychain must exist");
self.persist self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
@ -807,12 +793,11 @@ impl Wallet {
/// ///
/// If writing to persistent storage fails. /// If writing to persistent storage fails.
pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> { pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
let keychain = self.map_keychain(keychain);
let ((index, spk), index_changeset) = self let ((index, spk), index_changeset) = self
.indexed_graph .indexed_graph
.index .index
.next_unused_spk(&keychain) .next_unused_spk(&keychain)
.expect("must exist (we called map_keychain)"); .expect("keychain must exist");
self.persist self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
@ -852,7 +837,6 @@ impl Wallet {
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ { ) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
let keychain = self.map_keychain(keychain);
self.indexed_graph self.indexed_graph
.index .index
.unused_keychain_spks(&keychain) .unused_keychain_spks(&keychain)
@ -934,11 +918,10 @@ impl Wallet {
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone { ) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
let keychain = self.map_keychain(keychain);
self.indexed_graph self.indexed_graph
.index .index
.unbounded_spk_iter(&keychain) .unbounded_spk_iter(&keychain)
.expect("Must exist (we called map_keychain)") .expect("keychain must exist")
} }
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
@ -1248,7 +1231,9 @@ impl Wallet {
/// ``` /// ```
/// # use bdk_wallet::{Wallet, KeychainKind}; /// # use bdk_wallet::{Wallet, KeychainKind};
/// # use bdk_wallet::bitcoin::Network; /// # use bdk_wallet::bitcoin::Network;
/// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?; /// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
/// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
/// let wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) { /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/* /// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
/// println!("secret_key: {}", secret_key); /// println!("secret_key: {}", secret_key);
@ -1307,20 +1292,14 @@ impl Wallet {
) -> Result<Psbt, CreateTxError> { ) -> Result<Psbt, CreateTxError> {
let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect(); let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect();
let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist"); let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist");
let internal_descriptor = keychains.get(&KeychainKind::Internal); let internal_descriptor = keychains.get(&KeychainKind::Internal).expect("must exist");
let external_policy = external_descriptor let external_policy = external_descriptor
.extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)? .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
.unwrap(); .unwrap();
let internal_policy = internal_descriptor let internal_policy = internal_descriptor
.as_ref() .extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
.map(|desc| { .unwrap();
Ok::<_, CreateTxError>(
desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
.unwrap(),
)
})
.transpose()?;
// The policy allows spending external outputs, but it requires a policy path that hasn't been // The policy allows spending external outputs, but it requires a policy path that hasn't been
// provided // provided
@ -1332,8 +1311,7 @@ impl Wallet {
KeychainKind::External, KeychainKind::External,
)); ));
}; };
// Same for the internal_policy path, if present // Same for the internal_policy path
if let Some(internal_policy) = &internal_policy {
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
&& internal_policy.requires_path() && internal_policy.requires_path()
&& params.internal_policy_path.is_none() && params.internal_policy_path.is_none()
@ -1342,7 +1320,6 @@ impl Wallet {
KeychainKind::Internal, KeychainKind::Internal,
)); ));
}; };
}
let external_requirements = external_policy.get_condition( let external_requirements = external_policy.get_condition(
params params
@ -1350,21 +1327,14 @@ impl Wallet {
.as_ref() .as_ref()
.unwrap_or(&BTreeMap::new()), .unwrap_or(&BTreeMap::new()),
)?; )?;
let internal_requirements = internal_policy let internal_requirements = internal_policy.get_condition(
.map(|policy| {
Ok::<_, CreateTxError>(
policy.get_condition(
params params
.internal_policy_path .internal_policy_path
.as_ref() .as_ref()
.unwrap_or(&BTreeMap::new()), .unwrap_or(&BTreeMap::new()),
)?, )?;
)
})
.transpose()?;
let requirements = let requirements = external_requirements.merge(&internal_requirements)?;
external_requirements.merge(&internal_requirements.unwrap_or_default())?;
let version = match params.version { let version = match params.version {
Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0), Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0),
@ -1526,12 +1496,6 @@ impl Wallet {
fee_amount += (fee_rate * tx.weight()).to_sat(); fee_amount += (fee_rate * tx.weight()).to_sat();
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
&& internal_descriptor.is_none()
{
return Err(CreateTxError::ChangePolicyDescriptor);
}
let (required_utxos, optional_utxos) = let (required_utxos, optional_utxos) =
self.preselect_utxos(&params, Some(current_height.to_consensus_u32())); self.preselect_utxos(&params, Some(current_height.to_consensus_u32()));
@ -1539,12 +1503,12 @@ impl Wallet {
let drain_script = match params.drain_to { let drain_script = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(), Some(ref drain_recipient) => drain_recipient.clone(),
None => { None => {
let change_keychain = self.map_keychain(KeychainKind::Internal); let change_keychain = KeychainKind::Internal;
let ((index, spk), index_changeset) = self let ((index, spk), index_changeset) = self
.indexed_graph .indexed_graph
.index .index
.next_unused_spk(&change_keychain) .next_unused_spk(&change_keychain)
.expect("Keychain exists (we called map_keychain)"); .expect("keychain must exist");
let spk = spk.into(); let spk = spk.into();
self.indexed_graph.index.mark_used(change_keychain, index); self.indexed_graph.index.mark_used(change_keychain, index);
self.persist self.persist
@ -1773,9 +1737,11 @@ impl Wallet {
if tx.output.len() > 1 { if tx.output.len() > 1 {
let mut change_index = None; let mut change_index = None;
for (index, txout) in tx.output.iter().enumerate() { for (index, txout) in tx.output.iter().enumerate() {
let change_type = self.map_keychain(KeychainKind::Internal); let change_keychain = KeychainKind::Internal;
match txout_index.index_of_spk(&txout.script_pubkey) { match txout_index.index_of_spk(&txout.script_pubkey) {
Some((keychain, _)) if keychain == change_type => change_index = Some(index), Some((keychain, _)) if keychain == change_keychain => {
change_index = Some(index)
}
_ => {} _ => {}
} }
} }
@ -2014,8 +1980,7 @@ impl Wallet {
/// Returns the descriptor used to create addresses for a particular `keychain`. /// Returns the descriptor used to create addresses for a particular `keychain`.
pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor { pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
self.public_descriptor(self.map_keychain(keychain)) self.public_descriptor(keychain).expect("keychain exists")
.expect("we mapped it to external if it doesn't exist")
} }
/// The derivation index of this wallet. It will return `None` if it has not derived any addresses. /// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
@ -2026,11 +1991,10 @@ impl Wallet {
/// The index of the next address that you would get if you were to ask the wallet for a new address /// The index of the next address that you would get if you were to ask the wallet for a new address
pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 { pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
let keychain = self.map_keychain(keychain);
self.indexed_graph self.indexed_graph
.index .index
.next_index(&keychain) .next_index(&keychain)
.expect("Keychain must exist (we called map_keychain)") .expect("keychain must exist")
.0 .0
} }
@ -2049,16 +2013,6 @@ impl Wallet {
} }
} }
fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
if keychain == KeychainKind::Internal
&& self.public_descriptor(KeychainKind::Internal).is_none()
{
KeychainKind::External
} else {
keychain
}
}
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> { fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
let (keychain, child) = self let (keychain, child) = self
.indexed_graph .indexed_graph
@ -2569,22 +2523,22 @@ fn create_signers<E: IntoWalletDescriptor>(
index: &mut KeychainTxOutIndex<KeychainKind>, index: &mut KeychainTxOutIndex<KeychainKind>,
secp: &Secp256k1<All>, secp: &Secp256k1<All>,
descriptor: E, descriptor: E,
change_descriptor: Option<E>, change_descriptor: E,
network: Network, network: Network,
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), crate::descriptor::error::Error> { ) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?; let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
if descriptor.0 == change_descriptor.0 {
return Err(DescriptorError::ExternalAndInternalAreTheSame);
}
let (descriptor, keymap) = descriptor;
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp)); let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
let _ = index.insert_descriptor(KeychainKind::External, descriptor); let _ = index.insert_descriptor(KeychainKind::External, descriptor);
let change_signers = match change_descriptor { let (descriptor, keymap) = change_descriptor;
Some(descriptor) => { let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
let _ = index.insert_descriptor(KeychainKind::Internal, descriptor); let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
signers
}
None => Arc::new(SignersContainer::new()),
};
Ok((signers, change_signers)) Ok((signers, change_signers))
} }
@ -2613,7 +2567,7 @@ macro_rules! doctest_wallet {
let mut wallet = Wallet::new_no_persist( let mut wallet = Wallet::new_no_persist(
descriptor, descriptor,
Some(change_descriptor), change_descriptor,
Network::Regtest, Network::Regtest,
) )
.unwrap(); .unwrap();

View File

@ -67,8 +67,9 @@
//! //!
//! let custom_signer = CustomSigner::connect(); //! let custom_signer = CustomSigner::connect();
//! //!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; //! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?; //! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
//! let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
//! wallet.add_signer( //! wallet.add_signer(
//! KeychainKind::External, //! KeychainKind::External,
//! SignerOrdering(200), //! SignerOrdering(200),

View File

@ -15,12 +15,9 @@ use std::str::FromStr;
// The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000 // The funded wallet containing a tx with a 76_000 sats input and two outputs, one spending 25_000
// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
// sats are the transaction fee. // sats are the transaction fee.
pub fn get_funded_wallet_with_change( pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) {
descriptor: &str,
change: Option<&str>,
) -> (Wallet, bitcoin::Txid) {
let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap(); let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Regtest).unwrap();
let change_address = wallet.peek_address(KeychainKind::External, 0).address; let receive_address = wallet.peek_address(KeychainKind::External, 0).address;
let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
.expect("address") .expect("address")
.require_network(Network::Regtest) .require_network(Network::Regtest)
@ -40,7 +37,7 @@ pub fn get_funded_wallet_with_change(
}], }],
output: vec![TxOut { output: vec![TxOut {
value: Amount::from_sat(76_000), value: Amount::from_sat(76_000),
script_pubkey: change_address.script_pubkey(), script_pubkey: receive_address.script_pubkey(),
}], }],
}; };
@ -59,7 +56,7 @@ pub fn get_funded_wallet_with_change(
output: vec![ output: vec![
TxOut { TxOut {
value: Amount::from_sat(50_000), value: Amount::from_sat(50_000),
script_pubkey: change_address.script_pubkey(), script_pubkey: receive_address.script_pubkey(),
}, },
TxOut { TxOut {
value: Amount::from_sat(25_000), value: Amount::from_sat(25_000),
@ -108,13 +105,29 @@ pub fn get_funded_wallet_with_change(
// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
// sats are the transaction fee. // sats are the transaction fee.
pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) { pub fn get_funded_wallet(descriptor: &str) -> (Wallet, bitcoin::Txid) {
get_funded_wallet_with_change(descriptor, None) let change = get_test_wpkh_change();
get_funded_wallet_with_change(descriptor, change)
}
pub fn get_funded_wallet_wpkh() -> (Wallet, bitcoin::Txid) {
get_funded_wallet_with_change(get_test_wpkh(), get_test_wpkh_change())
} }
pub fn get_test_wpkh() -> &'static str { pub fn get_test_wpkh() -> &'static str {
"wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)" "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
} }
pub fn get_test_wpkh_with_change_desc() -> (&'static str, &'static str) {
(
"wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)",
get_test_wpkh_change(),
)
}
fn get_test_wpkh_change() -> &'static str {
"wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/0)"
}
pub fn get_test_single_sig_csv() -> &'static str { pub fn get_test_single_sig_csv() -> &'static str {
// and(pk(Alice),older(6)) // and(pk(Alice),older(6))
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))" "wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"
@ -150,6 +163,11 @@ pub fn get_test_tr_single_sig_xprv() -> &'static str {
"tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)" "tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
} }
pub fn get_test_tr_single_sig_xprv_with_change_desc() -> (&'static str, &'static str) {
("tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/0/*)",
"tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/1/*)")
}
pub fn get_test_tr_with_taptree_xprv() -> &'static str { pub fn get_test_tr_with_taptree_xprv() -> &'static str {
"tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})" "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
} }

View File

@ -142,7 +142,9 @@ fn test_psbt_fee_rate_with_missing_txout() {
assert!(wpkh_psbt.fee_amount().is_none()); assert!(wpkh_psbt.fee_amount().is_none());
assert!(wpkh_psbt.fee_rate().is_none()); assert!(wpkh_psbt.fee_rate().is_none());
let (mut pkh_wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)";
let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)";
let (mut pkh_wallet, _) = get_funded_wallet_with_change(desc, change_desc);
let addr = pkh_wallet.peek_address(KeychainKind::External, 0); let addr = pkh_wallet.peek_address(KeychainKind::External, 0);
let mut builder = pkh_wallet.build_tx(); let mut builder = pkh_wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.drain_to(addr.script_pubkey()).drain_wallet();
@ -170,7 +172,8 @@ fn test_psbt_multiple_internalkey_signers() {
let prv = PrivateKey::from_wif(wif).unwrap(); let prv = PrivateKey::from_wif(wif).unwrap();
let keypair = Keypair::from_secret_key(&secp, &prv.inner); let keypair = Keypair::from_secret_key(&secp, &prv.inner);
let (mut wallet, _) = get_funded_wallet(&desc); let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
let (mut wallet, _) = get_funded_wallet_with_change(&desc, change_desc);
let to_spend = wallet.balance().total(); let to_spend = wallet.balance().total();
let send_to = wallet.peek_address(KeychainKind::External, 0); let send_to = wallet.peek_address(KeychainKind::External, 0);
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();

View File

@ -7,7 +7,7 @@ use bdk_chain::COINBASE_MATURITY;
use bdk_chain::{BlockId, ConfirmationTime}; use bdk_chain::{BlockId, ConfirmationTime};
use bdk_persist::PersistBackend; use bdk_persist::PersistBackend;
use bdk_sqlite::rusqlite::Connection; use bdk_sqlite::rusqlite::Connection;
use bdk_wallet::descriptor::{calc_checksum, IntoWalletDescriptor}; use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
use bdk_wallet::psbt::PsbtUtils; use bdk_wallet::psbt::PsbtUtils;
use bdk_wallet::signer::{SignOptions, SignerError}; use bdk_wallet::signer::{SignOptions, SignerError};
use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection}; use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
@ -81,12 +81,13 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
{ {
let temp_dir = tempfile::tempdir().expect("must create tempdir"); let temp_dir = tempfile::tempdir().expect("must create tempdir");
let file_path = temp_dir.path().join(filename); let file_path = temp_dir.path().join(filename);
let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
// create new wallet // create new wallet
let wallet_spk_index = { let wallet_spk_index = {
let db = create_new(&file_path).expect("must create db"); let db = create_new(&file_path).expect("must create db");
let mut wallet = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet) let mut wallet =
.expect("must init wallet"); Wallet::new(desc, change_desc, db, Network::Testnet).expect("must init wallet");
wallet.reveal_next_address(KeychainKind::External).unwrap(); wallet.reveal_next_address(KeychainKind::External).unwrap();
wallet.spk_index().clone() wallet.spk_index().clone()
@ -108,8 +109,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
assert_eq!( assert_eq!(
*wallet.get_descriptor_for_keychain(KeychainKind::External), *wallet.get_descriptor_for_keychain(KeychainKind::External),
get_test_tr_single_sig_xprv() desc.into_wallet_descriptor(&secp, wallet.network())
.into_wallet_descriptor(&secp, wallet.network())
.unwrap() .unwrap()
.0 .0
); );
@ -118,7 +118,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
// `new` can only be called on empty db // `new` can only be called on empty db
{ {
let db = recover(&file_path).expect("must recover db"); let db = recover(&file_path).expect("must recover db");
let result = Wallet::new(get_test_tr_single_sig_xprv(), None, db, Network::Testnet); let result = Wallet::new(desc, change_desc, db, Network::Testnet);
assert!(matches!(result, Err(NewError::NonEmptyDatabase))); assert!(matches!(result, Err(NewError::NonEmptyDatabase)));
} }
@ -148,11 +148,12 @@ fn new_or_load() -> anyhow::Result<()> {
{ {
let temp_dir = tempfile::tempdir().expect("must create tempdir"); let temp_dir = tempfile::tempdir().expect("must create tempdir");
let file_path = temp_dir.path().join(filename); let file_path = temp_dir.path().join(filename);
let (desc, change_desc) = get_test_wpkh_with_change_desc();
// init wallet when non-existent // init wallet when non-existent
let wallet_keychains: BTreeMap<_, _> = { let wallet_keychains: BTreeMap<_, _> = {
let db = new_or_load(&file_path).expect("must create db"); let db = new_or_load(&file_path).expect("must create db");
let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet) let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet)
.expect("must init wallet"); .expect("must init wallet");
wallet.keychains().map(|(k, v)| (*k, v.clone())).collect() wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
}; };
@ -160,7 +161,7 @@ fn new_or_load() -> anyhow::Result<()> {
// wrong network // wrong network
{ {
let db = new_or_load(&file_path).expect("must create db"); let db = new_or_load(&file_path).expect("must create db");
let err = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Bitcoin) let err = Wallet::new_or_load(desc, change_desc, db, Network::Bitcoin)
.expect_err("wrong network"); .expect_err("wrong network");
assert!( assert!(
matches!( matches!(
@ -183,8 +184,8 @@ fn new_or_load() -> anyhow::Result<()> {
let db = new_or_load(&file_path).expect("must open db"); let db = new_or_load(&file_path).expect("must open db");
let err = Wallet::new_or_load_with_genesis_hash( let err = Wallet::new_or_load_with_genesis_hash(
get_test_wpkh(), desc,
None, change_desc,
db, db,
Network::Testnet, Network::Testnet,
exp_blockhash, exp_blockhash,
@ -204,13 +205,13 @@ fn new_or_load() -> anyhow::Result<()> {
// wrong external descriptor // wrong external descriptor
{ {
let exp_descriptor = get_test_tr_single_sig(); let exp_descriptor = get_test_tr_single_sig();
let got_descriptor = get_test_wpkh() let got_descriptor = desc
.into_wallet_descriptor(&Secp256k1::new(), Network::Testnet) .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
.unwrap() .unwrap()
.0; .0;
let db = new_or_load(&file_path).expect("must open db"); let db = new_or_load(&file_path).expect("must open db");
let err = Wallet::new_or_load(exp_descriptor, None, db, Network::Testnet) let err = Wallet::new_or_load(exp_descriptor, change_desc, db, Network::Testnet)
.expect_err("wrong external descriptor"); .expect_err("wrong external descriptor");
assert!( assert!(
matches!( matches!(
@ -225,17 +226,20 @@ fn new_or_load() -> anyhow::Result<()> {
// wrong internal descriptor // wrong internal descriptor
{ {
let exp_descriptor = Some(get_test_tr_single_sig()); let exp_descriptor = get_test_tr_single_sig();
let got_descriptor = None; let got_descriptor = change_desc
.into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
.unwrap()
.0;
let db = new_or_load(&file_path).expect("must open db"); let db = new_or_load(&file_path).expect("must open db");
let err = Wallet::new_or_load(get_test_wpkh(), exp_descriptor, db, Network::Testnet) let err = Wallet::new_or_load(desc, exp_descriptor, db, Network::Testnet)
.expect_err("wrong internal descriptor"); .expect_err("wrong internal descriptor");
assert!( assert!(
matches!( matches!(
err, err,
bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain } bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
if got == &got_descriptor && keychain == KeychainKind::Internal if got == &Some(got_descriptor) && keychain == KeychainKind::Internal
), ),
"err: {}", "err: {}",
err, err,
@ -245,7 +249,7 @@ fn new_or_load() -> anyhow::Result<()> {
// all parameters match // all parameters match
{ {
let db = new_or_load(&file_path).expect("must open db"); let db = new_or_load(&file_path).expect("must open db");
let wallet = Wallet::new_or_load(get_test_wpkh(), None, db, Network::Testnet) let wallet = Wallet::new_or_load(desc, change_desc, db, Network::Testnet)
.expect("must recover wallet"); .expect("must recover wallet");
assert_eq!(wallet.network(), Network::Testnet); assert_eq!(wallet.network(), Network::Testnet);
assert!(wallet assert!(wallet
@ -266,9 +270,31 @@ fn new_or_load() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_error_external_and_internal_are_the_same() {
// identical descriptors should fail to create wallet
let desc = get_test_wpkh();
let err = Wallet::new_no_persist(desc, desc, Network::Testnet);
assert!(
matches!(&err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
"expected same descriptors error, got {:?}",
err,
);
// public + private of same descriptor should fail to create wallet
let desc = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
let change_desc = "wpkh([3c31d632/84'/1'/0']tpubDCYwFkks2cg78N7eoYbBatsFEGje8vW8arSKW4rLwD1AU1s9KJMDRHE32JkvYERuiFjArrsH7qpWSpJATed5ShZbG9KsskA5Rmi6NSYgYN2/0/*)";
let err = Wallet::new_no_persist(desc, change_desc, Network::Testnet);
assert!(
matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
"expected same descriptors error, got {:?}",
err,
);
}
#[test] #[test]
fn test_descriptor_checksum() { fn test_descriptor_checksum() {
let (wallet, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _) = get_funded_wallet_wpkh();
let checksum = wallet.descriptor_checksum(KeychainKind::External); let checksum = wallet.descriptor_checksum(KeychainKind::External);
assert_eq!(checksum.len(), 8); assert_eq!(checksum.len(), 8);
@ -287,7 +313,7 @@ fn test_descriptor_checksum() {
#[test] #[test]
fn test_get_funded_wallet_balance() { fn test_get_funded_wallet_balance() {
let (wallet, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _) = get_funded_wallet_wpkh();
// The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000 // The funded wallet contains a tx with a 76_000 sats input and two outputs, one spending 25_000
// to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000
@ -297,7 +323,7 @@ fn test_get_funded_wallet_balance() {
#[test] #[test]
fn test_get_funded_wallet_sent_and_received() { fn test_get_funded_wallet_sent_and_received() {
let (wallet, txid) = get_funded_wallet(get_test_wpkh()); let (wallet, txid) = get_funded_wallet_wpkh();
let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet
.transactions() .transactions()
@ -317,7 +343,7 @@ fn test_get_funded_wallet_sent_and_received() {
#[test] #[test]
fn test_get_funded_wallet_tx_fees() { fn test_get_funded_wallet_tx_fees() {
let (wallet, txid) = get_funded_wallet(get_test_wpkh()); let (wallet, txid) = get_funded_wallet_wpkh();
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee"); let tx_fee = wallet.calculate_fee(&tx).expect("transaction fee");
@ -330,7 +356,7 @@ fn test_get_funded_wallet_tx_fees() {
#[test] #[test]
fn test_get_funded_wallet_tx_fee_rate() { fn test_get_funded_wallet_tx_fee_rate() {
let (wallet, txid) = get_funded_wallet(get_test_wpkh()); let (wallet, txid) = get_funded_wallet_wpkh();
let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx;
let tx_fee_rate = wallet let tx_fee_rate = wallet
@ -350,7 +376,7 @@ fn test_get_funded_wallet_tx_fee_rate() {
#[test] #[test]
fn test_list_output() { fn test_list_output() {
let (wallet, txid) = get_funded_wallet(get_test_wpkh()); let (wallet, txid) = get_funded_wallet_wpkh();
let txos = wallet let txos = wallet
.list_output() .list_output()
.map(|op| (op.outpoint, op)) .map(|op| (op.outpoint, op))
@ -428,14 +454,14 @@ macro_rules! from_str {
#[test] #[test]
#[should_panic(expected = "NoRecipients")] #[should_panic(expected = "NoRecipients")]
fn test_create_tx_empty_recipients() { fn test_create_tx_empty_recipients() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
wallet.build_tx().finish().unwrap(); wallet.build_tx().finish().unwrap();
} }
#[test] #[test]
#[should_panic(expected = "NoUtxosSelected")] #[should_panic(expected = "NoUtxosSelected")]
fn test_create_tx_manually_selected_empty_utxos() { fn test_create_tx_manually_selected_empty_utxos() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -446,7 +472,7 @@ fn test_create_tx_manually_selected_empty_utxos() {
#[test] #[test]
fn test_create_tx_version_0() { fn test_create_tx_version_0() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -468,7 +494,7 @@ fn test_create_tx_version_1_csv() {
#[test] #[test]
fn test_create_tx_custom_version() { fn test_create_tx_custom_version() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -481,7 +507,7 @@ fn test_create_tx_custom_version() {
#[test] #[test]
fn test_create_tx_default_locktime_is_last_sync_height() { fn test_create_tx_default_locktime_is_last_sync_height() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -495,7 +521,7 @@ fn test_create_tx_default_locktime_is_last_sync_height() {
#[test] #[test]
fn test_create_tx_fee_sniping_locktime_last_sync() { fn test_create_tx_fee_sniping_locktime_last_sync() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@ -522,7 +548,7 @@ fn test_create_tx_default_locktime_cltv() {
#[test] #[test]
fn test_create_tx_custom_locktime() { fn test_create_tx_custom_locktime() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -614,7 +640,7 @@ fn test_create_tx_no_rbf_cltv() {
#[test] #[test]
fn test_create_tx_invalid_rbf_sequence() { fn test_create_tx_invalid_rbf_sequence() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -625,7 +651,7 @@ fn test_create_tx_invalid_rbf_sequence() {
#[test] #[test]
fn test_create_tx_custom_rbf_sequence() { fn test_create_tx_custom_rbf_sequence() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -636,9 +662,33 @@ fn test_create_tx_custom_rbf_sequence() {
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF)); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xDEADBEEF));
} }
#[test]
fn test_create_tx_change_policy() {
let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.do_not_spend_change();
assert!(builder.finish().is_ok());
// wallet has no change, so setting `only_spend_change`
// should cause tx building to fail
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.only_spend_change();
assert!(matches!(
builder.finish(),
Err(CreateTxError::CoinSelection(
coin_selection::Error::InsufficientFunds { .. }
)),
));
}
#[test] #[test]
fn test_create_tx_default_sequence() { fn test_create_tx_default_sequence() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@ -647,20 +697,6 @@ fn test_create_tx_default_sequence() {
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE)); assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
} }
#[test]
fn test_create_tx_change_policy_no_internal() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh());
let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000))
.do_not_spend_change();
assert!(matches!(
builder.finish(),
Err(CreateTxError::ChangePolicyDescriptor)
));
}
macro_rules! check_fee { macro_rules! check_fee {
($wallet:expr, $psbt: expr) => {{ ($wallet:expr, $psbt: expr) => {{
let tx = $psbt.clone().extract_tx().expect("failed to extract tx"); let tx = $psbt.clone().extract_tx().expect("failed to extract tx");
@ -672,7 +708,7 @@ macro_rules! check_fee {
#[test] #[test]
fn test_create_tx_drain_wallet_and_drain_to() { fn test_create_tx_drain_wallet_and_drain_to() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.drain_to(addr.script_pubkey()).drain_wallet(); builder.drain_to(addr.script_pubkey()).drain_wallet();
@ -688,7 +724,7 @@ fn test_create_tx_drain_wallet_and_drain_to() {
#[test] #[test]
fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -720,7 +756,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
#[test] #[test]
fn test_create_tx_drain_to_and_utxos() { fn test_create_tx_drain_to_and_utxos() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect(); let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -741,7 +777,7 @@ fn test_create_tx_drain_to_and_utxos() {
#[test] #[test]
#[should_panic(expected = "NoRecipients")] #[should_panic(expected = "NoRecipients")]
fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let drain_addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.drain_to(drain_addr.script_pubkey()); builder.drain_to(drain_addr.script_pubkey());
@ -750,7 +786,7 @@ fn test_create_tx_drain_to_no_drain_wallet_no_utxos() {
#[test] #[test]
fn test_create_tx_default_fee_rate() { fn test_create_tx_default_fee_rate() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@ -762,7 +798,7 @@ fn test_create_tx_default_fee_rate() {
#[test] #[test]
fn test_create_tx_custom_fee_rate() { fn test_create_tx_custom_fee_rate() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -776,7 +812,7 @@ fn test_create_tx_custom_fee_rate() {
#[test] #[test]
fn test_create_tx_absolute_fee() { fn test_create_tx_absolute_fee() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -796,7 +832,7 @@ fn test_create_tx_absolute_fee() {
#[test] #[test]
fn test_create_tx_absolute_zero_fee() { fn test_create_tx_absolute_zero_fee() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -817,7 +853,7 @@ fn test_create_tx_absolute_zero_fee() {
#[test] #[test]
#[should_panic(expected = "InsufficientFunds")] #[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_absolute_high_fee() { fn test_create_tx_absolute_high_fee() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -831,7 +867,7 @@ fn test_create_tx_absolute_high_fee() {
fn test_create_tx_add_change() { fn test_create_tx_add_change() {
use bdk_wallet::wallet::tx_builder::TxOrdering; use bdk_wallet::wallet::tx_builder::TxOrdering;
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -850,7 +886,7 @@ fn test_create_tx_add_change() {
#[test] #[test]
fn test_create_tx_skip_change_dust() { fn test_create_tx_skip_change_dust() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800));
@ -865,7 +901,7 @@ fn test_create_tx_skip_change_dust() {
#[test] #[test]
#[should_panic(expected = "InsufficientFunds")] #[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_drain_to_dust_amount() { fn test_create_tx_drain_to_dust_amount() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
// very high fee rate, so that the only output would be below dust // very high fee rate, so that the only output would be below dust
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
@ -878,7 +914,7 @@ fn test_create_tx_drain_to_dust_amount() {
#[test] #[test]
fn test_create_tx_ordering_respected() { fn test_create_tx_ordering_respected() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -899,7 +935,7 @@ fn test_create_tx_ordering_respected() {
#[test] #[test]
fn test_create_tx_default_sighash() { fn test_create_tx_default_sighash() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000));
@ -910,7 +946,7 @@ fn test_create_tx_default_sighash() {
#[test] #[test]
fn test_create_tx_custom_sighash() { fn test_create_tx_custom_sighash() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -1088,7 +1124,7 @@ fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
#[test] #[test]
fn test_create_tx_add_utxo() { fn test_create_tx_add_utxo() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let small_output_tx = Transaction { let small_output_tx = Transaction {
input: vec![], input: vec![],
output: vec![TxOut { output: vec![TxOut {
@ -1138,7 +1174,7 @@ fn test_create_tx_add_utxo() {
#[test] #[test]
#[should_panic(expected = "InsufficientFunds")] #[should_panic(expected = "InsufficientFunds")]
fn test_create_tx_manually_selected_insufficient() { fn test_create_tx_manually_selected_insufficient() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let small_output_tx = Transaction { let small_output_tx = Transaction {
input: vec![], input: vec![],
output: vec![TxOut { output: vec![TxOut {
@ -1189,8 +1225,8 @@ fn test_create_tx_policy_path_required() {
#[test] #[test]
fn test_create_tx_policy_path_no_csv() { fn test_create_tx_policy_path_no_csv() {
let descriptors = get_test_wpkh(); let (desc, change_desc) = get_test_wpkh_with_change_desc();
let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap(); let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap();
let tx = Transaction { let tx = Transaction {
version: transaction::Version::non_standard(0), version: transaction::Version::non_standard(0),
@ -1284,13 +1320,13 @@ fn test_create_tx_global_xpubs_with_origin() {
let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap(); let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap(); let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
assert_eq!(psbt.xpub.len(), 1); assert_eq!(psbt.xpub.len(), 2);
assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path))); assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path)));
} }
#[test] #[test]
fn test_add_foreign_utxo() { fn test_add_foreign_utxo() {
let (mut wallet1, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet1, _) = get_funded_wallet_wpkh();
let (wallet2, _) = let (wallet2, _) =
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
@ -1366,7 +1402,7 @@ fn test_add_foreign_utxo() {
expected = "MissingTxOut([OutPoint { txid: 0x21d7fb1bceda00ab4069fc52d06baa13470803e9050edd16f5736e5d8c4925fd, vout: 0 }])" expected = "MissingTxOut([OutPoint { txid: 0x21d7fb1bceda00ab4069fc52d06baa13470803e9050edd16f5736e5d8c4925fd, vout: 0 }])"
)] )]
fn test_calculate_fee_with_missing_foreign_utxo() { fn test_calculate_fee_with_missing_foreign_utxo() {
let (mut wallet1, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet1, _) = get_funded_wallet_wpkh();
let (wallet2, _) = let (wallet2, _) =
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
@ -1397,7 +1433,7 @@ fn test_calculate_fee_with_missing_foreign_utxo() {
#[test] #[test]
fn test_add_foreign_utxo_invalid_psbt_input() { fn test_add_foreign_utxo_invalid_psbt_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let outpoint = wallet.list_unspent().next().expect("must exist").outpoint; let outpoint = wallet.list_unspent().next().expect("must exist").outpoint;
let foreign_utxo_satisfaction = wallet let foreign_utxo_satisfaction = wallet
.get_descriptor_for_keychain(KeychainKind::External) .get_descriptor_for_keychain(KeychainKind::External)
@ -1412,7 +1448,7 @@ fn test_add_foreign_utxo_invalid_psbt_input() {
#[test] #[test]
fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
let (mut wallet1, txid1) = get_funded_wallet(get_test_wpkh()); let (mut wallet1, txid1) = get_funded_wallet_wpkh();
let (wallet2, txid2) = let (wallet2, txid2) =
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
@ -1456,7 +1492,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() {
#[test] #[test]
fn test_add_foreign_utxo_only_witness_utxo() { fn test_add_foreign_utxo_only_witness_utxo() {
let (mut wallet1, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet1, _) = get_funded_wallet_wpkh();
let (wallet2, txid2) = let (wallet2, txid2) =
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@ -1523,7 +1559,7 @@ fn test_add_foreign_utxo_only_witness_utxo() {
#[test] #[test]
fn test_get_psbt_input() { fn test_get_psbt_input() {
// this should grab a known good utxo and set the input // this should grab a known good utxo and set the input
let (wallet, _) = get_funded_wallet(get_test_wpkh()); let (wallet, _) = get_funded_wallet_wpkh();
for utxo in wallet.list_unspent() { for utxo in wallet.list_unspent() {
let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap(); let psbt_input = wallet.get_psbt_input(utxo, None, false).unwrap();
assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some()); assert!(psbt_input.witness_utxo.is_some() || psbt_input.non_witness_utxo.is_some());
@ -1560,7 +1596,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap(); let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap(); let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
assert_eq!(psbt.xpub.len(), 1); assert_eq!(psbt.xpub.len(), 2);
assert_eq!( assert_eq!(
psbt.xpub.get(&key), psbt.xpub.get(&key),
Some(&(fingerprint, bip32::DerivationPath::default())) Some(&(fingerprint, bip32::DerivationPath::default()))
@ -1570,7 +1606,7 @@ fn test_create_tx_global_xpubs_master_without_origin() {
#[test] #[test]
#[should_panic(expected = "IrreplaceableTransaction")] #[should_panic(expected = "IrreplaceableTransaction")]
fn test_bump_fee_irreplaceable_tx() { fn test_bump_fee_irreplaceable_tx() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@ -1587,7 +1623,7 @@ fn test_bump_fee_irreplaceable_tx() {
#[test] #[test]
#[should_panic(expected = "TransactionConfirmed")] #[should_panic(expected = "TransactionConfirmed")]
fn test_bump_fee_confirmed_tx() { fn test_bump_fee_confirmed_tx() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000));
@ -1611,7 +1647,7 @@ fn test_bump_fee_confirmed_tx() {
#[test] #[test]
fn test_bump_fee_low_fee_rate() { fn test_bump_fee_low_fee_rate() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -1645,7 +1681,7 @@ fn test_bump_fee_low_fee_rate() {
#[test] #[test]
#[should_panic(expected = "FeeTooLow")] #[should_panic(expected = "FeeTooLow")]
fn test_bump_fee_low_abs() { fn test_bump_fee_low_abs() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -1668,7 +1704,7 @@ fn test_bump_fee_low_abs() {
#[test] #[test]
#[should_panic(expected = "FeeTooLow")] #[should_panic(expected = "FeeTooLow")]
fn test_bump_fee_zero_abs() { fn test_bump_fee_zero_abs() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = wallet.next_unused_address(KeychainKind::External).unwrap(); let addr = wallet.next_unused_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
@ -1689,7 +1725,7 @@ fn test_bump_fee_zero_abs() {
#[test] #[test]
fn test_bump_fee_reduce_change() { fn test_bump_fee_reduce_change() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -1788,7 +1824,7 @@ fn test_bump_fee_reduce_change() {
#[test] #[test]
fn test_bump_fee_reduce_single_recipient() { fn test_bump_fee_reduce_single_recipient() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -1836,7 +1872,7 @@ fn test_bump_fee_reduce_single_recipient() {
#[test] #[test]
fn test_bump_fee_absolute_reduce_single_recipient() { fn test_bump_fee_absolute_reduce_single_recipient() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -1882,7 +1918,7 @@ fn test_bump_fee_absolute_reduce_single_recipient() {
#[test] #[test]
fn test_bump_fee_drain_wallet() { fn test_bump_fee_drain_wallet() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
// receive an extra tx so that our wallet has two utxos. // receive an extra tx so that our wallet has two utxos.
let tx = Transaction { let tx = Transaction {
version: transaction::Version::ONE, version: transaction::Version::ONE,
@ -1944,7 +1980,7 @@ fn test_bump_fee_drain_wallet() {
#[test] #[test]
#[should_panic(expected = "InsufficientFunds")] #[should_panic(expected = "InsufficientFunds")]
fn test_bump_fee_remove_output_manually_selected_only() { fn test_bump_fee_remove_output_manually_selected_only() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
// receive an extra tx so that our wallet has two utxos. then we manually pick only one of // receive an extra tx so that our wallet has two utxos. then we manually pick only one of
// them, and make sure that `bump_fee` doesn't try to add more. This fails because we've // them, and make sure that `bump_fee` doesn't try to add more. This fails because we've
// told the wallet it's not allowed to add more inputs AND it can't reduce the value of the // told the wallet it's not allowed to add more inputs AND it can't reduce the value of the
@ -2006,7 +2042,7 @@ fn test_bump_fee_remove_output_manually_selected_only() {
#[test] #[test]
fn test_bump_fee_add_input() { fn test_bump_fee_add_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let init_tx = Transaction { let init_tx = Transaction {
version: transaction::Version::ONE, version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO, lock_time: absolute::LockTime::ZERO,
@ -2083,7 +2119,7 @@ fn test_bump_fee_add_input() {
#[test] #[test]
fn test_bump_fee_absolute_add_input() { fn test_bump_fee_absolute_add_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
receive_output_in_latest_block(&mut wallet, 25_000); receive_output_in_latest_block(&mut wallet, 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
@ -2141,7 +2177,7 @@ fn test_bump_fee_absolute_add_input() {
#[test] #[test]
fn test_bump_fee_no_change_add_input_and_change() { fn test_bump_fee_no_change_add_input_and_change() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let op = receive_output_in_latest_block(&mut wallet, 25_000); let op = receive_output_in_latest_block(&mut wallet, 25_000);
// initially make a tx without change by using `drain_to` // initially make a tx without change by using `drain_to`
@ -2210,7 +2246,7 @@ fn test_bump_fee_no_change_add_input_and_change() {
#[test] #[test]
fn test_bump_fee_add_input_change_dust() { fn test_bump_fee_add_input_change_dust() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
receive_output_in_latest_block(&mut wallet, 25_000); receive_output_in_latest_block(&mut wallet, 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
@ -2286,7 +2322,7 @@ fn test_bump_fee_add_input_change_dust() {
#[test] #[test]
fn test_bump_fee_force_add_input() { fn test_bump_fee_force_add_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000); let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@ -2352,7 +2388,7 @@ fn test_bump_fee_force_add_input() {
#[test] #[test]
fn test_bump_fee_absolute_force_add_input() { fn test_bump_fee_absolute_force_add_input() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000); let incoming_op = receive_output_in_latest_block(&mut wallet, 25_000);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@ -2427,7 +2463,7 @@ fn test_bump_fee_unconfirmed_inputs_only() {
// So, we fail with "InsufficientFunds", as per RBF rule 2: // So, we fail with "InsufficientFunds", as per RBF rule 2:
// The replacement transaction may only include an unconfirmed input // The replacement transaction may only include an unconfirmed input
// if that input was included in one of the original transactions. // if that input was included in one of the original transactions.
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -2464,7 +2500,7 @@ fn test_bump_fee_unconfirmed_input() {
// (BIP125 rule 2 only apply to newly added unconfirmed input, you can // (BIP125 rule 2 only apply to newly added unconfirmed input, you can
// always fee bump with an unconfirmed input if it was included in the // always fee bump with an unconfirmed input if it was included in the
// original transaction) // original transaction)
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -2508,7 +2544,7 @@ fn test_fee_amount_negative_drain_val() {
// This caused a bug in master where we would calculate the wrong fee // This caused a bug in master where we would calculate the wrong fee
// for a transaction. // for a transaction.
// See https://github.com/bitcoindevkit/bdk/issues/660 // See https://github.com/bitcoindevkit/bdk/issues/660
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -2625,7 +2661,9 @@ fn test_sign_single_xprv_no_hd_keypaths() {
#[test] #[test]
fn test_include_output_redeem_witness_script() { fn test_include_output_redeem_witness_script() {
let (mut wallet, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))"); let desc = get_test_wpkh();
let change_desc = "sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))";
let (mut wallet, _) = get_funded_wallet_with_change(desc, change_desc);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -2644,7 +2682,7 @@ fn test_include_output_redeem_witness_script() {
#[test] #[test]
fn test_signing_only_one_of_multiple_inputs() { fn test_signing_only_one_of_multiple_inputs() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -2800,8 +2838,9 @@ fn test_sign_nonstandard_sighash() {
#[test] #[test]
fn test_unused_address() { fn test_unused_address() {
let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
None, Network::Testnet).unwrap(); let change_desc = get_test_wpkh();
let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap();
// `list_unused_addresses` should be empty if we haven't revealed any // `list_unused_addresses` should be empty if we haven't revealed any
assert!(wallet assert!(wallet
@ -2829,7 +2868,8 @@ fn test_unused_address() {
#[test] #[test]
fn test_next_unused_address() { fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet).unwrap(); let change = get_test_wpkh();
let mut wallet = Wallet::new_no_persist(descriptor, change, Network::Testnet).unwrap();
assert_eq!(wallet.derivation_index(KeychainKind::External), None); assert_eq!(wallet.derivation_index(KeychainKind::External), None);
assert_eq!( assert_eq!(
@ -2877,8 +2917,9 @@ fn test_next_unused_address() {
#[test] #[test]
fn test_peek_address_at_index() { fn test_peek_address_at_index() {
let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
None, Network::Testnet).unwrap(); let change_desc = get_test_wpkh();
let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap();
assert_eq!( assert_eq!(
wallet.peek_address(KeychainKind::External, 1).to_string(), wallet.peek_address(KeychainKind::External, 1).to_string(),
@ -2916,7 +2957,7 @@ fn test_peek_address_at_index() {
#[test] #[test]
fn test_peek_address_at_index_not_derivable() { fn test_peek_address_at_index_not_derivable() {
let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet).unwrap(); get_test_wpkh(), Network::Testnet).unwrap();
assert_eq!( assert_eq!(
wallet.peek_address(KeychainKind::External, 1).to_string(), wallet.peek_address(KeychainKind::External, 1).to_string(),
@ -2937,7 +2978,7 @@ fn test_peek_address_at_index_not_derivable() {
#[test] #[test]
fn test_returns_index_and_address() { fn test_returns_index_and_address() {
let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet).unwrap(); get_test_wpkh(), Network::Testnet).unwrap();
// new index 0 // new index 0
assert_eq!( assert_eq!(
@ -2990,7 +3031,7 @@ fn test_returns_index_and_address() {
#[test] #[test]
fn test_sending_to_bip350_bech32m_address() { fn test_sending_to_bip350_bech32m_address() {
let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet, _) = get_funded_wallet_wpkh();
let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c") let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
.unwrap() .unwrap()
.assume_checked(); .assume_checked();
@ -3005,7 +3046,7 @@ fn test_get_address() {
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let wallet = Wallet::new_no_persist( let wallet = Wallet::new_no_persist(
Bip84(key, KeychainKind::External), Bip84(key, KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)), Bip84(key, KeychainKind::Internal),
Network::Regtest, Network::Regtest,
) )
.unwrap(); .unwrap();
@ -3031,27 +3072,12 @@ fn test_get_address() {
keychain: KeychainKind::Internal, keychain: KeychainKind::Internal,
} }
); );
let wallet =
Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap();
assert_eq!(
wallet.peek_address(KeychainKind::Internal, 0),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
},
"when there's no internal descriptor it should just use external"
);
} }
#[test] #[test]
fn test_reveal_addresses() { fn test_reveal_addresses() {
let desc = get_test_tr_single_sig_xprv(); let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
let mut wallet = Wallet::new_no_persist(desc, None, Network::Signet).unwrap(); let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Signet).unwrap();
let keychain = KeychainKind::External; let keychain = KeychainKind::External;
let last_revealed_addr = wallet let last_revealed_addr = wallet
@ -3071,13 +3097,17 @@ fn test_reveal_addresses() {
} }
#[test] #[test]
fn test_get_address_no_reuse_single_descriptor() { fn test_get_address_no_reuse() {
use bdk_wallet::descriptor::template::Bip84; use bdk_wallet::descriptor::template::Bip84;
use std::collections::HashSet; use std::collections::HashSet;
let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let mut wallet = let mut wallet = Wallet::new_no_persist(
Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap(); Bip84(key, KeychainKind::External),
Bip84(key, KeychainKind::Internal),
Network::Regtest,
)
.unwrap();
let mut used_set = HashSet::new(); let mut used_set = HashSet::new();
@ -3124,11 +3154,12 @@ fn test_taproot_remove_tapfields_after_finalize_sign_option() {
#[test] #[test]
fn test_taproot_psbt_populate_tap_key_origins() { fn test_taproot_psbt_populate_tap_key_origins() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_single_sig_xprv()); let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
let (mut wallet, _) = get_funded_wallet_with_change(desc, change_desc);
let addr = wallet.reveal_next_address(KeychainKind::External).unwrap(); let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); builder.drain_to(addr.script_pubkey()).drain_wallet();
let psbt = builder.finish().unwrap(); let psbt = builder.finish().unwrap();
assert_eq!( assert_eq!(
@ -3138,8 +3169,8 @@ fn test_taproot_psbt_populate_tap_key_origins() {
.into_iter() .into_iter()
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![( vec![(
from_str!("b96d3a3dc76a4fc74e976511b23aecb78e0754c23c0ed7a6513e18cbbc7178e9"), from_str!("0841db1dbaf949dbbda893e01a18f2cca9179cf8ea2d8e667857690502b06483"),
(vec![], (from_str!("f6a5cb8b"), from_str!("m/0"))) (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/0")))
)], )],
"Wrong input tap_key_origins" "Wrong input tap_key_origins"
); );
@ -3150,8 +3181,8 @@ fn test_taproot_psbt_populate_tap_key_origins() {
.into_iter() .into_iter()
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![( vec![(
from_str!("e9b03068cf4a2621d4f81e68f6c4216e6bd260fe6edf6acc55c8d8ae5aeff0a8"), from_str!("9187c1e80002d19ddde9c5c7f5394e9a063cee8695867b58815af0562695ca21"),
(vec![], (from_str!("f6a5cb8b"), from_str!("m/1"))) (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/1")))
)], )],
"Wrong output tap_key_origins" "Wrong output tap_key_origins"
); );
@ -3159,7 +3190,8 @@ fn test_taproot_psbt_populate_tap_key_origins() {
#[test] #[test]
fn test_taproot_psbt_populate_tap_key_origins_repeated_key() { fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
let (mut wallet, _) = get_funded_wallet(get_test_tr_repeated_key()); let (mut wallet, _) =
get_funded_wallet_with_change(get_test_tr_repeated_key(), get_test_tr_single_sig());
let addr = wallet.reveal_next_address(KeychainKind::External).unwrap(); let addr = wallet.reveal_next_address(KeychainKind::External).unwrap();
let path = vec![("rn4nre9c".to_string(), vec![0])] let path = vec![("rn4nre9c".to_string(), vec![0])]
@ -3168,7 +3200,8 @@ fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) .drain_to(addr.script_pubkey())
.drain_wallet()
.policy_path(path, KeychainKind::External); .policy_path(path, KeychainKind::External);
let psbt = builder.finish().unwrap(); let psbt = builder.finish().unwrap();
@ -3332,7 +3365,7 @@ fn test_taproot_sign_using_non_witness_utxo() {
#[test] #[test]
fn test_taproot_foreign_utxo() { fn test_taproot_foreign_utxo() {
let (mut wallet1, _) = get_funded_wallet(get_test_wpkh()); let (mut wallet1, _) = get_funded_wallet_wpkh();
let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig()); let (wallet2, _) = get_funded_wallet(get_test_tr_single_sig());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
@ -3575,8 +3608,12 @@ fn test_taproot_sign_derive_index_from_psbt() {
let mut psbt = builder.finish().unwrap(); let mut psbt = builder.finish().unwrap();
// re-create the wallet with an empty db // re-create the wallet with an empty db
let wallet_empty = let wallet_empty = Wallet::new_no_persist(
Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap(); get_test_tr_single_sig_xprv(),
get_test_tr_single_sig(),
Network::Regtest,
)
.unwrap();
// signing with an empty db means that we will only look at the psbt to infer the // signing with an empty db means that we will only look at the psbt to infer the
// derivation index // derivation index
@ -3675,8 +3712,8 @@ fn test_taproot_sign_non_default_sighash() {
#[test] #[test]
fn test_spend_coinbase() { fn test_spend_coinbase() {
let descriptor = get_test_wpkh(); let (desc, change_desc) = get_test_wpkh_with_change_desc();
let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap(); let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap();
let confirmation_height = 5; let confirmation_height = 5;
wallet wallet
@ -3937,7 +3974,7 @@ fn test_tx_cancellation() {
} }
let (mut wallet, _) = let (mut wallet, _) =
get_funded_wallet_with_change(get_test_wpkh(), Some(get_test_tr_single_sig_xprv())); get_funded_wallet_with_change(get_test_wpkh(), get_test_tr_single_sig_xprv());
let psbt1 = new_tx!(wallet); let psbt1 = new_tx!(wallet);
let change_derivation_1 = psbt1 let change_derivation_1 = psbt1

View File

@ -23,7 +23,7 @@ fn main() -> Result<(), anyhow::Error> {
let mut wallet = Wallet::new_or_load( let mut wallet = Wallet::new_or_load(
external_descriptor, external_descriptor,
Some(internal_descriptor), internal_descriptor,
db, db,
Network::Testnet, Network::Testnet,
)?; )?;

View File

@ -22,7 +22,7 @@ async fn main() -> Result<(), anyhow::Error> {
let mut wallet = Wallet::new_or_load( let mut wallet = Wallet::new_or_load(
external_descriptor, external_descriptor,
Some(internal_descriptor), internal_descriptor,
db, db,
Network::Signet, Network::Signet,
)?; )?;

View File

@ -21,7 +21,7 @@ fn main() -> Result<(), anyhow::Error> {
let mut wallet = Wallet::new_or_load( let mut wallet = Wallet::new_or_load(
external_descriptor, external_descriptor,
Some(internal_descriptor), internal_descriptor,
db, db,
Network::Testnet, Network::Testnet,
)?; )?;

View File

@ -25,7 +25,7 @@ pub struct Args {
pub descriptor: String, pub descriptor: String,
/// Wallet change descriptor /// Wallet change descriptor
#[clap(env = "CHANGE_DESCRIPTOR")] #[clap(env = "CHANGE_DESCRIPTOR")]
pub change_descriptor: Option<String>, pub change_descriptor: String,
/// Earliest block height to start sync from /// Earliest block height to start sync from
#[clap(env = "START_HEIGHT", long, default_value = "481824")] #[clap(env = "START_HEIGHT", long, default_value = "481824")]
pub start_height: u32, pub start_height: u32,
@ -88,7 +88,7 @@ fn main() -> anyhow::Result<()> {
let start_load_wallet = Instant::now(); let start_load_wallet = Instant::now();
let mut wallet = Wallet::new_or_load( let mut wallet = Wallet::new_or_load(
&args.descriptor, &args.descriptor,
args.change_descriptor.as_ref(), &args.change_descriptor,
Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new( Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
DB_MAGIC.as_bytes(), DB_MAGIC.as_bytes(),
args.db_path, args.db_path,