diff --git a/crates/hwi/src/lib.rs b/crates/hwi/src/lib.rs index 129ceac2..762af8ff 100644 --- a/crates/hwi/src/lib.rs +++ b/crates/hwi/src/lib.rs @@ -20,7 +20,7 @@ //! //! # let mut wallet = Wallet::new_no_persist( //! # "", -//! # None, +//! # "", //! # Network::Testnet, //! # )?; //! # diff --git a/crates/wallet/README.md b/crates/wallet/README.md index 37afee38..73ff4b58 100644 --- a/crates/wallet/README.md +++ b/crates/wallet/README.md @@ -74,7 +74,8 @@ fn main() { let db = bdk_file_store::Store::::open_or_create_new(b"magic_bytes", "path/to/my_wallet.db").expect("create store"); 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. let _ = wallet.insert_txout(outpoint, txout); diff --git a/crates/wallet/examples/compiler.rs b/crates/wallet/examples/compiler.rs index 116df473..a11899f4 100644 --- a/crates/wallet/examples/compiler.rs +++ b/crates/wallet/examples/compiler.rs @@ -32,21 +32,52 @@ use bdk_wallet::{KeychainKind, Wallet}; /// This example demonstrates the interaction between a bdk wallet and miniscript policy. fn main() -> Result<(), Box> { - // We start with a generic 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))))"; + // 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) + ) + ) + )" + .replace(&[' ', '\n', '\t'][..], ""); + println!("Compiling policy: \n{}", policy_str); // Parse the string as a [`Concrete`] type miniscript policy. - let policy = Concrete::::from_str(policy_str)?; + let policy = Concrete::::from_str(&policy_str)?; // Create a `wsh` type descriptor 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 - let mut wallet = Wallet::new_no_persist(&format!("{}", descriptor), None, Network::Regtest)?; + // Do the same for another (internal) keychain + 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::::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!( "First derived address from the descriptor: \n{}", diff --git a/crates/wallet/src/descriptor/error.rs b/crates/wallet/src/descriptor/error.rs index b2809f21..4d15e360 100644 --- a/crates/wallet/src/descriptor/error.rs +++ b/crates/wallet/src/descriptor/error.rs @@ -42,6 +42,8 @@ pub enum Error { Miniscript(miniscript::Error), /// Hex decoding error Hex(bitcoin::hex::HexToBytesError), + /// The provided wallet descriptors are identical + ExternalAndInternalAreTheSame, } impl From for Error { @@ -79,6 +81,9 @@ impl fmt::Display for Error { Self::Pk(err) => write!(f, "Key-related error: {}", err), Self::Miniscript(err) => write!(f, "Miniscript error: {}", err), Self::Hex(err) => write!(f, "Hex decoding error: {}", err), + Self::ExternalAndInternalAreTheSame => { + write!(f, "External and internal descriptors are the same") + } } } } diff --git a/crates/wallet/src/descriptor/template.rs b/crates/wallet/src/descriptor/template.rs index 528be1f3..528f227f 100644 --- a/crates/wallet/src/descriptor/template.rs +++ b/crates/wallet/src/descriptor/template.rs @@ -77,9 +77,12 @@ impl IntoWalletDescriptor for T { /// # use bdk_wallet::KeychainKind; /// use bdk_wallet::template::P2Pkh; /// -/// let key = +/// let key_external = /// 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!( /// wallet @@ -107,9 +110,15 @@ impl> DescriptorTemplate for P2Pkh { /// # use bdk_wallet::KeychainKind; /// use bdk_wallet::template::P2Wpkh_P2Sh; /// -/// let key = +/// let key_external = /// 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!( /// wallet @@ -138,9 +147,12 @@ impl> DescriptorTemplate for P2Wpkh_P2Sh { /// # use bdk_wallet::KeychainKind; /// use bdk_wallet::template::P2Wpkh; /// -/// let key = +/// let key_external = /// 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!( /// wallet @@ -168,9 +180,12 @@ impl> DescriptorTemplate for P2Wpkh { /// # use bdk_wallet::KeychainKind; /// use bdk_wallet::template::P2TR; /// -/// let key = +/// let key_external = /// 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!( /// wallet @@ -205,7 +220,7 @@ impl> DescriptorTemplate for P2TR { /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip44(key.clone(), KeychainKind::External), -/// Some(Bip44(key, KeychainKind::Internal)), +/// Bip44(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -242,7 +257,7 @@ impl> DescriptorTemplate for Bip44 { /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), +/// Bip44Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -278,7 +293,7 @@ impl> DescriptorTemplate for Bip44Public { /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip49(key.clone(), KeychainKind::External), -/// Some(Bip49(key, KeychainKind::Internal)), +/// Bip49(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -315,7 +330,7 @@ impl> DescriptorTemplate for Bip49 { /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), +/// Bip49Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -351,7 +366,7 @@ impl> DescriptorTemplate for Bip49Public { /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip84(key.clone(), KeychainKind::External), -/// Some(Bip84(key, KeychainKind::Internal)), +/// Bip84(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -388,7 +403,7 @@ impl> DescriptorTemplate for Bip84 { /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), +/// Bip84Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -424,7 +439,7 @@ impl> DescriptorTemplate for Bip84Public { /// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip86(key.clone(), KeychainKind::External), -/// Some(Bip86(key, KeychainKind::Internal)), +/// Bip86(key, KeychainKind::Internal), /// Network::Testnet, /// )?; /// @@ -461,7 +476,7 @@ impl> DescriptorTemplate for Bip86 { /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip86Public(key.clone(), fingerprint, KeychainKind::External), -/// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)), +/// Bip86Public(key, fingerprint, KeychainKind::Internal), /// Network::Testnet, /// )?; /// diff --git a/crates/wallet/src/wallet/error.rs b/crates/wallet/src/wallet/error.rs index 56612c54..3504b7d2 100644 --- a/crates/wallet/src/wallet/error.rs +++ b/crates/wallet/src/wallet/error.rs @@ -90,8 +90,6 @@ pub enum CreateTxError { NoUtxosSelected, /// Output created is under the dust limit, 546 satoshis OutputBelowDustLimit(usize), - /// The `change_policy` was set but the wallet does not have a change_descriptor - ChangePolicyDescriptor, /// There was an error with coin selection CoinSelection(coin_selection::Error), /// 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) => { 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::InsufficientFunds { needed, available } => { write!( diff --git a/crates/wallet/src/wallet/export.rs b/crates/wallet/src/wallet/export.rs index 9eea7bd6..3697f91d 100644 --- a/crates/wallet/src/wallet/export.rs +++ b/crates/wallet/src/wallet/export.rs @@ -31,7 +31,7 @@ //! let import = FullyNodedExport::from_str(import)?; //! let wallet = Wallet::new_no_persist( //! &import.descriptor(), -//! import.change_descriptor().as_ref(), +//! &import.change_descriptor().expect("change descriptor"), //! Network::Testnet, //! )?; //! # Ok::<_, Box>(()) @@ -44,7 +44,7 @@ //! # use bdk_wallet::*; //! let wallet = Wallet::new_no_persist( //! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", -//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"), +//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)", //! Network::Testnet, //! )?; //! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap(); @@ -142,19 +142,17 @@ impl FullyNodedExport { blockheight, }; - let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() { - false => None, - true => { - let descriptor = wallet - .get_descriptor_for_keychain(KeychainKind::Internal) - .to_string_with_secret( - &wallet - .get_signers(KeychainKind::Internal) - .as_key_map(wallet.secp_ctx()), - ); - Some(remove_checksum(descriptor)) - } + let change_descriptor = { + let descriptor = wallet + .get_descriptor_for_keychain(KeychainKind::Internal) + .to_string_with_secret( + &wallet + .get_signers(KeychainKind::Internal) + .as_key_map(wallet.secp_ctx()), + ); + Some(remove_checksum(descriptor)) }; + if export.change_descriptor() != change_descriptor { return Err("Incompatible change descriptor"); } @@ -223,11 +221,7 @@ mod test { use super::*; use crate::wallet::Wallet; - fn get_test_wallet( - descriptor: &str, - change_descriptor: Option<&str>, - network: Network, - ) -> Wallet { + fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet { let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap(); let transaction = Transaction { input: vec![], @@ -258,7 +252,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; 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(); assert_eq!(export.descriptor(), descriptor); @@ -270,13 +264,14 @@ mod test { #[test] #[should_panic(expected = "Incompatible change descriptor")] 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 - // existence of an internal descriptor + // existence of a compatible internal descriptor 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(); } @@ -289,7 +284,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; 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(); } @@ -306,7 +301,7 @@ mod test { [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(); assert_eq!(export.descriptor(), descriptor); @@ -319,7 +314,7 @@ mod test { fn test_export_tr() { let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; 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(); assert_eq!(export.descriptor(), descriptor); assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); @@ -332,7 +327,7 @@ mod test { let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; 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(); assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}"); diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 73fcaa21..af5f8244 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -166,7 +166,7 @@ impl Wallet { /// Creates a wallet that does not persist data. pub fn new_no_persist( descriptor: E, - change_descriptor: Option, + change_descriptor: E, network: Network, ) -> Result { 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. pub fn new_no_persist_with_genesis_hash( descriptor: E, - change_descriptor: Option, + change_descriptor: E, network: Network, genesis_hash: BlockHash, ) -> Result { @@ -398,7 +398,7 @@ impl Wallet { /// Initialize an empty [`Wallet`]. pub fn new( descriptor: E, - change_descriptor: Option, + change_descriptor: E, db: impl PersistBackend + Send + Sync + 'static, network: Network, ) -> Result { @@ -412,7 +412,7 @@ impl Wallet { /// for syncing from alternative networks. pub fn new_with_genesis_hash( descriptor: E, - change_descriptor: Option, + change_descriptor: E, mut db: impl PersistBackend + Send + Sync + 'static, network: Network, genesis_hash: BlockHash, @@ -524,7 +524,8 @@ impl Wallet { .indexer .keychains_added .get(&KeychainKind::Internal) - .cloned(); + .ok_or(LoadError::MissingDescriptor)? + .clone(); let (signers, change_signers) = 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. pub fn new_or_load( descriptor: E, - change_descriptor: Option, + change_descriptor: E, db: impl PersistBackend + Send + Sync + 'static, network: Network, ) -> Result { @@ -573,7 +574,7 @@ impl Wallet { /// useful for syncing from alternative networks. pub fn new_or_load_with_genesis_hash( descriptor: E, - change_descriptor: Option, + change_descriptor: E, mut db: impl PersistBackend + Send + Sync + 'static, network: Network, genesis_hash: BlockHash, @@ -639,44 +640,32 @@ impl Wallet { }); } - let expected_change_descriptor = if let Some(c) = change_descriptor { - Some( - c.into_wallet_descriptor(&wallet.secp, network) - .map_err(NewOrLoadError::Descriptor)?, - ) - } else { - None - }; + let (expected_change_descriptor, expected_change_descriptor_keymap) = + change_descriptor + .into_wallet_descriptor(&wallet.secp, network) + .map_err(NewOrLoadError::Descriptor)?; let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal).cloned(); - - match (expected_change_descriptor, wallet_change_descriptor) { - (Some((expected_descriptor, expected_keymap)), Some(wallet_descriptor)) - if wallet_descriptor == expected_descriptor => - { - // if expected change descriptor has private keys add them as new signers - if !expected_keymap.is_empty() { - let signer_container = SignersContainer::build( - expected_keymap, - &expected_descriptor, - &wallet.secp, - ); - signer_container.signers().into_iter().for_each(|signer| { - wallet.add_signer( - KeychainKind::Internal, - SignerOrdering::default(), - signer.clone(), - ) - }); - } - } - (None, None) => (), - (_, wallet_descriptor) => { - return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch { - got: wallet_descriptor, - keychain: KeychainKind::Internal, - }); - } + if wallet_change_descriptor != Some(expected_change_descriptor.clone()) { + return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch { + got: wallet_change_descriptor, + keychain: KeychainKind::Internal, + }); + } + // if expected change descriptor has private keys add them as new signers + if !expected_change_descriptor_keymap.is_empty() { + let signer_container = SignersContainer::build( + expected_change_descriptor_keymap, + &expected_change_descriptor, + &wallet.secp, + ); + signer_container.signers().into_iter().for_each(|signer| { + wallet.add_signer( + KeychainKind::Internal, + SignerOrdering::default(), + signer.clone(), + ) + }); } Ok(wallet) @@ -717,12 +706,11 @@ impl Wallet { /// 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. pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo { - let keychain = self.map_keychain(keychain); let mut spk_iter = self .indexed_graph .index .unbounded_spk_iter(&keychain) - .expect("Must exist (we called map_keychain)"); + .expect("keychain must exist"); if !spk_iter.descriptor().has_wildcard() { index = 0; } @@ -748,12 +736,11 @@ impl Wallet { /// /// If writing to persistent storage fails. pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result { - let keychain = self.map_keychain(keychain); let ((index, spk), index_changeset) = self .indexed_graph .index .reveal_next_spk(&keychain) - .expect("Must exist (we called map_keychain)"); + .expect("keychain must exist"); self.persist .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; @@ -780,12 +767,11 @@ impl Wallet { keychain: KeychainKind, index: u32, ) -> anyhow::Result + '_> { - let keychain = self.map_keychain(keychain); let (spk_iter, index_changeset) = self .indexed_graph .index .reveal_to_target(&keychain, index) - .expect("must exist (we called map_keychain)"); + .expect("keychain must exist"); self.persist .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; @@ -807,12 +793,11 @@ impl Wallet { /// /// If writing to persistent storage fails. pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result { - let keychain = self.map_keychain(keychain); let ((index, spk), index_changeset) = self .indexed_graph .index .next_unused_spk(&keychain) - .expect("must exist (we called map_keychain)"); + .expect("keychain must exist"); self.persist .stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?; @@ -852,7 +837,6 @@ impl Wallet { &self, keychain: KeychainKind, ) -> impl DoubleEndedIterator + '_ { - let keychain = self.map_keychain(keychain); self.indexed_graph .index .unused_keychain_spks(&keychain) @@ -934,11 +918,10 @@ impl Wallet { &self, keychain: KeychainKind, ) -> impl Iterator + Clone { - let keychain = self.map_keychain(keychain); self.indexed_graph .index .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 @@ -1248,7 +1231,9 @@ impl Wallet { /// ``` /// # use bdk_wallet::{Wallet, KeychainKind}; /// # 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()) { /// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/* /// println!("secret_key: {}", secret_key); @@ -1307,20 +1292,14 @@ impl Wallet { ) -> Result { let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect(); 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 .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)? .unwrap(); let internal_policy = internal_descriptor - .as_ref() - .map(|desc| { - Ok::<_, CreateTxError>( - desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)? - .unwrap(), - ) - }) - .transpose()?; + .extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)? + .unwrap(); // The policy allows spending external outputs, but it requires a policy path that hasn't been // provided @@ -1332,17 +1311,15 @@ impl Wallet { KeychainKind::External, )); }; - // Same for the internal_policy path, if present - if let Some(internal_policy) = &internal_policy { - if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden - && internal_policy.requires_path() - && params.internal_policy_path.is_none() - { - return Err(CreateTxError::SpendingPolicyRequired( - KeychainKind::Internal, - )); - }; - } + // Same for the internal_policy path + if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden + && internal_policy.requires_path() + && params.internal_policy_path.is_none() + { + return Err(CreateTxError::SpendingPolicyRequired( + KeychainKind::Internal, + )); + }; let external_requirements = external_policy.get_condition( params @@ -1350,21 +1327,14 @@ impl Wallet { .as_ref() .unwrap_or(&BTreeMap::new()), )?; - let internal_requirements = internal_policy - .map(|policy| { - Ok::<_, CreateTxError>( - policy.get_condition( - params - .internal_policy_path - .as_ref() - .unwrap_or(&BTreeMap::new()), - )?, - ) - }) - .transpose()?; + let internal_requirements = internal_policy.get_condition( + params + .internal_policy_path + .as_ref() + .unwrap_or(&BTreeMap::new()), + )?; - let requirements = - external_requirements.merge(&internal_requirements.unwrap_or_default())?; + let requirements = external_requirements.merge(&internal_requirements)?; let version = match params.version { Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0), @@ -1526,12 +1496,6 @@ impl Wallet { 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) = self.preselect_utxos(¶ms, Some(current_height.to_consensus_u32())); @@ -1539,12 +1503,12 @@ impl Wallet { let drain_script = match params.drain_to { Some(ref drain_recipient) => drain_recipient.clone(), None => { - let change_keychain = self.map_keychain(KeychainKind::Internal); + let change_keychain = KeychainKind::Internal; let ((index, spk), index_changeset) = self .indexed_graph .index .next_unused_spk(&change_keychain) - .expect("Keychain exists (we called map_keychain)"); + .expect("keychain must exist"); let spk = spk.into(); self.indexed_graph.index.mark_used(change_keychain, index); self.persist @@ -1773,9 +1737,11 @@ impl Wallet { if tx.output.len() > 1 { let mut change_index = None; 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) { - 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`. pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor { - self.public_descriptor(self.map_keychain(keychain)) - .expect("we mapped it to external if it doesn't exist") + self.public_descriptor(keychain).expect("keychain exists") } /// 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 pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 { - let keychain = self.map_keychain(keychain); self.indexed_graph .index .next_index(&keychain) - .expect("Keychain must exist (we called map_keychain)") + .expect("keychain must exist") .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 { let (keychain, child) = self .indexed_graph @@ -2569,22 +2523,22 @@ fn create_signers( index: &mut KeychainTxOutIndex, secp: &Secp256k1, descriptor: E, - change_descriptor: Option, + change_descriptor: E, network: Network, -) -> Result<(Arc, Arc), crate::descriptor::error::Error> { - let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?; +) -> Result<(Arc, Arc), DescriptorError> { + 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 _ = index.insert_descriptor(KeychainKind::External, descriptor); - let change_signers = match change_descriptor { - Some(descriptor) => { - 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); - signers - } - None => Arc::new(SignersContainer::new()), - }; + let (descriptor, keymap) = change_descriptor; + let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp)); + let _ = index.insert_descriptor(KeychainKind::Internal, descriptor); Ok((signers, change_signers)) } @@ -2613,7 +2567,7 @@ macro_rules! doctest_wallet { let mut wallet = Wallet::new_no_persist( descriptor, - Some(change_descriptor), + change_descriptor, Network::Regtest, ) .unwrap(); diff --git a/crates/wallet/src/wallet/signer.rs b/crates/wallet/src/wallet/signer.rs index 51420caa..280e7066 100644 --- a/crates/wallet/src/wallet/signer.rs +++ b/crates/wallet/src/wallet/signer.rs @@ -67,8 +67,9 @@ //! //! let custom_signer = CustomSigner::connect(); //! -//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; -//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?; +//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)"; +//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)"; +//! let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?; //! wallet.add_signer( //! KeychainKind::External, //! SignerOrdering(200), diff --git a/crates/wallet/tests/common.rs b/crates/wallet/tests/common.rs index a51dcafb..34ffda41 100644 --- a/crates/wallet/tests/common.rs +++ b/crates/wallet/tests/common.rs @@ -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 // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // sats are the transaction fee. -pub fn get_funded_wallet_with_change( - descriptor: &str, - change: Option<&str>, -) -> (Wallet, bitcoin::Txid) { +pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) { 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") .expect("address") .require_network(Network::Regtest) @@ -40,7 +37,7 @@ pub fn get_funded_wallet_with_change( }], output: vec![TxOut { 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![ TxOut { value: Amount::from_sat(50_000), - script_pubkey: change_address.script_pubkey(), + script_pubkey: receive_address.script_pubkey(), }, TxOut { 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 // sats are the transaction fee. 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 { "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 { // and(pk(Alice),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/*)" } +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 { "tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})" } diff --git a/crates/wallet/tests/psbt.rs b/crates/wallet/tests/psbt.rs index a858e8a4..155bb143 100644 --- a/crates/wallet/tests/psbt.rs +++ b/crates/wallet/tests/psbt.rs @@ -142,7 +142,9 @@ fn test_psbt_fee_rate_with_missing_txout() { assert!(wpkh_psbt.fee_amount().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 mut builder = pkh_wallet.build_tx(); 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 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 send_to = wallet.peek_address(KeychainKind::External, 0); let mut builder = wallet.build_tx(); diff --git a/crates/wallet/tests/wallet.rs b/crates/wallet/tests/wallet.rs index 93baff79..dff699a6 100644 --- a/crates/wallet/tests/wallet.rs +++ b/crates/wallet/tests/wallet.rs @@ -7,7 +7,7 @@ use bdk_chain::COINBASE_MATURITY; use bdk_chain::{BlockId, ConfirmationTime}; use bdk_persist::PersistBackend; 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::signer::{SignOptions, SignerError}; 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 file_path = temp_dir.path().join(filename); + let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc(); // create new wallet let wallet_spk_index = { 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) - .expect("must init wallet"); + let mut wallet = + Wallet::new(desc, change_desc, db, Network::Testnet).expect("must init wallet"); wallet.reveal_next_address(KeychainKind::External).unwrap(); wallet.spk_index().clone() @@ -108,8 +109,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> { let secp = Secp256k1::new(); assert_eq!( *wallet.get_descriptor_for_keychain(KeychainKind::External), - get_test_tr_single_sig_xprv() - .into_wallet_descriptor(&secp, wallet.network()) + desc.into_wallet_descriptor(&secp, wallet.network()) .unwrap() .0 ); @@ -118,7 +118,7 @@ fn load_recovers_wallet() -> anyhow::Result<()> { // `new` can only be called on empty 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))); } @@ -148,11 +148,12 @@ fn new_or_load() -> anyhow::Result<()> { { let temp_dir = tempfile::tempdir().expect("must create tempdir"); let file_path = temp_dir.path().join(filename); + let (desc, change_desc) = get_test_wpkh_with_change_desc(); // init wallet when non-existent let wallet_keychains: BTreeMap<_, _> = { 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"); wallet.keychains().map(|(k, v)| (*k, v.clone())).collect() }; @@ -160,7 +161,7 @@ fn new_or_load() -> anyhow::Result<()> { // wrong network { 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"); assert!( matches!( @@ -183,8 +184,8 @@ fn new_or_load() -> anyhow::Result<()> { let db = new_or_load(&file_path).expect("must open db"); let err = Wallet::new_or_load_with_genesis_hash( - get_test_wpkh(), - None, + desc, + change_desc, db, Network::Testnet, exp_blockhash, @@ -204,13 +205,13 @@ fn new_or_load() -> anyhow::Result<()> { // wrong external descriptor { 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) .unwrap() .0; 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"); assert!( matches!( @@ -225,17 +226,20 @@ fn new_or_load() -> anyhow::Result<()> { // wrong internal descriptor { - let exp_descriptor = Some(get_test_tr_single_sig()); - let got_descriptor = None; + let exp_descriptor = get_test_tr_single_sig(); + 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 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"); assert!( matches!( err, 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, @@ -245,7 +249,7 @@ fn new_or_load() -> anyhow::Result<()> { // all parameters match { 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"); assert_eq!(wallet.network(), Network::Testnet); assert!(wallet @@ -266,9 +270,31 @@ fn new_or_load() -> anyhow::Result<()> { 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] 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); assert_eq!(checksum.len(), 8); @@ -287,7 +313,7 @@ fn test_descriptor_checksum() { #[test] 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 // 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] 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 .transactions() @@ -317,7 +343,7 @@ fn test_get_funded_wallet_sent_and_received() { #[test] 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_fee = wallet.calculate_fee(&tx).expect("transaction fee"); @@ -330,7 +356,7 @@ fn test_get_funded_wallet_tx_fees() { #[test] 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_fee_rate = wallet @@ -350,7 +376,7 @@ fn test_get_funded_wallet_tx_fee_rate() { #[test] fn test_list_output() { - let (wallet, txid) = get_funded_wallet(get_test_wpkh()); + let (wallet, txid) = get_funded_wallet_wpkh(); let txos = wallet .list_output() .map(|op| (op.outpoint, op)) @@ -428,14 +454,14 @@ macro_rules! from_str { #[test] #[should_panic(expected = "NoRecipients")] 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(); } #[test] #[should_panic(expected = "NoUtxosSelected")] 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 mut builder = wallet.build_tx(); builder @@ -446,7 +472,7 @@ fn test_create_tx_manually_selected_empty_utxos() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -468,7 +494,7 @@ fn test_create_tx_version_1_csv() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -481,7 +507,7 @@ fn test_create_tx_custom_version() { #[test] 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 mut builder = wallet.build_tx(); @@ -495,7 +521,7 @@ fn test_create_tx_default_locktime_is_last_sync_height() { #[test] 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 mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -522,7 +548,7 @@ fn test_create_tx_default_locktime_cltv() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -614,7 +640,7 @@ fn test_create_tx_no_rbf_cltv() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -625,7 +651,7 @@ fn test_create_tx_invalid_rbf_sequence() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -636,9 +662,33 @@ fn test_create_tx_custom_rbf_sequence() { 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] 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 mut builder = wallet.build_tx(); 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)); } -#[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 { ($wallet:expr, $psbt: expr) => {{ let tx = $psbt.clone().extract_tx().expect("failed to extract tx"); @@ -672,7 +708,7 @@ macro_rules! check_fee { #[test] 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 mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); @@ -688,7 +724,7 @@ fn test_create_tx_drain_wallet_and_drain_to() { #[test] 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") .unwrap() .assume_checked(); @@ -720,7 +756,7 @@ fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { #[test] 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 utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect(); let mut builder = wallet.build_tx(); @@ -741,7 +777,7 @@ fn test_create_tx_drain_to_and_utxos() { #[test] #[should_panic(expected = "NoRecipients")] 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 mut builder = wallet.build_tx(); builder.drain_to(drain_addr.script_pubkey()); @@ -750,7 +786,7 @@ fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { #[test] 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 mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -762,7 +798,7 @@ fn test_create_tx_default_fee_rate() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -776,7 +812,7 @@ fn test_create_tx_custom_fee_rate() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -796,7 +832,7 @@ fn test_create_tx_absolute_fee() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -817,7 +853,7 @@ fn test_create_tx_absolute_zero_fee() { #[test] #[should_panic(expected = "InsufficientFunds")] 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 mut builder = wallet.build_tx(); builder @@ -831,7 +867,7 @@ fn test_create_tx_absolute_high_fee() { fn test_create_tx_add_change() { 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 mut builder = wallet.build_tx(); builder @@ -850,7 +886,7 @@ fn test_create_tx_add_change() { #[test] 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 mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800)); @@ -865,7 +901,7 @@ fn test_create_tx_skip_change_dust() { #[test] #[should_panic(expected = "InsufficientFunds")] 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(); // very high fee rate, so that the only output would be below dust let mut builder = wallet.build_tx(); @@ -878,7 +914,7 @@ fn test_create_tx_drain_to_dust_amount() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -899,7 +935,7 @@ fn test_create_tx_ordering_respected() { #[test] 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 mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); @@ -910,7 +946,7 @@ fn test_create_tx_default_sighash() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -1088,7 +1124,7 @@ fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() { #[test] 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 { input: vec![], output: vec![TxOut { @@ -1138,7 +1174,7 @@ fn test_create_tx_add_utxo() { #[test] #[should_panic(expected = "InsufficientFunds")] 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 { input: vec![], output: vec![TxOut { @@ -1189,8 +1225,8 @@ fn test_create_tx_policy_path_required() { #[test] fn test_create_tx_policy_path_no_csv() { - let descriptors = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(descriptors, None, Network::Regtest).unwrap(); + let (desc, change_desc) = get_test_wpkh_with_change_desc(); + let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap(); let tx = Transaction { 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 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))); } #[test] fn test_add_foreign_utxo() { - let (mut wallet1, _) = get_funded_wallet(get_test_wpkh()); + let (mut wallet1, _) = get_funded_wallet_wpkh(); let (wallet2, _) = get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); @@ -1366,7 +1402,7 @@ fn test_add_foreign_utxo() { expected = "MissingTxOut([OutPoint { txid: 0x21d7fb1bceda00ab4069fc52d06baa13470803e9050edd16f5736e5d8c4925fd, vout: 0 }])" )] 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, _) = get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); @@ -1397,7 +1433,7 @@ fn test_calculate_fee_with_missing_foreign_utxo() { #[test] 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 foreign_utxo_satisfaction = wallet .get_descriptor_for_keychain(KeychainKind::External) @@ -1412,7 +1448,7 @@ fn test_add_foreign_utxo_invalid_psbt_input() { #[test] 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) = get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); @@ -1456,7 +1492,7 @@ fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { #[test] 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) = get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") @@ -1523,7 +1559,7 @@ fn test_add_foreign_utxo_only_witness_utxo() { #[test] fn test_get_psbt_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() { 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()); @@ -1560,7 +1596,7 @@ fn test_create_tx_global_xpubs_master_without_origin() { let key = bip32::Xpub::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap(); let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap(); - assert_eq!(psbt.xpub.len(), 1); + assert_eq!(psbt.xpub.len(), 2); assert_eq!( psbt.xpub.get(&key), Some(&(fingerprint, bip32::DerivationPath::default())) @@ -1570,7 +1606,7 @@ fn test_create_tx_global_xpubs_master_without_origin() { #[test] #[should_panic(expected = "IrreplaceableTransaction")] 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 mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -1587,7 +1623,7 @@ fn test_bump_fee_irreplaceable_tx() { #[test] #[should_panic(expected = "TransactionConfirmed")] 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 mut builder = wallet.build_tx(); builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); @@ -1611,7 +1647,7 @@ fn test_bump_fee_confirmed_tx() { #[test] 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 mut builder = wallet.build_tx(); builder @@ -1645,7 +1681,7 @@ fn test_bump_fee_low_fee_rate() { #[test] #[should_panic(expected = "FeeTooLow")] 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 mut builder = wallet.build_tx(); builder @@ -1668,7 +1704,7 @@ fn test_bump_fee_low_abs() { #[test] #[should_panic(expected = "FeeTooLow")] 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 mut builder = wallet.build_tx(); builder @@ -1689,7 +1725,7 @@ fn test_bump_fee_zero_abs() { #[test] 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") .unwrap() .assume_checked(); @@ -1788,7 +1824,7 @@ fn test_bump_fee_reduce_change() { #[test] 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") .unwrap() .assume_checked(); @@ -1836,7 +1872,7 @@ fn test_bump_fee_reduce_single_recipient() { #[test] 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") .unwrap() .assume_checked(); @@ -1882,7 +1918,7 @@ fn test_bump_fee_absolute_reduce_single_recipient() { #[test] 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. let tx = Transaction { version: transaction::Version::ONE, @@ -1944,7 +1980,7 @@ fn test_bump_fee_drain_wallet() { #[test] #[should_panic(expected = "InsufficientFunds")] 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 // 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 @@ -2006,7 +2042,7 @@ fn test_bump_fee_remove_output_manually_selected_only() { #[test] 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 { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, @@ -2083,7 +2119,7 @@ fn test_bump_fee_add_input() { #[test] 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); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() @@ -2141,7 +2177,7 @@ fn test_bump_fee_absolute_add_input() { #[test] 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); // 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] 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); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() @@ -2286,7 +2322,7 @@ fn test_bump_fee_add_input_change_dust() { #[test] 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 addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") @@ -2352,7 +2388,7 @@ fn test_bump_fee_force_add_input() { #[test] 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 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: // The replacement transaction may only include an unconfirmed input // 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") .unwrap() .assume_checked(); @@ -2464,7 +2500,7 @@ fn test_bump_fee_unconfirmed_input() { // (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 // original transaction) - let (mut wallet, _) = get_funded_wallet(get_test_wpkh()); + let (mut wallet, _) = get_funded_wallet_wpkh(); let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() .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 // for a transaction. // 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") .unwrap() .assume_checked(); @@ -2625,7 +2661,9 @@ fn test_sign_single_xprv_no_hd_keypaths() { #[test] 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") .unwrap() .assume_checked(); @@ -2644,7 +2682,7 @@ fn test_include_output_redeem_witness_script() { #[test] 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") .unwrap() .assume_checked(); @@ -2800,8 +2838,9 @@ fn test_sign_nonstandard_sighash() { #[test] fn test_unused_address() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", - None, Network::Testnet).unwrap(); + let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; + 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 assert!(wallet @@ -2829,7 +2868,8 @@ fn test_unused_address() { #[test] fn test_next_unused_address() { 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!( @@ -2877,8 +2917,9 @@ fn test_next_unused_address() { #[test] fn test_peek_address_at_index() { - let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", - None, Network::Testnet).unwrap(); + let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; + let change_desc = get_test_wpkh(); + let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Testnet).unwrap(); assert_eq!( wallet.peek_address(KeychainKind::External, 1).to_string(), @@ -2916,7 +2957,7 @@ fn test_peek_address_at_index() { #[test] fn test_peek_address_at_index_not_derivable() { let wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)", - None, Network::Testnet).unwrap(); + get_test_wpkh(), Network::Testnet).unwrap(); assert_eq!( wallet.peek_address(KeychainKind::External, 1).to_string(), @@ -2937,7 +2978,7 @@ fn test_peek_address_at_index_not_derivable() { #[test] fn test_returns_index_and_address() { let mut wallet = Wallet::new_no_persist("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", - None, Network::Testnet).unwrap(); + get_test_wpkh(), Network::Testnet).unwrap(); // new index 0 assert_eq!( @@ -2990,7 +3031,7 @@ fn test_returns_index_and_address() { #[test] 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") .unwrap() .assume_checked(); @@ -3005,7 +3046,7 @@ fn test_get_address() { let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let wallet = Wallet::new_no_persist( Bip84(key, KeychainKind::External), - Some(Bip84(key, KeychainKind::Internal)), + Bip84(key, KeychainKind::Internal), Network::Regtest, ) .unwrap(); @@ -3031,27 +3072,12 @@ fn test_get_address() { 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] fn test_reveal_addresses() { - let desc = get_test_tr_single_sig_xprv(); - let mut wallet = Wallet::new_no_persist(desc, None, Network::Signet).unwrap(); + let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc(); + let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Signet).unwrap(); let keychain = KeychainKind::External; let last_revealed_addr = wallet @@ -3071,13 +3097,17 @@ fn test_reveal_addresses() { } #[test] -fn test_get_address_no_reuse_single_descriptor() { +fn test_get_address_no_reuse() { use bdk_wallet::descriptor::template::Bip84; use std::collections::HashSet; let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let mut wallet = - Wallet::new_no_persist(Bip84(key, KeychainKind::External), None, Network::Regtest).unwrap(); + let mut wallet = Wallet::new_no_persist( + Bip84(key, KeychainKind::External), + Bip84(key, KeychainKind::Internal), + Network::Regtest, + ) + .unwrap(); let mut used_set = HashSet::new(); @@ -3124,11 +3154,12 @@ fn test_taproot_remove_tapfields_after_finalize_sign_option() { #[test] 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 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(); assert_eq!( @@ -3138,8 +3169,8 @@ fn test_taproot_psbt_populate_tap_key_origins() { .into_iter() .collect::>(), vec![( - from_str!("b96d3a3dc76a4fc74e976511b23aecb78e0754c23c0ed7a6513e18cbbc7178e9"), - (vec![], (from_str!("f6a5cb8b"), from_str!("m/0"))) + from_str!("0841db1dbaf949dbbda893e01a18f2cca9179cf8ea2d8e667857690502b06483"), + (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/0"))) )], "Wrong input tap_key_origins" ); @@ -3150,8 +3181,8 @@ fn test_taproot_psbt_populate_tap_key_origins() { .into_iter() .collect::>(), vec![( - from_str!("e9b03068cf4a2621d4f81e68f6c4216e6bd260fe6edf6acc55c8d8ae5aeff0a8"), - (vec![], (from_str!("f6a5cb8b"), from_str!("m/1"))) + from_str!("9187c1e80002d19ddde9c5c7f5394e9a063cee8695867b58815af0562695ca21"), + (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/1"))) )], "Wrong output tap_key_origins" ); @@ -3159,7 +3190,8 @@ fn test_taproot_psbt_populate_tap_key_origins() { #[test] 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 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(); builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) + .drain_to(addr.script_pubkey()) + .drain_wallet() .policy_path(path, KeychainKind::External); let psbt = builder.finish().unwrap(); @@ -3332,7 +3365,7 @@ fn test_taproot_sign_using_non_witness_utxo() { #[test] 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 addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") @@ -3575,8 +3608,12 @@ fn test_taproot_sign_derive_index_from_psbt() { let mut psbt = builder.finish().unwrap(); // re-create the wallet with an empty db - let wallet_empty = - Wallet::new_no_persist(get_test_tr_single_sig_xprv(), None, Network::Regtest).unwrap(); + let wallet_empty = Wallet::new_no_persist( + 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 // derivation index @@ -3675,8 +3712,8 @@ fn test_taproot_sign_non_default_sighash() { #[test] fn test_spend_coinbase() { - let descriptor = get_test_wpkh(); - let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Regtest).unwrap(); + let (desc, change_desc) = get_test_wpkh_with_change_desc(); + let mut wallet = Wallet::new_no_persist(desc, change_desc, Network::Regtest).unwrap(); let confirmation_height = 5; wallet @@ -3937,7 +3974,7 @@ fn test_tx_cancellation() { } 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 change_derivation_1 = psbt1 diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 36d560e3..e6c01c20 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -23,7 +23,7 @@ fn main() -> Result<(), anyhow::Error> { let mut wallet = Wallet::new_or_load( external_descriptor, - Some(internal_descriptor), + internal_descriptor, db, Network::Testnet, )?; diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 9a9904a5..f46779b3 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -22,7 +22,7 @@ async fn main() -> Result<(), anyhow::Error> { let mut wallet = Wallet::new_or_load( external_descriptor, - Some(internal_descriptor), + internal_descriptor, db, Network::Signet, )?; diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 5ecb7122..92caca2c 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -21,7 +21,7 @@ fn main() -> Result<(), anyhow::Error> { let mut wallet = Wallet::new_or_load( external_descriptor, - Some(internal_descriptor), + internal_descriptor, db, Network::Testnet, )?; diff --git a/example-crates/wallet_rpc/src/main.rs b/example-crates/wallet_rpc/src/main.rs index 8c709a02..a64f0539 100644 --- a/example-crates/wallet_rpc/src/main.rs +++ b/example-crates/wallet_rpc/src/main.rs @@ -25,7 +25,7 @@ pub struct Args { pub descriptor: String, /// Wallet change descriptor #[clap(env = "CHANGE_DESCRIPTOR")] - pub change_descriptor: Option, + pub change_descriptor: String, /// Earliest block height to start sync from #[clap(env = "START_HEIGHT", long, default_value = "481824")] pub start_height: u32, @@ -88,7 +88,7 @@ fn main() -> anyhow::Result<()> { let start_load_wallet = Instant::now(); let mut wallet = Wallet::new_or_load( &args.descriptor, - args.change_descriptor.as_ref(), + &args.change_descriptor, Store::::open_or_create_new( DB_MAGIC.as_bytes(), args.db_path,