From e4d5b6a6df64f77f5d9c69346908049a78b9e31c Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 25 Sep 2025 14:32:39 -0400 Subject: [PATCH 01/10] feat: add KeyRing type --- src/keyring/mod.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 94 insertions(+) create mode 100644 src/keyring/mod.rs diff --git a/src/keyring/mod.rs b/src/keyring/mod.rs new file mode 100644 index 00000000..7cd710ed --- /dev/null +++ b/src/keyring/mod.rs @@ -0,0 +1,93 @@ +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! The KeyRing is a utility type used to streamline the building of wallets that handle any number +//! of descriptors. It ensures descriptors are usable together, consistent with a given network, +//! and will work with a BDK `Wallet`. + +use crate::descriptor::IntoWalletDescriptor; +use alloc::collections::BTreeMap; +use bitcoin::secp256k1::{All, Secp256k1}; +use bitcoin::Network; +use miniscript::{Descriptor, DescriptorPublicKey}; + +/// KeyRing. +#[derive(Debug, Clone)] +pub struct KeyRing { + pub(crate) secp: Secp256k1, + pub(crate) network: Network, + pub(crate) descriptors: BTreeMap>, + pub(crate) default_keychain: K, +} + +impl KeyRing +where + K: Ord + Clone, +{ + /// Construct a new [`KeyRing`] with the provided `network` and a descriptor. This descriptor + /// will automatically become your default keychain. You can change your default keychain + /// upon adding new ones with [`KeyRing::add_descriptor`]. Note that you cannot use a + /// multipath descriptor here. + pub fn new(network: Network, keychain: K, descriptor: impl IntoWalletDescriptor) -> Self { + let secp = Secp256k1::new(); + let descriptor = descriptor + .into_wallet_descriptor(&secp, network) + .expect("err: invalid descriptor") + .0; + assert!( + !descriptor.is_multipath(), + "err: Use `add_multipath_descriptor` instead" + ); + Self { + secp: Secp256k1::new(), + network, + descriptors: BTreeMap::from([(keychain.clone(), descriptor)]), + default_keychain: keychain.clone(), + } + } + + /// Add a descriptor. Must not be [multipath](miniscript::Descriptor::is_multipath). + pub fn add_descriptor( + &mut self, + keychain: K, + descriptor: impl IntoWalletDescriptor, + default: bool, + ) { + let descriptor = descriptor + .into_wallet_descriptor(&self.secp, self.network) + .expect("err: invalid descriptor") + .0; + assert!( + !descriptor.is_multipath(), + "err: Use `add_multipath_descriptor` instead" + ); + + if default { + self.default_keychain = keychain.clone(); + } + self.descriptors.insert(keychain, descriptor); + } + + /// Returns the specified default keychain on the KeyRing. + pub fn default_keychain(&self) -> K { + self.default_keychain.clone() + } + + /// Change the default keychain on this `KeyRing`. + pub fn set_default_keychain(&mut self, keychain: K) { + self.default_keychain = keychain; + } + + /// Return all keychains on this `KeyRing`. + pub fn list_keychains(&self) -> &BTreeMap> { + &self.descriptors + } +} diff --git a/src/lib.rs b/src/lib.rs index d54eac92..2926048f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub extern crate serde; pub extern crate serde_json; pub mod descriptor; +pub mod keyring; pub mod keys; pub mod psbt; #[cfg(feature = "test-utils")] From 2694bfbeb0728f5d04ba7e41a79aca8dd3f08d37 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 25 Sep 2025 14:35:00 -0400 Subject: [PATCH 02/10] test: add simple KeyRing tests --- tests/keyring.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/keyring.rs diff --git a/tests/keyring.rs b/tests/keyring.rs new file mode 100644 index 00000000..90810a5e --- /dev/null +++ b/tests/keyring.rs @@ -0,0 +1,42 @@ +use bdk_wallet::keyring::KeyRing; +use bdk_wallet::KeychainKind; +use bitcoin::Network; + +// From the mnemonic "awesome awesome awesome awesome awesome awesome awesome awesome awesome +// awesome awesome awesome" +const DESC_1: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/0/*)"; + +#[test] +fn test_simple_keyring() { + let network = Network::Regtest; + let keychain_id = KeychainKind::External; + + let keyring = KeyRing::new(network, keychain_id, DESC_1); + + assert_eq!(keyring.default_keychain(), keychain_id); + assert_eq!(keyring.list_keychains().len(), 1); +} + +#[test] +fn test_8_keychains_keyring() { + const DESC_1: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/0/*)"; + const DESC_2: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/1/*)"; + const DESC_3: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/2/*)"; + const DESC_4: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/3/*)"; + const DESC_5: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/4/*)"; + const DESC_6: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/5/*)"; + const DESC_7: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/6/*)"; + const DESC_8: &str = "tr(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/86'/1'/0'/7/*)"; + + let mut keyring = KeyRing::new(Network::Regtest, 1, DESC_1); + keyring.add_descriptor(2, DESC_2, false); + keyring.add_descriptor(3, DESC_3, false); + keyring.add_descriptor(4, DESC_4, false); + keyring.add_descriptor(5, DESC_5, false); + keyring.add_descriptor(6, DESC_6, false); + keyring.add_descriptor(7, DESC_7, false); + keyring.add_descriptor(8, DESC_8, false); + + assert_eq!(keyring.default_keychain(), 1); + assert_eq!(keyring.list_keychains().len(), 8); +} From e5a824da97a879d094f35013454dfec60dcdcc62 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Thu, 25 Sep 2025 14:39:35 -0400 Subject: [PATCH 03/10] feat: add KeyRing changeset --- src/keyring/changeset.rs | 47 ++++++++++++++++++++++++++++++++++++++++ src/keyring/mod.rs | 23 ++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/keyring/changeset.rs diff --git a/src/keyring/changeset.rs b/src/keyring/changeset.rs new file mode 100644 index 00000000..68a873be --- /dev/null +++ b/src/keyring/changeset.rs @@ -0,0 +1,47 @@ +use crate::keyring::BTreeMap; + +use bitcoin::Network; +use chain::Merge; +use miniscript::{Descriptor, DescriptorPublicKey}; +use serde::{Deserialize, Serialize}; + +/// Represents changes to the `KeyRing`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ChangeSet { + /// Network. + pub network: Option, + /// Added descriptors. + pub descriptors: BTreeMap>, + /// Default keychain + pub default_keychain: Option, +} + +impl Default for ChangeSet { + fn default() -> Self { + Self { + network: None, + descriptors: Default::default(), + default_keychain: None, + } + } +} + +impl Merge for ChangeSet { + fn merge(&mut self, other: Self) { + // merge network + if other.network.is_some() && self.network.is_none() { + self.network = other.network; + } + // merge descriptors + self.descriptors.extend(other.descriptors); + + // Note: if a new default keychain has been set, it will take precedence over the old one. + if other.default_keychain.is_some() { + self.default_keychain = other.default_keychain; + } + } + + fn is_empty(&self) -> bool { + self.network.is_none() && self.descriptors.is_empty() + } +} diff --git a/src/keyring/mod.rs b/src/keyring/mod.rs index 7cd710ed..e8f1cd58 100644 --- a/src/keyring/mod.rs +++ b/src/keyring/mod.rs @@ -13,7 +13,11 @@ //! of descriptors. It ensures descriptors are usable together, consistent with a given network, //! and will work with a BDK `Wallet`. +/// Contains `Changeset` corresponding to `KeyRing`. +pub mod changeset; + use crate::descriptor::IntoWalletDescriptor; +use crate::keyring::changeset::ChangeSet; use alloc::collections::BTreeMap; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::Network; @@ -90,4 +94,23 @@ where pub fn list_keychains(&self) -> &BTreeMap> { &self.descriptors } + + /// Initial changeset. + pub fn initial_changeset(&self) -> ChangeSet { + ChangeSet { + network: Some(self.network), + descriptors: self.descriptors.clone(), + default_keychain: Some(self.default_keychain.clone()), + } + } + + /// Construct from changeset. + pub fn from_changeset(changeset: ChangeSet) -> Option { + Some(Self { + secp: Secp256k1::new(), + network: changeset.network?, + descriptors: changeset.descriptors, + default_keychain: changeset.default_keychain?, + }) + } } From 237ecdfab17d562f90b662fef9167c9330305446 Mon Sep 17 00:00:00 2001 From: codingp110 Date: Sun, 28 Sep 2025 22:21:18 +0530 Subject: [PATCH 04/10] refactor!: comment out `Wallet` dependant code. added #![allow(unused)] to circumvent clippy errors. --- README.md | 4 +- examples/bitcoind_rpc.rs | 221 +- examples/compiler.rs | 81 +- examples/electrum.rs | 301 +- examples/esplora_async.rs | 325 +- examples/esplora_blocking.rs | 287 +- src/descriptor/mod.rs | 2 +- src/descriptor/template.rs | 1756 +++++----- src/persist_test_utils.rs | 709 ++-- src/test_utils.rs | 462 +-- src/wallet/changeset.rs | 207 +- src/wallet/coin_selection.rs | 159 +- src/wallet/export.rs | 437 +-- src/wallet/mod.rs | 5126 ++++++++++++++-------------- src/wallet/params.rs | 118 +- src/wallet/persisted.rs | 771 ++--- src/wallet/signer.rs | 135 +- src/wallet/tx_builder.rs | 1959 ++++++----- src/wallet/utils.rs | 2 +- tests/add_foreign_utxo.rs | 564 ++-- tests/build_fee_bump.rs | 1859 +++++------ tests/persisted_wallet.rs | 931 +++--- tests/psbt.rs | 461 +-- tests/wallet.rs | 6049 +++++++++++++++++----------------- 24 files changed, 11580 insertions(+), 11346 deletions(-) diff --git a/README.md b/README.md index 2756fedd..2dab2642 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ To persist `Wallet` state use a data storage crate that reads and writes [`Chang * [`bdk_file_store`]: Stores wallet changes in a simple flat file. * `rusqlite`: Stores wallet changes in a SQLite database. -**Example** + ## Minimum Supported Rust Version (MSRV) diff --git a/examples/bitcoind_rpc.rs b/examples/bitcoind_rpc.rs index f0bbd729..49fc7da1 100644 --- a/examples/bitcoind_rpc.rs +++ b/examples/bitcoind_rpc.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use bdk_bitcoind_rpc::{ bitcoincore_rpc::{Auth, Client, RpcApi}, Emitter, MempoolEvent, @@ -84,125 +85,125 @@ enum Emission { } fn main() -> anyhow::Result<()> { - let args = Args::parse(); + // let args = Args::parse(); - let rpc_client = Arc::new(args.client()?); - println!( - "Connected to Bitcoin Core RPC at {:?}", - rpc_client.get_blockchain_info().unwrap() - ); + // let rpc_client = Arc::new(args.client()?); + // println!( + // "Connected to Bitcoin Core RPC at {:?}", + // rpc_client.get_blockchain_info().unwrap() + // ); - let start_load_wallet = Instant::now(); - let mut db = Connection::open(args.db_path)?; - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(args.descriptor.clone())) - .descriptor(KeychainKind::Internal, args.change_descriptor.clone()) - .extract_keys() - .check_network(args.network) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => match &args.change_descriptor { - Some(change_desc) => Wallet::create(args.descriptor.clone(), change_desc.clone()) - .network(args.network) - .create_wallet(&mut db)?, - None => Wallet::create_single(args.descriptor.clone()) - .network(args.network) - .create_wallet(&mut db)?, - }, - }; - println!( - "Loaded wallet in {}s", - start_load_wallet.elapsed().as_secs_f32() - ); + // let start_load_wallet = Instant::now(); + // let mut db = Connection::open(args.db_path)?; + // let wallet_opt = Wallet::load() + // .descriptor(KeychainKind::External, Some(args.descriptor.clone())) + // .descriptor(KeychainKind::Internal, args.change_descriptor.clone()) + // .extract_keys() + // .check_network(args.network) + // .load_wallet(&mut db)?; + // let mut wallet = match wallet_opt { + // Some(wallet) => wallet, + // None => match &args.change_descriptor { + // Some(change_desc) => Wallet::create(args.descriptor.clone(), change_desc.clone()) + // .network(args.network) + // .create_wallet(&mut db)?, + // None => Wallet::create_single(args.descriptor.clone()) + // .network(args.network) + // .create_wallet(&mut db)?, + // }, + // }; + // println!( + // "Loaded wallet in {}s", + // start_load_wallet.elapsed().as_secs_f32() + // ); - let address = wallet.reveal_next_address(KeychainKind::External).address; - println!("Wallet address: {address}"); + // let address = wallet.reveal_next_address(KeychainKind::External).address; + // println!("Wallet address: {address}"); - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); + // let balance = wallet.balance(); + // println!("Wallet balance before syncing: {}", balance.total()); - let wallet_tip = wallet.latest_checkpoint(); - println!( - "Wallet tip: {} at height {}", - wallet_tip.hash(), - wallet_tip.height() - ); + // let wallet_tip = wallet.latest_checkpoint(); + // println!( + // "Wallet tip: {} at height {}", + // wallet_tip.hash(), + // wallet_tip.height() + // ); - let (sender, receiver) = sync_channel::(21); + // let (sender, receiver) = sync_channel::(21); - let signal_sender = sender.clone(); - let _ = ctrlc::set_handler(move || { - signal_sender - .send(Emission::SigTerm) - .expect("failed to send sigterm") - }); + // let signal_sender = sender.clone(); + // let _ = ctrlc::set_handler(move || { + // signal_sender + // .send(Emission::SigTerm) + // .expect("failed to send sigterm") + // }); - let mut emitter = Emitter::new( - rpc_client, - wallet_tip, - args.start_height, - wallet - .transactions() - .filter(|tx| tx.chain_position.is_unconfirmed()), - ); - spawn(move || -> Result<(), anyhow::Error> { - while let Some(emission) = emitter.next_block()? { - sender.send(Emission::Block(emission))?; - } - sender.send(Emission::Mempool(emitter.mempool()?))?; - Ok(()) - }); + // let mut emitter = Emitter::new( + // rpc_client, + // wallet_tip, + // args.start_height, + // wallet + // .transactions() + // .filter(|tx| tx.chain_position.is_unconfirmed()), + // ); + // spawn(move || -> Result<(), anyhow::Error> { + // while let Some(emission) = emitter.next_block()? { + // sender.send(Emission::Block(emission))?; + // } + // sender.send(Emission::Mempool(emitter.mempool()?))?; + // Ok(()) + // }); - let mut blocks_received = 0_usize; - for emission in receiver { - match emission { - Emission::SigTerm => { - println!("Sigterm received, exiting..."); - break; - } - Emission::Block(block_emission) => { - blocks_received += 1; - let height = block_emission.block_height(); - let hash = block_emission.block_hash(); - let connected_to = block_emission.connected_to(); - let start_apply_block = Instant::now(); - wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?; - wallet.persist(&mut db)?; - let elapsed = start_apply_block.elapsed().as_secs_f32(); - println!("Applied block {hash} at height {height} in {elapsed}s"); - } - Emission::Mempool(event) => { - let start_apply_mempool = Instant::now(); - wallet.apply_evicted_txs(event.evicted); - wallet.apply_unconfirmed_txs(event.update); - wallet.persist(&mut db)?; - println!( - "Applied unconfirmed transactions in {}s", - start_apply_mempool.elapsed().as_secs_f32() - ); - break; - } - } - } - let wallet_tip_end = wallet.latest_checkpoint(); - let balance = wallet.balance(); - println!( - "Synced {} blocks in {}s", - blocks_received, - start_load_wallet.elapsed().as_secs_f32(), - ); - println!( - "Wallet tip is '{}:{}'", - wallet_tip_end.height(), - wallet_tip_end.hash() - ); - println!("Wallet balance is {}", balance.total()); - println!( - "Wallet has {} transactions and {} utxos", - wallet.transactions().count(), - wallet.list_unspent().count() - ); + // let mut blocks_received = 0_usize; + // for emission in receiver { + // match emission { + // Emission::SigTerm => { + // println!("Sigterm received, exiting..."); + // break; + // } + // Emission::Block(block_emission) => { + // blocks_received += 1; + // let height = block_emission.block_height(); + // let hash = block_emission.block_hash(); + // let connected_to = block_emission.connected_to(); + // let start_apply_block = Instant::now(); + // wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?; + // wallet.persist(&mut db)?; + // let elapsed = start_apply_block.elapsed().as_secs_f32(); + // println!("Applied block {hash} at height {height} in {elapsed}s"); + // } + // Emission::Mempool(event) => { + // let start_apply_mempool = Instant::now(); + // wallet.apply_evicted_txs(event.evicted); + // wallet.apply_unconfirmed_txs(event.update); + // wallet.persist(&mut db)?; + // println!( + // "Applied unconfirmed transactions in {}s", + // start_apply_mempool.elapsed().as_secs_f32() + // ); + // break; + // } + // } + // } + // let wallet_tip_end = wallet.latest_checkpoint(); + // let balance = wallet.balance(); + // println!( + // "Synced {} blocks in {}s", + // blocks_received, + // start_load_wallet.elapsed().as_secs_f32(), + // ); + // println!( + // "Wallet tip is '{}:{}'", + // wallet_tip_end.height(), + // wallet_tip_end.hash() + // ); + // println!("Wallet balance is {}", balance.total()); + // println!( + // "Wallet has {} transactions and {} utxos", + // wallet.transactions().count(), + // wallet.list_unspent().count() + // ); Ok(()) } diff --git a/examples/compiler.rs b/examples/compiler.rs index 3db1692f..910a6b7d 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -8,11 +8,11 @@ // , at your option. // You may not use this file except in accordance with one or both of these // licenses. - +#![allow(unused)] extern crate bdk_wallet; -extern crate bitcoin; -extern crate miniscript; -extern crate serde_json; +// extern crate bitcoin; +// extern crate miniscript; +// extern crate serde_json; use std::error::Error; use std::str::FromStr; @@ -32,47 +32,52 @@ use bdk_wallet::{KeychainKind, Wallet}; /// This example demonstrates the interaction between a bdk wallet and miniscript policy. #[allow(clippy::print_stdout)] fn main() -> Result<(), Box> { - // 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'][..], ""); + // // 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}"); + // println!("Compiling policy: \n{policy_str}"); - // Parse the string as a [`Concrete`] type miniscript policy. - let policy = Concrete::::from_str(&policy_str)?; + // // Parse the string as a [`Concrete`] type miniscript policy. + // 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()?)?.to_string(); + // // Create a `wsh` type descriptor from the policy. + // // `policy.compile()` returns the resulting miniscript from the policy. + // let descriptor = Descriptor::new_wsh(policy.compile()?)?.to_string(); - println!("Compiled into Descriptor: \n{descriptor}"); + // println!("Compiled into Descriptor: \n{descriptor}"); - // Create a new wallet from descriptors - let mut wallet = Wallet::create_single(descriptor) - .network(Network::Regtest) - .create_wallet_no_persist()?; + // // Create a new wallet from descriptors + // let mut wallet = Wallet::create_single(descriptor) + // .network(Network::Regtest) + // .create_wallet_no_persist()?; - println!( - "First derived address from the descriptor: \n{}", - wallet.next_unused_address(KeychainKind::External), - ); + // println!( + // "First derived address from the descriptor: \n{}", + // wallet.next_unused_address(KeychainKind::External), + // ); - // BDK also has it's own `Policy` structure to represent the spending condition in a more - // human readable json format. - let spending_policy = wallet.policies(KeychainKind::External)?; - println!( - "The BDK spending policy: \n{}", - serde_json::to_string_pretty(&spending_policy)? - ); + // // BDK also has it's own `Policy` structure to represent the spending condition in a more + // // human readable json format. + // let spending_policy = wallet.policies(KeychainKind::External)?; + // println!( + // "The BDK spending policy: \n{}", + // serde_json::to_string_pretty(&spending_policy)? + // ); Ok(()) } diff --git a/examples/electrum.rs b/examples/electrum.rs index 8ccbe130..9f88f761 100644 --- a/examples/electrum.rs +++ b/examples/electrum.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use bdk_electrum::electrum_client; use bdk_electrum::BdkElectrumClient; use bdk_wallet::bitcoin::Amount; @@ -23,156 +24,156 @@ const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7 const ELECTRUM_URL: &str = "ssl://mempool.space:40002"; fn main() -> Result<(), anyhow::Error> { - let mut db = Connection::open(DB_PATH)?; - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() - .check_network(NETWORK) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - .network(NETWORK) - .create_wallet(&mut db)?, - }; - - let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; - println!("Generated Address: {address}"); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - println!("Performing Full Sync..."); - let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?); - - // Populate the electrum client's transaction cache so it doesn't redownload transaction we - // already have. - client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = HashSet::::new(); - move |k, spk_i, _| { - if once.insert(k) { - print!("\nScanning keychain [{k:?}]"); - } - print!(" {spk_i:<3}"); - stdout.flush().expect("must flush"); - } - }); - - let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?; - - println!(); - - wallet.apply_update(update)?; - wallet.persist(&mut db)?; - - let balance = wallet.balance(); - println!("Wallet balance after full sync: {}", balance.total()); - println!( - "Wallet has {} transactions and {} utxos after full sync", - wallet.transactions().count(), - wallet.list_unspent().count() - ); - - if balance.total() < SEND_AMOUNT { - println!("Please send at least {SEND_AMOUNT} to the receiving address"); - std::process::exit(0); - } - - let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); - let mut tx_builder = wallet.build_tx(); - tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); - tx_builder.fee_rate(target_fee_rate); - - let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized); - let original_fee = psbt.fee_amount().unwrap(); - let tx_feerate = psbt.fee_rate().unwrap(); - let tx = psbt.extract_tx()?; - client.transaction_broadcast(&tx)?; - let txid = tx.compute_txid(); - println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}"); - - println!("Partial Sync..."); - print!("SCANNING: "); - let mut last_printed = 0; - let sync_request = wallet - .start_sync_with_revealed_spks() - .inspect(move |_, sync_progress| { - let progress_percent = - (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; - let progress_percent = progress_percent.round() as u32; - if progress_percent % 5 == 0 && progress_percent > last_printed { - print!("{progress_percent}% "); - std::io::stdout().flush().expect("must flush"); - last_printed = progress_percent; - } - }); - client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); - let sync_update = client.sync(sync_request, BATCH_SIZE, false)?; - println!(); - wallet.apply_update(sync_update)?; - wallet.persist(&mut db)?; - - // bump fee rate for tx by at least 1 sat per vbyte - let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap(); - let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx"); - builder.fee_rate(feerate); - let mut bumped_psbt = builder.finish().unwrap(); - let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?; - assert!(finalize_btx); - let new_fee = bumped_psbt.fee_amount().unwrap(); - let bumped_tx = bumped_psbt.extract_tx()?; - assert_eq!( - bumped_tx - .output - .iter() - .find(|txout| txout.script_pubkey == address.script_pubkey()) - .unwrap() - .value, - SEND_AMOUNT, - "Recipient output should remain unchanged" - ); - assert!( - new_fee > original_fee, - "New fee ({new_fee}) should be higher than original ({original_fee})" - ); - - // wait for first transaction to make it into the mempool and be indexed on mempool.space - sleep(Duration::from_secs(10)); - client.transaction_broadcast(&bumped_tx)?; - println!( - "Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}", - bumped_tx.compute_txid() - ); - - println!("Syncing after bumped tx broadcast..."); - let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {}); - let sync_update = client.sync(sync_request, BATCH_SIZE, false)?; - - let mut evicted_txs = Vec::new(); - for (txid, last_seen) in &sync_update.tx_update.evicted_ats { - evicted_txs.push((*txid, *last_seen)); - } - - wallet.apply_update(sync_update)?; - if !evicted_txs.is_empty() { - println!("Applied {} evicted transactions", evicted_txs.len()); - } - wallet.persist(&mut db)?; - - let balance_after_sync = wallet.balance(); - println!("Wallet balance after sync: {}", balance_after_sync.total()); - println!( - "Wallet has {} transactions and {} utxos after partial sync", - wallet.transactions().count(), - wallet.list_unspent().count() - ); + // let mut db = Connection::open(DB_PATH)?; + // let wallet_opt = Wallet::load() + // .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + // .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + // .extract_keys() + // .check_network(NETWORK) + // .load_wallet(&mut db)?; + // let mut wallet = match wallet_opt { + // Some(wallet) => wallet, + // None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + // .network(NETWORK) + // .create_wallet(&mut db)?, + // }; + + // let address = wallet.next_unused_address(KeychainKind::External); + // wallet.persist(&mut db)?; + // println!("Generated Address: {address}"); + + // let balance = wallet.balance(); + // println!("Wallet balance before syncing: {}", balance.total()); + + // println!("Performing Full Sync..."); + // let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?); + + // // Populate the electrum client's transaction cache so it doesn't redownload transaction we + // // already have. + // client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + + // let request = wallet.start_full_scan().inspect({ + // let mut stdout = std::io::stdout(); + // let mut once = HashSet::::new(); + // move |k, spk_i, _| { + // if once.insert(k) { + // print!("\nScanning keychain [{k:?}]"); + // } + // print!(" {spk_i:<3}"); + // stdout.flush().expect("must flush"); + // } + // }); + + // let update = client.full_scan(request, STOP_GAP, BATCH_SIZE, false)?; + + // println!(); + + // wallet.apply_update(update)?; + // wallet.persist(&mut db)?; + + // let balance = wallet.balance(); + // println!("Wallet balance after full sync: {}", balance.total()); + // println!( + // "Wallet has {} transactions and {} utxos after full sync", + // wallet.transactions().count(), + // wallet.list_unspent().count() + // ); + + // if balance.total() < SEND_AMOUNT { + // println!("Please send at least {SEND_AMOUNT} to the receiving address"); + // std::process::exit(0); + // } + + // let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); + // let mut tx_builder = wallet.build_tx(); + // tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); + // tx_builder.fee_rate(target_fee_rate); + + // let mut psbt = tx_builder.finish()?; + // let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + // assert!(finalized); + // let original_fee = psbt.fee_amount().unwrap(); + // let tx_feerate = psbt.fee_rate().unwrap(); + // let tx = psbt.extract_tx()?; + // client.transaction_broadcast(&tx)?; + // let txid = tx.compute_txid(); + // println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}"); + + // println!("Partial Sync..."); + // print!("SCANNING: "); + // let mut last_printed = 0; + // let sync_request = wallet + // .start_sync_with_revealed_spks() + // .inspect(move |_, sync_progress| { + // let progress_percent = + // (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + // let progress_percent = progress_percent.round() as u32; + // if progress_percent % 5 == 0 && progress_percent > last_printed { + // print!("{progress_percent}% "); + // std::io::stdout().flush().expect("must flush"); + // last_printed = progress_percent; + // } + // }); + // client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx)); + // let sync_update = client.sync(sync_request, BATCH_SIZE, false)?; + // println!(); + // wallet.apply_update(sync_update)?; + // wallet.persist(&mut db)?; + + // // bump fee rate for tx by at least 1 sat per vbyte + // let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap(); + // let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx"); + // builder.fee_rate(feerate); + // let mut bumped_psbt = builder.finish().unwrap(); + // let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?; + // assert!(finalize_btx); + // let new_fee = bumped_psbt.fee_amount().unwrap(); + // let bumped_tx = bumped_psbt.extract_tx()?; + // assert_eq!( + // bumped_tx + // .output + // .iter() + // .find(|txout| txout.script_pubkey == address.script_pubkey()) + // .unwrap() + // .value, + // SEND_AMOUNT, + // "Recipient output should remain unchanged" + // ); + // assert!( + // new_fee > original_fee, + // "New fee ({new_fee}) should be higher than original ({original_fee})" + // ); + + // // wait for first transaction to make it into the mempool and be indexed on mempool.space + // sleep(Duration::from_secs(10)); + // client.transaction_broadcast(&bumped_tx)?; + // println!( + // "Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}", + // bumped_tx.compute_txid() + // ); + + // println!("Syncing after bumped tx broadcast..."); + // let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {}); + // let sync_update = client.sync(sync_request, BATCH_SIZE, false)?; + + // let mut evicted_txs = Vec::new(); + // for (txid, last_seen) in &sync_update.tx_update.evicted_ats { + // evicted_txs.push((*txid, *last_seen)); + // } + + // wallet.apply_update(sync_update)?; + // if !evicted_txs.is_empty() { + // println!("Applied {} evicted transactions", evicted_txs.len()); + // } + // wallet.persist(&mut db)?; + + // let balance_after_sync = wallet.balance(); + // println!("Wallet balance after sync: {}", balance_after_sync.total()); + // println!( + // "Wallet has {} transactions and {} utxos after partial sync", + // wallet.transactions().count(), + // wallet.list_unspent().count() + // ); Ok(()) } diff --git a/examples/esplora_async.rs b/examples/esplora_async.rs index 6e19069c..81294faa 100644 --- a/examples/esplora_async.rs +++ b/examples/esplora_async.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use bdk_esplora::{esplora_client, EsploraAsyncExt}; use bdk_wallet::{ bitcoin::{Amount, FeeRate, Network}, @@ -20,168 +21,168 @@ const ESPLORA_URL: &str = "https://mempool.space/testnet4/api"; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - let mut db = Connection::open(DB_PATH)?; - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() - .check_network(NETWORK) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - .network(NETWORK) - .create_wallet(&mut db)?, - }; - - let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; - println!("Next unused address: ({}) {address}", address.index); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - println!("Full Sync..."); - let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{keychain:?}]"); - } - print!(" {spk_i:<3}"); - stdout.flush().expect("must flush") - } - }); - - let update = client - .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) - .await?; - - wallet.apply_update(update)?; - wallet.persist(&mut db)?; - println!(); - - let balance = wallet.balance(); - println!("Wallet balance after full sync: {}", balance.total()); - println!( - "Wallet has {} transactions and {} utxos after full sync", - wallet.transactions().count(), - wallet.list_unspent().count() - ); - - if balance.total() < SEND_AMOUNT { - println!("Please send at least {SEND_AMOUNT} to the receiving address"); - std::process::exit(0); - } - - let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); - let mut tx_builder = wallet.build_tx(); - tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); - tx_builder.fee_rate(target_fee_rate); - - let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized); - let original_fee = psbt.fee_amount().unwrap(); - let tx_feerate = psbt.fee_rate().unwrap(); - let tx = psbt.extract_tx()?; - client.broadcast(&tx).await?; - let txid = tx.compute_txid(); - println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}"); - - println!("Partial Sync..."); - print!("SCANNING: "); - let mut printed: u32 = 0; - let sync_request = wallet - .start_sync_with_revealed_spks() - .inspect(move |_, sync_progress| { - let progress_percent = - (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; - let progress_percent = progress_percent.round() as u32; - if progress_percent % 5 == 0 && progress_percent > printed { - print!("{progress_percent}% "); - std::io::stdout().flush().expect("must flush"); - printed = progress_percent; - } - }); - let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?; - println!(); - wallet.apply_update(sync_update)?; - wallet.persist(&mut db)?; - - // bump fee rate for tx by at least 1 sat per vbyte - let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap(); - let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx"); - builder.fee_rate(feerate); - let mut bumped_psbt = builder.finish().unwrap(); - let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?; - assert!(finalize_btx); - let new_fee = bumped_psbt.fee_amount().unwrap(); - let bumped_tx = bumped_psbt.extract_tx()?; - assert_eq!( - bumped_tx - .output - .iter() - .find(|txout| txout.script_pubkey == address.script_pubkey()) - .unwrap() - .value, - SEND_AMOUNT, - "Outputs should be the same" - ); - assert!( - new_fee > original_fee, - "New fee ({new_fee}) should be higher than original ({original_fee})", - ); - - // wait for first transaction to make it into the mempool and be indexed on mempool.space - sleep(Duration::from_secs(10)).await; - client.broadcast(&bumped_tx).await?; - println!( - "Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}", - bumped_tx.compute_txid() - ); - - println!("syncing after broadcasting bumped tx..."); - print!("SCANNING: "); - let sync_request = wallet - .start_sync_with_revealed_spks() - .inspect(move |_, sync_progress| { - let progress_percent = - (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; - let progress_percent = progress_percent.round() as u32; - if progress_percent % 10 == 0 && progress_percent > printed { - print!("{progress_percent}% "); - std::io::stdout().flush().expect("must flush"); - printed = progress_percent; - } - }); - let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?; - println!(); - - let mut evicted_txs = Vec::new(); - - for (txid, last_seen) in &sync_update.tx_update.evicted_ats { - evicted_txs.push((*txid, *last_seen)); - } - - wallet.apply_update(sync_update)?; - - if !evicted_txs.is_empty() { - println!("Applied {} evicted transactions", evicted_txs.len()); - } - - wallet.persist(&mut db)?; - - let balance_after_sync = wallet.balance(); - println!("Wallet balance after sync: {}", balance_after_sync.total()); - println!( - "Wallet has {} transactions and {} utxos after partial sync", - wallet.transactions().count(), - wallet.list_unspent().count() - ); + // let mut db = Connection::open(DB_PATH)?; + // let wallet_opt = Wallet::load() + // .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + // .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + // .extract_keys() + // .check_network(NETWORK) + // .load_wallet(&mut db)?; + // let mut wallet = match wallet_opt { + // Some(wallet) => wallet, + // None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + // .network(NETWORK) + // .create_wallet(&mut db)?, + // }; + + // let address = wallet.next_unused_address(KeychainKind::External); + // wallet.persist(&mut db)?; + // println!("Next unused address: ({}) {address}", address.index); + + // let balance = wallet.balance(); + // println!("Wallet balance before syncing: {}", balance.total()); + + // println!("Full Sync..."); + // let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?; + + // let request = wallet.start_full_scan().inspect({ + // let mut stdout = std::io::stdout(); + // let mut once = BTreeSet::::new(); + // move |keychain, spk_i, _| { + // if once.insert(keychain) { + // print!("\nScanning keychain [{keychain:?}]"); + // } + // print!(" {spk_i:<3}"); + // stdout.flush().expect("must flush") + // } + // }); + + // let update = client + // .full_scan(request, STOP_GAP, PARALLEL_REQUESTS) + // .await?; + + // wallet.apply_update(update)?; + // wallet.persist(&mut db)?; + // println!(); + + // let balance = wallet.balance(); + // println!("Wallet balance after full sync: {}", balance.total()); + // println!( + // "Wallet has {} transactions and {} utxos after full sync", + // wallet.transactions().count(), + // wallet.list_unspent().count() + // ); + + // if balance.total() < SEND_AMOUNT { + // println!("Please send at least {SEND_AMOUNT} to the receiving address"); + // std::process::exit(0); + // } + + // let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); + // let mut tx_builder = wallet.build_tx(); + // tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); + // tx_builder.fee_rate(target_fee_rate); + + // let mut psbt = tx_builder.finish()?; + // let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + // assert!(finalized); + // let original_fee = psbt.fee_amount().unwrap(); + // let tx_feerate = psbt.fee_rate().unwrap(); + // let tx = psbt.extract_tx()?; + // client.broadcast(&tx).await?; + // let txid = tx.compute_txid(); + // println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}"); + + // println!("Partial Sync..."); + // print!("SCANNING: "); + // let mut printed: u32 = 0; + // let sync_request = wallet + // .start_sync_with_revealed_spks() + // .inspect(move |_, sync_progress| { + // let progress_percent = + // (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + // let progress_percent = progress_percent.round() as u32; + // if progress_percent % 5 == 0 && progress_percent > printed { + // print!("{progress_percent}% "); + // std::io::stdout().flush().expect("must flush"); + // printed = progress_percent; + // } + // }); + // let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?; + // println!(); + // wallet.apply_update(sync_update)?; + // wallet.persist(&mut db)?; + + // // bump fee rate for tx by at least 1 sat per vbyte + // let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap(); + // let mut builder = wallet.build_fee_bump(txid).expect("failed to bump tx"); + // builder.fee_rate(feerate); + // let mut bumped_psbt = builder.finish().unwrap(); + // let finalize_btx = wallet.sign(&mut bumped_psbt, SignOptions::default())?; + // assert!(finalize_btx); + // let new_fee = bumped_psbt.fee_amount().unwrap(); + // let bumped_tx = bumped_psbt.extract_tx()?; + // assert_eq!( + // bumped_tx + // .output + // .iter() + // .find(|txout| txout.script_pubkey == address.script_pubkey()) + // .unwrap() + // .value, + // SEND_AMOUNT, + // "Outputs should be the same" + // ); + // assert!( + // new_fee > original_fee, + // "New fee ({new_fee}) should be higher than original ({original_fee})", + // ); + + // // wait for first transaction to make it into the mempool and be indexed on mempool.space + // sleep(Duration::from_secs(10)).await; + // client.broadcast(&bumped_tx).await?; + // println!( + // "Broadcasted bumped tx. Txid: https://mempool.space/testnet4/tx/{}", + // bumped_tx.compute_txid() + // ); + + // println!("syncing after broadcasting bumped tx..."); + // print!("SCANNING: "); + // let sync_request = wallet + // .start_sync_with_revealed_spks() + // .inspect(move |_, sync_progress| { + // let progress_percent = + // (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + // let progress_percent = progress_percent.round() as u32; + // if progress_percent % 10 == 0 && progress_percent > printed { + // print!("{progress_percent}% "); + // std::io::stdout().flush().expect("must flush"); + // printed = progress_percent; + // } + // }); + // let sync_update = client.sync(sync_request, PARALLEL_REQUESTS).await?; + // println!(); + + // let mut evicted_txs = Vec::new(); + + // for (txid, last_seen) in &sync_update.tx_update.evicted_ats { + // evicted_txs.push((*txid, *last_seen)); + // } + + // wallet.apply_update(sync_update)?; + + // if !evicted_txs.is_empty() { + // println!("Applied {} evicted transactions", evicted_txs.len()); + // } + + // wallet.persist(&mut db)?; + + // let balance_after_sync = wallet.balance(); + // println!("Wallet balance after sync: {}", balance_after_sync.total()); + // println!( + // "Wallet has {} transactions and {} utxos after partial sync", + // wallet.transactions().count(), + // wallet.list_unspent().count() + // ); Ok(()) } diff --git a/examples/esplora_blocking.rs b/examples/esplora_blocking.rs index 3131bec0..6132a74e 100644 --- a/examples/esplora_blocking.rs +++ b/examples/esplora_blocking.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use bdk_esplora::{esplora_client, EsploraExt}; use bdk_wallet::rusqlite::Connection; use bdk_wallet::{ @@ -20,149 +21,149 @@ const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7 const ESPLORA_URL: &str = "https://mempool.space/testnet4/api"; fn main() -> Result<(), anyhow::Error> { - let mut db = Connection::open(DB_PATH)?; - let wallet_opt = Wallet::load() - .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - .extract_keys() - .check_network(NETWORK) - .load_wallet(&mut db)?; - let mut wallet = match wallet_opt { - Some(wallet) => wallet, - None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - .network(NETWORK) - .create_wallet(&mut db)?, - }; - - let address = wallet.next_unused_address(KeychainKind::External); - wallet.persist(&mut db)?; - println!( - "Next unused address: ({}) {}", - address.index, address.address - ); - - let balance = wallet.balance(); - println!("Wallet balance before syncing: {}", balance.total()); - - println!("Full Sync..."); - let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking(); - - let request = wallet.start_full_scan().inspect({ - let mut stdout = std::io::stdout(); - let mut once = BTreeSet::::new(); - move |keychain, spk_i, _| { - if once.insert(keychain) { - print!("\nScanning keychain [{keychain:?}] "); - } - print!(" {spk_i:<3}"); - stdout.flush().expect("must flush") - } - }); - - let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?; - wallet.apply_update(update)?; - wallet.persist(&mut db)?; - println!(); - - let balance = wallet.balance(); - println!("Wallet balance after syncing: {}", balance.total()); - - if balance.total() < SEND_AMOUNT { - println!("Please send at least {SEND_AMOUNT} to the receiving address"); - std::process::exit(0); - } - - let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); - let mut tx_builder = wallet.build_tx(); - tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); - tx_builder.fee_rate(target_fee_rate); - - let mut psbt = tx_builder.finish()?; - let finalized = wallet.sign(&mut psbt, SignOptions::default())?; - assert!(finalized); - let original_fee = psbt.fee_amount().unwrap(); - let tx_feerate = psbt.fee_rate().unwrap(); - let tx = psbt.extract_tx()?; - client.broadcast(&tx)?; - let txid = tx.compute_txid(); - println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}"); - - println!("Partial Sync..."); - print!("SCANNING: "); - let mut printed = 0; - let sync_request = wallet - .start_sync_with_revealed_spks() - .inspect(move |_, sync_progress| { - let progress_percent = - (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; - let progress_percent = progress_percent.round() as u32; - if progress_percent % 5 == 0 && progress_percent > printed { - print!("{progress_percent}% "); - std::io::stdout().flush().expect("must flush"); - printed = progress_percent; - } - }); - let sync_update = client.sync(sync_request, PARALLEL_REQUESTS)?; - - wallet.apply_update(sync_update)?; - wallet.persist(&mut db)?; - println!(); - - // bump fee rate for tx by at least 1 sat per vbyte - let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap(); - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(feerate); - let mut new_psbt = builder.finish().unwrap(); - let finalize_tx = wallet.sign(&mut new_psbt, SignOptions::default())?; - assert!(finalize_tx); - let new_fee = new_psbt.fee_amount().unwrap(); - let bumped_tx = new_psbt.extract_tx()?; - assert_eq!( - bumped_tx - .output - .iter() - .find(|txout| txout.script_pubkey == address.script_pubkey()) - .unwrap() - .value, - SEND_AMOUNT, - "Outputs should remain the same" - ); - assert!( - new_fee > original_fee, - "Replacement tx fee ({new_fee}) should be higher than original ({original_fee})", - ); - - // wait for first transaction to make it into the mempool and be indexed on mempool.space - sleep(Duration::from_secs(10)); - client.broadcast(&bumped_tx)?; - println!( - "Broadcast replacement transaction. Txid: https://mempool.space/testnet4/tx/{}", - bumped_tx.compute_txid() - ); - - println!("Syncing after bumped tx..."); - let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {}); - let sync_update = client.sync(sync_request, PARALLEL_REQUESTS)?; - println!(); - - let mut evicted_txs = Vec::new(); - for (txid, last_seen) in &sync_update.tx_update.evicted_ats { - evicted_txs.push((*txid, *last_seen)); - } - - wallet.apply_update(sync_update)?; - if !evicted_txs.is_empty() { - println!("Applied {} evicted transactions", evicted_txs.len()); - } - wallet.persist(&mut db)?; - - let balance_after_sync = wallet.balance(); - println!("Wallet balance after sync: {}", balance_after_sync.total()); - println!( - "Wallet has {} transactions and {} utxos", - wallet.transactions().count(), - wallet.list_unspent().count() - ); + // let mut db = Connection::open(DB_PATH)?; + // let wallet_opt = Wallet::load() + // .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) + // .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) + // .extract_keys() + // .check_network(NETWORK) + // .load_wallet(&mut db)?; + // let mut wallet = match wallet_opt { + // Some(wallet) => wallet, + // None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) + // .network(NETWORK) + // .create_wallet(&mut db)?, + // }; + + // let address = wallet.next_unused_address(KeychainKind::External); + // wallet.persist(&mut db)?; + // println!( + // "Next unused address: ({}) {}", + // address.index, address.address + // ); + + // let balance = wallet.balance(); + // println!("Wallet balance before syncing: {}", balance.total()); + + // println!("Full Sync..."); + // let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking(); + + // let request = wallet.start_full_scan().inspect({ + // let mut stdout = std::io::stdout(); + // let mut once = BTreeSet::::new(); + // move |keychain, spk_i, _| { + // if once.insert(keychain) { + // print!("\nScanning keychain [{keychain:?}] "); + // } + // print!(" {spk_i:<3}"); + // stdout.flush().expect("must flush") + // } + // }); + + // let update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?; + // wallet.apply_update(update)?; + // wallet.persist(&mut db)?; + // println!(); + + // let balance = wallet.balance(); + // println!("Wallet balance after syncing: {}", balance.total()); + + // if balance.total() < SEND_AMOUNT { + // println!("Please send at least {SEND_AMOUNT} to the receiving address"); + // std::process::exit(0); + // } + + // let target_fee_rate = FeeRate::from_sat_per_vb(1).unwrap(); + // let mut tx_builder = wallet.build_tx(); + // tx_builder.add_recipient(address.script_pubkey(), SEND_AMOUNT); + // tx_builder.fee_rate(target_fee_rate); + + // let mut psbt = tx_builder.finish()?; + // let finalized = wallet.sign(&mut psbt, SignOptions::default())?; + // assert!(finalized); + // let original_fee = psbt.fee_amount().unwrap(); + // let tx_feerate = psbt.fee_rate().unwrap(); + // let tx = psbt.extract_tx()?; + // client.broadcast(&tx)?; + // let txid = tx.compute_txid(); + // println!("Tx broadcasted! Txid: https://mempool.space/testnet4/tx/{txid}"); + + // println!("Partial Sync..."); + // print!("SCANNING: "); + // let mut printed = 0; + // let sync_request = wallet + // .start_sync_with_revealed_spks() + // .inspect(move |_, sync_progress| { + // let progress_percent = + // (100 * sync_progress.consumed()) as f32 / sync_progress.total() as f32; + // let progress_percent = progress_percent.round() as u32; + // if progress_percent % 5 == 0 && progress_percent > printed { + // print!("{progress_percent}% "); + // std::io::stdout().flush().expect("must flush"); + // printed = progress_percent; + // } + // }); + // let sync_update = client.sync(sync_request, PARALLEL_REQUESTS)?; + + // wallet.apply_update(sync_update)?; + // wallet.persist(&mut db)?; + // println!(); + + // // bump fee rate for tx by at least 1 sat per vbyte + // let feerate = FeeRate::from_sat_per_vb(tx_feerate.to_sat_per_vb_ceil() + 1).unwrap(); + // let mut builder = wallet.build_fee_bump(txid).unwrap(); + // builder.fee_rate(feerate); + // let mut new_psbt = builder.finish().unwrap(); + // let finalize_tx = wallet.sign(&mut new_psbt, SignOptions::default())?; + // assert!(finalize_tx); + // let new_fee = new_psbt.fee_amount().unwrap(); + // let bumped_tx = new_psbt.extract_tx()?; + // assert_eq!( + // bumped_tx + // .output + // .iter() + // .find(|txout| txout.script_pubkey == address.script_pubkey()) + // .unwrap() + // .value, + // SEND_AMOUNT, + // "Outputs should remain the same" + // ); + // assert!( + // new_fee > original_fee, + // "Replacement tx fee ({new_fee}) should be higher than original ({original_fee})", + // ); + + // // wait for first transaction to make it into the mempool and be indexed on mempool.space + // sleep(Duration::from_secs(10)); + // client.broadcast(&bumped_tx)?; + // println!( + // "Broadcast replacement transaction. Txid: https://mempool.space/testnet4/tx/{}", + // bumped_tx.compute_txid() + // ); + + // println!("Syncing after bumped tx..."); + // let sync_request = wallet.start_sync_with_revealed_spks().inspect(|_, _| {}); + // let sync_update = client.sync(sync_request, PARALLEL_REQUESTS)?; + // println!(); + + // let mut evicted_txs = Vec::new(); + // for (txid, last_seen) in &sync_update.tx_update.evicted_ats { + // evicted_txs.push((*txid, *last_seen)); + // } + + // wallet.apply_update(sync_update)?; + // if !evicted_txs.is_empty() { + // println!("Applied {} evicted transactions", evicted_txs.len()); + // } + // wallet.persist(&mut db)?; + + // let balance_after_sync = wallet.balance(); + // println!("Wallet balance after sync: {}", balance_after_sync.total()); + // println!( + // "Wallet has {} transactions and {} utxos", + // wallet.transactions().count(), + // wallet.list_unspent().count() + // ); Ok(()) } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 751bec69..75cbb6e6 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -13,7 +13,7 @@ //! //! This module contains generic utilities to work with descriptors, plus some re-exported types //! from [`miniscript`]. - +#![allow(unused)] use crate::alloc::string::ToString; use crate::collections::BTreeMap; use alloc::string::String; diff --git a/src/descriptor/template.rs b/src/descriptor/template.rs index 6a8ad686..608d056b 100644 --- a/src/descriptor/template.rs +++ b/src/descriptor/template.rs @@ -68,459 +68,501 @@ impl IntoWalletDescriptor for T { } } -/// P2PKH template. Expands to a descriptor `pkh(key)` -/// -/// ## Example -/// -/// ``` -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::Wallet; -/// # use bdk_wallet::KeychainKind; -/// use bdk_wallet::template::P2Pkh; -/// -/// let key_external = -/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let key_internal = -/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = Wallet::create(P2Pkh(key_external), P2Pkh(key_internal)) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!( -/// wallet -/// .next_unused_address(KeychainKind::External) -/// .to_string(), -/// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT" -/// ); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct P2Pkh>(pub K); - -impl> DescriptorTemplate for P2Pkh { - fn build(self, _network: Network) -> Result { - descriptor!(pkh(self.0)) - } -} - -/// P2WPKH-P2SH template. Expands to a descriptor `sh(wpkh(key))` -/// -/// ## Example -/// -/// ``` -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::Wallet; -/// # use bdk_wallet::KeychainKind; -/// use bdk_wallet::template::P2Wpkh_P2Sh; -/// -/// let key_external = -/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let key_internal = -/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = Wallet::create(P2Wpkh_P2Sh(key_external), P2Wpkh_P2Sh(key_internal)) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!( -/// wallet -/// .next_unused_address(KeychainKind::External) -/// .to_string(), -/// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" -/// ); -/// # Ok::<_, Box>(()) -/// ``` -#[allow(non_camel_case_types)] -#[derive(Debug, Clone)] -pub struct P2Wpkh_P2Sh>(pub K); - -impl> DescriptorTemplate for P2Wpkh_P2Sh { - fn build(self, _network: Network) -> Result { - descriptor!(sh(wpkh(self.0))) - } -} - -/// P2WPKH template. Expands to a descriptor `wpkh(key)` -/// -/// ## Example -/// -/// ``` -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::Wallet; -/// # use bdk_wallet::KeychainKind; -/// use bdk_wallet::template::P2Wpkh; -/// -/// let key_external = -/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let key_internal = -/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = Wallet::create(P2Wpkh(key_external), P2Wpkh(key_internal)) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!( -/// wallet -/// .next_unused_address(KeychainKind::External) -/// .to_string(), -/// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd" -/// ); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct P2Wpkh>(pub K); - -impl> DescriptorTemplate for P2Wpkh { - fn build(self, _network: Network) -> Result { - descriptor!(wpkh(self.0)) - } -} - -/// P2TR template. Expands to a descriptor `tr(key)` -/// -/// ## Example -/// -/// ``` -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::Wallet; -/// # use bdk_wallet::KeychainKind; -/// use bdk_wallet::template::P2TR; -/// -/// let key_external = -/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; -/// let key_internal = -/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; -/// let mut wallet = Wallet::create(P2TR(key_external), P2TR(key_internal)) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!( -/// wallet -/// .next_unused_address(KeychainKind::External) -/// .to_string(), -/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" -/// ); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct P2TR>(pub K); - -impl> DescriptorTemplate for P2TR { - fn build(self, _network: Network) -> Result { - descriptor!(tr(self.0)) - } -} - -/// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)` -/// -/// Since there are hardened derivation steps, this template requires a private derivable key -/// (generally a `xprv`/`tprv`). -/// -/// See [`Bip44Public`] for a template that can work with a `xpub`/`tpub`. -/// -/// ## Example -/// -/// ```rust -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip44; -/// -/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::create(Bip44(key.clone(), KeychainKind::External), Bip44(key, KeychainKind::Internal)) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip44>(pub K, pub KeychainKind); - -impl> DescriptorTemplate for Bip44 { - fn build(self, network: Network) -> Result { - P2Pkh(legacy::make_bipxx_private(44, self.0, self.1, network)?).build(network) - } -} - -/// BIP44 public template. Expands to `pkh(key/{0,1}/*)` -/// -/// This assumes that the key used has already been derived with `m/44'/0'/0'` for Mainnet or -/// `m/44'/1'/0'` for Testnet. -/// -/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. -/// -/// See [`Bip44`] for a template that does the full derivation, but requires private data -/// for the key. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{KeychainKind, Wallet}; -/// use bdk_wallet::template::Bip44Public; -/// -/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; -/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::create( -/// Bip44Public(key.clone(), fingerprint, KeychainKind::External), -/// Bip44Public(key, fingerprint, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); - -impl> DescriptorTemplate for Bip44Public { - fn build(self, network: Network) -> Result { - P2Pkh(legacy::make_bipxx_public( - 44, self.0, self.1, self.2, network, - )?) - .build(network) - } -} - -/// BIP49 template. Expands to `sh(wpkh(key/49'/{0,1}'/0'/{0,1}/*))` -/// -/// Since there are hardened derivation steps, this template requires a private derivable key -/// (generally a `xprv`/`tprv`). -/// -/// See [`Bip49Public`] for a template that can work with a `xpub`/`tpub`. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip49; -/// -/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::create( -/// Bip49(key.clone(), KeychainKind::External), -/// Bip49(key, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip49>(pub K, pub KeychainKind); - -impl> DescriptorTemplate for Bip49 { - fn build(self, network: Network) -> Result { - P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1, network)?).build(network) - } -} - -/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))` -/// -/// This assumes that the key used has already been derived with `m/49'/0'/0'` for Mainnet or -/// `m/49'/1'/0'` for Testnet. -/// -/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. -/// -/// See [`Bip49`] for a template that does the full derivation, but requires private data -/// for the key. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip49Public; -/// -/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; -/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::create( -/// Bip49Public(key.clone(), fingerprint, KeychainKind::External), -/// Bip49Public(key, fingerprint, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); - -impl> DescriptorTemplate for Bip49Public { - fn build(self, network: Network) -> Result { - P2Wpkh_P2Sh(segwit_v0::make_bipxx_public( - 49, self.0, self.1, self.2, network, - )?) - .build(network) - } -} - -/// BIP84 template. Expands to `wpkh(key/84'/{0,1}'/0'/{0,1}/*)` -/// -/// Since there are hardened derivation steps, this template requires a private derivable key -/// (generally a `xprv`/`tprv`). -/// -/// See [`Bip84Public`] for a template that can work with a `xpub`/`tpub`. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip84; -/// -/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::create( -/// Bip84(key.clone(), KeychainKind::External), -/// Bip84(key, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip84>(pub K, pub KeychainKind); - -impl> DescriptorTemplate for Bip84 { - fn build(self, network: Network) -> Result { - P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1, network)?).build(network) - } -} - -/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)` -/// -/// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or -/// `m/84'/1'/0'` for Testnet. -/// -/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. -/// -/// See [`Bip84`] for a template that does the full derivation, but requires private data -/// for the key. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip84Public; -/// -/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; -/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::create( -/// Bip84Public(key.clone(), fingerprint, KeychainKind::External), -/// Bip84Public(key, fingerprint, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); - -impl> DescriptorTemplate for Bip84Public { - fn build(self, network: Network) -> Result { - P2Wpkh(segwit_v0::make_bipxx_public( - 84, self.0, self.1, self.2, network, - )?) - .build(network) - } -} - -/// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)` -/// -/// Since there are hardened derivation steps, this template requires a private derivable key -/// (generally a `xprv`/`tprv`). -/// -/// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip86; -/// -/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; -/// let mut wallet = Wallet::create( -/// Bip86(key.clone(), KeychainKind::External), -/// Bip86(key, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip86>(pub K, pub KeychainKind); - -impl> DescriptorTemplate for Bip86 { - fn build(self, network: Network) -> Result { - P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network) - } -} - -/// BIP86 public template. Expands to `tr(key/{0,1}/*)` -/// -/// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or -/// `m/86'/1'/0'` for Testnet. -/// -/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. -/// -/// See [`Bip86`] for a template that does the full derivation, but requires private data -/// for the key. -/// -/// ## Example -/// -/// ``` -/// # use std::str::FromStr; -/// # use bdk_wallet::bitcoin::{PrivateKey, Network}; -/// # use bdk_wallet::{Wallet, KeychainKind}; -/// use bdk_wallet::template::Bip86Public; -/// -/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; -/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; -/// let mut wallet = Wallet::create( -/// Bip86Public(key.clone(), fingerprint, KeychainKind::External), -/// Bip86Public(key, fingerprint, KeychainKind::Internal), -/// ) -/// .network(Network::Testnet) -/// .create_wallet_no_persist()?; -/// -/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37"); -/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku"); -/// # Ok::<_, Box>(()) -/// ``` -#[derive(Debug, Clone)] -pub struct Bip86Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); - -impl> DescriptorTemplate for Bip86Public { - fn build(self, network: Network) -> Result { - P2TR(segwit_v1::make_bipxx_public( - 86, self.0, self.1, self.2, network, - )?) - .build(network) - } -} +// /// P2PKH template. Expands to a descriptor `pkh(key)` +// /// +// /// ## Example +// /// +// /// ``` +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::Wallet; +// /// # use bdk_wallet::KeychainKind; +// /// use bdk_wallet::template::P2Pkh; +// /// +// /// let key_external = +// /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +// /// let key_internal = +// /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; +// /// let mut wallet = Wallet::create(P2Pkh(key_external), P2Pkh(key_internal)) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!( +// /// wallet +// /// .next_unused_address(KeychainKind::External) +// /// .to_string(), +// /// "mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT" +// /// ); +// /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct P2Pkh>(pub K); + +// impl> DescriptorTemplate for P2Pkh { +// fn build(self, _network: Network) -> Result { +// descriptor!(pkh(self.0)) +// } +// } + +// /// P2WPKH-P2SH template. Expands to a descriptor `sh(wpkh(key))` +// /// +// /// ## Example +// /// +// /// ``` +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::Wallet; +// /// # use bdk_wallet::KeychainKind; +// /// use bdk_wallet::template::P2Wpkh_P2Sh; +// /// +// /// let key_external = +// /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +// /// let key_internal = +// /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; +// /// let mut wallet = Wallet::create(P2Wpkh_P2Sh(key_external), P2Wpkh_P2Sh(key_internal)) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!( +// /// wallet +// /// .next_unused_address(KeychainKind::External) +// /// .to_string(), +// /// "2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5" +// /// ); +// /// # Ok::<_, Box>(()) +// /// ``` +// #[allow(non_camel_case_types)] +// #[derive(Debug, Clone)] +// pub struct P2Wpkh_P2Sh>(pub K); + +// impl> DescriptorTemplate for P2Wpkh_P2Sh { +// fn build(self, _network: Network) -> Result { +// descriptor!(sh(wpkh(self.0))) +// } +// } + +// /// P2WPKH template. Expands to a descriptor `wpkh(key)` +// /// +// /// ## Example +// /// +// /// ``` +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::Wallet; +// /// # use bdk_wallet::KeychainKind; +// /// use bdk_wallet::template::P2Wpkh; +// /// +// /// let key_external = +// /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +// /// let key_internal = +// /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; +// /// let mut wallet = Wallet::create(P2Wpkh(key_external), P2Wpkh(key_internal)) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!( +// /// wallet +// /// .next_unused_address(KeychainKind::External) +// /// .to_string(), +// /// "tb1q4525hmgw265tl3drrl8jjta7ayffu6jf68ltjd" +// /// ); +// /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct P2Wpkh>(pub K); + +// impl> DescriptorTemplate for P2Wpkh { +// fn build(self, _network: Network) -> Result { +// descriptor!(wpkh(self.0)) +// } +// } + +// /// P2TR template. Expands to a descriptor `tr(key)` +// /// +// /// ## Example +// /// +// /// ``` +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::Wallet; +// /// # use bdk_wallet::KeychainKind; +// /// use bdk_wallet::template::P2TR; +// /// +// /// let key_external = +// /// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?; +// /// let key_internal = +// /// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?; +// /// let mut wallet = Wallet::create(P2TR(key_external), P2TR(key_internal)) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!( +// /// wallet +// /// .next_unused_address(KeychainKind::External) +// /// .to_string(), +// /// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46" +// /// ); +// /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct P2TR>(pub K); + +// impl> DescriptorTemplate for P2TR { +// fn build(self, _network: Network) -> Result { +// descriptor!(tr(self.0)) +// } +// } + +// /// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)` +// /// +// /// Since there are hardened derivation steps, this template requires a private derivable key +// /// (generally a `xprv`/`tprv`). +// /// +// /// See [`Bip44Public`] for a template that can work with a `xpub`/`tpub`. +// /// +// /// ## Example +// /// +// /// ```rust +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip44; +// /// +// /// let key = +// bitcoin::bip32::Xpriv::from_str(" +// tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m" +// )?; /// let mut wallet = Wallet::create(Bip44(key.clone(), KeychainKind::External), Bip44(key, +// KeychainKind::Internal)) /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "pkh([c55b303f/44'/1'/0' +// ]tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/ +// 0/*)#5wrnv0xt"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip44>(pub K, pub KeychainKind); + +// impl> DescriptorTemplate for Bip44 { +// fn build(self, network: Network) -> Result { +// P2Pkh(legacy::make_bipxx_private(44, self.0, self.1, network)?).build(network) +// } +// } + +// /// BIP44 public template. Expands to `pkh(key/{0,1}/*)` +// /// +// /// This assumes that the key used has already been derived with `m/44'/0'/0'` for Mainnet or +// /// `m/44'/1'/0'` for Testnet. +// /// +// /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +// /// +// /// See [`Bip44`] for a template that does the full derivation, but requires private data +// /// for the key. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{KeychainKind, Wallet}; +// /// use bdk_wallet::template::Bip44Public; +// /// +// /// let key = +// bitcoin::bip32::Xpub::from_str(" +// tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU" +// )?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; +// /// let mut wallet = Wallet::create( +// /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), +// /// Bip44Public(key, fingerprint, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "miNG7dJTzJqNbFS19svRdTCisC65dsubtR"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "pkh([c55b303f/44'/1'/0' +// ]tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/ +// 0/*)#cfhumdqz"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); + +// impl> DescriptorTemplate for Bip44Public { +// fn build(self, network: Network) -> Result { +// P2Pkh(legacy::make_bipxx_public( +// 44, self.0, self.1, self.2, network, +// )?) +// .build(network) +// } +// } + +// /// BIP49 template. Expands to `sh(wpkh(key/49'/{0,1}'/0'/{0,1}/*))` +// /// +// /// Since there are hardened derivation steps, this template requires a private derivable key +// /// (generally a `xprv`/`tprv`). +// /// +// /// See [`Bip49Public`] for a template that can work with a `xpub`/`tpub`. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip49; +// /// +// /// let key = +// bitcoin::bip32::Xpriv::from_str(" +// tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m" +// )?; /// let mut wallet = Wallet::create( +// /// Bip49(key.clone(), KeychainKind::External), +// /// Bip49(key, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "sh(wpkh([c55b303f/49'/1'/0' +// ]tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/ +// 0/*))#s9vxlc8e"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip49>(pub K, pub KeychainKind); + +// impl> DescriptorTemplate for Bip49 { +// fn build(self, network: Network) -> Result { +// P2Wpkh_P2Sh(segwit_v0::make_bipxx_private(49, self.0, self.1, network)?).build(network) +// } +// } + +// /// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))` +// /// +// /// This assumes that the key used has already been derived with `m/49'/0'/0'` for Mainnet or +// /// `m/49'/1'/0'` for Testnet. +// /// +// /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +// /// +// /// See [`Bip49`] for a template that does the full derivation, but requires private data +// /// for the key. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip49Public; +// /// +// /// let key = +// bitcoin::bip32::Xpub::from_str(" +// tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L" +// )?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; +// /// let mut wallet = Wallet::create( +// /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), +// /// Bip49Public(key, fingerprint, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "sh(wpkh([c55b303f/49'/1'/0' +// ]tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/ +// 0/*))#3tka9g0q"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub +// KeychainKind); + +// impl> DescriptorTemplate for Bip49Public { +// fn build(self, network: Network) -> Result { +// P2Wpkh_P2Sh(segwit_v0::make_bipxx_public( +// 49, self.0, self.1, self.2, network, +// )?) +// .build(network) +// } +// } + +// /// BIP84 template. Expands to `wpkh(key/84'/{0,1}'/0'/{0,1}/*)` +// /// +// /// Since there are hardened derivation steps, this template requires a private derivable key +// /// (generally a `xprv`/`tprv`). +// /// +// /// See [`Bip84Public`] for a template that can work with a `xpub`/`tpub`. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip84; +// /// +// /// let key = +// bitcoin::bip32::Xpriv::from_str(" +// tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m" +// )?; /// let mut wallet = Wallet::create( +// /// Bip84(key.clone(), KeychainKind::External), +// /// Bip84(key, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "wpkh([c55b303f/84'/1'/0' +// ]tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/ +// 0/*)#6kfecsmr"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip84>(pub K, pub KeychainKind); + +// impl> DescriptorTemplate for Bip84 { +// fn build(self, network: Network) -> Result { +// P2Wpkh(segwit_v0::make_bipxx_private(84, self.0, self.1, network)?).build(network) +// } +// } + +// /// BIP84 public template. Expands to `wpkh(key/{0,1}/*)` +// /// +// /// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or +// /// `m/84'/1'/0'` for Testnet. +// /// +// /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +// /// +// /// See [`Bip84`] for a template that does the full derivation, but requires private data +// /// for the key. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip84Public; +// /// +// /// let key = +// bitcoin::bip32::Xpub::from_str(" +// tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q" +// )?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; +// /// let mut wallet = Wallet::create( +// /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), +// /// Bip84Public(key, fingerprint, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "wpkh([c55b303f/84'/1'/0' +// ]tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/ +// 0/*)#dhu402yv"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub +// KeychainKind); + +// impl> DescriptorTemplate for Bip84Public { +// fn build(self, network: Network) -> Result { +// P2Wpkh(segwit_v0::make_bipxx_public( +// 84, self.0, self.1, self.2, network, +// )?) +// .build(network) +// } +// } + +// /// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)` +// /// +// /// Since there are hardened derivation steps, this template requires a private derivable key +// /// (generally a `xprv`/`tprv`). +// /// +// /// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip86; +// /// +// /// let key = +// bitcoin::bip32::Xpriv::from_str(" +// tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m" +// )?; /// let mut wallet = Wallet::create( +// /// Bip86(key.clone(), KeychainKind::External), +// /// Bip86(key, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "tr([c55b303f/86'/1'/0' +// ]tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/ +// 0/*)#dkgvr5hm"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip86>(pub K, pub KeychainKind); + +// impl> DescriptorTemplate for Bip86 { +// fn build(self, network: Network) -> Result { +// P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network) +// } +// } + +// /// BIP86 public template. Expands to `tr(key/{0,1}/*)` +// /// +// /// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or +// /// `m/86'/1'/0'` for Testnet. +// /// +// /// This template requires the parent fingerprint to populate correctly the metadata of PSBTs. +// /// +// /// See [`Bip86`] for a template that does the full derivation, but requires private data +// /// for the key. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bdk_wallet::bitcoin::{PrivateKey, Network}; +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// use bdk_wallet::template::Bip86Public; +// /// +// /// let key = +// bitcoin::bip32::Xpub::from_str(" +// tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q" +// )?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; +// /// let mut wallet = Wallet::create( +// /// Bip86Public(key.clone(), fingerprint, KeychainKind::External), +// /// Bip86Public(key, fingerprint, KeychainKind::Internal), +// /// ) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), +// "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37"); /// assert_eq!(wallet. +// public_descriptor(KeychainKind::External).to_string(), +// "tr([c55b303f/86'/1'/0' +// ]tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/ +// 0/*)#2p65srku"); /// # Ok::<_, Box>(()) +// /// ``` +// #[derive(Debug, Clone)] +// pub struct Bip86Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); + +// impl> DescriptorTemplate for Bip86Public { +// fn build(self, network: Network) -> Result { +// P2TR(segwit_v1::make_bipxx_public( +// 86, self.0, self.1, self.2, network, +// )?) +// .build(network) +// } +// } macro_rules! expand_make_bipxx { ( $mod_name:ident, $ctx:ty ) => { @@ -604,39 +646,43 @@ mod test { use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; use miniscript::Descriptor; - // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)` - #[test] - fn test_bip44_template_cointype() { - use bitcoin::bip32::ChildNumber::{self, Hardened}; - - let xprvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap(); - assert!(xprvkey.network.is_mainnet()); - let xdesc = Bip44(xprvkey, KeychainKind::Internal) - .build(Network::Bitcoin) - .unwrap(); - - if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { - let path: Vec = pkh.into_inner().full_derivation_path().unwrap().into(); - let purpose = path.first().unwrap(); - assert_matches!(purpose, Hardened { index: 44 }); - let coin_type = path.get(1).unwrap(); - assert_matches!(coin_type, Hardened { index: 0 }); - } - - let tprvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - assert!(!tprvkey.network.is_mainnet()); - let tdesc = Bip44(tprvkey, KeychainKind::Internal) - .build(Network::Testnet) - .unwrap(); - - if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { - let path: Vec = pkh.into_inner().full_derivation_path().unwrap().into(); - let purpose = path.first().unwrap(); - assert_matches!(purpose, Hardened { index: 44 }); - let coin_type = path.get(1).unwrap(); - assert_matches!(coin_type, Hardened { index: 1 }); - } - } + // // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)` + // #[test] + // fn test_bip44_template_cointype() { + // use bitcoin::bip32::ChildNumber::{self, Hardened}; + + // let xprvkey = + // bitcoin::bip32::Xpriv::from_str(" + // xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf" + // ).unwrap(); assert!(xprvkey.network.is_mainnet()); + // let xdesc = Bip44(xprvkey, KeychainKind::Internal) + // .build(Network::Bitcoin) + // .unwrap(); + + // if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { + // let path: Vec = pkh.into_inner().full_derivation_path().unwrap().into(); + // let purpose = path.first().unwrap(); + // assert_matches!(purpose, Hardened { index: 44 }); + // let coin_type = path.get(1).unwrap(); + // assert_matches!(coin_type, Hardened { index: 0 }); + // } + + // let tprvkey = + // bitcoin::bip32::Xpriv::from_str(" + // tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy" + // ).unwrap(); assert!(!tprvkey.network.is_mainnet()); + // let tdesc = Bip44(tprvkey, KeychainKind::Internal) + // .build(Network::Testnet) + // .unwrap(); + + // if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { + // let path: Vec = pkh.into_inner().full_derivation_path().unwrap().into(); + // let purpose = path.first().unwrap(); + // assert_matches!(purpose, Hardened { index: 44 }); + // let coin_type = path.get(1).unwrap(); + // assert_matches!(coin_type, Hardened { index: 1 }); + // } + // } // verify template descriptor generates expected address(es) fn check( @@ -663,365 +709,381 @@ mod test { } } - // P2PKH - #[test] - fn test_p2ph_template() { - let prvkey = - bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") - .unwrap(); - check( - P2Pkh(prvkey).build(Network::Bitcoin), - false, - false, - true, - Network::Regtest, - &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], - ); - - let pubkey = bitcoin::PublicKey::from_str( - "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", - ) - .unwrap(); - check( - P2Pkh(pubkey).build(Network::Bitcoin), - false, - false, - true, - Network::Regtest, - &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], - ); - } - - // P2WPKH-P2SH `sh(wpkh(key))` - #[test] - fn test_p2wphp2sh_template() { - let prvkey = - bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") - .unwrap(); - check( - P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin), - true, - false, - true, - Network::Regtest, - &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], - ); - - let pubkey = bitcoin::PublicKey::from_str( - "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", - ) - .unwrap(); - check( - P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin), - true, - false, - true, - Network::Regtest, - &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], - ); - } - - // P2WPKH `wpkh(key)` - #[test] - fn test_p2wph_template() { - let prvkey = - bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") - .unwrap(); - check( - P2Wpkh(prvkey).build(Network::Bitcoin), - true, - false, - true, - Network::Regtest, - &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], - ); - - let pubkey = bitcoin::PublicKey::from_str( - "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", - ) - .unwrap(); - check( - P2Wpkh(pubkey).build(Network::Bitcoin), - true, - false, - true, - Network::Regtest, - &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], - ); - } - - // P2TR `tr(key)` - #[test] - fn test_p2tr_template() { - let prvkey = - bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") - .unwrap(); - check( - P2TR(prvkey).build(Network::Bitcoin), - false, - true, - true, - Network::Regtest, - &["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"], - ); - - let pubkey = bitcoin::PublicKey::from_str( - "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", - ) - .unwrap(); - check( - P2TR(pubkey).build(Network::Bitcoin), - false, - true, - true, - Network::Regtest, - &["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"], - ); - } - - // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)` - #[test] - fn test_bip44_template() { - let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - check( - Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin), - false, - false, - false, - Network::Regtest, - &[ - "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5", - "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP", - "mzYvhRAuQqbdSKMVVzXNYyqihgNdRadAUQ", - ], - ); - check( - Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin), - false, - false, - false, - Network::Regtest, - &[ - "muHF98X9KxEzdKrnFAX85KeHv96eXopaip", - "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR", - "mgvkdv1ffmsXd2B1sRKQ5dByK3SzpG42rA", - ], - ); - } - - // BIP44 public `pkh(key/{0,1}/*)` - #[test] - fn test_bip44_public_template() { - let pubkey = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); - let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); - check( - Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), - false, - false, - false, - Network::Regtest, - &[ - "miNG7dJTzJqNbFS19svRdTCisC65dsubtR", - "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg", - "muCPpS6Ue7nkzeJMWDViw7Lkwr92Yc4K8g", - ], - ); - check( - Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), - false, - false, - false, - Network::Regtest, - &[ - "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H", - "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG", - "mhYiyat2rtEnV77cFfQsW32y1m2ceCGHPo", - ], - ); - } - - // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))` - #[test] - fn test_bip49_template() { - let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - check( - Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV", - "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS", - "2NAFTVtksF9T4a97M7nyCjwUBD24QevZ5Z4", - ], - ); - check( - Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG", - "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p", - "2NA8ek4CdQ6aMkveYF6AYuEYNrftB47QGTn", - ], - ); - } - - // BIP49 public `sh(wpkh(key/{0,1}/*))` - #[test] - fn test_bip49_public_template() { - let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); - let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); - check( - Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt", - "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX", - "2MveFxAuC8BYPzTybx7FxSzW8HSd8ATT4z7", - ], - ); - check( - Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ", - "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH", - "2NBs3CTVYPr1HCzjB4YFsnWCPCtNg8uMEfp", - ], - ); - } - - // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)` - #[test] - fn test_bip84_template() { - let prvkey = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - check( - Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s", - "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp", - "bcrt1q4h7fq9zhxst6e69p3n882nfj649l7w9g3zccfp", - ], - ); - check( - Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa", - "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45", - "bcrt1qpks7n0gq74hsgsz3phn5vuazjjq0f5eqhsgyce", - ], - ); - } - - // BIP84 public `wpkh(key/{0,1}/*)` - #[test] - fn test_bip84_public_template() { - let pubkey = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); - let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); - check( - Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h", - "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana", - "bcrt1qt9800y6xl3922jy3uyl0z33jh5wfpycyhcylr9", - ], - ); - check( - Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), - true, - false, - false, - Network::Regtest, - &[ - "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2", - "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp", - "bcrt1qhlac3c5ranv5w5emlnqs7wxhkxt8maelylcarp", - ], - ); - } - - // BIP86 `tr(key/86'/0'/0'/{0,1}/*)` - // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki - #[test] - fn test_bip86_template() { - let prvkey = bitcoin::bip32::Xpriv::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap(); - check( - Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin), - false, - true, - false, - Network::Bitcoin, - &[ - "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", - "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", - "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8", - ], - ); - check( - Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin), - false, - true, - false, - Network::Bitcoin, - &[ - "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", - "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", - "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5", - ], - ); - } - - // BIP86 public `tr(key/{0,1}/*)` - // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki - #[test] - fn test_bip86_public_template() { - let pubkey = bitcoin::bip32::Xpub::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap(); - let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap(); - check( - Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), - false, - true, - false, - Network::Bitcoin, - &[ - "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", - "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", - "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8", - ], - ); - check( - Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), - false, - true, - false, - Network::Bitcoin, - &[ - "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", - "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", - "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5", - ], - ); - } + // // P2PKH + // #[test] + // fn test_p2ph_template() { + // let prvkey = + // bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + // .unwrap(); + // check( + // P2Pkh(prvkey).build(Network::Bitcoin), + // false, + // false, + // true, + // Network::Regtest, + // &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], + // ); + + // let pubkey = bitcoin::PublicKey::from_str( + // "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + // ) + // .unwrap(); + // check( + // P2Pkh(pubkey).build(Network::Bitcoin), + // false, + // false, + // true, + // Network::Regtest, + // &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], + // ); + // } + + // // P2WPKH-P2SH `sh(wpkh(key))` + // #[test] + // fn test_p2wphp2sh_template() { + // let prvkey = + // bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + // .unwrap(); + // check( + // P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin), + // true, + // false, + // true, + // Network::Regtest, + // &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], + // ); + + // let pubkey = bitcoin::PublicKey::from_str( + // "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + // ) + // .unwrap(); + // check( + // P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin), + // true, + // false, + // true, + // Network::Regtest, + // &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], + // ); + // } + + // // P2WPKH `wpkh(key)` + // #[test] + // fn test_p2wph_template() { + // let prvkey = + // bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + // .unwrap(); + // check( + // P2Wpkh(prvkey).build(Network::Bitcoin), + // true, + // false, + // true, + // Network::Regtest, + // &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], + // ); + + // let pubkey = bitcoin::PublicKey::from_str( + // "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + // ) + // .unwrap(); + // check( + // P2Wpkh(pubkey).build(Network::Bitcoin), + // true, + // false, + // true, + // Network::Regtest, + // &["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], + // ); + // } + + // // P2TR `tr(key)` + // #[test] + // fn test_p2tr_template() { + // let prvkey = + // bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um") + // .unwrap(); + // check( + // P2TR(prvkey).build(Network::Bitcoin), + // false, + // true, + // true, + // Network::Regtest, + // &["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"], + // ); + + // let pubkey = bitcoin::PublicKey::from_str( + // "03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd", + // ) + // .unwrap(); + // check( + // P2TR(pubkey).build(Network::Bitcoin), + // false, + // true, + // true, + // Network::Regtest, + // &["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"], + // ); + // } + + // // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)` + // #[test] + // fn test_bip44_template() { + // let prvkey = + // bitcoin::bip32::Xpriv::from_str(" + // tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy" + // ).unwrap(); check( + // Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin), + // false, + // false, + // false, + // Network::Regtest, + // &[ + // "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5", + // "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP", + // "mzYvhRAuQqbdSKMVVzXNYyqihgNdRadAUQ", + // ], + // ); + // check( + // Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + // false, + // false, + // false, + // Network::Regtest, + // &[ + // "muHF98X9KxEzdKrnFAX85KeHv96eXopaip", + // "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR", + // "mgvkdv1ffmsXd2B1sRKQ5dByK3SzpG42rA", + // ], + // ); + // } + + // // BIP44 public `pkh(key/{0,1}/*)` + // #[test] + // fn test_bip44_public_template() { + // let pubkey = + // bitcoin::bip32::Xpub::from_str(" + // tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU" + // ).unwrap(); let fingerprint = + // bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( + // Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + // false, + // false, + // false, + // Network::Regtest, + // &[ + // "miNG7dJTzJqNbFS19svRdTCisC65dsubtR", + // "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg", + // "muCPpS6Ue7nkzeJMWDViw7Lkwr92Yc4K8g", + // ], + // ); + // check( + // Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + // false, + // false, + // false, + // Network::Regtest, + // &[ + // "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H", + // "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG", + // "mhYiyat2rtEnV77cFfQsW32y1m2ceCGHPo", + // ], + // ); + // } + + // // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))` + // #[test] + // fn test_bip49_template() { + // let prvkey = + // bitcoin::bip32::Xpriv::from_str(" + // tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy" + // ).unwrap(); check( + // Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV", + // "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS", + // "2NAFTVtksF9T4a97M7nyCjwUBD24QevZ5Z4", + // ], + // ); + // check( + // Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG", + // "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p", + // "2NA8ek4CdQ6aMkveYF6AYuEYNrftB47QGTn", + // ], + // ); + // } + + // // BIP49 public `sh(wpkh(key/{0,1}/*))` + // #[test] + // fn test_bip49_public_template() { + // let pubkey = + // bitcoin::bip32::Xpub::from_str(" + // tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L" + // ).unwrap(); let fingerprint = + // bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( + // Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt", + // "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX", + // "2MveFxAuC8BYPzTybx7FxSzW8HSd8ATT4z7", + // ], + // ); + // check( + // Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ", + // "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH", + // "2NBs3CTVYPr1HCzjB4YFsnWCPCtNg8uMEfp", + // ], + // ); + // } + + // // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)` + // #[test] + // fn test_bip84_template() { + // let prvkey = + // bitcoin::bip32::Xpriv::from_str(" + // tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy" + // ).unwrap(); check( + // Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s", + // "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp", + // "bcrt1q4h7fq9zhxst6e69p3n882nfj649l7w9g3zccfp", + // ], + // ); + // check( + // Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa", + // "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45", + // "bcrt1qpks7n0gq74hsgsz3phn5vuazjjq0f5eqhsgyce", + // ], + // ); + // } + + // // BIP84 public `wpkh(key/{0,1}/*)` + // #[test] + // fn test_bip84_public_template() { + // let pubkey = + // bitcoin::bip32::Xpub::from_str(" + // tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q" + // ).unwrap(); let fingerprint = + // bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( + // Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h", + // "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana", + // "bcrt1qt9800y6xl3922jy3uyl0z33jh5wfpycyhcylr9", + // ], + // ); + // check( + // Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + // true, + // false, + // false, + // Network::Regtest, + // &[ + // "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2", + // "bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp", + // "bcrt1qhlac3c5ranv5w5emlnqs7wxhkxt8maelylcarp", + // ], + // ); + // } + + // // BIP86 `tr(key/86'/0'/0'/{0,1}/*)` + // // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki + // #[test] + // fn test_bip86_template() { + // let prvkey = + // bitcoin::bip32::Xpriv::from_str(" + // xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu" + // ).unwrap(); check( + // Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin), + // false, + // true, + // false, + // Network::Bitcoin, + // &[ + // "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", + // "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", + // "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8", + // ], + // ); + // check( + // Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin), + // false, + // true, + // false, + // Network::Bitcoin, + // &[ + // "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", + // "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", + // "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5", + // ], + // ); + // } + + // // BIP86 public `tr(key/{0,1}/*)` + // // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki + // #[test] + // fn test_bip86_public_template() { + // let pubkey = + // bitcoin::bip32::Xpub::from_str(" + // xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ" + // ).unwrap(); let fingerprint = + // bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap(); check( + // Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), + // false, + // true, + // false, + // Network::Bitcoin, + // &[ + // "bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr", + // "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh", + // "bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8", + // ], + // ); + // check( + // Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), + // false, + // true, + // false, + // Network::Bitcoin, + // &[ + // "bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7", + // "bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj", + // "bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5", + // ], + // ); + // } } diff --git a/src/persist_test_utils.rs b/src/persist_test_utils.rs index 20ddf4eb..68b336f8 100644 --- a/src/persist_test_utils.rs +++ b/src/persist_test_utils.rs @@ -1,5 +1,5 @@ //! Utilities for testing custom persistence backends for `bdk_wallet` - +#![allow(unused)] use crate::{ bitcoin::{ absolute, key::Secp256k1, transaction, Address, Amount, Network, OutPoint, ScriptBuf, @@ -10,7 +10,8 @@ use crate::{ local_chain, tx_graph, ConfirmationBlockTime, DescriptorExt, Merge, SpkIterator, }, miniscript::descriptor::{Descriptor, DescriptorPublicKey}, - ChangeSet, WalletPersister, + ChangeSet, + // WalletPersister, }; macro_rules! block_id { @@ -65,355 +66,355 @@ fn spk_at_index(descriptor: &Descriptor, index: u32) -> Scr .script_pubkey() } -/// tests if [`Wallet`] is being persisted correctly -/// -/// [`Wallet`]: -/// [`ChangeSet`]: -/// -/// We create a dummy [`ChangeSet`], persist it and check if loaded [`ChangeSet`] matches -/// the persisted one. We then create another such dummy [`ChangeSet`], persist it and load it to -/// check if merged [`ChangeSet`] is returned. -pub fn persist_wallet_changeset(filename: &str, create_store: CreateStore) -where - CreateStore: Fn(&Path) -> anyhow::Result, - Store: WalletPersister, - Store::Error: Debug, -{ - // create store - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - let mut store = create_store(&file_path).expect("store should get created"); - - // initialize store - let changeset = - WalletPersister::initialize(&mut store).expect("empty changeset should get loaded"); - assert_eq!(changeset, ChangeSet::default()); - - // create changeset - let descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); - let change_descriptor: Descriptor = DESCRIPTORS[1].parse().unwrap(); - - let local_chain_changeset = local_chain::ChangeSet { - blocks: [ - (910234, Some(hash!("B"))), - (910233, Some(hash!("T"))), - (910235, Some(hash!("C"))), - ] - .into(), - }; - - let tx1 = Arc::new(create_one_inp_one_out_tx( - hash!("We_are_all_Satoshi"), - 30_000, - )); - let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000)); - - let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime { - block_id: block_id!(910234, "B"), - confirmation_time: 1755317160, - }; - - let tx_graph_changeset = tx_graph::ChangeSet:: { - txs: [tx1.clone()].into(), - txouts: [ - ( - OutPoint::new(hash!("Rust"), 0), - TxOut { - value: Amount::from_sat(1300), - script_pubkey: spk_at_index(&descriptor, 4), - }, - ), - ( - OutPoint::new(hash!("REDB"), 0), - TxOut { - value: Amount::from_sat(1400), - script_pubkey: spk_at_index(&descriptor, 10), - }, - ), - ] - .into(), - anchors: [(conf_anchor, tx1.compute_txid())].into(), - last_seen: [(tx1.compute_txid(), 1755317760)].into(), - first_seen: [(tx1.compute_txid(), 1755317750)].into(), - last_evicted: [(tx1.compute_txid(), 1755317760)].into(), - }; - - let keychain_txout_changeset = keychain_txout::ChangeSet { - last_revealed: [ - (descriptor.descriptor_id(), 12), - (change_descriptor.descriptor_id(), 10), - ] - .into(), - spk_cache: [ - ( - descriptor.descriptor_id(), - SpkIterator::new_with_range(&descriptor, 0..=37).collect(), - ), - ( - change_descriptor.descriptor_id(), - SpkIterator::new_with_range(&change_descriptor, 0..=35).collect(), - ), - ] - .into(), - }; - - let mut changeset = ChangeSet { - descriptor: Some(descriptor.clone()), - change_descriptor: Some(change_descriptor.clone()), - network: Some(Network::Testnet), - local_chain: local_chain_changeset, - tx_graph: tx_graph_changeset, - indexer: keychain_txout_changeset, - }; - - // persist and load - WalletPersister::persist(&mut store, &changeset).expect("changeset should get persisted"); - - let changeset_read = - WalletPersister::initialize(&mut store).expect("changeset should get loaded"); - - assert_eq!(changeset, changeset_read); - - // create another changeset - let local_chain_changeset = local_chain::ChangeSet { - blocks: [(910236, Some(hash!("BDK")))].into(), - }; - - let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime { - block_id: block_id!(910236, "BDK"), - confirmation_time: 1755317760, - }; - - let tx_graph_changeset = tx_graph::ChangeSet:: { - txs: [tx2.clone()].into(), - txouts: [( - OutPoint::new(hash!("Bitcoin_fixes_things"), 0), - TxOut { - value: Amount::from_sat(10000), - script_pubkey: spk_at_index(&descriptor, 21), - }, - )] - .into(), - anchors: [(conf_anchor, tx2.compute_txid())].into(), - last_seen: [(tx2.compute_txid(), 1755317700)].into(), - first_seen: [(tx2.compute_txid(), 1755317700)].into(), - last_evicted: [(tx2.compute_txid(), 1755317760)].into(), - }; - - let keychain_txout_changeset = keychain_txout::ChangeSet { - last_revealed: [(descriptor.descriptor_id(), 14)].into(), - spk_cache: [( - descriptor.descriptor_id(), - SpkIterator::new_with_range(&descriptor, 37..=39).collect(), - )] - .into(), - }; - - let changeset_new = ChangeSet { - descriptor: None, - change_descriptor: None, - network: None, - local_chain: local_chain_changeset, - tx_graph: tx_graph_changeset, - indexer: keychain_txout_changeset, - }; - - // persist, load and check if same as merged - WalletPersister::persist(&mut store, &changeset_new).expect("changeset should get persisted"); - let changeset_read_new = WalletPersister::initialize(&mut store).unwrap(); - - changeset.merge(changeset_new); - - assert_eq!(changeset, changeset_read_new); -} - -/// tests if multiple [`Wallet`]s can be persisted in a single file correctly -/// -/// [`Wallet`]: -/// [`ChangeSet`]: -/// -/// We create a dummy [`ChangeSet`] for first wallet and persist it then we create a dummy -/// [`ChangeSet`] for second wallet and persist that. Finally we load these two [`ChangeSet`]s and -/// check if they were persisted correctly. -pub fn persist_multiple_wallet_changesets( - filename: &str, - create_dbs: CreateStores, -) where - CreateStores: Fn(&Path) -> anyhow::Result<(Store, Store)>, - Store: WalletPersister, - Store::Error: Debug, -{ - // create stores - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - - let (mut store_first, mut store_sec) = - create_dbs(&file_path).expect("store should get created"); - - // initialize first store - let changeset = - WalletPersister::initialize(&mut store_first).expect("should load empty changeset"); - assert_eq!(changeset, ChangeSet::default()); - - // create first changeset - let descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); - let change_descriptor: Descriptor = DESCRIPTORS[1].parse().unwrap(); - - let changeset1 = ChangeSet { - descriptor: Some(descriptor.clone()), - change_descriptor: Some(change_descriptor.clone()), - network: Some(Network::Testnet), - ..ChangeSet::default() - }; - - // persist first changeset - WalletPersister::persist(&mut store_first, &changeset1).expect("should persist changeset"); - - // initialize second store - let changeset = - WalletPersister::initialize(&mut store_sec).expect("should load empty changeset"); - assert_eq!(changeset, ChangeSet::default()); - - // create second changeset - let descriptor: Descriptor = DESCRIPTORS[2].parse().unwrap(); - let change_descriptor: Descriptor = DESCRIPTORS[3].parse().unwrap(); - - let changeset2 = ChangeSet { - descriptor: Some(descriptor.clone()), - change_descriptor: Some(change_descriptor.clone()), - network: Some(Network::Testnet), - ..ChangeSet::default() - }; - - // persist second changeset - WalletPersister::persist(&mut store_sec, &changeset2).expect("should persist changeset"); - - // load first changeset - let changeset_read = - WalletPersister::initialize(&mut store_first).expect("should load persisted changeset1"); - assert_eq!(changeset_read, changeset1); - - // load second changeset - let changeset_read = - WalletPersister::initialize(&mut store_sec).expect("should load persisted changeset2"); - assert_eq!(changeset_read, changeset2); -} - -/// tests if [`Network`] is being persisted correctly -/// -/// [`Network`]: -/// [`ChangeSet`]: -/// -/// We create a dummy [`ChangeSet`] with only network field populated, persist it and check if -/// loaded [`ChangeSet`] has the same [`Network`] as what we persisted. -pub fn persist_network(filename: &str, create_store: CreateStore) -where - CreateStore: Fn(&Path) -> anyhow::Result, - Store: WalletPersister, - Store::Error: Debug, -{ - // create store - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - let mut store = create_store(&file_path).expect("store should get created"); - - // initialize store - let changeset = WalletPersister::initialize(&mut store) - .expect("should initialize and load empty changeset"); - assert_eq!(changeset, ChangeSet::default()); - - // persist the network - let changeset = ChangeSet { - network: Some(Network::Bitcoin), - ..ChangeSet::default() - }; - WalletPersister::persist(&mut store, &changeset).expect("should persist changeset"); - - // read the persisted network - let changeset_read = - WalletPersister::initialize(&mut store).expect("should load persisted changeset"); - - assert_eq!(changeset_read.network, Some(Network::Bitcoin)); -} - -/// tests if descriptors are being persisted correctly -/// -/// [`ChangeSet`]: -/// -/// We create a dummy [`ChangeSet`] with only descriptor fields populated, persist it and check if -/// loaded [`ChangeSet`] has the same descriptors as what we persisted. -pub fn persist_keychains(filename: &str, create_store: CreateStore) -where - CreateStore: Fn(&Path) -> anyhow::Result, - Store: WalletPersister, - Store::Error: Debug, -{ - // create store - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - let mut store = create_store(&file_path).expect("store should get created"); - - // initialize store - let changeset = WalletPersister::initialize(&mut store) - .expect("should initialize and load empty changeset"); - assert_eq!(changeset, ChangeSet::default()); - - // persist the descriptors - let descriptor: Descriptor = DESCRIPTORS[1].parse().unwrap(); - let change_descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); - - let changeset = ChangeSet { - descriptor: Some(descriptor.clone()), - change_descriptor: Some(change_descriptor.clone()), - ..ChangeSet::default() - }; - - WalletPersister::persist(&mut store, &changeset).expect("should persist descriptors"); - - // load the descriptors - let changeset_read = - WalletPersister::initialize(&mut store).expect("should read persisted changeset"); - - assert_eq!(changeset_read.descriptor.unwrap(), descriptor); - assert_eq!(changeset_read.change_descriptor.unwrap(), change_descriptor); -} - -/// tests if descriptor(in a single keychain wallet) is being persisted correctly -/// -/// [`ChangeSet`]: -/// -/// We create a dummy [`ChangeSet`] with only descriptor field populated, persist it and check if -/// loaded [`ChangeSet`] has the same descriptor as what we persisted. -pub fn persist_single_keychain(filename: &str, create_store: CreateStore) -where - CreateStore: Fn(&Path) -> anyhow::Result, - Store: WalletPersister, - Store::Error: Debug, -{ - // create store - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - let mut store = create_store(&file_path).expect("store should get created"); - - // initialize store - let changeset = WalletPersister::initialize(&mut store) - .expect("should initialize and load empty changeset"); - assert_eq!(changeset, ChangeSet::default()); - - // persist descriptor - let descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); - - let changeset = ChangeSet { - descriptor: Some(descriptor.clone()), - ..ChangeSet::default() - }; - - WalletPersister::persist(&mut store, &changeset).expect("should persist descriptors"); - - // load the descriptor - let changeset_read = - WalletPersister::initialize(&mut store).expect("should read persisted changeset"); - - assert_eq!(changeset_read.descriptor.unwrap(), descriptor); - assert_eq!(changeset_read.change_descriptor, None); -} +// /// tests if [`Wallet`] is being persisted correctly +// /// +// /// [`Wallet`]: +// /// [`ChangeSet`]: +// /// +// /// We create a dummy [`ChangeSet`], persist it and check if loaded [`ChangeSet`] matches +// /// the persisted one. We then create another such dummy [`ChangeSet`], persist it and load it to +// /// check if merged [`ChangeSet`] is returned. +// pub fn persist_wallet_changeset(filename: &str, create_store: CreateStore) +// where +// CreateStore: Fn(&Path) -> anyhow::Result, +// Store: WalletPersister, +// Store::Error: Debug, +// { +// // create store +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); +// let mut store = create_store(&file_path).expect("store should get created"); + +// // initialize store +// let changeset = +// WalletPersister::initialize(&mut store).expect("empty changeset should get loaded"); +// assert_eq!(changeset, ChangeSet::default()); + +// // create changeset +// let descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); +// let change_descriptor: Descriptor = DESCRIPTORS[1].parse().unwrap(); + +// let local_chain_changeset = local_chain::ChangeSet { +// blocks: [ +// (910234, Some(hash!("B"))), +// (910233, Some(hash!("T"))), +// (910235, Some(hash!("C"))), +// ] +// .into(), +// }; + +// let tx1 = Arc::new(create_one_inp_one_out_tx( +// hash!("We_are_all_Satoshi"), +// 30_000, +// )); +// let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000)); + +// let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime { +// block_id: block_id!(910234, "B"), +// confirmation_time: 1755317160, +// }; + +// let tx_graph_changeset = tx_graph::ChangeSet:: { +// txs: [tx1.clone()].into(), +// txouts: [ +// ( +// OutPoint::new(hash!("Rust"), 0), +// TxOut { +// value: Amount::from_sat(1300), +// script_pubkey: spk_at_index(&descriptor, 4), +// }, +// ), +// ( +// OutPoint::new(hash!("REDB"), 0), +// TxOut { +// value: Amount::from_sat(1400), +// script_pubkey: spk_at_index(&descriptor, 10), +// }, +// ), +// ] +// .into(), +// anchors: [(conf_anchor, tx1.compute_txid())].into(), +// last_seen: [(tx1.compute_txid(), 1755317760)].into(), +// first_seen: [(tx1.compute_txid(), 1755317750)].into(), +// last_evicted: [(tx1.compute_txid(), 1755317760)].into(), +// }; + +// let keychain_txout_changeset = keychain_txout::ChangeSet { +// last_revealed: [ +// (descriptor.descriptor_id(), 12), +// (change_descriptor.descriptor_id(), 10), +// ] +// .into(), +// spk_cache: [ +// ( +// descriptor.descriptor_id(), +// SpkIterator::new_with_range(&descriptor, 0..=37).collect(), +// ), +// ( +// change_descriptor.descriptor_id(), +// SpkIterator::new_with_range(&change_descriptor, 0..=35).collect(), +// ), +// ] +// .into(), +// }; + +// let mut changeset = ChangeSet { +// descriptor: Some(descriptor.clone()), +// change_descriptor: Some(change_descriptor.clone()), +// network: Some(Network::Testnet), +// local_chain: local_chain_changeset, +// tx_graph: tx_graph_changeset, +// indexer: keychain_txout_changeset, +// }; + +// // persist and load +// WalletPersister::persist(&mut store, &changeset).expect("changeset should get persisted"); + +// let changeset_read = +// WalletPersister::initialize(&mut store).expect("changeset should get loaded"); + +// assert_eq!(changeset, changeset_read); + +// // create another changeset +// let local_chain_changeset = local_chain::ChangeSet { +// blocks: [(910236, Some(hash!("BDK")))].into(), +// }; + +// let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime { +// block_id: block_id!(910236, "BDK"), +// confirmation_time: 1755317760, +// }; + +// let tx_graph_changeset = tx_graph::ChangeSet:: { +// txs: [tx2.clone()].into(), +// txouts: [( +// OutPoint::new(hash!("Bitcoin_fixes_things"), 0), +// TxOut { +// value: Amount::from_sat(10000), +// script_pubkey: spk_at_index(&descriptor, 21), +// }, +// )] +// .into(), +// anchors: [(conf_anchor, tx2.compute_txid())].into(), +// last_seen: [(tx2.compute_txid(), 1755317700)].into(), +// first_seen: [(tx2.compute_txid(), 1755317700)].into(), +// last_evicted: [(tx2.compute_txid(), 1755317760)].into(), +// }; + +// let keychain_txout_changeset = keychain_txout::ChangeSet { +// last_revealed: [(descriptor.descriptor_id(), 14)].into(), +// spk_cache: [( +// descriptor.descriptor_id(), +// SpkIterator::new_with_range(&descriptor, 37..=39).collect(), +// )] +// .into(), +// }; + +// let changeset_new = ChangeSet { +// descriptor: None, +// change_descriptor: None, +// network: None, +// local_chain: local_chain_changeset, +// tx_graph: tx_graph_changeset, +// indexer: keychain_txout_changeset, +// }; + +// // persist, load and check if same as merged +// WalletPersister::persist(&mut store, &changeset_new).expect("changeset should get +// persisted"); let changeset_read_new = WalletPersister::initialize(&mut store).unwrap(); + +// changeset.merge(changeset_new); + +// assert_eq!(changeset, changeset_read_new); +// } + +// /// tests if multiple [`Wallet`]s can be persisted in a single file correctly +// /// +// /// [`Wallet`]: +// /// [`ChangeSet`]: +// /// +// /// We create a dummy [`ChangeSet`] for first wallet and persist it then we create a dummy +// /// [`ChangeSet`] for second wallet and persist that. Finally we load these two [`ChangeSet`]s +// and /// check if they were persisted correctly. +// pub fn persist_multiple_wallet_changesets( +// filename: &str, +// create_dbs: CreateStores, +// ) where +// CreateStores: Fn(&Path) -> anyhow::Result<(Store, Store)>, +// Store: WalletPersister, +// Store::Error: Debug, +// { +// // create stores +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); + +// let (mut store_first, mut store_sec) = +// create_dbs(&file_path).expect("store should get created"); + +// // initialize first store +// let changeset = +// WalletPersister::initialize(&mut store_first).expect("should load empty changeset"); +// assert_eq!(changeset, ChangeSet::default()); + +// // create first changeset +// let descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); +// let change_descriptor: Descriptor = DESCRIPTORS[1].parse().unwrap(); + +// let changeset1 = ChangeSet { +// descriptor: Some(descriptor.clone()), +// change_descriptor: Some(change_descriptor.clone()), +// network: Some(Network::Testnet), +// ..ChangeSet::default() +// }; + +// // persist first changeset +// WalletPersister::persist(&mut store_first, &changeset1).expect("should persist changeset"); + +// // initialize second store +// let changeset = +// WalletPersister::initialize(&mut store_sec).expect("should load empty changeset"); +// assert_eq!(changeset, ChangeSet::default()); + +// // create second changeset +// let descriptor: Descriptor = DESCRIPTORS[2].parse().unwrap(); +// let change_descriptor: Descriptor = DESCRIPTORS[3].parse().unwrap(); + +// let changeset2 = ChangeSet { +// descriptor: Some(descriptor.clone()), +// change_descriptor: Some(change_descriptor.clone()), +// network: Some(Network::Testnet), +// ..ChangeSet::default() +// }; + +// // persist second changeset +// WalletPersister::persist(&mut store_sec, &changeset2).expect("should persist changeset"); + +// // load first changeset +// let changeset_read = +// WalletPersister::initialize(&mut store_first).expect("should load persisted changeset1"); +// assert_eq!(changeset_read, changeset1); + +// // load second changeset +// let changeset_read = +// WalletPersister::initialize(&mut store_sec).expect("should load persisted changeset2"); +// assert_eq!(changeset_read, changeset2); +// } + +// /// tests if [`Network`] is being persisted correctly +// /// +// /// [`Network`]: +// /// [`ChangeSet`]: +// /// +// /// We create a dummy [`ChangeSet`] with only network field populated, persist it and check if +// /// loaded [`ChangeSet`] has the same [`Network`] as what we persisted. +// pub fn persist_network(filename: &str, create_store: CreateStore) +// where +// CreateStore: Fn(&Path) -> anyhow::Result, +// Store: WalletPersister, +// Store::Error: Debug, +// { +// // create store +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); +// let mut store = create_store(&file_path).expect("store should get created"); + +// // initialize store +// let changeset = WalletPersister::initialize(&mut store) +// .expect("should initialize and load empty changeset"); +// assert_eq!(changeset, ChangeSet::default()); + +// // persist the network +// let changeset = ChangeSet { +// network: Some(Network::Bitcoin), +// ..ChangeSet::default() +// }; +// WalletPersister::persist(&mut store, &changeset).expect("should persist changeset"); + +// // read the persisted network +// let changeset_read = +// WalletPersister::initialize(&mut store).expect("should load persisted changeset"); + +// assert_eq!(changeset_read.network, Some(Network::Bitcoin)); +// } + +// /// tests if descriptors are being persisted correctly +// /// +// /// [`ChangeSet`]: +// /// +// /// We create a dummy [`ChangeSet`] with only descriptor fields populated, persist it and check +// if /// loaded [`ChangeSet`] has the same descriptors as what we persisted. +// pub fn persist_keychains(filename: &str, create_store: CreateStore) +// where +// CreateStore: Fn(&Path) -> anyhow::Result, +// Store: WalletPersister, +// Store::Error: Debug, +// { +// // create store +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); +// let mut store = create_store(&file_path).expect("store should get created"); + +// // initialize store +// let changeset = WalletPersister::initialize(&mut store) +// .expect("should initialize and load empty changeset"); +// assert_eq!(changeset, ChangeSet::default()); + +// // persist the descriptors +// let descriptor: Descriptor = DESCRIPTORS[1].parse().unwrap(); +// let change_descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); + +// let changeset = ChangeSet { +// descriptor: Some(descriptor.clone()), +// change_descriptor: Some(change_descriptor.clone()), +// ..ChangeSet::default() +// }; + +// WalletPersister::persist(&mut store, &changeset).expect("should persist descriptors"); + +// // load the descriptors +// let changeset_read = +// WalletPersister::initialize(&mut store).expect("should read persisted changeset"); + +// assert_eq!(changeset_read.descriptor.unwrap(), descriptor); +// assert_eq!(changeset_read.change_descriptor.unwrap(), change_descriptor); +// } + +// /// tests if descriptor(in a single keychain wallet) is being persisted correctly +// /// +// /// [`ChangeSet`]: +// /// +// /// We create a dummy [`ChangeSet`] with only descriptor field populated, persist it and check if +// /// loaded [`ChangeSet`] has the same descriptor as what we persisted. +// pub fn persist_single_keychain(filename: &str, create_store: CreateStore) +// where +// CreateStore: Fn(&Path) -> anyhow::Result, +// Store: WalletPersister, +// Store::Error: Debug, +// { +// // create store +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); +// let mut store = create_store(&file_path).expect("store should get created"); + +// // initialize store +// let changeset = WalletPersister::initialize(&mut store) +// .expect("should initialize and load empty changeset"); +// assert_eq!(changeset, ChangeSet::default()); + +// // persist descriptor +// let descriptor: Descriptor = DESCRIPTORS[0].parse().unwrap(); + +// let changeset = ChangeSet { +// descriptor: Some(descriptor.clone()), +// ..ChangeSet::default() +// }; + +// WalletPersister::persist(&mut store, &changeset).expect("should persist descriptors"); + +// // load the descriptor +// let changeset_read = +// WalletPersister::initialize(&mut store).expect("should read persisted changeset"); + +// assert_eq!(changeset_read.descriptor.unwrap(), descriptor); +// assert_eq!(changeset_read.change_descriptor, None); +// } diff --git a/src/test_utils.rs b/src/test_utils.rs index 11fd13b1..1eec4760 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,5 +1,5 @@ //! `bdk_wallet` test utilities - +#![allow(unused)] use alloc::string::ToString; use alloc::sync::Arc; use core::str::FromStr; @@ -12,127 +12,127 @@ use bitcoin::{ use crate::{KeychainKind, Update, Wallet}; -/// Return a fake wallet that appears to be funded for testing. -/// -/// 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. The remaining 1000 -/// sats are the transaction fee. -pub fn get_funded_wallet(descriptor: &str, change_descriptor: &str) -> (Wallet, Txid) { - new_funded_wallet(descriptor, Some(change_descriptor)) -} - -fn new_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wallet, Txid) { - let params = if let Some(change_desc) = change_descriptor { - Wallet::create(descriptor.to_string(), change_desc.to_string()) - } else { - Wallet::create_single(descriptor.to_string()) - }; - - let mut wallet = params - .network(Network::Regtest) - .create_wallet_no_persist() - .expect("descriptors must be valid"); - - let receive_address = wallet.peek_address(KeychainKind::External, 0).address; - let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") - .expect("address") - .require_network(Network::Regtest) - .unwrap(); - - let tx0 = Transaction { - output: vec![TxOut { - value: Amount::from_sat(76_000), - script_pubkey: receive_address.script_pubkey(), - }], - ..new_tx(0) - }; - - let tx1 = Transaction { - input: vec![TxIn { - previous_output: OutPoint { - txid: tx0.compute_txid(), - vout: 0, - }, - ..Default::default() - }], - output: vec![ - TxOut { - value: Amount::from_sat(50_000), - script_pubkey: receive_address.script_pubkey(), - }, - TxOut { - value: Amount::from_sat(25_000), - script_pubkey: sendto_address.script_pubkey(), - }, - ], - ..new_tx(0) - }; - - insert_checkpoint( - &mut wallet, - BlockId { - height: 42, - hash: BlockHash::all_zeros(), - }, - ); - insert_checkpoint( - &mut wallet, - BlockId { - height: 1_000, - hash: BlockHash::all_zeros(), - }, - ); - insert_checkpoint( - &mut wallet, - BlockId { - height: 2_000, - hash: BlockHash::all_zeros(), - }, - ); - - insert_tx(&mut wallet, tx0.clone()); - insert_anchor( - &mut wallet, - tx0.compute_txid(), - ConfirmationBlockTime { - block_id: BlockId { - height: 1_000, - hash: BlockHash::all_zeros(), - }, - confirmation_time: 100, - }, - ); - - insert_tx(&mut wallet, tx1.clone()); - insert_anchor( - &mut wallet, - tx1.compute_txid(), - ConfirmationBlockTime { - block_id: BlockId { - height: 2_000, - hash: BlockHash::all_zeros(), - }, - confirmation_time: 200, - }, - ); - - (wallet, tx1.compute_txid()) -} - -/// Return a fake wallet that appears to be funded for testing. -/// -/// 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. The remaining 1000 -/// sats are the transaction fee. -pub fn get_funded_wallet_single(descriptor: &str) -> (Wallet, Txid) { - new_funded_wallet(descriptor, None) -} - -/// Get funded segwit wallet -pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) { - let (desc, change_desc) = get_test_wpkh_and_change_desc(); - get_funded_wallet(desc, change_desc) -} +// /// Return a fake wallet that appears to be funded for testing. +// /// +// /// 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. The remaining 1000 +// /// sats are the transaction fee. +// pub fn get_funded_wallet(descriptor: &str, change_descriptor: &str) -> (Wallet, Txid) { +// new_funded_wallet(descriptor, Some(change_descriptor)) +// } + +// fn new_funded_wallet(descriptor: &str, change_descriptor: Option<&str>) -> (Wallet, Txid) { +// let params = if let Some(change_desc) = change_descriptor { +// Wallet::create(descriptor.to_string(), change_desc.to_string()) +// } else { +// Wallet::create_single(descriptor.to_string()) +// }; + +// let mut wallet = params +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .expect("descriptors must be valid"); + +// let receive_address = wallet.peek_address(KeychainKind::External, 0).address; +// let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") +// .expect("address") +// .require_network(Network::Regtest) +// .unwrap(); + +// let tx0 = Transaction { +// output: vec![TxOut { +// value: Amount::from_sat(76_000), +// script_pubkey: receive_address.script_pubkey(), +// }], +// ..new_tx(0) +// }; + +// let tx1 = Transaction { +// input: vec![TxIn { +// previous_output: OutPoint { +// txid: tx0.compute_txid(), +// vout: 0, +// }, +// ..Default::default() +// }], +// output: vec![ +// TxOut { +// value: Amount::from_sat(50_000), +// script_pubkey: receive_address.script_pubkey(), +// }, +// TxOut { +// value: Amount::from_sat(25_000), +// script_pubkey: sendto_address.script_pubkey(), +// }, +// ], +// ..new_tx(0) +// }; + +// insert_checkpoint( +// &mut wallet, +// BlockId { +// height: 42, +// hash: BlockHash::all_zeros(), +// }, +// ); +// insert_checkpoint( +// &mut wallet, +// BlockId { +// height: 1_000, +// hash: BlockHash::all_zeros(), +// }, +// ); +// insert_checkpoint( +// &mut wallet, +// BlockId { +// height: 2_000, +// hash: BlockHash::all_zeros(), +// }, +// ); + +// insert_tx(&mut wallet, tx0.clone()); +// insert_anchor( +// &mut wallet, +// tx0.compute_txid(), +// ConfirmationBlockTime { +// block_id: BlockId { +// height: 1_000, +// hash: BlockHash::all_zeros(), +// }, +// confirmation_time: 100, +// }, +// ); + +// insert_tx(&mut wallet, tx1.clone()); +// insert_anchor( +// &mut wallet, +// tx1.compute_txid(), +// ConfirmationBlockTime { +// block_id: BlockId { +// height: 2_000, +// hash: BlockHash::all_zeros(), +// }, +// confirmation_time: 200, +// }, +// ); + +// (wallet, tx1.compute_txid()) +// } + +// /// Return a fake wallet that appears to be funded for testing. +// /// +// /// 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. The remaining 1000 +// /// sats are the transaction fee. +// pub fn get_funded_wallet_single(descriptor: &str) -> (Wallet, Txid) { +// new_funded_wallet(descriptor, None) +// } + +// /// Get funded segwit wallet +// pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) { +// let (desc, change_desc) = get_test_wpkh_and_change_desc(); +// get_funded_wallet(desc, change_desc) +// } /// `pkh` single key descriptor pub fn get_test_pkh() -> &'static str { @@ -250,112 +250,112 @@ impl From for ReceiveTo { } } -/// Receive a tx output with the given value in the latest block -pub fn receive_output_in_latest_block(wallet: &mut Wallet, value: Amount) -> OutPoint { - let latest_cp = wallet.latest_checkpoint(); - let height = latest_cp.height(); - assert!(height > 0, "cannot receive tx into genesis block"); - receive_output( - wallet, - value, - ConfirmationBlockTime { - block_id: latest_cp.block_id(), - confirmation_time: 0, - }, - ) -} - -/// Receive a tx output with the given value and chain position -pub fn receive_output( - wallet: &mut Wallet, - value: Amount, - receive_to: impl Into, -) -> OutPoint { - let addr = wallet.next_unused_address(KeychainKind::External).address; - receive_output_to_address(wallet, addr, value, receive_to) -} - -/// Receive a tx output to an address with the given value and chain position -pub fn receive_output_to_address( - wallet: &mut Wallet, - addr: Address, - value: Amount, - receive_to: impl Into, -) -> OutPoint { - let tx = Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: addr.script_pubkey(), - value, - }], - }; - - let txid = tx.compute_txid(); - insert_tx(wallet, tx); - - match receive_to.into() { - ReceiveTo::Block(anchor) => insert_anchor(wallet, txid, anchor), - ReceiveTo::Mempool(last_seen) => insert_seen_at(wallet, txid, last_seen), - } - - OutPoint { txid, vout: 0 } -} - -/// Insert a checkpoint into the wallet. This can be used to extend the wallet's local chain -/// or to insert a block that did not exist previously. Note that if replacing a block with -/// a different one at the same height, then all later blocks are evicted as well. -pub fn insert_checkpoint(wallet: &mut Wallet, block: BlockId) { - let mut cp = wallet.latest_checkpoint(); - cp = cp.insert(block); - wallet - .apply_update(Update { - chain: Some(cp), - ..Default::default() - }) - .unwrap(); -} - -/// Inserts a transaction into the local view, assuming it is currently present in the mempool. -/// -/// This can be used, for example, to track a transaction immediately after it is broadcast. -pub fn insert_tx(wallet: &mut Wallet, tx: Transaction) { - let txid = tx.compute_txid(); - let seen_at = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); - let mut tx_update = TxUpdate::default(); - tx_update.txs = vec![Arc::new(tx)]; - tx_update.seen_ats = [(txid, seen_at)].into(); - wallet - .apply_update(Update { - tx_update, - ..Default::default() - }) - .expect("failed to apply update"); -} - -/// Simulates confirming a tx with `txid` by applying an update to the wallet containing -/// the given `anchor`. Note: to be considered confirmed the anchor block must exist in -/// the current active chain. -pub fn insert_anchor(wallet: &mut Wallet, txid: Txid, anchor: ConfirmationBlockTime) { - let mut tx_update = TxUpdate::default(); - tx_update.anchors = [(anchor, txid)].into(); - wallet - .apply_update(Update { - tx_update, - ..Default::default() - }) - .expect("failed to apply update"); -} - -/// Marks the given `txid` seen as unconfirmed at `seen_at` -pub fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) { - let mut tx_update = TxUpdate::default(); - tx_update.seen_ats = [(txid, seen_at)].into(); - wallet - .apply_update(Update { - tx_update, - ..Default::default() - }) - .expect("failed to apply update"); -} +// /// Receive a tx output with the given value in the latest block +// pub fn receive_output_in_latest_block(wallet: &mut Wallet, value: Amount) -> OutPoint { +// let latest_cp = wallet.latest_checkpoint(); +// let height = latest_cp.height(); +// assert!(height > 0, "cannot receive tx into genesis block"); +// receive_output( +// wallet, +// value, +// ConfirmationBlockTime { +// block_id: latest_cp.block_id(), +// confirmation_time: 0, +// }, +// ) +// } + +// /// Receive a tx output with the given value and chain position +// pub fn receive_output( +// wallet: &mut Wallet, +// value: Amount, +// receive_to: impl Into, +// ) -> OutPoint { +// let addr = wallet.next_unused_address(KeychainKind::External).address; +// receive_output_to_address(wallet, addr, value, receive_to) +// } + +// /// Receive a tx output to an address with the given value and chain position +// pub fn receive_output_to_address( +// wallet: &mut Wallet, +// addr: Address, +// value: Amount, +// receive_to: impl Into, +// ) -> OutPoint { +// let tx = Transaction { +// version: transaction::Version::ONE, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// script_pubkey: addr.script_pubkey(), +// value, +// }], +// }; + +// let txid = tx.compute_txid(); +// insert_tx(wallet, tx); + +// match receive_to.into() { +// ReceiveTo::Block(anchor) => insert_anchor(wallet, txid, anchor), +// ReceiveTo::Mempool(last_seen) => insert_seen_at(wallet, txid, last_seen), +// } + +// OutPoint { txid, vout: 0 } +// } + +// /// Insert a checkpoint into the wallet. This can be used to extend the wallet's local chain +// /// or to insert a block that did not exist previously. Note that if replacing a block with +// /// a different one at the same height, then all later blocks are evicted as well. +// pub fn insert_checkpoint(wallet: &mut Wallet, block: BlockId) { +// let mut cp = wallet.latest_checkpoint(); +// cp = cp.insert(block); +// wallet +// .apply_update(Update { +// chain: Some(cp), +// ..Default::default() +// }) +// .unwrap(); +// } + +// /// Inserts a transaction into the local view, assuming it is currently present in the mempool. +// /// +// /// This can be used, for example, to track a transaction immediately after it is broadcast. +// pub fn insert_tx(wallet: &mut Wallet, tx: Transaction) { +// let txid = tx.compute_txid(); +// let seen_at = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs(); +// let mut tx_update = TxUpdate::default(); +// tx_update.txs = vec![Arc::new(tx)]; +// tx_update.seen_ats = [(txid, seen_at)].into(); +// wallet +// .apply_update(Update { +// tx_update, +// ..Default::default() +// }) +// .expect("failed to apply update"); +// } + +// /// Simulates confirming a tx with `txid` by applying an update to the wallet containing +// /// the given `anchor`. Note: to be considered confirmed the anchor block must exist in +// /// the current active chain. +// pub fn insert_anchor(wallet: &mut Wallet, txid: Txid, anchor: ConfirmationBlockTime) { +// let mut tx_update = TxUpdate::default(); +// tx_update.anchors = [(anchor, txid)].into(); +// wallet +// .apply_update(Update { +// tx_update, +// ..Default::default() +// }) +// .expect("failed to apply update"); +// } + +// /// Marks the given `txid` seen as unconfirmed at `seen_at` +// pub fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) { +// let mut tx_update = TxUpdate::default(); +// tx_update.seen_ats = [(txid, seen_at)].into(); +// wallet +// .apply_update(Update { +// tx_update, +// ..Default::default() +// }) +// .expect("failed to apply update"); +// } diff --git a/src/wallet/changeset.rs b/src/wallet/changeset.rs index ebfdb9fb..b1498484 100644 --- a/src/wallet/changeset.rs +++ b/src/wallet/changeset.rs @@ -1,3 +1,4 @@ +use alloc::collections::btree_map::BTreeMap; use bdk_chain::{ indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge, }; @@ -157,123 +158,123 @@ impl Merge for ChangeSet { } } -#[cfg(feature = "rusqlite")] -impl ChangeSet { - /// Schema name for wallet. - pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet"; - /// Name of table to store wallet descriptors and network. - pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet"; +// #[cfg(feature = "rusqlite")] +// impl ChangeSet { +// /// Schema name for wallet. +// pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet"; +// /// Name of table to store wallet descriptors and network. +// pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet"; - /// Get v0 sqlite [ChangeSet] schema - pub fn schema_v0() -> alloc::string::String { - format!( - "CREATE TABLE {} ( \ - id INTEGER PRIMARY KEY NOT NULL CHECK (id = 0), \ - descriptor TEXT, \ - change_descriptor TEXT, \ - network TEXT \ - ) STRICT;", - Self::WALLET_TABLE_NAME, - ) - } +// /// Get v0 sqlite [ChangeSet] schema +// pub fn schema_v0() -> alloc::string::String { +// format!( +// "CREATE TABLE {} ( \ +// id INTEGER PRIMARY KEY NOT NULL CHECK (id = 0), \ +// descriptor TEXT, \ +// change_descriptor TEXT, \ +// network TEXT \ +// ) STRICT;", +// Self::WALLET_TABLE_NAME, +// ) +// } - /// Initialize sqlite tables for wallet tables. - pub fn init_sqlite_tables(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<()> { - crate::rusqlite_impl::migrate_schema( - db_tx, - Self::WALLET_SCHEMA_NAME, - &[&Self::schema_v0()], - )?; +// /// Initialize sqlite tables for wallet tables. +// pub fn init_sqlite_tables(db_tx: &chain::rusqlite::Transaction) -> +// chain::rusqlite::Result<()> { crate::rusqlite_impl::migrate_schema( +// db_tx, +// Self::WALLET_SCHEMA_NAME, +// &[&Self::schema_v0()], +// )?; - bdk_chain::local_chain::ChangeSet::init_sqlite_tables(db_tx)?; - bdk_chain::tx_graph::ChangeSet::::init_sqlite_tables(db_tx)?; - bdk_chain::keychain_txout::ChangeSet::init_sqlite_tables(db_tx)?; +// bdk_chain::local_chain::ChangeSet::init_sqlite_tables(db_tx)?; +// bdk_chain::tx_graph::ChangeSet::::init_sqlite_tables(db_tx)?; +// bdk_chain::keychain_txout::ChangeSet::init_sqlite_tables(db_tx)?; - Ok(()) - } +// Ok(()) +// } - /// Recover a [`ChangeSet`] from sqlite database. - pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result { - use chain::rusqlite::OptionalExtension; - use chain::Impl; +// /// Recover a [`ChangeSet`] from sqlite database. +// pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result { +// use chain::rusqlite::OptionalExtension; +// use chain::Impl; - let mut changeset = Self::default(); +// let mut changeset = Self::default(); - let mut wallet_statement = db_tx.prepare(&format!( - "SELECT descriptor, change_descriptor, network FROM {}", - Self::WALLET_TABLE_NAME, - ))?; - let row = wallet_statement - .query_row([], |row| { - Ok(( - row.get::<_, Option>>>("descriptor")?, - row.get::<_, Option>>>( - "change_descriptor", - )?, - row.get::<_, Option>>("network")?, - )) - }) - .optional()?; - if let Some((desc, change_desc, network)) = row { - changeset.descriptor = desc.map(Impl::into_inner); - changeset.change_descriptor = change_desc.map(Impl::into_inner); - changeset.network = network.map(Impl::into_inner); - } +// let mut wallet_statement = db_tx.prepare(&format!( +// "SELECT descriptor, change_descriptor, network FROM {}", +// Self::WALLET_TABLE_NAME, +// ))?; +// let row = wallet_statement +// .query_row([], |row| { +// Ok(( +// row.get::<_, Option>>>("descriptor")?, +// row.get::<_, Option>>>( +// "change_descriptor", +// )?, +// row.get::<_, Option>>("network")?, +// )) +// }) +// .optional()?; +// if let Some((desc, change_desc, network)) = row { +// changeset.descriptor = desc.map(Impl::into_inner); +// changeset.change_descriptor = change_desc.map(Impl::into_inner); +// changeset.network = network.map(Impl::into_inner); +// } - changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?; - changeset.tx_graph = tx_graph::ChangeSet::<_>::from_sqlite(db_tx)?; - changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?; +// changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?; +// changeset.tx_graph = tx_graph::ChangeSet::<_>::from_sqlite(db_tx)?; +// changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?; - Ok(changeset) - } +// Ok(changeset) +// } - /// Persist [`ChangeSet`] to sqlite database. - pub fn persist_to_sqlite( - &self, - db_tx: &chain::rusqlite::Transaction, - ) -> chain::rusqlite::Result<()> { - use chain::rusqlite::named_params; - use chain::Impl; +// /// Persist [`ChangeSet`] to sqlite database. +// pub fn persist_to_sqlite( +// &self, +// db_tx: &chain::rusqlite::Transaction, +// ) -> chain::rusqlite::Result<()> { +// use chain::rusqlite::named_params; +// use chain::Impl; - let mut descriptor_statement = db_tx.prepare_cached(&format!( - "INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE SET descriptor=:descriptor", - Self::WALLET_TABLE_NAME, - ))?; - if let Some(descriptor) = &self.descriptor { - descriptor_statement.execute(named_params! { - ":id": 0, - ":descriptor": Impl(descriptor.clone()), - })?; - } +// let mut descriptor_statement = db_tx.prepare_cached(&format!( +// "INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE +// SET descriptor=:descriptor", Self::WALLET_TABLE_NAME, +// ))?; +// if let Some(descriptor) = &self.descriptor { +// descriptor_statement.execute(named_params! { +// ":id": 0, +// ":descriptor": Impl(descriptor.clone()), +// })?; +// } - let mut change_descriptor_statement = db_tx.prepare_cached(&format!( - "INSERT INTO {}(id, change_descriptor) VALUES(:id, :change_descriptor) ON CONFLICT(id) DO UPDATE SET change_descriptor=:change_descriptor", - Self::WALLET_TABLE_NAME, - ))?; - if let Some(change_descriptor) = &self.change_descriptor { - change_descriptor_statement.execute(named_params! { - ":id": 0, - ":change_descriptor": Impl(change_descriptor.clone()), - })?; - } +// let mut change_descriptor_statement = db_tx.prepare_cached(&format!( +// "INSERT INTO {}(id, change_descriptor) VALUES(:id, :change_descriptor) ON +// CONFLICT(id) DO UPDATE SET change_descriptor=:change_descriptor", +// Self::WALLET_TABLE_NAME, ))?; +// if let Some(change_descriptor) = &self.change_descriptor { +// change_descriptor_statement.execute(named_params! { +// ":id": 0, +// ":change_descriptor": Impl(change_descriptor.clone()), +// })?; +// } - let mut network_statement = db_tx.prepare_cached(&format!( - "INSERT INTO {}(id, network) VALUES(:id, :network) ON CONFLICT(id) DO UPDATE SET network=:network", - Self::WALLET_TABLE_NAME, - ))?; - if let Some(network) = self.network { - network_statement.execute(named_params! { - ":id": 0, - ":network": Impl(network), - })?; - } +// let mut network_statement = db_tx.prepare_cached(&format!( +// "INSERT INTO {}(id, network) VALUES(:id, :network) ON CONFLICT(id) DO UPDATE SET +// network=:network", Self::WALLET_TABLE_NAME, +// ))?; +// if let Some(network) = self.network { +// network_statement.execute(named_params! { +// ":id": 0, +// ":network": Impl(network), +// })?; +// } - self.local_chain.persist_to_sqlite(db_tx)?; - self.tx_graph.persist_to_sqlite(db_tx)?; - self.indexer.persist_to_sqlite(db_tx)?; - Ok(()) - } -} +// self.local_chain.persist_to_sqlite(db_tx)?; +// self.tx_graph.persist_to_sqlite(db_tx)?; +// self.indexer.persist_to_sqlite(db_tx)?; +// Ok(()) +// } +// } impl From for ChangeSet { fn from(chain: local_chain::ChangeSet) -> Self { diff --git a/src/wallet/coin_selection.rs b/src/wallet/coin_selection.rs index fa032fbd..4336ced3 100644 --- a/src/wallet/coin_selection.rs +++ b/src/wallet/coin_selection.rs @@ -20,86 +20,85 @@ //! //! [`TxBuilder`]: super::tx_builder::TxBuilder //! [`coin_selection`]: super::tx_builder::TxBuilder::coin_selection -//! -//! ## Example -//! -//! ``` -//! # use std::str::FromStr; -//! # use bitcoin::*; -//! # use bdk_wallet::{self, ChangeSet, coin_selection::*, coin_selection}; -//! # use bdk_wallet::error::CreateTxError; -//! # use bdk_wallet::*; -//! # use bdk_wallet::coin_selection::decide_change; -//! # use anyhow::Error; -//! # use rand_core::RngCore; -//! #[derive(Debug)] -//! struct AlwaysSpendEverything; -//! -//! impl CoinSelectionAlgorithm for AlwaysSpendEverything { -//! fn coin_select( -//! &self, -//! required_utxos: Vec, -//! optional_utxos: Vec, -//! fee_rate: FeeRate, -//! target_amount: Amount, -//! drain_script: &Script, -//! rand: &mut R, -//! ) -> Result { -//! let mut selected_amount = Amount::ZERO; -//! let mut additional_weight = Weight::ZERO; -//! let all_utxos_selected = required_utxos -//! .into_iter() -//! .chain(optional_utxos) -//! .scan( -//! (&mut selected_amount, &mut additional_weight), -//! |(selected_amount, additional_weight), weighted_utxo| { -//! **selected_amount += weighted_utxo.utxo.txout().value; -//! **additional_weight += TxIn::default() -//! .segwit_weight() -//! .checked_add(weighted_utxo.satisfaction_weight) -//! .expect("`Weight` addition should not cause an integer overflow"); -//! Some(weighted_utxo.utxo) -//! }, -//! ) -//! .collect::>(); -//! let additional_fees = fee_rate * additional_weight; -//! let amount_needed_with_fees = additional_fees + target_amount; -//! if selected_amount < amount_needed_with_fees { -//! return Err(coin_selection::InsufficientFunds { -//! needed: amount_needed_with_fees, -//! available: selected_amount, -//! }); -//! } -//! -//! let remaining_amount = selected_amount - amount_needed_with_fees; -//! -//! let excess = decide_change(remaining_amount, fee_rate, drain_script); -//! -//! Ok(CoinSelectionResult { -//! selected: all_utxos_selected, -//! fee_amount: additional_fees, -//! excess, -//! }) -//! } -//! } -//! -//! # let mut wallet = doctest_wallet!(); -//! // create wallet, sync, ... -//! -//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") -//! .unwrap() -//! .require_network(Network::Testnet) -//! .unwrap(); -//! let psbt = { -//! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything); -//! builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); -//! builder.finish()? -//! }; -//! -//! // inspect, sign, broadcast, ... -//! -//! # Ok::<(), anyhow::Error>(()) -//! ``` +// //! ## Example +// //! +// //! ``` +// //! # use std::str::FromStr; +// //! # use bitcoin::*; +// //! # use bdk_wallet::{self, ChangeSet, coin_selection::*, coin_selection}; +// //! # use bdk_wallet::error::CreateTxError; +// //! # use bdk_wallet::*; +// //! # use bdk_wallet::coin_selection::decide_change; +// //! # use anyhow::Error; +// //! # use rand_core::RngCore; +// //! #[derive(Debug)] +// //! struct AlwaysSpendEverything; +// //! +// //! impl CoinSelectionAlgorithm for AlwaysSpendEverything { +// //! fn coin_select( +// //! &self, +// //! required_utxos: Vec, +// //! optional_utxos: Vec, +// //! fee_rate: FeeRate, +// //! target_amount: Amount, +// //! drain_script: &Script, +// //! rand: &mut R, +// //! ) -> Result { +// //! let mut selected_amount = Amount::ZERO; +// //! let mut additional_weight = Weight::ZERO; +// //! let all_utxos_selected = required_utxos +// //! .into_iter() +// //! .chain(optional_utxos) +// //! .scan( +// //! (&mut selected_amount, &mut additional_weight), +// //! |(selected_amount, additional_weight), weighted_utxo| { +// //! **selected_amount += weighted_utxo.utxo.txout().value; +// //! **additional_weight += TxIn::default() +// //! .segwit_weight() +// //! .checked_add(weighted_utxo.satisfaction_weight) +// //! .expect("`Weight` addition should not cause an integer overflow"); +// //! Some(weighted_utxo.utxo) +// //! }, +// //! ) +// //! .collect::>(); +// //! let additional_fees = fee_rate * additional_weight; +// //! let amount_needed_with_fees = additional_fees + target_amount; +// //! if selected_amount < amount_needed_with_fees { +// //! return Err(coin_selection::InsufficientFunds { +// //! needed: amount_needed_with_fees, +// //! available: selected_amount, +// //! }); +// //! } +// //! +// //! let remaining_amount = selected_amount - amount_needed_with_fees; +// //! +// //! let excess = decide_change(remaining_amount, fee_rate, drain_script); +// //! +// //! Ok(CoinSelectionResult { +// //! selected: all_utxos_selected, +// //! fee_amount: additional_fees, +// //! excess, +// //! }) +// //! } +// //! } +// //! +// //! # let mut wallet = doctest_wallet!(); +// //! // create wallet, sync, ... +// //! +// //! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") +// //! .unwrap() +// //! .require_network(Network::Testnet) +// //! .unwrap(); +// //! let psbt = { +// //! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything); +// //! builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); +// //! builder.finish()? +// //! }; +// //! +// //! // inspect, sign, broadcast, ... +// //! +// //! # Ok::<(), anyhow::Error>(()) +// //! ``` use crate::wallet::utils::IsDust; use crate::Utxo; diff --git a/src/wallet/export.rs b/src/wallet/export.rs index c07bdf48..68351e63 100644 --- a/src/wallet/export.rs +++ b/src/wallet/export.rs @@ -12,48 +12,51 @@ //! Wallet export //! //! This modules implements the wallet export format used by [FullyNoded](https://github.com/Fonta1n3/FullyNoded/blob/10b7808c8b929b171cca537fb50522d015168ac9/Docs/Wallets/Wallet-Export-Spec.md). -//! -//! ## Examples -//! -//! ### Import from JSON -//! -//! ``` -//! # use std::str::FromStr; -//! # use bitcoin::*; -//! # use bdk_wallet::export::*; -//! # use bdk_wallet::*; -//! let import = r#"{ -//! "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)", -//! "blockheight":1782088, -//! "label":"testnet" -//! }"#; -//! -//! let import = FullyNodedExport::from_str(import)?; -//! let wallet = Wallet::create( -//! import.descriptor(), -//! import.change_descriptor().expect("change descriptor"), -//! ) -//! .network(Network::Testnet) -//! .create_wallet_no_persist()?; -//! # Ok::<_, Box>(()) -//! ``` -//! -//! ### Export a `Wallet` -//! ``` -//! # use bitcoin::*; -//! # use bdk_wallet::export::*; -//! # use bdk_wallet::*; -//! let wallet = Wallet::create( -//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)", -//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)", -//! ) -//! .network(Network::Testnet) -//! .create_wallet_no_persist()?; -//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap(); -//! -//! println!("Exported: {}", export.to_string()); -//! # Ok::<_, Box>(()) -//! ``` +// //! ## Examples +// //! +// //! ### Import from JSON +// //! +// //! ``` +// //! # use std::str::FromStr; +// //! # use bitcoin::*; +// //! # use bdk_wallet::export::*; +// //! # use bdk_wallet::*; +// //! let import = r#"{ +// //! "descriptor": +// "wpkh([c258d2e4\/84h\/1h\/ +// 0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\ +// /0\/*)", //! "blockheight":1782088, +// //! "label":"testnet" +// //! }"#; +// //! +// //! let import = FullyNodedExport::from_str(import)?; +// //! let wallet = Wallet::create( +// //! import.descriptor(), +// //! import.change_descriptor().expect("change descriptor"), +// //! ) +// //! .network(Network::Testnet) +// //! .create_wallet_no_persist()?; +// //! # Ok::<_, Box>(()) +// //! ``` +// //! +// //! ### Export a `Wallet` +// //! ``` +// //! # use bitcoin::*; +// //! # use bdk_wallet::export::*; +// //! # use bdk_wallet::*; +// //! let wallet = Wallet::create( +// //! "wpkh([c258d2e4/84h/1h/ +// 0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/ +// 0/*)", //! "wpkh([c258d2e4/84h/1h/ +// 0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/ +// 1/*)", //! ) +// //! .network(Network::Testnet) +// //! .create_wallet_no_persist()?; +// //! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap(); +// //! +// //! println!("Exported: {}", export.to_string()); +// //! # Ok::<_, Box>(()) +// //! ``` use alloc::string::String; use core::fmt; @@ -101,66 +104,66 @@ fn remove_checksum(s: String) -> String { } impl FullyNodedExport { - /// Export a wallet - /// - /// This function returns an error if it determines that the `wallet`'s descriptor(s) are not - /// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44 - /// and others. - /// - /// If `include_blockheight` is `true`, this function will look into the `wallet`'s database - /// for the oldest transaction it knows and use that as the earliest block to rescan. - /// - /// If the database is empty or `include_blockheight` is false, the `blockheight` field - /// returned will be `0`. - pub fn export_wallet( - wallet: &Wallet, - label: &str, - include_blockheight: bool, - ) -> Result { - let descriptor = wallet - .public_descriptor(KeychainKind::External) - .to_string_with_secret( - &wallet - .get_signers(KeychainKind::External) - .as_key_map(wallet.secp_ctx()), - ); - let descriptor = remove_checksum(descriptor); - Self::is_compatible_with_core(&descriptor)?; - - let blockheight = if include_blockheight { - wallet.transactions().next().map_or(0, |canonical_tx| { - canonical_tx - .chain_position - .confirmation_height_upper_bound() - .unwrap_or(0) - }) - } else { - 0 - }; - - let export = FullyNodedExport { - descriptor, - label: label.into(), - blockheight, - }; - - let change_descriptor = { - let descriptor = wallet - .public_descriptor(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"); - } - - Ok(export) - } + // /// Export a wallet + // /// + // /// This function returns an error if it determines that the `wallet`'s descriptor(s) are not + // /// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44 + // /// and others. + // /// + // /// If `include_blockheight` is `true`, this function will look into the `wallet`'s database + // /// for the oldest transaction it knows and use that as the earliest block to rescan. + // /// + // /// If the database is empty or `include_blockheight` is false, the `blockheight` field + // /// returned will be `0`. + // pub fn export_wallet( + // wallet: &Wallet, + // label: &str, + // include_blockheight: bool, + // ) -> Result { + // let descriptor = wallet + // .public_descriptor(KeychainKind::External) + // .to_string_with_secret( + // &wallet + // .get_signers(KeychainKind::External) + // .as_key_map(wallet.secp_ctx()), + // ); + // let descriptor = remove_checksum(descriptor); + // Self::is_compatible_with_core(&descriptor)?; + + // let blockheight = if include_blockheight { + // wallet.transactions().next().map_or(0, |canonical_tx| { + // canonical_tx + // .chain_position + // .confirmation_height_upper_bound() + // .unwrap_or(0) + // }) + // } else { + // 0 + // }; + + // let export = FullyNodedExport { + // descriptor, + // label: label.into(), + // blockheight, + // }; + + // let change_descriptor = { + // let descriptor = wallet + // .public_descriptor(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"); + // } + + // Ok(export) + // } fn is_compatible_with_core(descriptor: &str) -> Result<(), &'static str> { fn check_ms( @@ -224,106 +227,136 @@ mod test { use crate::test_utils::*; use crate::Wallet; - fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet { - let mut wallet = Wallet::create(descriptor.to_string(), change_descriptor.to_string()) - .network(network) - .create_wallet_no_persist() - .expect("must create wallet"); - let block = BlockId { - height: 5000, - hash: BlockHash::all_zeros(), - }; - insert_checkpoint(&mut wallet, block); - receive_output_in_latest_block(&mut wallet, Amount::from_sat(10_000)); - - wallet - } - - #[test] - fn test_export_bip44() { - let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; - let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; - - 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); - assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); - assert_eq!(export.blockheight, 5000); - assert_eq!(export.label, "Test Label"); - } - - #[test] - #[should_panic(expected = "Incompatible change descriptor")] - fn test_export_no_change() { - // 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 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, change_descriptor, Network::Bitcoin); - FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); - } - - #[test] - #[should_panic(expected = "Incompatible change descriptor")] - fn test_export_incompatible_change() { - // This wallet has a change descriptor, but the derivation path is not in the "standard" - // bip44/49/etc format - - let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; - let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)"; - - let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); - FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); - } - - #[test] - fn test_export_multi() { - let descriptor = "wsh(multi(2,\ - [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*,\ - [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/0/*,\ - [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/0/*\ - ))"; - let change_descriptor = "wsh(multi(2,\ - [73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/1/*,\ - [f9f62194/48'/0'/0'/2']tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/1/*,\ - [c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\ - ))"; - - 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())); - assert_eq!(export.blockheight, 5000); - assert_eq!(export.label, "Test Label"); - } - - #[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, 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())); - assert_eq!(export.blockheight, 5000); - assert_eq!(export.label, "Test Label"); - } - - #[test] - fn test_export_to_json() { - let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)"; - let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)"; - - 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\"}"); - } + // fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet { + // let mut wallet = Wallet::create(descriptor.to_string(), change_descriptor.to_string()) + // .network(network) + // .create_wallet_no_persist() + // .expect("must create wallet"); + // let block = BlockId { + // height: 5000, + // hash: BlockHash::all_zeros(), + // }; + // insert_checkpoint(&mut wallet, block); + // receive_output_in_latest_block(&mut wallet, Amount::from_sat(10_000)); + + // wallet + // } + + // #[test] + // fn test_export_bip44() { + // let descriptor = + // "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/ + // 44'/0'/0'/0/*)"; let change_descriptor = + // "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/ + // 44'/0'/0'/1/*)"; + + // 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); + // assert_eq!(export.change_descriptor(), Some(change_descriptor.into())); + // assert_eq!(export.blockheight, 5000); + // assert_eq!(export.label, "Test Label"); + // } + + // #[test] + // #[should_panic(expected = "Incompatible change descriptor")] + // fn test_export_no_change() { + // // 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 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, change_descriptor, Network::Bitcoin); + // FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + // } + + // #[test] + // #[should_panic(expected = "Incompatible change descriptor")] + // fn test_export_incompatible_change() { + // // This wallet has a change descriptor, but the derivation path is not in the "standard" + // // bip44/49/etc format + + // let descriptor = + // "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/ + // 44'/0'/0'/0/*)"; let change_descriptor = + // "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/ + // 50'/0'/1/*)"; + + // let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin); + // FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap(); + // } + + // #[test] + // fn test_export_multi() { + // let descriptor = "wsh(multi(2,\ + // + // [73756c7f/48'/0'/0'/2' + // ]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/ + // 0/*,\ + // [f9f62194/48'/0'/0'/2' + // ]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/ + // 0/*,\ + // [c98b1535/48'/0'/0'/2' + // ]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/ + // 0/*\ ))"; + // let change_descriptor = "wsh(multi(2,\ + // + // [73756c7f/48'/0'/0'/2' + // ]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/ + // 1/*,\ + // [f9f62194/48'/0'/0'/2' + // ]tpubDDp3ZSH1yCwusRppH7zgSxq2t1VEUyXSeEp8E5aFS8m43MknUjiF1bSLo3CGWAxbDyhF1XowA5ukPzyJZjznYk3kYi6oe7QxtX2euvKWsk4/ + // 1/*,\ + // [c98b1535/48'/0'/0'/2' + // ]tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/ + // 1/*\ ))"; + + // 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())); + // assert_eq!(export.blockheight, 5000); + // assert_eq!(export.label, "Test Label"); + // } + + // #[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, 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())); + // assert_eq!(export.blockheight, 5000); + // assert_eq!(export.label, "Test Label"); + // } + + // #[test] + // fn test_export_to_json() { + // let descriptor = + // "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/ + // 44'/0'/0'/0/*)"; let change_descriptor = + // "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/ + // 44'/0'/0'/1/*)"; + + // 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\"}"); } #[test] fn test_export_from_json() { diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 273b5f6f..c0cef61e 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -12,7 +12,7 @@ //! Wallet //! //! This module defines the [`Wallet`]. - +#![allow(unused)] use alloc::{ boxed::Box, string::{String, ToString}, @@ -71,7 +71,7 @@ use crate::wallet::{ coin_selection::{DefaultCoinSelectionAlgorithm, Excess, InsufficientFunds}, error::{BuildFeeBumpError, CreateTxError, MiniscriptPsbtError}, signer::{SignOptions, SignerError, SignerOrdering, SignersContainer, TransactionSigner}, - tx_builder::{FeePolicy, TxBuilder, TxParams}, + // tx_builder::{FeePolicy, TxBuilder, TxParams}, utils::{check_nsequence_rbf, After, Older, SecpCtx}, }; @@ -313,2334 +313,2348 @@ impl std::error::Error for ApplyBlockError {} /// A `CanonicalTx` managed by a `Wallet`. pub type WalletTx<'a> = CanonicalTx<'a, Arc, ConfirmationBlockTime>; -impl Wallet { - /// Build a new single descriptor [`Wallet`]. - /// - /// If you have previously created a wallet, use [`load`](Self::load) instead. - /// - /// # Note - /// - /// Only use this method when creating a wallet designed to be used with a single - /// descriptor and keychain. Otherwise the recommended way to construct a new wallet is - /// by using [`Wallet::create`]. It's worth noting that not all features are available - /// with single descriptor wallets, for example setting a [`change_policy`] on [`TxBuilder`] - /// and related methods such as [`do_not_spend_change`]. This is because all payments are - /// received on the external keychain (including change), and without a change keychain - /// BDK lacks enough information to distinguish between change and outside payments. - /// - /// Additionally because this wallet has no internal (change) keychain, all methods that - /// require a [`KeychainKind`] as input, e.g. [`reveal_next_address`] should only be called - /// using the [`External`] variant. In most cases passing [`Internal`] is treated as the - /// equivalent of [`External`] but this behavior must not be relied on. - /// - /// # Example - /// - /// ```rust - /// # use bdk_wallet::Wallet; - /// # use bitcoin::Network; - /// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; - /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); - /// # let file_path = temp_dir.path().join("store.db"); - /// // Create a wallet that is persisted to SQLite database. - /// use bdk_wallet::rusqlite::Connection; - /// let mut conn = Connection::open(file_path)?; - /// let wallet = Wallet::create_single(EXTERNAL_DESC) - /// .network(Network::Testnet) - /// .create_wallet(&mut conn)?; - /// # Ok::<_, anyhow::Error>(()) - /// ``` - /// [`change_policy`]: TxBuilder::change_policy - /// [`do_not_spend_change`]: TxBuilder::do_not_spend_change - /// [`External`]: KeychainKind::External - /// [`Internal`]: KeychainKind::Internal - /// [`reveal_next_address`]: Self::reveal_next_address - pub fn create_single(descriptor: D) -> CreateParams - where - D: IntoWalletDescriptor + Send + Clone + 'static, - { - CreateParams::new_single(descriptor) - } - - /// Build a new [`Wallet`]. - /// - /// If you have previously created a wallet, use [`load`](Self::load) instead. - /// - /// # Synopsis - /// - /// ```rust - /// # use bdk_wallet::Wallet; - /// # use bitcoin::Network; - /// # fn main() -> anyhow::Result<()> { - /// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; - /// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - /// // Create a non-persisted wallet. - /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - /// .network(Network::Testnet) - /// .create_wallet_no_persist()?; - /// - /// // Create a wallet that is persisted to SQLite database. - /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); - /// # let file_path = temp_dir.path().join("store.db"); - /// use bdk_wallet::rusqlite::Connection; - /// let mut conn = Connection::open(file_path)?; - /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - /// .network(Network::Testnet) - /// .create_wallet(&mut conn)?; - /// # Ok(()) - /// # } - /// ``` - pub fn create(descriptor: D, change_descriptor: D) -> CreateParams - where - D: IntoWalletDescriptor + Send + Clone + 'static, - { - CreateParams::new(descriptor, change_descriptor) - } - - /// Build a new [`Wallet`] from a two-path descriptor. - /// - /// This function parses a multipath descriptor with exactly 2 paths and creates a wallet - /// using the existing receive and change wallet creation logic. Note that you can only use this - /// method with public extended keys (`xpub` prefix) to create watch-only wallets. - /// - /// Multipath descriptors follow [BIP 389] and allow defining both receive and change - /// derivation paths in a single descriptor using the `<0;1>` syntax. - /// - /// If you have previously created a wallet, use [`load`](Self::load) instead. - /// - /// # Errors - /// Returns an error if the descriptor is invalid, not a 2-path multipath descriptor, or if - /// the descriptor provided contains an extended private key (`xprv` prefix). - /// - /// # Synopsis - /// - /// ```rust - /// # use bdk_wallet::Wallet; - /// # use bitcoin::Network; - /// # use bdk_wallet::KeychainKind; - /// # const TWO_PATH_DESC: &str = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)"; - /// let wallet = Wallet::create_from_two_path_descriptor(TWO_PATH_DESC) - /// .network(Network::Testnet) - /// .create_wallet_no_persist() - /// .unwrap(); - /// - /// // The multipath descriptor automatically creates separate receive and change descriptors - /// let receive_addr = wallet.peek_address(KeychainKind::External, 0); // Uses path /0/* - /// let change_addr = wallet.peek_address(KeychainKind::Internal, 0); // Uses path /1/* - /// assert_ne!(receive_addr.address, change_addr.address); - /// ``` - /// - /// [BIP 389]: https://github.com/bitcoin/bips/blob/master/bip-0389.mediawiki - pub fn create_from_two_path_descriptor(two_path_descriptor: D) -> CreateParams - where - D: IntoWalletDescriptor + Send + Clone + 'static, - { - CreateParams::new_two_path(two_path_descriptor) - } - - /// Create a new [`Wallet`] with given `params`. - /// - /// Refer to [`Wallet::create`] for more. - pub fn create_with_params(params: CreateParams) -> Result { - let secp = SecpCtx::new(); - let network = params.network; - let genesis_hash = params - .genesis_hash - .unwrap_or(genesis_block(network).block_hash()); - let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); - - let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network)?; - check_wallet_descriptor(&descriptor)?; - descriptor_keymap.extend(params.descriptor_keymap); - - let signers = Arc::new(SignersContainer::build( - descriptor_keymap, - &descriptor, - &secp, - )); - - let (change_descriptor, change_signers) = match params.change_descriptor { - Some(make_desc) => { - let (change_descriptor, mut internal_keymap) = make_desc(&secp, network)?; - check_wallet_descriptor(&change_descriptor)?; - internal_keymap.extend(params.change_descriptor_keymap); - let change_signers = Arc::new(SignersContainer::build( - internal_keymap, - &change_descriptor, - &secp, - )); - (Some(change_descriptor), change_signers) - } - None => (None, Arc::new(SignersContainer::new())), - }; - - let mut stage = ChangeSet { - descriptor: Some(descriptor.clone()), - change_descriptor: change_descriptor.clone(), - local_chain: chain_changeset, - network: Some(network), - ..Default::default() - }; - - let indexed_graph = make_indexed_graph( - &mut stage, - Default::default(), - Default::default(), - descriptor, - change_descriptor, - params.lookahead, - params.use_spk_cache, - )?; - - Ok(Wallet { - signers, - change_signers, - network, - chain, - indexed_graph, - stage, - secp, - }) - } - - /// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. - /// - /// Note that the descriptor secret keys are not persisted to the db. You can add - /// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. You - /// can also add keys when building the wallet by using [`LoadParams::keymap`]. Finally - /// you can check the wallet's descriptors are what you expect with [`LoadParams::descriptor`] - /// which will try to populate signers if [`LoadParams::extract_keys`] is enabled. - /// - /// # Synopsis - /// - /// ```rust,no_run - /// # use bdk_wallet::{Wallet, ChangeSet, KeychainKind}; - /// # use bitcoin::{BlockHash, Network, hashes::Hash}; - /// # fn main() -> anyhow::Result<()> { - /// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; - /// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; - /// # let changeset = ChangeSet::default(); - /// // Load a wallet from changeset (no persistence). - /// let wallet = Wallet::load() - /// .load_wallet_no_persist(changeset)? - /// .expect("must have data to load wallet"); - /// - /// // Load a wallet that is persisted to SQLite database. - /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); - /// # let file_path = temp_dir.path().join("store.db"); - /// # let external_keymap = Default::default(); - /// # let internal_keymap = Default::default(); - /// # let genesis_hash = BlockHash::all_zeros(); - /// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?; - /// let mut wallet = Wallet::load() - /// // check loaded descriptors matches these values and extract private keys - /// .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) - /// .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) - /// .extract_keys() - /// // you can also manually add private keys - /// .keymap(KeychainKind::External, external_keymap) - /// .keymap(KeychainKind::Internal, internal_keymap) - /// // ensure loaded wallet's genesis hash matches this value - /// .check_genesis_hash(genesis_hash) - /// // set a lookahead for our indexer - /// .lookahead(101) - /// .load_wallet(&mut conn)? - /// .expect("must have data to load wallet"); - /// # Ok(()) - /// # } - /// ``` - pub fn load() -> LoadParams { - LoadParams::new() - } - - /// Load [`Wallet`] from the given previously persisted [`ChangeSet`] and `params`. - /// - /// Returns `Ok(None)` if the changeset is empty. Refer to [`Wallet::load`] for more. - pub fn load_with_params( - changeset: ChangeSet, - params: LoadParams, - ) -> Result, LoadError> { - if changeset.is_empty() { - return Ok(None); - } - let secp = Secp256k1::new(); - let network = changeset.network.ok_or(LoadError::MissingNetwork)?; - let chain = LocalChain::from_changeset(changeset.local_chain) - .map_err(|_| LoadError::MissingGenesis)?; - - if let Some(exp_network) = params.check_network { - if network != exp_network { - return Err(LoadError::Mismatch(LoadMismatch::Network { - loaded: network, - expected: exp_network, - })); - } - } - if let Some(exp_genesis_hash) = params.check_genesis_hash { - if chain.genesis_hash() != exp_genesis_hash { - return Err(LoadError::Mismatch(LoadMismatch::Genesis { - loaded: chain.genesis_hash(), - expected: exp_genesis_hash, - })); - } - } - - let descriptor = changeset - .descriptor - .ok_or(LoadError::MissingDescriptor(KeychainKind::External))?; - check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?; - let mut external_keymap = params.descriptor_keymap; - - if let Some(expected) = params.check_descriptor { - if let Some(make_desc) = expected { - let (exp_desc, keymap) = - make_desc(&secp, network).map_err(LoadError::Descriptor)?; - if descriptor.descriptor_id() != exp_desc.descriptor_id() { - return Err(LoadError::Mismatch(LoadMismatch::Descriptor { - keychain: KeychainKind::External, - loaded: Some(descriptor), - expected: Some(exp_desc), - })); - } - if params.extract_keys { - external_keymap.extend(keymap); - } - } else { - return Err(LoadError::Mismatch(LoadMismatch::Descriptor { - keychain: KeychainKind::External, - loaded: Some(descriptor), - expected: None, - })); - } - } - let signers = Arc::new(SignersContainer::build(external_keymap, &descriptor, &secp)); - - let mut change_descriptor = None; - let mut internal_keymap = params.change_descriptor_keymap; - - match (changeset.change_descriptor, params.check_change_descriptor) { - // empty signer - (None, None) => {} - (None, Some(expect)) => { - // expected desc but none loaded - if let Some(make_desc) = expect { - let (exp_desc, _) = make_desc(&secp, network).map_err(LoadError::Descriptor)?; - return Err(LoadError::Mismatch(LoadMismatch::Descriptor { - keychain: KeychainKind::Internal, - loaded: None, - expected: Some(exp_desc), - })); - } - } - // nothing expected - (Some(desc), None) => { - check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?; - change_descriptor = Some(desc); - } - (Some(desc), Some(expect)) => match expect { - // expected none for existing - None => { - return Err(LoadError::Mismatch(LoadMismatch::Descriptor { - keychain: KeychainKind::Internal, - loaded: Some(desc), - expected: None, - })) - } - // parameters must match - Some(make_desc) => { - check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?; - let (exp_desc, keymap) = - make_desc(&secp, network).map_err(LoadError::Descriptor)?; - if desc.descriptor_id() != exp_desc.descriptor_id() { - return Err(LoadError::Mismatch(LoadMismatch::Descriptor { - keychain: KeychainKind::Internal, - loaded: Some(desc), - expected: Some(exp_desc), - })); - } - if params.extract_keys { - internal_keymap.extend(keymap); - } - change_descriptor = Some(desc); - } - }, - } - - let change_signers = match change_descriptor { - Some(ref change_descriptor) => Arc::new(SignersContainer::build( - internal_keymap, - change_descriptor, - &secp, - )), - None => Arc::new(SignersContainer::new()), - }; - - let mut stage = ChangeSet::default(); - - let indexed_graph = make_indexed_graph( - &mut stage, - changeset.tx_graph, - changeset.indexer, - descriptor, - change_descriptor, - params.lookahead, - params.use_spk_cache, - ) - .map_err(LoadError::Descriptor)?; - - Ok(Some(Wallet { - signers, - change_signers, - chain, - indexed_graph, - stage, - network, - secp, - })) - } - - /// Get the Bitcoin network the wallet is using. - pub fn network(&self) -> Network { - self.network - } - - /// Iterator over all keychains in this wallet - pub fn keychains(&self) -> impl Iterator { - self.indexed_graph.index.keychains() - } - - /// Peek an address of the given `keychain` at `index` without revealing it. - /// - /// For non-wildcard descriptors this returns the same address at every provided index. - /// - /// # Panics - /// - /// 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("keychain must exist"); - if !spk_iter.descriptor().has_wildcard() { - index = 0; - } - let (index, spk) = spk_iter - .nth(index as usize) - .expect("derivation index is out of bounds"); - - AddressInfo { - index, - address: Address::from_script(&spk, self.network).expect("must have address form"), - keychain, - } - } - - /// Attempt to reveal the next address of the given `keychain`. - /// - /// This will increment the keychain's derivation index. If the keychain's descriptor doesn't - /// contain a wildcard or every address is already revealed up to the maximum derivation - /// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), - /// then the last revealed address will be returned. - /// - /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more - /// calls to this method before closing the wallet. For example: - /// - /// ```rust,no_run - /// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind}; - /// use bdk_chain::rusqlite::Connection; - /// let mut conn = Connection::open_in_memory().expect("must open connection"); - /// let mut wallet = LoadParams::new() - /// .load_wallet(&mut conn) - /// .expect("database is okay") - /// .expect("database has data"); - /// let next_address = wallet.reveal_next_address(KeychainKind::External); - /// wallet.persist(&mut conn).expect("write is okay"); - /// - /// // Now it's safe to show the user their next address! - /// println!("Next address: {}", next_address.address); - /// # Ok::<(), anyhow::Error>(()) - /// ``` - pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> AddressInfo { - let keychain = self.map_keychain(keychain); - let index = &mut self.indexed_graph.index; - let stage = &mut self.stage; - - let ((index, spk), index_changeset) = index - .reveal_next_spk(keychain) - .expect("keychain must exist"); - - stage.merge(index_changeset.into()); - - AddressInfo { - index, - address: Address::from_script(spk.as_script(), self.network) - .expect("must have address form"), - keychain, - } - } - - /// Reveal addresses up to and including the target `index` and return an iterator - /// of newly revealed addresses. - /// - /// If the target `index` is unreachable, we make a best effort to reveal up to the last - /// possible index. If all addresses up to the given `index` are already revealed, then - /// no new addresses are returned. - /// - /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more - /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. - pub fn reveal_addresses_to( - &mut self, - keychain: KeychainKind, - index: u32, - ) -> impl Iterator + '_ { - let keychain = self.map_keychain(keychain); - let (spks, index_changeset) = self - .indexed_graph - .index - .reveal_to_target(keychain, index) - .expect("keychain must exist"); - - self.stage.merge(index_changeset.into()); - - spks.into_iter().map(move |(index, spk)| AddressInfo { - index, - address: Address::from_script(&spk, self.network).expect("must have address form"), - keychain, - }) - } - - /// Get the next unused address for the given `keychain`, i.e. the address with the lowest - /// derivation index that hasn't been used in a transaction. - /// - /// This will attempt to reveal a new address if all previously revealed addresses have - /// been used, in which case the returned address will be the same as calling - /// [`Wallet::reveal_next_address`]. - /// - /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more - /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. - pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo { - let keychain = self.map_keychain(keychain); - let index = &mut self.indexed_graph.index; - - let ((index, spk), index_changeset) = index - .next_unused_spk(keychain) - .expect("keychain must exist"); - - self.stage - .merge(indexed_tx_graph::ChangeSet::from(index_changeset).into()); - - AddressInfo { - index, - address: Address::from_script(spk.as_script(), self.network) - .expect("must have address form"), - keychain, - } - } - - /// Marks an address used of the given `keychain` at `index`. - /// - /// Returns whether the given index was present and then removed from the unused set. - pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool { - self.indexed_graph.index.mark_used(keychain, index) - } - - /// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted - /// back into the unused set. - /// - /// Since this is only a superficial marker, it will have no effect if the address at the given - /// `index` was actually used, i.e. the wallet has previously indexed a tx output for the - /// derived spk. - /// - /// [`mark_used`]: Self::mark_used - pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool { - self.indexed_graph.index.unmark_used(keychain, index) - } - - /// List addresses that are revealed but unused. - /// - /// Note if the returned iterator is empty you can reveal more addresses - /// by using [`reveal_next_address`](Self::reveal_next_address) or - /// [`reveal_addresses_to`](Self::reveal_addresses_to). - pub fn list_unused_addresses( - &self, - keychain: KeychainKind, - ) -> impl DoubleEndedIterator + '_ { - self.indexed_graph - .index - .unused_keychain_spks(self.map_keychain(keychain)) - .map(move |(index, spk)| AddressInfo { - index, - address: Address::from_script(spk.as_script(), self.network) - .expect("must have address form"), - keychain, - }) - } - - /// Return whether or not a `script` is part of this wallet (either internal or external) - pub fn is_mine(&self, script: ScriptBuf) -> bool { - self.indexed_graph.index.index_of_spk(script).is_some() - } - - /// Finds how the wallet derived the script pubkey `spk`. - /// - /// Will only return `Some(_)` if the wallet has given out the spk. - pub fn derivation_of_spk(&self, spk: ScriptBuf) -> Option<(KeychainKind, u32)> { - self.indexed_graph.index.index_of_spk(spk).cloned() - } - - /// Return the list of unspent outputs of this wallet - pub fn list_unspent(&self) -> impl Iterator + '_ { - self.indexed_graph - .graph() - .filter_chain_unspents( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - self.indexed_graph.index.outpoints().iter().cloned(), - ) - .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) - } - - /// Get the [`TxDetails`] of a wallet transaction. - /// - /// If the transaction with txid [`Txid`] cannot be found in the wallet's transactions, `None` - /// is returned. - pub fn tx_details(&self, txid: Txid) -> Option { - let tx: WalletTx = self.transactions().find(|c| c.tx_node.txid == txid)?; - - let (sent, received) = self.sent_and_received(&tx.tx_node.tx); - let fee: Option = self.calculate_fee(&tx.tx_node.tx).ok(); - let fee_rate: Option = self.calculate_fee_rate(&tx.tx_node.tx).ok(); - let balance_delta: SignedAmount = self.indexed_graph.index.net_value(&tx.tx_node.tx, ..); - let chain_position = tx.chain_position; - - let tx_details: TxDetails = TxDetails { - txid, - received, - sent, - fee, - fee_rate, - balance_delta, - chain_position, - tx: tx.tx_node.tx, - }; - - Some(tx_details) - } - - /// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed). - /// - /// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead. - pub fn list_output(&self) -> impl Iterator + '_ { - self.indexed_graph - .graph() - .filter_chain_txouts( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - self.indexed_graph.index.outpoints().iter().cloned(), - ) - .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) - } - - /// Get all the checkpoints the wallet is currently storing indexed by height. - pub fn checkpoints(&self) -> CheckPointIter { - self.chain.iter_checkpoints() - } - - /// Returns the latest checkpoint. - pub fn latest_checkpoint(&self) -> CheckPoint { - self.chain.tip() - } - - /// Get unbounded script pubkey iterators for both `Internal` and `External` keychains. - /// - /// This is intended to be used when doing a full scan of your addresses (e.g. after restoring - /// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g. - /// electrum server) which will go through each address until it reaches a *stop gap*. - /// - /// Note carefully that iterators go over **all** script pubkeys on the keychains (not what - /// script pubkeys the wallet is storing internally). - pub fn all_unbounded_spk_iters( - &self, - ) -> BTreeMap> + Clone> { - self.indexed_graph.index.all_unbounded_spk_iters() - } - - /// Get an unbounded script pubkey iterator for the given `keychain`. - /// - /// See [`all_unbounded_spk_iters`] for more documentation - /// - /// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters - pub fn unbounded_spk_iter( - &self, - keychain: KeychainKind, - ) -> impl Iterator> + Clone { - self.indexed_graph - .index - .unbounded_spk_iter(self.map_keychain(keychain)) - .expect("keychain must exist") - } - - /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the - /// wallet's database. - pub fn get_utxo(&self, op: OutPoint) -> Option { - let ((keychain, index), _) = self.indexed_graph.index.txout(op)?; - self.indexed_graph - .graph() - .filter_chain_unspents( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - core::iter::once(((), op)), - ) - .map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo)) - .next() - } - - /// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph. - /// - /// This is used for providing a previous output's value so that we can use [`calculate_fee`] - /// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will - /// not be returned in [`list_unspent`] or [`list_output`]. - /// - /// **WARNINGS:** This should only be used to add `TxOut`s that the wallet does not own. Only - /// insert `TxOut`s that you trust the values for! - /// - /// You must persist the changes resulting from one or more calls to this method if you need - /// the inserted `TxOut` data to be reloaded after closing the wallet. - /// See [`Wallet::reveal_next_address`]. - /// - /// [`calculate_fee`]: Self::calculate_fee - /// [`calculate_fee_rate`]: Self::calculate_fee_rate - /// [`list_unspent`]: Self::list_unspent - /// [`list_output`]: Self::list_output - pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) { - let additions = self.indexed_graph.insert_txout(outpoint, txout); - self.stage.merge(additions.into()); - } - - /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase - /// transaction. - /// - /// To calculate the fee for a [`Transaction`] with inputs not owned by this wallet you must - /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function. - /// - /// Note `tx` does not have to be in the graph for this to work. - /// - /// # Examples - /// - /// ```rust, no_run - /// # use bitcoin::Txid; - /// # use bdk_wallet::Wallet; - /// # let mut wallet: Wallet = todo!(); - /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - /// let fee = wallet.calculate_fee(&tx).expect("fee"); - /// ``` - /// - /// ```rust, no_run - /// # use bitcoin::Psbt; - /// # use bdk_wallet::Wallet; - /// # let mut wallet: Wallet = todo!(); - /// # let mut psbt: Psbt = todo!(); - /// let tx = &psbt.clone().extract_tx().expect("tx"); - /// let fee = wallet.calculate_fee(tx).expect("fee"); - /// ``` - /// [`insert_txout`]: Self::insert_txout - pub fn calculate_fee(&self, tx: &Transaction) -> Result { - self.indexed_graph.graph().calculate_fee(tx) - } - - /// Calculate the [`FeeRate`] for a given transaction. - /// - /// To calculate the fee rate for a [`Transaction`] with inputs not owned by this wallet you - /// must manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function. - /// - /// Note `tx` does not have to be in the graph for this to work. - /// - /// # Examples - /// - /// ```rust, no_run - /// # use bitcoin::Txid; - /// # use bdk_wallet::Wallet; - /// # let mut wallet: Wallet = todo!(); - /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate"); - /// ``` - /// - /// ```rust, no_run - /// # use bitcoin::Psbt; - /// # use bdk_wallet::Wallet; - /// # let mut wallet: Wallet = todo!(); - /// # let mut psbt: Psbt = todo!(); - /// let tx = &psbt.clone().extract_tx().expect("tx"); - /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate"); - /// ``` - /// [`insert_txout`]: Self::insert_txout - pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { - self.calculate_fee(tx).map(|fee| fee / tx.weight()) - } - - /// Compute the `tx`'s sent and received [`Amount`]s. - /// - /// This method returns a tuple `(sent, received)`. Sent is the sum of the txin amounts - /// that spend from previous txouts tracked by this wallet. Received is the summation - /// of this tx's outputs that send to script pubkeys tracked by this wallet. - /// - /// # Examples - /// - /// ```rust, no_run - /// # use bitcoin::Txid; - /// # use bdk_wallet::Wallet; - /// # let mut wallet: Wallet = todo!(); - /// # let txid:Txid = todo!(); - /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx; - /// let (sent, received) = wallet.sent_and_received(&tx); - /// ``` - /// - /// ```rust, no_run - /// # use bitcoin::Psbt; - /// # use bdk_wallet::Wallet; - /// # let mut wallet: Wallet = todo!(); - /// # let mut psbt: Psbt = todo!(); - /// let tx = &psbt.clone().extract_tx().expect("tx"); - /// let (sent, received) = wallet.sent_and_received(tx); - /// ``` - pub fn sent_and_received(&self, tx: &Transaction) -> (Amount, Amount) { - self.indexed_graph.index.sent_and_received(tx, ..) - } - - /// Get a single transaction from the wallet as a [`WalletTx`] (if the transaction exists). - /// - /// `WalletTx` contains the full transaction alongside meta-data such as: - /// * Blocks that the transaction is [`Anchor`]ed in. These may or may not be blocks that exist - /// in the best chain. - /// * The [`ChainPosition`] of the transaction in the best chain - whether the transaction is - /// confirmed or unconfirmed. If the transaction is confirmed, the anchor which proves the - /// confirmation is provided. If the transaction is unconfirmed, the unix timestamp of when - /// the transaction was last seen in the mempool is provided. - /// - /// ```rust, no_run - /// use bdk_chain::Anchor; - /// use bdk_wallet::{chain::ChainPosition, Wallet}; - /// # let wallet: Wallet = todo!(); - /// # let my_txid: bitcoin::Txid = todo!(); - /// - /// let wallet_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist"); - /// - /// // get reference to full transaction - /// println!("my tx: {:#?}", wallet_tx.tx_node.tx); - /// - /// // list all transaction anchors - /// for anchor in wallet_tx.tx_node.anchors { - /// println!( - /// "tx is anchored by block of hash {}", - /// anchor.anchor_block().hash - /// ); - /// } - /// - /// // get confirmation status of transaction - /// match wallet_tx.chain_position { - /// ChainPosition::Confirmed { - /// anchor, - /// transitively: None, - /// } => println!( - /// "tx is confirmed at height {}, we know this since {}:{} is in the best chain", - /// anchor.block_id.height, anchor.block_id.height, anchor.block_id.hash, - /// ), - /// ChainPosition::Confirmed { - /// anchor, - /// transitively: Some(_), - /// } => println!( - /// "tx is an ancestor of a tx anchored in {}:{}", - /// anchor.block_id.height, anchor.block_id.hash, - /// ), - /// ChainPosition::Unconfirmed { first_seen, last_seen } => println!( - /// "tx is first seen at {:?}, last seen at {:?}, it is unconfirmed as it is not anchored in the best chain", - /// first_seen, last_seen - /// ), - /// } - /// ``` - /// - /// [`Anchor`]: bdk_chain::Anchor - pub fn get_tx(&self, txid: Txid) -> Option> { - let graph = self.indexed_graph.graph(); - graph - .list_canonical_txs( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - ) - .find(|tx| tx.tx_node.txid == txid) - } - - /// Iterate over relevant and canonical transactions in the wallet. - /// - /// A transaction is relevant when it spends from or spends to at least one tracked output. A - /// transaction is canonical when it is confirmed in the best chain, or does not conflict - /// with any transaction confirmed in the best chain. - /// - /// To iterate over all transactions, including those that are irrelevant and not canonical, use - /// [`TxGraph::full_txs`]. - /// - /// To iterate over all canonical transactions, including those that are irrelevant, use - /// [`TxGraph::list_canonical_txs`]. - pub fn transactions<'a>(&'a self) -> impl Iterator> + 'a { - let tx_graph = self.indexed_graph.graph(); - let tx_index = &self.indexed_graph.index; - tx_graph - .list_canonical_txs( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - ) - .filter(|c_tx| tx_index.is_tx_relevant(&c_tx.tx_node.tx)) - } - - /// Array of relevant and canonical transactions in the wallet sorted with a comparator - /// function. - /// - /// This is a helper method equivalent to collecting the result of [`Wallet::transactions`] - /// into a [`Vec`] and then sorting it. - /// - /// # Example - /// - /// ```rust,no_run - /// # use bdk_wallet::{LoadParams, Wallet, WalletTx}; - /// # let mut wallet:Wallet = todo!(); - /// // Transactions by chain position: first unconfirmed then descending by confirmed height. - /// let sorted_txs: Vec = - /// wallet.transactions_sort_by(|tx1, tx2| tx2.chain_position.cmp(&tx1.chain_position)); - /// # Ok::<(), anyhow::Error>(()) - /// ``` - pub fn transactions_sort_by(&self, compare: F) -> Vec> - where - F: FnMut(&WalletTx, &WalletTx) -> Ordering, - { - let mut txs: Vec = self.transactions().collect(); - txs.sort_unstable_by(compare); - txs - } - - /// Return the balance, separated into available, trusted-pending, untrusted-pending, and - /// immature values. - pub fn balance(&self) -> Balance { - self.indexed_graph.graph().balance( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - self.indexed_graph.index.outpoints().iter().cloned(), - |&(k, _), _| k == KeychainKind::Internal, - ) - } - - /// Add an external signer - /// - /// See [the `signer` module](signer) for an example. - pub fn add_signer( - &mut self, - keychain: KeychainKind, - ordering: SignerOrdering, - signer: Arc, - ) { - let signers = match keychain { - KeychainKind::External => Arc::make_mut(&mut self.signers), - KeychainKind::Internal => Arc::make_mut(&mut self.change_signers), - }; - - signers.add_external(signer.id(&self.secp), ordering, signer); - } - - /// Set the keymap for a given keychain. - /// - /// Note this does nothing if the given keychain has no descriptor because we won't - /// know the context (segwit, taproot, etc) in which to create signatures. - pub fn set_keymap(&mut self, keychain: KeychainKind, keymap: KeyMap) { - let wallet_signers = match keychain { - KeychainKind::External => Arc::make_mut(&mut self.signers), - KeychainKind::Internal => Arc::make_mut(&mut self.change_signers), - }; - if let Some(descriptor) = self.indexed_graph.index.get_descriptor(keychain) { - *wallet_signers = SignersContainer::build(keymap, descriptor, &self.secp) - } - } - - /// Set the keymap for each keychain. - pub fn set_keymaps(&mut self, keymaps: impl IntoIterator) { - for (keychain, keymap) in keymaps { - self.set_keymap(keychain, keymap); - } - } - - /// Get the signers - /// - /// ## Example - /// - /// ``` - /// # use bdk_wallet::{Wallet, KeychainKind}; - /// # use bdk_wallet::bitcoin::Network; - /// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)"; - /// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)"; - /// let wallet = Wallet::create(descriptor, change_descriptor) - /// .network(Network::Testnet) - /// .create_wallet_no_persist()?; - /// 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); - /// } - /// - /// Ok::<(), Box>(()) - /// ``` - pub fn get_signers(&self, keychain: KeychainKind) -> Arc { - match keychain { - KeychainKind::External => Arc::clone(&self.signers), - KeychainKind::Internal => Arc::clone(&self.change_signers), - } - } - - /// Start building a transaction. - /// - /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the - /// transaction. - /// - /// ## Example - /// - /// ``` - /// # use std::str::FromStr; - /// # use bitcoin::*; - /// # use bdk_wallet::*; - /// # use bdk_wallet::ChangeSet; - /// # use bdk_wallet::error::CreateTxError; - /// # use anyhow::Error; - /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; - /// # let mut wallet = doctest_wallet!(); - /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); - /// let psbt = { - /// let mut builder = wallet.build_tx(); - /// builder - /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); - /// builder.finish()? - /// }; - /// - /// // sign and broadcast ... - /// # Ok::<(), anyhow::Error>(()) - /// ``` - /// - /// [`TxBuilder`]: crate::TxBuilder - pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm> { - TxBuilder { - wallet: self, - params: TxParams::default(), - coin_selection: DefaultCoinSelectionAlgorithm::default(), - } - } - - pub(crate) fn create_tx( - &mut self, - coin_selection: Cs, - params: TxParams, - rng: &mut impl RngCore, - ) -> 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 external_policy = external_descriptor - .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)? - .unwrap(); - let internal_policy = internal_descriptor - .map(|desc| { - 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 provided - if params.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange - && external_policy.requires_path() - && params.external_policy_path.is_none() - { - return Err(CreateTxError::SpendingPolicyRequired( - KeychainKind::External, - )); - }; - // Same for the internal_policy path - 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, - )); - }; - } - - let external_requirements = external_policy.get_condition( - params - .external_policy_path - .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 requirements = - external_requirements.merge(&internal_requirements.unwrap_or_default())?; - - let version = match params.version { - Some(transaction::Version(0)) => return Err(CreateTxError::Version0), - Some(transaction::Version::ONE) if requirements.csv.is_some() => { - return Err(CreateTxError::Version1Csv) - } - Some(v) => v, - None => transaction::Version::TWO, - }; - - // We use a match here instead of a unwrap_or_else as it's way more readable :) - let current_height = match params.current_height { - // If they didn't tell us the current height, we assume it's the latest sync height. - None => { - let tip_height = self.chain.tip().height(); - absolute::LockTime::from_height(tip_height).expect("invalid height") - } - Some(h) => h, - }; - - let lock_time = match params.locktime { - // When no nLockTime is specified, we try to prevent fee sniping, if possible - None => { - // Fee sniping can be partially prevented by setting the timelock - // to current_height. If we don't know the current_height, - // we default to 0. - let fee_sniping_height = current_height; - - // We choose the biggest between the required nlocktime and the fee sniping - // height - match requirements.timelock { - // No requirement, just use the fee_sniping_height - None => fee_sniping_height, - // There's a block-based requirement, but the value is lower than the - // fee_sniping_height - Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => { - fee_sniping_height - } - // There's a time-based requirement or a block-based requirement greater - // than the fee_sniping_height use that value - Some(value) => value, - } - } - // Specific nLockTime required and we have no constraints, so just set to that value - Some(x) if requirements.timelock.is_none() => x, - // Specific nLockTime required and it's compatible with the constraints - Some(x) - if requirements.timelock.unwrap().is_same_unit(x) - && x >= requirements.timelock.unwrap() => - { - x - } - // Invalid nLockTime required - Some(x) => { - return Err(CreateTxError::LockTime { - requested: x, - required: requirements.timelock.unwrap(), - }) - } - }; - - // nSequence value for inputs - // When not explicitly specified, defaults to 0xFFFFFFFD, - // meaning RBF signaling is enabled - let n_sequence = match (params.sequence, requirements.csv) { - // Enable RBF by default - (None, None) => Sequence::ENABLE_RBF_NO_LOCKTIME, - // None requested, use required - (None, Some(csv)) => csv, - // Requested sequence is incompatible with requirements - (Some(sequence), Some(csv)) if !check_nsequence_rbf(sequence, csv) => { - return Err(CreateTxError::RbfSequenceCsv { sequence, csv }) - } - // Use requested nSequence value - (Some(sequence), _) => sequence, - }; - - let (fee_rate, mut fee_amount) = match params.fee_policy.unwrap_or_default() { - //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256 - FeePolicy::FeeAmount(fee) => { - if let Some(previous_fee) = params.bumping_fee { - if fee < previous_fee.absolute { - return Err(CreateTxError::FeeTooLow { - required: previous_fee.absolute, - }); - } - } - (FeeRate::ZERO, fee) - } - FeePolicy::FeeRate(rate) => { - if let Some(previous_fee) = params.bumping_fee { - let required_feerate = FeeRate::from_sat_per_kwu( - previous_fee.rate.to_sat_per_kwu() - + FeeRate::BROADCAST_MIN.to_sat_per_kwu(), // +1 sat/vb - ); - if rate < required_feerate { - return Err(CreateTxError::FeeRateTooLow { - required: required_feerate, - }); - } - } - (rate, Amount::ZERO) - } - }; - - let mut tx = Transaction { - version, - lock_time, - input: vec![], - output: vec![], - }; - - if params.manually_selected_only && params.utxos.is_empty() { - return Err(CreateTxError::NoUtxosSelected); - } - - let mut outgoing = Amount::ZERO; - let recipients = params.recipients.iter().map(|(r, v)| (r, *v)); - - for (index, (script_pubkey, value)) in recipients.enumerate() { - if !params.allow_dust && value.is_dust(script_pubkey) && !script_pubkey.is_op_return() { - return Err(CreateTxError::OutputBelowDustLimit(index)); - } - - let new_out = TxOut { - script_pubkey: script_pubkey.clone(), - value, - }; - - tx.output.push(new_out); - - outgoing += value; - } - - fee_amount += fee_rate * tx.weight(); - - let (required_utxos, optional_utxos) = { - // NOTE: manual selection overrides unspendable - let mut required: Vec = params.utxos.clone(); - let optional = self.filter_utxos(¶ms, current_height.to_consensus_u32()); - - // if drain_wallet is true, all UTxOs are required - if params.drain_wallet { - required.extend(optional); - (required, vec![]) - } else { - (required, optional) - } - }; - - // get drain script - let mut drain_index = Option::<(KeychainKind, u32)>::None; - let drain_script = match params.drain_to { - Some(ref drain_recipient) => drain_recipient.clone(), - None => { - let change_keychain = self.map_keychain(KeychainKind::Internal); - let (index, spk) = self - .indexed_graph - .index - .unused_keychain_spks(change_keychain) - .next() - .unwrap_or_else(|| { - let (next_index, _) = self - .indexed_graph - .index - .next_index(change_keychain) - .expect("keychain must exist"); - let spk = self - .peek_address(change_keychain, next_index) - .script_pubkey(); - (next_index, spk) - }); - drain_index = Some((change_keychain, index)); - spk - } - }; - - let coin_selection = coin_selection - .coin_select( - required_utxos, - optional_utxos, - fee_rate, - outgoing + fee_amount, - &drain_script, - rng, - ) - .map_err(CreateTxError::CoinSelection)?; - - let excess = &coin_selection.excess; - tx.input = coin_selection - .selected - .iter() - .map(|u| bitcoin::TxIn { - previous_output: u.outpoint(), - script_sig: ScriptBuf::default(), - sequence: u.sequence().unwrap_or(n_sequence), - witness: Witness::new(), - }) - .collect(); - - if tx.output.is_empty() { - // Uh oh, our transaction has no outputs. - // We allow this when: - // - We have a drain_to address and the utxos we must spend (this happens, - // for example, when we RBF) - // - We have a drain_to address and drain_wallet set - // Otherwise, we don't know who we should send the funds to, and how much - // we should send! - if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) { - if let Excess::NoChange { - dust_threshold, - remaining_amount, - change_fee, - } = excess - { - return Err(CreateTxError::CoinSelection(InsufficientFunds { - needed: *dust_threshold, - available: remaining_amount - .checked_sub(*change_fee) - .unwrap_or_default(), - })); - } - } else { - return Err(CreateTxError::NoRecipients); - } - } - - // if there's change, create and add a change output - if let Excess::Change { amount, .. } = excess { - // create drain output - let drain_output = TxOut { - value: *amount, - script_pubkey: drain_script, - }; - - // TODO: We should pay attention when adding a new output: this might increase - // the length of the "number of vouts" parameter by 2 bytes, potentially making - // our feerate too low - tx.output.push(drain_output); - } - - // sort input/outputs according to the chosen algorithm - params.ordering.sort_tx_with_aux_rand(&mut tx, rng); - - let psbt = self.complete_transaction(tx, coin_selection.selected, params)?; - - // recording changes to the change keychain - if let (Excess::Change { .. }, Some((keychain, index))) = (excess, drain_index) { - if let Some((_, index_changeset)) = - self.indexed_graph.index.reveal_to_target(keychain, index) - { - self.stage.merge(index_changeset.into()); - self.mark_used(keychain, index); - } - } - - Ok(psbt) - } - - /// Bump the fee of a transaction previously created with this wallet. - /// - /// Returns an error if the transaction is already confirmed or doesn't explicitly signal - /// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a [`TxBuilder`] - /// pre-populated with the inputs and outputs of the original transaction. - /// - /// ## Example - /// - /// ```no_run - /// # // TODO: remove norun -- bumping fee seems to need the tx in the wallet database first. - /// # use std::str::FromStr; - /// # use bitcoin::*; - /// # use bdk_wallet::*; - /// # use bdk_wallet::ChangeSet; - /// # use bdk_wallet::error::CreateTxError; - /// # use anyhow::Error; - /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; - /// # let mut wallet = doctest_wallet!(); - /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); - /// let mut psbt = { - /// let mut builder = wallet.build_tx(); - /// builder - /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); - /// builder.finish()? - /// }; - /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; - /// let tx = psbt.clone().extract_tx().expect("tx"); - /// // broadcast tx but it's taking too long to confirm so we want to bump the fee - /// let mut psbt = { - /// let mut builder = wallet.build_fee_bump(tx.compute_txid())?; - /// builder - /// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate")); - /// builder.finish()? - /// }; - /// - /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; - /// let fee_bumped_tx = psbt.extract_tx(); - /// // broadcast fee_bumped_tx to replace original - /// # Ok::<(), anyhow::Error>(()) - /// ``` - // TODO: support for merging multiple transactions while bumping the fees - pub fn build_fee_bump( - &mut self, - txid: Txid, - ) -> Result, BuildFeeBumpError> { - let graph = self.indexed_graph.graph(); - let txout_index = &self.indexed_graph.index; - let chain_tip = self.chain.tip().block_id(); - let chain_positions = graph - .list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default()) - .map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position)) - .collect::>(); - - let mut tx = graph - .get_tx(txid) - .ok_or(BuildFeeBumpError::TransactionNotFound(txid))? - .as_ref() - .clone(); - - if chain_positions - .get(&txid) - .ok_or(BuildFeeBumpError::TransactionNotFound(txid))? - .is_confirmed() - { - return Err(BuildFeeBumpError::TransactionConfirmed(txid)); - } - - if !tx - .input - .iter() - .any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD) - { - return Err(BuildFeeBumpError::IrreplaceableTransaction( - tx.compute_txid(), - )); - } - - let fee = self - .calculate_fee(&tx) - .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?; - let fee_rate = self - .calculate_fee_rate(&tx) - .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?; - - // remove the inputs from the tx and process them - let utxos = tx - .input - .drain(..) - .map(|txin| -> Result<_, BuildFeeBumpError> { - graph - // Get previous transaction - .get_tx(txin.previous_output.txid) - .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output)) - // Get chain position - .and_then(|prev_tx| { - let chain_position = chain_positions - .get(&txin.previous_output.txid) - .cloned() - .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?; - let prev_txout = prev_tx - .output - .get(txin.previous_output.vout as usize) - .ok_or(BuildFeeBumpError::InvalidOutputIndex(txin.previous_output)) - .cloned()?; - Ok((prev_tx, prev_txout, chain_position)) - }) - .map(|(prev_tx, prev_txout, chain_position)| { - match txout_index.index_of_spk(prev_txout.script_pubkey.clone()) { - Some(&(keychain, derivation_index)) => WeightedUtxo { - satisfaction_weight: self - .public_descriptor(keychain) - .max_weight_to_satisfy() - .unwrap(), - utxo: Utxo::Local(LocalOutput { - outpoint: txin.previous_output, - txout: prev_txout.clone(), - keychain, - is_spent: true, - derivation_index, - chain_position, - }), - }, - None => { - let satisfaction_weight = Weight::from_wu_usize( - serialize(&txin.script_sig).len() * 4 - + serialize(&txin.witness).len(), - ); - WeightedUtxo { - utxo: Utxo::Foreign { - outpoint: txin.previous_output, - sequence: txin.sequence, - psbt_input: Box::new(psbt::Input { - witness_utxo: prev_txout - .script_pubkey - .witness_version() - .map(|_| prev_txout.clone()), - non_witness_utxo: Some(prev_tx.as_ref().clone()), - ..Default::default() - }), - }, - satisfaction_weight, - } - } - } - }) - }) - .collect::, BuildFeeBumpError>>()?; - - if tx.output.len() > 1 { - let mut change_index = None; - for (index, txout) in tx.output.iter().enumerate() { - let change_keychain = self.map_keychain(KeychainKind::Internal); - match txout_index.index_of_spk(txout.script_pubkey.clone()) { - Some((keychain, _)) if *keychain == change_keychain => { - change_index = Some(index) - } - _ => {} - } - } - - if let Some(change_index) = change_index { - tx.output.remove(change_index); - } - } - - let params = TxParams { - // TODO: figure out what rbf option should be? - version: Some(tx.version), - recipients: tx - .output - .into_iter() - .map(|txout| (txout.script_pubkey, txout.value)) - .collect(), - utxos, - bumping_fee: Some(tx_builder::PreviousFee { - absolute: fee, - rate: fee_rate, - }), - ..Default::default() - }; - - Ok(TxBuilder { - wallet: self, - params, - coin_selection: DefaultCoinSelectionAlgorithm::default(), - }) - } - - /// Sign a transaction with all the wallet's signers, in the order specified by every signer's - /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that - /// has the value true if the PSBT was finalized, or false otherwise. - /// - /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way - /// the transaction is finalized at the end. Note that it can't be guaranteed that *every* - /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined - /// in this library will. - /// - /// ## Example - /// - /// ``` - /// # use std::str::FromStr; - /// # use bitcoin::*; - /// # use bdk_wallet::*; - /// # use bdk_wallet::ChangeSet; - /// # use bdk_wallet::error::CreateTxError; - /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; - /// # let mut wallet = doctest_wallet!(); - /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); - /// let mut psbt = { - /// let mut builder = wallet.build_tx(); - /// builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); - /// builder.finish()? - /// }; - /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?; - /// assert!(finalized, "we should have signed all the inputs"); - /// # Ok::<(),anyhow::Error>(()) - pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result { - // This adds all the PSBT metadata for the inputs, which will help us later figure out how - // to derive our keys - self.update_psbt_with_descriptor(psbt) - .map_err(SignerError::MiniscriptPsbt)?; - - // If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and - // finalized ones) has the `non_witness_utxo` - if !sign_options.trust_witness_utxo - && psbt - .inputs - .iter() - .filter(|i| i.final_script_witness.is_none() && i.final_script_sig.is_none()) - .filter(|i| i.tap_internal_key.is_none() && i.tap_merkle_root.is_none()) - .any(|i| i.non_witness_utxo.is_none()) - { - return Err(SignerError::MissingNonWitnessUtxo); - } - - // If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input - // is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot - if !sign_options.allow_all_sighashes - && !psbt.inputs.iter().all(|i| { - i.sighash_type.is_none() - || i.sighash_type == Some(EcdsaSighashType::All.into()) - || i.sighash_type == Some(TapSighashType::All.into()) - || i.sighash_type == Some(TapSighashType::Default.into()) - }) - { - return Err(SignerError::NonStandardSighash); - } - - for signer in self - .signers - .signers() - .iter() - .chain(self.change_signers.signers().iter()) - { - signer.sign_transaction(psbt, &sign_options, &self.secp)?; - } - - // attempt to finalize - if sign_options.try_finalize { - self.finalize_psbt(psbt, sign_options) - } else { - Ok(false) - } - } - - /// Return the spending policies for the wallet's descriptor - pub fn policies(&self, keychain: KeychainKind) -> Result, DescriptorError> { - let signers = match keychain { - KeychainKind::External => &self.signers, - KeychainKind::Internal => &self.change_signers, - }; - - self.public_descriptor(keychain).extract_policy( - signers, - BuildSatisfaction::None, - &self.secp, - ) - } - - /// Returns the descriptor used to create addresses for a particular `keychain`. - /// - /// It's the "public" version of the wallet's descriptor, meaning a new descriptor that has - /// the same structure but with the all secret keys replaced by their corresponding public key. - /// This can be used to build a watch-only version of a wallet. - pub fn public_descriptor(&self, keychain: KeychainKind) -> &ExtendedDescriptor { - self.indexed_graph - .index - .get_descriptor(self.map_keychain(keychain)) - .expect("keychain must exist") - } - - /// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass - /// validation and construct the respective `scriptSig` or `scriptWitness`. Please refer to - /// [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Input_Finalizer), - /// and [BIP371](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki) - /// for further information. - /// - /// Returns `true` if the PSBT could be finalized, and `false` otherwise. - /// - /// The [`SignOptions`] can be used to tweak the behavior of the finalizer. - pub fn finalize_psbt( - &self, - psbt: &mut Psbt, - sign_options: SignOptions, - ) -> Result { - let tx = &psbt.unsigned_tx; - let chain_tip = self.chain.tip().block_id(); - let prev_txids = tx - .input - .iter() - .map(|txin| txin.previous_output.txid) - .collect::>(); - let confirmation_heights = self - .indexed_graph - .graph() - .list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default()) - .filter(|canon_tx| prev_txids.contains(&canon_tx.tx_node.txid)) - // This is for a small performance gain. Although `.filter` filters out excess txs, it - // will still consume the internal `CanonicalIter` entirely. Having a `.take` here - // allows us to stop further unnecessary canonicalization. - .take(prev_txids.len()) - .map(|canon_tx| { - let txid = canon_tx.tx_node.txid; - match canon_tx.chain_position { - ChainPosition::Confirmed { anchor, .. } => (txid, anchor.block_id.height), - ChainPosition::Unconfirmed { .. } => (txid, u32::MAX), - } - }) - .collect::>(); - - let mut finished = true; - - for (n, input) in tx.input.iter().enumerate() { - let psbt_input = &psbt - .inputs - .get(n) - .ok_or(IndexOutOfBoundsError::new(n, psbt.inputs.len()))?; - if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() { - continue; - } - let confirmation_height = confirmation_heights - .get(&input.previous_output.txid) - .copied(); - let current_height = sign_options - .assume_height - .unwrap_or_else(|| self.chain.tip().height()); - - // - Try to derive the descriptor by looking at the txout. If it's in our database, we - // know exactly which `keychain` to use, and which derivation index it is - // - If that fails, try to derive it by looking at the psbt input: the complete logic is - // in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`, - // `redeem_script` and `witness_script` to determine the right derivation - // - If that also fails, it will try it on the internal descriptor, if present - let desc = psbt - .get_utxo_for(n) - .and_then(|txout| self.get_descriptor_for_txout(&txout)) - .or_else(|| { - self.indexed_graph.index.keychains().find_map(|(_, desc)| { - desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp) - }) - }); - - match desc { - Some(desc) => { - let mut tmp_input = bitcoin::TxIn::default(); - match desc.satisfy( - &mut tmp_input, - ( - PsbtInputSatisfier::new(psbt, n), - After::new(Some(current_height), false), - Older::new(Some(current_height), confirmation_height, false), - ), - ) { - Ok(_) => { - let length = psbt.inputs.len(); - // Set the UTXO fields, final script_sig and witness - // and clear everything else. - let psbt_input = psbt - .inputs - .get_mut(n) - .ok_or(IndexOutOfBoundsError::new(n, length))?; - let original = mem::take(psbt_input); - psbt_input.non_witness_utxo = original.non_witness_utxo; - psbt_input.witness_utxo = original.witness_utxo; - if !tmp_input.script_sig.is_empty() { - psbt_input.final_script_sig = Some(tmp_input.script_sig); - } - if !tmp_input.witness.is_empty() { - psbt_input.final_script_witness = Some(tmp_input.witness); - } - } - Err(_) => finished = false, - } - } - None => finished = false, - } - } - - // Clear derivation paths from outputs - if finished { - for output in &mut psbt.outputs { - output.bip32_derivation.clear(); - output.tap_key_origins.clear(); - } - } - - Ok(finished) - } - - /// Return the secp256k1 context used for all signing operations - pub fn secp_ctx(&self) -> &SecpCtx { - &self.secp - } - - /// The derivation index of this wallet. It will return `None` if it has not derived any - /// addresses. Otherwise, it will return the index of the highest address it has derived. - pub fn derivation_index(&self, keychain: KeychainKind) -> Option { - self.indexed_graph.index.last_revealed_index(keychain) - } - - /// 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 { - self.indexed_graph - .index - .next_index(self.map_keychain(keychain)) - .expect("keychain must exist") - .0 - } - - /// Informs the wallet that you no longer intend to broadcast a tx that was built from it. - /// - /// This frees up the change address used when creating the tx for use in future transactions. - // TODO: Make this free up reserved utxos when that's implemented - pub fn cancel_tx(&mut self, tx: &Transaction) { - let txout_index = &mut self.indexed_graph.index; - for txout in &tx.output { - if let Some((keychain, index)) = txout_index.index_of_spk(txout.script_pubkey.clone()) { - // NOTE: unmark_used will **not** make something unused if it has actually been used - // by a tx in the tracker. It only removes the superficial marking. - txout_index.unmark_used(*keychain, *index); - } - } - } - - fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option { - let &(keychain, child) = self - .indexed_graph - .index - .index_of_spk(txout.script_pubkey.clone())?; - let descriptor = self.public_descriptor(keychain); - descriptor.at_derivation_index(child).ok() - } - - /// Given the options returns the list of utxos that must be used to form the - /// transaction and any further that may be used if needed. - fn filter_utxos(&self, params: &TxParams, current_height: u32) -> Vec { - if params.manually_selected_only { - vec![] - // only process optional UTxOs if manually_selected_only is false - } else { - let manually_selected_outpoints = params - .utxos - .iter() - .map(|wutxo| wutxo.utxo.outpoint()) - .collect::>(); - self.indexed_graph - .graph() - // get all unspent UTxOs from wallet - // NOTE: the UTxOs returned by the following method already belong to wallet as the - // call chain uses get_tx_node infallibly - .filter_chain_unspents( - &self.chain, - self.chain.tip().block_id(), - CanonicalizationParams::default(), - self.indexed_graph.index.outpoints().iter().cloned(), - ) - // only create LocalOutput if UTxO is mature - .filter_map(move |((k, i), full_txo)| { - full_txo - .is_mature(current_height) - .then(|| new_local_utxo(k, i, full_txo)) - }) - // only process UTXOs not selected manually, they will be considered later in the - // chain - // NOTE: this avoid UTXOs in both required and optional list - .filter(|may_spend| !manually_selected_outpoints.contains(&may_spend.outpoint)) - // only add to optional UTxOs those which satisfy the change policy if we reuse - // change - .filter(|local_output| { - self.keychains().count() == 1 - || params.change_policy.is_satisfied_by(local_output) - }) - // only add to optional UTxOs those marked as spendable - .filter(|local_output| !params.unspendable.contains(&local_output.outpoint)) - // if bumping fees only add to optional UTxOs those confirmed - .filter(|local_output| { - params.bumping_fee.is_none() || local_output.chain_position.is_confirmed() - }) - .map(|utxo| WeightedUtxo { - satisfaction_weight: self - .public_descriptor(utxo.keychain) - .max_weight_to_satisfy() - .unwrap(), - utxo: Utxo::Local(utxo), - }) - .collect() - } - } - - fn complete_transaction( - &self, - tx: Transaction, - selected: Vec, - params: TxParams, - ) -> Result { - let mut psbt = Psbt::from_unsigned_tx(tx)?; - - if params.add_global_xpubs { - let all_xpubs = self - .keychains() - .flat_map(|(_, desc)| desc.get_extended_keys()) - .collect::>(); - - for xpub in all_xpubs { - let origin = match xpub.origin { - Some(origin) => origin, - None if xpub.xkey.depth == 0 => { - (xpub.root_fingerprint(&self.secp), vec![].into()) - } - _ => return Err(CreateTxError::MissingKeyOrigin(xpub.xkey.to_string())), - }; - - psbt.xpub.insert(xpub.xkey, origin); - } - } - - let mut lookup_output = selected - .into_iter() - .map(|utxo| (utxo.outpoint(), utxo)) - .collect::>(); - - // add metadata for the inputs - for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) { - let utxo = match lookup_output.remove(&input.previous_output) { - Some(utxo) => utxo, - None => continue, - }; - - match utxo { - Utxo::Local(utxo) => { - *psbt_input = - match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) { - Ok(psbt_input) => psbt_input, - Err(e) => match e { - CreateTxError::UnknownUtxo => psbt::Input { - sighash_type: params.sighash, - ..psbt::Input::default() - }, - _ => return Err(e), - }, - } - } - Utxo::Foreign { - outpoint, - psbt_input: foreign_psbt_input, - .. - } => { - let is_taproot = foreign_psbt_input - .witness_utxo - .as_ref() - .map(|txout| txout.script_pubkey.is_p2tr()) - .unwrap_or(false); - if !is_taproot - && !params.only_witness_utxo - && foreign_psbt_input.non_witness_utxo.is_none() - { - return Err(CreateTxError::MissingNonWitnessUtxo(outpoint)); - } - *psbt_input = *foreign_psbt_input; - } - } - } - - self.update_psbt_with_descriptor(&mut psbt)?; - - Ok(psbt) - } - - /// get the corresponding PSBT Input for a LocalUtxo - pub fn get_psbt_input( - &self, - utxo: LocalOutput, - sighash_type: Option, - only_witness_utxo: bool, - ) -> Result { - // Try to find the prev_script in our db to figure out if this is internal or external, - // and the derivation index - let &(keychain, child) = self - .indexed_graph - .index - .index_of_spk(utxo.txout.script_pubkey) - .ok_or(CreateTxError::UnknownUtxo)?; - - let mut psbt_input = psbt::Input { - sighash_type, - ..psbt::Input::default() - }; - - let desc = self.public_descriptor(keychain); - let derived_descriptor = desc - .at_derivation_index(child) - .expect("child can't be hardened"); - - psbt_input - .update_with_descriptor_unchecked(&derived_descriptor) - .map_err(MiniscriptPsbtError::Conversion)?; - - let prev_output = utxo.outpoint; - if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) { - // We want to check that the prevout actually exists in the tx before continuing. - let prevout = prev_tx.output.get(prev_output.vout as usize).ok_or( - MiniscriptPsbtError::UtxoUpdate(miniscript::psbt::UtxoUpdateError::UtxoCheck), - )?; - if desc.is_witness() || desc.is_taproot() { - psbt_input.witness_utxo = Some(prevout.clone()); - } - if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) { - psbt_input.non_witness_utxo = Some(prev_tx.as_ref().clone()); - } - } - Ok(psbt_input) - } - - fn update_psbt_with_descriptor(&self, psbt: &mut Psbt) -> Result<(), MiniscriptPsbtError> { - // We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for all - // the input utxos and outputs - let utxos = (0..psbt.inputs.len()) - .filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo))) - .chain( - psbt.unsigned_tx - .output - .iter() - .enumerate() - .map(|(i, out)| (false, i, out.clone())), - ) - .collect::>(); - - // Try to figure out the keychain and derivation for every input and output - for (is_input, index, out) in utxos.into_iter() { - if let Some(&(keychain, child)) = - self.indexed_graph.index.index_of_spk(out.script_pubkey) - { - let desc = self.public_descriptor(keychain); - let desc = desc - .at_derivation_index(child) - .expect("child can't be hardened"); - - if is_input { - psbt.update_input_with_descriptor(index, &desc) - .map_err(MiniscriptPsbtError::UtxoUpdate)?; - } else { - psbt.update_output_with_descriptor(index, &desc) - .map_err(MiniscriptPsbtError::OutputUpdate)?; - } - } - } - - Ok(()) - } - - /// Return the checksum of the public descriptor associated to `keychain` - /// - /// Internally calls [`Self::public_descriptor`] to fetch the right descriptor - pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String { - self.public_descriptor(keychain) - .to_string() - .split_once('#') - .unwrap() - .1 - .to_string() - } - - /// Applies an update to the wallet and stages the changes (but does not persist them). - /// - /// Usually you create an `update` by interacting with some blockchain data source and inserting - /// transactions related to your wallet into it. - /// - /// After applying updates you should persist the staged wallet changes. For an example of how - /// to persist staged wallet changes see [`Wallet::reveal_next_address`]. - pub fn apply_update(&mut self, update: impl Into) -> Result<(), CannotConnectError> { - let update = update.into(); - let mut changeset = match update.chain { - Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?), - None => ChangeSet::default(), - }; - - let index_changeset = self - .indexed_graph - .index - .reveal_to_target_multi(&update.last_active_indices); - changeset.merge(index_changeset.into()); - changeset.merge(self.indexed_graph.apply_update(update.tx_update).into()); - self.stage.merge(changeset); - Ok(()) - } - - /// Get a reference of the staged [`ChangeSet`] that is yet to be committed (if any). - pub fn staged(&self) -> Option<&ChangeSet> { - if self.stage.is_empty() { - None - } else { - Some(&self.stage) - } - } - - /// Get a mutable reference of the staged [`ChangeSet`] that is yet to be committed (if any). - pub fn staged_mut(&mut self) -> Option<&mut ChangeSet> { - if self.stage.is_empty() { - None - } else { - Some(&mut self.stage) - } - } - - /// Take the staged [`ChangeSet`] to be persisted now (if any). - pub fn take_staged(&mut self) -> Option { - self.stage.take() - } - - /// Get a reference to the inner [`TxGraph`]. - pub fn tx_graph(&self) -> &TxGraph { - self.indexed_graph.graph() - } - - /// Get a reference to the inner [`KeychainTxOutIndex`]. - pub fn spk_index(&self) -> &KeychainTxOutIndex { - &self.indexed_graph.index - } - - /// Get a reference to the inner [`LocalChain`]. - pub fn local_chain(&self) -> &LocalChain { - &self.chain - } - - /// Introduces a `block` of `height` to the wallet, and tries to connect it to the - /// `prev_blockhash` of the block's header. - /// - /// This is a convenience method that is equivalent to calling [`apply_block_connected_to`] - /// with `prev_blockhash` and `height-1` as the `connected_to` parameter. - /// - /// [`apply_block_connected_to`]: Self::apply_block_connected_to - pub fn apply_block(&mut self, block: &Block, height: u32) -> Result<(), CannotConnectError> { - let connected_to = match height.checked_sub(1) { - Some(prev_height) => BlockId { - height: prev_height, - hash: block.header.prev_blockhash, - }, - None => BlockId { - height, - hash: block.block_hash(), - }, - }; - self.apply_block_connected_to(block, height, connected_to) - .map_err(|err| match err { - ApplyHeaderError::InconsistentBlocks => { - unreachable!("connected_to is derived from the block so must be consistent") - } - ApplyHeaderError::CannotConnect(err) => err, - }) - } - - /// Applies relevant transactions from `block` of `height` to the wallet, and connects the - /// block to the internal chain. - /// - /// The `connected_to` parameter informs the wallet how this block connects to the internal - /// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the - /// internal [`TxGraph`]. - /// - /// **WARNING**: You must persist the changes resulting from one or more calls to this method - /// if you need the inserted block data to be reloaded after closing the wallet. - /// See [`Wallet::reveal_next_address`]. - pub fn apply_block_connected_to( - &mut self, - block: &Block, - height: u32, - connected_to: BlockId, - ) -> Result<(), ApplyHeaderError> { - let mut changeset = ChangeSet::default(); - changeset.merge( - self.chain - .apply_header_connected_to(&block.header, height, connected_to)? - .into(), - ); - changeset.merge( - self.indexed_graph - .apply_block_relevant(block, height) - .into(), - ); - self.stage.merge(changeset); - Ok(()) - } - - /// Apply relevant unconfirmed transactions to the wallet. - /// - /// Transactions that are not relevant are filtered out. - /// - /// This method takes in an iterator of `(tx, last_seen)` where `last_seen` is the timestamp of - /// when the transaction was last seen in the mempool. This is used for conflict resolution - /// when there is conflicting unconfirmed transactions. The transaction with the later - /// `last_seen` is prioritized. - /// - /// **WARNING**: You must persist the changes resulting from one or more calls to this method - /// if you need the applied unconfirmed transactions to be reloaded after closing the wallet. - /// See [`Wallet::reveal_next_address`]. - pub fn apply_unconfirmed_txs>>( - &mut self, - unconfirmed_txs: impl IntoIterator, - ) { - let indexed_graph_changeset = self - .indexed_graph - .batch_insert_relevant_unconfirmed(unconfirmed_txs); - self.stage.merge(indexed_graph_changeset.into()); - } - - /// Apply evictions of the given transaction IDs with their associated timestamps. - /// - /// This function is used to mark specific unconfirmed transactions as evicted from the mempool. - /// Eviction means that these transactions are not considered canonical by default, and will - /// no longer be part of the wallet's [`transactions`] set. This can happen for example when - /// a transaction is dropped from the mempool due to low fees or conflicts with another - /// transaction. - /// - /// Only transactions that are currently unconfirmed and canonical are considered for eviction. - /// Transactions that are not relevant to the wallet are ignored. Note that an evicted - /// transaction can become canonical again if it is later observed on-chain or seen in the - /// mempool with a higher priority (e.g., due to a fee bump). - /// - /// ## Parameters - /// - /// `evicted_txs`: An iterator of `(Txid, u64)` tuples, where: - /// - `Txid`: The transaction ID of the transaction to be evicted. - /// - `u64`: The timestamp indicating when the transaction was evicted from the mempool. This - /// will usually correspond to the time of the latest chain sync. See docs for - /// [`start_sync_with_revealed_spks`]. - /// - /// ## Notes - /// - /// - Not all blockchain backends support automatic mempool eviction handling - this method may - /// be used in such cases. It can also be used to negate the effect of - /// [`apply_unconfirmed_txs`] for a particular transaction without the need for an additional - /// sync. - /// - The changes are staged in the wallet's internal state and must be persisted to ensure they - /// are retained across wallet restarts. Use [`Wallet::take_staged`] to retrieve the staged - /// changes and persist them to your database of choice. - /// - Evicted transactions are removed from the wallet's canonical transaction set, but the data - /// remains in the wallet's internal transaction graph for historical purposes. - /// - Ensure that the timestamps provided are accurate and monotonically increasing, as they - /// influence the wallet's canonicalization logic. - /// - /// [`transactions`]: Wallet::transactions - /// [`apply_unconfirmed_txs`]: Wallet::apply_unconfirmed_txs - /// [`start_sync_with_revealed_spks`]: Wallet::start_sync_with_revealed_spks - pub fn apply_evicted_txs(&mut self, evicted_txs: impl IntoIterator) { - let chain = &self.chain; - let canon_txids: Vec = self - .indexed_graph - .graph() - .list_canonical_txs( - chain, - chain.tip().block_id(), - CanonicalizationParams::default(), - ) - .map(|c| c.tx_node.txid) - .collect(); - - let changeset = self.indexed_graph.batch_insert_relevant_evicted_at( - evicted_txs - .into_iter() - .filter(|(txid, _)| canon_txids.contains(txid)), - ); - - self.stage.merge(changeset.into()); - } - - /// Used internally to ensure that all methods requiring a [`KeychainKind`] will use a - /// keychain with an associated descriptor. For example in case the wallet was created - /// with only one keychain, passing [`KeychainKind::Internal`] here will instead return - /// [`KeychainKind::External`]. - fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind { - if self.keychains().count() == 1 { - KeychainKind::External - } else { - keychain - } - } -} - -/// Methods to construct sync/full-scan requests for spk-based chain sources. -impl Wallet { - /// Create a partial [`SyncRequest`] for all revealed spks at `start_time`. - /// - /// The `start_time` is used to record the time that a mempool transaction was last seen - /// (or evicted). See [`Wallet::start_sync_with_revealed_spks`] for more. - pub fn start_sync_with_revealed_spks_at( - &self, - start_time: u64, - ) -> SyncRequestBuilder<(KeychainKind, u32)> { - use bdk_chain::keychain_txout::SyncRequestBuilderExt; - SyncRequest::builder_at(start_time) - .chain_tip(self.chain.tip()) - .revealed_spks_from_indexer(&self.indexed_graph.index, ..) - .expected_spk_txids(self.indexed_graph.list_expected_spk_txids( - &self.chain, - self.chain.tip().block_id(), - .., - )) - } - - /// Create a partial [`SyncRequest`] for this wallet for all revealed spks. - /// - /// This is the first step when performing a spk-based wallet partial sync, the returned - /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to - /// start a blockchain sync with a spk based blockchain client. - /// - /// The time of the sync is the current system time and is used to record the - /// tx last-seen for mempool transactions. Or if an expected transaction is missing - /// or evicted, it is the time of the eviction. Note that timestamps may only increase - /// to be counted by the tx graph. To supply your own start time see - /// [`Wallet::start_sync_with_revealed_spks_at`]. - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - #[cfg(feature = "std")] - pub fn start_sync_with_revealed_spks(&self) -> SyncRequestBuilder<(KeychainKind, u32)> { - use bdk_chain::keychain_txout::SyncRequestBuilderExt; - SyncRequest::builder() - .chain_tip(self.chain.tip()) - .revealed_spks_from_indexer(&self.indexed_graph.index, ..) - .expected_spk_txids(self.indexed_graph.list_expected_spk_txids( - &self.chain, - self.chain.tip().block_id(), - .., - )) - } - - /// Create a [`FullScanRequest] for this wallet. - /// - /// This is the first step when performing a spk-based wallet full scan, the returned - /// [`FullScanRequest] collects iterators for the wallet's keychain script pub keys needed to - /// start a blockchain full scan with a spk based blockchain client. - /// - /// This operation is generally only used when importing or restoring a previously used wallet - /// in which the list of used scripts is not known. - /// - /// The time of the scan is the current system time and is used to record the tx last-seen for - /// mempool transactions. To supply your own start time see [`Wallet::start_full_scan_at`]. - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - #[cfg(feature = "std")] - pub fn start_full_scan(&self) -> FullScanRequestBuilder { - use bdk_chain::keychain_txout::FullScanRequestBuilderExt; - FullScanRequest::builder() - .chain_tip(self.chain.tip()) - .spks_from_indexer(&self.indexed_graph.index) - } - - /// Create a [`FullScanRequest`] builder at `start_time`. - pub fn start_full_scan_at(&self, start_time: u64) -> FullScanRequestBuilder { - use bdk_chain::keychain_txout::FullScanRequestBuilderExt; - FullScanRequest::builder_at(start_time) - .chain_tip(self.chain.tip()) - .spks_from_indexer(&self.indexed_graph.index) - } -} - -impl AsRef> for Wallet { - fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph { - self.indexed_graph.graph() - } -} +// impl Wallet { +// /// Build a new single descriptor [`Wallet`]. +// /// +// /// If you have previously created a wallet, use [`load`](Self::load) instead. +// /// +// /// # Note +// /// +// /// Only use this method when creating a wallet designed to be used with a single +// /// descriptor and keychain. Otherwise the recommended way to construct a new wallet is +// /// by using [`Wallet::create`]. It's worth noting that not all features are available +// /// with single descriptor wallets, for example setting a [`change_policy`] on [`TxBuilder`] +// /// and related methods such as [`do_not_spend_change`]. This is because all payments are +// /// received on the external keychain (including change), and without a change keychain +// /// BDK lacks enough information to distinguish between change and outside payments. +// /// +// /// Additionally because this wallet has no internal (change) keychain, all methods that +// /// require a [`KeychainKind`] as input, e.g. [`reveal_next_address`] should only be called +// /// using the [`External`] variant. In most cases passing [`Internal`] is treated as the +// /// equivalent of [`External`] but this behavior must not be relied on. +// /// +// /// # Example +// /// +// /// ```rust +// /// # use bdk_wallet::Wallet; +// /// # use bitcoin::Network; +// /// # const EXTERNAL_DESC: &str = +// "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/ +// 84'/1'/0'/0/*)"; /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// /// # let file_path = temp_dir.path().join("store.db"); +// /// // Create a wallet that is persisted to SQLite database. +// /// use bdk_wallet::rusqlite::Connection; +// /// let mut conn = Connection::open(file_path)?; +// /// let wallet = Wallet::create_single(EXTERNAL_DESC) +// /// .network(Network::Testnet) +// /// .create_wallet(&mut conn)?; +// /// # Ok::<_, anyhow::Error>(()) +// /// ``` +// /// [`change_policy`]: TxBuilder::change_policy +// /// [`do_not_spend_change`]: TxBuilder::do_not_spend_change +// /// [`External`]: KeychainKind::External +// /// [`Internal`]: KeychainKind::Internal +// /// [`reveal_next_address`]: Self::reveal_next_address +// pub fn create_single(descriptor: D) -> CreateParams +// where +// D: IntoWalletDescriptor + Send + Clone + 'static, +// { +// CreateParams::new_single(descriptor) +// } + +// /// Build a new [`Wallet`]. +// /// +// /// If you have previously created a wallet, use [`load`](Self::load) instead. +// /// +// /// # Synopsis +// /// +// /// ```rust +// /// # use bdk_wallet::Wallet; +// /// # use bitcoin::Network; +// /// # fn main() -> anyhow::Result<()> { +// /// # const EXTERNAL_DESC: &str = +// "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/ +// 84'/1'/0'/0/*)"; /// # const INTERNAL_DESC: &str = +// "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/ +// 84'/1'/0'/1/*)"; /// // Create a non-persisted wallet. +// /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// +// /// // Create a wallet that is persisted to SQLite database. +// /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// /// # let file_path = temp_dir.path().join("store.db"); +// /// use bdk_wallet::rusqlite::Connection; +// /// let mut conn = Connection::open(file_path)?; +// /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) +// /// .network(Network::Testnet) +// /// .create_wallet(&mut conn)?; +// /// # Ok(()) +// /// # } +// /// ``` +// pub fn create(descriptor: D, change_descriptor: D) -> CreateParams +// where +// D: IntoWalletDescriptor + Send + Clone + 'static, +// { +// CreateParams::new(descriptor, change_descriptor) +// } + +// /// Build a new [`Wallet`] from a two-path descriptor. +// /// +// /// This function parses a multipath descriptor with exactly 2 paths and creates a wallet +// /// using the existing receive and change wallet creation logic. Note that you can only use +// this /// method with public extended keys (`xpub` prefix) to create watch-only wallets. +// /// +// /// Multipath descriptors follow [BIP 389] and allow defining both receive and change +// /// derivation paths in a single descriptor using the `<0;1>` syntax. +// /// +// /// If you have previously created a wallet, use [`load`](Self::load) instead. +// /// +// /// # Errors +// /// Returns an error if the descriptor is invalid, not a 2-path multipath descriptor, or if +// /// the descriptor provided contains an extended private key (`xprv` prefix). +// /// +// /// # Synopsis +// /// +// /// ```rust +// /// # use bdk_wallet::Wallet; +// /// # use bitcoin::Network; +// /// # use bdk_wallet::KeychainKind; +// /// # const TWO_PATH_DESC: &str = +// "wpkh([9a6a2580/84'/1'/0' +// ]tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/ +// <0;1>/*)"; /// let wallet = Wallet::create_from_two_path_descriptor(TWO_PATH_DESC) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist() +// /// .unwrap(); +// /// +// /// // The multipath descriptor automatically creates separate receive and change descriptors +// /// let receive_addr = wallet.peek_address(KeychainKind::External, 0); // Uses path /0/* +// /// let change_addr = wallet.peek_address(KeychainKind::Internal, 0); // Uses path /1/* +// /// assert_ne!(receive_addr.address, change_addr.address); +// /// ``` +// /// +// /// [BIP 389]: https://github.com/bitcoin/bips/blob/master/bip-0389.mediawiki +// pub fn create_from_two_path_descriptor(two_path_descriptor: D) -> CreateParams +// where +// D: IntoWalletDescriptor + Send + Clone + 'static, +// { +// CreateParams::new_two_path(two_path_descriptor) +// } + +// /// Create a new [`Wallet`] with given `params`. +// /// +// /// Refer to [`Wallet::create`] for more. +// pub fn create_with_params(params: CreateParams) -> Result { +// let secp = SecpCtx::new(); +// let network = params.network; +// let genesis_hash = params +// .genesis_hash +// .unwrap_or(genesis_block(network).block_hash()); +// let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); + +// let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network)?; +// check_wallet_descriptor(&descriptor)?; +// descriptor_keymap.extend(params.descriptor_keymap); + +// let signers = Arc::new(SignersContainer::build( +// descriptor_keymap, +// &descriptor, +// &secp, +// )); + +// let (change_descriptor, change_signers) = match params.change_descriptor { +// Some(make_desc) => { +// let (change_descriptor, mut internal_keymap) = make_desc(&secp, network)?; +// check_wallet_descriptor(&change_descriptor)?; +// internal_keymap.extend(params.change_descriptor_keymap); +// let change_signers = Arc::new(SignersContainer::build( +// internal_keymap, +// &change_descriptor, +// &secp, +// )); +// (Some(change_descriptor), change_signers) +// } +// None => (None, Arc::new(SignersContainer::new())), +// }; + +// let mut stage = ChangeSet { +// descriptor: Some(descriptor.clone()), +// change_descriptor: change_descriptor.clone(), +// local_chain: chain_changeset, +// network: Some(network), +// ..Default::default() +// }; + +// let indexed_graph = make_indexed_graph( +// &mut stage, +// Default::default(), +// Default::default(), +// descriptor, +// change_descriptor, +// params.lookahead, +// params.use_spk_cache, +// )?; + +// Ok(Wallet { +// signers, +// change_signers, +// network, +// chain, +// indexed_graph, +// stage, +// secp, +// }) +// } + +// /// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. +// /// +// /// Note that the descriptor secret keys are not persisted to the db. You can add +// /// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. You +// /// can also add keys when building the wallet by using [`LoadParams::keymap`]. Finally +// /// you can check the wallet's descriptors are what you expect with +// [`LoadParams::descriptor`] /// which will try to populate signers if +// [`LoadParams::extract_keys`] is enabled. /// +// /// # Synopsis +// /// +// /// ```rust,no_run +// /// # use bdk_wallet::{Wallet, ChangeSet, KeychainKind}; +// /// # use bitcoin::{BlockHash, Network, hashes::Hash}; +// /// # fn main() -> anyhow::Result<()> { +// /// # const EXTERNAL_DESC: &str = +// "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/ +// 84'/1'/0'/0/*)"; /// # const INTERNAL_DESC: &str = +// "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/ +// 84'/1'/0'/1/*)"; /// # let changeset = ChangeSet::default(); +// /// // Load a wallet from changeset (no persistence). +// /// let wallet = Wallet::load() +// /// .load_wallet_no_persist(changeset)? +// /// .expect("must have data to load wallet"); +// /// +// /// // Load a wallet that is persisted to SQLite database. +// /// # let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// /// # let file_path = temp_dir.path().join("store.db"); +// /// # let external_keymap = Default::default(); +// /// # let internal_keymap = Default::default(); +// /// # let genesis_hash = BlockHash::all_zeros(); +// /// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?; +// /// let mut wallet = Wallet::load() +// /// // check loaded descriptors matches these values and extract private keys +// /// .descriptor(KeychainKind::External, Some(EXTERNAL_DESC)) +// /// .descriptor(KeychainKind::Internal, Some(INTERNAL_DESC)) +// /// .extract_keys() +// /// // you can also manually add private keys +// /// .keymap(KeychainKind::External, external_keymap) +// /// .keymap(KeychainKind::Internal, internal_keymap) +// /// // ensure loaded wallet's genesis hash matches this value +// /// .check_genesis_hash(genesis_hash) +// /// // set a lookahead for our indexer +// /// .lookahead(101) +// /// .load_wallet(&mut conn)? +// /// .expect("must have data to load wallet"); +// /// # Ok(()) +// /// # } +// /// ``` +// pub fn load() -> LoadParams { +// LoadParams::new() +// } + +// /// Load [`Wallet`] from the given previously persisted [`ChangeSet`] and `params`. +// /// +// /// Returns `Ok(None)` if the changeset is empty. Refer to [`Wallet::load`] for more. +// pub fn load_with_params( +// changeset: ChangeSet, +// params: LoadParams, +// ) -> Result, LoadError> { +// if changeset.is_empty() { +// return Ok(None); +// } +// let secp = Secp256k1::new(); +// let network = changeset.network.ok_or(LoadError::MissingNetwork)?; +// let chain = LocalChain::from_changeset(changeset.local_chain) +// .map_err(|_| LoadError::MissingGenesis)?; + +// if let Some(exp_network) = params.check_network { +// if network != exp_network { +// return Err(LoadError::Mismatch(LoadMismatch::Network { +// loaded: network, +// expected: exp_network, +// })); +// } +// } +// if let Some(exp_genesis_hash) = params.check_genesis_hash { +// if chain.genesis_hash() != exp_genesis_hash { +// return Err(LoadError::Mismatch(LoadMismatch::Genesis { +// loaded: chain.genesis_hash(), +// expected: exp_genesis_hash, +// })); +// } +// } + +// let descriptor = changeset +// .descriptor +// .ok_or(LoadError::MissingDescriptor(KeychainKind::External))?; +// check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?; +// let mut external_keymap = params.descriptor_keymap; + +// if let Some(expected) = params.check_descriptor { +// if let Some(make_desc) = expected { +// let (exp_desc, keymap) = +// make_desc(&secp, network).map_err(LoadError::Descriptor)?; +// if descriptor.descriptor_id() != exp_desc.descriptor_id() { +// return Err(LoadError::Mismatch(LoadMismatch::Descriptor { +// keychain: KeychainKind::External, +// loaded: Some(descriptor), +// expected: Some(exp_desc), +// })); +// } +// if params.extract_keys { +// external_keymap.extend(keymap); +// } +// } else { +// return Err(LoadError::Mismatch(LoadMismatch::Descriptor { +// keychain: KeychainKind::External, +// loaded: Some(descriptor), +// expected: None, +// })); +// } +// } +// let signers = Arc::new(SignersContainer::build(external_keymap, &descriptor, &secp)); + +// let mut change_descriptor = None; +// let mut internal_keymap = params.change_descriptor_keymap; + +// match (changeset.change_descriptor, params.check_change_descriptor) { +// // empty signer +// (None, None) => {} +// (None, Some(expect)) => { +// // expected desc but none loaded +// if let Some(make_desc) = expect { +// let (exp_desc, _) = make_desc(&secp, +// network).map_err(LoadError::Descriptor)?; return +// Err(LoadError::Mismatch(LoadMismatch::Descriptor { keychain: +// KeychainKind::Internal, loaded: None, +// expected: Some(exp_desc), +// })); +// } +// } +// // nothing expected +// (Some(desc), None) => { +// check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?; +// change_descriptor = Some(desc); +// } +// (Some(desc), Some(expect)) => match expect { +// // expected none for existing +// None => { +// return Err(LoadError::Mismatch(LoadMismatch::Descriptor { +// keychain: KeychainKind::Internal, +// loaded: Some(desc), +// expected: None, +// })) +// } +// // parameters must match +// Some(make_desc) => { +// check_wallet_descriptor(&desc).map_err(LoadError::Descriptor)?; +// let (exp_desc, keymap) = +// make_desc(&secp, network).map_err(LoadError::Descriptor)?; +// if desc.descriptor_id() != exp_desc.descriptor_id() { +// return Err(LoadError::Mismatch(LoadMismatch::Descriptor { +// keychain: KeychainKind::Internal, +// loaded: Some(desc), +// expected: Some(exp_desc), +// })); +// } +// if params.extract_keys { +// internal_keymap.extend(keymap); +// } +// change_descriptor = Some(desc); +// } +// }, +// } + +// let change_signers = match change_descriptor { +// Some(ref change_descriptor) => Arc::new(SignersContainer::build( +// internal_keymap, +// change_descriptor, +// &secp, +// )), +// None => Arc::new(SignersContainer::new()), +// }; + +// let mut stage = ChangeSet::default(); + +// let indexed_graph = make_indexed_graph( +// &mut stage, +// changeset.tx_graph, +// changeset.indexer, +// descriptor, +// change_descriptor, +// params.lookahead, +// params.use_spk_cache, +// ) +// .map_err(LoadError::Descriptor)?; + +// Ok(Some(Wallet { +// signers, +// change_signers, +// chain, +// indexed_graph, +// stage, +// network, +// secp, +// })) +// } + +// /// Get the Bitcoin network the wallet is using. +// pub fn network(&self) -> Network { +// self.network +// } + +// /// Iterator over all keychains in this wallet +// pub fn keychains(&self) -> impl Iterator { +// self.indexed_graph.index.keychains() +// } + +// /// Peek an address of the given `keychain` at `index` without revealing it. +// /// +// /// For non-wildcard descriptors this returns the same address at every provided index. +// /// +// /// # Panics +// /// +// /// 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("keychain must exist"); +// if !spk_iter.descriptor().has_wildcard() { +// index = 0; +// } +// let (index, spk) = spk_iter +// .nth(index as usize) +// .expect("derivation index is out of bounds"); + +// AddressInfo { +// index, +// address: Address::from_script(&spk, self.network).expect("must have address form"), +// keychain, +// } +// } + +// /// Attempt to reveal the next address of the given `keychain`. +// /// +// /// This will increment the keychain's derivation index. If the keychain's descriptor doesn't +// /// contain a wildcard or every address is already revealed up to the maximum derivation +// /// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), +// /// then the last revealed address will be returned. +// /// +// /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or +// more /// calls to this method before closing the wallet. For example: +// /// +// /// ```rust,no_run +// /// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind}; +// /// use bdk_chain::rusqlite::Connection; +// /// let mut conn = Connection::open_in_memory().expect("must open connection"); +// /// let mut wallet = LoadParams::new() +// /// .load_wallet(&mut conn) +// /// .expect("database is okay") +// /// .expect("database has data"); +// /// let next_address = wallet.reveal_next_address(KeychainKind::External); +// /// wallet.persist(&mut conn).expect("write is okay"); +// /// +// /// // Now it's safe to show the user their next address! +// /// println!("Next address: {}", next_address.address); +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> AddressInfo { +// let keychain = self.map_keychain(keychain); +// let index = &mut self.indexed_graph.index; +// let stage = &mut self.stage; + +// let ((index, spk), index_changeset) = index +// .reveal_next_spk(keychain) +// .expect("keychain must exist"); + +// stage.merge(index_changeset.into()); + +// AddressInfo { +// index, +// address: Address::from_script(spk.as_script(), self.network) +// .expect("must have address form"), +// keychain, +// } +// } + +// /// Reveal addresses up to and including the target `index` and return an iterator +// /// of newly revealed addresses. +// /// +// /// If the target `index` is unreachable, we make a best effort to reveal up to the last +// /// possible index. If all addresses up to the given `index` are already revealed, then +// /// no new addresses are returned. +// /// +// /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or +// more /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. +// pub fn reveal_addresses_to( +// &mut self, +// keychain: KeychainKind, +// index: u32, +// ) -> impl Iterator + '_ { +// let keychain = self.map_keychain(keychain); +// let (spks, index_changeset) = self +// .indexed_graph +// .index +// .reveal_to_target(keychain, index) +// .expect("keychain must exist"); + +// self.stage.merge(index_changeset.into()); + +// spks.into_iter().map(move |(index, spk)| AddressInfo { +// index, +// address: Address::from_script(&spk, self.network).expect("must have address form"), +// keychain, +// }) +// } + +// /// Get the next unused address for the given `keychain`, i.e. the address with the lowest +// /// derivation index that hasn't been used in a transaction. +// /// +// /// This will attempt to reveal a new address if all previously revealed addresses have +// /// been used, in which case the returned address will be the same as calling +// /// [`Wallet::reveal_next_address`]. +// /// +// /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or +// more /// calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. +// pub fn next_unused_address(&mut self, keychain: KeychainKind) -> AddressInfo { +// let keychain = self.map_keychain(keychain); +// let index = &mut self.indexed_graph.index; + +// let ((index, spk), index_changeset) = index +// .next_unused_spk(keychain) +// .expect("keychain must exist"); + +// self.stage +// .merge(indexed_tx_graph::ChangeSet::from(index_changeset).into()); + +// AddressInfo { +// index, +// address: Address::from_script(spk.as_script(), self.network) +// .expect("must have address form"), +// keychain, +// } +// } + +// /// Marks an address used of the given `keychain` at `index`. +// /// +// /// Returns whether the given index was present and then removed from the unused set. +// pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> bool { +// self.indexed_graph.index.mark_used(keychain, index) +// } + +// /// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted +// /// back into the unused set. +// /// +// /// Since this is only a superficial marker, it will have no effect if the address at the +// given /// `index` was actually used, i.e. the wallet has previously indexed a tx output for +// the /// derived spk. +// /// +// /// [`mark_used`]: Self::mark_used +// pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> bool { +// self.indexed_graph.index.unmark_used(keychain, index) +// } + +// /// List addresses that are revealed but unused. +// /// +// /// Note if the returned iterator is empty you can reveal more addresses +// /// by using [`reveal_next_address`](Self::reveal_next_address) or +// /// [`reveal_addresses_to`](Self::reveal_addresses_to). +// pub fn list_unused_addresses( +// &self, +// keychain: KeychainKind, +// ) -> impl DoubleEndedIterator + '_ { +// self.indexed_graph +// .index +// .unused_keychain_spks(self.map_keychain(keychain)) +// .map(move |(index, spk)| AddressInfo { +// index, +// address: Address::from_script(spk.as_script(), self.network) +// .expect("must have address form"), +// keychain, +// }) +// } + +// /// Return whether or not a `script` is part of this wallet (either internal or external) +// pub fn is_mine(&self, script: ScriptBuf) -> bool { +// self.indexed_graph.index.index_of_spk(script).is_some() +// } + +// /// Finds how the wallet derived the script pubkey `spk`. +// /// +// /// Will only return `Some(_)` if the wallet has given out the spk. +// pub fn derivation_of_spk(&self, spk: ScriptBuf) -> Option<(KeychainKind, u32)> { +// self.indexed_graph.index.index_of_spk(spk).cloned() +// } + +// /// Return the list of unspent outputs of this wallet +// pub fn list_unspent(&self) -> impl Iterator + '_ { +// self.indexed_graph +// .graph() +// .filter_chain_unspents( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// self.indexed_graph.index.outpoints().iter().cloned(), +// ) +// .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) +// } + +// /// Get the [`TxDetails`] of a wallet transaction. +// /// +// /// If the transaction with txid [`Txid`] cannot be found in the wallet's transactions, +// `None` /// is returned. +// pub fn tx_details(&self, txid: Txid) -> Option { +// let tx: WalletTx = self.transactions().find(|c| c.tx_node.txid == txid)?; + +// let (sent, received) = self.sent_and_received(&tx.tx_node.tx); +// let fee: Option = self.calculate_fee(&tx.tx_node.tx).ok(); +// let fee_rate: Option = self.calculate_fee_rate(&tx.tx_node.tx).ok(); +// let balance_delta: SignedAmount = self.indexed_graph.index.net_value(&tx.tx_node.tx, ..); +// let chain_position = tx.chain_position; + +// let tx_details: TxDetails = TxDetails { +// txid, +// received, +// sent, +// fee, +// fee_rate, +// balance_delta, +// chain_position, +// tx: tx.tx_node.tx, +// }; + +// Some(tx_details) +// } + +// /// List all relevant outputs (includes both spent and unspent, confirmed and unconfirmed). +// /// +// /// To list only unspent outputs (UTXOs), use [`Wallet::list_unspent`] instead. +// pub fn list_output(&self) -> impl Iterator + '_ { +// self.indexed_graph +// .graph() +// .filter_chain_txouts( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// self.indexed_graph.index.outpoints().iter().cloned(), +// ) +// .map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo)) +// } + +// /// Get all the checkpoints the wallet is currently storing indexed by height. +// pub fn checkpoints(&self) -> CheckPointIter { +// self.chain.iter_checkpoints() +// } + +// /// Returns the latest checkpoint. +// pub fn latest_checkpoint(&self) -> CheckPoint { +// self.chain.tip() +// } + +// /// Get unbounded script pubkey iterators for both `Internal` and `External` keychains. +// /// +// /// This is intended to be used when doing a full scan of your addresses (e.g. after +// restoring /// from seed words). You pass the `BTreeMap` of iterators to a blockchain data +// source (e.g. /// electrum server) which will go through each address until it reaches a *stop +// gap*. /// +// /// Note carefully that iterators go over **all** script pubkeys on the keychains (not what +// /// script pubkeys the wallet is storing internally). +// pub fn all_unbounded_spk_iters( +// &self, +// ) -> BTreeMap> + Clone> { +// self.indexed_graph.index.all_unbounded_spk_iters() +// } + +// /// Get an unbounded script pubkey iterator for the given `keychain`. +// /// +// /// See [`all_unbounded_spk_iters`] for more documentation +// /// +// /// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters +// pub fn unbounded_spk_iter( +// &self, +// keychain: KeychainKind, +// ) -> impl Iterator> + Clone { +// self.indexed_graph +// .index +// .unbounded_spk_iter(self.map_keychain(keychain)) +// .expect("keychain must exist") +// } + +// /// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the +// /// wallet's database. +// pub fn get_utxo(&self, op: OutPoint) -> Option { +// let ((keychain, index), _) = self.indexed_graph.index.txout(op)?; +// self.indexed_graph +// .graph() +// .filter_chain_unspents( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// core::iter::once(((), op)), +// ) +// .map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo)) +// .next() +// } + +// /// Inserts a [`TxOut`] at [`OutPoint`] into the wallet's transaction graph. +// /// +// /// This is used for providing a previous output's value so that we can use [`calculate_fee`] +// /// or [`calculate_fee_rate`] on a given transaction. Outputs inserted with this method will +// /// not be returned in [`list_unspent`] or [`list_output`]. +// /// +// /// **WARNINGS:** This should only be used to add `TxOut`s that the wallet does not own. Only +// /// insert `TxOut`s that you trust the values for! +// /// +// /// You must persist the changes resulting from one or more calls to this method if you need +// /// the inserted `TxOut` data to be reloaded after closing the wallet. +// /// See [`Wallet::reveal_next_address`]. +// /// +// /// [`calculate_fee`]: Self::calculate_fee +// /// [`calculate_fee_rate`]: Self::calculate_fee_rate +// /// [`list_unspent`]: Self::list_unspent +// /// [`list_output`]: Self::list_output +// pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) { +// let additions = self.indexed_graph.insert_txout(outpoint, txout); +// self.stage.merge(additions.into()); +// } + +// /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase +// /// transaction. +// /// +// /// To calculate the fee for a [`Transaction`] with inputs not owned by this wallet you must +// /// manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function. +// /// +// /// Note `tx` does not have to be in the graph for this to work. +// /// +// /// # Examples +// /// +// /// ```rust, no_run +// /// # use bitcoin::Txid; +// /// # use bdk_wallet::Wallet; +// /// # let mut wallet: Wallet = todo!(); +// /// # let txid:Txid = todo!(); +// /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; +// /// let fee = wallet.calculate_fee(&tx).expect("fee"); +// /// ``` +// /// +// /// ```rust, no_run +// /// # use bitcoin::Psbt; +// /// # use bdk_wallet::Wallet; +// /// # let mut wallet: Wallet = todo!(); +// /// # let mut psbt: Psbt = todo!(); +// /// let tx = &psbt.clone().extract_tx().expect("tx"); +// /// let fee = wallet.calculate_fee(tx).expect("fee"); +// /// ``` +// /// [`insert_txout`]: Self::insert_txout +// pub fn calculate_fee(&self, tx: &Transaction) -> Result { +// self.indexed_graph.graph().calculate_fee(tx) +// } + +// /// Calculate the [`FeeRate`] for a given transaction. +// /// +// /// To calculate the fee rate for a [`Transaction`] with inputs not owned by this wallet you +// /// must manually insert the TxOut(s) into the tx graph using the [`insert_txout`] function. +// /// +// /// Note `tx` does not have to be in the graph for this to work. +// /// +// /// # Examples +// /// +// /// ```rust, no_run +// /// # use bitcoin::Txid; +// /// # use bdk_wallet::Wallet; +// /// # let mut wallet: Wallet = todo!(); +// /// # let txid:Txid = todo!(); +// /// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; +// /// let fee_rate = wallet.calculate_fee_rate(&tx).expect("fee rate"); +// /// ``` +// /// +// /// ```rust, no_run +// /// # use bitcoin::Psbt; +// /// # use bdk_wallet::Wallet; +// /// # let mut wallet: Wallet = todo!(); +// /// # let mut psbt: Psbt = todo!(); +// /// let tx = &psbt.clone().extract_tx().expect("tx"); +// /// let fee_rate = wallet.calculate_fee_rate(tx).expect("fee rate"); +// /// ``` +// /// [`insert_txout`]: Self::insert_txout +// pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { +// self.calculate_fee(tx).map(|fee| fee / tx.weight()) +// } + +// /// Compute the `tx`'s sent and received [`Amount`]s. +// /// +// /// This method returns a tuple `(sent, received)`. Sent is the sum of the txin amounts +// /// that spend from previous txouts tracked by this wallet. Received is the summation +// /// of this tx's outputs that send to script pubkeys tracked by this wallet. +// /// +// /// # Examples +// /// +// /// ```rust, no_run +// /// # use bitcoin::Txid; +// /// # use bdk_wallet::Wallet; +// /// # let mut wallet: Wallet = todo!(); +// /// # let txid:Txid = todo!(); +// /// let tx = wallet.get_tx(txid).expect("tx exists").tx_node.tx; +// /// let (sent, received) = wallet.sent_and_received(&tx); +// /// ``` +// /// +// /// ```rust, no_run +// /// # use bitcoin::Psbt; +// /// # use bdk_wallet::Wallet; +// /// # let mut wallet: Wallet = todo!(); +// /// # let mut psbt: Psbt = todo!(); +// /// let tx = &psbt.clone().extract_tx().expect("tx"); +// /// let (sent, received) = wallet.sent_and_received(tx); +// /// ``` +// pub fn sent_and_received(&self, tx: &Transaction) -> (Amount, Amount) { +// self.indexed_graph.index.sent_and_received(tx, ..) +// } + +// /// Get a single transaction from the wallet as a [`WalletTx`] (if the transaction exists). +// /// +// /// `WalletTx` contains the full transaction alongside meta-data such as: +// /// * Blocks that the transaction is [`Anchor`]ed in. These may or may not be blocks that +// exist /// in the best chain. +// /// * The [`ChainPosition`] of the transaction in the best chain - whether the transaction is +// /// confirmed or unconfirmed. If the transaction is confirmed, the anchor which proves the +// /// confirmation is provided. If the transaction is unconfirmed, the unix timestamp of when +// /// the transaction was last seen in the mempool is provided. +// /// +// /// ```rust, no_run +// /// use bdk_chain::Anchor; +// /// use bdk_wallet::{chain::ChainPosition, Wallet}; +// /// # let wallet: Wallet = todo!(); +// /// # let my_txid: bitcoin::Txid = todo!(); +// /// +// /// let wallet_tx = wallet.get_tx(my_txid).expect("panic if tx does not exist"); +// /// +// /// // get reference to full transaction +// /// println!("my tx: {:#?}", wallet_tx.tx_node.tx); +// /// +// /// // list all transaction anchors +// /// for anchor in wallet_tx.tx_node.anchors { +// /// println!( +// /// "tx is anchored by block of hash {}", +// /// anchor.anchor_block().hash +// /// ); +// /// } +// /// +// /// // get confirmation status of transaction +// /// match wallet_tx.chain_position { +// /// ChainPosition::Confirmed { +// /// anchor, +// /// transitively: None, +// /// } => println!( +// /// "tx is confirmed at height {}, we know this since {}:{} is in the best chain", +// /// anchor.block_id.height, anchor.block_id.height, anchor.block_id.hash, +// /// ), +// /// ChainPosition::Confirmed { +// /// anchor, +// /// transitively: Some(_), +// /// } => println!( +// /// "tx is an ancestor of a tx anchored in {}:{}", +// /// anchor.block_id.height, anchor.block_id.hash, +// /// ), +// /// ChainPosition::Unconfirmed { first_seen, last_seen } => println!( +// /// "tx is first seen at {:?}, last seen at {:?}, it is unconfirmed as it is not +// anchored in the best chain", /// first_seen, last_seen +// /// ), +// /// } +// /// ``` +// /// +// /// [`Anchor`]: bdk_chain::Anchor +// pub fn get_tx(&self, txid: Txid) -> Option> { +// let graph = self.indexed_graph.graph(); +// graph +// .list_canonical_txs( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// ) +// .find(|tx| tx.tx_node.txid == txid) +// } + +// /// Iterate over relevant and canonical transactions in the wallet. +// /// +// /// A transaction is relevant when it spends from or spends to at least one tracked output. A +// /// transaction is canonical when it is confirmed in the best chain, or does not conflict +// /// with any transaction confirmed in the best chain. +// /// +// /// To iterate over all transactions, including those that are irrelevant and not canonical, +// use /// [`TxGraph::full_txs`]. +// /// +// /// To iterate over all canonical transactions, including those that are irrelevant, use +// /// [`TxGraph::list_canonical_txs`]. +// pub fn transactions<'a>(&'a self) -> impl Iterator> + 'a { +// let tx_graph = self.indexed_graph.graph(); +// let tx_index = &self.indexed_graph.index; +// tx_graph +// .list_canonical_txs( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// ) +// .filter(|c_tx| tx_index.is_tx_relevant(&c_tx.tx_node.tx)) +// } + +// /// Array of relevant and canonical transactions in the wallet sorted with a comparator +// /// function. +// /// +// /// This is a helper method equivalent to collecting the result of [`Wallet::transactions`] +// /// into a [`Vec`] and then sorting it. +// /// +// /// # Example +// /// +// /// ```rust,no_run +// /// # use bdk_wallet::{LoadParams, Wallet, WalletTx}; +// /// # let mut wallet:Wallet = todo!(); +// /// // Transactions by chain position: first unconfirmed then descending by confirmed height. +// /// let sorted_txs: Vec = +// /// wallet.transactions_sort_by(|tx1, tx2| tx2.chain_position.cmp(&tx1.chain_position)); +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// pub fn transactions_sort_by(&self, compare: F) -> Vec> +// where +// F: FnMut(&WalletTx, &WalletTx) -> Ordering, +// { +// let mut txs: Vec = self.transactions().collect(); +// txs.sort_unstable_by(compare); +// txs +// } + +// /// Return the balance, separated into available, trusted-pending, untrusted-pending, and +// /// immature values. +// pub fn balance(&self) -> Balance { +// self.indexed_graph.graph().balance( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// self.indexed_graph.index.outpoints().iter().cloned(), +// |&(k, _), _| k == KeychainKind::Internal, +// ) +// } + +// /// Add an external signer +// /// +// /// See [the `signer` module](signer) for an example. +// pub fn add_signer( +// &mut self, +// keychain: KeychainKind, +// ordering: SignerOrdering, +// signer: Arc, +// ) { +// let signers = match keychain { +// KeychainKind::External => Arc::make_mut(&mut self.signers), +// KeychainKind::Internal => Arc::make_mut(&mut self.change_signers), +// }; + +// signers.add_external(signer.id(&self.secp), ordering, signer); +// } + +// /// Set the keymap for a given keychain. +// /// +// /// Note this does nothing if the given keychain has no descriptor because we won't +// /// know the context (segwit, taproot, etc) in which to create signatures. +// pub fn set_keymap(&mut self, keychain: KeychainKind, keymap: KeyMap) { +// let wallet_signers = match keychain { +// KeychainKind::External => Arc::make_mut(&mut self.signers), +// KeychainKind::Internal => Arc::make_mut(&mut self.change_signers), +// }; +// if let Some(descriptor) = self.indexed_graph.index.get_descriptor(keychain) { +// *wallet_signers = SignersContainer::build(keymap, descriptor, &self.secp) +// } +// } + +// /// Set the keymap for each keychain. +// pub fn set_keymaps(&mut self, keymaps: impl IntoIterator) { +// for (keychain, keymap) in keymaps { +// self.set_keymap(keychain, keymap); +// } +// } + +// /// Get the signers +// /// +// /// ## Example +// /// +// /// ``` +// /// # use bdk_wallet::{Wallet, KeychainKind}; +// /// # use bdk_wallet::bitcoin::Network; +// /// let descriptor = +// "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/ +// 84'/1'/0'/0/*)"; /// let change_descriptor = +// "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/ +// 84'/1'/0'/1/*)"; /// let wallet = Wallet::create(descriptor, change_descriptor) +// /// .network(Network::Testnet) +// /// .create_wallet_no_persist()?; +// /// 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); +// /// } +// /// +// /// Ok::<(), Box>(()) +// /// ``` +// pub fn get_signers(&self, keychain: KeychainKind) -> Arc { +// match keychain { +// KeychainKind::External => Arc::clone(&self.signers), +// KeychainKind::Internal => Arc::clone(&self.change_signers), +// } +// } + +// /// Start building a transaction. +// /// +// /// This returns a blank [`TxBuilder`] from which you can specify the parameters for the +// /// transaction. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bitcoin::*; +// /// # use bdk_wallet::*; +// /// # use bdk_wallet::ChangeSet; +// /// # use bdk_wallet::error::CreateTxError; +// /// # use anyhow::Error; +// /// # let descriptor = +// "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/ +// *)"; /// # let mut wallet = doctest_wallet!(); +// /// # let to_address = +// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let +// psbt = { /// let mut builder = wallet.build_tx(); +// /// builder +// /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); +// /// builder.finish()? +// /// }; +// /// +// /// // sign and broadcast ... +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// /// +// /// [`TxBuilder`]: crate::TxBuilder +// pub fn build_tx(&mut self) -> TxBuilder<'_, DefaultCoinSelectionAlgorithm> { +// TxBuilder { +// wallet: self, +// params: TxParams::default(), +// coin_selection: DefaultCoinSelectionAlgorithm::default(), +// } +// } + +// pub(crate) fn create_tx( +// &mut self, +// coin_selection: Cs, +// params: TxParams, +// rng: &mut impl RngCore, +// ) -> 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 external_policy = external_descriptor +// .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)? +// .unwrap(); +// let internal_policy = internal_descriptor +// .map(|desc| { +// 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 provided +// if params.change_policy != tx_builder::ChangeSpendPolicy::OnlyChange +// && external_policy.requires_path() +// && params.external_policy_path.is_none() +// { +// return Err(CreateTxError::SpendingPolicyRequired( +// KeychainKind::External, +// )); +// }; +// // Same for the internal_policy path +// 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, +// )); +// }; +// } + +// let external_requirements = external_policy.get_condition( +// params +// .external_policy_path +// .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 requirements = +// external_requirements.merge(&internal_requirements.unwrap_or_default())?; + +// let version = match params.version { +// Some(transaction::Version(0)) => return Err(CreateTxError::Version0), +// Some(transaction::Version::ONE) if requirements.csv.is_some() => { +// return Err(CreateTxError::Version1Csv) +// } +// Some(v) => v, +// None => transaction::Version::TWO, +// }; + +// // We use a match here instead of a unwrap_or_else as it's way more readable :) +// let current_height = match params.current_height { +// // If they didn't tell us the current height, we assume it's the latest sync height. +// None => { +// let tip_height = self.chain.tip().height(); +// absolute::LockTime::from_height(tip_height).expect("invalid height") +// } +// Some(h) => h, +// }; + +// let lock_time = match params.locktime { +// // When no nLockTime is specified, we try to prevent fee sniping, if possible +// None => { +// // Fee sniping can be partially prevented by setting the timelock +// // to current_height. If we don't know the current_height, +// // we default to 0. +// let fee_sniping_height = current_height; + +// // We choose the biggest between the required nlocktime and the fee sniping +// // height +// match requirements.timelock { +// // No requirement, just use the fee_sniping_height +// None => fee_sniping_height, +// // There's a block-based requirement, but the value is lower than the +// // fee_sniping_height +// Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => +// { fee_sniping_height +// } +// // There's a time-based requirement or a block-based requirement greater +// // than the fee_sniping_height use that value +// Some(value) => value, +// } +// } +// // Specific nLockTime required and we have no constraints, so just set to that value +// Some(x) if requirements.timelock.is_none() => x, +// // Specific nLockTime required and it's compatible with the constraints +// Some(x) +// if requirements.timelock.unwrap().is_same_unit(x) +// && x >= requirements.timelock.unwrap() => +// { +// x +// } +// // Invalid nLockTime required +// Some(x) => { +// return Err(CreateTxError::LockTime { +// requested: x, +// required: requirements.timelock.unwrap(), +// }) +// } +// }; + +// // nSequence value for inputs +// // When not explicitly specified, defaults to 0xFFFFFFFD, +// // meaning RBF signaling is enabled +// let n_sequence = match (params.sequence, requirements.csv) { +// // Enable RBF by default +// (None, None) => Sequence::ENABLE_RBF_NO_LOCKTIME, +// // None requested, use required +// (None, Some(csv)) => csv, +// // Requested sequence is incompatible with requirements +// (Some(sequence), Some(csv)) if !check_nsequence_rbf(sequence, csv) => { +// return Err(CreateTxError::RbfSequenceCsv { sequence, csv }) +// } +// // Use requested nSequence value +// (Some(sequence), _) => sequence, +// }; + +// let (fee_rate, mut fee_amount) = match params.fee_policy.unwrap_or_default() { +// //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256 +// FeePolicy::FeeAmount(fee) => { +// if let Some(previous_fee) = params.bumping_fee { +// if fee < previous_fee.absolute { +// return Err(CreateTxError::FeeTooLow { +// required: previous_fee.absolute, +// }); +// } +// } +// (FeeRate::ZERO, fee) +// } +// FeePolicy::FeeRate(rate) => { +// if let Some(previous_fee) = params.bumping_fee { +// let required_feerate = FeeRate::from_sat_per_kwu( +// previous_fee.rate.to_sat_per_kwu() +// + FeeRate::BROADCAST_MIN.to_sat_per_kwu(), // +1 sat/vb +// ); +// if rate < required_feerate { +// return Err(CreateTxError::FeeRateTooLow { +// required: required_feerate, +// }); +// } +// } +// (rate, Amount::ZERO) +// } +// }; + +// let mut tx = Transaction { +// version, +// lock_time, +// input: vec![], +// output: vec![], +// }; + +// if params.manually_selected_only && params.utxos.is_empty() { +// return Err(CreateTxError::NoUtxosSelected); +// } + +// let mut outgoing = Amount::ZERO; +// let recipients = params.recipients.iter().map(|(r, v)| (r, *v)); + +// for (index, (script_pubkey, value)) in recipients.enumerate() { +// if !params.allow_dust && value.is_dust(script_pubkey) && +// !script_pubkey.is_op_return() { return +// Err(CreateTxError::OutputBelowDustLimit(index)); } + +// let new_out = TxOut { +// script_pubkey: script_pubkey.clone(), +// value, +// }; + +// tx.output.push(new_out); + +// outgoing += value; +// } + +// fee_amount += fee_rate * tx.weight(); + +// let (required_utxos, optional_utxos) = { +// // NOTE: manual selection overrides unspendable +// let mut required: Vec = params.utxos.clone(); +// let optional = self.filter_utxos(¶ms, current_height.to_consensus_u32()); + +// // if drain_wallet is true, all UTxOs are required +// if params.drain_wallet { +// required.extend(optional); +// (required, vec![]) +// } else { +// (required, optional) +// } +// }; + +// // get drain script +// let mut drain_index = Option::<(KeychainKind, u32)>::None; +// let drain_script = match params.drain_to { +// Some(ref drain_recipient) => drain_recipient.clone(), +// None => { +// let change_keychain = self.map_keychain(KeychainKind::Internal); +// let (index, spk) = self +// .indexed_graph +// .index +// .unused_keychain_spks(change_keychain) +// .next() +// .unwrap_or_else(|| { +// let (next_index, _) = self +// .indexed_graph +// .index +// .next_index(change_keychain) +// .expect("keychain must exist"); +// let spk = self +// .peek_address(change_keychain, next_index) +// .script_pubkey(); +// (next_index, spk) +// }); +// drain_index = Some((change_keychain, index)); +// spk +// } +// }; + +// let coin_selection = coin_selection +// .coin_select( +// required_utxos, +// optional_utxos, +// fee_rate, +// outgoing + fee_amount, +// &drain_script, +// rng, +// ) +// .map_err(CreateTxError::CoinSelection)?; + +// let excess = &coin_selection.excess; +// tx.input = coin_selection +// .selected +// .iter() +// .map(|u| bitcoin::TxIn { +// previous_output: u.outpoint(), +// script_sig: ScriptBuf::default(), +// sequence: u.sequence().unwrap_or(n_sequence), +// witness: Witness::new(), +// }) +// .collect(); + +// if tx.output.is_empty() { +// // Uh oh, our transaction has no outputs. +// // We allow this when: +// // - We have a drain_to address and the utxos we must spend (this happens, +// // for example, when we RBF) +// // - We have a drain_to address and drain_wallet set +// // Otherwise, we don't know who we should send the funds to, and how much +// // we should send! +// if params.drain_to.is_some() && (params.drain_wallet || !params.utxos.is_empty()) { +// if let Excess::NoChange { +// dust_threshold, +// remaining_amount, +// change_fee, +// } = excess +// { +// return Err(CreateTxError::CoinSelection(InsufficientFunds { +// needed: *dust_threshold, +// available: remaining_amount +// .checked_sub(*change_fee) +// .unwrap_or_default(), +// })); +// } +// } else { +// return Err(CreateTxError::NoRecipients); +// } +// } + +// // if there's change, create and add a change output +// if let Excess::Change { amount, .. } = excess { +// // create drain output +// let drain_output = TxOut { +// value: *amount, +// script_pubkey: drain_script, +// }; + +// // TODO: We should pay attention when adding a new output: this might increase +// // the length of the "number of vouts" parameter by 2 bytes, potentially making +// // our feerate too low +// tx.output.push(drain_output); +// } + +// // sort input/outputs according to the chosen algorithm +// params.ordering.sort_tx_with_aux_rand(&mut tx, rng); + +// let psbt = self.complete_transaction(tx, coin_selection.selected, params)?; + +// // recording changes to the change keychain +// if let (Excess::Change { .. }, Some((keychain, index))) = (excess, drain_index) { +// if let Some((_, index_changeset)) = +// self.indexed_graph.index.reveal_to_target(keychain, index) +// { +// self.stage.merge(index_changeset.into()); +// self.mark_used(keychain, index); +// } +// } + +// Ok(psbt) +// } + +// /// Bump the fee of a transaction previously created with this wallet. +// /// +// /// Returns an error if the transaction is already confirmed or doesn't explicitly signal +// /// *replace by fee* (RBF). If the transaction can be fee bumped then it returns a +// [`TxBuilder`] /// pre-populated with the inputs and outputs of the original transaction. +// /// +// /// ## Example +// /// +// /// ```no_run +// /// # // TODO: remove norun -- bumping fee seems to need the tx in the wallet database first. +// /// # use std::str::FromStr; +// /// # use bitcoin::*; +// /// # use bdk_wallet::*; +// /// # use bdk_wallet::ChangeSet; +// /// # use bdk_wallet::error::CreateTxError; +// /// # use anyhow::Error; +// /// # let descriptor = +// "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/ +// *)"; /// # let mut wallet = doctest_wallet!(); +// /// # let to_address = +// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let +// mut psbt = { /// let mut builder = wallet.build_tx(); +// /// builder +// /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); +// /// builder.finish()? +// /// }; +// /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; +// /// let tx = psbt.clone().extract_tx().expect("tx"); +// /// // broadcast tx but it's taking too long to confirm so we want to bump the fee +// /// let mut psbt = { +// /// let mut builder = wallet.build_fee_bump(tx.compute_txid())?; +// /// builder +// /// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate")); +// /// builder.finish()? +// /// }; +// /// +// /// let _ = wallet.sign(&mut psbt, SignOptions::default())?; +// /// let fee_bumped_tx = psbt.extract_tx(); +// /// // broadcast fee_bumped_tx to replace original +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// // TODO: support for merging multiple transactions while bumping the fees +// pub fn build_fee_bump( +// &mut self, +// txid: Txid, +// ) -> Result, BuildFeeBumpError> { +// let graph = self.indexed_graph.graph(); +// let txout_index = &self.indexed_graph.index; +// let chain_tip = self.chain.tip().block_id(); +// let chain_positions = graph +// .list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default()) +// .map(|canon_tx| (canon_tx.tx_node.txid, canon_tx.chain_position)) +// .collect::>(); + +// let mut tx = graph +// .get_tx(txid) +// .ok_or(BuildFeeBumpError::TransactionNotFound(txid))? +// .as_ref() +// .clone(); + +// if chain_positions +// .get(&txid) +// .ok_or(BuildFeeBumpError::TransactionNotFound(txid))? +// .is_confirmed() +// { +// return Err(BuildFeeBumpError::TransactionConfirmed(txid)); +// } + +// if !tx +// .input +// .iter() +// .any(|txin| txin.sequence.to_consensus_u32() <= 0xFFFFFFFD) +// { +// return Err(BuildFeeBumpError::IrreplaceableTransaction( +// tx.compute_txid(), +// )); +// } + +// let fee = self +// .calculate_fee(&tx) +// .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?; +// let fee_rate = self +// .calculate_fee_rate(&tx) +// .map_err(|_| BuildFeeBumpError::FeeRateUnavailable)?; + +// // remove the inputs from the tx and process them +// let utxos = tx +// .input +// .drain(..) +// .map(|txin| -> Result<_, BuildFeeBumpError> { +// graph +// // Get previous transaction +// .get_tx(txin.previous_output.txid) +// .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output)) +// // Get chain position +// .and_then(|prev_tx| { +// let chain_position = chain_positions +// .get(&txin.previous_output.txid) +// .cloned() +// .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output))?; +// let prev_txout = prev_tx +// .output +// .get(txin.previous_output.vout as usize) +// .ok_or(BuildFeeBumpError::InvalidOutputIndex(txin.previous_output)) +// .cloned()?; +// Ok((prev_tx, prev_txout, chain_position)) +// }) +// .map(|(prev_tx, prev_txout, chain_position)| { +// match txout_index.index_of_spk(prev_txout.script_pubkey.clone()) { +// Some(&(keychain, derivation_index)) => WeightedUtxo { +// satisfaction_weight: self +// .public_descriptor(keychain) +// .max_weight_to_satisfy() +// .unwrap(), +// utxo: Utxo::Local(LocalOutput { +// outpoint: txin.previous_output, +// txout: prev_txout.clone(), +// keychain, +// is_spent: true, +// derivation_index, +// chain_position, +// }), +// }, +// None => { +// let satisfaction_weight = Weight::from_wu_usize( +// serialize(&txin.script_sig).len() * 4 +// + serialize(&txin.witness).len(), +// ); +// WeightedUtxo { +// utxo: Utxo::Foreign { +// outpoint: txin.previous_output, +// sequence: txin.sequence, +// psbt_input: Box::new(psbt::Input { +// witness_utxo: prev_txout +// .script_pubkey +// .witness_version() +// .map(|_| prev_txout.clone()), +// non_witness_utxo: Some(prev_tx.as_ref().clone()), +// ..Default::default() +// }), +// }, +// satisfaction_weight, +// } +// } +// } +// }) +// }) +// .collect::, BuildFeeBumpError>>()?; + +// if tx.output.len() > 1 { +// let mut change_index = None; +// for (index, txout) in tx.output.iter().enumerate() { +// let change_keychain = self.map_keychain(KeychainKind::Internal); +// match txout_index.index_of_spk(txout.script_pubkey.clone()) { +// Some((keychain, _)) if *keychain == change_keychain => { +// change_index = Some(index) +// } +// _ => {} +// } +// } + +// if let Some(change_index) = change_index { +// tx.output.remove(change_index); +// } +// } + +// let params = TxParams { +// // TODO: figure out what rbf option should be? +// version: Some(tx.version), +// recipients: tx +// .output +// .into_iter() +// .map(|txout| (txout.script_pubkey, txout.value)) +// .collect(), +// utxos, +// bumping_fee: Some(tx_builder::PreviousFee { +// absolute: fee, +// rate: fee_rate, +// }), +// ..Default::default() +// }; + +// Ok(TxBuilder { +// wallet: self, +// params, +// coin_selection: DefaultCoinSelectionAlgorithm::default(), +// }) +// } + +// /// Sign a transaction with all the wallet's signers, in the order specified by every +// signer's /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated +// `bool` that /// has the value true if the PSBT was finalized, or false otherwise. +// /// +// /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the +// way /// the transaction is finalized at the end. Note that it can't be guaranteed that +// *every* /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) +// defined /// in this library will. +// /// +// /// ## Example +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bitcoin::*; +// /// # use bdk_wallet::*; +// /// # use bdk_wallet::ChangeSet; +// /// # use bdk_wallet::error::CreateTxError; +// /// # let descriptor = +// "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/ +// *)"; /// # let mut wallet = doctest_wallet!(); +// /// # let to_address = +// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let +// mut psbt = { /// let mut builder = wallet.build_tx(); +// /// builder.add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)); +// /// builder.finish()? +// /// }; +// /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?; +// /// assert!(finalized, "we should have signed all the inputs"); +// /// # Ok::<(),anyhow::Error>(()) +// pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result { +// // This adds all the PSBT metadata for the inputs, which will help us later figure out +// how // to derive our keys +// self.update_psbt_with_descriptor(psbt) +// .map_err(SignerError::MiniscriptPsbt)?; + +// // If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and +// // finalized ones) has the `non_witness_utxo` +// if !sign_options.trust_witness_utxo +// && psbt +// .inputs +// .iter() +// .filter(|i| i.final_script_witness.is_none() && i.final_script_sig.is_none()) +// .filter(|i| i.tap_internal_key.is_none() && i.tap_merkle_root.is_none()) +// .any(|i| i.non_witness_utxo.is_none()) +// { +// return Err(SignerError::MissingNonWitnessUtxo); +// } + +// // If the user hasn't explicitly opted-in, refuse to sign the transaction unless every +// input // is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot +// if !sign_options.allow_all_sighashes +// && !psbt.inputs.iter().all(|i| { +// i.sighash_type.is_none() +// || i.sighash_type == Some(EcdsaSighashType::All.into()) +// || i.sighash_type == Some(TapSighashType::All.into()) +// || i.sighash_type == Some(TapSighashType::Default.into()) +// }) +// { +// return Err(SignerError::NonStandardSighash); +// } + +// for signer in self +// .signers +// .signers() +// .iter() +// .chain(self.change_signers.signers().iter()) +// { +// signer.sign_transaction(psbt, &sign_options, &self.secp)?; +// } + +// // attempt to finalize +// if sign_options.try_finalize { +// self.finalize_psbt(psbt, sign_options) +// } else { +// Ok(false) +// } +// } + +// /// Return the spending policies for the wallet's descriptor +// pub fn policies(&self, keychain: KeychainKind) -> Result, DescriptorError> { +// let signers = match keychain { +// KeychainKind::External => &self.signers, +// KeychainKind::Internal => &self.change_signers, +// }; + +// self.public_descriptor(keychain).extract_policy( +// signers, +// BuildSatisfaction::None, +// &self.secp, +// ) +// } + +// /// Returns the descriptor used to create addresses for a particular `keychain`. +// /// +// /// It's the "public" version of the wallet's descriptor, meaning a new descriptor that has +// /// the same structure but with the all secret keys replaced by their corresponding public +// key. /// This can be used to build a watch-only version of a wallet. +// pub fn public_descriptor(&self, keychain: KeychainKind) -> &ExtendedDescriptor { +// self.indexed_graph +// .index +// .get_descriptor(self.map_keychain(keychain)) +// .expect("keychain must exist") +// } + +// /// Finalize a PSBT, i.e., for each input determine if sufficient data is available to pass +// /// validation and construct the respective `scriptSig` or `scriptWitness`. Please refer to +// /// [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Input_Finalizer), +// /// and [BIP371](https://github.com/bitcoin/bips/blob/master/bip-0371.mediawiki) +// /// for further information. +// /// +// /// Returns `true` if the PSBT could be finalized, and `false` otherwise. +// /// +// /// The [`SignOptions`] can be used to tweak the behavior of the finalizer. +// pub fn finalize_psbt( +// &self, +// psbt: &mut Psbt, +// sign_options: SignOptions, +// ) -> Result { +// let tx = &psbt.unsigned_tx; +// let chain_tip = self.chain.tip().block_id(); +// let prev_txids = tx +// .input +// .iter() +// .map(|txin| txin.previous_output.txid) +// .collect::>(); +// let confirmation_heights = self +// .indexed_graph +// .graph() +// .list_canonical_txs(&self.chain, chain_tip, CanonicalizationParams::default()) +// .filter(|canon_tx| prev_txids.contains(&canon_tx.tx_node.txid)) +// // This is for a small performance gain. Although `.filter` filters out excess txs, +// it // will still consume the internal `CanonicalIter` entirely. Having a `.take` here +// // allows us to stop further unnecessary canonicalization. +// .take(prev_txids.len()) +// .map(|canon_tx| { +// let txid = canon_tx.tx_node.txid; +// match canon_tx.chain_position { +// ChainPosition::Confirmed { anchor, .. } => (txid, anchor.block_id.height), +// ChainPosition::Unconfirmed { .. } => (txid, u32::MAX), +// } +// }) +// .collect::>(); + +// let mut finished = true; + +// for (n, input) in tx.input.iter().enumerate() { +// let psbt_input = &psbt +// .inputs +// .get(n) +// .ok_or(IndexOutOfBoundsError::new(n, psbt.inputs.len()))?; +// if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() +// { continue; +// } +// let confirmation_height = confirmation_heights +// .get(&input.previous_output.txid) +// .copied(); +// let current_height = sign_options +// .assume_height +// .unwrap_or_else(|| self.chain.tip().height()); + +// // - Try to derive the descriptor by looking at the txout. If it's in our database, +// we // know exactly which `keychain` to use, and which derivation index it is +// // - If that fails, try to derive it by looking at the psbt input: the complete logic +// is // in `src/descriptor/mod.rs`, but it will basically look at `bip32_derivation`, +// // `redeem_script` and `witness_script` to determine the right derivation +// // - If that also fails, it will try it on the internal descriptor, if present +// let desc = psbt +// .get_utxo_for(n) +// .and_then(|txout| self.get_descriptor_for_txout(&txout)) +// .or_else(|| { +// self.indexed_graph.index.keychains().find_map(|(_, desc)| { +// desc.derive_from_psbt_input(psbt_input, psbt.get_utxo_for(n), &self.secp) +// }) +// }); + +// match desc { +// Some(desc) => { +// let mut tmp_input = bitcoin::TxIn::default(); +// match desc.satisfy( +// &mut tmp_input, +// ( +// PsbtInputSatisfier::new(psbt, n), +// After::new(Some(current_height), false), +// Older::new(Some(current_height), confirmation_height, false), +// ), +// ) { +// Ok(_) => { +// let length = psbt.inputs.len(); +// // Set the UTXO fields, final script_sig and witness +// // and clear everything else. +// let psbt_input = psbt +// .inputs +// .get_mut(n) +// .ok_or(IndexOutOfBoundsError::new(n, length))?; +// let original = mem::take(psbt_input); +// psbt_input.non_witness_utxo = original.non_witness_utxo; +// psbt_input.witness_utxo = original.witness_utxo; +// if !tmp_input.script_sig.is_empty() { +// psbt_input.final_script_sig = Some(tmp_input.script_sig); +// } +// if !tmp_input.witness.is_empty() { +// psbt_input.final_script_witness = Some(tmp_input.witness); +// } +// } +// Err(_) => finished = false, +// } +// } +// None => finished = false, +// } +// } + +// // Clear derivation paths from outputs +// if finished { +// for output in &mut psbt.outputs { +// output.bip32_derivation.clear(); +// output.tap_key_origins.clear(); +// } +// } + +// Ok(finished) +// } + +// /// Return the secp256k1 context used for all signing operations +// pub fn secp_ctx(&self) -> &SecpCtx { +// &self.secp +// } + +// /// The derivation index of this wallet. It will return `None` if it has not derived any +// /// addresses. Otherwise, it will return the index of the highest address it has derived. +// pub fn derivation_index(&self, keychain: KeychainKind) -> Option { +// self.indexed_graph.index.last_revealed_index(keychain) +// } + +// /// 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 { +// self.indexed_graph +// .index +// .next_index(self.map_keychain(keychain)) +// .expect("keychain must exist") +// .0 +// } + +// /// Informs the wallet that you no longer intend to broadcast a tx that was built from it. +// /// +// /// This frees up the change address used when creating the tx for use in future +// transactions. // TODO: Make this free up reserved utxos when that's implemented +// pub fn cancel_tx(&mut self, tx: &Transaction) { +// let txout_index = &mut self.indexed_graph.index; +// for txout in &tx.output { +// if let Some((keychain, index)) = +// txout_index.index_of_spk(txout.script_pubkey.clone()) { // NOTE: unmark_used will +// **not** make something unused if it has actually been used // by a tx in the +// tracker. It only removes the superficial marking. +// txout_index.unmark_used(*keychain, *index); } +// } +// } + +// fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option { +// let &(keychain, child) = self +// .indexed_graph +// .index +// .index_of_spk(txout.script_pubkey.clone())?; +// let descriptor = self.public_descriptor(keychain); +// descriptor.at_derivation_index(child).ok() +// } + +// /// Given the options returns the list of utxos that must be used to form the +// /// transaction and any further that may be used if needed. +// fn filter_utxos(&self, params: &TxParams, current_height: u32) -> Vec { +// if params.manually_selected_only { +// vec![] +// // only process optional UTxOs if manually_selected_only is false +// } else { +// let manually_selected_outpoints = params +// .utxos +// .iter() +// .map(|wutxo| wutxo.utxo.outpoint()) +// .collect::>(); +// self.indexed_graph +// .graph() +// // get all unspent UTxOs from wallet +// // NOTE: the UTxOs returned by the following method already belong to wallet as +// the // call chain uses get_tx_node infallibly +// .filter_chain_unspents( +// &self.chain, +// self.chain.tip().block_id(), +// CanonicalizationParams::default(), +// self.indexed_graph.index.outpoints().iter().cloned(), +// ) +// // only create LocalOutput if UTxO is mature +// .filter_map(move |((k, i), full_txo)| { +// full_txo +// .is_mature(current_height) +// .then(|| new_local_utxo(k, i, full_txo)) +// }) +// // only process UTXOs not selected manually, they will be considered later in the +// // chain +// // NOTE: this avoid UTXOs in both required and optional list +// .filter(|may_spend| !manually_selected_outpoints.contains(&may_spend.outpoint)) +// // only add to optional UTxOs those which satisfy the change policy if we reuse +// // change +// .filter(|local_output| { +// self.keychains().count() == 1 +// || params.change_policy.is_satisfied_by(local_output) +// }) +// // only add to optional UTxOs those marked as spendable +// .filter(|local_output| !params.unspendable.contains(&local_output.outpoint)) +// // if bumping fees only add to optional UTxOs those confirmed +// .filter(|local_output| { +// params.bumping_fee.is_none() || local_output.chain_position.is_confirmed() +// }) +// .map(|utxo| WeightedUtxo { +// satisfaction_weight: self +// .public_descriptor(utxo.keychain) +// .max_weight_to_satisfy() +// .unwrap(), +// utxo: Utxo::Local(utxo), +// }) +// .collect() +// } +// } + +// fn complete_transaction( +// &self, +// tx: Transaction, +// selected: Vec, +// params: TxParams, +// ) -> Result { +// let mut psbt = Psbt::from_unsigned_tx(tx)?; + +// if params.add_global_xpubs { +// let all_xpubs = self +// .keychains() +// .flat_map(|(_, desc)| desc.get_extended_keys()) +// .collect::>(); + +// for xpub in all_xpubs { +// let origin = match xpub.origin { +// Some(origin) => origin, +// None if xpub.xkey.depth == 0 => { +// (xpub.root_fingerprint(&self.secp), vec![].into()) +// } +// _ => return Err(CreateTxError::MissingKeyOrigin(xpub.xkey.to_string())), +// }; + +// psbt.xpub.insert(xpub.xkey, origin); +// } +// } + +// let mut lookup_output = selected +// .into_iter() +// .map(|utxo| (utxo.outpoint(), utxo)) +// .collect::>(); + +// // add metadata for the inputs +// for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) { +// let utxo = match lookup_output.remove(&input.previous_output) { +// Some(utxo) => utxo, +// None => continue, +// }; + +// match utxo { +// Utxo::Local(utxo) => { +// *psbt_input = +// match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) +// { Ok(psbt_input) => psbt_input, +// Err(e) => match e { +// CreateTxError::UnknownUtxo => psbt::Input { +// sighash_type: params.sighash, +// ..psbt::Input::default() +// }, +// _ => return Err(e), +// }, +// } +// } +// Utxo::Foreign { +// outpoint, +// psbt_input: foreign_psbt_input, +// .. +// } => { +// let is_taproot = foreign_psbt_input +// .witness_utxo +// .as_ref() +// .map(|txout| txout.script_pubkey.is_p2tr()) +// .unwrap_or(false); +// if !is_taproot +// && !params.only_witness_utxo +// && foreign_psbt_input.non_witness_utxo.is_none() +// { +// return Err(CreateTxError::MissingNonWitnessUtxo(outpoint)); +// } +// *psbt_input = *foreign_psbt_input; +// } +// } +// } + +// self.update_psbt_with_descriptor(&mut psbt)?; + +// Ok(psbt) +// } + +// /// get the corresponding PSBT Input for a LocalUtxo +// pub fn get_psbt_input( +// &self, +// utxo: LocalOutput, +// sighash_type: Option, +// only_witness_utxo: bool, +// ) -> Result { +// // Try to find the prev_script in our db to figure out if this is internal or external, +// // and the derivation index +// let &(keychain, child) = self +// .indexed_graph +// .index +// .index_of_spk(utxo.txout.script_pubkey) +// .ok_or(CreateTxError::UnknownUtxo)?; + +// let mut psbt_input = psbt::Input { +// sighash_type, +// ..psbt::Input::default() +// }; + +// let desc = self.public_descriptor(keychain); +// let derived_descriptor = desc +// .at_derivation_index(child) +// .expect("child can't be hardened"); + +// psbt_input +// .update_with_descriptor_unchecked(&derived_descriptor) +// .map_err(MiniscriptPsbtError::Conversion)?; + +// let prev_output = utxo.outpoint; +// if let Some(prev_tx) = self.indexed_graph.graph().get_tx(prev_output.txid) { +// // We want to check that the prevout actually exists in the tx before continuing. +// let prevout = prev_tx.output.get(prev_output.vout as usize).ok_or( +// MiniscriptPsbtError::UtxoUpdate(miniscript::psbt::UtxoUpdateError::UtxoCheck), +// )?; +// if desc.is_witness() || desc.is_taproot() { +// psbt_input.witness_utxo = Some(prevout.clone()); +// } +// if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) { +// psbt_input.non_witness_utxo = Some(prev_tx.as_ref().clone()); +// } +// } +// Ok(psbt_input) +// } + +// fn update_psbt_with_descriptor(&self, psbt: &mut Psbt) -> Result<(), MiniscriptPsbtError> { +// // We need to borrow `psbt` mutably within the loops, so we have to allocate a vec for +// all // the input utxos and outputs +// let utxos = (0..psbt.inputs.len()) +// .filter_map(|i| psbt.get_utxo_for(i).map(|utxo| (true, i, utxo))) +// .chain( +// psbt.unsigned_tx +// .output +// .iter() +// .enumerate() +// .map(|(i, out)| (false, i, out.clone())), +// ) +// .collect::>(); + +// // Try to figure out the keychain and derivation for every input and output +// for (is_input, index, out) in utxos.into_iter() { +// if let Some(&(keychain, child)) = +// self.indexed_graph.index.index_of_spk(out.script_pubkey) +// { +// let desc = self.public_descriptor(keychain); +// let desc = desc +// .at_derivation_index(child) +// .expect("child can't be hardened"); + +// if is_input { +// psbt.update_input_with_descriptor(index, &desc) +// .map_err(MiniscriptPsbtError::UtxoUpdate)?; +// } else { +// psbt.update_output_with_descriptor(index, &desc) +// .map_err(MiniscriptPsbtError::OutputUpdate)?; +// } +// } +// } + +// Ok(()) +// } + +// /// Return the checksum of the public descriptor associated to `keychain` +// /// +// /// Internally calls [`Self::public_descriptor`] to fetch the right descriptor +// pub fn descriptor_checksum(&self, keychain: KeychainKind) -> String { +// self.public_descriptor(keychain) +// .to_string() +// .split_once('#') +// .unwrap() +// .1 +// .to_string() +// } + +// /// Applies an update to the wallet and stages the changes (but does not persist them). +// /// +// /// Usually you create an `update` by interacting with some blockchain data source and +// inserting /// transactions related to your wallet into it. +// /// +// /// After applying updates you should persist the staged wallet changes. For an example of +// how /// to persist staged wallet changes see [`Wallet::reveal_next_address`]. +// pub fn apply_update(&mut self, update: impl Into) -> Result<(), CannotConnectError> { +// let update = update.into(); +// let mut changeset = match update.chain { +// Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?), +// None => ChangeSet::default(), +// }; + +// let index_changeset = self +// .indexed_graph +// .index +// .reveal_to_target_multi(&update.last_active_indices); +// changeset.merge(index_changeset.into()); +// changeset.merge(self.indexed_graph.apply_update(update.tx_update).into()); +// self.stage.merge(changeset); +// Ok(()) +// } + +// /// Get a reference of the staged [`ChangeSet`] that is yet to be committed (if any). +// pub fn staged(&self) -> Option<&ChangeSet> { +// if self.stage.is_empty() { +// None +// } else { +// Some(&self.stage) +// } +// } + +// /// Get a mutable reference of the staged [`ChangeSet`] that is yet to be committed (if any). +// pub fn staged_mut(&mut self) -> Option<&mut ChangeSet> { +// if self.stage.is_empty() { +// None +// } else { +// Some(&mut self.stage) +// } +// } + +// /// Take the staged [`ChangeSet`] to be persisted now (if any). +// pub fn take_staged(&mut self) -> Option { +// self.stage.take() +// } + +// /// Get a reference to the inner [`TxGraph`]. +// pub fn tx_graph(&self) -> &TxGraph { +// self.indexed_graph.graph() +// } + +// /// Get a reference to the inner [`KeychainTxOutIndex`]. +// pub fn spk_index(&self) -> &KeychainTxOutIndex { +// &self.indexed_graph.index +// } + +// /// Get a reference to the inner [`LocalChain`]. +// pub fn local_chain(&self) -> &LocalChain { +// &self.chain +// } + +// /// Introduces a `block` of `height` to the wallet, and tries to connect it to the +// /// `prev_blockhash` of the block's header. +// /// +// /// This is a convenience method that is equivalent to calling [`apply_block_connected_to`] +// /// with `prev_blockhash` and `height-1` as the `connected_to` parameter. +// /// +// /// [`apply_block_connected_to`]: Self::apply_block_connected_to +// pub fn apply_block(&mut self, block: &Block, height: u32) -> Result<(), CannotConnectError> { +// let connected_to = match height.checked_sub(1) { +// Some(prev_height) => BlockId { +// height: prev_height, +// hash: block.header.prev_blockhash, +// }, +// None => BlockId { +// height, +// hash: block.block_hash(), +// }, +// }; +// self.apply_block_connected_to(block, height, connected_to) +// .map_err(|err| match err { +// ApplyHeaderError::InconsistentBlocks => { +// unreachable!("connected_to is derived from the block so must be consistent") +// } +// ApplyHeaderError::CannotConnect(err) => err, +// }) +// } + +// /// Applies relevant transactions from `block` of `height` to the wallet, and connects the +// /// block to the internal chain. +// /// +// /// The `connected_to` parameter informs the wallet how this block connects to the internal +// /// [`LocalChain`]. Relevant transactions are filtered from the `block` and inserted into the +// /// internal [`TxGraph`]. +// /// +// /// **WARNING**: You must persist the changes resulting from one or more calls to this method +// /// if you need the inserted block data to be reloaded after closing the wallet. +// /// See [`Wallet::reveal_next_address`]. +// pub fn apply_block_connected_to( +// &mut self, +// block: &Block, +// height: u32, +// connected_to: BlockId, +// ) -> Result<(), ApplyHeaderError> { +// let mut changeset = ChangeSet::default(); +// changeset.merge( +// self.chain +// .apply_header_connected_to(&block.header, height, connected_to)? +// .into(), +// ); +// changeset.merge( +// self.indexed_graph +// .apply_block_relevant(block, height) +// .into(), +// ); +// self.stage.merge(changeset); +// Ok(()) +// } + +// /// Apply relevant unconfirmed transactions to the wallet. +// /// +// /// Transactions that are not relevant are filtered out. +// /// +// /// This method takes in an iterator of `(tx, last_seen)` where `last_seen` is the timestamp +// of /// when the transaction was last seen in the mempool. This is used for conflict +// resolution /// when there is conflicting unconfirmed transactions. The transaction with the +// later /// `last_seen` is prioritized. +// /// +// /// **WARNING**: You must persist the changes resulting from one or more calls to this method +// /// if you need the applied unconfirmed transactions to be reloaded after closing the wallet. +// /// See [`Wallet::reveal_next_address`]. +// pub fn apply_unconfirmed_txs>>( +// &mut self, +// unconfirmed_txs: impl IntoIterator, +// ) { +// let indexed_graph_changeset = self +// .indexed_graph +// .batch_insert_relevant_unconfirmed(unconfirmed_txs); +// self.stage.merge(indexed_graph_changeset.into()); +// } + +// /// Apply evictions of the given transaction IDs with their associated timestamps. +// /// +// /// This function is used to mark specific unconfirmed transactions as evicted from the +// mempool. /// Eviction means that these transactions are not considered canonical by default, +// and will /// no longer be part of the wallet's [`transactions`] set. This can happen for +// example when /// a transaction is dropped from the mempool due to low fees or conflicts with +// another /// transaction. +// /// +// /// Only transactions that are currently unconfirmed and canonical are considered for +// eviction. /// Transactions that are not relevant to the wallet are ignored. Note that an +// evicted /// transaction can become canonical again if it is later observed on-chain or seen +// in the /// mempool with a higher priority (e.g., due to a fee bump). +// /// +// /// ## Parameters +// /// +// /// `evicted_txs`: An iterator of `(Txid, u64)` tuples, where: +// /// - `Txid`: The transaction ID of the transaction to be evicted. +// /// - `u64`: The timestamp indicating when the transaction was evicted from the mempool. This +// /// will usually correspond to the time of the latest chain sync. See docs for +// /// [`start_sync_with_revealed_spks`]. +// /// +// /// ## Notes +// /// +// /// - Not all blockchain backends support automatic mempool eviction handling - this method +// may /// be used in such cases. It can also be used to negate the effect of +// /// [`apply_unconfirmed_txs`] for a particular transaction without the need for an +// additional /// sync. +// /// - The changes are staged in the wallet's internal state and must be persisted to ensure +// they /// are retained across wallet restarts. Use [`Wallet::take_staged`] to retrieve the +// staged /// changes and persist them to your database of choice. +// /// - Evicted transactions are removed from the wallet's canonical transaction set, but the +// data /// remains in the wallet's internal transaction graph for historical purposes. +// /// - Ensure that the timestamps provided are accurate and monotonically increasing, as they +// /// influence the wallet's canonicalization logic. +// /// +// /// [`transactions`]: Wallet::transactions +// /// [`apply_unconfirmed_txs`]: Wallet::apply_unconfirmed_txs +// /// [`start_sync_with_revealed_spks`]: Wallet::start_sync_with_revealed_spks +// pub fn apply_evicted_txs(&mut self, evicted_txs: impl IntoIterator) { +// let chain = &self.chain; +// let canon_txids: Vec = self +// .indexed_graph +// .graph() +// .list_canonical_txs( +// chain, +// chain.tip().block_id(), +// CanonicalizationParams::default(), +// ) +// .map(|c| c.tx_node.txid) +// .collect(); + +// let changeset = self.indexed_graph.batch_insert_relevant_evicted_at( +// evicted_txs +// .into_iter() +// .filter(|(txid, _)| canon_txids.contains(txid)), +// ); + +// self.stage.merge(changeset.into()); +// } + +// /// Used internally to ensure that all methods requiring a [`KeychainKind`] will use a +// /// keychain with an associated descriptor. For example in case the wallet was created +// /// with only one keychain, passing [`KeychainKind::Internal`] here will instead return +// /// [`KeychainKind::External`]. +// fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind { +// if self.keychains().count() == 1 { +// KeychainKind::External +// } else { +// keychain +// } +// } +// } + +// /// Methods to construct sync/full-scan requests for spk-based chain sources. +// impl Wallet { +// /// Create a partial [`SyncRequest`] for all revealed spks at `start_time`. +// /// +// /// The `start_time` is used to record the time that a mempool transaction was last seen +// /// (or evicted). See [`Wallet::start_sync_with_revealed_spks`] for more. +// pub fn start_sync_with_revealed_spks_at( +// &self, +// start_time: u64, +// ) -> SyncRequestBuilder<(KeychainKind, u32)> { +// use bdk_chain::keychain_txout::SyncRequestBuilderExt; +// SyncRequest::builder_at(start_time) +// .chain_tip(self.chain.tip()) +// .revealed_spks_from_indexer(&self.indexed_graph.index, ..) +// .expected_spk_txids(self.indexed_graph.list_expected_spk_txids( +// &self.chain, +// self.chain.tip().block_id(), +// .., +// )) +// } + +// /// Create a partial [`SyncRequest`] for this wallet for all revealed spks. +// /// +// /// This is the first step when performing a spk-based wallet partial sync, the returned +// /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to +// /// start a blockchain sync with a spk based blockchain client. +// /// +// /// The time of the sync is the current system time and is used to record the +// /// tx last-seen for mempool transactions. Or if an expected transaction is missing +// /// or evicted, it is the time of the eviction. Note that timestamps may only increase +// /// to be counted by the tx graph. To supply your own start time see +// /// [`Wallet::start_sync_with_revealed_spks_at`]. +// #[cfg_attr(docsrs, doc(cfg(feature = "std")))] +// #[cfg(feature = "std")] +// pub fn start_sync_with_revealed_spks(&self) -> SyncRequestBuilder<(KeychainKind, u32)> { +// use bdk_chain::keychain_txout::SyncRequestBuilderExt; +// SyncRequest::builder() +// .chain_tip(self.chain.tip()) +// .revealed_spks_from_indexer(&self.indexed_graph.index, ..) +// .expected_spk_txids(self.indexed_graph.list_expected_spk_txids( +// &self.chain, +// self.chain.tip().block_id(), +// .., +// )) +// } + +// /// Create a [`FullScanRequest] for this wallet. +// /// +// /// This is the first step when performing a spk-based wallet full scan, the returned +// /// [`FullScanRequest] collects iterators for the wallet's keychain script pub keys needed to +// /// start a blockchain full scan with a spk based blockchain client. +// /// +// /// This operation is generally only used when importing or restoring a previously used +// wallet /// in which the list of used scripts is not known. +// /// +// /// The time of the scan is the current system time and is used to record the tx last-seen +// for /// mempool transactions. To supply your own start time see +// [`Wallet::start_full_scan_at`]. #[cfg_attr(docsrs, doc(cfg(feature = "std")))] +// #[cfg(feature = "std")] +// pub fn start_full_scan(&self) -> FullScanRequestBuilder { +// use bdk_chain::keychain_txout::FullScanRequestBuilderExt; +// FullScanRequest::builder() +// .chain_tip(self.chain.tip()) +// .spks_from_indexer(&self.indexed_graph.index) +// } + +// /// Create a [`FullScanRequest`] builder at `start_time`. +// pub fn start_full_scan_at(&self, start_time: u64) -> FullScanRequestBuilder { +// use bdk_chain::keychain_txout::FullScanRequestBuilderExt; +// FullScanRequest::builder_at(start_time) +// .chain_tip(self.chain.tip()) +// .spks_from_indexer(&self.indexed_graph.index) +// } +// } + +// impl AsRef> for Wallet { +// fn as_ref(&self) -> &bdk_chain::tx_graph::TxGraph { +// self.keychain_tx_graph.graph() +// } +// } /// Deterministically generate a unique name given the descriptors defining the wallet /// @@ -2686,62 +2700,62 @@ fn new_local_utxo( } } -fn make_indexed_graph( - stage: &mut ChangeSet, - tx_graph_changeset: chain::tx_graph::ChangeSet, - indexer_changeset: chain::keychain_txout::ChangeSet, - descriptor: ExtendedDescriptor, - change_descriptor: Option, - lookahead: u32, - use_spk_cache: bool, -) -> Result>, DescriptorError> -{ - let (indexed_graph, changeset) = IndexedTxGraph::from_changeset( - chain::indexed_tx_graph::ChangeSet { - tx_graph: tx_graph_changeset, - indexer: indexer_changeset, - }, - |idx_cs| -> Result, DescriptorError> { - let mut idx = KeychainTxOutIndex::from_changeset(lookahead, use_spk_cache, idx_cs); - - let descriptor_inserted = idx - .insert_descriptor(KeychainKind::External, descriptor) - .expect("already checked to be a unique, wildcard, non-multipath descriptor"); - assert!( - descriptor_inserted, - "this must be the first time we are seeing this descriptor" - ); - - let change_descriptor = match change_descriptor { - Some(change_descriptor) => change_descriptor, - None => return Ok(idx), - }; - - let change_descriptor_inserted = idx - .insert_descriptor(KeychainKind::Internal, change_descriptor) - .map_err(|e| { - use bdk_chain::indexer::keychain_txout::InsertDescriptorError; - match e { - InsertDescriptorError::DescriptorAlreadyAssigned { .. } => { - crate::descriptor::error::Error::ExternalAndInternalAreTheSame - } - InsertDescriptorError::KeychainAlreadyAssigned { .. } => { - unreachable!("this is the first time we're assigning internal") - } - } - })?; - assert!( - change_descriptor_inserted, - "this must be the first time we are seeing this descriptor" - ); - - Ok(idx) - }, - )?; - stage.tx_graph.merge(changeset.tx_graph); - stage.indexer.merge(changeset.indexer); - Ok(indexed_graph) -} +// fn make_indexed_graph( +// stage: &mut ChangeSet, +// tx_graph_changeset: chain::tx_graph::ChangeSet, +// indexer_changeset: chain::keychain_txout::ChangeSet, +// descriptor: ExtendedDescriptor, +// change_descriptor: Option, +// lookahead: u32, +// use_spk_cache: bool, +// ) -> Result>, +// DescriptorError> { +// let (indexed_graph, changeset) = IndexedTxGraph::from_changeset( +// chain::indexed_tx_graph::ChangeSet { +// tx_graph: tx_graph_changeset, +// indexer: indexer_changeset, +// }, +// |idx_cs| -> Result, DescriptorError> { +// let mut idx = KeychainTxOutIndex::from_changeset(lookahead, use_spk_cache, idx_cs); + +// let descriptor_inserted = idx +// .insert_descriptor(KeychainKind::External, descriptor) +// .expect("already checked to be a unique, wildcard, non-multipath descriptor"); +// assert!( +// descriptor_inserted, +// "this must be the first time we are seeing this descriptor" +// ); + +// let change_descriptor = match change_descriptor { +// Some(change_descriptor) => change_descriptor, +// None => return Ok(idx), +// }; + +// let change_descriptor_inserted = idx +// .insert_descriptor(KeychainKind::Internal, change_descriptor) +// .map_err(|e| { +// use bdk_chain::indexer::keychain_txout::InsertDescriptorError; +// match e { +// InsertDescriptorError::DescriptorAlreadyAssigned { .. } => { +// crate::descriptor::error::Error::ExternalAndInternalAreTheSame +// } +// InsertDescriptorError::KeychainAlreadyAssigned { .. } => { +// unreachable!("this is the first time we're assigning internal") +// } +// } +// })?; +// assert!( +// change_descriptor_inserted, +// "this must be the first time we are seeing this descriptor" +// ); + +// Ok(idx) +// }, +// )?; +// stage.tx_graph.merge(changeset.tx_graph); +// stage.indexer.merge(changeset.indexer); +// Ok(indexed_graph) +// } /// Transforms a [`FeeRate`] to `f64` with unit as sat/vb. #[macro_export] @@ -2754,171 +2768,185 @@ macro_rules! floating_rate { }}; } -#[macro_export] -#[doc(hidden)] -/// Macro for getting a wallet for use in a doctest -macro_rules! doctest_wallet { - () => {{ - use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash}; - use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph, tx_graph}; - use $crate::{Update, KeychainKind, Wallet}; - use $crate::test_utils::*; - let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; - let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)"; - - let mut wallet = Wallet::create(descriptor, change_descriptor) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - let address = wallet.peek_address(KeychainKind::External, 0).address; - let tx = Transaction { - version: transaction::Version::TWO, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - value: Amount::from_sat(500_000), - script_pubkey: address.script_pubkey(), - }], - }; - let txid = tx.compute_txid(); - let block_id = BlockId { height: 500, hash: BlockHash::all_zeros() }; - insert_checkpoint(&mut wallet, block_id); - insert_checkpoint(&mut wallet, BlockId { height: 1_000, hash: BlockHash::all_zeros() }); - insert_tx(&mut wallet, tx); - let anchor = ConfirmationBlockTime { - confirmation_time: 50_000, - block_id, - }; - insert_anchor(&mut wallet, txid, anchor); - wallet - }} -} +// #[macro_export] +// #[doc(hidden)] +// /// Macro for getting a wallet for use in a doctest +// macro_rules! doctest_wallet { +// () => {{ +// use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash}; +// use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph, tx_graph}; +// use $crate::{Update, KeychainKind, Wallet}; +// use $crate::test_utils::*; +// let descriptor = +// "tr([73c5da0a/86'/0'/0' +// ]tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/ +// 0/*)"; let change_descriptor = +// "tr([73c5da0a/86'/0'/0' +// ]tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/ +// 1/*)"; + +// let mut wallet = Wallet::create(descriptor, change_descriptor) +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .unwrap(); +// let address = wallet.peek_address(KeychainKind::External, 0).address; +// let tx = Transaction { +// version: transaction::Version::TWO, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// value: Amount::from_sat(500_000), +// script_pubkey: address.script_pubkey(), +// }], +// }; +// let txid = tx.compute_txid(); +// let block_id = BlockId { height: 500, hash: BlockHash::all_zeros() }; +// insert_checkpoint(&mut wallet, block_id); +// insert_checkpoint(&mut wallet, BlockId { height: 1_000, hash: BlockHash::all_zeros() }); +// insert_tx(&mut wallet, tx); +// let anchor = ConfirmationBlockTime { +// confirmation_time: 50_000, +// block_id, +// }; +// insert_anchor(&mut wallet, txid, anchor); +// wallet +// }} +// } #[cfg(test)] mod test { use super::*; - use crate::miniscript::Error::Unexpected; - use crate::test_utils::get_test_tr_single_sig_xprv_and_change_desc; - use crate::test_utils::insert_tx; - - #[test] - fn not_duplicated_utxos_across_optional_and_required() { - let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - - // create new wallet - let mut wallet = Wallet::create(external_desc, internal_desc) - .network(Network::Testnet) - .create_wallet_no_persist() - .unwrap(); - - let two_output_tx = Transaction { - input: vec![], - output: vec![ - TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }, - TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(75_000), - }, - ], - version: transaction::Version::non_standard(0), - lock_time: absolute::LockTime::ZERO, - }; - - let txid = two_output_tx.compute_txid(); - insert_tx(&mut wallet, two_output_tx); - - let outpoint = OutPoint { txid, vout: 0 }; - let mut builder = wallet.build_tx(); - builder.add_utxo(outpoint).expect("should add local utxo"); - let params = builder.params.clone(); - // enforce selection of first output in transaction - let received = wallet.filter_utxos(¶ms, wallet.latest_checkpoint().block_id().height); - // notice expected doesn't include the first output from two_output_tx as it should be - // filtered out - let expected = vec![wallet - .get_utxo(OutPoint { txid, vout: 1 }) - .map(|utxo| WeightedUtxo { - satisfaction_weight: wallet - .public_descriptor(utxo.keychain) - .max_weight_to_satisfy() - .unwrap(), - utxo: Utxo::Local(utxo), - }) - .unwrap()]; - - assert_eq!(expected, received); - } - - #[test] - fn test_create_two_path_wallet() { - let two_path_descriptor = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1>/*)"; - - // Test successful creation of a two-path wallet - let params = Wallet::create_from_two_path_descriptor(two_path_descriptor); - let wallet = params.network(Network::Testnet).create_wallet_no_persist(); - assert!(wallet.is_ok()); - - let wallet = wallet.unwrap(); - - // Verify that the wallet has both external and internal keychains - let keychains: Vec<_> = wallet.keychains().collect(); - assert_eq!(keychains.len(), 2); - - // Verify that the descriptors are different (receive vs change) - let external_desc = keychains - .iter() - .find(|(k, _)| *k == KeychainKind::External) - .unwrap() - .1; - let internal_desc = keychains - .iter() - .find(|(k, _)| *k == KeychainKind::Internal) - .unwrap() - .1; - assert_ne!(external_desc.to_string(), internal_desc.to_string()); - - // Verify that addresses can be generated - let external_addr = wallet.peek_address(KeychainKind::External, 0); - let internal_addr = wallet.peek_address(KeychainKind::Internal, 0); - assert_ne!(external_addr.address, internal_addr.address); - } - - #[test] - fn test_create_two_path_wallet_invalid_descriptor() { - // Test with invalid single-path descriptor - let single_path_descriptor = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/0/*)"; - let params = Wallet::create_from_two_path_descriptor(single_path_descriptor); - let wallet = params.network(Network::Testnet).create_wallet_no_persist(); - assert!(matches!(wallet, Err(DescriptorError::MultiPath))); - - // Test with a private descriptor - // You get a Miniscript(Unexpected("Can't make an extended private key with multiple paths - // into a public key.")) error. - let private_multipath_descriptor = "wpkh(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/84'/1'/0'/<0;1>/*)"; - let params = Wallet::create_from_two_path_descriptor(private_multipath_descriptor); - let wallet = params.network(Network::Testnet).create_wallet_no_persist(); - assert!(matches!( - wallet, - Err(DescriptorError::Miniscript(Unexpected(..))) - )); - - // Test with invalid 3-path multipath descriptor - let three_path_descriptor = "wpkh([9a6a2580/84'/1'/0']tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/<0;1;2>/*)"; - let params = Wallet::create_from_two_path_descriptor(three_path_descriptor); - let wallet = params.network(Network::Testnet).create_wallet_no_persist(); - assert!(matches!(wallet, Err(DescriptorError::MultiPath))); - - // Test with completely invalid descriptor - let invalid_descriptor = "invalid_descriptor"; - let params = Wallet::create_from_two_path_descriptor(invalid_descriptor); - let wallet = params.network(Network::Testnet).create_wallet_no_persist(); - assert!(wallet.is_err()); - } + // use crate::miniscript::Error::Unexpected; + // use crate::test_utils::get_test_tr_single_sig_xprv_and_change_desc; + // use crate::test_utils::insert_tx; + + // #[test] + // fn not_duplicated_utxos_across_optional_and_required() { + // let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); + + // // create new wallet + // let mut wallet = Wallet::create(external_desc, internal_desc) + // .network(Network::Testnet) + // .create_wallet_no_persist() + // .unwrap(); + + // let two_output_tx = Transaction { + // input: vec![], + // output: vec![ + // TxOut { + // script_pubkey: wallet + // .next_unused_address(KeychainKind::External) + // .script_pubkey(), + // value: Amount::from_sat(25_000), + // }, + // TxOut { + // script_pubkey: wallet + // .next_unused_address(KeychainKind::External) + // .script_pubkey(), + // value: Amount::from_sat(75_000), + // }, + // ], + // version: transaction::Version::non_standard(0), + // lock_time: absolute::LockTime::ZERO, + // }; + + // let txid = two_output_tx.compute_txid(); + // insert_tx(&mut wallet, two_output_tx); + + // let outpoint = OutPoint { txid, vout: 0 }; + // let mut builder = wallet.build_tx(); + // builder.add_utxo(outpoint).expect("should add local utxo"); + // let params = builder.params.clone(); + // // enforce selection of first output in transaction + // let received = wallet.filter_utxos(¶ms, + // wallet.latest_checkpoint().block_id().height); // notice expected doesn't include the + // first output from two_output_tx as it should be // filtered out + // let expected = vec![wallet + // .get_utxo(OutPoint { txid, vout: 1 }) + // .map(|utxo| WeightedUtxo { + // satisfaction_weight: wallet + // .public_descriptor(utxo.keychain) + // .max_weight_to_satisfy() + // .unwrap(), + // utxo: Utxo::Local(utxo), + // }) + // .unwrap()]; + + // assert_eq!(expected, received); + // } + + // #[test] + // fn test_create_two_path_wallet() { + // let two_path_descriptor = + // "wpkh([9a6a2580/84'/1'/0' + // ]tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/ + // <0;1>/*)"; + + // // Test successful creation of a two-path wallet + // let params = Wallet::create_from_two_path_descriptor(two_path_descriptor); + // let wallet = params.network(Network::Testnet).create_wallet_no_persist(); + // assert!(wallet.is_ok()); + + // let wallet = wallet.unwrap(); + + // // Verify that the wallet has both external and internal keychains + // let keychains: Vec<_> = wallet.keychains().collect(); + // assert_eq!(keychains.len(), 2); + + // // Verify that the descriptors are different (receive vs change) + // let external_desc = keychains + // .iter() + // .find(|(k, _)| *k == KeychainKind::External) + // .unwrap() + // .1; + // let internal_desc = keychains + // .iter() + // .find(|(k, _)| *k == KeychainKind::Internal) + // .unwrap() + // .1; + // assert_ne!(external_desc.to_string(), internal_desc.to_string()); + + // // Verify that addresses can be generated + // let external_addr = wallet.peek_address(KeychainKind::External, 0); + // let internal_addr = wallet.peek_address(KeychainKind::Internal, 0); + // assert_ne!(external_addr.address, internal_addr.address); + // } + + // #[test] + // fn test_create_two_path_wallet_invalid_descriptor() { + // // Test with invalid single-path descriptor + // let single_path_descriptor = + // "wpkh([9a6a2580/84'/1'/0' + // ]tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/ + // 0/*)"; let params = Wallet::create_from_two_path_descriptor(single_path_descriptor); + // let wallet = params.network(Network::Testnet).create_wallet_no_persist(); + // assert!(matches!(wallet, Err(DescriptorError::MultiPath))); + + // // Test with a private descriptor + // // You get a Miniscript(Unexpected("Can't make an extended private key with multiple + // paths // into a public key.")) error. + // let private_multipath_descriptor = + // "wpkh(tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4/ + // 84'/1'/0'/<0;1>/*)"; let params = + // Wallet::create_from_two_path_descriptor(private_multipath_descriptor); let wallet = + // params.network(Network::Testnet).create_wallet_no_persist(); assert!(matches!( + // wallet, + // Err(DescriptorError::Miniscript(Unexpected(..))) + // )); + + // // Test with invalid 3-path multipath descriptor + // let three_path_descriptor = + // "wpkh([9a6a2580/84'/1'/0' + // ]tpubDDnGNapGEY6AZAdQbfRJgMg9fvz8pUBrLwvyvUqEgcUfgzM6zc2eVK4vY9x9L5FJWdX8WumXuLEDV5zDZnTfbn87vLe9XceCFwTu9so9Kks/ + // <0;1;2>/*)"; let params = + // Wallet::create_from_two_path_descriptor(three_path_descriptor); let wallet = + // params.network(Network::Testnet).create_wallet_no_persist(); assert!(matches! + // (wallet, Err(DescriptorError::MultiPath))); + + // // Test with completely invalid descriptor + // let invalid_descriptor = "invalid_descriptor"; + // let params = Wallet::create_from_two_path_descriptor(invalid_descriptor); + // let wallet = params.network(Network::Testnet).create_wallet_no_persist(); + // assert!(wallet.is_err()); + // } } diff --git a/src/wallet/params.rs b/src/wallet/params.rs index 80bfdf97..2a7eceef 100644 --- a/src/wallet/params.rs +++ b/src/wallet/params.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use alloc::boxed::Box; use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD; use bitcoin::{BlockHash, Network}; @@ -6,11 +7,18 @@ use miniscript::descriptor::KeyMap; use crate::{ descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor}, utils::SecpCtx, - AsyncWalletPersister, CreateWithPersistError, KeychainKind, LoadWithPersistError, Wallet, - WalletPersister, + // AsyncWalletPersister, CreateWithPersistError, + KeychainKind, + LoadWithPersistError, + Wallet, + // WalletPersister, }; -use super::{ChangeSet, LoadError, PersistedWallet}; +use super::{ + ChangeSet, + LoadError, + // PersistedWallet +}; fn make_two_path_descriptor_to_extract( two_path_descriptor: D, @@ -181,32 +189,32 @@ impl CreateParams { self } - /// Create [`PersistedWallet`] with the given [`WalletPersister`]. - pub fn create_wallet

( - self, - persister: &mut P, - ) -> Result, CreateWithPersistError> - where - P: WalletPersister, - { - PersistedWallet::create(persister, self) - } - - /// Create [`PersistedWallet`] with the given [`AsyncWalletPersister`]. - pub async fn create_wallet_async

( - self, - persister: &mut P, - ) -> Result, CreateWithPersistError> - where - P: AsyncWalletPersister, - { - PersistedWallet::create_async(persister, self).await - } - - /// Create [`Wallet`] without persistence. - pub fn create_wallet_no_persist(self) -> Result { - Wallet::create_with_params(self) - } + // /// Create [`PersistedWallet`] with the given [`WalletPersister`]. + // pub fn create_wallet

( + // self, + // persister: &mut P, + // ) -> Result, CreateWithPersistError> + // where + // P: WalletPersister, + // { + // PersistedWallet::create(persister, self) + // } + + // /// Create [`PersistedWallet`] with the given [`AsyncWalletPersister`]. + // pub async fn create_wallet_async

( + // self, + // persister: &mut P, + // ) -> Result, CreateWithPersistError> + // where + // P: AsyncWalletPersister, + // { + // PersistedWallet::create_async(persister, self).await + // } + + // /// Create [`Wallet`] without persistence. + // pub fn create_wallet_no_persist(self) -> Result { + // Wallet::create_with_params(self) + // } } /// Parameters for [`Wallet::load`] or [`PersistedWallet::load`]. @@ -308,32 +316,32 @@ impl LoadParams { self } - /// Load [`PersistedWallet`] with the given [`WalletPersister`]. - pub fn load_wallet

( - self, - persister: &mut P, - ) -> Result>, LoadWithPersistError> - where - P: WalletPersister, - { - PersistedWallet::load(persister, self) - } - - /// Load [`PersistedWallet`] with the given [`AsyncWalletPersister`]. - pub async fn load_wallet_async

( - self, - persister: &mut P, - ) -> Result>, LoadWithPersistError> - where - P: AsyncWalletPersister, - { - PersistedWallet::load_async(persister, self).await - } - - /// Load [`Wallet`] without persistence. - pub fn load_wallet_no_persist(self, changeset: ChangeSet) -> Result, LoadError> { - Wallet::load_with_params(changeset, self) - } + // /// Load [`PersistedWallet`] with the given [`WalletPersister`]. + // pub fn load_wallet

( + // self, + // persister: &mut P, + // ) -> Result>, LoadWithPersistError> + // where + // P: WalletPersister, + // { + // PersistedWallet::load(persister, self) + // } + + // /// Load [`PersistedWallet`] with the given [`AsyncWalletPersister`]. + // pub async fn load_wallet_async

( + // self, + // persister: &mut P, + // ) -> Result>, LoadWithPersistError> + // where + // P: AsyncWalletPersister, + // { + // PersistedWallet::load_async(persister, self).await + // } + + // /// Load [`Wallet`] without persistence. + // pub fn load_wallet_no_persist(self, changeset: ChangeSet) -> Result, + // LoadError> { Wallet::load_with_params(changeset, self) + // } } impl Default for LoadParams { diff --git a/src/wallet/persisted.rs b/src/wallet/persisted.rs index 74516e4b..3f3fdf13 100644 --- a/src/wallet/persisted.rs +++ b/src/wallet/persisted.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use core::{ fmt, future::Future, @@ -14,321 +15,321 @@ use crate::{ ChangeSet, CreateParams, LoadParams, Wallet, }; -/// Trait that persists [`PersistedWallet`]. -/// -/// For an async version, use [`AsyncWalletPersister`]. -/// -/// Associated functions of this trait should not be called directly, and the trait is designed so -/// that associated functions are hard to find (since they are not methods!). [`WalletPersister`] is -/// used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) which enforces some level of -/// safety. Refer to [`PersistedWallet`] for more about the safety checks. -pub trait WalletPersister { - /// Error type of the persister. - type Error; - - /// Initialize the `persister` and load all data. - /// - /// This is called by [`PersistedWallet::create`] and [`PersistedWallet::load`] to ensure - /// the [`WalletPersister`] is initialized and returns all data in the `persister`. - /// - /// # Implementation Details - /// - /// The database schema of the `persister` (if any), should be initialized and migrated here. - /// - /// The implementation must return all data currently stored in the `persister`. If there is no - /// data, return an empty changeset (using [`ChangeSet::default()`]). - /// - /// Error should only occur on database failure. Multiple calls to `initialize` should not - /// error. Calling `initialize` inbetween calls to `persist` should not error. - /// - /// Calling [`persist`] before the `persister` is `initialize`d may error. However, some - /// persister implementations may NOT require initialization at all (and not error). - /// - /// [`persist`]: WalletPersister::persist - fn initialize(persister: &mut Self) -> Result; - - /// Persist the given `changeset` to the `persister`. - /// - /// This method can fail if the `persister` is not [`initialize`]d. - /// - /// [`initialize`]: WalletPersister::initialize - fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error>; -} +// /// Trait that persists [`PersistedWallet`]. +// /// +// /// For an async version, use [`AsyncWalletPersister`]. +// /// +// /// Associated functions of this trait should not be called directly, and the trait is designed +// so /// that associated functions are hard to find (since they are not methods!). +// [`WalletPersister`] is /// used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) which +// enforces some level of /// safety. Refer to [`PersistedWallet`] for more about the safety checks. +// pub trait WalletPersister { +// /// Error type of the persister. +// type Error; + +// /// Initialize the `persister` and load all data. +// /// +// /// This is called by [`PersistedWallet::create`] and [`PersistedWallet::load`] to ensure +// /// the [`WalletPersister`] is initialized and returns all data in the `persister`. +// /// +// /// # Implementation Details +// /// +// /// The database schema of the `persister` (if any), should be initialized and migrated here. +// /// +// /// The implementation must return all data currently stored in the `persister`. If there is +// no /// data, return an empty changeset (using [`ChangeSet::default()`]). +// /// +// /// Error should only occur on database failure. Multiple calls to `initialize` should not +// /// error. Calling `initialize` inbetween calls to `persist` should not error. +// /// +// /// Calling [`persist`] before the `persister` is `initialize`d may error. However, some +// /// persister implementations may NOT require initialization at all (and not error). +// /// +// /// [`persist`]: WalletPersister::persist +// fn initialize(persister: &mut Self) -> Result; + +// /// Persist the given `changeset` to the `persister`. +// /// +// /// This method can fail if the `persister` is not [`initialize`]d. +// /// +// /// [`initialize`]: WalletPersister::initialize +// fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error>; +// } type FutureResult<'a, T, E> = Pin> + Send + 'a>>; -/// Async trait that persists [`PersistedWallet`]. -/// -/// For a blocking version, use [`WalletPersister`]. -/// -/// Associated functions of this trait should not be called directly, and the trait is designed so -/// that associated functions are hard to find (since they are not methods!). -/// [`AsyncWalletPersister`] is used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) -/// which enforces some level of safety. Refer to [`PersistedWallet`] for more about the safety -/// checks. -pub trait AsyncWalletPersister { - /// Error type of the persister. - type Error; - - /// Initialize the `persister` and load all data. - /// - /// This is called by [`PersistedWallet::create_async`] and [`PersistedWallet::load_async`] to - /// ensure the [`AsyncWalletPersister`] is initialized and returns all data in the `persister`. - /// - /// # Implementation Details - /// - /// The database schema of the `persister` (if any), should be initialized and migrated here. - /// - /// The implementation must return all data currently stored in the `persister`. If there is no - /// data, return an empty changeset (using [`ChangeSet::default()`]). - /// - /// Error should only occur on database failure. Multiple calls to `initialize` should not - /// error. Calling `initialize` inbetween calls to `persist` should not error. - /// - /// Calling [`persist`] before the `persister` is `initialize`d may error. However, some - /// persister implementations may NOT require initialization at all (and not error). - /// - /// [`persist`]: AsyncWalletPersister::persist - fn initialize<'a>(persister: &'a mut Self) -> FutureResult<'a, ChangeSet, Self::Error> - where - Self: 'a; - - /// Persist the given `changeset` to the `persister`. - /// - /// This method can fail if the `persister` is not [`initialize`]d. - /// - /// [`initialize`]: AsyncWalletPersister::initialize - fn persist<'a>( - persister: &'a mut Self, - changeset: &'a ChangeSet, - ) -> FutureResult<'a, (), Self::Error> - where - Self: 'a; -} - -/// Represents a persisted wallet which persists into type `P`. -/// -/// This is a light wrapper around [`Wallet`] that enforces some level of safety-checking when used -/// with a [`WalletPersister`] or [`AsyncWalletPersister`] implementation. Safety checks assume that -/// [`WalletPersister`] and/or [`AsyncWalletPersister`] are implemented correctly. -/// -/// Checks include: -/// -/// * Ensure the persister is initialized before data is persisted. -/// * Ensure there were no previously persisted wallet data before creating a fresh wallet and -/// persisting it. -/// * Only clear the staged changes of [`Wallet`] after persisting succeeds. -/// * Ensure the wallet is persisted to the same `P` type as when created/loaded. Note that this is -/// not completely fool-proof as you can have multiple instances of the same `P` type that are -/// connected to different databases. -#[derive(Debug)] -pub struct PersistedWallet

{ - inner: Wallet, - _marker: PhantomData, -} - -impl

Deref for PersistedWallet

{ - type Target = Wallet; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl

DerefMut for PersistedWallet

{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - -/// Methods when `P` is a [`WalletPersister`]. -impl PersistedWallet

{ - /// Create a new [`PersistedWallet`] with the given `persister` and `params`. - pub fn create( - persister: &mut P, - params: CreateParams, - ) -> Result> { - let existing = P::initialize(persister).map_err(CreateWithPersistError::Persist)?; - if !existing.is_empty() { - return Err(CreateWithPersistError::DataAlreadyExists(existing)); - } - let mut inner = - Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?; - if let Some(changeset) = inner.take_staged() { - P::persist(persister, &changeset).map_err(CreateWithPersistError::Persist)?; - } - Ok(Self { - inner, - _marker: PhantomData, - }) - } - - /// Load a previously [`PersistedWallet`] from the given `persister` and `params`. - pub fn load( - persister: &mut P, - params: LoadParams, - ) -> Result, LoadWithPersistError> { - let changeset = P::initialize(persister).map_err(LoadWithPersistError::Persist)?; - Wallet::load_with_params(changeset, params) - .map(|opt| { - opt.map(|inner| PersistedWallet { - inner, - _marker: PhantomData, - }) - }) - .map_err(LoadWithPersistError::InvalidChangeSet) - } - - /// Persist staged changes of wallet into `persister`. - /// - /// Returns whether any new changes were persisted. - /// - /// If the `persister` errors, the staged changes will not be cleared. - pub fn persist(&mut self, persister: &mut P) -> Result { - match self.inner.staged_mut() { - Some(stage) => { - P::persist(persister, &*stage)?; - let _ = stage.take(); - Ok(true) - } - None => Ok(false), - } - } -} - -/// Methods when `P` is an [`AsyncWalletPersister`]. -impl PersistedWallet

{ - /// Create a new [`PersistedWallet`] with the given async `persister` and `params`. - pub async fn create_async( - persister: &mut P, - params: CreateParams, - ) -> Result> { - let existing = P::initialize(persister) - .await - .map_err(CreateWithPersistError::Persist)?; - if !existing.is_empty() { - return Err(CreateWithPersistError::DataAlreadyExists(existing)); - } - let mut inner = - Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?; - if let Some(changeset) = inner.take_staged() { - P::persist(persister, &changeset) - .await - .map_err(CreateWithPersistError::Persist)?; - } - Ok(Self { - inner, - _marker: PhantomData, - }) - } - - /// Load a previously [`PersistedWallet`] from the given async `persister` and `params`. - pub async fn load_async( - persister: &mut P, - params: LoadParams, - ) -> Result, LoadWithPersistError> { - let changeset = P::initialize(persister) - .await - .map_err(LoadWithPersistError::Persist)?; - Wallet::load_with_params(changeset, params) - .map(|opt| { - opt.map(|inner| PersistedWallet { - inner, - _marker: PhantomData, - }) - }) - .map_err(LoadWithPersistError::InvalidChangeSet) - } - - /// Persist staged changes of wallet into an async `persister`. - /// - /// Returns whether any new changes were persisted. - /// - /// If the `persister` errors, the staged changes will not be cleared. - pub async fn persist_async(&mut self, persister: &mut P) -> Result { - match self.inner.staged_mut() { - Some(stage) => { - P::persist(persister, &*stage).await?; - let _ = stage.take(); - Ok(true) - } - None => Ok(false), - } - } -} - -#[cfg(feature = "rusqlite")] -impl WalletPersister for bdk_chain::rusqlite::Transaction<'_> { - type Error = bdk_chain::rusqlite::Error; - - fn initialize(persister: &mut Self) -> Result { - ChangeSet::init_sqlite_tables(&*persister)?; - ChangeSet::from_sqlite(persister) - } - - fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { - changeset.persist_to_sqlite(persister) - } -} - -#[cfg(feature = "rusqlite")] -impl WalletPersister for bdk_chain::rusqlite::Connection { - type Error = bdk_chain::rusqlite::Error; - - fn initialize(persister: &mut Self) -> Result { - let db_tx = persister.transaction()?; - ChangeSet::init_sqlite_tables(&db_tx)?; - let changeset = ChangeSet::from_sqlite(&db_tx)?; - db_tx.commit()?; - Ok(changeset) - } - - fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { - let db_tx = persister.transaction()?; - changeset.persist_to_sqlite(&db_tx)?; - db_tx.commit() - } -} - -/// Error for [`bdk_file_store`]'s implementation of [`WalletPersister`]. -#[cfg(feature = "file_store")] -#[derive(Debug)] -pub enum FileStoreError { - /// Error when loading from the store. - Load(bdk_file_store::StoreErrorWithDump), - /// Error when writing to the store. - Write(std::io::Error), -} - -#[cfg(feature = "file_store")] -impl core::fmt::Display for FileStoreError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use core::fmt::Display; - match self { - FileStoreError::Load(e) => Display::fmt(e, f), - FileStoreError::Write(e) => Display::fmt(e, f), - } - } -} - -#[cfg(feature = "file_store")] -impl std::error::Error for FileStoreError {} - -#[cfg(feature = "file_store")] -impl WalletPersister for bdk_file_store::Store { - type Error = FileStoreError; - - fn initialize(persister: &mut Self) -> Result { - persister - .dump() - .map(Option::unwrap_or_default) - .map_err(FileStoreError::Load) - } - - fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { - persister.append(changeset).map_err(FileStoreError::Write) - } -} +// /// Async trait that persists [`PersistedWallet`]. +// /// +// /// For a blocking version, use [`WalletPersister`]. +// /// +// /// Associated functions of this trait should not be called directly, and the trait is designed +// so /// that associated functions are hard to find (since they are not methods!). +// /// [`AsyncWalletPersister`] is used by [`PersistedWallet`] (a light wrapper around [`Wallet`]) +// /// which enforces some level of safety. Refer to [`PersistedWallet`] for more about the safety +// /// checks. +// pub trait AsyncWalletPersister { +// /// Error type of the persister. +// type Error; + +// /// Initialize the `persister` and load all data. +// /// +// /// This is called by [`PersistedWallet::create_async`] and [`PersistedWallet::load_async`] +// to /// ensure the [`AsyncWalletPersister`] is initialized and returns all data in the +// `persister`. /// +// /// # Implementation Details +// /// +// /// The database schema of the `persister` (if any), should be initialized and migrated here. +// /// +// /// The implementation must return all data currently stored in the `persister`. If there is +// no /// data, return an empty changeset (using [`ChangeSet::default()`]). +// /// +// /// Error should only occur on database failure. Multiple calls to `initialize` should not +// /// error. Calling `initialize` inbetween calls to `persist` should not error. +// /// +// /// Calling [`persist`] before the `persister` is `initialize`d may error. However, some +// /// persister implementations may NOT require initialization at all (and not error). +// /// +// /// [`persist`]: AsyncWalletPersister::persist +// fn initialize<'a>(persister: &'a mut Self) -> FutureResult<'a, ChangeSet, Self::Error> +// where +// Self: 'a; + +// /// Persist the given `changeset` to the `persister`. +// /// +// /// This method can fail if the `persister` is not [`initialize`]d. +// /// +// /// [`initialize`]: AsyncWalletPersister::initialize +// fn persist<'a>( +// persister: &'a mut Self, +// changeset: &'a ChangeSet, +// ) -> FutureResult<'a, (), Self::Error> +// where +// Self: 'a; +// } + +// /// Represents a persisted wallet which persists into type `P`. +// /// +// /// This is a light wrapper around [`Wallet`] that enforces some level of safety-checking when +// used /// with a [`WalletPersister`] or [`AsyncWalletPersister`] implementation. Safety checks +// assume that /// [`WalletPersister`] and/or [`AsyncWalletPersister`] are implemented correctly. +// /// +// /// Checks include: +// /// +// /// * Ensure the persister is initialized before data is persisted. +// /// * Ensure there were no previously persisted wallet data before creating a fresh wallet and +// /// persisting it. +// /// * Only clear the staged changes of [`Wallet`] after persisting succeeds. +// /// * Ensure the wallet is persisted to the same `P` type as when created/loaded. Note that this +// is /// not completely fool-proof as you can have multiple instances of the same `P` type that +// are /// connected to different databases. +// #[derive(Debug)] +// pub struct PersistedWallet

{ +// inner: Wallet, +// _marker: PhantomData, +// } + +// impl

Deref for PersistedWallet

{ +// type Target = Wallet; + +// fn deref(&self) -> &Self::Target { +// &self.inner +// } +// } + +// impl

DerefMut for PersistedWallet

{ +// fn deref_mut(&mut self) -> &mut Self::Target { +// &mut self.inner +// } +// } + +// /// Methods when `P` is a [`WalletPersister`]. +// impl PersistedWallet

{ +// /// Create a new [`PersistedWallet`] with the given `persister` and `params`. +// pub fn create( +// persister: &mut P, +// params: CreateParams, +// ) -> Result> { +// let existing = P::initialize(persister).map_err(CreateWithPersistError::Persist)?; +// if !existing.is_empty() { +// return Err(CreateWithPersistError::DataAlreadyExists(existing)); +// } +// let mut inner = +// Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?; +// if let Some(changeset) = inner.take_staged() { +// P::persist(persister, &changeset).map_err(CreateWithPersistError::Persist)?; +// } +// Ok(Self { +// inner, +// _marker: PhantomData, +// }) +// } + +// /// Load a previously [`PersistedWallet`] from the given `persister` and `params`. +// pub fn load( +// persister: &mut P, +// params: LoadParams, +// ) -> Result, LoadWithPersistError> { +// let changeset = P::initialize(persister).map_err(LoadWithPersistError::Persist)?; +// Wallet::load_with_params(changeset, params) +// .map(|opt| { +// opt.map(|inner| PersistedWallet { +// inner, +// _marker: PhantomData, +// }) +// }) +// .map_err(LoadWithPersistError::InvalidChangeSet) +// } + +// /// Persist staged changes of wallet into `persister`. +// /// +// /// Returns whether any new changes were persisted. +// /// +// /// If the `persister` errors, the staged changes will not be cleared. +// pub fn persist(&mut self, persister: &mut P) -> Result { +// match self.inner.staged_mut() { +// Some(stage) => { +// P::persist(persister, &*stage)?; +// let _ = stage.take(); +// Ok(true) +// } +// None => Ok(false), +// } +// } +// } + +// /// Methods when `P` is an [`AsyncWalletPersister`]. +// impl PersistedWallet

{ +// /// Create a new [`PersistedWallet`] with the given async `persister` and `params`. +// pub async fn create_async( +// persister: &mut P, +// params: CreateParams, +// ) -> Result> { +// let existing = P::initialize(persister) +// .await +// .map_err(CreateWithPersistError::Persist)?; +// if !existing.is_empty() { +// return Err(CreateWithPersistError::DataAlreadyExists(existing)); +// } +// let mut inner = +// Wallet::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?; +// if let Some(changeset) = inner.take_staged() { +// P::persist(persister, &changeset) +// .await +// .map_err(CreateWithPersistError::Persist)?; +// } +// Ok(Self { +// inner, +// _marker: PhantomData, +// }) +// } + +// /// Load a previously [`PersistedWallet`] from the given async `persister` and `params`. +// pub async fn load_async( +// persister: &mut P, +// params: LoadParams, +// ) -> Result, LoadWithPersistError> { +// let changeset = P::initialize(persister) +// .await +// .map_err(LoadWithPersistError::Persist)?; +// Wallet::load_with_params(changeset, params) +// .map(|opt| { +// opt.map(|inner| PersistedWallet { +// inner, +// _marker: PhantomData, +// }) +// }) +// .map_err(LoadWithPersistError::InvalidChangeSet) +// } + +// /// Persist staged changes of wallet into an async `persister`. +// /// +// /// Returns whether any new changes were persisted. +// /// +// /// If the `persister` errors, the staged changes will not be cleared. +// pub async fn persist_async(&mut self, persister: &mut P) -> Result { +// match self.inner.staged_mut() { +// Some(stage) => { +// P::persist(persister, &*stage).await?; +// let _ = stage.take(); +// Ok(true) +// } +// None => Ok(false), +// } +// } +// } + +// #[cfg(feature = "rusqlite")] +// impl WalletPersister for bdk_chain::rusqlite::Transaction<'_> { +// type Error = bdk_chain::rusqlite::Error; + +// fn initialize(persister: &mut Self) -> Result { +// ChangeSet::init_sqlite_tables(&*persister)?; +// ChangeSet::from_sqlite(persister) +// } + +// fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { +// changeset.persist_to_sqlite(persister) +// } +// } + +// #[cfg(feature = "rusqlite")] +// impl WalletPersister for bdk_chain::rusqlite::Connection { +// type Error = bdk_chain::rusqlite::Error; + +// fn initialize(persister: &mut Self) -> Result { +// let db_tx = persister.transaction()?; +// ChangeSet::init_sqlite_tables(&db_tx)?; +// let changeset = ChangeSet::from_sqlite(&db_tx)?; +// db_tx.commit()?; +// Ok(changeset) +// } + +// fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { +// let db_tx = persister.transaction()?; +// changeset.persist_to_sqlite(&db_tx)?; +// db_tx.commit() +// } +// } + +// /// Error for [`bdk_file_store`]'s implementation of [`WalletPersister`]. +// #[cfg(feature = "file_store")] +// #[derive(Debug)] +// pub enum FileStoreError { +// /// Error when loading from the store. +// Load(bdk_file_store::StoreErrorWithDump), +// /// Error when writing to the store. +// Write(std::io::Error), +// } + +// #[cfg(feature = "file_store")] +// impl core::fmt::Display for FileStoreError { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// use core::fmt::Display; +// match self { +// FileStoreError::Load(e) => Display::fmt(e, f), +// FileStoreError::Write(e) => Display::fmt(e, f), +// } +// } +// } + +// #[cfg(feature = "file_store")] +// impl std::error::Error for FileStoreError {} + +// #[cfg(feature = "file_store")] +// impl WalletPersister for bdk_file_store::Store { +// type Error = FileStoreError; + +// fn initialize(persister: &mut Self) -> Result { +// persister +// .dump() +// .map(Option::unwrap_or_default) +// .map_err(FileStoreError::Load) +// } + +// fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> { +// persister.append(changeset).map_err(FileStoreError::Write) +// } +// } /// Error type for [`PersistedWallet::load`]. #[derive(Debug, PartialEq)] @@ -351,76 +352,76 @@ impl fmt::Display for LoadWithPersistError { #[cfg(feature = "std")] impl std::error::Error for LoadWithPersistError {} -/// Error type for [`PersistedWallet::create`]. -#[derive(Debug)] -pub enum CreateWithPersistError { - /// Error from persistence. - Persist(E), - /// Persister already has wallet data. - DataAlreadyExists(ChangeSet), - /// Occurs when the loaded changeset cannot construct [`Wallet`]. - Descriptor(DescriptorError), -} - -impl fmt::Display for CreateWithPersistError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Persist(err) => write!(f, "{err}"), - Self::DataAlreadyExists(changeset) => { - write!( - f, - "Cannot create wallet in a persister which already contains data: " - )?; - changeset_info(f, changeset) - } - Self::Descriptor(err) => { - write!(f, "{err}") - } - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for CreateWithPersistError {} - -/// Helper function to display basic information about a [`ChangeSet`]. -fn changeset_info(f: &mut fmt::Formatter<'_>, changeset: &ChangeSet) -> fmt::Result { - let network = changeset - .network - .as_ref() - .map_or("None".to_string(), |n| n.to_string()); - - let descriptor_checksum = changeset - .descriptor - .as_ref() - .and_then(|d| calc_checksum(&d.to_string()).ok()) - .unwrap_or_else(|| "None".to_string()); - - let change_descriptor_checksum = changeset - .change_descriptor - .as_ref() - .and_then(|d| calc_checksum(&d.to_string()).ok()) - .unwrap_or_else(|| "None".to_string()); - - let tx_count = changeset.tx_graph.txs.len(); - - let anchor_count = changeset.tx_graph.anchors.len(); - - let block_count = if let Some(&count) = changeset.local_chain.blocks.keys().last() { - count - } else { - 0 - }; - - writeln!(f, " Network: {network}")?; - writeln!(f, " Descriptor Checksum: {descriptor_checksum}")?; - writeln!( - f, - " Change Descriptor Checksum: {change_descriptor_checksum}" - )?; - writeln!(f, " Transaction Count: {tx_count}")?; - writeln!(f, " Anchor Count: {anchor_count}")?; - writeln!(f, " Block Count: {block_count}")?; - - Ok(()) -} +// /// Error type for [`PersistedWallet::create`]. +// #[derive(Debug)] +// pub enum CreateWithPersistError { +// /// Error from persistence. +// Persist(E), +// /// Persister already has wallet data. +// DataAlreadyExists(ChangeSet), +// /// Occurs when the loaded changeset cannot construct [`Wallet`]. +// Descriptor(DescriptorError), +// } + +// impl fmt::Display for CreateWithPersistError { +// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +// match self { +// Self::Persist(err) => write!(f, "{err}"), +// Self::DataAlreadyExists(changeset) => { +// write!( +// f, +// "Cannot create wallet in a persister which already contains data: " +// )?; +// changeset_info(f, changeset) +// } +// Self::Descriptor(err) => { +// write!(f, "{err}") +// } +// } +// } +// } + +// #[cfg(feature = "std")] +// impl std::error::Error for CreateWithPersistError {} + +// /// Helper function to display basic information about a [`ChangeSet`]. +// fn changeset_info(f: &mut fmt::Formatter<'_>, changeset: &ChangeSet) -> fmt::Result { +// let network = changeset +// .network +// .as_ref() +// .map_or("None".to_string(), |n| n.to_string()); + +// let descriptor_checksum = changeset +// .descriptor +// .as_ref() +// .and_then(|d| calc_checksum(&d.to_string()).ok()) +// .unwrap_or_else(|| "None".to_string()); + +// let change_descriptor_checksum = changeset +// .change_descriptor +// .as_ref() +// .and_then(|d| calc_checksum(&d.to_string()).ok()) +// .unwrap_or_else(|| "None".to_string()); + +// let tx_count = changeset.tx_graph.txs.len(); + +// let anchor_count = changeset.tx_graph.anchors.len(); + +// let block_count = if let Some(&count) = changeset.local_chain.blocks.keys().last() { +// count +// } else { +// 0 +// }; + +// writeln!(f, " Network: {network}")?; +// writeln!(f, " Descriptor Checksum: {descriptor_checksum}")?; +// writeln!( +// f, +// " Change Descriptor Checksum: {change_descriptor_checksum}" +// )?; +// writeln!(f, " Transaction Count: {tx_count}")?; +// writeln!(f, " Anchor Count: {anchor_count}")?; +// writeln!(f, " Block Count: {block_count}")?; + +// Ok(()) +// } diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 50974647..1edae929 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -13,73 +13,74 @@ //! //! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet) //! through the [`Wallet::add_signer`](super::Wallet::add_signer) function. -//! -//! ``` -//! # use alloc::sync::Arc; -//! # use core::str::FromStr; -//! # use bitcoin::secp256k1::{Secp256k1, All}; -//! # use bitcoin::*; -//! # use bdk_wallet::signer::*; -//! # use bdk_wallet::*; -//! # #[derive(Debug)] -//! # struct CustomHSM; -//! # impl CustomHSM { -//! # fn hsm_sign_input(&self, _psbt: &mut Psbt, _input: usize) -> Result<(), SignerError> { -//! # Ok(()) -//! # } -//! # fn connect() -> Self { -//! # CustomHSM -//! # } -//! # fn get_id(&self) -> SignerId { -//! # SignerId::Dummy(0) -//! # } -//! # } -//! #[derive(Debug)] -//! struct CustomSigner { -//! device: CustomHSM, -//! } -//! -//! impl CustomSigner { -//! fn connect() -> Self { -//! CustomSigner { device: CustomHSM::connect() } -//! } -//! } -//! -//! impl SignerCommon for CustomSigner { -//! fn id(&self, _secp: &Secp256k1) -> SignerId { -//! self.device.get_id() -//! } -//! } -//! -//! impl InputSigner for CustomSigner { -//! fn sign_input( -//! &self, -//! psbt: &mut Psbt, -//! input_index: usize, -//! _sign_options: &SignOptions, -//! _secp: &Secp256k1, -//! ) -> Result<(), SignerError> { -//! self.device.hsm_sign_input(psbt, input_index)?; -//! -//! Ok(()) -//! } -//! } -//! -//! let custom_signer = CustomSigner::connect(); -//! -//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)"; -//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)"; -//! let mut wallet = Wallet::create(descriptor, change_descriptor) -//! .network(Network::Testnet) -//! .create_wallet_no_persist()?; -//! wallet.add_signer( -//! KeychainKind::External, -//! SignerOrdering(200), -//! Arc::new(custom_signer) -//! ); -//! -//! # Ok::<_, anyhow::Error>(()) -//! ``` +// //! ``` +// //! # use alloc::sync::Arc; +// //! # use core::str::FromStr; +// //! # use bitcoin::secp256k1::{Secp256k1, All}; +// //! # use bitcoin::*; +// //! # use bdk_wallet::signer::*; +// //! # use bdk_wallet::*; +// //! # #[derive(Debug)] +// //! # struct CustomHSM; +// //! # impl CustomHSM { +// //! # fn hsm_sign_input(&self, _psbt: &mut Psbt, _input: usize) -> Result<(), SignerError> { +// //! # Ok(()) +// //! # } +// //! # fn connect() -> Self { +// //! # CustomHSM +// //! # } +// //! # fn get_id(&self) -> SignerId { +// //! # SignerId::Dummy(0) +// //! # } +// //! # } +// //! #[derive(Debug)] +// //! struct CustomSigner { +// //! device: CustomHSM, +// //! } +// //! +// //! impl CustomSigner { +// //! fn connect() -> Self { +// //! CustomSigner { device: CustomHSM::connect() } +// //! } +// //! } +// //! +// //! impl SignerCommon for CustomSigner { +// //! fn id(&self, _secp: &Secp256k1) -> SignerId { +// //! self.device.get_id() +// //! } +// //! } +// //! +// //! impl InputSigner for CustomSigner { +// //! fn sign_input( +// //! &self, +// //! psbt: &mut Psbt, +// //! input_index: usize, +// //! _sign_options: &SignOptions, +// //! _secp: &Secp256k1, +// //! ) -> Result<(), SignerError> { +// //! self.device.hsm_sign_input(psbt, input_index)?; +// //! +// //! Ok(()) +// //! } +// //! } +// //! +// //! let custom_signer = CustomSigner::connect(); +// //! +// //! let descriptor = +// "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/ +// 0/*)"; //! let change_descriptor = +// "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/ +// 1/*)"; //! let mut wallet = Wallet::create(descriptor, change_descriptor) +// //! .network(Network::Testnet) +// //! .create_wallet_no_persist()?; +// //! wallet.add_signer( +// //! KeychainKind::External, +// //! SignerOrdering(200), +// //! Arc::new(custom_signer) +// //! ); +// //! +// //! # Ok::<_, anyhow::Error>(()) +// //! ``` use crate::collections::BTreeMap; use alloc::string::String; diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index f3bfe3f9..dad59165 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -10,32 +10,31 @@ // licenses. //! Transaction builder -//! -//! ## Example -//! -//! ``` -//! # use std::str::FromStr; -//! # use bitcoin::*; -//! # use bdk_wallet::*; -//! # use bdk_wallet::ChangeSet; -//! # use bdk_wallet::error::CreateTxError; -//! # use anyhow::Error; -//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); -//! # let mut wallet = doctest_wallet!(); -//! // create a TxBuilder from a wallet -//! let mut tx_builder = wallet.build_tx(); -//! -//! tx_builder -//! // Create a transaction with one output to `to_address` of 50_000 satoshi -//! .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)) -//! // With a custom fee rate of 5.0 satoshi/vbyte -//! .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate")) -//! // Only spend non-change outputs -//! .do_not_spend_change(); -//! let psbt = tx_builder.finish()?; -//! # Ok::<(), anyhow::Error>(()) -//! ``` - +// //! ## Example +// //! +// //! ``` +// //! # use std::str::FromStr; +// //! # use bitcoin::*; +// //! # use bdk_wallet::*; +// //! # use bdk_wallet::ChangeSet; +// //! # use bdk_wallet::error::CreateTxError; +// //! # use anyhow::Error; +// //! # let to_address = +// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); //! # let mut +// wallet = doctest_wallet!(); //! // create a TxBuilder from a wallet +// //! let mut tx_builder = wallet.build_tx(); +// //! +// //! tx_builder +// //! // Create a transaction with one output to `to_address` of 50_000 satoshi +// //! .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)) +// //! // With a custom fee rate of 5.0 satoshi/vbyte +// //! .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate")) +// //! // Only spend non-change outputs +// //! .do_not_spend_change(); +// //! let psbt = tx_builder.finish()?; +// //! # Ok::<(), anyhow::Error>(()) +// //! ``` +#![allow(unused)] use alloc::{boxed::Box, string::String, vec::Vec}; use core::fmt; @@ -55,66 +54,66 @@ use super::{CreateTxError, Wallet}; use crate::collections::{BTreeMap, HashMap, HashSet}; use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo}; -/// A transaction builder -/// -/// A `TxBuilder` is created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. After -/// assigning it, you set options on it until finally calling [`finish`] to consume the builder and -/// generate the transaction. -/// -/// Each option setting method on `TxBuilder` takes and returns `&mut self` so you can chain calls -/// as in the following example: -/// -/// ``` -/// # use bdk_wallet::*; -/// # use bdk_wallet::tx_builder::*; -/// # use bitcoin::*; -/// # use core::str::FromStr; -/// # use bdk_wallet::ChangeSet; -/// # use bdk_wallet::error::CreateTxError; -/// # use anyhow::Error; -/// # let mut wallet = doctest_wallet!(); -/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); -/// # let addr2 = addr1.clone(); -/// // chaining -/// let psbt1 = { -/// let mut builder = wallet.build_tx(); -/// builder -/// .ordering(TxOrdering::Untouched) -/// .add_recipient(addr1.script_pubkey(), Amount::from_sat(50_000)) -/// .add_recipient(addr2.script_pubkey(), Amount::from_sat(50_000)); -/// builder.finish()? -/// }; -/// -/// // non-chaining -/// let psbt2 = { -/// let mut builder = wallet.build_tx(); -/// builder.ordering(TxOrdering::Untouched); -/// for addr in &[addr1, addr2] { -/// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(50_000)); -/// } -/// builder.finish()? -/// }; -/// -/// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]); -/// # Ok::<(), anyhow::Error>(()) -/// ``` -/// -/// At the moment [`coin_selection`] is an exception to the rule as it consumes `self`. -/// This means it is usually best to call [`coin_selection`] on the return value of `build_tx` -/// before assigning it. -/// -/// For further examples see [this module](super::tx_builder)'s documentation; -/// -/// [`build_tx`]: Wallet::build_tx -/// [`build_fee_bump`]: Wallet::build_fee_bump -/// [`finish`]: Self::finish -/// [`coin_selection`]: Self::coin_selection -#[derive(Debug)] -pub struct TxBuilder<'a, Cs> { - pub(crate) wallet: &'a mut Wallet, - pub(crate) params: TxParams, - pub(crate) coin_selection: Cs, -} +// /// A transaction builder +// /// +// /// A `TxBuilder` is created by calling [`build_tx`] or [`build_fee_bump`] on a wallet. After +// /// assigning it, you set options on it until finally calling [`finish`] to consume the builder +// and /// generate the transaction. +// /// +// /// Each option setting method on `TxBuilder` takes and returns `&mut self` so you can chain +// calls /// as in the following example: +// /// +// /// ``` +// /// # use bdk_wallet::*; +// /// # use bdk_wallet::tx_builder::*; +// /// # use bitcoin::*; +// /// # use core::str::FromStr; +// /// # use bdk_wallet::ChangeSet; +// /// # use bdk_wallet::error::CreateTxError; +// /// # use anyhow::Error; +// /// # let mut wallet = doctest_wallet!(); +// /// # let addr1 = +// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// # let +// addr2 = addr1.clone(); /// // chaining +// /// let psbt1 = { +// /// let mut builder = wallet.build_tx(); +// /// builder +// /// .ordering(TxOrdering::Untouched) +// /// .add_recipient(addr1.script_pubkey(), Amount::from_sat(50_000)) +// /// .add_recipient(addr2.script_pubkey(), Amount::from_sat(50_000)); +// /// builder.finish()? +// /// }; +// /// +// /// // non-chaining +// /// let psbt2 = { +// /// let mut builder = wallet.build_tx(); +// /// builder.ordering(TxOrdering::Untouched); +// /// for addr in &[addr1, addr2] { +// /// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(50_000)); +// /// } +// /// builder.finish()? +// /// }; +// /// +// /// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]); +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// /// +// /// At the moment [`coin_selection`] is an exception to the rule as it consumes `self`. +// /// This means it is usually best to call [`coin_selection`] on the return value of `build_tx` +// /// before assigning it. +// /// +// /// For further examples see [this module](super::tx_builder)'s documentation; +// /// +// /// [`build_tx`]: Wallet::build_tx +// /// [`build_fee_bump`]: Wallet::build_fee_bump +// /// [`finish`]: Self::finish +// /// [`coin_selection`]: Self::coin_selection +// #[derive(Debug)] +// pub struct TxBuilder<'a, Cs> { +// pub(crate) wallet: &'a mut Wallet, +// pub(crate) params: TxParams, +// pub(crate) coin_selection: Cs, +// } /// The parameters for transaction creation sans coin selection algorithm. //TODO: TxParams should eventually be exposed publicly. @@ -161,622 +160,622 @@ impl Default for FeePolicy { } } -// Methods supported for any CoinSelectionAlgorithm. -impl<'a, Cs> TxBuilder<'a, Cs> { - /// Set a custom fee rate. - /// - /// This method sets the mining fee paid by the transaction as a rate on its size. - /// This means that the total fee paid is equal to `fee_rate` times the size - /// of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core's default - /// relay policy. - /// - /// Note that this is really a minimum feerate -- it's possible to - /// overshoot it slightly since adding a change output to drain the remaining - /// excess might not be viable. - pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { - self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate)); - self - } - - /// Set an absolute fee - /// The fee_absolute method refers to the absolute transaction fee in [`Amount`]. - /// If anyone sets both the `fee_absolute` method and the `fee_rate` method, - /// the `FeePolicy` enum will be set by whichever method was called last, - /// as the [`FeeRate`] and `FeeAmount` are mutually exclusive. - /// - /// Note that this is really a minimum absolute fee -- it's possible to - /// overshoot it slightly since adding a change output to drain the remaining - /// excess might not be viable. - pub fn fee_absolute(&mut self, fee_amount: Amount) -> &mut Self { - self.params.fee_policy = Some(FeePolicy::FeeAmount(fee_amount)); - self - } - - /// Set the policy path to use while creating the transaction for a given keychain. - /// - /// This method accepts a map where the key is the policy node id (see - /// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes of - /// the items that are intended to be satisfied from the policy node (see - /// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)). - /// - /// ## Example - /// - /// An example of when the policy path is needed is the following descriptor: - /// `wsh(thresh(2,pk(A),sj:and_v(v:pk(B),n:older(6)),snj:and_v(v:pk(C),after(630000))))`, - /// derived from the miniscript policy - /// `thresh(2,pk(A),and(pk(B),older(6)),and(pk(C),after(630000)))`. It declares three - /// descriptor fragments, and at the top level it uses `thresh()` to ensure that at least - /// two of them are satisfied. The individual fragments are: - /// - /// 1. `pk(A)` - /// 2. `and(pk(B),older(6))` - /// 3. `and(pk(C),after(630000))` - /// - /// When those conditions are combined in pairs, it's clear that the transaction needs to be - /// created differently depending on how the user intends to satisfy the policy afterwards: - /// - /// * If fragments `1` and `2` are used, the transaction will need to use a specific - /// `n_sequence` in order to spend an `OP_CSV` branch. - /// * If fragments `1` and `3` are used, the transaction will need to use a specific `locktime` - /// in order to spend an `OP_CLTV` branch. - /// * If fragments `2` and `3` are used, the transaction will need both. - /// - /// When the spending policy is represented as a tree (see - /// [`Wallet::policies`](super::Wallet::policies)), every node - /// is assigned a unique identifier that can be used in the policy path to specify which of - /// the node's children the user intends to satisfy: for instance, assuming the `thresh()` - /// root node of this example has an id of `aabbccdd`, the policy path map would look like: - /// - /// `{ "aabbccdd" => [0, 1] }` - /// - /// where the key is the node's id, and the value is a list of the children that should be - /// used, in no particular order. - /// - /// If a particularly complex descriptor has multiple ambiguous thresholds in its structure, - /// multiple entries can be added to the map, one for each node that requires an explicit path. - /// - /// ``` - /// # use std::str::FromStr; - /// # use std::collections::BTreeMap; - /// # use bitcoin::*; - /// # use bdk_wallet::*; - /// # let to_address = - /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") - /// .unwrap() - /// .assume_checked(); - /// # let mut wallet = doctest_wallet!(); - /// let mut path = BTreeMap::new(); - /// path.insert("aabbccdd".to_string(), vec![0, 1]); - /// - /// let builder = wallet - /// .build_tx() - /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)) - /// .policy_path(path, KeychainKind::External); - /// - /// # Ok::<(), anyhow::Error>(()) - /// ``` - pub fn policy_path( - &mut self, - policy_path: BTreeMap>, - keychain: KeychainKind, - ) -> &mut Self { - let to_update = match keychain { - KeychainKind::Internal => &mut self.params.internal_policy_path, - KeychainKind::External => &mut self.params.external_policy_path, - }; - - *to_update = Some(policy_path); - self - } - - /// Add the list of outpoints to the internal list of UTXOs that **must** be spent. - /// - /// If an error occurs while adding any of the UTXOs then none of them are added and the error - /// is returned. - /// - /// These have priority over the "unspendable" UTXOs, meaning that if a UTXO is present both in - /// the "UTXOs" and the "unspendable" list, it will be spent. - /// - /// If a UTXO is inserted multiple times, only the final insertion will take effect. - pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> { - // Canonicalize once, instead of once for every call to `get_utxo`. - let unspent: HashMap = self - .wallet - .list_unspent() - .map(|output| (output.outpoint, output)) - .collect(); - - // Ensure that only unique outpoints are added, but keep insertion order. - let mut visited = >::new(); - - let utxos: Vec = outpoints - .iter() - .filter(|&&op| visited.insert(op)) - .map(|&op| -> Result<_, AddUtxoError> { - let output = unspent - .get(&op) - .cloned() - .ok_or(AddUtxoError::UnknownUtxo(op))?; - Ok(WeightedUtxo { - satisfaction_weight: self - .wallet - .public_descriptor(output.keychain) - .max_weight_to_satisfy() - .expect("descriptor should be satisfiable"), - utxo: Utxo::Local(output), - }) - }) - .collect::>()?; - - // Remove already inserted UTXOs first. - self.params - .utxos - .retain(|wutxo| !visited.contains(&wutxo.utxo.outpoint())); - - // Update the removed UTXOs in their new positions. - self.params.utxos.extend_from_slice(&utxos); - - Ok(self) - } - - /// Add a UTXO to the internal list of UTXOs that **must** be spent - /// - /// These have priority over the "unspendable" UTXOs, meaning that if a UTXO is present both in - /// the "UTXOs" and the "unspendable" list, it will be spent. - pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, AddUtxoError> { - self.add_utxos(&[outpoint]) - } - - /// Add a foreign UTXO i.e. a UTXO not known by this wallet. - /// - /// Foreign UTXOs are not prioritized over local UTXOs. If a local UTXO is added to the - /// manually selected list, it will replace any conflicting foreign UTXOs. However, a foreign - /// UTXO cannot replace a conflicting local UTXO. - /// - /// There might be cases where the UTXO belongs to the wallet but it doesn't have knowledge of - /// it. This is possible if the wallet is not synced or its not being use to track - /// transactions. In those cases is the responsibility of the user to add any possible local - /// UTXOs through the [`TxBuilder::add_utxo`] method. - /// A manually added local UTXO will always have greater precedence than a foreign UTXO. No - /// matter if it was added before or after the foreign UTXO. - /// - /// At a minimum to add a foreign UTXO we need: - /// - /// 1. `outpoint`: To add it to the raw transaction. - /// 2. `psbt_input`: To know the value. - /// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the - /// transaction for fee calculation. - /// - /// There are several security concerns about adding foreign UTXOs that application - /// developers should consider. First, how do you know the value of the input is correct? If a - /// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies the - /// value by checking it against the transaction. If only a `witness_utxo` is provided then this - /// method doesn't verify the value but just takes it as a given -- it is up to you to check - /// that whoever sent you the `input_psbt` was not lying! - /// - /// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your - /// application it may be important that this be known precisely. If not, a malicious - /// counterparty may fool you into putting in a value that is too low, giving the transaction a - /// lower than expected feerate. They could also fool you into putting a value that is too high - /// causing you to pay a fee that is too high. The party who is broadcasting the transaction can - /// of course check the real input weight matches the expected weight prior to broadcasting. - /// - /// To guarantee the `max_weight_to_satisfy` is correct, you can require the party providing the - /// `psbt_input` provide a miniscript descriptor for the input so you can check it against the - /// `script_pubkey` and then ask it for the [`max_weight_to_satisfy`]. - /// - /// This is an **EXPERIMENTAL** feature, API and other major changes are expected. - /// - /// In order to use [`Wallet::calculate_fee`] or [`Wallet::calculate_fee_rate`] for a - /// transaction created with foreign UTXO(s) you must manually insert the corresponding - /// TxOut(s) into the tx graph using the [`Wallet::insert_txout`] function. - /// - /// # Errors - /// - /// This method returns errors in the following circumstances: - /// - /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`. - /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`. - /// - /// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this - /// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`] - /// is called. - /// - /// [`only_witness_utxo`]: Self::only_witness_utxo - /// [`finish`]: Self::finish - /// [`max_weight_to_satisfy`]: miniscript::Descriptor::max_weight_to_satisfy - pub fn add_foreign_utxo( - &mut self, - outpoint: OutPoint, - psbt_input: psbt::Input, - satisfaction_weight: Weight, - ) -> Result<&mut Self, AddForeignUtxoError> { - self.add_foreign_utxo_with_sequence( - outpoint, - psbt_input, - satisfaction_weight, - Sequence::MAX, - ) - } - - /// Same as [add_foreign_utxo](TxBuilder::add_foreign_utxo) but allows to set the nSequence - /// value. - pub fn add_foreign_utxo_with_sequence( - &mut self, - outpoint: OutPoint, - psbt_input: psbt::Input, - satisfaction_weight: Weight, - sequence: Sequence, - ) -> Result<&mut Self, AddForeignUtxoError> { - if psbt_input.witness_utxo.is_none() { - match psbt_input.non_witness_utxo.as_ref() { - Some(tx) => { - if tx.compute_txid() != outpoint.txid { - return Err(AddForeignUtxoError::InvalidTxid { - input_txid: tx.compute_txid(), - foreign_utxo: outpoint, - }); - } - if tx.output.len() <= outpoint.vout as usize { - return Err(AddForeignUtxoError::InvalidOutpoint(outpoint)); - } - } - None => { - return Err(AddForeignUtxoError::MissingUtxo); - } - } - } - - let mut existing_index: Option = None; - - for (idx, wutxo) in self.params.utxos.iter().enumerate() { - if wutxo.utxo.outpoint() == outpoint { - match wutxo.utxo { - Utxo::Local(..) => return Ok(self), - Utxo::Foreign { .. } => { - existing_index = Some(idx); - break; - } - } - } - } - - if let Some(idx) = existing_index { - self.params.utxos.remove(idx); - } - - self.params.utxos.push(WeightedUtxo { - satisfaction_weight, - utxo: Utxo::Foreign { - outpoint, - sequence, - psbt_input: Box::new(psbt_input), - }, - }); - - Ok(self) - } - - /// Only spend utxos added by [`add_utxo`]. - /// - /// The wallet will **not** add additional utxos to the transaction even if they are needed to - /// make the transaction valid. - /// - /// [`add_utxo`]: Self::add_utxo - pub fn manually_selected_only(&mut self) -> &mut Self { - self.params.manually_selected_only = true; - self - } - - /// Replace the internal list of unspendable utxos with a new list - /// - /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`] - /// have priority over these. See the docs of the two linked methods for more details. - pub fn unspendable(&mut self, unspendable: Vec) -> &mut Self { - self.params.unspendable = unspendable.into_iter().collect(); - self - } - - /// Add a utxo to the internal list of unspendable utxos - /// - /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`] - /// have priority over this. See the docs of the two linked methods for more details. - pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self { - self.params.unspendable.insert(unspendable); - self - } - - /// Excludes any outpoints whose enclosing transaction has fewer than `min_confirms` - /// confirmations. - /// - /// `min_confirms` is the minimum number of confirmations a transaction must have in order for - /// its outpoints to remain spendable. - /// - Passing `0` will include all transactions (no filtering). - /// - Passing `1` will exclude all unconfirmed transactions (equivalent to - /// `exclude_unconfirmed`). - /// - Passing `6` will only allow outpoints from transactions with at least 6 confirmations. - /// - /// If you chain this with other filtering methods, the final set of unspendable outpoints will - /// be the union of all filters. - pub fn exclude_below_confirmations(&mut self, min_confirms: u32) -> &mut Self { - let tip_height = self.wallet.latest_checkpoint().height(); - let to_exclude = self - .wallet - .list_unspent() - .filter(|utxo| { - utxo.chain_position - .confirmation_height_upper_bound() - .map_or(0, |h| tip_height.saturating_add(1).saturating_sub(h)) - < min_confirms - }) - .map(|utxo| utxo.outpoint); - for op in to_exclude { - self.params.unspendable.insert(op); - } - self - } - - /// Exclude outpoints whose enclosing transaction is unconfirmed. - /// - /// This is a shorthand for [`exclude_below_confirmations(1)`]. - /// - /// [`exclude_below_confirmations(1)`]: Self::exclude_below_confirmations - pub fn exclude_unconfirmed(&mut self) -> &mut Self { - self.exclude_below_confirmations(1) - } - - /// Sign with a specific sig hash - /// - /// **Use this option very carefully** - pub fn sighash(&mut self, sighash: psbt::PsbtSighashType) -> &mut Self { - self.params.sighash = Some(sighash); - self - } - - /// Choose the ordering for inputs and outputs of the transaction - /// - /// When [TxBuilder::ordering] is set to [TxOrdering::Untouched], the insertion order of - /// recipients and manually selected UTXOs is preserved and reflected exactly in transaction's - /// output and input vectors respectively. If algorithmically selected UTXOs are included, they - /// will be placed after all the manually selected ones in the transaction's input vector. - pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self { - self.params.ordering = ordering; - self - } - - /// Use a specific nLockTime while creating the transaction - /// - /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator. - pub fn nlocktime(&mut self, locktime: absolute::LockTime) -> &mut Self { - self.params.locktime = Some(locktime); - self - } - - /// Build a transaction with a specific version - /// - /// The `version` should always be greater than `0` and greater than `1` if the wallet's - /// descriptors contain an "older" (OP_CSV) operator. - pub fn version(&mut self, version: i32) -> &mut Self { - self.params.version = Some(Version(version)); - self - } - - /// Do not spend change outputs - /// - /// This effectively adds all the change outputs to the "unspendable" list. See - /// [`TxBuilder::unspendable`]. This method assumes the presence of an internal - /// keychain, otherwise it has no effect. - pub fn do_not_spend_change(&mut self) -> &mut Self { - self.params.change_policy = ChangeSpendPolicy::ChangeForbidden; - self - } - - /// Only spend change outputs - /// - /// This effectively adds all the non-change outputs to the "unspendable" list. See - /// [`TxBuilder::unspendable`]. This method assumes the presence of an internal - /// keychain, otherwise it has no effect. - pub fn only_spend_change(&mut self) -> &mut Self { - self.params.change_policy = ChangeSpendPolicy::OnlyChange; - self - } - - /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and - /// [`TxBuilder::only_spend_change`] for some shortcuts. This method assumes the presence - /// of an internal keychain, otherwise it has no effect. - pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self { - self.params.change_policy = change_policy; - self - } - - /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::psbt::Input::witness_utxo) field - /// when spending from SegWit descriptors. - /// - /// This reduces the size of the PSBT, but some signers might reject them due to the lack of - /// the `non_witness_utxo`. - pub fn only_witness_utxo(&mut self) -> &mut Self { - self.params.only_witness_utxo = true; - self - } - - /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and - /// [`psbt::Output::witness_script`](bitcoin::psbt::Output::witness_script) fields. - /// - /// This is useful for signers which always require it, like ColdCard hardware wallets. - pub fn include_output_redeem_witness_script(&mut self) -> &mut Self { - self.params.include_output_redeem_witness_script = true; - self - } - - /// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the external - /// and internal descriptors - /// - /// This is useful for offline signers that take part to a multisig. Some hardware wallets like - /// BitBox and ColdCard are known to require this. - pub fn add_global_xpubs(&mut self) -> &mut Self { - self.params.add_global_xpubs = true; - self - } - - /// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and - /// the change policy. - pub fn drain_wallet(&mut self) -> &mut Self { - self.params.drain_wallet = true; - self - } - - /// Choose the coin selection algorithm - /// - /// Overrides the [`CoinSelectionAlgorithm`]. - /// - /// Note that this function consumes the builder and returns it so it is usually best to put - /// this as the first call on the builder. - pub fn coin_selection(self, coin_selection: P) -> TxBuilder<'a, P> { - TxBuilder { - wallet: self.wallet, - params: self.params, - coin_selection, - } - } - - /// Set an exact nSequence value - /// - /// This can cause conflicts if the wallet's descriptors contain an - /// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. - pub fn set_exact_sequence(&mut self, n_sequence: Sequence) -> &mut Self { - self.params.sequence = Some(n_sequence); - self - } - - /// Set the current blockchain height. - /// - /// This will be used to: - /// 1. Set the nLockTime for preventing fee sniping. **Note**: This will be ignored if you - /// manually specify a nlocktime using [`TxBuilder::nlocktime`]. - /// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not mature - /// at spending height, which is `current_height` + 1, we ignore them in the coin selection. - /// If you want to create a transaction that spends immature coinbase inputs, manually add - /// them using [`TxBuilder::add_utxos`]. - /// - /// In both cases, if you don't provide a current height, we use the last sync height. - pub fn current_height(&mut self, height: u32) -> &mut Self { - self.params.current_height = - Some(absolute::LockTime::from_height(height).expect("Invalid height")); - self - } - - /// Set whether or not the dust limit is checked. - /// - /// **Note**: by avoiding a dust limit check you may end up with a transaction that is - /// non-standard. - pub fn allow_dust(&mut self, allow_dust: bool) -> &mut Self { - self.params.allow_dust = allow_dust; - self - } - - /// Replace the recipients already added with a new list - pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, Amount)>) -> &mut Self { - self.params.recipients = recipients; - self - } - - /// Add a recipient to the internal list - pub fn add_recipient( - &mut self, - script_pubkey: impl Into, - amount: Amount, - ) -> &mut Self { - self.params.recipients.push((script_pubkey.into(), amount)); - self - } - - /// Add data as an output, using OP_RETURN - pub fn add_data>(&mut self, data: &T) -> &mut Self { - let script = ScriptBuf::new_op_return(data); - self.add_recipient(script, Amount::ZERO); - self - } - - /// Sets the address to *drain* excess coins to. - /// - /// Usually, when there are excess coins they are sent to a change address generated by the - /// wallet. This option replaces the usual change address with an arbitrary `script_pubkey` of - /// your choosing. Just as with a change output, if the drain output is not needed (the excess - /// coins are too small) it will not be included in the resulting transaction. The only - /// difference is that it is valid to use `drain_to` without setting any ordinary recipients - /// with [`add_recipient`] (but it is perfectly fine to add recipients as well). - /// - /// If you choose not to set any recipients, you should provide the utxos that the - /// transaction should spend via [`add_utxos`]. - /// - /// # Example - /// - /// `drain_to` is very useful for draining all the coins in a wallet with [`drain_wallet`] to a - /// single address. - /// - /// ``` - /// # use std::str::FromStr; - /// # use bitcoin::*; - /// # use bdk_wallet::*; - /// # use bdk_wallet::ChangeSet; - /// # use bdk_wallet::error::CreateTxError; - /// # use anyhow::Error; - /// # let to_address = - /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") - /// .unwrap() - /// .assume_checked(); - /// # let mut wallet = doctest_wallet!(); - /// let mut tx_builder = wallet.build_tx(); - /// - /// tx_builder - /// // Spend all outputs in this wallet. - /// .drain_wallet() - /// // Send the excess (which is all the coins minus the fee) to this address. - /// .drain_to(to_address.script_pubkey()) - /// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate")); - /// let psbt = tx_builder.finish()?; - /// # Ok::<(), anyhow::Error>(()) - /// ``` - /// - /// [`add_recipient`]: Self::add_recipient - /// [`add_utxos`]: Self::add_utxos - /// [`drain_wallet`]: Self::drain_wallet - pub fn drain_to(&mut self, script_pubkey: ScriptBuf) -> &mut Self { - self.params.drain_to = Some(script_pubkey); - self - } -} - -impl TxBuilder<'_, Cs> { - /// Finish building the transaction. - /// - /// Uses the thread-local random number generator (rng). - /// - /// Returns a new [`Psbt`] per [`BIP174`]. - /// - /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki - /// - /// **WARNING**: To avoid change address reuse you must persist the changes resulting from one - /// or more calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. - #[cfg(feature = "std")] - pub fn finish(self) -> Result { - self.finish_with_aux_rand(&mut bitcoin::key::rand::thread_rng()) - } - - /// Finish building the transaction. - /// - /// Uses a provided random number generator (rng). - /// - /// Returns a new [`Psbt`] per [`BIP174`]. - /// - /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki - /// - /// **WARNING**: To avoid change address reuse you must persist the changes resulting from one - /// or more calls to this method before closing the wallet. See [`Wallet::reveal_next_address`]. - pub fn finish_with_aux_rand(self, rng: &mut impl RngCore) -> Result { - self.wallet.create_tx(self.coin_selection, self.params, rng) - } -} +// // Methods supported for any CoinSelectionAlgorithm. +// impl<'a, Cs> TxBuilder<'a, Cs> { +// /// Set a custom fee rate. +// /// +// /// This method sets the mining fee paid by the transaction as a rate on its size. +// /// This means that the total fee paid is equal to `fee_rate` times the size +// /// of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core's default +// /// relay policy. +// /// +// /// Note that this is really a minimum feerate -- it's possible to +// /// overshoot it slightly since adding a change output to drain the remaining +// /// excess might not be viable. +// pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { +// self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate)); +// self +// } + +// /// Set an absolute fee +// /// The fee_absolute method refers to the absolute transaction fee in [`Amount`]. +// /// If anyone sets both the `fee_absolute` method and the `fee_rate` method, +// /// the `FeePolicy` enum will be set by whichever method was called last, +// /// as the [`FeeRate`] and `FeeAmount` are mutually exclusive. +// /// +// /// Note that this is really a minimum absolute fee -- it's possible to +// /// overshoot it slightly since adding a change output to drain the remaining +// /// excess might not be viable. +// pub fn fee_absolute(&mut self, fee_amount: Amount) -> &mut Self { +// self.params.fee_policy = Some(FeePolicy::FeeAmount(fee_amount)); +// self +// } + +// /// Set the policy path to use while creating the transaction for a given keychain. +// /// +// /// This method accepts a map where the key is the policy node id (see +// /// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes +// of /// the items that are intended to be satisfied from the policy node (see +// /// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)). +// /// +// /// ## Example +// /// +// /// An example of when the policy path is needed is the following descriptor: +// /// `wsh(thresh(2,pk(A),sj:and_v(v:pk(B),n:older(6)),snj:and_v(v:pk(C),after(630000))))`, +// /// derived from the miniscript policy +// /// `thresh(2,pk(A),and(pk(B),older(6)),and(pk(C),after(630000)))`. It declares three +// /// descriptor fragments, and at the top level it uses `thresh()` to ensure that at least +// /// two of them are satisfied. The individual fragments are: +// /// +// /// 1. `pk(A)` +// /// 2. `and(pk(B),older(6))` +// /// 3. `and(pk(C),after(630000))` +// /// +// /// When those conditions are combined in pairs, it's clear that the transaction needs to be +// /// created differently depending on how the user intends to satisfy the policy afterwards: +// /// +// /// * If fragments `1` and `2` are used, the transaction will need to use a specific +// /// `n_sequence` in order to spend an `OP_CSV` branch. +// /// * If fragments `1` and `3` are used, the transaction will need to use a specific +// `locktime` /// in order to spend an `OP_CLTV` branch. +// /// * If fragments `2` and `3` are used, the transaction will need both. +// /// +// /// When the spending policy is represented as a tree (see +// /// [`Wallet::policies`](super::Wallet::policies)), every node +// /// is assigned a unique identifier that can be used in the policy path to specify which of +// /// the node's children the user intends to satisfy: for instance, assuming the `thresh()` +// /// root node of this example has an id of `aabbccdd`, the policy path map would look like: +// /// +// /// `{ "aabbccdd" => [0, 1] }` +// /// +// /// where the key is the node's id, and the value is a list of the children that should be +// /// used, in no particular order. +// /// +// /// If a particularly complex descriptor has multiple ambiguous thresholds in its structure, +// /// multiple entries can be added to the map, one for each node that requires an explicit +// path. /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use std::collections::BTreeMap; +// /// # use bitcoin::*; +// /// # use bdk_wallet::*; +// /// # let to_address = +// /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") +// /// .unwrap() +// /// .assume_checked(); +// /// # let mut wallet = doctest_wallet!(); +// /// let mut path = BTreeMap::new(); +// /// path.insert("aabbccdd".to_string(), vec![0, 1]); +// /// +// /// let builder = wallet +// /// .build_tx() +// /// .add_recipient(to_address.script_pubkey(), Amount::from_sat(50_000)) +// /// .policy_path(path, KeychainKind::External); +// /// +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// pub fn policy_path( +// &mut self, +// policy_path: BTreeMap>, +// keychain: KeychainKind, +// ) -> &mut Self { +// let to_update = match keychain { +// KeychainKind::Internal => &mut self.params.internal_policy_path, +// KeychainKind::External => &mut self.params.external_policy_path, +// }; + +// *to_update = Some(policy_path); +// self +// } + +// /// Add the list of outpoints to the internal list of UTXOs that **must** be spent. +// /// +// /// If an error occurs while adding any of the UTXOs then none of them are added and the +// error /// is returned. +// /// +// /// These have priority over the "unspendable" UTXOs, meaning that if a UTXO is present both +// in /// the "UTXOs" and the "unspendable" list, it will be spent. +// /// +// /// If a UTXO is inserted multiple times, only the final insertion will take effect. +// pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> { +// // Canonicalize once, instead of once for every call to `get_utxo`. +// let unspent: HashMap = self +// .wallet +// .list_unspent() +// .map(|output| (output.outpoint, output)) +// .collect(); + +// // Ensure that only unique outpoints are added, but keep insertion order. +// let mut visited = >::new(); + +// let utxos: Vec = outpoints +// .iter() +// .filter(|&&op| visited.insert(op)) +// .map(|&op| -> Result<_, AddUtxoError> { +// let output = unspent +// .get(&op) +// .cloned() +// .ok_or(AddUtxoError::UnknownUtxo(op))?; +// Ok(WeightedUtxo { +// satisfaction_weight: self +// .wallet +// .public_descriptor(output.keychain) +// .max_weight_to_satisfy() +// .expect("descriptor should be satisfiable"), +// utxo: Utxo::Local(output), +// }) +// }) +// .collect::>()?; + +// // Remove already inserted UTXOs first. +// self.params +// .utxos +// .retain(|wutxo| !visited.contains(&wutxo.utxo.outpoint())); + +// // Update the removed UTXOs in their new positions. +// self.params.utxos.extend_from_slice(&utxos); + +// Ok(self) +// } + +// /// Add a UTXO to the internal list of UTXOs that **must** be spent +// /// +// /// These have priority over the "unspendable" UTXOs, meaning that if a UTXO is present both +// in /// the "UTXOs" and the "unspendable" list, it will be spent. +// pub fn add_utxo(&mut self, outpoint: OutPoint) -> Result<&mut Self, AddUtxoError> { +// self.add_utxos(&[outpoint]) +// } + +// /// Add a foreign UTXO i.e. a UTXO not known by this wallet. +// /// +// /// Foreign UTXOs are not prioritized over local UTXOs. If a local UTXO is added to the +// /// manually selected list, it will replace any conflicting foreign UTXOs. However, a foreign +// /// UTXO cannot replace a conflicting local UTXO. +// /// +// /// There might be cases where the UTXO belongs to the wallet but it doesn't have knowledge +// of /// it. This is possible if the wallet is not synced or its not being use to track +// /// transactions. In those cases is the responsibility of the user to add any possible local +// /// UTXOs through the [`TxBuilder::add_utxo`] method. +// /// A manually added local UTXO will always have greater precedence than a foreign UTXO. No +// /// matter if it was added before or after the foreign UTXO. +// /// +// /// At a minimum to add a foreign UTXO we need: +// /// +// /// 1. `outpoint`: To add it to the raw transaction. +// /// 2. `psbt_input`: To know the value. +// /// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the +// /// transaction for fee calculation. +// /// +// /// There are several security concerns about adding foreign UTXOs that application +// /// developers should consider. First, how do you know the value of the input is correct? If +// a /// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies +// the /// value by checking it against the transaction. If only a `witness_utxo` is provided +// then this /// method doesn't verify the value but just takes it as a given -- it is up to you +// to check /// that whoever sent you the `input_psbt` was not lying! +// /// +// /// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your +// /// application it may be important that this be known precisely. If not, a malicious +// /// counterparty may fool you into putting in a value that is too low, giving the transaction +// a /// lower than expected feerate. They could also fool you into putting a value that is too +// high /// causing you to pay a fee that is too high. The party who is broadcasting the +// transaction can /// of course check the real input weight matches the expected weight prior +// to broadcasting. /// +// /// To guarantee the `max_weight_to_satisfy` is correct, you can require the party providing +// the /// `psbt_input` provide a miniscript descriptor for the input so you can check it +// against the /// `script_pubkey` and then ask it for the [`max_weight_to_satisfy`]. +// /// +// /// This is an **EXPERIMENTAL** feature, API and other major changes are expected. +// /// +// /// In order to use [`Wallet::calculate_fee`] or [`Wallet::calculate_fee_rate`] for a +// /// transaction created with foreign UTXO(s) you must manually insert the corresponding +// /// TxOut(s) into the tx graph using the [`Wallet::insert_txout`] function. +// /// +// /// # Errors +// /// +// /// This method returns errors in the following circumstances: +// /// +// /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`. +// /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`. +// /// +// /// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this +// /// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`] +// /// is called. +// /// +// /// [`only_witness_utxo`]: Self::only_witness_utxo +// /// [`finish`]: Self::finish +// /// [`max_weight_to_satisfy`]: miniscript::Descriptor::max_weight_to_satisfy +// pub fn add_foreign_utxo( +// &mut self, +// outpoint: OutPoint, +// psbt_input: psbt::Input, +// satisfaction_weight: Weight, +// ) -> Result<&mut Self, AddForeignUtxoError> { +// self.add_foreign_utxo_with_sequence( +// outpoint, +// psbt_input, +// satisfaction_weight, +// Sequence::MAX, +// ) +// } + +// /// Same as [add_foreign_utxo](TxBuilder::add_foreign_utxo) but allows to set the nSequence +// /// value. +// pub fn add_foreign_utxo_with_sequence( +// &mut self, +// outpoint: OutPoint, +// psbt_input: psbt::Input, +// satisfaction_weight: Weight, +// sequence: Sequence, +// ) -> Result<&mut Self, AddForeignUtxoError> { +// if psbt_input.witness_utxo.is_none() { +// match psbt_input.non_witness_utxo.as_ref() { +// Some(tx) => { +// if tx.compute_txid() != outpoint.txid { +// return Err(AddForeignUtxoError::InvalidTxid { +// input_txid: tx.compute_txid(), +// foreign_utxo: outpoint, +// }); +// } +// if tx.output.len() <= outpoint.vout as usize { +// return Err(AddForeignUtxoError::InvalidOutpoint(outpoint)); +// } +// } +// None => { +// return Err(AddForeignUtxoError::MissingUtxo); +// } +// } +// } + +// let mut existing_index: Option = None; + +// for (idx, wutxo) in self.params.utxos.iter().enumerate() { +// if wutxo.utxo.outpoint() == outpoint { +// match wutxo.utxo { +// Utxo::Local(..) => return Ok(self), +// Utxo::Foreign { .. } => { +// existing_index = Some(idx); +// break; +// } +// } +// } +// } + +// if let Some(idx) = existing_index { +// self.params.utxos.remove(idx); +// } + +// self.params.utxos.push(WeightedUtxo { +// satisfaction_weight, +// utxo: Utxo::Foreign { +// outpoint, +// sequence, +// psbt_input: Box::new(psbt_input), +// }, +// }); + +// Ok(self) +// } + +// /// Only spend utxos added by [`add_utxo`]. +// /// +// /// The wallet will **not** add additional utxos to the transaction even if they are needed +// to /// make the transaction valid. +// /// +// /// [`add_utxo`]: Self::add_utxo +// pub fn manually_selected_only(&mut self) -> &mut Self { +// self.params.manually_selected_only = true; +// self +// } + +// /// Replace the internal list of unspendable utxos with a new list +// /// +// /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`] +// /// have priority over these. See the docs of the two linked methods for more details. +// pub fn unspendable(&mut self, unspendable: Vec) -> &mut Self { +// self.params.unspendable = unspendable.into_iter().collect(); +// self +// } + +// /// Add a utxo to the internal list of unspendable utxos +// /// +// /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::add_utxo`] +// /// have priority over this. See the docs of the two linked methods for more details. +// pub fn add_unspendable(&mut self, unspendable: OutPoint) -> &mut Self { +// self.params.unspendable.insert(unspendable); +// self +// } + +// /// Excludes any outpoints whose enclosing transaction has fewer than `min_confirms` +// /// confirmations. +// /// +// /// `min_confirms` is the minimum number of confirmations a transaction must have in order +// for /// its outpoints to remain spendable. +// /// - Passing `0` will include all transactions (no filtering). +// /// - Passing `1` will exclude all unconfirmed transactions (equivalent to +// /// `exclude_unconfirmed`). +// /// - Passing `6` will only allow outpoints from transactions with at least 6 confirmations. +// /// +// /// If you chain this with other filtering methods, the final set of unspendable outpoints +// will /// be the union of all filters. +// pub fn exclude_below_confirmations(&mut self, min_confirms: u32) -> &mut Self { +// let tip_height = self.wallet.latest_checkpoint().height(); +// let to_exclude = self +// .wallet +// .list_unspent() +// .filter(|utxo| { +// utxo.chain_position +// .confirmation_height_upper_bound() +// .map_or(0, |h| tip_height.saturating_add(1).saturating_sub(h)) +// < min_confirms +// }) +// .map(|utxo| utxo.outpoint); +// for op in to_exclude { +// self.params.unspendable.insert(op); +// } +// self +// } + +// /// Exclude outpoints whose enclosing transaction is unconfirmed. +// /// +// /// This is a shorthand for [`exclude_below_confirmations(1)`]. +// /// +// /// [`exclude_below_confirmations(1)`]: Self::exclude_below_confirmations +// pub fn exclude_unconfirmed(&mut self) -> &mut Self { +// self.exclude_below_confirmations(1) +// } + +// /// Sign with a specific sig hash +// /// +// /// **Use this option very carefully** +// pub fn sighash(&mut self, sighash: psbt::PsbtSighashType) -> &mut Self { +// self.params.sighash = Some(sighash); +// self +// } + +// /// Choose the ordering for inputs and outputs of the transaction +// /// +// /// When [TxBuilder::ordering] is set to [TxOrdering::Untouched], the insertion order of +// /// recipients and manually selected UTXOs is preserved and reflected exactly in +// transaction's /// output and input vectors respectively. If algorithmically selected UTXOs +// are included, they /// will be placed after all the manually selected ones in the +// transaction's input vector. pub fn ordering(&mut self, ordering: TxOrdering) -> &mut Self { +// self.params.ordering = ordering; +// self +// } + +// /// Use a specific nLockTime while creating the transaction +// /// +// /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) +// operator. pub fn nlocktime(&mut self, locktime: absolute::LockTime) -> &mut Self { +// self.params.locktime = Some(locktime); +// self +// } + +// /// Build a transaction with a specific version +// /// +// /// The `version` should always be greater than `0` and greater than `1` if the wallet's +// /// descriptors contain an "older" (OP_CSV) operator. +// pub fn version(&mut self, version: i32) -> &mut Self { +// self.params.version = Some(Version(version)); +// self +// } + +// /// Do not spend change outputs +// /// +// /// This effectively adds all the change outputs to the "unspendable" list. See +// /// [`TxBuilder::unspendable`]. This method assumes the presence of an internal +// /// keychain, otherwise it has no effect. +// pub fn do_not_spend_change(&mut self) -> &mut Self { +// self.params.change_policy = ChangeSpendPolicy::ChangeForbidden; +// self +// } + +// /// Only spend change outputs +// /// +// /// This effectively adds all the non-change outputs to the "unspendable" list. See +// /// [`TxBuilder::unspendable`]. This method assumes the presence of an internal +// /// keychain, otherwise it has no effect. +// pub fn only_spend_change(&mut self) -> &mut Self { +// self.params.change_policy = ChangeSpendPolicy::OnlyChange; +// self +// } + +// /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and +// /// [`TxBuilder::only_spend_change`] for some shortcuts. This method assumes the presence +// /// of an internal keychain, otherwise it has no effect. +// pub fn change_policy(&mut self, change_policy: ChangeSpendPolicy) -> &mut Self { +// self.params.change_policy = change_policy; +// self +// } + +// /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::psbt::Input::witness_utxo) field +// /// when spending from SegWit descriptors. +// /// +// /// This reduces the size of the PSBT, but some signers might reject them due to the lack of +// /// the `non_witness_utxo`. +// pub fn only_witness_utxo(&mut self) -> &mut Self { +// self.params.only_witness_utxo = true; +// self +// } + +// /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and +// /// [`psbt::Output::witness_script`](bitcoin::psbt::Output::witness_script) fields. +// /// +// /// This is useful for signers which always require it, like ColdCard hardware wallets. +// pub fn include_output_redeem_witness_script(&mut self) -> &mut Self { +// self.params.include_output_redeem_witness_script = true; +// self +// } + +// /// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the +// external /// and internal descriptors +// /// +// /// This is useful for offline signers that take part to a multisig. Some hardware wallets +// like /// BitBox and ColdCard are known to require this. +// pub fn add_global_xpubs(&mut self) -> &mut Self { +// self.params.add_global_xpubs = true; +// self +// } + +// /// Spend all the available inputs. This respects filters like [`TxBuilder::unspendable`] and +// /// the change policy. +// pub fn drain_wallet(&mut self) -> &mut Self { +// self.params.drain_wallet = true; +// self +// } + +// /// Choose the coin selection algorithm +// /// +// /// Overrides the [`CoinSelectionAlgorithm`]. +// /// +// /// Note that this function consumes the builder and returns it so it is usually best to put +// /// this as the first call on the builder. +// pub fn coin_selection(self, coin_selection: P) -> TxBuilder<'a, P> +// { TxBuilder { +// wallet: self.wallet, +// params: self.params, +// coin_selection, +// } +// } + +// /// Set an exact nSequence value +// /// +// /// This can cause conflicts if the wallet's descriptors contain an +// /// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. +// pub fn set_exact_sequence(&mut self, n_sequence: Sequence) -> &mut Self { +// self.params.sequence = Some(n_sequence); +// self +// } + +// /// Set the current blockchain height. +// /// +// /// This will be used to: +// /// 1. Set the nLockTime for preventing fee sniping. **Note**: This will be ignored if you +// /// manually specify a nlocktime using [`TxBuilder::nlocktime`]. +// /// 2. Decide whether coinbase outputs are mature or not. If the coinbase outputs are not +// mature /// at spending height, which is `current_height` + 1, we ignore them in the coin +// selection. /// If you want to create a transaction that spends immature coinbase inputs, +// manually add /// them using [`TxBuilder::add_utxos`]. +// /// +// /// In both cases, if you don't provide a current height, we use the last sync height. +// pub fn current_height(&mut self, height: u32) -> &mut Self { +// self.params.current_height = +// Some(absolute::LockTime::from_height(height).expect("Invalid height")); +// self +// } + +// /// Set whether or not the dust limit is checked. +// /// +// /// **Note**: by avoiding a dust limit check you may end up with a transaction that is +// /// non-standard. +// pub fn allow_dust(&mut self, allow_dust: bool) -> &mut Self { +// self.params.allow_dust = allow_dust; +// self +// } + +// /// Replace the recipients already added with a new list +// pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, Amount)>) -> &mut Self { +// self.params.recipients = recipients; +// self +// } + +// /// Add a recipient to the internal list +// pub fn add_recipient( +// &mut self, +// script_pubkey: impl Into, +// amount: Amount, +// ) -> &mut Self { +// self.params.recipients.push((script_pubkey.into(), amount)); +// self +// } + +// /// Add data as an output, using OP_RETURN +// pub fn add_data>(&mut self, data: &T) -> &mut Self { +// let script = ScriptBuf::new_op_return(data); +// self.add_recipient(script, Amount::ZERO); +// self +// } + +// /// Sets the address to *drain* excess coins to. +// /// +// /// Usually, when there are excess coins they are sent to a change address generated by the +// /// wallet. This option replaces the usual change address with an arbitrary `script_pubkey` +// of /// your choosing. Just as with a change output, if the drain output is not needed (the +// excess /// coins are too small) it will not be included in the resulting transaction. The +// only /// difference is that it is valid to use `drain_to` without setting any ordinary +// recipients /// with [`add_recipient`] (but it is perfectly fine to add recipients as well). +// /// +// /// If you choose not to set any recipients, you should provide the utxos that the +// /// transaction should spend via [`add_utxos`]. +// /// +// /// # Example +// /// +// /// `drain_to` is very useful for draining all the coins in a wallet with [`drain_wallet`] to +// a /// single address. +// /// +// /// ``` +// /// # use std::str::FromStr; +// /// # use bitcoin::*; +// /// # use bdk_wallet::*; +// /// # use bdk_wallet::ChangeSet; +// /// # use bdk_wallet::error::CreateTxError; +// /// # use anyhow::Error; +// /// # let to_address = +// /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") +// /// .unwrap() +// /// .assume_checked(); +// /// # let mut wallet = doctest_wallet!(); +// /// let mut tx_builder = wallet.build_tx(); +// /// +// /// tx_builder +// /// // Spend all outputs in this wallet. +// /// .drain_wallet() +// /// // Send the excess (which is all the coins minus the fee) to this address. +// /// .drain_to(to_address.script_pubkey()) +// /// .fee_rate(FeeRate::from_sat_per_vb(5).expect("valid feerate")); +// /// let psbt = tx_builder.finish()?; +// /// # Ok::<(), anyhow::Error>(()) +// /// ``` +// /// +// /// [`add_recipient`]: Self::add_recipient +// /// [`add_utxos`]: Self::add_utxos +// /// [`drain_wallet`]: Self::drain_wallet +// pub fn drain_to(&mut self, script_pubkey: ScriptBuf) -> &mut Self { +// self.params.drain_to = Some(script_pubkey); +// self +// } +// } + +// impl TxBuilder<'_, Cs> { +// /// Finish building the transaction. +// /// +// /// Uses the thread-local random number generator (rng). +// /// +// /// Returns a new [`Psbt`] per [`BIP174`]. +// /// +// /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +// /// +// /// **WARNING**: To avoid change address reuse you must persist the changes resulting from +// one /// or more calls to this method before closing the wallet. See +// [`Wallet::reveal_next_address`]. #[cfg(feature = "std")] +// pub fn finish(self) -> Result { +// self.finish_with_aux_rand(&mut bitcoin::key::rand::thread_rng()) +// } + +// /// Finish building the transaction. +// /// +// /// Uses a provided random number generator (rng). +// /// +// /// Returns a new [`Psbt`] per [`BIP174`]. +// /// +// /// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki +// /// +// /// **WARNING**: To avoid change address reuse you must persist the changes resulting from +// one /// or more calls to this method before closing the wallet. See +// [`Wallet::reveal_next_address`]. pub fn finish_with_aux_rand(self, rng: &mut impl RngCore) -> +// Result { self.wallet.create_tx(self.coin_selection, self.params, +// rng) } +// } #[derive(Debug)] /// Error returned from [`TxBuilder::add_utxo`] and [`TxBuilder::add_utxos`] @@ -1165,282 +1164,282 @@ mod test { assert_eq!(filtered[0].keychain, KeychainKind::Internal); } - #[test] - fn test_exclude_unconfirmed() { - use crate::test_utils::*; - use bdk_chain::BlockId; - use bitcoin::{hashes::Hash, BlockHash, Network}; - - let mut wallet = Wallet::create_single(get_test_tr_single_sig()) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - let recipient = wallet.next_unused_address(KeychainKind::External).address; - - insert_checkpoint( - &mut wallet, - BlockId { - height: 1, - hash: BlockHash::all_zeros(), - }, - ); - insert_checkpoint( - &mut wallet, - BlockId { - height: 2, - hash: BlockHash::all_zeros(), - }, - ); - receive_output( - &mut wallet, - Amount::ONE_BTC, - ReceiveTo::Block(chain::ConfirmationBlockTime { - block_id: BlockId { - height: 1, - hash: BlockHash::all_zeros(), - }, - confirmation_time: 1, - }), - ); - receive_output( - &mut wallet, - Amount::ONE_BTC * 2, - ReceiveTo::Block(chain::ConfirmationBlockTime { - block_id: BlockId { - height: 2, - hash: BlockHash::all_zeros(), - }, - confirmation_time: 2, - }), - ); - receive_output(&mut wallet, Amount::ONE_BTC * 3, ReceiveTo::Mempool(100)); - - // Exclude nothing. - { - let mut builder = wallet.build_tx(); - builder - .fee_rate(FeeRate::ZERO) - .exclude_below_confirmations(0) - .drain_wallet() - .drain_to(recipient.script_pubkey()); - let tx = builder.finish().unwrap(); - let output = tx.unsigned_tx.output.first().expect("must have one output"); - assert_eq!(output.value, Amount::ONE_BTC * 6); - } - - // Exclude < 1 conf (a.k.a exclude unconfirmed). - { - let mut builder = wallet.build_tx(); - builder - .fee_rate(FeeRate::ZERO) - .exclude_below_confirmations(1) - .drain_wallet() - .drain_to(recipient.script_pubkey()); - let tx = builder.finish().unwrap(); - let output = tx.unsigned_tx.output.first().expect("must have one output"); - assert_eq!(output.value, Amount::ONE_BTC * 3); - } - - // Exclude < 2 conf (a.k.a need at least 2 conf) - { - let mut builder = wallet.build_tx(); - builder - .fee_rate(FeeRate::ZERO) - .exclude_below_confirmations(2) - .drain_wallet() - .drain_to(recipient.script_pubkey()); - let tx = builder.finish().unwrap(); - let output = tx.unsigned_tx.output.first().expect("must have one output"); - assert_eq!(output.value, Amount::ONE_BTC); - } - } - - #[test] - fn test_build_fee_bump_remove_change_output_single_desc() { - use crate::test_utils::*; - use bdk_chain::BlockId; - use bitcoin::{hashes::Hash, BlockHash, Network}; - - let mut wallet = Wallet::create_single(get_test_tr_single_sig()) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - - insert_checkpoint( - &mut wallet, - BlockId { - height: 1, - hash: BlockHash::all_zeros(), - }, - ); - - receive_output_in_latest_block(&mut wallet, Amount::ONE_BTC); - - // tx1 sending 15k sat to a recipient - let recip = ScriptBuf::from_hex( - "5120e8f5c4dc2f5d6a7595e7b108cb063da9c7550312da1e22875d78b9db62b59cd5", - ) - .unwrap(); - let mut builder = wallet.build_tx(); - builder.add_recipient(recip.clone(), Amount::from_sat(15_000)); - builder.fee_absolute(Amount::from_sat(1_000)); - let psbt = builder.finish().unwrap(); - - let tx = psbt.extract_tx().unwrap(); - let txid = tx.compute_txid(); - let feerate = wallet.calculate_fee_rate(&tx).unwrap().to_sat_per_kwu(); - insert_tx(&mut wallet, tx); - - // build fee bump - let mut builder = wallet.build_fee_bump(txid).unwrap(); - assert_eq!( - builder.params.recipients, - vec![(recip, Amount::from_sat(15_000))] - ); - builder.fee_rate(FeeRate::from_sat_per_kwu(feerate + 250)); - let _ = builder.finish().unwrap(); - } - - #[test] - fn duplicated_utxos_in_add_utxos_are_only_added_once() { - use crate::test_utils::get_funded_wallet_wpkh; - - let (mut wallet, _) = get_funded_wallet_wpkh(); - let utxo = wallet.list_unspent().next().unwrap(); - let op = utxo.outpoint; - - let mut tx_builder = wallet.build_tx(); - tx_builder.add_utxos(&[op, op, op]).unwrap(); - - assert_eq!(tx_builder.params.utxos.len(), 1); - } - - #[test] - fn not_duplicated_utxos_in_required_list() { - use crate::test_utils::get_funded_wallet_wpkh; - let (mut wallet1, _) = get_funded_wallet_wpkh(); - let utxo1 @ LocalOutput { outpoint, .. } = wallet1.list_unspent().next().unwrap(); - let mut builder = wallet1.build_tx(); - let fake_weighted_utxo = WeightedUtxo { - satisfaction_weight: Weight::from_wu(107), - utxo: Utxo::Local(utxo1.clone()), - }; - - for _ in 0..3 { - builder.add_utxo(outpoint).expect("should add"); - } - assert_eq!(vec![fake_weighted_utxo], builder.params.utxos); - } - - #[test] - fn not_duplicated_foreign_utxos_with_same_outpoint_but_different_weight() { - use crate::test_utils::{get_funded_wallet_single, get_funded_wallet_wpkh, get_test_wpkh}; - - // Use two different wallets to avoid adding local UTXOs - let (wallet1, txid1) = get_funded_wallet_wpkh(); - let (mut wallet2, txid2) = get_funded_wallet_single(get_test_wpkh()); - - // if the transactions were produced by the same wallet the following assert should fail - assert_ne!(txid1, txid2); - - let utxo1 = wallet1.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); - - let satisfaction_weight = wallet1 - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - let mut builder = wallet2.build_tx(); - - // add foreign UTXO with satisfaction weight x - assert!(builder - .add_foreign_utxo( - utxo1.outpoint, - psbt::Input { - non_witness_utxo: Some(tx1.as_ref().clone()), - ..Default::default() - }, - satisfaction_weight, - ) - .is_ok()); - - let modified_satisfaction_weight = satisfaction_weight - Weight::from_wu(6); - - assert_ne!(satisfaction_weight, modified_satisfaction_weight); - - // add foreign UTXO with same outpoint but satisfaction weight x - 6wu - assert!(builder - .add_foreign_utxo( - utxo1.outpoint, - psbt::Input { - non_witness_utxo: Some(tx1.as_ref().clone()), - ..Default::default() - }, - modified_satisfaction_weight, - ) - .is_ok()); - - assert_eq!(builder.params.utxos.len(), 1); - assert_eq!( - builder.params.utxos[0].satisfaction_weight, - modified_satisfaction_weight - ); - } - - // Test that local outputs have precedence over utxos added via `add_foreign_utxo` - #[test] - fn test_local_utxos_have_precedence_over_foreign_utxos() { - use crate::test_utils::get_funded_wallet_wpkh; - let (mut wallet, _) = get_funded_wallet_wpkh(); - - let utxo = wallet.list_unspent().next().unwrap(); - let outpoint = utxo.outpoint; - - // case 1: add foreign after local, expect local is kept - let mut builder = wallet.build_tx(); - builder.add_utxo(outpoint).unwrap(); - - assert_eq!(builder.params.utxos[0].utxo.outpoint(), outpoint); - - builder - .add_foreign_utxo( - outpoint, - psbt::Input { - witness_utxo: Some(utxo.txout.clone()), - ..Default::default() - }, - Weight::from_wu(107), - ) - .unwrap(); - - assert_eq!(builder.params.utxos.len(), 1); - assert!(matches!( - &builder.params.utxos[0].utxo, - Utxo::Local(output) if output.outpoint == outpoint, - )); - - // case 2: add local after foreign, expect foreign is removed - builder.params = TxParams::default(); - - builder - .add_foreign_utxo( - outpoint, - psbt::Input { - witness_utxo: Some(utxo.txout), - ..Default::default() - }, - Weight::from_wu(107), - ) - .unwrap(); - - assert_eq!(builder.params.utxos[0].utxo.outpoint(), outpoint); - - builder.add_utxo(outpoint).unwrap(); - - assert_eq!(builder.params.utxos.len(), 1); - assert!( - matches!(&builder.params.utxos[0].utxo, Utxo::Local(output) if output.outpoint == outpoint) - ); - } + // #[test] + // fn test_exclude_unconfirmed() { + // use crate::test_utils::*; + // use bdk_chain::BlockId; + // use bitcoin::{hashes::Hash, BlockHash, Network}; + + // let mut wallet = Wallet::create_single(get_test_tr_single_sig()) + // .network(Network::Regtest) + // .create_wallet_no_persist() + // .unwrap(); + // let recipient = wallet.next_unused_address(KeychainKind::External).address; + + // insert_checkpoint( + // &mut wallet, + // BlockId { + // height: 1, + // hash: BlockHash::all_zeros(), + // }, + // ); + // insert_checkpoint( + // &mut wallet, + // BlockId { + // height: 2, + // hash: BlockHash::all_zeros(), + // }, + // ); + // receive_output( + // &mut wallet, + // Amount::ONE_BTC, + // ReceiveTo::Block(chain::ConfirmationBlockTime { + // block_id: BlockId { + // height: 1, + // hash: BlockHash::all_zeros(), + // }, + // confirmation_time: 1, + // }), + // ); + // receive_output( + // &mut wallet, + // Amount::ONE_BTC * 2, + // ReceiveTo::Block(chain::ConfirmationBlockTime { + // block_id: BlockId { + // height: 2, + // hash: BlockHash::all_zeros(), + // }, + // confirmation_time: 2, + // }), + // ); + // receive_output(&mut wallet, Amount::ONE_BTC * 3, ReceiveTo::Mempool(100)); + + // // Exclude nothing. + // { + // let mut builder = wallet.build_tx(); + // builder + // .fee_rate(FeeRate::ZERO) + // .exclude_below_confirmations(0) + // .drain_wallet() + // .drain_to(recipient.script_pubkey()); + // let tx = builder.finish().unwrap(); + // let output = tx.unsigned_tx.output.first().expect("must have one output"); + // assert_eq!(output.value, Amount::ONE_BTC * 6); + // } + + // // Exclude < 1 conf (a.k.a exclude unconfirmed). + // { + // let mut builder = wallet.build_tx(); + // builder + // .fee_rate(FeeRate::ZERO) + // .exclude_below_confirmations(1) + // .drain_wallet() + // .drain_to(recipient.script_pubkey()); + // let tx = builder.finish().unwrap(); + // let output = tx.unsigned_tx.output.first().expect("must have one output"); + // assert_eq!(output.value, Amount::ONE_BTC * 3); + // } + + // // Exclude < 2 conf (a.k.a need at least 2 conf) + // { + // let mut builder = wallet.build_tx(); + // builder + // .fee_rate(FeeRate::ZERO) + // .exclude_below_confirmations(2) + // .drain_wallet() + // .drain_to(recipient.script_pubkey()); + // let tx = builder.finish().unwrap(); + // let output = tx.unsigned_tx.output.first().expect("must have one output"); + // assert_eq!(output.value, Amount::ONE_BTC); + // } + // } + + // #[test] + // fn test_build_fee_bump_remove_change_output_single_desc() { + // use crate::test_utils::*; + // use bdk_chain::BlockId; + // use bitcoin::{hashes::Hash, BlockHash, Network}; + + // let mut wallet = Wallet::create_single(get_test_tr_single_sig()) + // .network(Network::Regtest) + // .create_wallet_no_persist() + // .unwrap(); + + // insert_checkpoint( + // &mut wallet, + // BlockId { + // height: 1, + // hash: BlockHash::all_zeros(), + // }, + // ); + + // receive_output_in_latest_block(&mut wallet, Amount::ONE_BTC); + + // // tx1 sending 15k sat to a recipient + // let recip = ScriptBuf::from_hex( + // "5120e8f5c4dc2f5d6a7595e7b108cb063da9c7550312da1e22875d78b9db62b59cd5", + // ) + // .unwrap(); + // let mut builder = wallet.build_tx(); + // builder.add_recipient(recip.clone(), Amount::from_sat(15_000)); + // builder.fee_absolute(Amount::from_sat(1_000)); + // let psbt = builder.finish().unwrap(); + + // let tx = psbt.extract_tx().unwrap(); + // let txid = tx.compute_txid(); + // let feerate = wallet.calculate_fee_rate(&tx).unwrap().to_sat_per_kwu(); + // insert_tx(&mut wallet, tx); + + // // build fee bump + // let mut builder = wallet.build_fee_bump(txid).unwrap(); + // assert_eq!( + // builder.params.recipients, + // vec![(recip, Amount::from_sat(15_000))] + // ); + // builder.fee_rate(FeeRate::from_sat_per_kwu(feerate + 250)); + // let _ = builder.finish().unwrap(); + // } + + // #[test] + // fn duplicated_utxos_in_add_utxos_are_only_added_once() { + // use crate::test_utils::get_funded_wallet_wpkh; + + // let (mut wallet, _) = get_funded_wallet_wpkh(); + // let utxo = wallet.list_unspent().next().unwrap(); + // let op = utxo.outpoint; + + // let mut tx_builder = wallet.build_tx(); + // tx_builder.add_utxos(&[op, op, op]).unwrap(); + + // assert_eq!(tx_builder.params.utxos.len(), 1); + // } + + // #[test] + // fn not_duplicated_utxos_in_required_list() { + // use crate::test_utils::get_funded_wallet_wpkh; + // let (mut wallet1, _) = get_funded_wallet_wpkh(); + // let utxo1 @ LocalOutput { outpoint, .. } = wallet1.list_unspent().next().unwrap(); + // let mut builder = wallet1.build_tx(); + // let fake_weighted_utxo = WeightedUtxo { + // satisfaction_weight: Weight::from_wu(107), + // utxo: Utxo::Local(utxo1.clone()), + // }; + + // for _ in 0..3 { + // builder.add_utxo(outpoint).expect("should add"); + // } + // assert_eq!(vec![fake_weighted_utxo], builder.params.utxos); + // } + + // #[test] + // fn not_duplicated_foreign_utxos_with_same_outpoint_but_different_weight() { + // use crate::test_utils::{get_funded_wallet_single, get_funded_wallet_wpkh, get_test_wpkh}; + + // // Use two different wallets to avoid adding local UTXOs + // let (wallet1, txid1) = get_funded_wallet_wpkh(); + // let (mut wallet2, txid2) = get_funded_wallet_single(get_test_wpkh()); + + // // if the transactions were produced by the same wallet the following assert should fail + // assert_ne!(txid1, txid2); + + // let utxo1 = wallet1.list_unspent().next().unwrap(); + // let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); + + // let satisfaction_weight = wallet1 + // .public_descriptor(KeychainKind::External) + // .max_weight_to_satisfy() + // .unwrap(); + + // let mut builder = wallet2.build_tx(); + + // // add foreign UTXO with satisfaction weight x + // assert!(builder + // .add_foreign_utxo( + // utxo1.outpoint, + // psbt::Input { + // non_witness_utxo: Some(tx1.as_ref().clone()), + // ..Default::default() + // }, + // satisfaction_weight, + // ) + // .is_ok()); + + // let modified_satisfaction_weight = satisfaction_weight - Weight::from_wu(6); + + // assert_ne!(satisfaction_weight, modified_satisfaction_weight); + + // // add foreign UTXO with same outpoint but satisfaction weight x - 6wu + // assert!(builder + // .add_foreign_utxo( + // utxo1.outpoint, + // psbt::Input { + // non_witness_utxo: Some(tx1.as_ref().clone()), + // ..Default::default() + // }, + // modified_satisfaction_weight, + // ) + // .is_ok()); + + // assert_eq!(builder.params.utxos.len(), 1); + // assert_eq!( + // builder.params.utxos[0].satisfaction_weight, + // modified_satisfaction_weight + // ); + // } + + // // Test that local outputs have precedence over utxos added via `add_foreign_utxo` + // #[test] + // fn test_local_utxos_have_precedence_over_foreign_utxos() { + // use crate::test_utils::get_funded_wallet_wpkh; + // let (mut wallet, _) = get_funded_wallet_wpkh(); + + // let utxo = wallet.list_unspent().next().unwrap(); + // let outpoint = utxo.outpoint; + + // // case 1: add foreign after local, expect local is kept + // let mut builder = wallet.build_tx(); + // builder.add_utxo(outpoint).unwrap(); + + // assert_eq!(builder.params.utxos[0].utxo.outpoint(), outpoint); + + // builder + // .add_foreign_utxo( + // outpoint, + // psbt::Input { + // witness_utxo: Some(utxo.txout.clone()), + // ..Default::default() + // }, + // Weight::from_wu(107), + // ) + // .unwrap(); + + // assert_eq!(builder.params.utxos.len(), 1); + // assert!(matches!( + // &builder.params.utxos[0].utxo, + // Utxo::Local(output) if output.outpoint == outpoint, + // )); + + // // case 2: add local after foreign, expect foreign is removed + // builder.params = TxParams::default(); + + // builder + // .add_foreign_utxo( + // outpoint, + // psbt::Input { + // witness_utxo: Some(utxo.txout), + // ..Default::default() + // }, + // Weight::from_wu(107), + // ) + // .unwrap(); + + // assert_eq!(builder.params.utxos[0].utxo.outpoint(), outpoint); + + // builder.add_utxo(outpoint).unwrap(); + + // assert_eq!(builder.params.utxos.len(), 1); + // assert!( + // matches!(&builder.params.utxos[0].utxo, Utxo::Local(output) if output.outpoint == + // outpoint) ); + // } } diff --git a/src/wallet/utils.rs b/src/wallet/utils.rs index bfabfe84..aa22daf8 100644 --- a/src/wallet/utils.rs +++ b/src/wallet/utils.rs @@ -8,7 +8,7 @@ // , at your option. // You may not use this file except in accordance with one or both of these // licenses. - +#![allow(unused)] use alloc::sync::Arc; use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::{ diff --git a/tests/add_foreign_utxo.rs b/tests/add_foreign_utxo.rs index 1dd0a8c9..87d53ef3 100644 --- a/tests/add_foreign_utxo.rs +++ b/tests/add_foreign_utxo.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use std::str::FromStr; use bdk_wallet::psbt::PsbtUtils; @@ -9,284 +11,284 @@ use bitcoin::{psbt, Address, Amount}; mod common; -#[test] -fn test_add_foreign_utxo() { - let (mut wallet1, _) = get_funded_wallet_wpkh(); - let (wallet2, _) = - get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let utxo = wallet2.list_unspent().next().expect("must take!"); - let foreign_utxo_satisfaction = wallet2 - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - let psbt_input = psbt::Input { - witness_utxo: Some(utxo.txout.clone()), - ..Default::default() - }; - - let mut builder = wallet1.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) - .only_witness_utxo() - .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) - .unwrap(); - let mut psbt = builder.finish().unwrap(); - wallet1.insert_txout(utxo.outpoint, utxo.txout); - let fee = check_fee!(wallet1, psbt); - let (sent, received) = - wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - - assert_eq!( - (sent - received), - Amount::from_sat(10_000) + fee, - "we should have only net spent ~10_000" - ); - - assert!( - psbt.unsigned_tx - .input - .iter() - .any(|input| input.previous_output == utxo.outpoint), - "foreign_utxo should be in there" - ); - - let finished = wallet1 - .sign( - &mut psbt, - SignOptions { - trust_witness_utxo: true, - ..Default::default() - }, - ) - .unwrap(); - - assert!( - !finished, - "only one of the inputs should have been signed so far" - ); - - let finished = wallet2 - .sign( - &mut psbt, - SignOptions { - trust_witness_utxo: true, - ..Default::default() - }, - ) - .unwrap(); - assert!(finished, "all the inputs should have been signed now"); -} - -#[test] -fn test_calculate_fee_with_missing_foreign_utxo() { - use bdk_chain::tx_graph::CalculateFeeError; - let (mut wallet1, _) = get_funded_wallet_wpkh(); - let (wallet2, _) = - get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let utxo = wallet2.list_unspent().next().expect("must take!"); - let foreign_utxo_satisfaction = wallet2 - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - let psbt_input = psbt::Input { - witness_utxo: Some(utxo.txout.clone()), - ..Default::default() - }; - - let mut builder = wallet1.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) - .only_witness_utxo() - .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) - .unwrap(); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let res = wallet1.calculate_fee(&tx); - assert!( - matches!(res, Err(CalculateFeeError::MissingTxOut(outpoints)) if outpoints[0] == utxo.outpoint) - ); -} - -#[test] -fn test_add_foreign_utxo_invalid_psbt_input() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let outpoint = wallet.list_unspent().next().expect("must exist").outpoint; - let foreign_utxo_satisfaction = wallet - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - let mut builder = wallet.build_tx(); - let result = - builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction); - assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo))); -} - -#[test] -fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { - let (mut wallet1, txid1) = get_funded_wallet_wpkh(); - let (wallet2, txid2) = - get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); - - let utxo2 = wallet2.list_unspent().next().unwrap(); - let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); - let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone(); - - let satisfaction_weight = wallet2 - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - let mut builder = wallet1.build_tx(); - assert!( - builder - .add_foreign_utxo( - utxo2.outpoint, - psbt::Input { - non_witness_utxo: Some(tx1.as_ref().clone()), - ..Default::default() - }, - satisfaction_weight - ) - .is_err(), - "should fail when outpoint doesn't match psbt_input" - ); - assert!( - builder - .add_foreign_utxo( - utxo2.outpoint, - psbt::Input { - non_witness_utxo: Some(tx2.as_ref().clone()), - ..Default::default() - }, - satisfaction_weight - ) - .is_ok(), - "should be ok when outpoint does match psbt_input" - ); -} - -#[test] -fn test_add_foreign_utxo_only_witness_utxo() { - let (mut wallet1, _) = get_funded_wallet_wpkh(); - let (wallet2, txid2) = - get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let utxo2 = wallet2.list_unspent().next().unwrap(); - - let satisfaction_weight = wallet2 - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - { - let mut builder = wallet1.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); - - let psbt_input = psbt::Input { - witness_utxo: Some(utxo2.txout.clone()), - ..Default::default() - }; - builder - .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) - .unwrap(); - assert!( - builder.finish().is_err(), - "psbt_input with witness_utxo should fail with only witness_utxo" - ); - } - - { - let mut builder = wallet1.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); - - let psbt_input = psbt::Input { - witness_utxo: Some(utxo2.txout.clone()), - ..Default::default() - }; - builder - .only_witness_utxo() - .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) - .unwrap(); - assert!( - builder.finish().is_ok(), - "psbt_input with just witness_utxo should succeed when `only_witness_utxo` is enabled" - ); - } - - { - let mut builder = wallet1.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); - - let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx; - let psbt_input = psbt::Input { - non_witness_utxo: Some(tx2.as_ref().clone()), - ..Default::default() - }; - builder - .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) - .unwrap(); - assert!( - builder.finish().is_ok(), - "psbt_input with non_witness_utxo should succeed by default" - ); - } -} - -#[test] -fn test_taproot_foreign_utxo() { - let (mut wallet1, _) = get_funded_wallet_wpkh(); - let (wallet2, _) = get_funded_wallet_single(get_test_tr_single_sig()); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let utxo = wallet2.list_unspent().next().unwrap(); - let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap(); - let foreign_utxo_satisfaction = wallet2 - .public_descriptor(KeychainKind::External) - .max_weight_to_satisfy() - .unwrap(); - - assert!( - psbt_input.non_witness_utxo.is_none(), - "`non_witness_utxo` should never be populated for taproot" - ); - - let mut builder = wallet1.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) - .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) - .unwrap(); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - wallet1.insert_txout(utxo.outpoint, utxo.txout); - let fee = check_fee!(wallet1, psbt); - - assert_eq!( - sent - received, - Amount::from_sat(10_000) + fee, - "we should have only net spent ~10_000" - ); - - assert!( - psbt.unsigned_tx - .input - .iter() - .any(|input| input.previous_output == utxo.outpoint), - "foreign_utxo should be in there" - ); -} +// #[test] +// fn test_add_foreign_utxo() { +// let (mut wallet1, _) = get_funded_wallet_wpkh(); +// let (wallet2, _) = +// get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let utxo = wallet2.list_unspent().next().expect("must take!"); +// let foreign_utxo_satisfaction = wallet2 +// .public_descriptor(KeychainKind::External) +// .max_weight_to_satisfy() +// .unwrap(); + +// let psbt_input = psbt::Input { +// witness_utxo: Some(utxo.txout.clone()), +// ..Default::default() +// }; + +// let mut builder = wallet1.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) +// .only_witness_utxo() +// .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) +// .unwrap(); +// let mut psbt = builder.finish().unwrap(); +// wallet1.insert_txout(utxo.outpoint, utxo.txout); +// let fee = check_fee!(wallet1, psbt); +// let (sent, received) = +// wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); + +// assert_eq!( +// (sent - received), +// Amount::from_sat(10_000) + fee, +// "we should have only net spent ~10_000" +// ); + +// assert!( +// psbt.unsigned_tx +// .input +// .iter() +// .any(|input| input.previous_output == utxo.outpoint), +// "foreign_utxo should be in there" +// ); + +// let finished = wallet1 +// .sign( +// &mut psbt, +// SignOptions { +// trust_witness_utxo: true, +// ..Default::default() +// }, +// ) +// .unwrap(); + +// assert!( +// !finished, +// "only one of the inputs should have been signed so far" +// ); + +// let finished = wallet2 +// .sign( +// &mut psbt, +// SignOptions { +// trust_witness_utxo: true, +// ..Default::default() +// }, +// ) +// .unwrap(); +// assert!(finished, "all the inputs should have been signed now"); +// } + +// #[test] +// fn test_calculate_fee_with_missing_foreign_utxo() { +// use bdk_chain::tx_graph::CalculateFeeError; +// let (mut wallet1, _) = get_funded_wallet_wpkh(); +// let (wallet2, _) = +// get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let utxo = wallet2.list_unspent().next().expect("must take!"); +// let foreign_utxo_satisfaction = wallet2 +// .public_descriptor(KeychainKind::External) +// .max_weight_to_satisfy() +// .unwrap(); + +// let psbt_input = psbt::Input { +// witness_utxo: Some(utxo.txout.clone()), +// ..Default::default() +// }; + +// let mut builder = wallet1.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) +// .only_witness_utxo() +// .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) +// .unwrap(); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let res = wallet1.calculate_fee(&tx); +// assert!( +// matches!(res, Err(CalculateFeeError::MissingTxOut(outpoints)) if outpoints[0] == +// utxo.outpoint) ); +// } + +// #[test] +// fn test_add_foreign_utxo_invalid_psbt_input() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let outpoint = wallet.list_unspent().next().expect("must exist").outpoint; +// let foreign_utxo_satisfaction = wallet +// .public_descriptor(KeychainKind::External) +// .max_weight_to_satisfy() +// .unwrap(); + +// let mut builder = wallet.build_tx(); +// let result = +// builder.add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction); +// assert!(matches!(result, Err(AddForeignUtxoError::MissingUtxo))); +// } + +// #[test] +// fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { +// let (mut wallet1, txid1) = get_funded_wallet_wpkh(); +// let (wallet2, txid2) = +// get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); + +// let utxo2 = wallet2.list_unspent().next().unwrap(); +// let tx1 = wallet1.get_tx(txid1).unwrap().tx_node.tx.clone(); +// let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx.clone(); + +// let satisfaction_weight = wallet2 +// .public_descriptor(KeychainKind::External) +// .max_weight_to_satisfy() +// .unwrap(); + +// let mut builder = wallet1.build_tx(); +// assert!( +// builder +// .add_foreign_utxo( +// utxo2.outpoint, +// psbt::Input { +// non_witness_utxo: Some(tx1.as_ref().clone()), +// ..Default::default() +// }, +// satisfaction_weight +// ) +// .is_err(), +// "should fail when outpoint doesn't match psbt_input" +// ); +// assert!( +// builder +// .add_foreign_utxo( +// utxo2.outpoint, +// psbt::Input { +// non_witness_utxo: Some(tx2.as_ref().clone()), +// ..Default::default() +// }, +// satisfaction_weight +// ) +// .is_ok(), +// "should be ok when outpoint does match psbt_input" +// ); +// } + +// #[test] +// fn test_add_foreign_utxo_only_witness_utxo() { +// let (mut wallet1, _) = get_funded_wallet_wpkh(); +// let (wallet2, txid2) = +// get_funded_wallet_single("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let utxo2 = wallet2.list_unspent().next().unwrap(); + +// let satisfaction_weight = wallet2 +// .public_descriptor(KeychainKind::External) +// .max_weight_to_satisfy() +// .unwrap(); + +// { +// let mut builder = wallet1.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); + +// let psbt_input = psbt::Input { +// witness_utxo: Some(utxo2.txout.clone()), +// ..Default::default() +// }; +// builder +// .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) +// .unwrap(); +// assert!( +// builder.finish().is_err(), +// "psbt_input with witness_utxo should fail with only witness_utxo" +// ); +// } + +// { +// let mut builder = wallet1.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); + +// let psbt_input = psbt::Input { +// witness_utxo: Some(utxo2.txout.clone()), +// ..Default::default() +// }; +// builder +// .only_witness_utxo() +// .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) +// .unwrap(); +// assert!( +// builder.finish().is_ok(), +// "psbt_input with just witness_utxo should succeed when `only_witness_utxo` is +// enabled" ); +// } + +// { +// let mut builder = wallet1.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)); + +// let tx2 = wallet2.get_tx(txid2).unwrap().tx_node.tx; +// let psbt_input = psbt::Input { +// non_witness_utxo: Some(tx2.as_ref().clone()), +// ..Default::default() +// }; +// builder +// .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) +// .unwrap(); +// assert!( +// builder.finish().is_ok(), +// "psbt_input with non_witness_utxo should succeed by default" +// ); +// } +// } + +// #[test] +// fn test_taproot_foreign_utxo() { +// let (mut wallet1, _) = get_funded_wallet_wpkh(); +// let (wallet2, _) = get_funded_wallet_single(get_test_tr_single_sig()); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let utxo = wallet2.list_unspent().next().unwrap(); +// let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap(); +// let foreign_utxo_satisfaction = wallet2 +// .public_descriptor(KeychainKind::External) +// .max_weight_to_satisfy() +// .unwrap(); + +// assert!( +// psbt_input.non_witness_utxo.is_none(), +// "`non_witness_utxo` should never be populated for taproot" +// ); + +// let mut builder = wallet1.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(60_000)) +// .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) +// .unwrap(); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet1.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// wallet1.insert_txout(utxo.outpoint, utxo.txout); +// let fee = check_fee!(wallet1, psbt); + +// assert_eq!( +// sent - received, +// Amount::from_sat(10_000) + fee, +// "we should have only net spent ~10_000" +// ); + +// assert!( +// psbt.unsigned_tx +// .input +// .iter() +// .any(|input| input.previous_output == utxo.outpoint), +// "foreign_utxo should be in there" +// ); +// } diff --git a/tests/build_fee_bump.rs b/tests/build_fee_bump.rs index aa3613b1..ec14503f 100644 --- a/tests/build_fee_bump.rs +++ b/tests/build_fee_bump.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use std::str::FromStr; use assert_matches::assert_matches; @@ -15,932 +16,932 @@ use bitcoin::{ mod common; use common::*; -#[test] -#[should_panic(expected = "IrreplaceableTransaction")] -fn test_bump_fee_irreplaceable_tx() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - builder.set_exact_sequence(Sequence(0xFFFFFFFE)); - let psbt = builder.finish().unwrap(); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - wallet.build_fee_bump(txid).unwrap().finish().unwrap(); -} - -#[test] -#[should_panic(expected = "TransactionConfirmed")] -fn test_bump_fee_confirmed_tx() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - - insert_tx(&mut wallet, tx); - - let anchor = ConfirmationBlockTime { - block_id: wallet.latest_checkpoint().get(42).unwrap().block_id(), - confirmation_time: 42_000, - }; - insert_anchor(&mut wallet, txid, anchor); - - wallet.build_fee_bump(txid).unwrap().finish().unwrap(); -} - -#[test] -fn test_bump_fee_low_fee_rate() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - - let psbt = builder.finish().unwrap(); - let feerate = psbt.fee_rate().unwrap(); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::BROADCAST_MIN); - let res = builder.finish(); - assert_matches!( - res, - Err(CreateTxError::FeeRateTooLow { .. }), - "expected FeeRateTooLow error" - ); - - let required = feerate.to_sat_per_kwu() + 250; // +1 sat/vb - let sat_vb = required as f64 / 250.0; - let expect = format!("Fee rate too low: required {sat_vb} sat/vb"); - assert_eq!(res.unwrap_err().to_string(), expect); -} - -#[test] -#[should_panic(expected = "FeeTooLow")] -fn test_bump_fee_low_abs() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(Amount::from_sat(10)); - builder.finish().unwrap(); -} - -#[test] -#[should_panic(expected = "FeeTooLow")] -fn test_bump_fee_zero_abs() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(Amount::ZERO); - builder.finish().unwrap(); -} - -#[test] -fn test_bump_fee_reduce_change() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - let original_sent_received = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let original_fee = check_fee!(wallet, psbt); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(feerate); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0); - assert_eq!(received + fee, original_sent_received.1 + original_fee); - assert!(fee > original_fee); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(25_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_fee_rate!(psbt, fee, feerate, @add_signature); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(Amount::from_sat(200)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0); - assert_eq!(received + fee, original_sent_received.1 + original_fee); - assert!(fee > original_fee, "{fee} > {original_fee}"); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(25_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_eq!(fee, Amount::from_sat(200)); -} - -#[test] -fn test_bump_fee_reduce_single_recipient() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - let tx = psbt.clone().extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - let original_fee = check_fee!(wallet, psbt); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .fee_rate(feerate) - // remove original tx drain_to address and amount - .set_recipients(Vec::new()) - // set back original drain_to address - .drain_to(addr.script_pubkey()) - // drain wallet output amount will be re-calculated with new fee rate - .drain_wallet(); - let psbt = builder.finish().unwrap(); - let (sent, _received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0); - assert!(fee > original_fee); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value + fee, sent); - - assert_fee_rate!(psbt, fee, feerate, @add_signature); -} - -#[test] -fn test_bump_fee_absolute_reduce_single_recipient() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - let original_fee = check_fee!(wallet, psbt); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .fee_absolute(Amount::from_sat(300)) - // remove original tx drain_to address and amount - .set_recipients(Vec::new()) - // set back original drain_to address - .drain_to(addr.script_pubkey()) - // drain wallet output amount will be re-calculated with new fee rate - .drain_wallet(); - let psbt = builder.finish().unwrap(); - let tx = &psbt.unsigned_tx; - let (sent, _received) = wallet.sent_and_received(tx); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0); - assert!(fee > original_fee); - - assert_eq!(tx.output.len(), 1); - assert_eq!(tx.output[0].value + fee, sent); - - assert_eq!(fee, Amount::from_sat(300)); -} - -#[test] -fn test_bump_fee_drain_wallet() { - 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, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - }; - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx.clone()); - let anchor = ConfirmationBlockTime { - block_id: wallet.latest_checkpoint().block_id(), - confirmation_time: 42_000, - }; - insert_anchor(&mut wallet, txid, anchor); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .add_utxo(OutPoint { - txid: tx.compute_txid(), - vout: 0, - }) - .unwrap() - .manually_selected_only(); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); - - // for the new feerate, it should be enough to reduce the output, but since we specify - // `drain_wallet` we expect to spend everything - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .drain_wallet() - .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); - let psbt = builder.finish().unwrap(); - let (sent, _received) = - wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx")); - - assert_eq!(sent, Amount::from_sat(75_000)); -} - -#[test] -#[should_panic(expected = "InsufficientFunds")] -fn test_bump_fee_remove_output_manually_selected_only() { - 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 - // existing output. In other words, bump_fee + manually_selected_only is always an error - // unless there is a change output. - let init_tx = Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - }; - - let position: ChainPosition = - wallet.transactions().last().unwrap().chain_position; - insert_tx(&mut wallet, init_tx.clone()); - match position { - ChainPosition::Confirmed { anchor, .. } => { - insert_anchor(&mut wallet, init_tx.compute_txid(), anchor) - } - other => panic!("all wallet txs must be confirmed: {other:?}"), - } - - let outpoint = OutPoint { - txid: init_tx.compute_txid(), - vout: 0, - }; - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .add_utxo(outpoint) - .unwrap() - .manually_selected_only(); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .manually_selected_only() - .fee_rate(FeeRate::from_sat_per_vb_unchecked(255)); - builder.finish().unwrap(); -} - -#[test] -fn test_bump_fee_add_input() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let init_tx = Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - }; - let txid = init_tx.compute_txid(); - let pos: ChainPosition = - wallet.transactions().last().unwrap().chain_position; - insert_tx(&mut wallet, init_tx); - match pos { - ChainPosition::Confirmed { anchor, .. } => insert_anchor(&mut wallet, txid, anchor), - other => panic!("all wallet txs must be confirmed: {other:?}"), - } - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_details = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - assert_eq!(sent, original_details.0 + Amount::from_sat(25_000)); - assert_eq!(fee + received, Amount::from_sat(30_000)); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(45_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(50), @add_signature); -} - -#[test] -fn test_bump_fee_absolute_add_input() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(Amount::from_sat(6_000)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0 + Amount::from_sat(25_000)); - assert_eq!(fee + received, Amount::from_sat(30_000)); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(45_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_eq!(fee, Amount::from_sat(6_000)); -} - -#[test] -fn test_bump_fee_no_change_add_input_and_change() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); - - // initially make a tx without change by using `drain_to` - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .add_utxo(op) - .unwrap() - .manually_selected_only(); - let psbt = builder.finish().unwrap(); - let original_sent_received = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let original_fee = check_fee!(wallet, psbt); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - // Now bump the fees, the wallet should add an extra input and a change output, and leave - // the original output untouched. - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - let original_send_all_amount = original_sent_received.0 - original_fee; - assert_eq!(sent, original_sent_received.0 + Amount::from_sat(50_000)); - assert_eq!( - received, - Amount::from_sat(75_000) - original_send_all_amount - fee - ); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - original_send_all_amount - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(75_000) - original_send_all_amount - fee - ); - - assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(50), @add_signature); -} - -#[test] -fn test_bump_fee_force_add_input() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let incoming_op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - let psbt = builder.finish().unwrap(); - let mut tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - for txin in &mut tx.input { - txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) - txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) - } - insert_tx(&mut wallet, tx.clone()); - // the new fee_rate is low enough that just reducing the change would be fine, but we force - // the addition of an extra input with `add_utxo()` - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .add_utxo(incoming_op) - .unwrap() - .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0 + Amount::from_sat(25_000)); - assert_eq!(fee + received, Amount::from_sat(30_000)); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(45_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(5), @add_signature); -} - -#[test] -fn test_bump_fee_absolute_force_add_input() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let incoming_op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - let psbt = builder.finish().unwrap(); - let mut tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - // skip saving the new utxos, we know they can't be used anyways - for txin in &mut tx.input { - txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) - txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) - } - insert_tx(&mut wallet, tx.clone()); - - // the new fee_rate is low enough that just reducing the change would be fine, but we force - // the addition of an extra input with `add_utxo()` - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .add_utxo(incoming_op) - .unwrap() - .fee_absolute(Amount::from_sat(250)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent_received.0 + Amount::from_sat(25_000)); - assert_eq!(fee + received, Amount::from_sat(30_000)); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(45_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_eq!(fee, Amount::from_sat(250)); -} - -#[test] -#[should_panic(expected = "InsufficientFunds")] -fn test_bump_fee_unconfirmed_inputs_only() { - // We try to bump the fee, but: - // - We can't reduce the change, as we have no change - // - All our UTXOs are unconfirmed - // 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_wpkh(); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.drain_wallet().drain_to(addr.script_pubkey()); - let psbt = builder.finish().unwrap(); - // Now we receive one transaction with 0 confirmations. We won't be able to use that for - // fee bumping, as it's still unconfirmed! - receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); - let mut tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - for txin in &mut tx.input { - txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) - txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) - } - insert_tx(&mut wallet, tx); - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25)); - builder.finish().unwrap(); -} - -#[test] -fn test_bump_fee_unconfirmed_input() { - // We create a tx draining the wallet and spending one confirmed - // and one unconfirmed UTXO. We check that we can fee bump normally - // (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_wpkh(); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - // We receive a tx with 0 confirmations, which will be used as an input - // in the drain tx. - receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); - let mut builder = wallet.build_tx(); - builder.drain_wallet().drain_to(addr.script_pubkey()); - let psbt = builder.finish().unwrap(); - let mut tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - for txin in &mut tx.input { - txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) - txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) - } - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .fee_rate(FeeRate::from_sat_per_vb_unchecked(15)) - // remove original tx drain_to address and amount - .set_recipients(Vec::new()) - // set back original drain_to address - .drain_to(addr.script_pubkey()) - // drain wallet output amount will be re-calculated with new fee rate - .drain_wallet(); - builder.finish().unwrap(); -} - -#[test] -#[should_panic(expected = "FeeTooLow")] -fn test_legacy_bump_fee_zero_abs() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - let tx = psbt.extract_tx().expect("failed to extract tx"); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(Amount::ZERO); - builder.finish().unwrap(); -} - -#[test] -fn test_legacy_bump_fee_drain_wallet() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - // receive an extra tx so that our wallet has two utxos. - let tx = Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - value: Amount::from_sat(25_000), - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - }], - }; - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx.clone()); - let anchor = ConfirmationBlockTime { - block_id: wallet.latest_checkpoint().block_id(), - confirmation_time: 42_000, - }; - insert_anchor(&mut wallet, txid, anchor); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .add_utxo(OutPoint { - txid: tx.compute_txid(), - vout: 0, - }) - .unwrap() - .manually_selected_only(); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_sent_received = wallet.sent_and_received(&tx); - - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); - - // for the new feerate, it should be enough to reduce the output, but since we specify - // `drain_wallet` we expect to spend everything - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder - .drain_wallet() - .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); - let psbt = builder.finish().unwrap(); - let (sent, _received) = - wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx")); - - assert_eq!(sent, Amount::from_sat(75_000)); -} - -#[test] -fn test_legacy_bump_fee_add_input() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let init_tx = Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - }; - let txid = init_tx.compute_txid(); - let pos: ChainPosition = - wallet.transactions().last().unwrap().chain_position; - insert_tx(&mut wallet, init_tx); - match pos { - ChainPosition::Confirmed { anchor, .. } => insert_anchor(&mut wallet, txid, anchor), - other => panic!("all wallet txs must be confirmed: {other:?}"), - } - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let original_details = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - assert_eq!(sent, original_details.0 + Amount::from_sat(25_000)); - assert_eq!(fee + received, Amount::from_sat(30_000)); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(45_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_fee_rate_legacy!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(50), @add_signature); -} - -#[test] -fn test_legacy_bump_fee_absolute_add_input() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - let psbt = builder.finish().unwrap(); - let tx = psbt.extract_tx().expect("failed to extract tx"); - let (original_sent, _original_received) = wallet.sent_and_received(&tx); - let txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - - let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_absolute(Amount::from_sat(6_000)); - let psbt = builder.finish().unwrap(); - let (sent, received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - let fee = check_fee!(wallet, psbt); - - assert_eq!(sent, original_sent + Amount::from_sat(25_000)); - assert_eq!(fee + received, Amount::from_sat(30_000)); - - let tx = &psbt.unsigned_tx; - assert_eq!(tx.input.len(), 2); - assert_eq!(tx.output.len(), 2); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey == addr.script_pubkey()) - .unwrap() - .value, - Amount::from_sat(45_000) - ); - assert_eq!( - tx.output - .iter() - .find(|txout| txout.script_pubkey != addr.script_pubkey()) - .unwrap() - .value, - received - ); - - assert_eq!(fee, Amount::from_sat(6_000)); -} +// #[test] +// #[should_panic(expected = "IrreplaceableTransaction")] +// fn test_bump_fee_irreplaceable_tx() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// builder.set_exact_sequence(Sequence(0xFFFFFFFE)); +// let psbt = builder.finish().unwrap(); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); +// wallet.build_fee_bump(txid).unwrap().finish().unwrap(); +// } + +// #[test] +// #[should_panic(expected = "TransactionConfirmed")] +// fn test_bump_fee_confirmed_tx() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); + +// insert_tx(&mut wallet, tx); + +// let anchor = ConfirmationBlockTime { +// block_id: wallet.latest_checkpoint().get(42).unwrap().block_id(), +// confirmation_time: 42_000, +// }; +// insert_anchor(&mut wallet, txid, anchor); + +// wallet.build_fee_bump(txid).unwrap().finish().unwrap(); +// } + +// #[test] +// fn test_bump_fee_low_fee_rate() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); + +// let psbt = builder.finish().unwrap(); +// let feerate = psbt.fee_rate().unwrap(); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_rate(FeeRate::BROADCAST_MIN); +// let res = builder.finish(); +// assert_matches!( +// res, +// Err(CreateTxError::FeeRateTooLow { .. }), +// "expected FeeRateTooLow error" +// ); + +// let required = feerate.to_sat_per_kwu() + 250; // +1 sat/vb +// let sat_vb = required as f64 / 250.0; +// let expect = format!("Fee rate too low: required {sat_vb} sat/vb"); +// assert_eq!(res.unwrap_err().to_string(), expect); +// } + +// #[test] +// #[should_panic(expected = "FeeTooLow")] +// fn test_bump_fee_low_abs() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_absolute(Amount::from_sat(10)); +// builder.finish().unwrap(); +// } + +// #[test] +// #[should_panic(expected = "FeeTooLow")] +// fn test_bump_fee_zero_abs() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_absolute(Amount::ZERO); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_bump_fee_reduce_change() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); +// let original_sent_received = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let original_fee = check_fee!(wallet, psbt); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_rate(feerate); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0); +// assert_eq!(received + fee, original_sent_received.1 + original_fee); +// assert!(fee > original_fee); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(25_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_fee_rate!(psbt, fee, feerate, @add_signature); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_absolute(Amount::from_sat(200)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0); +// assert_eq!(received + fee, original_sent_received.1 + original_fee); +// assert!(fee > original_fee, "{fee} > {original_fee}"); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(25_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_eq!(fee, Amount::from_sat(200)); +// } + +// #[test] +// fn test_bump_fee_reduce_single_recipient() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.clone().extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); +// let original_fee = check_fee!(wallet, psbt); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let feerate = FeeRate::from_sat_per_kwu(625); // 2.5 sat/vb +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .fee_rate(feerate) +// // remove original tx drain_to address and amount +// .set_recipients(Vec::new()) +// // set back original drain_to address +// .drain_to(addr.script_pubkey()) +// // drain wallet output amount will be re-calculated with new fee rate +// .drain_wallet(); +// let psbt = builder.finish().unwrap(); +// let (sent, _received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0); +// assert!(fee > original_fee); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.output.len(), 1); +// assert_eq!(tx.output[0].value + fee, sent); + +// assert_fee_rate!(psbt, fee, feerate, @add_signature); +// } + +// #[test] +// fn test_bump_fee_absolute_reduce_single_recipient() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); +// let original_fee = check_fee!(wallet, psbt); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .fee_absolute(Amount::from_sat(300)) +// // remove original tx drain_to address and amount +// .set_recipients(Vec::new()) +// // set back original drain_to address +// .drain_to(addr.script_pubkey()) +// // drain wallet output amount will be re-calculated with new fee rate +// .drain_wallet(); +// let psbt = builder.finish().unwrap(); +// let tx = &psbt.unsigned_tx; +// let (sent, _received) = wallet.sent_and_received(tx); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0); +// assert!(fee > original_fee); + +// assert_eq!(tx.output.len(), 1); +// assert_eq!(tx.output[0].value + fee, sent); + +// assert_eq!(fee, Amount::from_sat(300)); +// } + +// #[test] +// fn test_bump_fee_drain_wallet() { +// 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, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// }; +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx.clone()); +// let anchor = ConfirmationBlockTime { +// block_id: wallet.latest_checkpoint().block_id(), +// confirmation_time: 42_000, +// }; +// insert_anchor(&mut wallet, txid, anchor); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); + +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .add_utxo(OutPoint { +// txid: tx.compute_txid(), +// vout: 0, +// }) +// .unwrap() +// .manually_selected_only(); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); + +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); +// assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); + +// // for the new feerate, it should be enough to reduce the output, but since we specify +// // `drain_wallet` we expect to spend everything +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .drain_wallet() +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); +// let psbt = builder.finish().unwrap(); +// let (sent, _received) = +// wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx")); + +// assert_eq!(sent, Amount::from_sat(75_000)); +// } + +// #[test] +// #[should_panic(expected = "InsufficientFunds")] +// fn test_bump_fee_remove_output_manually_selected_only() { +// 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 +// // existing output. In other words, bump_fee + manually_selected_only is always an error +// // unless there is a change output. +// let init_tx = Transaction { +// version: transaction::Version::ONE, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// }; + +// let position: ChainPosition = +// wallet.transactions().last().unwrap().chain_position; +// insert_tx(&mut wallet, init_tx.clone()); +// match position { +// ChainPosition::Confirmed { anchor, .. } => { +// insert_anchor(&mut wallet, init_tx.compute_txid(), anchor) +// } +// other => panic!("all wallet txs must be confirmed: {other:?}"), +// } + +// let outpoint = OutPoint { +// txid: init_tx.compute_txid(), +// vout: 0, +// }; +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .add_utxo(outpoint) +// .unwrap() +// .manually_selected_only(); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); +// assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .manually_selected_only() +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(255)); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_bump_fee_add_input() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let init_tx = Transaction { +// version: transaction::Version::ONE, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// }; +// let txid = init_tx.compute_txid(); +// let pos: ChainPosition = +// wallet.transactions().last().unwrap().chain_position; +// insert_tx(&mut wallet, init_tx); +// match pos { +// ChainPosition::Confirmed { anchor, .. } => insert_anchor(&mut wallet, txid, anchor), +// other => panic!("all wallet txs must be confirmed: {other:?}"), +// } + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_details = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); +// assert_eq!(sent, original_details.0 + Amount::from_sat(25_000)); +// assert_eq!(fee + received, Amount::from_sat(30_000)); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(45_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(50), @add_signature); +// } + +// #[test] +// fn test_bump_fee_absolute_add_input() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_absolute(Amount::from_sat(6_000)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0 + Amount::from_sat(25_000)); +// assert_eq!(fee + received, Amount::from_sat(30_000)); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(45_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_eq!(fee, Amount::from_sat(6_000)); +// } + +// #[test] +// fn test_bump_fee_no_change_add_input_and_change() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); + +// // initially make a tx without change by using `drain_to` +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .add_utxo(op) +// .unwrap() +// .manually_selected_only(); +// let psbt = builder.finish().unwrap(); +// let original_sent_received = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let original_fee = check_fee!(wallet, psbt); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// // Now bump the fees, the wallet should add an extra input and a change output, and leave +// // the original output untouched. +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// let original_send_all_amount = original_sent_received.0 - original_fee; +// assert_eq!(sent, original_sent_received.0 + Amount::from_sat(50_000)); +// assert_eq!( +// received, +// Amount::from_sat(75_000) - original_send_all_amount - fee +// ); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// original_send_all_amount +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(75_000) - original_send_all_amount - fee +// ); + +// assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(50), @add_signature); +// } + +// #[test] +// fn test_bump_fee_force_add_input() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let incoming_op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// let psbt = builder.finish().unwrap(); +// let mut tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// for txin in &mut tx.input { +// txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) +// txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) +// } +// insert_tx(&mut wallet, tx.clone()); +// // the new fee_rate is low enough that just reducing the change would be fine, but we force +// // the addition of an extra input with `add_utxo()` +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .add_utxo(incoming_op) +// .unwrap() +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0 + Amount::from_sat(25_000)); +// assert_eq!(fee + received, Amount::from_sat(30_000)); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(45_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(5), @add_signature); +// } + +// #[test] +// fn test_bump_fee_absolute_force_add_input() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let incoming_op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// let psbt = builder.finish().unwrap(); +// let mut tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// // skip saving the new utxos, we know they can't be used anyways +// for txin in &mut tx.input { +// txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) +// txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) +// } +// insert_tx(&mut wallet, tx.clone()); + +// // the new fee_rate is low enough that just reducing the change would be fine, but we force +// // the addition of an extra input with `add_utxo()` +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .add_utxo(incoming_op) +// .unwrap() +// .fee_absolute(Amount::from_sat(250)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent_received.0 + Amount::from_sat(25_000)); +// assert_eq!(fee + received, Amount::from_sat(30_000)); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(45_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_eq!(fee, Amount::from_sat(250)); +// } + +// #[test] +// #[should_panic(expected = "InsufficientFunds")] +// fn test_bump_fee_unconfirmed_inputs_only() { +// // We try to bump the fee, but: +// // - We can't reduce the change, as we have no change +// // - All our UTXOs are unconfirmed +// // 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_wpkh(); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.drain_wallet().drain_to(addr.script_pubkey()); +// let psbt = builder.finish().unwrap(); +// // Now we receive one transaction with 0 confirmations. We won't be able to use that for +// // fee bumping, as it's still unconfirmed! +// receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); +// let mut tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// for txin in &mut tx.input { +// txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) +// txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) +// } +// insert_tx(&mut wallet, tx); +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25)); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_bump_fee_unconfirmed_input() { +// // We create a tx draining the wallet and spending one confirmed +// // and one unconfirmed UTXO. We check that we can fee bump normally +// // (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_wpkh(); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// // We receive a tx with 0 confirmations, which will be used as an input +// // in the drain tx. +// receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); +// let mut builder = wallet.build_tx(); +// builder.drain_wallet().drain_to(addr.script_pubkey()); +// let psbt = builder.finish().unwrap(); +// let mut tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// for txin in &mut tx.input { +// txin.witness.push([0x00; P2WPKH_FAKE_SIG_SIZE]); // sig (72) +// txin.witness.push([0x00; P2WPKH_FAKE_PK_SIZE]); // pk (33) +// } +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(15)) +// // remove original tx drain_to address and amount +// .set_recipients(Vec::new()) +// // set back original drain_to address +// .drain_to(addr.script_pubkey()) +// // drain wallet output amount will be re-calculated with new fee rate +// .drain_wallet(); +// builder.finish().unwrap(); +// } + +// #[test] +// #[should_panic(expected = "FeeTooLow")] +// fn test_legacy_bump_fee_zero_abs() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_absolute(Amount::ZERO); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_legacy_bump_fee_drain_wallet() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// // receive an extra tx so that our wallet has two utxos. +// let tx = Transaction { +// version: transaction::Version::ONE, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// value: Amount::from_sat(25_000), +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// }], +// }; +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx.clone()); +// let anchor = ConfirmationBlockTime { +// block_id: wallet.latest_checkpoint().block_id(), +// confirmation_time: 42_000, +// }; +// insert_anchor(&mut wallet, txid, anchor); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); + +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .add_utxo(OutPoint { +// txid: tx.compute_txid(), +// vout: 0, +// }) +// .unwrap() +// .manually_selected_only(); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_sent_received = wallet.sent_and_received(&tx); + +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); +// assert_eq!(original_sent_received.0, Amount::from_sat(25_000)); + +// // for the new feerate, it should be enough to reduce the output, but since we specify +// // `drain_wallet` we expect to spend everything +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder +// .drain_wallet() +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); +// let psbt = builder.finish().unwrap(); +// let (sent, _received) = +// wallet.sent_and_received(&psbt.extract_tx().expect("failed to extract tx")); + +// assert_eq!(sent, Amount::from_sat(75_000)); +// } + +// #[test] +// fn test_legacy_bump_fee_add_input() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let init_tx = Transaction { +// version: transaction::Version::ONE, +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// }; +// let txid = init_tx.compute_txid(); +// let pos: ChainPosition = +// wallet.transactions().last().unwrap().chain_position; +// insert_tx(&mut wallet, init_tx); +// match pos { +// ChainPosition::Confirmed { anchor, .. } => insert_anchor(&mut wallet, txid, anchor), +// other => panic!("all wallet txs must be confirmed: {other:?}"), +// } + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let original_details = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); +// assert_eq!(sent, original_details.0 + Amount::from_sat(25_000)); +// assert_eq!(fee + received, Amount::from_sat(30_000)); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(45_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_fee_rate_legacy!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(50), @add_signature); +// } + +// #[test] +// fn test_legacy_bump_fee_absolute_add_input() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// receive_output_in_latest_block(&mut wallet, Amount::from_sat(25_000)); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx().coin_selection(LargestFirstCoinSelection); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// let psbt = builder.finish().unwrap(); +// let tx = psbt.extract_tx().expect("failed to extract tx"); +// let (original_sent, _original_received) = wallet.sent_and_received(&tx); +// let txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); + +// let mut builder = wallet.build_fee_bump(txid).unwrap(); +// builder.fee_absolute(Amount::from_sat(6_000)); +// let psbt = builder.finish().unwrap(); +// let (sent, received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(sent, original_sent + Amount::from_sat(25_000)); +// assert_eq!(fee + received, Amount::from_sat(30_000)); + +// let tx = &psbt.unsigned_tx; +// assert_eq!(tx.input.len(), 2); +// assert_eq!(tx.output.len(), 2); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey == addr.script_pubkey()) +// .unwrap() +// .value, +// Amount::from_sat(45_000) +// ); +// assert_eq!( +// tx.output +// .iter() +// .find(|txout| txout.script_pubkey != addr.script_pubkey()) +// .unwrap() +// .value, +// received +// ); + +// assert_eq!(fee, Amount::from_sat(6_000)); +// } diff --git a/tests/persisted_wallet.rs b/tests/persisted_wallet.rs index 062beee7..14716c3f 100644 --- a/tests/persisted_wallet.rs +++ b/tests/persisted_wallet.rs @@ -1,465 +1,466 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::path::Path; - -use anyhow::Context; -use assert_matches::assert_matches; -use bdk_chain::DescriptorId; -use bdk_chain::{ - keychain_txout::DEFAULT_LOOKAHEAD, ChainPosition, ConfirmationBlockTime, DescriptorExt, -}; -use bdk_wallet::descriptor::IntoWalletDescriptor; -use bdk_wallet::test_utils::*; -use bdk_wallet::{ - ChangeSet, KeychainKind, LoadError, LoadMismatch, LoadWithPersistError, Wallet, WalletPersister, -}; -use bitcoin::constants::ChainHash; -use bitcoin::hashes::Hash; -use bitcoin::key::Secp256k1; -use bitcoin::{ - absolute, secp256k1, transaction, Amount, BlockHash, Network, ScriptBuf, Transaction, TxOut, -}; -use miniscript::{Descriptor, DescriptorPublicKey}; - -use bdk_wallet::persist_test_utils::{ - persist_keychains, persist_network, persist_single_keychain, persist_wallet_changeset, -}; - -mod common; -use common::*; - -const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48]; - -#[test] -fn wallet_is_persisted() -> anyhow::Result<()> { - type SpkCacheChangeSet = BTreeMap>; - - /// Check whether the spk-cache field of the changeset contains the expected spk indices. - fn check_cache_cs( - cache_cs: &SpkCacheChangeSet, - expected: impl IntoIterator)>, - msg: impl AsRef, - ) { - let secp = secp256k1::Secp256k1::new(); - let (external, internal) = get_test_tr_single_sig_xprv_and_change_desc(); - let (external_desc, _) = external - .into_wallet_descriptor(&secp, Network::Testnet) - .unwrap(); - let (internal_desc, _) = internal - .into_wallet_descriptor(&secp, Network::Testnet) - .unwrap(); - let external_did = external_desc.descriptor_id(); - let internal_did = internal_desc.descriptor_id(); - - let cache_cmp = cache_cs - .iter() - .map(|(did, spks)| { - let kind: KeychainKind; - if did == &external_did { - kind = KeychainKind::External; - } else if did == &internal_did { - kind = KeychainKind::Internal; - } else { - unreachable!(); - } - let spk_indices = spks.keys().copied().collect::>(); - (kind, spk_indices) - }) - .filter(|(_, spk_indices)| !spk_indices.is_empty()) - .collect::>>(); - - let expected_cmp = expected - .into_iter() - .map(|(kind, indices)| (kind, indices.into_iter().collect::>())) - .filter(|(_, spk_indices)| !spk_indices.is_empty()) - .collect::>>(); - - assert_eq!(cache_cmp, expected_cmp, "{}", msg.as_ref()); - } - - fn staged_cache(wallet: &Wallet) -> SpkCacheChangeSet { - wallet.staged().map_or(SpkCacheChangeSet::default(), |cs| { - cs.indexer.spk_cache.clone() - }) - } - - fn run( - filename: &str, - create_db: CreateDb, - open_db: OpenDb, - ) -> anyhow::Result<()> - where - CreateDb: Fn(&Path) -> anyhow::Result, - OpenDb: Fn(&Path) -> anyhow::Result, - Db: WalletPersister, - Db::Error: std::error::Error + Send + Sync + 'static, - { - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - - // create new wallet - let wallet_spk_index = { - let mut db = create_db(&file_path)?; - let mut wallet = Wallet::create(external_desc, internal_desc) - .network(Network::Testnet) - .use_spk_cache(true) - .create_wallet(&mut db)?; - - wallet.reveal_next_address(KeychainKind::External); - - check_cache_cs( - &staged_cache(&wallet), - [ - (KeychainKind::External, 0..DEFAULT_LOOKAHEAD + 1), - (KeychainKind::Internal, 0..DEFAULT_LOOKAHEAD), - ], - "cache cs must return initial set + the external index that was just derived", - ); - - // persist new wallet changes - assert!(wallet.persist(&mut db)?, "must write"); - wallet.spk_index().clone() - }; - - // recover wallet - { - let mut db = open_db(&file_path).context("failed to recover db")?; - let wallet = Wallet::load() - .descriptor(KeychainKind::External, Some(external_desc)) - .descriptor(KeychainKind::Internal, Some(internal_desc)) - .check_network(Network::Testnet) - .load_wallet(&mut db)? - .expect("wallet must exist"); - - assert_eq!(wallet.network(), Network::Testnet); - assert_eq!( - wallet.spk_index().keychains().collect::>(), - wallet_spk_index.keychains().collect::>() - ); - assert_eq!( - wallet.spk_index().last_revealed_indices(), - wallet_spk_index.last_revealed_indices() - ); - let secp = Secp256k1::new(); - assert_eq!( - *wallet.public_descriptor(KeychainKind::External), - external_desc - .into_wallet_descriptor(&secp, wallet.network()) - .unwrap() - .0 - ); - } - - // Test SPK cache - { - let mut db = open_db(&file_path).context("failed to recover db")?; - let mut wallet = Wallet::load() - .check_network(Network::Testnet) - .use_spk_cache(true) - .load_wallet(&mut db)? - .expect("wallet must exist"); - - assert!(wallet.staged().is_none()); - - let revealed_external_addr = wallet.reveal_next_address(KeychainKind::External); - check_cache_cs( - &staged_cache(&wallet), - [( - KeychainKind::External, - [revealed_external_addr.index + DEFAULT_LOOKAHEAD], - )], - "must only persist the revealed+LOOKAHEAD indexed external spk", - ); - - // Clear the stage - let _ = wallet.take_staged(); - - let revealed_internal_addr = wallet.reveal_next_address(KeychainKind::Internal); - check_cache_cs( - &staged_cache(&wallet), - [( - KeychainKind::Internal, - [revealed_internal_addr.index + DEFAULT_LOOKAHEAD], - )], - "must only persist the revealed+LOOKAHEAD indexed internal spk", - ); - } - - // SPK cache requires load params - { - let mut db = open_db(&file_path).context("failed to recover db")?; - let mut wallet = Wallet::load() - .check_network(Network::Testnet) - // .use_spk_cache(false) - .load_wallet(&mut db)? - .expect("wallet must exist"); - - let internal_did = wallet - .public_descriptor(KeychainKind::Internal) - .descriptor_id(); - - assert!(wallet.staged().is_none()); - - let _addr = wallet.reveal_next_address(KeychainKind::Internal); - let cs = wallet.staged().expect("we should have staged a changeset"); - assert_eq!(cs.indexer.last_revealed.get(&internal_did), Some(&0)); - assert!( - cs.indexer.spk_cache.is_empty(), - "we didn't set `use_spk_cache`" - ); - } - - Ok(()) - } - - run( - "store.db", - |path| Ok(bdk_file_store::Store::create(DB_MAGIC, path)?), - |path| Ok(bdk_file_store::Store::load(DB_MAGIC, path)?.0), - )?; - run::( - "store.sqlite", - |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), - |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), - )?; - - Ok(()) -} - -#[test] -fn wallet_load_checks() -> anyhow::Result<()> { - fn run( - filename: &str, - create_db: CreateDb, - open_db: OpenDb, - ) -> anyhow::Result<()> - where - CreateDb: Fn(&Path) -> anyhow::Result, - OpenDb: Fn(&Path) -> anyhow::Result, - Db: WalletPersister + std::fmt::Debug, - Db::Error: std::error::Error + Send + Sync + 'static, - { - let temp_dir = tempfile::tempdir().expect("must create tempdir"); - let file_path = temp_dir.path().join(filename); - let network = Network::Testnet; - let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - - // create new wallet - let _ = Wallet::create(external_desc, internal_desc) - .network(network) - .create_wallet(&mut create_db(&file_path)?)?; - - assert_matches!( - Wallet::load() - .check_network(Network::Regtest) - .load_wallet(&mut open_db(&file_path)?), - Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( - LoadMismatch::Network { - loaded: Network::Testnet, - expected: Network::Regtest, - } - ))), - "unexpected network check result: Regtest (check) is not Testnet (loaded)", - ); - let mainnet_hash = BlockHash::from_byte_array(ChainHash::BITCOIN.to_bytes()); - assert_matches!( - Wallet::load().check_genesis_hash(mainnet_hash).load_wallet(&mut open_db(&file_path)?), - Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Genesis { .. }))), - "unexpected genesis hash check result: mainnet hash (check) is not testnet hash (loaded)", - ); - assert_matches!( - Wallet::load() - .descriptor(KeychainKind::External, Some(internal_desc)) - .load_wallet(&mut open_db(&file_path)?), - Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( - LoadMismatch::Descriptor { .. } - ))), - "unexpected descriptors check result", - ); - assert_matches!( - Wallet::load() - .descriptor(KeychainKind::External, Option::<&str>::None) - .load_wallet(&mut open_db(&file_path)?), - Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( - LoadMismatch::Descriptor { .. } - ))), - "unexpected descriptors check result", - ); - // check setting keymaps - let (_, external_keymap) = parse_descriptor(external_desc); - let (_, internal_keymap) = parse_descriptor(internal_desc); - let wallet = Wallet::load() - .keymap(KeychainKind::External, external_keymap) - .keymap(KeychainKind::Internal, internal_keymap) - .load_wallet(&mut open_db(&file_path)?) - .expect("db should not fail") - .expect("wallet was persisted"); - for keychain in [KeychainKind::External, KeychainKind::Internal] { - let keymap = wallet.get_signers(keychain).as_key_map(wallet.secp_ctx()); - assert!( - !keymap.is_empty(), - "load should populate keymap for keychain {keychain:?}" - ); - } - Ok(()) - } - - run( - "store.db", - |path| Ok(bdk_file_store::Store::::create(DB_MAGIC, path)?), - |path| Ok(bdk_file_store::Store::::load(DB_MAGIC, path)?.0), - )?; - run( - "store.sqlite", - |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), - |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), - )?; - - Ok(()) -} - -#[test] -fn wallet_should_persist_anchors_and_recover() { - use bdk_chain::rusqlite; - let temp_dir = tempfile::tempdir().unwrap(); - let db_path = temp_dir.path().join("wallet.db"); - let mut db = rusqlite::Connection::open(db_path).unwrap(); - - let desc = get_test_tr_single_sig_xprv(); - let mut wallet = Wallet::create_single(desc) - .network(Network::Testnet) - .create_wallet(&mut db) - .unwrap(); - let small_output_tx = Transaction { - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - version: transaction::Version::non_standard(0), - lock_time: absolute::LockTime::ZERO, - }; - let txid = small_output_tx.compute_txid(); - insert_tx(&mut wallet, small_output_tx); - let expected_anchor = ConfirmationBlockTime { - block_id: wallet.latest_checkpoint().block_id(), - confirmation_time: 200, - }; - insert_anchor(&mut wallet, txid, expected_anchor); - assert!(wallet.persist(&mut db).unwrap()); - - // should recover persisted wallet - let secp = wallet.secp_ctx(); - let (_, keymap) = >::parse_descriptor(secp, desc).unwrap(); - assert!(!keymap.is_empty()); - let wallet = Wallet::load() - .descriptor(KeychainKind::External, Some(desc)) - .extract_keys() - .load_wallet(&mut db) - .unwrap() - .expect("must have loaded changeset"); - // stored anchor should be retrieved in the same condition it was persisted - if let ChainPosition::Confirmed { - anchor: obtained_anchor, - .. - } = wallet - .get_tx(txid) - .expect("should retrieve stored tx") - .chain_position - { - assert_eq!(obtained_anchor, expected_anchor) - } else { - panic!("Should have got ChainPosition::Confirmed)"); - } -} - -#[test] -fn single_descriptor_wallet_persist_and_recover() { - use bdk_chain::miniscript::Descriptor; - use bdk_chain::miniscript::DescriptorPublicKey; - use bdk_chain::rusqlite; - let temp_dir = tempfile::tempdir().unwrap(); - let db_path = temp_dir.path().join("wallet.db"); - let mut db = rusqlite::Connection::open(db_path).unwrap(); - - let desc = get_test_tr_single_sig_xprv(); - let mut wallet = Wallet::create_single(desc) - .network(Network::Testnet) - .create_wallet(&mut db) - .unwrap(); - let _ = wallet.reveal_addresses_to(KeychainKind::External, 2); - assert!(wallet.persist(&mut db).unwrap()); - - // should recover persisted wallet - let secp = wallet.secp_ctx(); - let (_, keymap) = >::parse_descriptor(secp, desc).unwrap(); - assert!(!keymap.is_empty()); - let wallet = Wallet::load() - .descriptor(KeychainKind::External, Some(desc)) - .extract_keys() - .load_wallet(&mut db) - .unwrap() - .expect("must have loaded changeset"); - assert_eq!(wallet.derivation_index(KeychainKind::External), Some(2)); - // should have private key - assert_eq!( - wallet.get_signers(KeychainKind::External).as_key_map(secp), - keymap, - ); - - // should error on wrong internal params - let desc = get_test_wpkh(); - let (exp_desc, _) = >::parse_descriptor(secp, desc).unwrap(); - let err = Wallet::load() - .descriptor(KeychainKind::Internal, Some(desc)) - .extract_keys() - .load_wallet(&mut db); - assert_matches!( - err, - Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Descriptor { keychain, loaded, expected }))) - if keychain == KeychainKind::Internal && loaded.is_none() && expected == Some(exp_desc), - "single descriptor wallet should refuse change descriptor param" - ); -} - -#[test] -fn wallet_changeset_is_persisted() { - persist_wallet_changeset("store.db", |path| { - Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) - }); - persist_wallet_changeset::("store.sqlite", |path| { - Ok(bdk_chain::rusqlite::Connection::open(path)?) - }); -} - -#[test] -fn keychains_are_persisted() { - persist_keychains("store.db", |path| { - Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) - }); - persist_keychains::("store.sqlite", |path| { - Ok(bdk_chain::rusqlite::Connection::open(path)?) - }); -} - -#[test] -fn single_keychain_is_persisted() { - persist_single_keychain("store.db", |path| { - Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) - }); - persist_single_keychain::("store.sqlite", |path| { - Ok(bdk_chain::rusqlite::Connection::open(path)?) - }); -} - -#[test] -fn network_is_persisted() { - persist_network("store.db", |path| { - Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) - }); - persist_network::("store.sqlite", |path| { - Ok(bdk_chain::rusqlite::Connection::open(path)?) - }); -} +// use std::collections::{BTreeMap, BTreeSet}; +// use std::path::Path; + +// use anyhow::Context; +// use assert_matches::assert_matches; +// use bdk_chain::DescriptorId; +// use bdk_chain::{ +// keychain_txout::DEFAULT_LOOKAHEAD, ChainPosition, ConfirmationBlockTime, DescriptorExt, +// }; +// use bdk_wallet::descriptor::IntoWalletDescriptor; +// use bdk_wallet::test_utils::*; +// use bdk_wallet::{ +// ChangeSet, KeychainKind, LoadError, LoadMismatch, LoadWithPersistError, Wallet, +// WalletPersister, }; +// use bitcoin::constants::ChainHash; +// use bitcoin::hashes::Hash; +// use bitcoin::key::Secp256k1; +// use bitcoin::{ +// absolute, secp256k1, transaction, Amount, BlockHash, Network, ScriptBuf, Transaction, TxOut, +// }; +// use miniscript::{Descriptor, DescriptorPublicKey}; + +// use bdk_wallet::persist_test_utils::{ +// persist_keychains, persist_network, persist_single_keychain, persist_wallet_changeset, +// }; + +// mod common; +// use common::*; + +// const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48]; + +// #[test] +// fn wallet_is_persisted() -> anyhow::Result<()> { +// type SpkCacheChangeSet = BTreeMap>; + +// /// Check whether the spk-cache field of the changeset contains the expected spk indices. +// fn check_cache_cs( +// cache_cs: &SpkCacheChangeSet, +// expected: impl IntoIterator)>, +// msg: impl AsRef, +// ) { +// let secp = secp256k1::Secp256k1::new(); +// let (external, internal) = get_test_tr_single_sig_xprv_and_change_desc(); +// let (external_desc, _) = external +// .into_wallet_descriptor(&secp, Network::Testnet) +// .unwrap(); +// let (internal_desc, _) = internal +// .into_wallet_descriptor(&secp, Network::Testnet) +// .unwrap(); +// let external_did = external_desc.descriptor_id(); +// let internal_did = internal_desc.descriptor_id(); + +// let cache_cmp = cache_cs +// .iter() +// .map(|(did, spks)| { +// let kind: KeychainKind; +// if did == &external_did { +// kind = KeychainKind::External; +// } else if did == &internal_did { +// kind = KeychainKind::Internal; +// } else { +// unreachable!(); +// } +// let spk_indices = spks.keys().copied().collect::>(); +// (kind, spk_indices) +// }) +// .filter(|(_, spk_indices)| !spk_indices.is_empty()) +// .collect::>>(); + +// let expected_cmp = expected +// .into_iter() +// .map(|(kind, indices)| (kind, indices.into_iter().collect::>())) +// .filter(|(_, spk_indices)| !spk_indices.is_empty()) +// .collect::>>(); + +// assert_eq!(cache_cmp, expected_cmp, "{}", msg.as_ref()); +// } + +// fn staged_cache(wallet: &Wallet) -> SpkCacheChangeSet { +// wallet.staged().map_or(SpkCacheChangeSet::default(), |cs| { +// cs.indexer.spk_cache.clone() +// }) +// } + +// fn run( +// filename: &str, +// create_db: CreateDb, +// open_db: OpenDb, +// ) -> anyhow::Result<()> +// where +// CreateDb: Fn(&Path) -> anyhow::Result, +// OpenDb: Fn(&Path) -> anyhow::Result, +// Db: WalletPersister, +// Db::Error: std::error::Error + Send + Sync + 'static, +// { +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); +// let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); + +// // create new wallet +// let wallet_spk_index = { +// let mut db = create_db(&file_path)?; +// let mut wallet = Wallet::create(external_desc, internal_desc) +// .network(Network::Testnet) +// .use_spk_cache(true) +// .create_wallet(&mut db)?; + +// wallet.reveal_next_address(KeychainKind::External); + +// check_cache_cs( +// &staged_cache(&wallet), +// [ +// (KeychainKind::External, 0..DEFAULT_LOOKAHEAD + 1), +// (KeychainKind::Internal, 0..DEFAULT_LOOKAHEAD), +// ], +// "cache cs must return initial set + the external index that was just derived", +// ); + +// // persist new wallet changes +// assert!(wallet.persist(&mut db)?, "must write"); +// wallet.spk_index().clone() +// }; + +// // recover wallet +// { +// let mut db = open_db(&file_path).context("failed to recover db")?; +// let wallet = Wallet::load() +// .descriptor(KeychainKind::External, Some(external_desc)) +// .descriptor(KeychainKind::Internal, Some(internal_desc)) +// .check_network(Network::Testnet) +// .load_wallet(&mut db)? +// .expect("wallet must exist"); + +// assert_eq!(wallet.network(), Network::Testnet); +// assert_eq!( +// wallet.spk_index().keychains().collect::>(), +// wallet_spk_index.keychains().collect::>() +// ); +// assert_eq!( +// wallet.spk_index().last_revealed_indices(), +// wallet_spk_index.last_revealed_indices() +// ); +// let secp = Secp256k1::new(); +// assert_eq!( +// *wallet.public_descriptor(KeychainKind::External), +// external_desc +// .into_wallet_descriptor(&secp, wallet.network()) +// .unwrap() +// .0 +// ); +// } + +// // Test SPK cache +// { +// let mut db = open_db(&file_path).context("failed to recover db")?; +// let mut wallet = Wallet::load() +// .check_network(Network::Testnet) +// .use_spk_cache(true) +// .load_wallet(&mut db)? +// .expect("wallet must exist"); + +// assert!(wallet.staged().is_none()); + +// let revealed_external_addr = wallet.reveal_next_address(KeychainKind::External); +// check_cache_cs( +// &staged_cache(&wallet), +// [( +// KeychainKind::External, +// [revealed_external_addr.index + DEFAULT_LOOKAHEAD], +// )], +// "must only persist the revealed+LOOKAHEAD indexed external spk", +// ); + +// // Clear the stage +// let _ = wallet.take_staged(); + +// let revealed_internal_addr = wallet.reveal_next_address(KeychainKind::Internal); +// check_cache_cs( +// &staged_cache(&wallet), +// [( +// KeychainKind::Internal, +// [revealed_internal_addr.index + DEFAULT_LOOKAHEAD], +// )], +// "must only persist the revealed+LOOKAHEAD indexed internal spk", +// ); +// } + +// // SPK cache requires load params +// { +// let mut db = open_db(&file_path).context("failed to recover db")?; +// let mut wallet = Wallet::load() +// .check_network(Network::Testnet) +// // .use_spk_cache(false) +// .load_wallet(&mut db)? +// .expect("wallet must exist"); + +// let internal_did = wallet +// .public_descriptor(KeychainKind::Internal) +// .descriptor_id(); + +// assert!(wallet.staged().is_none()); + +// let _addr = wallet.reveal_next_address(KeychainKind::Internal); +// let cs = wallet.staged().expect("we should have staged a changeset"); +// assert_eq!(cs.indexer.last_revealed.get(&internal_did), Some(&0)); +// assert!( +// cs.indexer.spk_cache.is_empty(), +// "we didn't set `use_spk_cache`" +// ); +// } + +// Ok(()) +// } + +// run( +// "store.db", +// |path| Ok(bdk_file_store::Store::create(DB_MAGIC, path)?), +// |path| Ok(bdk_file_store::Store::load(DB_MAGIC, path)?.0), +// )?; +// run::( +// "store.sqlite", +// |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), +// |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), +// )?; + +// Ok(()) +// } + +// #[test] +// fn wallet_load_checks() -> anyhow::Result<()> { +// fn run( +// filename: &str, +// create_db: CreateDb, +// open_db: OpenDb, +// ) -> anyhow::Result<()> +// where +// CreateDb: Fn(&Path) -> anyhow::Result, +// OpenDb: Fn(&Path) -> anyhow::Result, +// Db: WalletPersister + std::fmt::Debug, +// Db::Error: std::error::Error + Send + Sync + 'static, +// { +// let temp_dir = tempfile::tempdir().expect("must create tempdir"); +// let file_path = temp_dir.path().join(filename); +// let network = Network::Testnet; +// let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); + +// // create new wallet +// let _ = Wallet::create(external_desc, internal_desc) +// .network(network) +// .create_wallet(&mut create_db(&file_path)?)?; + +// assert_matches!( +// Wallet::load() +// .check_network(Network::Regtest) +// .load_wallet(&mut open_db(&file_path)?), +// Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( +// LoadMismatch::Network { +// loaded: Network::Testnet, +// expected: Network::Regtest, +// } +// ))), +// "unexpected network check result: Regtest (check) is not Testnet (loaded)", +// ); +// let mainnet_hash = BlockHash::from_byte_array(ChainHash::BITCOIN.to_bytes()); +// assert_matches!( +// Wallet::load().check_genesis_hash(mainnet_hash).load_wallet(&mut +// open_db(&file_path)?), +// Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Genesis { .. }))), +// "unexpected genesis hash check result: mainnet hash (check) is not testnet hash +// (loaded)", ); +// assert_matches!( +// Wallet::load() +// .descriptor(KeychainKind::External, Some(internal_desc)) +// .load_wallet(&mut open_db(&file_path)?), +// Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( +// LoadMismatch::Descriptor { .. } +// ))), +// "unexpected descriptors check result", +// ); +// assert_matches!( +// Wallet::load() +// .descriptor(KeychainKind::External, Option::<&str>::None) +// .load_wallet(&mut open_db(&file_path)?), +// Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch( +// LoadMismatch::Descriptor { .. } +// ))), +// "unexpected descriptors check result", +// ); +// // check setting keymaps +// let (_, external_keymap) = parse_descriptor(external_desc); +// let (_, internal_keymap) = parse_descriptor(internal_desc); +// let wallet = Wallet::load() +// .keymap(KeychainKind::External, external_keymap) +// .keymap(KeychainKind::Internal, internal_keymap) +// .load_wallet(&mut open_db(&file_path)?) +// .expect("db should not fail") +// .expect("wallet was persisted"); +// for keychain in [KeychainKind::External, KeychainKind::Internal] { +// let keymap = wallet.get_signers(keychain).as_key_map(wallet.secp_ctx()); +// assert!( +// !keymap.is_empty(), +// "load should populate keymap for keychain {keychain:?}" +// ); +// } +// Ok(()) +// } + +// run( +// "store.db", +// |path| Ok(bdk_file_store::Store::::create(DB_MAGIC, path)?), +// |path| Ok(bdk_file_store::Store::::load(DB_MAGIC, path)?.0), +// )?; +// run( +// "store.sqlite", +// |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), +// |path| Ok(bdk_chain::rusqlite::Connection::open(path)?), +// )?; + +// Ok(()) +// } + +// #[test] +// fn wallet_should_persist_anchors_and_recover() { +// use bdk_chain::rusqlite; +// let temp_dir = tempfile::tempdir().unwrap(); +// let db_path = temp_dir.path().join("wallet.db"); +// let mut db = rusqlite::Connection::open(db_path).unwrap(); + +// let desc = get_test_tr_single_sig_xprv(); +// let mut wallet = Wallet::create_single(desc) +// .network(Network::Testnet) +// .create_wallet(&mut db) +// .unwrap(); +// let small_output_tx = Transaction { +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// version: transaction::Version::non_standard(0), +// lock_time: absolute::LockTime::ZERO, +// }; +// let txid = small_output_tx.compute_txid(); +// insert_tx(&mut wallet, small_output_tx); +// let expected_anchor = ConfirmationBlockTime { +// block_id: wallet.latest_checkpoint().block_id(), +// confirmation_time: 200, +// }; +// insert_anchor(&mut wallet, txid, expected_anchor); +// assert!(wallet.persist(&mut db).unwrap()); + +// // should recover persisted wallet +// let secp = wallet.secp_ctx(); +// let (_, keymap) = >::parse_descriptor(secp, desc).unwrap(); +// assert!(!keymap.is_empty()); +// let wallet = Wallet::load() +// .descriptor(KeychainKind::External, Some(desc)) +// .extract_keys() +// .load_wallet(&mut db) +// .unwrap() +// .expect("must have loaded changeset"); +// // stored anchor should be retrieved in the same condition it was persisted +// if let ChainPosition::Confirmed { +// anchor: obtained_anchor, +// .. +// } = wallet +// .get_tx(txid) +// .expect("should retrieve stored tx") +// .chain_position +// { +// assert_eq!(obtained_anchor, expected_anchor) +// } else { +// panic!("Should have got ChainPosition::Confirmed)"); +// } +// } + +// #[test] +// fn single_descriptor_wallet_persist_and_recover() { +// use bdk_chain::miniscript::Descriptor; +// use bdk_chain::miniscript::DescriptorPublicKey; +// use bdk_chain::rusqlite; +// let temp_dir = tempfile::tempdir().unwrap(); +// let db_path = temp_dir.path().join("wallet.db"); +// let mut db = rusqlite::Connection::open(db_path).unwrap(); + +// let desc = get_test_tr_single_sig_xprv(); +// let mut wallet = Wallet::create_single(desc) +// .network(Network::Testnet) +// .create_wallet(&mut db) +// .unwrap(); +// let _ = wallet.reveal_addresses_to(KeychainKind::External, 2); +// assert!(wallet.persist(&mut db).unwrap()); + +// // should recover persisted wallet +// let secp = wallet.secp_ctx(); +// let (_, keymap) = >::parse_descriptor(secp, desc).unwrap(); +// assert!(!keymap.is_empty()); +// let wallet = Wallet::load() +// .descriptor(KeychainKind::External, Some(desc)) +// .extract_keys() +// .load_wallet(&mut db) +// .unwrap() +// .expect("must have loaded changeset"); +// assert_eq!(wallet.derivation_index(KeychainKind::External), Some(2)); +// // should have private key +// assert_eq!( +// wallet.get_signers(KeychainKind::External).as_key_map(secp), +// keymap, +// ); + +// // should error on wrong internal params +// let desc = get_test_wpkh(); +// let (exp_desc, _) = >::parse_descriptor(secp, desc).unwrap(); +// let err = Wallet::load() +// .descriptor(KeychainKind::Internal, Some(desc)) +// .extract_keys() +// .load_wallet(&mut db); +// assert_matches!( +// err, +// Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Descriptor { +// keychain, loaded, expected }))) if keychain == KeychainKind::Internal && loaded.is_none() +// && expected == Some(exp_desc), "single descriptor wallet should refuse change descriptor +// param" ); +// } + +// #[test] +// fn wallet_changeset_is_persisted() { +// persist_wallet_changeset("store.db", |path| { +// Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) +// }); +// persist_wallet_changeset::("store.sqlite", |path| { +// Ok(bdk_chain::rusqlite::Connection::open(path)?) +// }); +// } + +// #[test] +// fn keychains_are_persisted() { +// persist_keychains("store.db", |path| { +// Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) +// }); +// persist_keychains::("store.sqlite", |path| { +// Ok(bdk_chain::rusqlite::Connection::open(path)?) +// }); +// } + +// #[test] +// fn single_keychain_is_persisted() { +// persist_single_keychain("store.db", |path| { +// Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) +// }); +// persist_single_keychain::("store.sqlite", |path| { +// Ok(bdk_chain::rusqlite::Connection::open(path)?) +// }); +// } + +// #[test] +// fn network_is_persisted() { +// persist_network("store.db", |path| { +// Ok(bdk_file_store::Store::create(DB_MAGIC, path)?) +// }); +// persist_network::("store.sqlite", |path| { +// Ok(bdk_chain::rusqlite::Connection::open(path)?) +// }); +// } diff --git a/tests/psbt.rs b/tests/psbt.rs index 08c4acc9..f8fe2778 100644 --- a/tests/psbt.rs +++ b/tests/psbt.rs @@ -1,223 +1,238 @@ -use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, TxIn}; -use bdk_wallet::test_utils::*; -use bdk_wallet::{psbt, KeychainKind, SignOptions}; -use core::str::FromStr; - -// from bip 174 -const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA"; - -#[test] -#[should_panic(expected = "InputIndexOutOfRange")] -fn test_psbt_malformed_psbt_input_legacy() { - let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); - let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); - let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); - let mut psbt = builder.finish().unwrap(); - psbt.inputs.push(psbt_bip.inputs[0].clone()); - let options = SignOptions { - trust_witness_utxo: true, - ..Default::default() - }; - let _ = wallet.sign(&mut psbt, options).unwrap(); -} - -#[test] -#[should_panic(expected = "InputIndexOutOfRange")] -fn test_psbt_malformed_psbt_input_segwit() { - let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); - let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); - let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); - let mut psbt = builder.finish().unwrap(); - psbt.inputs.push(psbt_bip.inputs[1].clone()); - let options = SignOptions { - trust_witness_utxo: true, - ..Default::default() - }; - let _ = wallet.sign(&mut psbt, options).unwrap(); -} - -#[test] -#[should_panic(expected = "InputIndexOutOfRange")] -fn test_psbt_malformed_tx_input() { - let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); - let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); - let mut psbt = builder.finish().unwrap(); - psbt.unsigned_tx.input.push(TxIn::default()); - let options = SignOptions { - trust_witness_utxo: true, - ..Default::default() - }; - let _ = wallet.sign(&mut psbt, options).unwrap(); -} - -#[test] -fn test_psbt_sign_with_finalized() { - let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); - let (mut wallet, _) = get_funded_wallet_wpkh(); - let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); - let mut psbt = builder.finish().unwrap(); - - // add a finalized input - psbt.inputs.push(psbt_bip.inputs[0].clone()); - psbt.unsigned_tx - .input - .push(psbt_bip.unsigned_tx.input[0].clone()); - - let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); -} - -#[test] -fn test_psbt_fee_rate_with_witness_utxo() { - use psbt::PsbtUtils; - - let expected_fee_rate = FeeRate::from_sat_per_kwu(310); - - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(expected_fee_rate); - let mut psbt = builder.finish().unwrap(); - let fee_amount = psbt.fee_amount(); - assert!(fee_amount.is_some()); - - let unfinalized_fee_rate = psbt.fee_rate().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let finalized_fee_rate = psbt.fee_rate().unwrap(); - assert!(finalized_fee_rate >= expected_fee_rate); - assert!(finalized_fee_rate < unfinalized_fee_rate); -} - -#[test] -fn test_psbt_fee_rate_with_nonwitness_utxo() { - use psbt::PsbtUtils; - - let expected_fee_rate = FeeRate::from_sat_per_kwu(310); - - let (mut wallet, _) = get_funded_wallet_single("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(expected_fee_rate); - let mut psbt = builder.finish().unwrap(); - let fee_amount = psbt.fee_amount(); - assert!(fee_amount.is_some()); - let unfinalized_fee_rate = psbt.fee_rate().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let finalized_fee_rate = psbt.fee_rate().unwrap(); - assert!(finalized_fee_rate >= expected_fee_rate); - assert!(finalized_fee_rate < unfinalized_fee_rate); -} - -#[test] -fn test_psbt_fee_rate_with_missing_txout() { - use psbt::PsbtUtils; - - let expected_fee_rate = FeeRate::from_sat_per_kwu(310); - - let (mut wpkh_wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wpkh_wallet.peek_address(KeychainKind::External, 0); - let mut builder = wpkh_wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(expected_fee_rate); - let mut wpkh_psbt = builder.finish().unwrap(); - - wpkh_psbt.inputs[0].witness_utxo = None; - wpkh_psbt.inputs[0].non_witness_utxo = None; - assert!(wpkh_psbt.fee_amount().is_none()); - assert!(wpkh_psbt.fee_rate().is_none()); - - let desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/0)"; - let change_desc = "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/1)"; - let (mut pkh_wallet, _) = get_funded_wallet(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(); - builder.fee_rate(expected_fee_rate); - let mut pkh_psbt = builder.finish().unwrap(); - - pkh_psbt.inputs[0].non_witness_utxo = None; - assert!(pkh_psbt.fee_amount().is_none()); - assert!(pkh_psbt.fee_rate().is_none()); -} - -#[test] -fn test_psbt_multiple_internalkey_signers() { - use bdk_wallet::signer::{SignerContext, SignerOrdering, SignerWrapper}; - use bdk_wallet::KeychainKind; - use bitcoin::key::TapTweak; - use bitcoin::secp256k1::{schnorr, Keypair, Message, Secp256k1, XOnlyPublicKey}; - use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType}; - use bitcoin::{PrivateKey, TxOut}; - use std::sync::Arc; - - let secp = Secp256k1::new(); - let wif = "cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG"; - let desc = format!("tr({wif})"); - let prv = PrivateKey::from_wif(wif).unwrap(); - let keypair = Keypair::from_secret_key(&secp, &prv.inner); - - let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; - let (mut wallet, _) = get_funded_wallet(&desc, change_desc); - let to_spend = wallet.balance().total(); - let send_to = wallet.peek_address(KeychainKind::External, 0); - let mut builder = wallet.build_tx(); - builder.drain_to(send_to.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - let unsigned_tx = psbt.unsigned_tx.clone(); - - // Adds a signer for the wrong internal key, bdk should not use this key to sign - wallet.add_signer( - KeychainKind::External, - // A signerordering lower than 100, bdk will use this signer first - SignerOrdering(0), - Arc::new(SignerWrapper::new( - PrivateKey::from_wif("5J5PZqvCe1uThJ3FZeUUFLCh2FuK9pZhtEK4MzhNmugqTmxCdwE").unwrap(), - SignerContext::Tap { - is_internal_key: true, - }, - )), - ); - let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); - assert!(finalized); - - // To verify, we need the signature, message, and pubkey - let witness = psbt.inputs[0].final_script_witness.as_ref().unwrap(); - assert!(!witness.is_empty()); - let signature = schnorr::Signature::from_slice(witness.iter().next().unwrap()).unwrap(); - - // the prevout we're spending - let prevouts = &[TxOut { - script_pubkey: send_to.script_pubkey(), - value: to_spend, - }]; - let prevouts = Prevouts::All(prevouts); - let input_index = 0; - let mut sighash_cache = SighashCache::new(unsigned_tx); - let sighash = sighash_cache - .taproot_key_spend_signature_hash(input_index, &prevouts, TapSighashType::Default) - .unwrap(); - let message = Message::from(sighash); - - // add tweak. this was taken from `signer::sign_psbt_schnorr` - let keypair = keypair.tap_tweak(&secp, None).to_keypair(); - let (xonlykey, _parity) = XOnlyPublicKey::from_keypair(&keypair); - - // Must verify if we used the correct key to sign - let verify_res = secp.verify_schnorr(&signature, &message, &xonlykey); - assert!(verify_res.is_ok(), "The wrong internal key was used"); -} +// use bdk_wallet::bitcoin::{Amount, FeeRate, Psbt, TxIn}; +// use bdk_wallet::test_utils::*; +// use bdk_wallet::{psbt, KeychainKind, SignOptions}; +// use core::str::FromStr; + +// // from bip 174 +// const PSBT_STR: &str = +// "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+//// +// qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7/// +// 8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/ +// p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/ +// REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/ +// HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA" +// ; + +// #[test] +// #[should_panic(expected = "InputIndexOutOfRange")] +// fn test_psbt_malformed_psbt_input_legacy() { +// let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); +// let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); +// let send_to = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); +// let mut psbt = builder.finish().unwrap(); +// psbt.inputs.push(psbt_bip.inputs[0].clone()); +// let options = SignOptions { +// trust_witness_utxo: true, +// ..Default::default() +// }; +// let _ = wallet.sign(&mut psbt, options).unwrap(); +// } + +// #[test] +// #[should_panic(expected = "InputIndexOutOfRange")] +// fn test_psbt_malformed_psbt_input_segwit() { +// let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); +// let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); +// let send_to = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); +// let mut psbt = builder.finish().unwrap(); +// psbt.inputs.push(psbt_bip.inputs[1].clone()); +// let options = SignOptions { +// trust_witness_utxo: true, +// ..Default::default() +// }; +// let _ = wallet.sign(&mut psbt, options).unwrap(); +// } + +// #[test] +// #[should_panic(expected = "InputIndexOutOfRange")] +// fn test_psbt_malformed_tx_input() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_wpkh()); +// let send_to = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); +// let mut psbt = builder.finish().unwrap(); +// psbt.unsigned_tx.input.push(TxIn::default()); +// let options = SignOptions { +// trust_witness_utxo: true, +// ..Default::default() +// }; +// let _ = wallet.sign(&mut psbt, options).unwrap(); +// } + +// #[test] +// fn test_psbt_sign_with_finalized() { +// let psbt_bip = Psbt::from_str(PSBT_STR).unwrap(); +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let send_to = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(send_to.script_pubkey(), Amount::from_sat(10_000)); +// let mut psbt = builder.finish().unwrap(); + +// // add a finalized input +// psbt.inputs.push(psbt_bip.inputs[0].clone()); +// psbt.unsigned_tx +// .input +// .push(psbt_bip.unsigned_tx.input[0].clone()); + +// let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); +// } + +// #[test] +// fn test_psbt_fee_rate_with_witness_utxo() { +// use psbt::PsbtUtils; + +// let expected_fee_rate = FeeRate::from_sat_per_kwu(310); + +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// builder.fee_rate(expected_fee_rate); +// let mut psbt = builder.finish().unwrap(); +// let fee_amount = psbt.fee_amount(); +// assert!(fee_amount.is_some()); + +// let unfinalized_fee_rate = psbt.fee_rate().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let finalized_fee_rate = psbt.fee_rate().unwrap(); +// assert!(finalized_fee_rate >= expected_fee_rate); +// assert!(finalized_fee_rate < unfinalized_fee_rate); +// } + +// #[test] +// fn test_psbt_fee_rate_with_nonwitness_utxo() { +// use psbt::PsbtUtils; + +// let expected_fee_rate = FeeRate::from_sat_per_kwu(310); + +// let (mut wallet, _) = +// get_funded_wallet_single(" +// pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// builder.fee_rate(expected_fee_rate); +// let mut psbt = builder.finish().unwrap(); +// let fee_amount = psbt.fee_amount(); +// assert!(fee_amount.is_some()); +// let unfinalized_fee_rate = psbt.fee_rate().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let finalized_fee_rate = psbt.fee_rate().unwrap(); +// assert!(finalized_fee_rate >= expected_fee_rate); +// assert!(finalized_fee_rate < unfinalized_fee_rate); +// } + +// #[test] +// fn test_psbt_fee_rate_with_missing_txout() { +// use psbt::PsbtUtils; + +// let expected_fee_rate = FeeRate::from_sat_per_kwu(310); + +// let (mut wpkh_wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wpkh_wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wpkh_wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// builder.fee_rate(expected_fee_rate); +// let mut wpkh_psbt = builder.finish().unwrap(); + +// wpkh_psbt.inputs[0].witness_utxo = None; +// wpkh_psbt.inputs[0].non_witness_utxo = None; +// assert!(wpkh_psbt.fee_amount().is_none()); +// assert!(wpkh_psbt.fee_rate().is_none()); + +// let desc = +// "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// 0)"; let change_desc = +// "pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// 1)"; let (mut pkh_wallet, _) = get_funded_wallet(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(); +// builder.fee_rate(expected_fee_rate); +// let mut pkh_psbt = builder.finish().unwrap(); + +// pkh_psbt.inputs[0].non_witness_utxo = None; +// assert!(pkh_psbt.fee_amount().is_none()); +// assert!(pkh_psbt.fee_rate().is_none()); +// } + +// #[test] +// fn test_psbt_multiple_internalkey_signers() { +// use bdk_wallet::signer::{SignerContext, SignerOrdering, SignerWrapper}; +// use bdk_wallet::KeychainKind; +// use bitcoin::key::TapTweak; +// use bitcoin::secp256k1::{schnorr, Keypair, Message, Secp256k1, XOnlyPublicKey}; +// use bitcoin::sighash::{Prevouts, SighashCache, TapSighashType}; +// use bitcoin::{PrivateKey, TxOut}; +// use std::sync::Arc; + +// let secp = Secp256k1::new(); +// let wif = "cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG"; +// let desc = format!("tr({wif})"); +// let prv = PrivateKey::from_wif(wif).unwrap(); +// let keypair = Keypair::from_secret_key(&secp, &prv.inner); + +// let change_desc = "tr(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"; +// let (mut wallet, _) = get_funded_wallet(&desc, change_desc); +// let to_spend = wallet.balance().total(); +// let send_to = wallet.peek_address(KeychainKind::External, 0); +// let mut builder = wallet.build_tx(); +// builder.drain_to(send_to.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); +// let unsigned_tx = psbt.unsigned_tx.clone(); + +// // Adds a signer for the wrong internal key, bdk should not use this key to sign +// wallet.add_signer( +// KeychainKind::External, +// // A signerordering lower than 100, bdk will use this signer first +// SignerOrdering(0), +// Arc::new(SignerWrapper::new( +// PrivateKey::from_wif("5J5PZqvCe1uThJ3FZeUUFLCh2FuK9pZhtEK4MzhNmugqTmxCdwE").unwrap(), +// SignerContext::Tap { +// is_internal_key: true, +// }, +// )), +// ); +// let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); +// assert!(finalized); + +// // To verify, we need the signature, message, and pubkey +// let witness = psbt.inputs[0].final_script_witness.as_ref().unwrap(); +// assert!(!witness.is_empty()); +// let signature = schnorr::Signature::from_slice(witness.iter().next().unwrap()).unwrap(); + +// // the prevout we're spending +// let prevouts = &[TxOut { +// script_pubkey: send_to.script_pubkey(), +// value: to_spend, +// }]; +// let prevouts = Prevouts::All(prevouts); +// let input_index = 0; +// let mut sighash_cache = SighashCache::new(unsigned_tx); +// let sighash = sighash_cache +// .taproot_key_spend_signature_hash(input_index, &prevouts, TapSighashType::Default) +// .unwrap(); +// let message = Message::from(sighash); + +// // add tweak. this was taken from `signer::sign_psbt_schnorr` +// let keypair = keypair.tap_tweak(&secp, None).to_keypair(); +// let (xonlykey, _parity) = XOnlyPublicKey::from_keypair(&keypair); + +// // Must verify if we used the correct key to sign +// let verify_res = secp.verify_schnorr(&signature, &message, &xonlykey); +// assert!(verify_res.is_ok(), "The wrong internal key was used"); +// } diff --git a/tests/wallet.rs b/tests/wallet.rs index c779c0a4..8fe279bb 100644 --- a/tests/wallet.rs +++ b/tests/wallet.rs @@ -1,3 +1,4 @@ +#![allow(unused)] use std::str::FromStr; use std::sync::Arc; @@ -10,7 +11,14 @@ use bdk_wallet::psbt::PsbtUtils; use bdk_wallet::signer::{SignOptions, SignerError}; use bdk_wallet::test_utils::*; use bdk_wallet::KeychainKind; -use bdk_wallet::{AddressInfo, Balance, PersistedWallet, Update, Wallet, WalletTx}; +use bdk_wallet::{ + AddressInfo, + Balance, + // PersistedWallet, + Update, + Wallet, + WalletTx, +}; use bitcoin::constants::COINBASE_MATURITY; use bitcoin::hashes::Hash; use bitcoin::script::PushBytesBuf; @@ -25,150 +33,153 @@ use rand::SeedableRng; mod common; -#[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::create(desc, desc) - .network(Network::Testnet) - .create_wallet_no_persist(); - 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::create(desc, change_desc) - .network(Network::Testnet) - .create_wallet_no_persist(); - assert!( - matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)), - "expected same descriptors error, got {err:?}", - ); -} - -#[test] -fn test_descriptor_checksum() { - let (wallet, _) = get_funded_wallet_wpkh(); - let checksum = wallet.descriptor_checksum(KeychainKind::External); - assert_eq!(checksum.len(), 8); - - let raw_descriptor = wallet - .keychains() - .next() - .unwrap() - .1 - .to_string() - .split_once('#') - .unwrap() - .0 - .to_string(); - assert_eq!(calc_checksum(&raw_descriptor).unwrap(), checksum); -} - -#[test] -fn test_get_funded_wallet_balance() { - 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 sats are the transaction fee. - assert_eq!(wallet.balance().confirmed, Amount::from_sat(50_000)); -} - -#[test] -fn test_get_funded_wallet_sent_and_received() { - let (wallet, txid) = get_funded_wallet_wpkh(); - - let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet - .transactions() - .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node))) - .collect(); - tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); - - let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - let (sent, received) = wallet.sent_and_received(&tx); - - // 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 sats are the transaction fee. - assert_eq!(sent.to_sat(), 76_000); - assert_eq!(received.to_sat(), 50_000); -} - -#[test] -fn test_get_funded_wallet_tx_fees() { - 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"); - - // 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 sats are the transaction fee. - assert_eq!(tx_fee, Amount::from_sat(1000)) -} - -#[test] -fn test_get_funded_wallet_tx_fee_rate() { - let (wallet, txid) = get_funded_wallet_wpkh(); - - let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - let tx_fee_rate = wallet - .calculate_fee_rate(&tx) - .expect("transaction fee rate"); - - // 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 sats are the transaction fee. - - // tx weight = 452 wu, as vbytes = (452 + 3) / 4 = 113 - // fee_rate (sats per kwu) = fee / weight = 1000sat / 0.452kwu = 2212 - // fee_rate (sats per vbyte ceil) = fee / vsize = 1000sat / 113vb = 9 - assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2212); - assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); -} - -#[test] -fn test_legacy_get_funded_wallet_tx_fee_rate() { - let (wallet, txid) = get_funded_wallet_single(get_test_pkh()); - - let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; - let tx_fee_rate = wallet - .calculate_fee_rate(&tx) - .expect("transaction fee rate"); - - // 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 sats are the transaction fee. - - // tx weight = 464 wu, as vbytes = (464)/4 = 116 - // fee rate (sats per kwu) = fee / weight = 1000sat / 0.464kwu = 2155 - // fee rate (sats per vbyte ceil) = fee / kwu = 1000 / 116 = 8.621 - assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2155); - assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); -} - -#[test] -fn test_list_output() { - let (wallet, txid) = get_funded_wallet_wpkh(); - let txos = wallet - .list_output() - .map(|op| (op.outpoint, op)) - .collect::>(); - assert_eq!(txos.len(), 2); - for (op, txo) in txos { - if op.txid == txid { - assert_eq!(txo.txout.value.to_sat(), 50_000); - assert!(!txo.is_spent); - } else { - assert_eq!(txo.txout.value.to_sat(), 76_000); - assert!(txo.is_spent); - } - } -} +// #[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::create(desc, desc) +// .network(Network::Testnet) +// .create_wallet_no_persist(); +// 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::create(desc, change_desc) +// .network(Network::Testnet) +// .create_wallet_no_persist(); +// assert!( +// matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)), +// "expected same descriptors error, got {err:?}", +// ); +// } + +// #[test] +// fn test_descriptor_checksum() { +// let (wallet, _) = get_funded_wallet_wpkh(); +// let checksum = wallet.descriptor_checksum(KeychainKind::External); +// assert_eq!(checksum.len(), 8); + +// let raw_descriptor = wallet +// .keychains() +// .next() +// .unwrap() +// .1 +// .to_string() +// .split_once('#') +// .unwrap() +// .0 +// .to_string(); +// assert_eq!(calc_checksum(&raw_descriptor).unwrap(), checksum); +// } + +// #[test] +// fn test_get_funded_wallet_balance() { +// 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 sats are the transaction fee. +// assert_eq!(wallet.balance().confirmed, Amount::from_sat(50_000)); +// } + +// #[test] +// fn test_get_funded_wallet_sent_and_received() { +// let (wallet, txid) = get_funded_wallet_wpkh(); + +// let mut tx_amounts: Vec<(Txid, (Amount, Amount))> = wallet +// .transactions() +// .map(|ct| (ct.tx_node.txid, wallet.sent_and_received(&ct.tx_node))) +// .collect(); +// tx_amounts.sort_by(|a1, a2| a1.0.cmp(&a2.0)); + +// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; +// let (sent, received) = wallet.sent_and_received(&tx); + +// // 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 sats are the transaction fee. +// assert_eq!(sent.to_sat(), 76_000); +// assert_eq!(received.to_sat(), 50_000); +// } + +// #[test] +// fn test_get_funded_wallet_tx_fees() { +// 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"); + +// // 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 sats are the transaction fee. +// assert_eq!(tx_fee, Amount::from_sat(1000)) +// } + +// #[test] +// fn test_get_funded_wallet_tx_fee_rate() { +// let (wallet, txid) = get_funded_wallet_wpkh(); + +// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; +// let tx_fee_rate = wallet +// .calculate_fee_rate(&tx) +// .expect("transaction fee rate"); + +// // 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 sats are the transaction fee. + +// // tx weight = 452 wu, as vbytes = (452 + 3) / 4 = 113 +// // fee_rate (sats per kwu) = fee / weight = 1000sat / 0.452kwu = 2212 +// // fee_rate (sats per vbyte ceil) = fee / vsize = 1000sat / 113vb = 9 +// assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2212); +// assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); +// } + +// #[test] +// fn test_legacy_get_funded_wallet_tx_fee_rate() { +// let (wallet, txid) = get_funded_wallet_single(get_test_pkh()); + +// let tx = wallet.get_tx(txid).expect("transaction").tx_node.tx; +// let tx_fee_rate = wallet +// .calculate_fee_rate(&tx) +// .expect("transaction fee rate"); + +// // 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 sats are the transaction fee. + +// // tx weight = 464 wu, as vbytes = (464)/4 = 116 +// // fee rate (sats per kwu) = fee / weight = 1000sat / 0.464kwu = 2155 +// // fee rate (sats per vbyte ceil) = fee / kwu = 1000 / 116 = 8.621 +// assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2155); +// assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); +// } + +// #[test] +// fn test_list_output() { +// let (wallet, txid) = get_funded_wallet_wpkh(); +// let txos = wallet +// .list_output() +// .map(|op| (op.outpoint, op)) +// .collect::>(); +// assert_eq!(txos.len(), 2); +// for (op, txo) in txos { +// if op.txid == txid { +// assert_eq!(txo.txout.value.to_sat(), 50_000); +// assert!(!txo.is_spent); +// } else { +// assert_eq!(txo.txout.value.to_sat(), 76_000); +// assert!(txo.is_spent); +// } +// } +// } macro_rules! from_str { ($e:expr, $t:ty) => {{ @@ -181,2848 +192,2910 @@ macro_rules! from_str { }; } -#[test] -#[should_panic(expected = "NoRecipients")] -fn test_create_tx_empty_recipients() { - 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_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .manually_selected_only(); - builder.finish().unwrap(); -} - -#[test] -fn test_create_tx_version_0() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .version(0); - assert!(matches!(builder.finish(), Err(CreateTxError::Version0))); -} - -#[test] -fn test_create_tx_version_1_csv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .version(1); - assert!(matches!(builder.finish(), Err(CreateTxError::Version1Csv))); -} - -#[test] -fn test_create_tx_custom_version() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .version(42); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.version.0, 42); -} - -#[test] -fn test_create_tx_default_locktime_is_last_sync_height() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - // Since we never synced the wallet we don't have a last_sync_height - // we could use to try to prevent fee sniping. We default to 0. - assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 2_000); -} - -#[test] -fn test_create_tx_fee_sniping_locktime_last_sync() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - - let psbt = builder.finish().unwrap(); - - // If there's no current_height we're left with using the last sync height - assert_eq!( - psbt.unsigned_tx.lock_time.to_consensus_u32(), - wallet.latest_checkpoint().height() - ); -} - -#[test] -fn test_create_tx_default_locktime_cltv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 100_000); -} - -#[test] -fn test_create_tx_locktime_cltv_timestamp() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv_timestamp()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 1_734_230_218); - - let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); - - assert!(finalized); -} - -#[test] -fn test_create_tx_custom_locktime() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .current_height(630_001) - .nlocktime(absolute::LockTime::from_height(630_000).unwrap()); - let psbt = builder.finish().unwrap(); - - // When we explicitly specify a nlocktime - // we don't try any fee sniping prevention trick - // (we ignore the current_height) - assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000); -} - -#[test] -fn test_create_tx_custom_locktime_compatible_with_cltv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .nlocktime(absolute::LockTime::from_height(630_000).unwrap()); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000); -} - -#[test] -fn test_create_tx_custom_locktime_incompatible_with_cltv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .nlocktime(absolute::LockTime::from_height(50000).unwrap()); - assert!(matches!(builder.finish(), - Err(CreateTxError::LockTime { requested, required }) - if requested.to_consensus_u32() == 50_000 && required.to_consensus_u32() == 100_000)); -} - -#[test] -fn test_create_tx_custom_csv() { - // desc: wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6))) - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .set_exact_sequence(Sequence(42)) - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - // we allow setting a sequence higher than required - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(42)); -} - -#[test] -fn test_create_tx_no_rbf_csv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6)); -} - -#[test] -fn test_create_tx_incompatible_csv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .set_exact_sequence(Sequence(3)); - assert!(matches!(builder.finish(), - Err(CreateTxError::RbfSequenceCsv { sequence, csv }) - if sequence.to_consensus_u32() == 3 && csv.to_consensus_u32() == 6)); -} - -#[test] -fn test_create_tx_with_default_rbf_csv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - // When CSV is enabled it takes precedence over the rbf value (unless forced by the user). - // It will be set to the OP_CSV value, in this case 6 - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6)); -} - -#[test] -fn test_create_tx_no_rbf_cltv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - builder.set_exact_sequence(Sequence(0xFFFFFFFE)); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE)); -} - -#[test] -fn test_create_tx_custom_rbf_sequence() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .set_exact_sequence(Sequence(0xDEADBEEF)); - let psbt = builder.finish().unwrap(); - - 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); - 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::InsufficientFunds { .. } - )), - )); -} - -#[test] -fn test_create_tx_default_sequence() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD)); -} - -#[test] -fn test_create_tx_drain_wallet_and_drain_to() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee - ); -} - -#[test] -fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") - .unwrap() - .assume_checked(); - let drain_addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(20_000)) - .drain_to(drain_addr.script_pubkey()) - .drain_wallet(); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - let outputs = psbt.unsigned_tx.output; - - assert_eq!(outputs.len(), 2); - let main_output = outputs - .iter() - .find(|x| x.script_pubkey == addr.script_pubkey()) - .unwrap(); - let drain_output = outputs - .iter() - .find(|x| x.script_pubkey == drain_addr.script_pubkey()) - .unwrap(); - assert_eq!(main_output.value, Amount::from_sat(20_000)); - assert_eq!(drain_output.value, Amount::from_sat(30_000) - fee); -} - -#[test] -fn test_create_tx_drain_to_and_utxos() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect(); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .add_utxos(&utxos) - .unwrap(); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee - ); -} - -#[test] -#[should_panic(expected = "NoRecipients")] -fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let drain_addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(drain_addr.script_pubkey()); - builder.finish().unwrap(); -} - -#[test] -fn test_create_tx_default_fee_rate() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_fee_rate!(psbt, fee, FeeRate::BROADCAST_MIN, @add_signature); -} - -#[test] -fn test_create_tx_custom_fee_rate() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(5), @add_signature); -} - -#[test] -fn test_legacy_create_tx_custom_fee_rate() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_fee_rate_legacy!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(5), @add_signature); -} - -#[test] -fn test_create_tx_absolute_fee() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_absolute(Amount::from_sat(100)); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(fee, Amount::from_sat(100)); - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee - ); -} - -#[test] -fn test_legacy_create_tx_absolute_fee() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_absolute(Amount::from_sat(100)); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(fee, Amount::from_sat(100)); - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee - ); -} - -#[test] -fn test_create_tx_absolute_zero_fee() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_absolute(Amount::ZERO); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(fee, Amount::ZERO); - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee - ); -} - -#[test] -fn test_legacy_create_tx_absolute_zero_fee() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_absolute(Amount::ZERO); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(fee, Amount::ZERO); - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(50_000) - fee - ); -} - -#[test] -#[should_panic(expected = "InsufficientFunds")] -fn test_create_tx_absolute_high_fee() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_absolute(Amount::from_sat(60_000)); - let _ = builder.finish().unwrap(); -} - -#[test] -#[should_panic(expected = "InsufficientFunds")] -fn test_legacy_create_tx_absolute_high_fee() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_absolute(Amount::from_sat(60_000)); - let _ = builder.finish().unwrap(); -} - -#[test] -fn test_create_tx_add_change() { - use bdk_wallet::tx_builder::TxOrdering; - let seed = [0; 32]; - let mut rng: StdRng = SeedableRng::from_seed(seed); - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .ordering(TxOrdering::Shuffle); - let psbt = builder.finish_with_aux_rand(&mut rng).unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(psbt.unsigned_tx.output.len(), 2); - assert_eq!(psbt.unsigned_tx.output[0].value, Amount::from_sat(25_000)); - assert_eq!( - psbt.unsigned_tx.output[1].value, - Amount::from_sat(25_000) - fee - ); -} - -#[test] -fn test_create_tx_skip_change_dust() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800)); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(psbt.unsigned_tx.output.len(), 1); - assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 49_800); - assert_eq!(fee, Amount::from_sat(200)); -} - -#[test] -#[should_panic(expected = "InsufficientFunds")] -fn test_create_tx_drain_to_dust_amount() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - // very high fee rate, so that the only output would be below dust - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_rate(FeeRate::from_sat_per_vb_unchecked(454)); - builder.finish().unwrap(); -} - -#[test] -fn test_create_tx_ordering_respected() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - - let bip69_txin_cmp = |tx_a: &TxIn, tx_b: &TxIn| { - let project_outpoint = |t: &TxIn| (t.previous_output.txid, t.previous_output.vout); - project_outpoint(tx_a).cmp(&project_outpoint(tx_b)) - }; - - let bip69_txout_cmp = |tx_a: &TxOut, tx_b: &TxOut| { - let project_utxo = |t: &TxOut| (t.value, t.script_pubkey.clone()); - project_utxo(tx_a).cmp(&project_utxo(tx_b)) - }; - - let custom_bip69_ordering = bdk_wallet::tx_builder::TxOrdering::Custom { - input_sort: Arc::new(bip69_txin_cmp), - output_sort: Arc::new(bip69_txout_cmp), - }; - - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)) - .ordering(custom_bip69_ordering); - - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(psbt.unsigned_tx.output.len(), 3); - assert_eq!( - psbt.unsigned_tx.output[0].value, - Amount::from_sat(10_000) - fee - ); - assert_eq!(psbt.unsigned_tx.output[1].value, Amount::from_sat(10_000)); - assert_eq!(psbt.unsigned_tx.output[2].value, Amount::from_sat(30_000)); -} - -#[test] -fn test_create_tx_default_sighash() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.inputs[0].sighash_type, None); -} - -#[test] -fn test_legacy_create_tx_default_sighash() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.inputs[0].sighash_type, None); -} - -#[test] -fn test_create_tx_custom_sighash() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .sighash(EcdsaSighashType::Single.into()); - let psbt = builder.finish().unwrap(); - - assert_eq!( - psbt.inputs[0].sighash_type, - Some(EcdsaSighashType::Single.into()) - ); -} - -#[test] -fn test_legacy_create_tx_custom_sighash() { - let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .sighash(EcdsaSighashType::Single.into()); - let psbt = builder.finish().unwrap(); - - assert_eq!( - psbt.inputs[0].sighash_type, - Some(EcdsaSighashType::Single.into()) - ); -} - -#[test] -fn test_create_tx_input_hd_keypaths() { - use bitcoin::bip32::{DerivationPath, Fingerprint}; - use core::str::FromStr; - - let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1); - assert_eq!( - psbt.inputs[0].bip32_derivation.values().next().unwrap(), - &( - Fingerprint::from_str("d34db33f").unwrap(), - DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap() - ) - ); -} - -#[test] -fn test_create_tx_output_hd_keypaths() { - use bitcoin::bip32::{DerivationPath, Fingerprint}; - use core::str::FromStr; - - let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)"); - - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1); - let expected_derivation_path = format!("m/44'/0'/0'/0/{}", addr.index); - assert_eq!( - psbt.outputs[0].bip32_derivation.values().next().unwrap(), - &( - Fingerprint::from_str("d34db33f").unwrap(), - DerivationPath::from_str(&expected_derivation_path).unwrap() - ) - ); -} - -#[test] -fn test_create_tx_set_redeem_script_p2sh() { - use bitcoin::hex::FromHex; - - let (mut wallet, _) = - get_funded_wallet_single("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert_eq!( - psbt.inputs[0].redeem_script, - Some(ScriptBuf::from( - Vec::::from_hex( - "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac" - ) - .unwrap() - )) - ); - assert_eq!(psbt.inputs[0].witness_script, None); -} - -#[test] -fn test_create_tx_set_witness_script_p2wsh() { - use bitcoin::hex::FromHex; - - let (mut wallet, _) = - get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.inputs[0].redeem_script, None); - assert_eq!( - psbt.inputs[0].witness_script, - Some(ScriptBuf::from( - Vec::::from_hex( - "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac" - ) - .unwrap() - )) - ); -} - -#[test] -fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() { - let (mut wallet, _) = get_funded_wallet_single( - "sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))", - ); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - let script = ScriptBuf::from_hex( - "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac", - ) - .unwrap(); - - assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_p2wsh())); - assert_eq!(psbt.inputs[0].witness_script, Some(script)); -} - -#[test] -fn test_create_tx_non_witness_utxo() { - let (mut wallet, _) = - get_funded_wallet_single("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert!(psbt.inputs[0].non_witness_utxo.is_some()); - assert!(psbt.inputs[0].witness_utxo.is_none()); -} - -#[test] -fn test_create_tx_only_witness_utxo() { - let (mut wallet, _) = - get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .only_witness_utxo() - .drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert!(psbt.inputs[0].non_witness_utxo.is_none()); - assert!(psbt.inputs[0].witness_utxo.is_some()); -} - -#[test] -fn test_create_tx_shwpkh_has_witness_utxo() { - let (mut wallet, _) = - get_funded_wallet_single("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert!(psbt.inputs[0].witness_utxo.is_some()); -} - -#[test] -fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() { - let (mut wallet, _) = - get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert!(psbt.inputs[0].non_witness_utxo.is_some()); - assert!(psbt.inputs[0].witness_utxo.is_some()); -} - -#[test] -fn test_create_tx_add_utxo() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let small_output_tx = Transaction { - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - version: transaction::Version::non_standard(0), - lock_time: absolute::LockTime::ZERO, - }; - let txid = small_output_tx.compute_txid(); - insert_tx(&mut wallet, small_output_tx); - let anchor = ConfirmationBlockTime { - block_id: wallet.latest_checkpoint().get(2000).unwrap().block_id(), - confirmation_time: 200, - }; - insert_anchor(&mut wallet, txid, anchor); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .add_utxo(OutPoint { txid, vout: 0 }) - .unwrap(); - let psbt = builder.finish().unwrap(); - let (sent, _received) = - wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); - - assert_eq!( - psbt.unsigned_tx.input.len(), - 2, - "should add an additional input since 25_000 < 30_000" - ); - assert_eq!( - sent, - Amount::from_sat(75_000), - "total should be sum of both inputs" - ); -} - -#[test] -#[should_panic(expected = "InsufficientFunds")] -fn test_create_tx_manually_selected_insufficient() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let small_output_tx = Transaction { - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - version: transaction::Version::non_standard(0), - lock_time: absolute::LockTime::ZERO, - }; - let txid = small_output_tx.compute_txid(); - insert_tx(&mut wallet, small_output_tx.clone()); - let anchor = ConfirmationBlockTime { - block_id: wallet.latest_checkpoint().get(2000).unwrap().block_id(), - confirmation_time: 200, - }; - insert_anchor(&mut wallet, txid, anchor); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .add_utxo(OutPoint { txid, vout: 0 }) - .unwrap() - .manually_selected_only(); - builder.finish().unwrap(); -} - -#[test] -#[should_panic(expected = "SpendingPolicyRequired(External)")] -fn test_create_tx_policy_path_required() { - let (mut wallet, _) = get_funded_wallet_single(get_test_a_or_b_plus_csv()); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)); - builder.finish().unwrap(); -} - -#[test] -fn test_create_tx_policy_path_no_csv() { - let (descriptor, change_descriptor) = get_test_wpkh_and_change_desc(); - let mut wallet = Wallet::create(descriptor, change_descriptor) - .network(Network::Regtest) - .create_wallet_no_persist() - .expect("wallet"); - - let tx = Transaction { - version: transaction::Version::non_standard(0), - lock_time: absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(50_000), - }], - }; - insert_tx(&mut wallet, tx); - - let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); - let root_id = external_policy.id; - // child #0 is just the key "A" - let path = vec![(root_id, vec![0])].into_iter().collect(); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .policy_path(path, KeychainKind::External); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD)); -} - -#[test] -fn test_create_tx_policy_path_use_csv() { - let (mut wallet, _) = get_funded_wallet_single(get_test_a_or_b_plus_csv()); - - let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); - let root_id = external_policy.id; - // child #1 is or(pk(B),older(144)) - let path = vec![(root_id, vec![1])].into_iter().collect(); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .policy_path(path, KeychainKind::External); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144)); -} - -#[test] -fn test_create_tx_policy_path_ignored_subtree_with_csv() { - let (mut wallet, _) = get_funded_wallet_single("wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),or_i(and_v(v:pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(30)),and_v(v:pkh(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(90)))))"); - - let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); - let root_id = external_policy.id; - // child #0 is pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu) - let path = vec![(root_id, vec![0])].into_iter().collect(); - - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) - .policy_path(path, KeychainKind::External); - let psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD)); -} - -#[test] -fn test_create_tx_global_xpubs_with_origin() { - use bitcoin::bip32; - let (mut wallet, _) = get_funded_wallet_single("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .add_global_xpubs(); - let psbt = builder.finish().unwrap(); - - let key = bip32::Xpub::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap(); - 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.get(&key), Some(&(fingerprint, path))); -} - -#[test] -fn test_create_tx_increment_change_index() { - // Test derivation index and unused index of change keychain when creating a transaction - // Cases include wildcard and non-wildcard descriptors with and without an internal keychain - // note the test assumes that the first external address is revealed since we're using - // `receive_output` - struct TestCase { - name: &'static str, - descriptor: &'static str, - change_descriptor: Option<&'static str>, - // amount to send - to_send: Amount, - // (derivation index, next unused index) of *change keychain* - expect: (Option, u32), - } - // total wallet funds - let amount = Amount::from_sat(10_000); - let recipient = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") - .unwrap() - .assume_checked() - .script_pubkey(); - let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - [ - TestCase { - name: "two wildcard, builder error", - descriptor: desc, - change_descriptor: Some(change_desc), - to_send: amount + Amount::from_sat(1), - // should not use or derive change index - expect: (None, 0), - }, - TestCase { - name: "two wildcard, create change", - descriptor: desc, - change_descriptor: Some(change_desc), - to_send: Amount::from_sat(5_000), - // should use change index - expect: (Some(0), 1), - }, - TestCase { - name: "two wildcard, no change", - descriptor: desc, - change_descriptor: Some(change_desc), - to_send: Amount::from_sat(9_850), - // should not use change index - expect: (None, 0), - }, - TestCase { - name: "one wildcard, create change", - descriptor: desc, - change_descriptor: None, - to_send: Amount::from_sat(5_000), - // should use change index of external keychain - expect: (Some(1), 2), - }, - TestCase { - name: "one wildcard, no change", - descriptor: desc, - change_descriptor: None, - to_send: Amount::from_sat(9_850), - // should not use change index - expect: (Some(0), 1), - }, - TestCase { - name: "single key, create change", - descriptor: get_test_tr_single_sig(), - change_descriptor: None, - to_send: Amount::from_sat(5_000), - // single key only has one derivation index (0) - expect: (Some(0), 0), - }, - TestCase { - name: "single key, no change", - descriptor: get_test_tr_single_sig(), - change_descriptor: None, - to_send: Amount::from_sat(9_850), - expect: (Some(0), 0), - }, - ] - .into_iter() - .for_each(|test| { - // create wallet - let (params, change_keychain) = match test.change_descriptor { - Some(change_desc) => ( - Wallet::create(test.descriptor, change_desc), - KeychainKind::Internal, - ), - None => ( - Wallet::create_single(test.descriptor), - KeychainKind::External, - ), - }; - let mut wallet = params - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - // fund wallet - receive_output(&mut wallet, amount, ReceiveTo::Mempool(0)); - // create tx - let mut builder = wallet.build_tx(); - builder.add_recipient(recipient.clone(), test.to_send); - let res = builder.finish(); - if !test.name.contains("error") { - assert!(res.is_ok()); - } - let (exp_derivation_index, exp_next_unused) = test.expect; - assert_eq!( - wallet.derivation_index(change_keychain), - exp_derivation_index, - "derivation index test {}", - test.name, - ); - assert_eq!( - wallet.next_unused_address(change_keychain).index, - exp_next_unused, - "next unused index test {}", - test.name, - ); - }); -} - -#[test] -fn test_get_psbt_input() { - // this should grab a known good utxo and set the input - 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()); - } -} - -#[test] -#[should_panic( - expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")" -)] -fn test_create_tx_global_xpubs_origin_missing() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .add_global_xpubs(); - builder.finish().unwrap(); -} - -#[test] -fn test_create_tx_global_xpubs_master_without_origin() { - use bitcoin::bip32; - let (mut wallet, _) = get_funded_wallet_single("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) - .add_global_xpubs(); - let psbt = builder.finish().unwrap(); - - 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.get(&key), - Some(&(fingerprint, bip32::DerivationPath::default())) - ); -} - -#[test] -fn test_fee_amount_negative_drain_val() { - // While building the transaction, bdk would calculate the drain_value - // as - // current_delta - fee_amount - drain_fee - // using saturating_sub, meaning that if the result would end up negative, - // it'll remain to zero instead. - // 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_wpkh(); - let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") - .unwrap() - .assume_checked(); - let fee_rate = FeeRate::from_sat_per_kwu(500); - let incoming_op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(8859)); - - let mut builder = wallet.build_tx(); - builder - .add_recipient(send_to.script_pubkey(), Amount::from_sat(8630)) - .add_utxo(incoming_op) - .unwrap() - .fee_rate(fee_rate); - let psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - assert_eq!(psbt.inputs.len(), 1); - assert_fee_rate!(psbt, fee, fee_rate, @add_signature); -} - -#[test] -fn test_sign_single_xprv() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!(extracted.input[0].witness.len(), 2); -} - -#[test] -fn test_sign_single_xprv_with_master_fingerprint_and_path() { - let (mut wallet, _) = get_funded_wallet_single("wpkh([d34db33f/84h/1h/0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!(extracted.input[0].witness.len(), 2); -} - -#[test] -fn test_sign_single_xprv_bip44_path() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!(extracted.input[0].witness.len(), 2); -} - -#[test] -fn test_sign_single_xprv_sh_wpkh() { - let (mut wallet, _) = get_funded_wallet_single("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!(extracted.input[0].witness.len(), 2); -} - -#[test] -fn test_sign_single_wif() { - let (mut wallet, _) = - get_funded_wallet_single("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!(extracted.input[0].witness.len(), 2); -} - -#[test] -fn test_sign_single_xprv_no_hd_keypaths() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - psbt.inputs[0].bip32_derivation.clear(); - assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0); - - let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); - assert!(finalized); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!(extracted.input[0].witness.len(), 2); -} - -#[test] -fn test_include_output_redeem_witness_script() { - let desc = get_test_wpkh(); - let change_desc = "sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))"; - let (mut wallet, _) = get_funded_wallet(desc, change_desc); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)) - .include_output_redeem_witness_script(); - let psbt = builder.finish().unwrap(); - - // p2sh-p2wsh transaction should contain both witness and redeem scripts - assert!(psbt - .outputs - .iter() - .any(|output| output.redeem_script.is_some() && output.witness_script.is_some())); -} - -#[test] -fn test_signing_only_one_of_multiple_inputs() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)) - .include_output_redeem_witness_script(); - let mut psbt = builder.finish().unwrap(); - - // add another input to the psbt that is at least passable. - let dud_input = bitcoin::psbt::Input { - witness_utxo: Some(TxOut { - value: Amount::from_sat(100_000), - script_pubkey: miniscript::Descriptor::::from_str( - "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)", - ) - .unwrap() - .script_pubkey(), - }), - ..Default::default() - }; - - psbt.inputs.push(dud_input); - psbt.unsigned_tx.input.push(bitcoin::TxIn::default()); - let is_final = wallet - .sign( - &mut psbt, - SignOptions { - trust_witness_utxo: true, - ..Default::default() - }, - ) - .unwrap(); - assert!( - !is_final, - "shouldn't be final since we can't sign one of the inputs" - ); - assert!( - psbt.inputs[0].final_script_witness.is_some(), - "should finalized input it signed" - ) -} - -#[test] -fn test_try_finalize_sign_option() { - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - - for try_finalize in &[true, false] { - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet - .sign( - &mut psbt, - SignOptions { - try_finalize: *try_finalize, - ..Default::default() - }, - ) - .unwrap(); - - psbt.inputs.iter().for_each(|input| { - if *try_finalize { - assert!(finalized); - assert!(input.final_script_sig.is_none()); - assert!(input.final_script_witness.is_some()); - } else { - assert!(!finalized); - assert!(input.final_script_sig.is_none()); - assert!(input.final_script_witness.is_none()); - } - }); - } -} - -#[test] -fn test_taproot_try_finalize_sign_option() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); - - for try_finalize in &[true, false] { - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let finalized = wallet - .sign( - &mut psbt, - SignOptions { - try_finalize: *try_finalize, - ..Default::default() - }, - ) - .unwrap(); - - psbt.inputs.iter().for_each(|input| { - if *try_finalize { - assert!(finalized); - assert!(input.final_script_sig.is_none()); - assert!(input.final_script_witness.is_some()); - assert!(input.tap_key_sig.is_none()); - assert!(input.tap_script_sigs.is_empty()); - assert!(input.tap_scripts.is_empty()); - assert!(input.tap_key_origins.is_empty()); - assert!(input.tap_internal_key.is_none()); - assert!(input.tap_merkle_root.is_none()); - } else { - assert!(!finalized); - assert!(input.final_script_sig.is_none()); - assert!(input.final_script_witness.is_none()); - } - }); - psbt.outputs.iter().for_each(|output| { - if *try_finalize { - assert!(finalized); - assert!(output.tap_key_origins.is_empty()); - } else { - assert!(!finalized); - assert!(!output.tap_key_origins.is_empty()); - } - }); - } -} - -#[test] -fn test_sign_nonstandard_sighash() { - let sighash = EcdsaSighashType::NonePlusAnyoneCanPay; - - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .sighash(sighash.into()) - .drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let result = wallet.sign(&mut psbt, Default::default()); - assert!( - result.is_err(), - "Signing should have failed because the TX uses non-standard sighashes" - ); - assert_matches!( - result, - Err(SignerError::NonStandardSighash), - "Signing failed with the wrong error type" - ); - - // try again after opting-in - let result = wallet.sign( - &mut psbt, - SignOptions { - allow_all_sighashes: true, - ..Default::default() - }, - ); - assert!(result.is_ok(), "Signing should have worked"); - assert!( - result.unwrap(), - "Should finalize the input since we can produce signatures" - ); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!( - *extracted.input[0].witness.to_vec()[0].last().unwrap(), - sighash.to_u32() as u8, - "The signature should have been made with the right sighash" - ); -} - -#[test] -fn test_unused_address() { - let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; - let change_descriptor = get_test_wpkh(); - let mut wallet = Wallet::create(descriptor, change_descriptor) - .network(Network::Testnet) - .create_wallet_no_persist() - .expect("wallet"); - - // `list_unused_addresses` should be empty if we haven't revealed any - assert!(wallet - .list_unused_addresses(KeychainKind::External) - .next() - .is_none()); - - assert_eq!( - wallet - .next_unused_address(KeychainKind::External) - .to_string(), - "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" - ); - assert_eq!( - wallet - .list_unused_addresses(KeychainKind::External) - .next() - .unwrap() - .to_string(), - "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" - ); -} - -#[test] -fn test_next_unused_address() { - let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; - let change_descriptor = get_test_wpkh(); - let mut wallet = Wallet::create(descriptor, change_descriptor) - .network(Network::Testnet) - .create_wallet_no_persist() - .expect("wallet"); - assert_eq!(wallet.derivation_index(KeychainKind::External), None); - - assert_eq!( - wallet - .next_unused_address(KeychainKind::External) - .to_string(), - "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" - ); - assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0)); - // calling next_unused again gives same address - assert_eq!( - wallet - .next_unused_address(KeychainKind::External) - .to_string(), - "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" - ); - assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0)); - - // test mark used / unused - assert!(wallet.mark_used(KeychainKind::External, 0)); - let next_unused_addr = wallet.next_unused_address(KeychainKind::External); - assert_eq!(next_unused_addr.index, 1); - - assert!(wallet.unmark_used(KeychainKind::External, 0)); - let next_unused_addr = wallet.next_unused_address(KeychainKind::External); - assert_eq!(next_unused_addr.index, 0); - - // use the above address - receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); - - assert_eq!( - wallet - .next_unused_address(KeychainKind::External) - .to_string(), - "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" - ); - assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1)); - - // trying to mark index 0 unused should return false - assert!(!wallet.unmark_used(KeychainKind::External, 0)); -} - -#[test] -fn test_peek_address_at_index() { - let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; - let change_descriptor = get_test_wpkh(); - let mut wallet = Wallet::create(descriptor, change_descriptor) - .network(Network::Testnet) - .create_wallet_no_persist() - .expect("wallet"); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 1).to_string(), - "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" - ); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 0).to_string(), - "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" - ); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 2).to_string(), - "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2" - ); - - // current new address is not affected - assert_eq!( - wallet - .reveal_next_address(KeychainKind::External) - .to_string(), - "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" - ); - - assert_eq!( - wallet - .reveal_next_address(KeychainKind::External) - .to_string(), - "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" - ); -} - -#[test] -fn test_peek_address_at_index_not_derivable() { - let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)"; - let wallet = Wallet::create(descriptor, get_test_wpkh()) - .network(Network::Testnet) - .create_wallet_no_persist() - .unwrap(); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 1).to_string(), - "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" - ); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 0).to_string(), - "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" - ); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 2).to_string(), - "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" - ); -} - -#[test] -fn test_returns_index_and_address() { - let descriptor = - "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; - let mut wallet = Wallet::create(descriptor, get_test_wpkh()) - .network(Network::Testnet) - .create_wallet_no_persist() - .unwrap(); - - // new index 0 - assert_eq!( - wallet.reveal_next_address(KeychainKind::External), - AddressInfo { - index: 0, - address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a") - .unwrap() - .assume_checked(), - keychain: KeychainKind::External, - } - ); - - // new index 1 - assert_eq!( - wallet.reveal_next_address(KeychainKind::External), - AddressInfo { - index: 1, - address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7") - .unwrap() - .assume_checked(), - keychain: KeychainKind::External, - } - ); - - // peek index 25 - assert_eq!( - wallet.peek_address(KeychainKind::External, 25), - AddressInfo { - index: 25, - address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2") - .unwrap() - .assume_checked(), - keychain: KeychainKind::External, - } - ); - - // new index 2 - assert_eq!( - wallet.reveal_next_address(KeychainKind::External), - AddressInfo { - index: 2, - address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2") - .unwrap() - .assume_checked(), - keychain: KeychainKind::External, - } - ); -} - -#[test] -fn test_sending_to_bip350_bech32m_address() { - let (mut wallet, _) = get_funded_wallet_wpkh(); - let addr = Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); - builder.finish().unwrap(); -} - -#[test] -fn test_get_address() { - use bdk_wallet::descriptor::template::Bip84; - let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); - let wallet = Wallet::create( - Bip84(key, KeychainKind::External), - Bip84(key, KeychainKind::Internal), - ) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - - assert_eq!( - wallet.peek_address(KeychainKind::External, 0), - AddressInfo { - index: 0, - address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w") - .unwrap() - .assume_checked(), - keychain: KeychainKind::External, - } - ); - - assert_eq!( - wallet.peek_address(KeychainKind::Internal, 0), - AddressInfo { - index: 0, - address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79") - .unwrap() - .assume_checked(), - keychain: KeychainKind::Internal, - } - ); -} - -#[test] -fn test_reveal_addresses() { - let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - let mut wallet = Wallet::create(desc, change_desc) - .network(Network::Signet) - .create_wallet_no_persist() - .unwrap(); - let keychain = KeychainKind::External; - - let last_revealed_addr = wallet.reveal_addresses_to(keychain, 9).last().unwrap(); - assert_eq!(wallet.derivation_index(keychain), Some(9)); - - let unused_addrs = wallet.list_unused_addresses(keychain).collect::>(); - assert_eq!(unused_addrs.len(), 10); - assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr); - - // revealing to an already revealed index returns nothing - let mut already_revealed = wallet.reveal_addresses_to(keychain, 9); - assert!(already_revealed.next().is_none()); -} - -#[test] -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::create( - Bip84(key, KeychainKind::External), - Bip84(key, KeychainKind::Internal), - ) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - - let mut used_set = HashSet::new(); - - (0..3).for_each(|_| { - let external_addr = wallet.reveal_next_address(KeychainKind::External).address; - assert!(used_set.insert(external_addr)); - - let internal_addr = wallet.reveal_next_address(KeychainKind::Internal).address; - assert!(used_set.insert(internal_addr)); - }); -} - -#[test] -fn test_taproot_psbt_populate_tap_key_origins() { - let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - let (mut wallet, _) = get_funded_wallet(desc, change_desc); - let addr = wallet.reveal_next_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert_eq!( - psbt.inputs[0] - .tap_key_origins - .clone() - .into_iter() - .collect::>(), - vec![( - from_str!("0841db1dbaf949dbbda893e01a18f2cca9179cf8ea2d8e667857690502b06483"), - (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/0"))) - )], - "Wrong input tap_key_origins" - ); - assert_eq!( - psbt.outputs[0] - .tap_key_origins - .clone() - .into_iter() - .collect::>(), - vec![( - from_str!("9187c1e80002d19ddde9c5c7f5394e9a063cee8695867b58815af0562695ca21"), - (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/1"))) - )], - "Wrong output 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(), get_test_tr_single_sig()); - let addr = wallet.reveal_next_address(KeychainKind::External); - - let path = vec![("rn4nre9c".to_string(), vec![0])] - .into_iter() - .collect(); - - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .policy_path(path, KeychainKind::External); - let psbt = builder.finish().unwrap(); - - let mut input_key_origins = psbt.inputs[0] - .tap_key_origins - .clone() - .into_iter() - .collect::>(); - input_key_origins.sort(); - - assert_eq!( - input_key_origins, - vec![ - ( - from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"), - ( - vec![ - from_str!( - "858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e" - ), - from_str!( - "f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903" - ), - ], - (FromStr::from_str("ece52657").unwrap(), vec![].into()) - ) - ), - ( - from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"), - ( - vec![], - (FromStr::from_str("871fd295").unwrap(), vec![].into()) - ) - ) - ], - "Wrong input tap_key_origins" - ); - - let mut output_key_origins = psbt.outputs[0] - .tap_key_origins - .clone() - .into_iter() - .collect::>(); - output_key_origins.sort(); - - assert_eq!( - input_key_origins, output_key_origins, - "Wrong output tap_key_origins" - ); -} - -#[test] -fn test_taproot_psbt_input_tap_tree() { - use bitcoin::hex::FromHex; - use bitcoin::taproot; - - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let psbt = builder.finish().unwrap(); - - assert_eq!( - psbt.inputs[0].tap_merkle_root, - Some( - TapNodeHash::from_str( - "61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986" - ) - .unwrap() - ), - ); - assert_eq!( - psbt.inputs[0].tap_scripts.clone().into_iter().collect::>(), - vec![ - (taproot::ControlBlock::decode(&Vec::::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (ScriptBuf::from_hex("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac").unwrap(), taproot::LeafVersion::TapScript)), - (taproot::ControlBlock::decode(&Vec::::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (ScriptBuf::from_hex("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac").unwrap(), taproot::LeafVersion::TapScript)), - ], - ); - assert_eq!( - psbt.inputs[0].tap_internal_key, - Some(from_str!( - "b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55" - )) - ); - - // Since we are creating an output to the same address as the input, assert that the - // internal_key is the same - assert_eq!( - psbt.inputs[0].tap_internal_key, - psbt.outputs[0].tap_internal_key - ); - - let tap_tree: bitcoin::taproot::TapTree = serde_json::from_str(r#"[1,{"Script":["2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",192]},1,{"Script":["208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac",192]}]"#).unwrap(); - assert_eq!(psbt.outputs[0].tap_tree, Some(tap_tree)); -} - -#[test] -fn test_taproot_sign_missing_witness_utxo() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - let witness_utxo = psbt.inputs[0].witness_utxo.take(); - - let result = wallet.sign( - &mut psbt, - SignOptions { - allow_all_sighashes: true, - ..Default::default() - }, - ); - assert_matches!( - result, - Err(SignerError::MissingWitnessUtxo), - "Signing should have failed with the correct error because the witness_utxo is missing" - ); - - // restore the witness_utxo - psbt.inputs[0].witness_utxo = witness_utxo; - - let result = wallet.sign( - &mut psbt, - SignOptions { - allow_all_sighashes: true, - ..Default::default() - }, - ); - - assert_matches!( - result, - Ok(true), - "Should finalize the input since we can produce signatures" - ); -} - -#[test] -fn test_taproot_sign_using_non_witness_utxo() { - let (mut wallet, prev_txid) = get_funded_wallet_single(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder.drain_to(addr.script_pubkey()).drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - psbt.inputs[0].witness_utxo = None; - psbt.inputs[0].non_witness_utxo = - Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone()); - assert!( - psbt.inputs[0].non_witness_utxo.is_some(), - "Previous tx should be present in the database" - ); - - let result = wallet.sign(&mut psbt, Default::default()); - assert!(result.is_ok(), "Signing should have worked"); - assert!( - result.unwrap(), - "Should finalize the input since we can produce signatures" - ); -} - -fn test_spend_from_wallet(mut wallet: Wallet) { - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - - assert_eq!(psbt.unsigned_tx.version.0, 2); - assert!( - wallet.sign(&mut psbt, Default::default()).unwrap(), - "Unable to finalize tx" - ); -} - -// #[test] -// fn test_taproot_key_spend() { -// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); -// test_spend_from_wallet(wallet); - -// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig_xprv()); -// test_spend_from_wallet(wallet); +// #[test] +// #[should_panic(expected = "NoRecipients")] +// fn test_create_tx_empty_recipients() { +// 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_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .manually_selected_only(); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_create_tx_version_0() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .version(0); +// assert!(matches!(builder.finish(), Err(CreateTxError::Version0))); +// } + +// #[test] +// fn test_create_tx_version_1_csv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .version(1); +// assert!(matches!(builder.finish(), Err(CreateTxError::Version1Csv))); +// } + +// #[test] +// fn test_create_tx_custom_version() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .version(42); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.version.0, 42); +// } + +// #[test] +// fn test_create_tx_default_locktime_is_last_sync_height() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); + +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// // Since we never synced the wallet we don't have a last_sync_height +// // we could use to try to prevent fee sniping. We default to 0. +// assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 2_000); +// } + +// #[test] +// fn test_create_tx_fee_sniping_locktime_last_sync() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); + +// let psbt = builder.finish().unwrap(); + +// // If there's no current_height we're left with using the last sync height +// assert_eq!( +// psbt.unsigned_tx.lock_time.to_consensus_u32(), +// wallet.latest_checkpoint().height() +// ); +// } + +// #[test] +// fn test_create_tx_default_locktime_cltv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 100_000); +// } + +// #[test] +// fn test_create_tx_locktime_cltv_timestamp() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv_timestamp()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 1_734_230_218); + +// let finalized = wallet.sign(&mut psbt, SignOptions::default()).unwrap(); + +// assert!(finalized); +// } + +// #[test] +// fn test_create_tx_custom_locktime() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .current_height(630_001) +// .nlocktime(absolute::LockTime::from_height(630_000).unwrap()); +// let psbt = builder.finish().unwrap(); + +// // When we explicitly specify a nlocktime +// // we don't try any fee sniping prevention trick +// // (we ignore the current_height) +// assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000); +// } + +// #[test] +// fn test_create_tx_custom_locktime_compatible_with_cltv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .nlocktime(absolute::LockTime::from_height(630_000).unwrap()); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.lock_time.to_consensus_u32(), 630_000); +// } + +// #[test] +// fn test_create_tx_custom_locktime_incompatible_with_cltv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .nlocktime(absolute::LockTime::from_height(50000).unwrap()); +// assert!(matches!(builder.finish(), +// Err(CreateTxError::LockTime { requested, required }) +// if requested.to_consensus_u32() == 50_000 && required.to_consensus_u32() == 100_000)); +// } + +// #[test] +// fn test_create_tx_custom_csv() { +// // desc: wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6))) +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .set_exact_sequence(Sequence(42)) +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); +// // we allow setting a sequence higher than required +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(42)); +// } + +// #[test] +// fn test_create_tx_no_rbf_csv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6)); +// } + +// #[test] +// fn test_create_tx_incompatible_csv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .set_exact_sequence(Sequence(3)); +// assert!(matches!(builder.finish(), +// Err(CreateTxError::RbfSequenceCsv { sequence, csv }) +// if sequence.to_consensus_u32() == 3 && csv.to_consensus_u32() == 6)); +// } + +// #[test] +// fn test_create_tx_with_default_rbf_csv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_csv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); +// // When CSV is enabled it takes precedence over the rbf value (unless forced by the user). +// // It will be set to the OP_CSV value, in this case 6 +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(6)); +// } + +// #[test] +// fn test_create_tx_no_rbf_cltv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// builder.set_exact_sequence(Sequence(0xFFFFFFFE)); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE)); +// } + +// #[test] +// fn test_create_tx_custom_rbf_sequence() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .set_exact_sequence(Sequence(0xDEADBEEF)); +// let psbt = builder.finish().unwrap(); + +// 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); +// 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::InsufficientFunds { .. } +// )), +// )); +// } + +// #[test] +// fn test_create_tx_default_sequence() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD)); +// } + +// #[test] +// fn test_create_tx_drain_wallet_and_drain_to() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(50_000) - fee +// ); +// } + +// #[test] +// fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") +// .unwrap() +// .assume_checked(); +// let drain_addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(20_000)) +// .drain_to(drain_addr.script_pubkey()) +// .drain_wallet(); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); +// let outputs = psbt.unsigned_tx.output; + +// assert_eq!(outputs.len(), 2); +// let main_output = outputs +// .iter() +// .find(|x| x.script_pubkey == addr.script_pubkey()) +// .unwrap(); +// let drain_output = outputs +// .iter() +// .find(|x| x.script_pubkey == drain_addr.script_pubkey()) +// .unwrap(); +// assert_eq!(main_output.value, Amount::from_sat(20_000)); +// assert_eq!(drain_output.value, Amount::from_sat(30_000) - fee); +// } + +// #[test] +// fn test_create_tx_drain_to_and_utxos() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let utxos: Vec<_> = wallet.list_unspent().map(|u| u.outpoint).collect(); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .add_utxos(&utxos) +// .unwrap(); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(50_000) - fee +// ); +// } + +// #[test] +// #[should_panic(expected = "NoRecipients")] +// fn test_create_tx_drain_to_no_drain_wallet_no_utxos() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let drain_addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(drain_addr.script_pubkey()); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_create_tx_default_fee_rate() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_fee_rate!(psbt, fee, FeeRate::BROADCAST_MIN, @add_signature); +// } + +// #[test] +// fn test_create_tx_custom_fee_rate() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_fee_rate!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(5), @add_signature); +// } + +// #[test] +// fn test_legacy_create_tx_custom_fee_rate() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_fee_rate_legacy!(psbt, fee, FeeRate::from_sat_per_vb_unchecked(5), @add_signature); +// } + +// #[test] +// fn test_create_tx_absolute_fee() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_absolute(Amount::from_sat(100)); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(fee, Amount::from_sat(100)); +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(50_000) - fee +// ); +// } + +// #[test] +// fn test_legacy_create_tx_absolute_fee() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_absolute(Amount::from_sat(100)); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(fee, Amount::from_sat(100)); +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(50_000) - fee +// ); +// } + +// #[test] +// fn test_create_tx_absolute_zero_fee() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_absolute(Amount::ZERO); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(fee, Amount::ZERO); +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(50_000) - fee +// ); +// } + +// #[test] +// fn test_legacy_create_tx_absolute_zero_fee() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_absolute(Amount::ZERO); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(fee, Amount::ZERO); +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(50_000) - fee +// ); +// } + +// #[test] +// #[should_panic(expected = "InsufficientFunds")] +// fn test_create_tx_absolute_high_fee() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_absolute(Amount::from_sat(60_000)); +// let _ = builder.finish().unwrap(); +// } + +// #[test] +// #[should_panic(expected = "InsufficientFunds")] +// fn test_legacy_create_tx_absolute_high_fee() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_absolute(Amount::from_sat(60_000)); +// let _ = builder.finish().unwrap(); +// } + +// #[test] +// fn test_create_tx_add_change() { +// use bdk_wallet::tx_builder::TxOrdering; +// let seed = [0; 32]; +// let mut rng: StdRng = SeedableRng::from_seed(seed); +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .ordering(TxOrdering::Shuffle); +// let psbt = builder.finish_with_aux_rand(&mut rng).unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(psbt.unsigned_tx.output.len(), 2); +// assert_eq!(psbt.unsigned_tx.output[0].value, Amount::from_sat(25_000)); +// assert_eq!( +// psbt.unsigned_tx.output[1].value, +// Amount::from_sat(25_000) - fee +// ); +// } + +// #[test] +// fn test_create_tx_skip_change_dust() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(49_800)); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(psbt.unsigned_tx.output.len(), 1); +// assert_eq!(psbt.unsigned_tx.output[0].value.to_sat(), 49_800); +// assert_eq!(fee, Amount::from_sat(200)); +// } + +// #[test] +// #[should_panic(expected = "InsufficientFunds")] +// fn test_create_tx_drain_to_dust_amount() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// // very high fee rate, so that the only output would be below dust +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_rate(FeeRate::from_sat_per_vb_unchecked(454)); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_create_tx_ordering_respected() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let bip69_txin_cmp = |tx_a: &TxIn, tx_b: &TxIn| { +// let project_outpoint = |t: &TxIn| (t.previous_output.txid, t.previous_output.vout); +// project_outpoint(tx_a).cmp(&project_outpoint(tx_b)) +// }; + +// let bip69_txout_cmp = |tx_a: &TxOut, tx_b: &TxOut| { +// let project_utxo = |t: &TxOut| (t.value, t.script_pubkey.clone()); +// project_utxo(tx_a).cmp(&project_utxo(tx_b)) +// }; + +// let custom_bip69_ordering = bdk_wallet::tx_builder::TxOrdering::Custom { +// input_sort: Arc::new(bip69_txin_cmp), +// output_sort: Arc::new(bip69_txout_cmp), +// }; + +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)) +// .ordering(custom_bip69_ordering); + +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(psbt.unsigned_tx.output.len(), 3); +// assert_eq!( +// psbt.unsigned_tx.output[0].value, +// Amount::from_sat(10_000) - fee +// ); +// assert_eq!(psbt.unsigned_tx.output[1].value, Amount::from_sat(10_000)); +// assert_eq!(psbt.unsigned_tx.output[2].value, Amount::from_sat(30_000)); +// } + +// #[test] +// fn test_create_tx_default_sighash() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.inputs[0].sighash_type, None); +// } + +// #[test] +// fn test_legacy_create_tx_default_sighash() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.inputs[0].sighash_type, None); +// } + +// #[test] +// fn test_create_tx_custom_sighash() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .sighash(EcdsaSighashType::Single.into()); +// let psbt = builder.finish().unwrap(); + +// assert_eq!( +// psbt.inputs[0].sighash_type, +// Some(EcdsaSighashType::Single.into()) +// ); +// } + +// #[test] +// fn test_legacy_create_tx_custom_sighash() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_pkh()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .sighash(EcdsaSighashType::Single.into()); +// let psbt = builder.finish().unwrap(); + +// assert_eq!( +// psbt.inputs[0].sighash_type, +// Some(EcdsaSighashType::Single.into()) +// ); +// } + +// #[test] +// fn test_create_tx_input_hd_keypaths() { +// use bitcoin::bip32::{DerivationPath, Fingerprint}; +// use core::str::FromStr; + +// let (mut wallet, _) = +// get_funded_wallet_single("wpkh([d34db33f/44'/0'/0' +// ]tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/ +// 0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.inputs[0].bip32_derivation.len(), 1); +// assert_eq!( +// psbt.inputs[0].bip32_derivation.values().next().unwrap(), +// &( +// Fingerprint::from_str("d34db33f").unwrap(), +// DerivationPath::from_str("m/44'/0'/0'/0/0").unwrap() +// ) +// ); +// } + +// #[test] +// fn test_create_tx_output_hd_keypaths() { +// use bitcoin::bip32::{DerivationPath, Fingerprint}; +// use core::str::FromStr; + +// let (mut wallet, _) = +// get_funded_wallet_single("wpkh([d34db33f/44'/0'/0' +// ]tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/ +// 0/*)"); + +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.outputs[0].bip32_derivation.len(), 1); +// let expected_derivation_path = format!("m/44'/0'/0'/0/{}", addr.index); +// assert_eq!( +// psbt.outputs[0].bip32_derivation.values().next().unwrap(), +// &( +// Fingerprint::from_str("d34db33f").unwrap(), +// DerivationPath::from_str(&expected_derivation_path).unwrap() +// ) +// ); +// } + +// #[test] +// fn test_create_tx_set_redeem_script_p2sh() { +// use bitcoin::hex::FromHex; + +// let (mut wallet, _) = +// get_funded_wallet_single("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert_eq!( +// psbt.inputs[0].redeem_script, +// Some(ScriptBuf::from( +// Vec::::from_hex( +// "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac" +// ) +// .unwrap() +// )) +// ); +// assert_eq!(psbt.inputs[0].witness_script, None); +// } + +// #[test] +// fn test_create_tx_set_witness_script_p2wsh() { +// use bitcoin::hex::FromHex; + +// let (mut wallet, _) = +// get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))" +// ); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.inputs[0].redeem_script, None); +// assert_eq!( +// psbt.inputs[0].witness_script, +// Some(ScriptBuf::from( +// Vec::::from_hex( +// "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac" +// ) +// .unwrap() +// )) +// ); +// } + +// #[test] +// fn test_create_tx_set_redeem_witness_script_p2wsh_p2sh() { +// let (mut wallet, _) = get_funded_wallet_single( +// "sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))", +// ); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// let script = ScriptBuf::from_hex( +// "21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac", +// ) +// .unwrap(); + +// assert_eq!(psbt.inputs[0].redeem_script, Some(script.to_p2wsh())); +// assert_eq!(psbt.inputs[0].witness_script, Some(script)); +// } + +// #[test] +// fn test_create_tx_non_witness_utxo() { +// let (mut wallet, _) = +// get_funded_wallet_single("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert!(psbt.inputs[0].non_witness_utxo.is_some()); +// assert!(psbt.inputs[0].witness_utxo.is_none()); +// } + +// #[test] +// fn test_create_tx_only_witness_utxo() { +// let (mut wallet, _) = +// get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))" +// ); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .only_witness_utxo() +// .drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert!(psbt.inputs[0].non_witness_utxo.is_none()); +// assert!(psbt.inputs[0].witness_utxo.is_some()); +// } + +// #[test] +// fn test_create_tx_shwpkh_has_witness_utxo() { +// let (mut wallet, _) = +// get_funded_wallet_single("sh(wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))" +// ); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert!(psbt.inputs[0].witness_utxo.is_some()); +// } + +// #[test] +// fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() { +// let (mut wallet, _) = +// get_funded_wallet_single("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))" +// ); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert!(psbt.inputs[0].non_witness_utxo.is_some()); +// assert!(psbt.inputs[0].witness_utxo.is_some()); +// } + +// #[test] +// fn test_create_tx_add_utxo() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let small_output_tx = Transaction { +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// version: transaction::Version::non_standard(0), +// lock_time: absolute::LockTime::ZERO, +// }; +// let txid = small_output_tx.compute_txid(); +// insert_tx(&mut wallet, small_output_tx); +// let anchor = ConfirmationBlockTime { +// block_id: wallet.latest_checkpoint().get(2000).unwrap().block_id(), +// confirmation_time: 200, +// }; +// insert_anchor(&mut wallet, txid, anchor); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .add_utxo(OutPoint { txid, vout: 0 }) +// .unwrap(); +// let psbt = builder.finish().unwrap(); +// let (sent, _received) = +// wallet.sent_and_received(&psbt.clone().extract_tx().expect("failed to extract tx")); + +// assert_eq!( +// psbt.unsigned_tx.input.len(), +// 2, +// "should add an additional input since 25_000 < 30_000" +// ); +// assert_eq!( +// sent, +// Amount::from_sat(75_000), +// "total should be sum of both inputs" +// ); +// } + +// #[test] +// #[should_panic(expected = "InsufficientFunds")] +// fn test_create_tx_manually_selected_insufficient() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let small_output_tx = Transaction { +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// version: transaction::Version::non_standard(0), +// lock_time: absolute::LockTime::ZERO, +// }; +// let txid = small_output_tx.compute_txid(); +// insert_tx(&mut wallet, small_output_tx.clone()); +// let anchor = ConfirmationBlockTime { +// block_id: wallet.latest_checkpoint().get(2000).unwrap().block_id(), +// confirmation_time: 200, +// }; +// insert_anchor(&mut wallet, txid, anchor); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .add_utxo(OutPoint { txid, vout: 0 }) +// .unwrap() +// .manually_selected_only(); +// builder.finish().unwrap(); +// } + +// #[test] +// #[should_panic(expected = "SpendingPolicyRequired(External)")] +// fn test_create_tx_policy_path_required() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_a_or_b_plus_csv()); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_create_tx_policy_path_no_csv() { +// let (descriptor, change_descriptor) = get_test_wpkh_and_change_desc(); +// let mut wallet = Wallet::create(descriptor, change_descriptor) +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .expect("wallet"); + +// let tx = Transaction { +// version: transaction::Version::non_standard(0), +// lock_time: absolute::LockTime::ZERO, +// input: vec![], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(50_000), +// }], +// }; +// insert_tx(&mut wallet, tx); + +// let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); +// let root_id = external_policy.id; +// // child #0 is just the key "A" +// let path = vec![(root_id, vec![0])].into_iter().collect(); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .policy_path(path, KeychainKind::External); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD)); +// } + +// #[test] +// fn test_create_tx_policy_path_use_csv() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_a_or_b_plus_csv()); + +// let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); +// let root_id = external_policy.id; +// // child #1 is or(pk(B),older(144)) +// let path = vec![(root_id, vec![1])].into_iter().collect(); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .policy_path(path, KeychainKind::External); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144)); +// } + +// #[test] +// fn test_create_tx_policy_path_ignored_subtree_with_csv() { +// let (mut wallet, _) = +// get_funded_wallet_single("wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu), +// or_i(and_v(v:pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(30)),and_v(v: +// pkh(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(90)))))"); + +// let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap(); +// let root_id = external_policy.id; +// // child #0 is pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu) +// let path = vec![(root_id, vec![0])].into_iter().collect(); + +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(30_000)) +// .policy_path(path, KeychainKind::External); +// let psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFD)); +// } + +// #[test] +// fn test_create_tx_global_xpubs_with_origin() { +// use bitcoin::bip32; +// let (mut wallet, _) = +// get_funded_wallet_single("wpkh([73756c7f/48'/0'/0'/2' +// ]tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/ +// 0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .add_global_xpubs(); +// let psbt = builder.finish().unwrap(); + +// let key = +// bip32::Xpub::from_str(" +// tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3" +// ).unwrap(); 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.get(&key), Some(&(fingerprint, path))); +// } + +// #[test] +// fn test_create_tx_increment_change_index() { +// // Test derivation index and unused index of change keychain when creating a transaction +// // Cases include wildcard and non-wildcard descriptors with and without an internal keychain +// // note the test assumes that the first external address is revealed since we're using +// // `receive_output` +// struct TestCase { +// name: &'static str, +// descriptor: &'static str, +// change_descriptor: Option<&'static str>, +// // amount to send +// to_send: Amount, +// // (derivation index, next unused index) of *change keychain* +// expect: (Option, u32), +// } +// // total wallet funds +// let amount = Amount::from_sat(10_000); +// let recipient = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5") +// .unwrap() +// .assume_checked() +// .script_pubkey(); +// let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); +// [ +// TestCase { +// name: "two wildcard, builder error", +// descriptor: desc, +// change_descriptor: Some(change_desc), +// to_send: amount + Amount::from_sat(1), +// // should not use or derive change index +// expect: (None, 0), +// }, +// TestCase { +// name: "two wildcard, create change", +// descriptor: desc, +// change_descriptor: Some(change_desc), +// to_send: Amount::from_sat(5_000), +// // should use change index +// expect: (Some(0), 1), +// }, +// TestCase { +// name: "two wildcard, no change", +// descriptor: desc, +// change_descriptor: Some(change_desc), +// to_send: Amount::from_sat(9_850), +// // should not use change index +// expect: (None, 0), +// }, +// TestCase { +// name: "one wildcard, create change", +// descriptor: desc, +// change_descriptor: None, +// to_send: Amount::from_sat(5_000), +// // should use change index of external keychain +// expect: (Some(1), 2), +// }, +// TestCase { +// name: "one wildcard, no change", +// descriptor: desc, +// change_descriptor: None, +// to_send: Amount::from_sat(9_850), +// // should not use change index +// expect: (Some(0), 1), +// }, +// TestCase { +// name: "single key, create change", +// descriptor: get_test_tr_single_sig(), +// change_descriptor: None, +// to_send: Amount::from_sat(5_000), +// // single key only has one derivation index (0) +// expect: (Some(0), 0), +// }, +// TestCase { +// name: "single key, no change", +// descriptor: get_test_tr_single_sig(), +// change_descriptor: None, +// to_send: Amount::from_sat(9_850), +// expect: (Some(0), 0), +// }, +// ] +// .into_iter() +// .for_each(|test| { +// // create wallet +// let (params, change_keychain) = match test.change_descriptor { +// Some(change_desc) => ( +// Wallet::create(test.descriptor, change_desc), +// KeychainKind::Internal, +// ), +// None => ( +// Wallet::create_single(test.descriptor), +// KeychainKind::External, +// ), +// }; +// let mut wallet = params +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .unwrap(); +// // fund wallet +// receive_output(&mut wallet, amount, ReceiveTo::Mempool(0)); +// // create tx +// let mut builder = wallet.build_tx(); +// builder.add_recipient(recipient.clone(), test.to_send); +// let res = builder.finish(); +// if !test.name.contains("error") { +// assert!(res.is_ok()); +// } +// let (exp_derivation_index, exp_next_unused) = test.expect; +// assert_eq!( +// wallet.derivation_index(change_keychain), +// exp_derivation_index, +// "derivation index test {}", +// test.name, +// ); +// assert_eq!( +// wallet.next_unused_address(change_keychain).index, +// exp_next_unused, +// "next unused index test {}", +// test.name, +// ); +// }); +// } + +// #[test] +// fn test_get_psbt_input() { +// // this should grab a known good utxo and set the input +// 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()); +// } +// } + +// #[test] +// #[should_panic( +// expected = +// "MissingKeyOrigin(\" +// tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\ +// ")" )] +// fn test_create_tx_global_xpubs_origin_missing() { +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/ +// 0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .add_global_xpubs(); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_create_tx_global_xpubs_master_without_origin() { +// use bitcoin::bip32; +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/ +// 0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)) +// .add_global_xpubs(); +// let psbt = builder.finish().unwrap(); + +// 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.get(&key), +// Some(&(fingerprint, bip32::DerivationPath::default())) +// ); +// } + +// #[test] +// fn test_fee_amount_negative_drain_val() { +// // While building the transaction, bdk would calculate the drain_value +// // as +// // current_delta - fee_amount - drain_fee +// // using saturating_sub, meaning that if the result would end up negative, +// // it'll remain to zero instead. +// // 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_wpkh(); +// let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") +// .unwrap() +// .assume_checked(); +// let fee_rate = FeeRate::from_sat_per_kwu(500); +// let incoming_op = receive_output_in_latest_block(&mut wallet, Amount::from_sat(8859)); + +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(send_to.script_pubkey(), Amount::from_sat(8630)) +// .add_utxo(incoming_op) +// .unwrap() +// .fee_rate(fee_rate); +// let psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// assert_eq!(psbt.inputs.len(), 1); +// assert_fee_rate!(psbt, fee, fee_rate, @add_signature); +// } + +// #[test] +// fn test_sign_single_xprv() { +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!(extracted.input[0].witness.len(), 2); +// } + +// #[test] +// fn test_sign_single_xprv_with_master_fingerprint_and_path() { +// let (mut wallet, _) = +// get_funded_wallet_single("wpkh([d34db33f/84h/1h/ +// 0h]tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!(extracted.input[0].witness.len(), 2); +// } + +// #[test] +// fn test_sign_single_xprv_bip44_path() { +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// 44'/0'/0'/0/*)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!(extracted.input[0].witness.len(), 2); +// } + +// #[test] +// fn test_sign_single_xprv_sh_wpkh() { +// let (mut wallet, _) = +// get_funded_wallet_single(" +// sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *))"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!(extracted.input[0].witness.len(), 2); +// } + +// #[test] +// fn test_sign_single_wif() { +// let (mut wallet, _) = +// get_funded_wallet_single("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!(extracted.input[0].witness.len(), 2); +// } + +// #[test] +// fn test_sign_single_xprv_no_hd_keypaths() { +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// psbt.inputs[0].bip32_derivation.clear(); +// assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0); + +// let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); +// assert!(finalized); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!(extracted.input[0].witness.len(), 2); +// } + +// #[test] +// fn test_include_output_redeem_witness_script() { +// let desc = get_test_wpkh(); +// let change_desc = +// "sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW, +// cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))"; let (mut wallet, _) = +// get_funded_wallet(desc, change_desc); let addr = +// Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)) +// .include_output_redeem_witness_script(); +// let psbt = builder.finish().unwrap(); + +// // p2sh-p2wsh transaction should contain both witness and redeem scripts +// assert!(psbt +// .outputs +// .iter() +// .any(|output| output.redeem_script.is_some() && output.witness_script.is_some())); +// } + +// #[test] +// fn test_signing_only_one_of_multiple_inputs() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)) +// .include_output_redeem_witness_script(); +// let mut psbt = builder.finish().unwrap(); + +// // add another input to the psbt that is at least passable. +// let dud_input = bitcoin::psbt::Input { +// witness_utxo: Some(TxOut { +// value: Amount::from_sat(100_000), +// script_pubkey: miniscript::Descriptor::::from_str( +// "wpkh(025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357)", +// ) +// .unwrap() +// .script_pubkey(), +// }), +// ..Default::default() +// }; + +// psbt.inputs.push(dud_input); +// psbt.unsigned_tx.input.push(bitcoin::TxIn::default()); +// let is_final = wallet +// .sign( +// &mut psbt, +// SignOptions { +// trust_witness_utxo: true, +// ..Default::default() +// }, +// ) +// .unwrap(); +// assert!( +// !is_final, +// "shouldn't be final since we can't sign one of the inputs" +// ); +// assert!( +// psbt.inputs[0].final_script_witness.is_some(), +// "should finalized input it signed" +// ) +// } + +// #[test] +// fn test_try_finalize_sign_option() { +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); + +// for try_finalize in &[true, false] { +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet +// .sign( +// &mut psbt, +// SignOptions { +// try_finalize: *try_finalize, +// ..Default::default() +// }, +// ) +// .unwrap(); + +// psbt.inputs.iter().for_each(|input| { +// if *try_finalize { +// assert!(finalized); +// assert!(input.final_script_sig.is_none()); +// assert!(input.final_script_witness.is_some()); +// } else { +// assert!(!finalized); +// assert!(input.final_script_sig.is_none()); +// assert!(input.final_script_witness.is_none()); +// } +// }); +// } +// } + +// #[test] +// fn test_taproot_try_finalize_sign_option() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); + +// for try_finalize in &[true, false] { +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let finalized = wallet +// .sign( +// &mut psbt, +// SignOptions { +// try_finalize: *try_finalize, +// ..Default::default() +// }, +// ) +// .unwrap(); + +// psbt.inputs.iter().for_each(|input| { +// if *try_finalize { +// assert!(finalized); +// assert!(input.final_script_sig.is_none()); +// assert!(input.final_script_witness.is_some()); +// assert!(input.tap_key_sig.is_none()); +// assert!(input.tap_script_sigs.is_empty()); +// assert!(input.tap_scripts.is_empty()); +// assert!(input.tap_key_origins.is_empty()); +// assert!(input.tap_internal_key.is_none()); +// assert!(input.tap_merkle_root.is_none()); +// } else { +// assert!(!finalized); +// assert!(input.final_script_sig.is_none()); +// assert!(input.final_script_witness.is_none()); +// } +// }); +// psbt.outputs.iter().for_each(|output| { +// if *try_finalize { +// assert!(finalized); +// assert!(output.tap_key_origins.is_empty()); +// } else { +// assert!(!finalized); +// assert!(!output.tap_key_origins.is_empty()); +// } +// }); +// } +// } + +// #[test] +// fn test_sign_nonstandard_sighash() { +// let sighash = EcdsaSighashType::NonePlusAnyoneCanPay; + +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .sighash(sighash.into()) +// .drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let result = wallet.sign(&mut psbt, Default::default()); +// assert!( +// result.is_err(), +// "Signing should have failed because the TX uses non-standard sighashes" +// ); +// assert_matches!( +// result, +// Err(SignerError::NonStandardSighash), +// "Signing failed with the wrong error type" +// ); + +// // try again after opting-in +// let result = wallet.sign( +// &mut psbt, +// SignOptions { +// allow_all_sighashes: true, +// ..Default::default() +// }, +// ); +// assert!(result.is_ok(), "Signing should have worked"); +// assert!( +// result.unwrap(), +// "Should finalize the input since we can produce signatures" +// ); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!( +// *extracted.input[0].witness.to_vec()[0].last().unwrap(), +// sighash.to_u32() as u8, +// "The signature should have been made with the right sighash" +// ); +// } + +// #[test] +// fn test_unused_address() { +// let descriptor = +// "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/ +// *)"; let change_descriptor = get_test_wpkh(); +// let mut wallet = Wallet::create(descriptor, change_descriptor) +// .network(Network::Testnet) +// .create_wallet_no_persist() +// .expect("wallet"); + +// // `list_unused_addresses` should be empty if we haven't revealed any +// assert!(wallet +// .list_unused_addresses(KeychainKind::External) +// .next() +// .is_none()); + +// assert_eq!( +// wallet +// .next_unused_address(KeychainKind::External) +// .to_string(), +// "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" +// ); +// assert_eq!( +// wallet +// .list_unused_addresses(KeychainKind::External) +// .next() +// .unwrap() +// .to_string(), +// "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" +// ); +// } + +// #[test] +// fn test_next_unused_address() { +// let descriptor = +// "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/ +// *)"; let change_descriptor = get_test_wpkh(); +// let mut wallet = Wallet::create(descriptor, change_descriptor) +// .network(Network::Testnet) +// .create_wallet_no_persist() +// .expect("wallet"); +// assert_eq!(wallet.derivation_index(KeychainKind::External), None); + +// assert_eq!( +// wallet +// .next_unused_address(KeychainKind::External) +// .to_string(), +// "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" +// ); +// assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0)); +// // calling next_unused again gives same address +// assert_eq!( +// wallet +// .next_unused_address(KeychainKind::External) +// .to_string(), +// "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" +// ); +// assert_eq!(wallet.derivation_index(KeychainKind::External), Some(0)); + +// // test mark used / unused +// assert!(wallet.mark_used(KeychainKind::External, 0)); +// let next_unused_addr = wallet.next_unused_address(KeychainKind::External); +// assert_eq!(next_unused_addr.index, 1); + +// assert!(wallet.unmark_used(KeychainKind::External, 0)); +// let next_unused_addr = wallet.next_unused_address(KeychainKind::External); +// assert_eq!(next_unused_addr.index, 0); + +// // use the above address +// receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); + +// assert_eq!( +// wallet +// .next_unused_address(KeychainKind::External) +// .to_string(), +// "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" +// ); +// assert_eq!(wallet.derivation_index(KeychainKind::External), Some(1)); + +// // trying to mark index 0 unused should return false +// assert!(!wallet.unmark_used(KeychainKind::External, 0)); +// } + +// #[test] +// fn test_peek_address_at_index() { +// let descriptor = +// "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/ +// *)"; let change_descriptor = get_test_wpkh(); +// let mut wallet = Wallet::create(descriptor, change_descriptor) +// .network(Network::Testnet) +// .create_wallet_no_persist() +// .expect("wallet"); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 1).to_string(), +// "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" +// ); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 0).to_string(), +// "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" +// ); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 2).to_string(), +// "tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2" +// ); + +// // current new address is not affected +// assert_eq!( +// wallet +// .reveal_next_address(KeychainKind::External) +// .to_string(), +// "tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a" +// ); + +// assert_eq!( +// wallet +// .reveal_next_address(KeychainKind::External) +// .to_string(), +// "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" +// ); +// } + +// #[test] +// fn test_peek_address_at_index_not_derivable() { +// let descriptor = +// "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/ +// 1)"; let wallet = Wallet::create(descriptor, get_test_wpkh()) +// .network(Network::Testnet) +// .create_wallet_no_persist() +// .unwrap(); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 1).to_string(), +// "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" +// ); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 0).to_string(), +// "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" +// ); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 2).to_string(), +// "tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7" +// ); +// } + +// #[test] +// fn test_returns_index_and_address() { +// let descriptor = +// "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)"; +// let mut wallet = Wallet::create(descriptor, get_test_wpkh()) +// .network(Network::Testnet) +// .create_wallet_no_persist() +// .unwrap(); + +// // new index 0 +// assert_eq!( +// wallet.reveal_next_address(KeychainKind::External), +// AddressInfo { +// index: 0, +// address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a") +// .unwrap() +// .assume_checked(), +// keychain: KeychainKind::External, +// } +// ); + +// // new index 1 +// assert_eq!( +// wallet.reveal_next_address(KeychainKind::External), +// AddressInfo { +// index: 1, +// address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7") +// .unwrap() +// .assume_checked(), +// keychain: KeychainKind::External, +// } +// ); + +// // peek index 25 +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 25), +// AddressInfo { +// index: 25, +// address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2") +// .unwrap() +// .assume_checked(), +// keychain: KeychainKind::External, +// } +// ); + +// // new index 2 +// assert_eq!( +// wallet.reveal_next_address(KeychainKind::External), +// AddressInfo { +// index: 2, +// address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2") +// .unwrap() +// .assume_checked(), +// keychain: KeychainKind::External, +// } +// ); +// } + +// #[test] +// fn test_sending_to_bip350_bech32m_address() { +// let (mut wallet, _) = get_funded_wallet_wpkh(); +// let addr = +// Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(45_000)); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_get_address() { +// use bdk_wallet::descriptor::template::Bip84; +// let key = +// bitcoin::bip32::Xpriv::from_str(" +// tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy" +// ).unwrap(); let wallet = Wallet::create( +// Bip84(key, KeychainKind::External), +// Bip84(key, KeychainKind::Internal), +// ) +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .unwrap(); + +// assert_eq!( +// wallet.peek_address(KeychainKind::External, 0), +// AddressInfo { +// index: 0, +// address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w") +// .unwrap() +// .assume_checked(), +// keychain: KeychainKind::External, +// } +// ); + +// assert_eq!( +// wallet.peek_address(KeychainKind::Internal, 0), +// AddressInfo { +// index: 0, +// address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79") +// .unwrap() +// .assume_checked(), +// keychain: KeychainKind::Internal, +// } +// ); +// } + +// #[test] +// fn test_reveal_addresses() { +// let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); +// let mut wallet = Wallet::create(desc, change_desc) +// .network(Network::Signet) +// .create_wallet_no_persist() +// .unwrap(); +// let keychain = KeychainKind::External; + +// let last_revealed_addr = wallet.reveal_addresses_to(keychain, 9).last().unwrap(); +// assert_eq!(wallet.derivation_index(keychain), Some(9)); + +// let unused_addrs = wallet.list_unused_addresses(keychain).collect::>(); +// assert_eq!(unused_addrs.len(), 10); +// assert_eq!(unused_addrs.last().unwrap(), &last_revealed_addr); + +// // revealing to an already revealed index returns nothing +// let mut already_revealed = wallet.reveal_addresses_to(keychain, 9); +// assert!(already_revealed.next().is_none()); +// } + +// #[test] +// 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::create( +// Bip84(key, KeychainKind::External), +// Bip84(key, KeychainKind::Internal), +// ) +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .unwrap(); + +// let mut used_set = HashSet::new(); + +// (0..3).for_each(|_| { +// let external_addr = wallet.reveal_next_address(KeychainKind::External).address; +// assert!(used_set.insert(external_addr)); + +// let internal_addr = wallet.reveal_next_address(KeychainKind::Internal).address; +// assert!(used_set.insert(internal_addr)); +// }); +// } + +// #[test] +// fn test_taproot_psbt_populate_tap_key_origins() { +// let (desc, change_desc) = get_test_tr_single_sig_xprv_and_change_desc(); +// let (mut wallet, _) = get_funded_wallet(desc, change_desc); +// let addr = wallet.reveal_next_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert_eq!( +// psbt.inputs[0] +// .tap_key_origins +// .clone() +// .into_iter() +// .collect::>(), +// vec![( +// from_str!("0841db1dbaf949dbbda893e01a18f2cca9179cf8ea2d8e667857690502b06483"), +// (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/0"))) +// )], +// "Wrong input tap_key_origins" +// ); +// assert_eq!( +// psbt.outputs[0] +// .tap_key_origins +// .clone() +// .into_iter() +// .collect::>(), +// vec![( +// from_str!("9187c1e80002d19ddde9c5c7f5394e9a063cee8695867b58815af0562695ca21"), +// (vec![], (from_str!("f6a5cb8b"), from_str!("m/0/1"))) +// )], +// "Wrong output 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(), +// get_test_tr_single_sig()); let addr = wallet.reveal_next_address(KeychainKind::External); + +// let path = vec![("rn4nre9c".to_string(), vec![0])] +// .into_iter() +// .collect(); + +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .policy_path(path, KeychainKind::External); +// let psbt = builder.finish().unwrap(); + +// let mut input_key_origins = psbt.inputs[0] +// .tap_key_origins +// .clone() +// .into_iter() +// .collect::>(); +// input_key_origins.sort(); + +// assert_eq!( +// input_key_origins, +// vec![ +// ( +// from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"), +// ( +// vec![ +// from_str!( +// "858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e" +// ), +// from_str!( +// "f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903" +// ), +// ], +// (FromStr::from_str("ece52657").unwrap(), vec![].into()) +// ) +// ), +// ( +// from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"), +// ( +// vec![], +// (FromStr::from_str("871fd295").unwrap(), vec![].into()) +// ) +// ) +// ], +// "Wrong input tap_key_origins" +// ); + +// let mut output_key_origins = psbt.outputs[0] +// .tap_key_origins +// .clone() +// .into_iter() +// .collect::>(); +// output_key_origins.sort(); + +// assert_eq!( +// input_key_origins, output_key_origins, +// "Wrong output tap_key_origins" +// ); +// } + +// #[test] +// fn test_taproot_psbt_input_tap_tree() { +// use bitcoin::hex::FromHex; +// use bitcoin::taproot; + +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let psbt = builder.finish().unwrap(); + +// assert_eq!( +// psbt.inputs[0].tap_merkle_root, +// Some( +// TapNodeHash::from_str( +// "61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986" +// ) +// .unwrap() +// ), +// ); +// assert_eq!( +// psbt.inputs[0].tap_scripts.clone().into_iter().collect::>(), +// vec![ +// +// (taproot::ControlBlock::decode(&Vec::::from_hex(" +// c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2" +// ).unwrap()).unwrap(), +// (ScriptBuf::from_hex("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac"). +// unwrap(), taproot::LeafVersion::TapScript)), +// (taproot::ControlBlock::decode(&Vec::::from_hex(" +// c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834" +// ).unwrap()).unwrap(), +// (ScriptBuf::from_hex("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac"). +// unwrap(), taproot::LeafVersion::TapScript)), ], +// ); +// assert_eq!( +// psbt.inputs[0].tap_internal_key, +// Some(from_str!( +// "b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55" +// )) +// ); + +// // Since we are creating an output to the same address as the input, assert that the +// // internal_key is the same +// assert_eq!( +// psbt.inputs[0].tap_internal_key, +// psbt.outputs[0].tap_internal_key +// ); + +// let tap_tree: bitcoin::taproot::TapTree = +// serde_json::from_str(r#"[1,{"Script":[" +// 2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",192]},1,{"Script":[" +// 208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac",192]}]"#).unwrap(); +// assert_eq!(psbt.outputs[0].tap_tree, Some(tap_tree)); +// } + +// #[test] +// fn test_taproot_sign_missing_witness_utxo() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); +// let witness_utxo = psbt.inputs[0].witness_utxo.take(); + +// let result = wallet.sign( +// &mut psbt, +// SignOptions { +// allow_all_sighashes: true, +// ..Default::default() +// }, +// ); +// assert_matches!( +// result, +// Err(SignerError::MissingWitnessUtxo), +// "Signing should have failed with the correct error because the witness_utxo is missing" +// ); + +// // restore the witness_utxo +// psbt.inputs[0].witness_utxo = witness_utxo; + +// let result = wallet.sign( +// &mut psbt, +// SignOptions { +// allow_all_sighashes: true, +// ..Default::default() +// }, +// ); + +// assert_matches!( +// result, +// Ok(true), +// "Should finalize the input since we can produce signatures" +// ); +// } + +// #[test] +// fn test_taproot_sign_using_non_witness_utxo() { +// let (mut wallet, prev_txid) = get_funded_wallet_single(get_test_tr_single_sig()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder.drain_to(addr.script_pubkey()).drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// psbt.inputs[0].witness_utxo = None; +// psbt.inputs[0].non_witness_utxo = +// Some(wallet.get_tx(prev_txid).unwrap().tx_node.as_ref().clone()); +// assert!( +// psbt.inputs[0].non_witness_utxo.is_some(), +// "Previous tx should be present in the database" +// ); + +// let result = wallet.sign(&mut psbt, Default::default()); +// assert!(result.is_ok(), "Signing should have worked"); +// assert!( +// result.unwrap(), +// "Should finalize the input since we can produce signatures" +// ); +// } + +// fn test_spend_from_wallet(mut wallet: Wallet) { +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); + +// assert_eq!(psbt.unsigned_tx.version.0, 2); +// assert!( +// wallet.sign(&mut psbt, Default::default()).unwrap(), +// "Unable to finalize tx" +// ); +// } + +// // #[test] +// // fn test_taproot_key_spend() { +// // let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); +// // test_spend_from_wallet(wallet); + +// // let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig_xprv()); +// // test_spend_from_wallet(wallet); +// // } + +// #[test] +// fn test_taproot_no_key_spend() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); + +// assert!( +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// sign_with_tap_internal_key: false, +// ..Default::default() +// }, +// ) +// .unwrap(), +// "Unable to finalize tx" +// ); + +// assert!(psbt.inputs.iter().all(|i| i.tap_key_sig.is_none())); +// } + +// #[test] +// fn test_taproot_script_spend() { +// let (wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); +// test_spend_from_wallet(wallet); + +// let (wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_xprv()); +// test_spend_from_wallet(wallet); +// } + +// #[test] +// fn test_taproot_script_spend_sign_all_leaves() { +// use bdk_wallet::signer::TapLeavesOptions; +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); + +// assert!( +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// tap_leaves_options: TapLeavesOptions::All, +// ..Default::default() +// }, +// ) +// .unwrap(), +// "Unable to finalize tx" +// ); + +// assert!(psbt +// .inputs +// .iter() +// .all(|i| i.tap_script_sigs.len() == i.tap_scripts.len())); +// } + +// #[test] +// fn test_taproot_script_spend_sign_include_some_leaves() { +// use bdk_wallet::signer::TapLeavesOptions; +// use bitcoin::taproot::TapLeafHash; + +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); +// let mut script_leaves: Vec<_> = psbt.inputs[0] +// .tap_scripts +// .clone() +// .values() +// .map(|(script, version)| TapLeafHash::from_script(script, *version)) +// .collect(); +// let included_script_leaves = vec![script_leaves.pop().unwrap()]; +// let excluded_script_leaves = script_leaves; + +// assert!( +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// tap_leaves_options: +// TapLeavesOptions::Include(included_script_leaves.clone()), +// ..Default::default() }, +// ) +// .unwrap(), +// "Unable to finalize tx" +// ); + +// assert!(psbt.inputs[0] +// .tap_script_sigs +// .iter() +// .all(|s| included_script_leaves.contains(&s.0 .1) +// && !excluded_script_leaves.contains(&s.0 .1))); +// } + +// #[test] +// fn test_taproot_script_spend_sign_exclude_some_leaves() { +// use bdk_wallet::signer::TapLeavesOptions; +// use bitcoin::taproot::TapLeafHash; + +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); +// let mut script_leaves: Vec<_> = psbt.inputs[0] +// .tap_scripts +// .clone() +// .values() +// .map(|(script, version)| TapLeafHash::from_script(script, *version)) +// .collect(); +// let included_script_leaves = [script_leaves.pop().unwrap()]; +// let excluded_script_leaves = script_leaves; + +// assert!( +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// tap_leaves_options: +// TapLeavesOptions::Exclude(excluded_script_leaves.clone()), +// ..Default::default() }, +// ) +// .unwrap(), +// "Unable to finalize tx" +// ); + +// assert!(psbt.inputs[0] +// .tap_script_sigs +// .iter() +// .all(|s| included_script_leaves.contains(&s.0 .1) +// && !excluded_script_leaves.contains(&s.0 .1))); +// } + +// #[test] +// fn test_taproot_script_spend_sign_no_leaves() { +// use bdk_wallet::signer::TapLeavesOptions; +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); + +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// tap_leaves_options: TapLeavesOptions::None, +// ..Default::default() +// }, +// ) +// .unwrap(); + +// assert!(psbt.inputs.iter().all(|i| i.tap_script_sigs.is_empty())); +// } + +// #[test] +// fn test_taproot_sign_derive_index_from_psbt() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig_xprv()); + +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); +// let mut psbt = builder.finish().unwrap(); + +// // re-create the wallet with an empty db +// let wallet_empty = Wallet::create(get_test_tr_single_sig_xprv(), get_test_tr_single_sig()) +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .unwrap(); + +// // signing with an empty db means that we will only look at the psbt to infer the +// // derivation index +// assert!( +// wallet_empty.sign(&mut psbt, Default::default()).unwrap(), +// "Unable to finalize tx" +// ); +// } + +// #[test] +// fn test_taproot_sign_explicit_sighash_all() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .sighash(TapSighashType::All.into()) +// .drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let result = wallet.sign(&mut psbt, Default::default()); +// assert!( +// result.is_ok(), +// "Signing should work because SIGHASH_ALL is safe" +// ) +// } + +// #[test] +// fn test_taproot_sign_non_default_sighash() { +// let sighash = TapSighashType::NonePlusAnyoneCanPay; + +// let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); +// let addr = wallet.next_unused_address(KeychainKind::External); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .sighash(sighash.into()) +// .drain_wallet(); +// let mut psbt = builder.finish().unwrap(); + +// let witness_utxo = psbt.inputs[0].witness_utxo.take(); + +// let result = wallet.sign(&mut psbt, Default::default()); +// assert!( +// result.is_err(), +// "Signing should have failed because the TX uses non-standard sighashes" +// ); +// assert_matches!( +// result, +// Err(SignerError::NonStandardSighash), +// "Signing failed with the wrong error type" +// ); + +// // try again after opting-in +// let result = wallet.sign( +// &mut psbt, +// SignOptions { +// allow_all_sighashes: true, +// ..Default::default() +// }, +// ); +// assert!( +// result.is_err(), +// "Signing should have failed because the witness_utxo is missing" +// ); +// assert_matches!( +// result, +// Err(SignerError::MissingWitnessUtxo), +// "Signing failed with the wrong error type" +// ); + +// // restore the witness_utxo +// psbt.inputs[0].witness_utxo = witness_utxo; + +// let result = wallet.sign( +// &mut psbt, +// SignOptions { +// allow_all_sighashes: true, +// ..Default::default() +// }, +// ); + +// assert!(result.is_ok(), "Signing should have worked"); +// assert!( +// result.unwrap(), +// "Should finalize the input since we can produce signatures" +// ); + +// let extracted = psbt.extract_tx().expect("failed to extract tx"); +// assert_eq!( +// *extracted.input[0].witness.to_vec()[0].last().unwrap(), +// sighash as u8, +// "The signature should have been made with the right sighash" +// ); +// } + +// #[test] +// fn test_spend_coinbase() { +// let (desc, change_desc) = get_test_wpkh_and_change_desc(); +// let mut wallet = Wallet::create(desc, change_desc) +// .network(Network::Regtest) +// .create_wallet_no_persist() +// .unwrap(); + +// let confirmation_height = 5; +// let confirmation_block_id = BlockId { +// height: confirmation_height, +// hash: BlockHash::all_zeros(), +// }; +// insert_checkpoint(&mut wallet, confirmation_block_id); +// let coinbase_tx = Transaction { +// version: transaction::Version::ONE, +// lock_time: absolute::LockTime::ZERO, +// input: vec![TxIn { +// previous_output: OutPoint::null(), +// ..Default::default() +// }], +// output: vec![TxOut { +// script_pubkey: wallet +// .next_unused_address(KeychainKind::External) +// .script_pubkey(), +// value: Amount::from_sat(25_000), +// }], +// }; +// let txid = coinbase_tx.compute_txid(); + +// let anchor = ConfirmationBlockTime { +// block_id: confirmation_block_id, +// confirmation_time: 30_000, +// }; + +// // Insert coinbase transaction into the local view, and also simulates confirming tx by +// applying // the update with an `anchor`. +// let mut tx_update = bdk_chain::TxUpdate::default(); +// tx_update.txs = vec![Arc::new(coinbase_tx)]; +// tx_update.anchors = [(anchor, txid)].into(); +// wallet +// .apply_update(Update { +// tx_update, +// ..Default::default() +// }) +// .unwrap(); + +// // NOTE: A transaction spending an output coming from the coinbase tx at height h, is +// eligible // to be included in block h + [100 = COINBASE_MATURITY] or higher. +// // Tx elibible to be included in the next block will be accepted in the mempool, used in +// block // templates and relayed on the network. +// // Miners may include such tx in a block when their chaintip is at h + [99 = +// COINBASE_MATURITY - // 1]. This means these coins are available for selection at height h + +// 99. // // By https://bitcoin.stackexchange.com/a/119017 let not_yet_mature_time = +// confirmation_height + COINBASE_MATURITY - 2; let maturity_time = confirmation_height + +// COINBASE_MATURITY - 1; + +// let balance = wallet.balance(); +// assert_eq!( +// balance, +// Balance { +// immature: Amount::from_sat(25_000), +// trusted_pending: Amount::ZERO, +// untrusted_pending: Amount::ZERO, +// confirmed: Amount::ZERO +// } +// ); + +// // We try to create a transaction, only to notice that all +// // our funds are unspendable +// let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), balance.immature / 2) +// .current_height(confirmation_height); +// assert!(matches!( +// builder.finish(), +// Err(CreateTxError::CoinSelection( +// coin_selection::InsufficientFunds { +// needed: _, +// available: Amount::ZERO +// } +// )) +// )); + +// // Still unspendable... +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), balance.immature / 2) +// .current_height(not_yet_mature_time); +// assert_matches!( +// builder.finish(), +// Err(CreateTxError::CoinSelection( +// coin_selection::InsufficientFunds { +// needed: _, +// available: Amount::ZERO +// } +// )) +// ); + +// insert_checkpoint( +// &mut wallet, +// BlockId { +// height: maturity_time, +// hash: BlockHash::all_zeros(), +// }, +// ); +// let balance = wallet.balance(); +// assert_eq!( +// balance, +// Balance { +// immature: Amount::ZERO, +// trusted_pending: Amount::ZERO, +// untrusted_pending: Amount::ZERO, +// confirmed: Amount::from_sat(25_000) +// } +// ); +// let mut builder = wallet.build_tx(); +// builder +// .add_recipient(addr.script_pubkey(), balance.confirmed / 2) +// .current_height(maturity_time); +// builder.finish().unwrap(); +// } + +// #[test] +// fn test_allow_dust_limit() { +// let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); + +// let addr = wallet.next_unused_address(KeychainKind::External); + +// let mut builder = wallet.build_tx(); + +// builder.add_recipient(addr.script_pubkey(), Amount::ZERO); + +// assert_matches!( +// builder.finish(), +// Err(CreateTxError::OutputBelowDustLimit(0)) +// ); + +// let mut builder = wallet.build_tx(); + +// builder +// .allow_dust(true) +// .add_recipient(addr.script_pubkey(), Amount::ZERO); + +// assert!(builder.finish().is_ok()); +// } + +// #[test] +// fn test_fee_rate_sign_no_grinding_high_r() { +// // Our goal is to obtain a transaction with a signature with high-R (71 bytes +// // instead of 70). We then check that our fee rate and fee calculation is +// // alright. +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); +// let mut builder = wallet.build_tx(); +// let mut data = PushBytesBuf::try_from(vec![0]).unwrap(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_rate(fee_rate) +// .add_data(&data); +// let mut psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); +// let (op_return_vout, _) = psbt +// .unsigned_tx +// .output +// .iter() +// .enumerate() +// .find(|(_n, i)| i.script_pubkey.is_op_return()) +// .unwrap(); + +// let mut sig_len: usize = 0; +// // We try to sign many different times until we find a longer signature (71 bytes) +// while sig_len < 71 { +// // Changing the OP_RETURN data will make the signature change (but not the fee, until +// // data[0] is small enough) +// data.as_mut_bytes()[0] += 1; +// psbt.unsigned_tx.output[op_return_vout].script_pubkey = ScriptBuf::new_op_return(&data); +// // Clearing the previous signature +// psbt.inputs[0].partial_sigs.clear(); +// // Signing +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// try_finalize: false, +// allow_grinding: false, +// ..Default::default() +// }, +// ) +// .unwrap(); +// // We only have one key in the partial_sigs map, this is a trick to retrieve it +// let key = psbt.inputs[0].partial_sigs.keys().next().unwrap(); +// sig_len = psbt.inputs[0].partial_sigs[key] +// .signature +// .serialize_der() +// .len(); +// } +// // Actually finalizing the transaction... +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// allow_grinding: false, +// ..Default::default() +// }, +// ) +// .unwrap(); +// // ...and checking that everything is fine +// assert_fee_rate!(psbt, fee, fee_rate); +// } + +// #[test] +// fn test_fee_rate_sign_grinding_low_r() { +// // Our goal is to obtain a transaction with a signature with low-R (70 bytes) +// // by setting the `allow_grinding` signing option as true. +// // We then check that our fee rate and fee calculation is alright and that our +// // signature is 70 bytes. +// let (mut wallet, _) = +// get_funded_wallet_single(" +// wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/ +// *)"); let addr = wallet.next_unused_address(KeychainKind::External); +// let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); +// let mut builder = wallet.build_tx(); +// builder +// .drain_to(addr.script_pubkey()) +// .drain_wallet() +// .fee_rate(fee_rate); +// let mut psbt = builder.finish().unwrap(); +// let fee = check_fee!(wallet, psbt); + +// wallet +// .sign( +// &mut psbt, +// SignOptions { +// try_finalize: false, +// allow_grinding: true, +// ..Default::default() +// }, +// ) +// .unwrap(); + +// let key = psbt.inputs[0].partial_sigs.keys().next().unwrap(); +// let sig_len = psbt.inputs[0].partial_sigs[key] +// .signature +// .serialize_der() +// .len(); +// assert_eq!(sig_len, 70); +// assert_fee_rate!(psbt, fee, fee_rate); +// } + +// #[test] +// fn test_taproot_load_descriptor_duplicated_keys() { +// // Added after issue https://github.com/bitcoindevkit/bdk/issues/760 +// // +// // Having the same key in multiple taproot leaves is safe and should be accepted by BDK + +// let (wallet, _) = get_funded_wallet_single(get_test_tr_dup_keys()); +// let addr = wallet.peek_address(KeychainKind::External, 0); + +// assert_eq!( +// addr.to_string(), +// "bcrt1pvysh4nmh85ysrkpwtrr8q8gdadhgdejpy6f9v424a8v9htjxjhyqw9c5s5" +// ); +// } + +// /// In dev mode this test panics, but in release mode, or if the `debug_panic` in +// /// `TxOutIndex::replenish_inner_index` is commented out, there is no panic and the balance is +// /// calculated correctly. See issue [#1483] and PR [#1486] for discussion on mixing non-wildcard +// and /// wildcard descriptors. +// /// +// /// [#1483]: https://github.com/bitcoindevkit/bdk/issues/1483 +// /// [#1486]: https://github.com/bitcoindevkit/bdk/pull/1486 +// #[test] +// #[cfg(debug_assertions)] +// #[should_panic( +// expected = "replenish lookahead: must not have existing spk: keychain=Internal, lookahead=25, +// next_index=0" )] +// fn test_keychains_with_overlapping_spks() { +// // this can happen if a non-wildcard descriptor keychain derives an spk that a +// // wildcard descriptor keychain in the same wallet also derives. + +// // index 1 spk overlaps with non-wildcard change descriptor +// let wildcard_keychain = +// "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/ +// *)"; let non_wildcard_keychain = +// "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/ +// 1)"; + +// let (mut wallet, _) = get_funded_wallet(wildcard_keychain, non_wildcard_keychain); +// assert_eq!(wallet.balance().confirmed, Amount::from_sat(50000)); + +// let addr = wallet +// .reveal_addresses_to(KeychainKind::External, 1) +// .last() +// .unwrap() +// .address; +// let anchor = ConfirmationBlockTime { +// block_id: BlockId { +// height: 2000, +// hash: BlockHash::all_zeros(), +// }, +// confirmation_time: 0, +// }; +// let _outpoint = receive_output_to_address(&mut wallet, addr, Amount::from_sat(8000), anchor); +// assert_eq!(wallet.balance().confirmed, Amount::from_sat(58000)); +// } + +// #[test] +// /// The wallet should re-use previously allocated change addresses when the tx using them is +// /// cancelled +// fn test_tx_cancellation() { +// macro_rules! new_tx { +// ($wallet:expr) => {{ +// let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") +// .unwrap() +// .assume_checked(); +// let mut builder = $wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)); + +// let psbt = builder.finish().unwrap(); + +// psbt +// }}; // } -#[test] -fn test_taproot_no_key_spend() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - - assert!( - wallet - .sign( - &mut psbt, - SignOptions { - sign_with_tap_internal_key: false, - ..Default::default() - }, - ) - .unwrap(), - "Unable to finalize tx" - ); - - assert!(psbt.inputs.iter().all(|i| i.tap_key_sig.is_none())); -} - -#[test] -fn test_taproot_script_spend() { - let (wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree()); - test_spend_from_wallet(wallet); - - let (wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_xprv()); - test_spend_from_wallet(wallet); -} - -#[test] -fn test_taproot_script_spend_sign_all_leaves() { - use bdk_wallet::signer::TapLeavesOptions; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - - assert!( - wallet - .sign( - &mut psbt, - SignOptions { - tap_leaves_options: TapLeavesOptions::All, - ..Default::default() - }, - ) - .unwrap(), - "Unable to finalize tx" - ); - - assert!(psbt - .inputs - .iter() - .all(|i| i.tap_script_sigs.len() == i.tap_scripts.len())); -} - -#[test] -fn test_taproot_script_spend_sign_include_some_leaves() { - use bdk_wallet::signer::TapLeavesOptions; - use bitcoin::taproot::TapLeafHash; - - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - let mut script_leaves: Vec<_> = psbt.inputs[0] - .tap_scripts - .clone() - .values() - .map(|(script, version)| TapLeafHash::from_script(script, *version)) - .collect(); - let included_script_leaves = vec![script_leaves.pop().unwrap()]; - let excluded_script_leaves = script_leaves; - - assert!( - wallet - .sign( - &mut psbt, - SignOptions { - tap_leaves_options: TapLeavesOptions::Include(included_script_leaves.clone()), - ..Default::default() - }, - ) - .unwrap(), - "Unable to finalize tx" - ); - - assert!(psbt.inputs[0] - .tap_script_sigs - .iter() - .all(|s| included_script_leaves.contains(&s.0 .1) - && !excluded_script_leaves.contains(&s.0 .1))); -} - -#[test] -fn test_taproot_script_spend_sign_exclude_some_leaves() { - use bdk_wallet::signer::TapLeavesOptions; - use bitcoin::taproot::TapLeafHash; - - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - let mut script_leaves: Vec<_> = psbt.inputs[0] - .tap_scripts - .clone() - .values() - .map(|(script, version)| TapLeafHash::from_script(script, *version)) - .collect(); - let included_script_leaves = [script_leaves.pop().unwrap()]; - let excluded_script_leaves = script_leaves; - - assert!( - wallet - .sign( - &mut psbt, - SignOptions { - tap_leaves_options: TapLeavesOptions::Exclude(excluded_script_leaves.clone()), - ..Default::default() - }, - ) - .unwrap(), - "Unable to finalize tx" - ); - - assert!(psbt.inputs[0] - .tap_script_sigs - .iter() - .all(|s| included_script_leaves.contains(&s.0 .1) - && !excluded_script_leaves.contains(&s.0 .1))); -} - -#[test] -fn test_taproot_script_spend_sign_no_leaves() { - use bdk_wallet::signer::TapLeavesOptions; - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_with_taptree_both_priv()); - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - - wallet - .sign( - &mut psbt, - SignOptions { - tap_leaves_options: TapLeavesOptions::None, - ..Default::default() - }, - ) - .unwrap(); - - assert!(psbt.inputs.iter().all(|i| i.tap_script_sigs.is_empty())); -} - -#[test] -fn test_taproot_sign_derive_index_from_psbt() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig_xprv()); - - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(25_000)); - let mut psbt = builder.finish().unwrap(); - - // re-create the wallet with an empty db - let wallet_empty = Wallet::create(get_test_tr_single_sig_xprv(), get_test_tr_single_sig()) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - - // signing with an empty db means that we will only look at the psbt to infer the - // derivation index - assert!( - wallet_empty.sign(&mut psbt, Default::default()).unwrap(), - "Unable to finalize tx" - ); -} - -#[test] -fn test_taproot_sign_explicit_sighash_all() { - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .sighash(TapSighashType::All.into()) - .drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let result = wallet.sign(&mut psbt, Default::default()); - assert!( - result.is_ok(), - "Signing should work because SIGHASH_ALL is safe" - ) -} - -#[test] -fn test_taproot_sign_non_default_sighash() { - let sighash = TapSighashType::NonePlusAnyoneCanPay; - - let (mut wallet, _) = get_funded_wallet_single(get_test_tr_single_sig()); - let addr = wallet.next_unused_address(KeychainKind::External); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .sighash(sighash.into()) - .drain_wallet(); - let mut psbt = builder.finish().unwrap(); - - let witness_utxo = psbt.inputs[0].witness_utxo.take(); - - let result = wallet.sign(&mut psbt, Default::default()); - assert!( - result.is_err(), - "Signing should have failed because the TX uses non-standard sighashes" - ); - assert_matches!( - result, - Err(SignerError::NonStandardSighash), - "Signing failed with the wrong error type" - ); - - // try again after opting-in - let result = wallet.sign( - &mut psbt, - SignOptions { - allow_all_sighashes: true, - ..Default::default() - }, - ); - assert!( - result.is_err(), - "Signing should have failed because the witness_utxo is missing" - ); - assert_matches!( - result, - Err(SignerError::MissingWitnessUtxo), - "Signing failed with the wrong error type" - ); - - // restore the witness_utxo - psbt.inputs[0].witness_utxo = witness_utxo; - - let result = wallet.sign( - &mut psbt, - SignOptions { - allow_all_sighashes: true, - ..Default::default() - }, - ); - - assert!(result.is_ok(), "Signing should have worked"); - assert!( - result.unwrap(), - "Should finalize the input since we can produce signatures" - ); - - let extracted = psbt.extract_tx().expect("failed to extract tx"); - assert_eq!( - *extracted.input[0].witness.to_vec()[0].last().unwrap(), - sighash as u8, - "The signature should have been made with the right sighash" - ); -} - -#[test] -fn test_spend_coinbase() { - let (desc, change_desc) = get_test_wpkh_and_change_desc(); - let mut wallet = Wallet::create(desc, change_desc) - .network(Network::Regtest) - .create_wallet_no_persist() - .unwrap(); - - let confirmation_height = 5; - let confirmation_block_id = BlockId { - height: confirmation_height, - hash: BlockHash::all_zeros(), - }; - insert_checkpoint(&mut wallet, confirmation_block_id); - let coinbase_tx = Transaction { - version: transaction::Version::ONE, - lock_time: absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint::null(), - ..Default::default() - }], - output: vec![TxOut { - script_pubkey: wallet - .next_unused_address(KeychainKind::External) - .script_pubkey(), - value: Amount::from_sat(25_000), - }], - }; - let txid = coinbase_tx.compute_txid(); - - let anchor = ConfirmationBlockTime { - block_id: confirmation_block_id, - confirmation_time: 30_000, - }; - - // Insert coinbase transaction into the local view, and also simulates confirming tx by applying - // the update with an `anchor`. - let mut tx_update = bdk_chain::TxUpdate::default(); - tx_update.txs = vec![Arc::new(coinbase_tx)]; - tx_update.anchors = [(anchor, txid)].into(); - wallet - .apply_update(Update { - tx_update, - ..Default::default() - }) - .unwrap(); - - // NOTE: A transaction spending an output coming from the coinbase tx at height h, is eligible - // to be included in block h + [100 = COINBASE_MATURITY] or higher. - // Tx elibible to be included in the next block will be accepted in the mempool, used in block - // templates and relayed on the network. - // Miners may include such tx in a block when their chaintip is at h + [99 = COINBASE_MATURITY - - // 1]. This means these coins are available for selection at height h + 99. - // - // By https://bitcoin.stackexchange.com/a/119017 - let not_yet_mature_time = confirmation_height + COINBASE_MATURITY - 2; - let maturity_time = confirmation_height + COINBASE_MATURITY - 1; - - let balance = wallet.balance(); - assert_eq!( - balance, - Balance { - immature: Amount::from_sat(25_000), - trusted_pending: Amount::ZERO, - untrusted_pending: Amount::ZERO, - confirmed: Amount::ZERO - } - ); - - // We try to create a transaction, only to notice that all - // our funds are unspendable - let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), balance.immature / 2) - .current_height(confirmation_height); - assert!(matches!( - builder.finish(), - Err(CreateTxError::CoinSelection( - coin_selection::InsufficientFunds { - needed: _, - available: Amount::ZERO - } - )) - )); - - // Still unspendable... - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), balance.immature / 2) - .current_height(not_yet_mature_time); - assert_matches!( - builder.finish(), - Err(CreateTxError::CoinSelection( - coin_selection::InsufficientFunds { - needed: _, - available: Amount::ZERO - } - )) - ); - - insert_checkpoint( - &mut wallet, - BlockId { - height: maturity_time, - hash: BlockHash::all_zeros(), - }, - ); - let balance = wallet.balance(); - assert_eq!( - balance, - Balance { - immature: Amount::ZERO, - trusted_pending: Amount::ZERO, - untrusted_pending: Amount::ZERO, - confirmed: Amount::from_sat(25_000) - } - ); - let mut builder = wallet.build_tx(); - builder - .add_recipient(addr.script_pubkey(), balance.confirmed / 2) - .current_height(maturity_time); - builder.finish().unwrap(); -} - -#[test] -fn test_allow_dust_limit() { - let (mut wallet, _) = get_funded_wallet_single(get_test_single_sig_cltv()); - - let addr = wallet.next_unused_address(KeychainKind::External); - - let mut builder = wallet.build_tx(); - - builder.add_recipient(addr.script_pubkey(), Amount::ZERO); - - assert_matches!( - builder.finish(), - Err(CreateTxError::OutputBelowDustLimit(0)) - ); - - let mut builder = wallet.build_tx(); - - builder - .allow_dust(true) - .add_recipient(addr.script_pubkey(), Amount::ZERO); - - assert!(builder.finish().is_ok()); -} - -#[test] -fn test_fee_rate_sign_no_grinding_high_r() { - // Our goal is to obtain a transaction with a signature with high-R (71 bytes - // instead of 70). We then check that our fee rate and fee calculation is - // alright. - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); - let mut builder = wallet.build_tx(); - let mut data = PushBytesBuf::try_from(vec![0]).unwrap(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_rate(fee_rate) - .add_data(&data); - let mut psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - let (op_return_vout, _) = psbt - .unsigned_tx - .output - .iter() - .enumerate() - .find(|(_n, i)| i.script_pubkey.is_op_return()) - .unwrap(); - - let mut sig_len: usize = 0; - // We try to sign many different times until we find a longer signature (71 bytes) - while sig_len < 71 { - // Changing the OP_RETURN data will make the signature change (but not the fee, until - // data[0] is small enough) - data.as_mut_bytes()[0] += 1; - psbt.unsigned_tx.output[op_return_vout].script_pubkey = ScriptBuf::new_op_return(&data); - // Clearing the previous signature - psbt.inputs[0].partial_sigs.clear(); - // Signing - wallet - .sign( - &mut psbt, - SignOptions { - try_finalize: false, - allow_grinding: false, - ..Default::default() - }, - ) - .unwrap(); - // We only have one key in the partial_sigs map, this is a trick to retrieve it - let key = psbt.inputs[0].partial_sigs.keys().next().unwrap(); - sig_len = psbt.inputs[0].partial_sigs[key] - .signature - .serialize_der() - .len(); - } - // Actually finalizing the transaction... - wallet - .sign( - &mut psbt, - SignOptions { - allow_grinding: false, - ..Default::default() - }, - ) - .unwrap(); - // ...and checking that everything is fine - assert_fee_rate!(psbt, fee, fee_rate); -} - -#[test] -fn test_fee_rate_sign_grinding_low_r() { - // Our goal is to obtain a transaction with a signature with low-R (70 bytes) - // by setting the `allow_grinding` signing option as true. - // We then check that our fee rate and fee calculation is alright and that our - // signature is 70 bytes. - let (mut wallet, _) = get_funded_wallet_single("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); - let addr = wallet.next_unused_address(KeychainKind::External); - let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); - let mut builder = wallet.build_tx(); - builder - .drain_to(addr.script_pubkey()) - .drain_wallet() - .fee_rate(fee_rate); - let mut psbt = builder.finish().unwrap(); - let fee = check_fee!(wallet, psbt); - - wallet - .sign( - &mut psbt, - SignOptions { - try_finalize: false, - allow_grinding: true, - ..Default::default() - }, - ) - .unwrap(); - - let key = psbt.inputs[0].partial_sigs.keys().next().unwrap(); - let sig_len = psbt.inputs[0].partial_sigs[key] - .signature - .serialize_der() - .len(); - assert_eq!(sig_len, 70); - assert_fee_rate!(psbt, fee, fee_rate); -} - -#[test] -fn test_taproot_load_descriptor_duplicated_keys() { - // Added after issue https://github.com/bitcoindevkit/bdk/issues/760 - // - // Having the same key in multiple taproot leaves is safe and should be accepted by BDK - - let (wallet, _) = get_funded_wallet_single(get_test_tr_dup_keys()); - let addr = wallet.peek_address(KeychainKind::External, 0); - - assert_eq!( - addr.to_string(), - "bcrt1pvysh4nmh85ysrkpwtrr8q8gdadhgdejpy6f9v424a8v9htjxjhyqw9c5s5" - ); -} - -/// In dev mode this test panics, but in release mode, or if the `debug_panic` in -/// `TxOutIndex::replenish_inner_index` is commented out, there is no panic and the balance is -/// calculated correctly. See issue [#1483] and PR [#1486] for discussion on mixing non-wildcard and -/// wildcard descriptors. -/// -/// [#1483]: https://github.com/bitcoindevkit/bdk/issues/1483 -/// [#1486]: https://github.com/bitcoindevkit/bdk/pull/1486 -#[test] -#[cfg(debug_assertions)] -#[should_panic( - expected = "replenish lookahead: must not have existing spk: keychain=Internal, lookahead=25, next_index=0" -)] -fn test_keychains_with_overlapping_spks() { - // this can happen if a non-wildcard descriptor keychain derives an spk that a - // wildcard descriptor keychain in the same wallet also derives. - - // index 1 spk overlaps with non-wildcard change descriptor - let wildcard_keychain = "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"; - let non_wildcard_keychain = "wpkh(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/1)"; - - let (mut wallet, _) = get_funded_wallet(wildcard_keychain, non_wildcard_keychain); - assert_eq!(wallet.balance().confirmed, Amount::from_sat(50000)); - - let addr = wallet - .reveal_addresses_to(KeychainKind::External, 1) - .last() - .unwrap() - .address; - let anchor = ConfirmationBlockTime { - block_id: BlockId { - height: 2000, - hash: BlockHash::all_zeros(), - }, - confirmation_time: 0, - }; - let _outpoint = receive_output_to_address(&mut wallet, addr, Amount::from_sat(8000), anchor); - assert_eq!(wallet.balance().confirmed, Amount::from_sat(58000)); -} - -#[test] -/// The wallet should re-use previously allocated change addresses when the tx using them is -/// cancelled -fn test_tx_cancellation() { - macro_rules! new_tx { - ($wallet:expr) => {{ - let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") - .unwrap() - .assume_checked(); - let mut builder = $wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), Amount::from_sat(10_000)); - - let psbt = builder.finish().unwrap(); - - psbt - }}; - } - - let (mut wallet, _) = get_funded_wallet(get_test_wpkh(), get_test_tr_single_sig_xprv()); - - let psbt1 = new_tx!(wallet); - let change_derivation_1 = psbt1 - .unsigned_tx - .output - .iter() - .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) - .unwrap(); - assert_eq!(change_derivation_1, (KeychainKind::Internal, 0)); - - let psbt2 = new_tx!(wallet); - - let change_derivation_2 = psbt2 - .unsigned_tx - .output - .iter() - .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) - .unwrap(); - assert_eq!(change_derivation_2, (KeychainKind::Internal, 1)); - - wallet.cancel_tx(&psbt1.extract_tx().expect("failed to extract tx")); - - let psbt3 = new_tx!(wallet); - let change_derivation_3 = psbt3 - .unsigned_tx - .output - .iter() - .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) - .unwrap(); - assert_eq!(change_derivation_3, (KeychainKind::Internal, 0)); - - let psbt3 = new_tx!(wallet); - let change_derivation_3 = psbt3 - .unsigned_tx - .output - .iter() - .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) - .unwrap(); - assert_eq!(change_derivation_3, (KeychainKind::Internal, 2)); - - wallet.cancel_tx(&psbt3.extract_tx().expect("failed to extract tx")); - - let psbt3 = new_tx!(wallet); - let change_derivation_4 = psbt3 - .unsigned_tx - .output - .iter() - .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) - .unwrap(); - assert_eq!(change_derivation_4, (KeychainKind::Internal, 2)); -} - -#[test] -fn test_thread_safety() { - fn thread_safe() {} - thread_safe::(); // compiles only if true - thread_safe::>(); -} - -#[test] -fn single_descriptor_wallet_can_create_tx_and_receive_change() { - // create single descriptor wallet and fund it - let mut wallet = Wallet::create_single(get_test_tr_single_sig_xprv()) - .network(Network::Testnet) - .create_wallet_no_persist() - .unwrap(); - assert_eq!(wallet.keychains().count(), 1); - let amount = Amount::from_sat(5_000); - receive_output(&mut wallet, amount * 2, ReceiveTo::Mempool(2)); - // create spend tx that produces a change output - let addr = Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm") - .unwrap() - .assume_checked(); - let mut builder = wallet.build_tx(); - builder.add_recipient(addr.script_pubkey(), amount); - let mut psbt = builder.finish().unwrap(); - assert!(wallet.sign(&mut psbt, SignOptions::default()).unwrap()); - let tx = psbt.extract_tx().unwrap(); - let _txid = tx.compute_txid(); - insert_tx(&mut wallet, tx); - let unspent: Vec<_> = wallet.list_unspent().collect(); - assert_eq!(unspent.len(), 1); - let utxo = unspent.first().unwrap(); - assert!(utxo.txout.value < amount); - assert_eq!( - utxo.keychain, - KeychainKind::External, - "tx change should go to external keychain" - ); -} - -#[test] -fn test_transactions_sort_by() { - let (mut wallet, _txid) = get_funded_wallet_wpkh(); - receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); - - // sort by chain position, unconfirmed then confirmed by descending block height - let sorted_txs: Vec = - wallet.transactions_sort_by(|t1, t2| t2.chain_position.cmp(&t1.chain_position)); - let conf_heights: Vec> = sorted_txs - .iter() - .map(|tx| tx.chain_position.confirmation_height_upper_bound()) - .collect(); - assert_eq!([None, Some(2000), Some(1000)], conf_heights.as_slice()); -} - -#[test] -fn test_tx_builder_is_send_safe() { - let (mut wallet, _txid) = get_funded_wallet_wpkh(); - let _box: Box = Box::new(wallet.build_tx()); -} - -#[test] -fn test_wallet_transactions_relevant() { - let (mut test_wallet, _txid) = get_funded_wallet_wpkh(); - let relevant_tx_count_before = test_wallet.transactions().count(); - let full_tx_count_before = test_wallet.tx_graph().full_txs().count(); - let chain_tip = test_wallet.local_chain().tip().block_id(); - let canonical_tx_count_before = test_wallet - .tx_graph() - .list_canonical_txs( - test_wallet.local_chain(), - chain_tip, - CanonicalizationParams::default(), - ) - .count(); - - // add not relevant transaction to test wallet - let (other_external_desc, other_internal_desc) = get_test_tr_single_sig_xprv_and_change_desc(); - let (other_wallet, other_txid) = get_funded_wallet(other_internal_desc, other_external_desc); - let test_wallet_update = Update { - tx_update: other_wallet.tx_graph().clone().into(), - ..Default::default() - }; - test_wallet.apply_update(test_wallet_update).unwrap(); - - // verify transaction from other wallet was added but is not in relevant transactions list. - let relevant_tx_count_after = test_wallet.transactions().count(); - let full_tx_count_after = test_wallet.tx_graph().full_txs().count(); - let canonical_tx_count_after = test_wallet - .tx_graph() - .list_canonical_txs( - test_wallet.local_chain(), - chain_tip, - CanonicalizationParams::default(), - ) - .count(); - - assert_eq!(relevant_tx_count_before, relevant_tx_count_after); - assert!(!test_wallet - .transactions() - .any(|wallet_tx| wallet_tx.tx_node.txid == other_txid)); - assert!(test_wallet - .tx_graph() - .list_canonical_txs( - test_wallet.local_chain(), - chain_tip, - CanonicalizationParams::default() - ) - .any(|wallet_tx| wallet_tx.tx_node.txid == other_txid)); - assert!(full_tx_count_before < full_tx_count_after); - assert!(canonical_tx_count_before < canonical_tx_count_after); -} - -#[test] -fn test_tx_details_method() { - let (test_wallet, txid_1) = get_funded_wallet_wpkh(); - let tx_details_1_option = test_wallet.tx_details(txid_1); - - assert!(tx_details_1_option.is_some()); - let tx_details_1 = tx_details_1_option.unwrap(); - - assert_eq!( - tx_details_1.txid.to_string(), - "f2a03cdfe1bb6a295b0a4bb4385ca42f95e4b2c6d9a7a59355d32911f957a5b3" - ); - assert_eq!(tx_details_1.received, Amount::from_sat(50000)); - assert_eq!(tx_details_1.sent, Amount::from_sat(76000)); - assert_eq!(tx_details_1.fee.unwrap(), Amount::from_sat(1000)); - assert_eq!(tx_details_1.balance_delta, SignedAmount::from_sat(-26000)); - - // Transaction id not part of the TxGraph - let txid_2 = Txid::from_raw_hash(Hash::all_zeros()); - let tx_details_2_option = test_wallet.tx_details(txid_2); - assert!(tx_details_2_option.is_none()); -} - -#[test] -fn test_tx_ordering_untouched_preserves_insertion_ordering() { - let (mut wallet, txid) = get_funded_wallet_wpkh(); - let script_pubkey = wallet - .next_unused_address(KeychainKind::External) - .address - .script_pubkey(); - let tx1 = Transaction { - input: vec![TxIn { - previous_output: OutPoint { txid, vout: 0 }, - ..Default::default() - }], - output: vec![ - TxOut { - value: Amount::from_sat(500), - script_pubkey: script_pubkey.clone(), - }; - 4 - ], - ..new_tx(0) - }; - - insert_tx(&mut wallet, tx1); - let utxos = wallet - .list_unspent() - .map(|o| o.outpoint) - .take(2) - .collect::>(); - - let mut builder = wallet.build_tx(); - builder - .ordering(bdk_wallet::TxOrdering::Untouched) - .add_utxos(&utxos) - .unwrap() - .add_recipient(script_pubkey.clone(), Amount::from_sat(400)) - .add_recipient(script_pubkey.clone(), Amount::from_sat(300)) - .add_recipient(script_pubkey.clone(), Amount::from_sat(500)); - - let tx = builder.finish().unwrap().unsigned_tx; - let txins = tx - .input - .iter() - .take(2) // First two UTxOs should be manually selected and sorted by insertion - .map(|txin| txin.previous_output) - .collect::>(); - - assert!(txins == utxos); - - let txouts = tx - .output - .iter() - .take(3) // Exclude possible change output - .map(|txout| txout.value.to_sat()) - .collect::>(); - - // Check vout is sorted by recipient insertion order - assert!(txouts == vec![400, 300, 500]); -} +// let (mut wallet, _) = get_funded_wallet(get_test_wpkh(), get_test_tr_single_sig_xprv()); + +// let psbt1 = new_tx!(wallet); +// let change_derivation_1 = psbt1 +// .unsigned_tx +// .output +// .iter() +// .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) +// .unwrap(); +// assert_eq!(change_derivation_1, (KeychainKind::Internal, 0)); + +// let psbt2 = new_tx!(wallet); + +// let change_derivation_2 = psbt2 +// .unsigned_tx +// .output +// .iter() +// .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) +// .unwrap(); +// assert_eq!(change_derivation_2, (KeychainKind::Internal, 1)); + +// wallet.cancel_tx(&psbt1.extract_tx().expect("failed to extract tx")); + +// let psbt3 = new_tx!(wallet); +// let change_derivation_3 = psbt3 +// .unsigned_tx +// .output +// .iter() +// .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) +// .unwrap(); +// assert_eq!(change_derivation_3, (KeychainKind::Internal, 0)); + +// let psbt3 = new_tx!(wallet); +// let change_derivation_3 = psbt3 +// .unsigned_tx +// .output +// .iter() +// .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) +// .unwrap(); +// assert_eq!(change_derivation_3, (KeychainKind::Internal, 2)); + +// wallet.cancel_tx(&psbt3.extract_tx().expect("failed to extract tx")); + +// let psbt3 = new_tx!(wallet); +// let change_derivation_4 = psbt3 +// .unsigned_tx +// .output +// .iter() +// .find_map(|txout| wallet.derivation_of_spk(txout.script_pubkey.clone())) +// .unwrap(); +// assert_eq!(change_derivation_4, (KeychainKind::Internal, 2)); +// } + +// #[test] +// fn test_thread_safety() { +// fn thread_safe() {} +// thread_safe::(); // compiles only if true +// thread_safe::>(); +// } + +// #[test] +// fn single_descriptor_wallet_can_create_tx_and_receive_change() { +// // create single descriptor wallet and fund it +// let mut wallet = Wallet::create_single(get_test_tr_single_sig_xprv()) +// .network(Network::Testnet) +// .create_wallet_no_persist() +// .unwrap(); +// assert_eq!(wallet.keychains().count(), 1); +// let amount = Amount::from_sat(5_000); +// receive_output(&mut wallet, amount * 2, ReceiveTo::Mempool(2)); +// // create spend tx that produces a change output +// let addr = Address::from_str("bcrt1qc6fweuf4xjvz4x3gx3t9e0fh4hvqyu2qw4wvxm") +// .unwrap() +// .assume_checked(); +// let mut builder = wallet.build_tx(); +// builder.add_recipient(addr.script_pubkey(), amount); +// let mut psbt = builder.finish().unwrap(); +// assert!(wallet.sign(&mut psbt, SignOptions::default()).unwrap()); +// let tx = psbt.extract_tx().unwrap(); +// let _txid = tx.compute_txid(); +// insert_tx(&mut wallet, tx); +// let unspent: Vec<_> = wallet.list_unspent().collect(); +// assert_eq!(unspent.len(), 1); +// let utxo = unspent.first().unwrap(); +// assert!(utxo.txout.value < amount); +// assert_eq!( +// utxo.keychain, +// KeychainKind::External, +// "tx change should go to external keychain" +// ); +// } + +// #[test] +// fn test_transactions_sort_by() { +// let (mut wallet, _txid) = get_funded_wallet_wpkh(); +// receive_output(&mut wallet, Amount::from_sat(25_000), ReceiveTo::Mempool(0)); + +// // sort by chain position, unconfirmed then confirmed by descending block height +// let sorted_txs: Vec = +// wallet.transactions_sort_by(|t1, t2| t2.chain_position.cmp(&t1.chain_position)); +// let conf_heights: Vec> = sorted_txs +// .iter() +// .map(|tx| tx.chain_position.confirmation_height_upper_bound()) +// .collect(); +// assert_eq!([None, Some(2000), Some(1000)], conf_heights.as_slice()); +// } + +// #[test] +// fn test_tx_builder_is_send_safe() { +// let (mut wallet, _txid) = get_funded_wallet_wpkh(); +// let _box: Box = Box::new(wallet.build_tx()); +// } + +// #[test] +// fn test_wallet_transactions_relevant() { +// let (mut test_wallet, _txid) = get_funded_wallet_wpkh(); +// let relevant_tx_count_before = test_wallet.transactions().count(); +// let full_tx_count_before = test_wallet.tx_graph().full_txs().count(); +// let chain_tip = test_wallet.local_chain().tip().block_id(); +// let canonical_tx_count_before = test_wallet +// .tx_graph() +// .list_canonical_txs( +// test_wallet.local_chain(), +// chain_tip, +// CanonicalizationParams::default(), +// ) +// .count(); + +// // add not relevant transaction to test wallet +// let (other_external_desc, other_internal_desc) = +// get_test_tr_single_sig_xprv_and_change_desc(); let (other_wallet, other_txid) = +// get_funded_wallet(other_internal_desc, other_external_desc); let test_wallet_update = Update +// { tx_update: other_wallet.tx_graph().clone().into(), +// ..Default::default() +// }; +// test_wallet.apply_update(test_wallet_update).unwrap(); + +// // verify transaction from other wallet was added but is not in relevant transactions list. +// let relevant_tx_count_after = test_wallet.transactions().count(); +// let full_tx_count_after = test_wallet.tx_graph().full_txs().count(); +// let canonical_tx_count_after = test_wallet +// .tx_graph() +// .list_canonical_txs( +// test_wallet.local_chain(), +// chain_tip, +// CanonicalizationParams::default(), +// ) +// .count(); + +// assert_eq!(relevant_tx_count_before, relevant_tx_count_after); +// assert!(!test_wallet +// .transactions() +// .any(|wallet_tx| wallet_tx.tx_node.txid == other_txid)); +// assert!(test_wallet +// .tx_graph() +// .list_canonical_txs( +// test_wallet.local_chain(), +// chain_tip, +// CanonicalizationParams::default() +// ) +// .any(|wallet_tx| wallet_tx.tx_node.txid == other_txid)); +// assert!(full_tx_count_before < full_tx_count_after); +// assert!(canonical_tx_count_before < canonical_tx_count_after); +// } + +// #[test] +// fn test_tx_details_method() { +// let (test_wallet, txid_1) = get_funded_wallet_wpkh(); +// let tx_details_1_option = test_wallet.tx_details(txid_1); + +// assert!(tx_details_1_option.is_some()); +// let tx_details_1 = tx_details_1_option.unwrap(); + +// assert_eq!( +// tx_details_1.txid.to_string(), +// "f2a03cdfe1bb6a295b0a4bb4385ca42f95e4b2c6d9a7a59355d32911f957a5b3" +// ); +// assert_eq!(tx_details_1.received, Amount::from_sat(50000)); +// assert_eq!(tx_details_1.sent, Amount::from_sat(76000)); +// assert_eq!(tx_details_1.fee.unwrap(), Amount::from_sat(1000)); +// assert_eq!(tx_details_1.balance_delta, SignedAmount::from_sat(-26000)); + +// // Transaction id not part of the TxGraph +// let txid_2 = Txid::from_raw_hash(Hash::all_zeros()); +// let tx_details_2_option = test_wallet.tx_details(txid_2); +// assert!(tx_details_2_option.is_none()); +// } + +// #[test] +// fn test_tx_ordering_untouched_preserves_insertion_ordering() { +// let (mut wallet, txid) = get_funded_wallet_wpkh(); +// let script_pubkey = wallet +// .next_unused_address(KeychainKind::External) +// .address +// .script_pubkey(); +// let tx1 = Transaction { +// input: vec![TxIn { +// previous_output: OutPoint { txid, vout: 0 }, +// ..Default::default() +// }], +// output: vec![ +// TxOut { +// value: Amount::from_sat(500), +// script_pubkey: script_pubkey.clone(), +// }; +// 4 +// ], +// ..new_tx(0) +// }; + +// insert_tx(&mut wallet, tx1); +// let utxos = wallet +// .list_unspent() +// .map(|o| o.outpoint) +// .take(2) +// .collect::>(); + +// let mut builder = wallet.build_tx(); +// builder +// .ordering(bdk_wallet::TxOrdering::Untouched) +// .add_utxos(&utxos) +// .unwrap() +// .add_recipient(script_pubkey.clone(), Amount::from_sat(400)) +// .add_recipient(script_pubkey.clone(), Amount::from_sat(300)) +// .add_recipient(script_pubkey.clone(), Amount::from_sat(500)); + +// let tx = builder.finish().unwrap().unsigned_tx; +// let txins = tx +// .input +// .iter() +// .take(2) // First two UTxOs should be manually selected and sorted by insertion +// .map(|txin| txin.previous_output) +// .collect::>(); + +// assert!(txins == utxos); + +// let txouts = tx +// .output +// .iter() +// .take(3) // Exclude possible change output +// .map(|txout| txout.value.to_sat()) +// .collect::>(); + +// // Check vout is sorted by recipient insertion order +// assert!(txouts == vec![400, 300, 500]); +// } From 2af1d582dd487d1139dd1002221286ec6f58a5a7 Mon Sep 17 00:00:00 2001 From: codingp110 Date: Mon, 29 Sep 2025 09:38:15 +0530 Subject: [PATCH 05/10] feat!: modify Wallet struct to hold a `KeyRing` Modified the `ChangeSet` accordingly. --- src/wallet/changeset.rs | 69 ++++++++++++++++++----------------------- src/wallet/mod.rs | 37 +++++++++++----------- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/wallet/changeset.rs b/src/wallet/changeset.rs index b1498484..e62edd2f 100644 --- a/src/wallet/changeset.rs +++ b/src/wallet/changeset.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; type IndexedTxGraphChangeSet = indexed_tx_graph::ChangeSet; +use crate::keyring; + /// A change set for [`Wallet`] /// /// ## Definition @@ -101,14 +103,10 @@ type IndexedTxGraphChangeSet = /// [`WalletPersister`]: crate::WalletPersister /// [`Wallet::staged`]: crate::Wallet::staged /// [`Wallet`]: crate::Wallet -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] -pub struct ChangeSet { - /// Descriptor for recipient addresses. - pub descriptor: Option>, - /// Descriptor for change addresses. - pub change_descriptor: Option>, - /// Stores the network type of the transaction data. - pub network: Option, +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct ChangeSet { + /// Stores the `KeyRing` containing the network and descriptor data. + pub keyring: keyring::changeset::ChangeSet, /// Changes to the [`LocalChain`](local_chain::LocalChain). pub local_chain: local_chain::ChangeSet, /// Changes to [`TxGraph`](tx_graph::TxGraph). @@ -117,41 +115,34 @@ pub struct ChangeSet { pub indexer: keychain_txout::ChangeSet, } -impl Merge for ChangeSet { - /// Merge another [`ChangeSet`] into itself. - fn merge(&mut self, other: Self) { - if other.descriptor.is_some() { - debug_assert!( - self.descriptor.is_none() || self.descriptor == other.descriptor, - "descriptor must never change" - ); - self.descriptor = other.descriptor; - } - if other.change_descriptor.is_some() { - debug_assert!( - self.change_descriptor.is_none() - || self.change_descriptor == other.change_descriptor, - "change descriptor must never change" - ); - self.change_descriptor = other.change_descriptor; - } - if other.network.is_some() { - debug_assert!( - self.network.is_none() || self.network == other.network, - "network must never change" - ); - self.network = other.network; +impl Default for ChangeSet +where + K: Ord, +{ + fn default() -> Self { + Self { + keyring: Default::default(), + local_chain: Default::default(), + tx_graph: Default::default(), + indexer: Default::default(), } + } +} +impl Merge for ChangeSet +where + K: Ord, +{ + /// Merge another [`ChangeSet`] into itself. + fn merge(&mut self, other: Self) { + Merge::merge(&mut self.keyring, other.keyring); Merge::merge(&mut self.local_chain, other.local_chain); Merge::merge(&mut self.tx_graph, other.tx_graph); Merge::merge(&mut self.indexer, other.indexer); } fn is_empty(&self) -> bool { - self.descriptor.is_none() - && self.change_descriptor.is_none() - && self.network.is_none() + self.keyring.is_empty() && self.local_chain.is_empty() && self.tx_graph.is_empty() && self.indexer.is_empty() @@ -276,7 +267,7 @@ impl Merge for ChangeSet { // } // } -impl From for ChangeSet { +impl From for ChangeSet { fn from(chain: local_chain::ChangeSet) -> Self { Self { local_chain: chain, @@ -285,7 +276,7 @@ impl From for ChangeSet { } } -impl From for ChangeSet { +impl From for ChangeSet { fn from(indexed_tx_graph: IndexedTxGraphChangeSet) -> Self { Self { tx_graph: indexed_tx_graph.tx_graph, @@ -295,7 +286,7 @@ impl From for ChangeSet { } } -impl From> for ChangeSet { +impl From> for ChangeSet { fn from(tx_graph: tx_graph::ChangeSet) -> Self { Self { tx_graph, @@ -304,7 +295,7 @@ impl From> for ChangeSet { } } -impl From for ChangeSet { +impl From for ChangeSet { fn from(indexer: keychain_txout::ChangeSet) -> Self { Self { indexer, diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index c0cef61e..aa1f1d7f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -22,14 +22,13 @@ use alloc::{ use core::{cmp::Ordering, fmt, mem, ops::Deref}; use bdk_chain::{ - indexed_tx_graph, - indexer::keychain_txout::KeychainTxOutIndex, + indexer::keychain_txout::{self, KeychainTxOutIndex, DEFAULT_LOOKAHEAD}, local_chain::{ApplyHeaderError, CannotConnectError, CheckPoint, CheckPointIter, LocalChain}, spk_client::{ FullScanRequest, FullScanRequestBuilder, FullScanResponse, SyncRequest, SyncRequestBuilder, SyncResponse, }, - tx_graph::{CalculateFeeError, CanonicalTx, TxGraph, TxUpdate}, + tx_graph::{self, CalculateFeeError, CanonicalTx, TxGraph, TxUpdate}, BlockId, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, DescriptorExt, FullTxOut, Indexed, IndexedTxGraph, Indexer, Merge, }; @@ -59,12 +58,12 @@ pub mod signer; pub mod tx_builder; pub(crate) mod utils; -use crate::collections::{BTreeMap, HashMap, HashSet}; use crate::descriptor::{ check_wallet_descriptor, error::Error as DescriptorError, policy::BuildSatisfaction, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils, }; +use crate::keyring::KeyRing; use crate::psbt::PsbtUtils; use crate::types::*; use crate::wallet::{ @@ -74,6 +73,10 @@ use crate::wallet::{ // tx_builder::{FeePolicy, TxBuilder, TxParams}, utils::{check_nsequence_rbf, After, Older, SecpCtx}, }; +use crate::{ + collections::{BTreeMap, HashMap, HashSet}, + keyring, +}; // re-exports pub use bdk_chain::Balance; @@ -83,32 +86,28 @@ pub use persisted::*; pub use utils::IsDust; pub use utils::TxDetails; +type KeychainTxGraph = IndexedTxGraph>; + /// A Bitcoin wallet /// /// The `Wallet` acts as a way of coherently interfacing with output descriptors and related -/// transactions. Its main components are: -/// -/// 1. output *descriptors* from which it can derive addresses. -/// 2. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors. +/// transactions. Its main component is a [`KeyRing`] which holds the network and output +/// descriptors. /// /// The user is responsible for loading and writing wallet changes which are represented as /// [`ChangeSet`]s (see [`take_staged`]). Also see individual functions and example for instructions /// on when [`Wallet`] state needs to be persisted. /// -/// The `Wallet` descriptor (external) and change descriptor (internal) must not derive the same -/// script pubkeys. See [`KeychainTxOutIndex::insert_descriptor()`] for more details. +/// The `Wallet` descriptors must not derive the same script pubkeys. +/// See [`KeychainTxOutIndex::insert_descriptor()`] for more details. /// -/// [`signer`]: crate::signer /// [`take_staged`]: Wallet::take_staged #[derive(Debug)] -pub struct Wallet { - signers: Arc, - change_signers: Arc, +pub struct Wallet { + keyring: KeyRing, chain: LocalChain, - indexed_graph: IndexedTxGraph>, - stage: ChangeSet, - network: Network, - secp: SecpCtx, + tx_graph: KeychainTxGraph, + stage: ChangeSet, } /// An update to [`Wallet`]. @@ -633,7 +632,7 @@ pub type WalletTx<'a> = CanonicalTx<'a, Arc, ConfirmationBlockTime> // let (exp_desc, _) = make_desc(&secp, // network).map_err(LoadError::Descriptor)?; return // Err(LoadError::Mismatch(LoadMismatch::Descriptor { keychain: -// KeychainKind::Internal, loaded: None, +// KeychainKind::Internal, loaded: None, // expected: Some(exp_desc), // })); // } From 8d94a221315519ebe0c5eaf0141019d0901cd698 Mon Sep 17 00:00:00 2001 From: codingp110 Date: Mon, 29 Sep 2025 17:18:38 +0530 Subject: [PATCH 06/10] feat: Add a basic constructor for `Wallet` --- src/wallet/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index aa1f1d7f..d2cd4344 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -312,6 +312,49 @@ impl std::error::Error for ApplyBlockError {} /// A `CanonicalTx` managed by a `Wallet`. pub type WalletTx<'a> = CanonicalTx<'a, Arc, ConfirmationBlockTime>; +impl Wallet +where + K: Clone + fmt::Debug + Ord, +{ + /// Construct a new [`Wallet`] with the given `keyring`. + /// + /// Note: The network must be either the mainnet or one of the test networks. Also default value + /// of lookahead is used with no spk_cache. + pub fn new(mut keyring: KeyRing) -> Self { + let network = keyring.network; + + let genesis_hash = bitcoin::constants::genesis_block(network).block_hash(); + let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash); + + let keyring_changeset = keyring.initial_changeset(); + + let mut index = KeychainTxOutIndex::new(DEFAULT_LOOKAHEAD, false); + let descriptors = core::mem::take(&mut keyring.descriptors); + for (keychain, desc) in descriptors { + let _inserted = index + .insert_descriptor(keychain, desc) + .expect("err: failed to insert descriptor"); + assert!(_inserted); + } + + let tx_graph = KeychainTxGraph::new(index); + + let stage = ChangeSet { + keyring: keyring_changeset, + local_chain: chain_changeset, + tx_graph: bdk_chain::tx_graph::ChangeSet::default(), + indexer: bdk_chain::keychain_txout::ChangeSet::default(), + }; + + Self { + keyring, + chain, + tx_graph, + stage, + } + } +} + // impl Wallet { // /// Build a new single descriptor [`Wallet`]. // /// From 01dc8ff71b0aa903846248e8aabfa934c14841db Mon Sep 17 00:00:00 2001 From: codingp110 Date: Mon, 29 Sep 2025 21:43:42 +0530 Subject: [PATCH 07/10] feat!: modify AddressInfo, add reveal_next_address Also added default version of `reveal_next_address`. --- src/wallet/mod.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 4 deletions(-) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index d2cd4344..afadffc6 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -149,16 +149,16 @@ impl From for Update { /// A derived address and the index it was found at. /// For convenience this automatically derefs to `Address` #[derive(Debug, Clone, PartialEq, Eq)] -pub struct AddressInfo { +pub struct AddressInfo { /// Child index of this address pub index: u32, /// Address pub address: Address, /// Type of keychain - pub keychain: KeychainKind, + pub keychain: K, } -impl Deref for AddressInfo { +impl Deref for AddressInfo { type Target = Address; fn deref(&self) -> &Self::Target { @@ -166,7 +166,7 @@ impl Deref for AddressInfo { } } -impl fmt::Display for AddressInfo { +impl fmt::Display for AddressInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.address) } @@ -353,6 +353,77 @@ where stage, } } + + /// Reveal the next address of the default `keychain`. + /// + /// This is equivalent to calling [`Self::reveal_next_address`] with the default `keychain` as + /// argument. Note: This is not fallible as the default keychain must always exist! + /// + /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more + /// calls to this method before closing the wallet. For example: + // TODO: Fix the following example: + // /// + // /// ```rust,no_run + // /// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind}; + // /// use bdk_chain::rusqlite::Connection; + // /// let mut conn = Connection::open_in_memory().expect("must open connection"); + // /// let mut wallet = LoadParams::new() + // /// .load_wallet(&mut conn) + // /// .expect("database is okay") + // /// .expect("database has data"); + // /// let next_address = wallet.reveal_next_address(KeychainKind::External); + // /// wallet.persist(&mut conn).expect("write is okay"); + // /// + // /// // Now it's safe to show the user their next address! + // /// println!("Next address: {}", next_address.address); + // /// # Ok::<(), anyhow::Error>(()) + // /// ``` + pub fn reveal_next_default_address(&mut self) -> AddressInfo { + self.reveal_next_address(self.keyring.default_keychain()) + .expect("default keychain must always exist!") + } + + /// Attempt to reveal the next address of the given `keychain`. + /// + /// This will increment the keychain's derivation index. If the keychain's descriptor doesn't + /// contain a wildcard or every address is already revealed up to the maximum derivation + /// index defined in [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), + /// then the last revealed address will be returned. + /// + /// **WARNING**: To avoid address reuse you must persist the changes resulting from one or more + /// calls to this method before closing the wallet. For example: + // TODO: Fix the following example: + // /// + // /// ```rust,no_run + // /// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind}; + // /// use bdk_chain::rusqlite::Connection; + // /// let mut conn = Connection::open_in_memory().expect("must open connection"); + // /// let mut wallet = LoadParams::new() + // /// .load_wallet(&mut conn) + // /// .expect("database is okay") + // /// .expect("database has data"); + // /// let next_address = wallet.reveal_next_address(KeychainKind::External); + // /// wallet.persist(&mut conn).expect("write is okay"); + // /// + // /// // Now it's safe to show the user their next address! + // /// println!("Next address: {}", next_address.address); + // /// # Ok::<(), anyhow::Error>(()) + // /// ``` + pub fn reveal_next_address(&mut self, keychain: K) -> Option> { + let index = &mut self.tx_graph.index; + let stage = &mut self.stage; + + let ((index, spk), index_changeset) = index.reveal_next_spk(keychain.clone())?; + + stage.merge(index_changeset.into()); + + Some(AddressInfo { + index, + address: Address::from_script(spk.as_script(), self.keyring.network) + .expect("must have address form"), + keychain, + }) + } } // impl Wallet { @@ -2861,6 +2932,82 @@ mod test { // use crate::miniscript::Error::Unexpected; // use crate::test_utils::get_test_tr_single_sig_xprv_and_change_desc; // use crate::test_utils::insert_tx; + use bdk_chain::DescriptorId; + use core::str::FromStr; + use miniscript::{Descriptor, DescriptorPublicKey}; + + const DESCRIPTORS: [&str; 6] = [ + "wpkh(tpubDCzuCBKnZA5TNKhiJnASku7kq8Q4iqcVF82JV7mHo2NxWpXkLRbrJaGA5ToE7LCuWpcPErBbpDzbdWKN8aTdJzmRy1jQPmZvnqpwwDwCdy7/1/*)", + "wpkh(tpubDCzuCBKnZA5TNKhiJnASku7kq8Q4iqcVF82JV7mHo2NxWpXkLRbrJaGA5ToE7LCuWpcPErBbpDzbdWKN8aTdJzmRy1jQPmZvnqpwwDwCdy7/2/*)", + "tr(tpubDCzuCBKnZA5TNKhiJnASku7kq8Q4iqcVF82JV7mHo2NxWpXkLRbrJaGA5ToE7LCuWpcPErBbpDzbdWKN8aTdJzmRy1jQPmZvnqpwwDwCdy7/3/*)", + "tr(tpubDCzuCBKnZA5TNKhiJnASku7kq8Q4iqcVF82JV7mHo2NxWpXkLRbrJaGA5ToE7LCuWpcPErBbpDzbdWKN8aTdJzmRy1jQPmZvnqpwwDwCdy7/4/*)", + "pkh(tpubDCzuCBKnZA5TNKhiJnASku7kq8Q4iqcVF82JV7mHo2NxWpXkLRbrJaGA5ToE7LCuWpcPErBbpDzbdWKN8aTdJzmRy1jQPmZvnqpwwDwCdy7/5/*)", + "pkh(tpubDCzuCBKnZA5TNKhiJnASku7kq8Q4iqcVF82JV7mHo2NxWpXkLRbrJaGA5ToE7LCuWpcPErBbpDzbdWKN8aTdJzmRy1jQPmZvnqpwwDwCdy7/6/*)"]; + + /// Parse a descriptor string + fn parse_descriptor(s: &str) -> Descriptor { + Descriptor::parse_descriptor(&Secp256k1::new(), s) + .expect("failed to parse descriptor") + .0 + } + + fn test_keyring(desc_strs: impl IntoIterator) -> KeyRing { + let mut desc_strs = desc_strs.into_iter(); + let desc = parse_descriptor(desc_strs.next().unwrap()); + let mut keyring = KeyRing::new(Network::Testnet4, desc.descriptor_id(), desc); + for desc_str in desc_strs { + let desc = parse_descriptor(desc_str); + keyring.add_descriptor(desc.descriptor_id(), desc, false); + } + keyring + } + + #[test] + fn correct_address_is_revealed() { + let mut wallet = Wallet::new(test_keyring(DESCRIPTORS)); + let addrinfo = wallet.reveal_next_default_address(); + assert_eq!( + addrinfo.address.into_unchecked(), + Address::from_str("tb1qat8h88r778d8e0x38t2ljtzmgkjusgn2u38avs").unwrap() + ); + let addrinfo = wallet + .reveal_next_address(parse_descriptor(DESCRIPTORS[1]).descriptor_id()) + .unwrap(); + assert_eq!( + addrinfo.address.into_unchecked(), + Address::from_str("tb1qun8txyd3p4xgts6y6lj8h2dcxk20s487ll7ss3").unwrap() + ); + let addrinfo = wallet + .reveal_next_address(parse_descriptor(DESCRIPTORS[2]).descriptor_id()) + .unwrap(); + assert_eq!( + addrinfo.address.into_unchecked(), + Address::from_str("tb1pnz3jex4wnz88e46rfzckpd9xyvdde8h2hnes4wrllkhygump8c2se9rusg") + .unwrap() + ); + let addrinfo = wallet + .reveal_next_address(parse_descriptor(DESCRIPTORS[3]).descriptor_id()) + .unwrap(); + assert_eq!( + addrinfo.address.into_unchecked(), + Address::from_str("tb1pv6hmnghp0wtxzeqsvshdq4ennvmqg3eq78vluvzvfkqtmtd5e49q8zht5v") + .unwrap() + ); + let addrinfo = wallet + .reveal_next_address(parse_descriptor(DESCRIPTORS[4]).descriptor_id()) + .unwrap(); + assert_eq!( + addrinfo.address.into_unchecked(), + Address::from_str("n3TJoFpLPBMGisVYHUGcEBwd9d1FVBwbJQ").unwrap() + ); + let addrinfo = wallet + .reveal_next_address(parse_descriptor(DESCRIPTORS[5]).descriptor_id()) + .unwrap(); + assert_eq!( + addrinfo.address.into_unchecked(), + Address::from_str("mq2r39CD8ZnMqyuytQq2zPa1sfHTT6Rjo8").unwrap() + ); + } // #[test] // fn not_duplicated_utxos_across_optional_and_required() { From 4a3486ef0d7ef6de20537bdb64a6c82e056d5c59 Mon Sep 17 00:00:00 2001 From: codingp110 Date: Tue, 30 Sep 2025 00:06:33 +0530 Subject: [PATCH 08/10] feat: add `Wallet::with_custom_params` API So that advanced users can specify custom genesis_hash and lookahead or enable spk_cache. --- src/wallet/mod.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index afadffc6..f76b3d2f 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -354,6 +354,50 @@ where } } + /// Construct a new [`Wallet`] with the given `keyring`, `genesis_hash` and `lookahead`. + /// + /// The `genesis_hash` (if not specified) will be inferred from `keyring.network` and + /// `DEFAULT_LOOKAHEAD` will be used in case `lookahead` is not specified. + pub fn with_custom_params( + mut keyring: KeyRing, + genesis_hash: Option, + lookahead: Option, + use_spk_cache: bool, + ) -> Self { + let network = keyring.network; + let genesis_inferred = bitcoin::constants::genesis_block(network).block_hash(); + + let (chain, chain_changeset) = + LocalChain::from_genesis_hash(genesis_hash.unwrap_or(genesis_inferred)); + + let mut index = + KeychainTxOutIndex::new(lookahead.unwrap_or(DEFAULT_LOOKAHEAD), use_spk_cache); + + let descriptors = core::mem::take(&mut keyring.descriptors); + for (keychain, desc) in descriptors { + let _inserted = index + .insert_descriptor(keychain, desc) + .expect("err: failed to insert descriptor"); + assert!(_inserted); + } + + let tx_graph = KeychainTxGraph::new(index); + + let stage = ChangeSet { + keyring: keyring.initial_changeset(), + local_chain: chain_changeset, + tx_graph: bdk_chain::tx_graph::ChangeSet::default(), + indexer: bdk_chain::keychain_txout::ChangeSet::default(), + }; + + Self { + keyring, + chain, + tx_graph, + stage, + } + } + /// Reveal the next address of the default `keychain`. /// /// This is equivalent to calling [`Self::reveal_next_address`] with the default `keychain` as From 29fd25caa72f440d726879e08d4050e46f864b0f Mon Sep 17 00:00:00 2001 From: codingp110 Date: Sat, 4 Oct 2025 10:56:52 +0530 Subject: [PATCH 09/10] docs: add `KeychainKind` based example Also added a folder to consolidate example utilities. --- Cargo.toml | 3 +++ examples/example_utils/mod.rs | 21 +++++++++++++++++++++ examples/simple_keyring.rs | 22 ++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 examples/example_utils/mod.rs create mode 100644 examples/simple_keyring.rs diff --git a/Cargo.toml b/Cargo.toml index cb08925c..eec62968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,3 +74,6 @@ name = "esplora_blocking" [[example]] name = "bitcoind_rpc" + +[[example]] +name = "simple_keyring" diff --git a/examples/example_utils/mod.rs b/examples/example_utils/mod.rs new file mode 100644 index 00000000..aafbf5a6 --- /dev/null +++ b/examples/example_utils/mod.rs @@ -0,0 +1,21 @@ +#![allow(dead_code)] + +use bitcoin::secp256k1::Secp256k1; +use miniscript::{Descriptor, DescriptorPublicKey}; + +pub const DESCRIPTORS: [&str; 8] = [ + "pkh([21a559b8/44h/1h/0h]tpubDD2jQhEsAU9uU6Qec2M6k5ygyLS96cCa9iwpQrGAS7p6GRW7eVjCiy1WGwTxqCPiACg99A4vUCZWs5w3xgHgTohxYu2z1ZzhLrLaFHG8at1/0/*)#8t439hyk", + "pkh([21a559b8/44h/1h/0h]tpubDD2jQhEsAU9uU6Qec2M6k5ygyLS96cCa9iwpQrGAS7p6GRW7eVjCiy1WGwTxqCPiACg99A4vUCZWs5w3xgHgTohxYu2z1ZzhLrLaFHG8at1/1/*)#klsscz5w", + "sh(wpkh([21a559b8/49h/1h/0h]tpubDCpBeRAYSsoF8LkbeSa1joNjNPMgTk8jttvydCXTxDwKHJsQab4pr6NeHn11fTKBjHE7NmHrUjEgH3mkQZrQ89qy9tLpZhpMc9w6NvkKkFL/0/*))#5adnu2fe", + "sh(wpkh([21a559b8/49h/1h/0h]tpubDCpBeRAYSsoF8LkbeSa1joNjNPMgTk8jttvydCXTxDwKHJsQab4pr6NeHn11fTKBjHE7NmHrUjEgH3mkQZrQ89qy9tLpZhpMc9w6NvkKkFL/1/*))#pur9y4ux", + "tr([21a559b8/86h/1h/0h]tpubDCdJ64usD8SEL17thc2K43vG4zHNMdgvnRWNvD3j2bEGCh2ER4KjJN3zhhD54AkBuc5k695JsgkTtdVaARepr2kNxv83zKP5Ra6CsUqoPuu/0/*)#2sd7vzr2", + "tr([21a559b8/86h/1h/0h]tpubDCdJ64usD8SEL17thc2K43vG4zHNMdgvnRWNvD3j2bEGCh2ER4KjJN3zhhD54AkBuc5k695JsgkTtdVaARepr2kNxv83zKP5Ra6CsUqoPuu/1/*)#mygl3hnj", + "wpkh([21a559b8/84h/1h/0h]tpubDCWfvMa9sCes3z6nxkF7ox5kc6gjddkkJJHTJKfnj96uRGXDWY9WbpNxupEYREV4kHij4JUBYzp9ziM7qhvsUjcisQHaSQgya39nDvuimkF/0/*)#7gmvfd4d", + "wpkh([21a559b8/84h/1h/0h]tpubDCWfvMa9sCes3z6nxkF7ox5kc6gjddkkJJHTJKfnj96uRGXDWY9WbpNxupEYREV4kHij4JUBYzp9ziM7qhvsUjcisQHaSQgya39nDvuimkF/1/*)#0u7d5c94" +]; + +pub fn get_descriptor(desc_str: &str) -> Descriptor { + Descriptor::parse_descriptor(&Secp256k1::new(), desc_str) + .unwrap() + .0 +} diff --git a/examples/simple_keyring.rs b/examples/simple_keyring.rs new file mode 100644 index 00000000..b71f7010 --- /dev/null +++ b/examples/simple_keyring.rs @@ -0,0 +1,22 @@ +use bdk_wallet::keyring::KeyRing; +use bdk_wallet::{KeychainKind, Wallet}; +use bitcoin::Network; + +static EXTERNAL_DESCRIPTOR: &str = "tr([5bc5d243/86'/1'/0']tpubDC72NVP1RK5qwy2QdEfWphDsUBAfBu7oiV6jEFooHP8tGQGFVUeFxhgZxuk1j6EQRJ1YsS3th2RyDgReRqCL4zqp4jtuV2z7gbiqDH2iyUS/0/*)"; +static INTERNAL_DESCRIPTOR: &str = "tr([5bc5d243/86'/1'/0']tpubDC72NVP1RK5qwy2QdEfWphDsUBAfBu7oiV6jEFooHP8tGQGFVUeFxhgZxuk1j6EQRJ1YsS3th2RyDgReRqCL4zqp4jtuV2z7gbiqDH2iyUS/1/*)"; + +// Simple KeyRing, allowing us to build a standard 2-descriptor wallet with receive and change +// keychains. + +fn main() { + let mut keyring: KeyRing = KeyRing::new( + Network::Regtest, + KeychainKind::External, + EXTERNAL_DESCRIPTOR, + ); + keyring.add_descriptor(KeychainKind::Internal, INTERNAL_DESCRIPTOR, false); + + let mut wallet = Wallet::new(keyring); + let address = wallet.reveal_next_address(KeychainKind::External).unwrap(); + println!("Address at index {}: {}", address.index, address.address) +} From 16d6f8cbfd16455c40bac97338921b799dbb2fd7 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Tue, 7 Oct 2025 09:02:39 -0400 Subject: [PATCH 10/10] docs: add complex multi-keychain example --- examples/multi_keychain_wallet.rs | 127 ++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 examples/multi_keychain_wallet.rs diff --git a/examples/multi_keychain_wallet.rs b/examples/multi_keychain_wallet.rs new file mode 100644 index 00000000..cbf25606 --- /dev/null +++ b/examples/multi_keychain_wallet.rs @@ -0,0 +1,127 @@ +use bdk_wallet::keyring::KeyRing; +use bdk_wallet::Wallet; +use bitcoin::Network; +use miniscript::descriptor::DescriptorType; + +// The KeyRing holds a map of keychain identifiers (`K`) to public descriptors. These keychain +// identifiers can be simple (something like the `DescriptorId` or the `KeychainKind` types work +// well, e.g. see the `simple_keyring.rs` example), but they can also be more complex if required by +// the application. This example shows how the keychain identifier can be used to carry metadata +// about the descriptors, which could be used to select which keychain to use in different scenarios +// when calling methods like `Wallet::reveal_next_address`. + +// In this example, Johnny has a lot of keychains he keeps track of in his wallet. The map of +// KeychainIdentifier -> Descriptor uses keys that are custom-made at the application layer, and +// useful for its business logic (for example, choose a weekend keychain when Johnny is out partying +// on the weekend). + +const DESC_1: &str = "tr([5bc5d243/86'/1'/0']tpubDC72NVP1RK5qwy2QdEfWphDsUBAfBu7oiV6jEFooHP8tGQGFVUeFxhgZxuk1j6EQRJ1YsS3th2RyDgReRqCL4zqp4jtuV2z7gbiqDH2iyUS/0/*)#xh44xwsp"; +const DESC_2: &str = "wpkh([5bc5d243/84'/1'/0']tpubDCA4DcMLVSDifbfUxyJaVVAx57ztsVjke6DRYF95jFFgJqvzA9oENovVd7n34NNURmZxFNRB1VLGyDEqxvaZNXie3ZroEGFbeTS2xLYuaN1/0/*)#q8afsa3z"; +const DESC_3: &str = "pkh([5bc5d243/44'/1'/0']tpubDDNQtvd8Sg1mXtSGtxRWEcgg7PbPwUSAyAmBonDSL3HLuutthe54Yih4XDYcywVdcduwqaQonpbTAGjjSh5kcLeCj5MTjYooa9ve2Npx6ho/1/*)#g73kgtdn"; +const DESC_4: &str = "tr([5bc5d243/86'/1'/0']tpubDC72NVP1RK5qwy2QdEfWphDsUBAfBu7oiV6jEFooHP8tGQGFVUeFxhgZxuk1j6EQRJ1YsS3th2RyDgReRqCL4zqp4jtuV2z7gbiqDH2iyUS/42/42)"; +const DESC_5: &str = "sh(wpkh([5bc5d243/49'/1'/0']tpubDDd6eupua2nhRp2egUAgYGjkxHeh5jPrBDaKLExeySwRvUb1hU7s8osoeACRhXs2w1UGZSMmEpZ1FkjYJ2Pxvfsy7w6XRqYYW7Vw89Unrzr/0/*))#svvvc6el"; + +fn main() { + let keychain_1 = KeychainId { + number: 1, + nickname: "Johnny's favorite keychain", + script_type: DescriptorType::Tr, + color: Color::Blue, + time_of_week_keychain: DayType::WeekDay, + }; + let keychain_2 = KeychainId { + number: 2, + nickname: "Johnny's party keychain", + script_type: DescriptorType::Wpkh, + color: Color::Green, + time_of_week_keychain: DayType::WeekEnd, + }; + let keychain_3 = KeychainId { + number: 3, + nickname: "Johnny's old P2PKH keychain", + script_type: DescriptorType::Pkh, + color: Color::Blue, + time_of_week_keychain: DayType::AnyDay, + }; + let keychain_4 = KeychainId { + number: 4, + nickname: "Johnny's project donations keychain", + script_type: DescriptorType::Tr, + color: Color::Yellow, + time_of_week_keychain: DayType::AnyDay, + }; + let keychain_5 = KeychainId { + number: 5, + nickname: "The secret keychain", + script_type: DescriptorType::ShWpkh, + color: Color::Blue, + time_of_week_keychain: DayType::AnyDay, + }; + + let mut keyring: KeyRing = KeyRing::new(Network::Signet, keychain_1, DESC_1); + keyring.add_descriptor(keychain_2, DESC_2, false); + keyring.add_descriptor(keychain_3, DESC_3, false); + keyring.add_descriptor(keychain_4, DESC_4, false); + keyring.add_descriptor(keychain_5, DESC_5, false); + + // DESC_1 is the default keychain (the first one added to the keyring is automatically the + // default keychain), but this can also be changed later on with the + // KeyRing::set_default_keychain API. This default keychain is useful because you can use + // APIs like `Wallet::reveal_next_default_address()` which will always work with your + // default keychain. + + let mut wallet = Wallet::new(keyring); + + let address1 = wallet.reveal_next_default_address(); + println!("Default keychain address: {}", address1.address); + + let party_address = wallet.reveal_next_address(keychain_2).unwrap(); + println!("Party address: {}", party_address.address); + + let donation_address = wallet.reveal_next_address(keychain_4).unwrap(); + println!("Donation address: {}", donation_address.address); +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +struct KeychainId { + number: u32, + nickname: &'static str, + script_type: DescriptorType, + color: Color, + time_of_week_keychain: DayType, +} + +impl PartialEq for KeychainId { + fn eq(&self, other: &Self) -> bool { + self.number == other.number + } +} + +impl Eq for KeychainId {} + +impl PartialOrd for KeychainId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for KeychainId { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.number.cmp(&other.number) + } +} + +#[derive(Debug, Clone, Copy)] +enum Color { + Blue, + Green, + Yellow, +} + +#[derive(Debug, Clone, Copy)] +enum DayType { + AnyDay, + WeekDay, + WeekEnd, +}