diff --git a/Cargo.toml b/Cargo.toml index fbde1ace..fee6ec16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,11 @@ resolver = "2" members = [ "wallet", - "examples/example_wallet_electrum", - "examples/example_wallet_esplora_blocking", - "examples/example_wallet_esplora_async", - "examples/example_wallet_rpc", + "examples/example_n_keychains", +# "examples/example_wallet_electrum", +# "examples/example_wallet_esplora_blocking", +# "examples/example_wallet_esplora_async", +# "examples/example_wallet_rpc", ] [workspace.package] diff --git a/examples/example_n_keychains/Cargo.toml b/examples/example_n_keychains/Cargo.toml new file mode 100644 index 00000000..07ed3149 --- /dev/null +++ b/examples/example_n_keychains/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "example_wallet_esplora_blocking" +version = "0.2.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bdk_wallet = { path = "../../wallet" } +bdk_esplora = { version = "0.20", features = ["blocking"] } +anyhow = "1" diff --git a/examples/example_n_keychains/src/main.rs b/examples/example_n_keychains/src/main.rs new file mode 100644 index 00000000..1bbdab4b --- /dev/null +++ b/examples/example_n_keychains/src/main.rs @@ -0,0 +1,53 @@ +use bdk_esplora::{esplora_client, EsploraExt}; +use bdk_wallet::chain::DescriptorId; +use bdk_wallet::Wallet; +use bdk_wallet::bitcoin::Network; +use bdk_wallet::KeychainKind; +use bdk_wallet::keyring::KeyRing; + +const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net"; +const STOP_GAP: usize = 5; +const PARALLEL_REQUESTS: usize = 5; + +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +const OTHER_DESC_21: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/21/*)"; +const OTHER_DESC_31: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/31/*)"; +const OTHER_DESC_41: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/41/*)"; + +fn main() -> Result<(), anyhow::Error> { + // Create a keyring with a single, default descriptor (aka the KeychainKind::External from the 1.2.0 API) + let mut keyring = KeyRing::new(EXTERNAL_DESC, Network::Signet); + + // Add 3 new custom descriptors + keyring.add_other_descriptor(OTHER_DESC_21); + keyring.add_other_descriptor(OTHER_DESC_31); + keyring.add_other_descriptor(OTHER_DESC_41); + + let keychain_ids: Vec = keyring.list_keychain_ids(); + println!("{:?}", keychain_ids); + + // Create the wallet and peek addresses on each of the descriptors + let mut wallet: Wallet = Wallet::create(keyring).create_wallet_no_persist()?; + let address_1 = wallet.peek_address(KeychainKind::Default, 0).unwrap(); + let address_2 = wallet.peek_address(KeychainKind::Other(keychain_ids[1]), 0).unwrap(); + let address_3 = wallet.peek_address(KeychainKind::Other(keychain_ids[2]), 0).unwrap(); + let address_4 = wallet.peek_address(KeychainKind::Other(keychain_ids[3]), 0).unwrap(); + + println!("Address 1 {:?} at index {:?} on keychain {:?}", address_1.address, address_1.index, address_1.keychain); + println!("Address 2 {:?} at index {:?} on keychain {:?}", address_2.address, address_2.index, address_2.keychain); + println!("Address 3 {:?} at index {:?} on keychain {:?}", address_3.address, address_3.index, address_3.keychain); + println!("Address 4 {:?} at index {:?} on keychain {:?}", address_4.address, address_4.index, address_4.keychain); + + let balance = wallet.balance(); + println!("Balance before sync {:?}", balance); + + let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking(); + let full_scan_request = wallet.start_full_scan().build(); + let update = client.full_scan(full_scan_request, STOP_GAP, PARALLEL_REQUESTS)?; + wallet.apply_update(update)?; + + let new_balance = wallet.balance(); + println!("Wallet balance after syncing: {}", new_balance.total()); + + Ok(()) +} diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index a23c1265..53c3b468 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bdk_wallet" homepage = "https://bitcoindevkit.org" -version = "2.0.0-alpha.0" +version = "1.3.0-alpha.0" repository = "https://github.com/bitcoindevkit/bdk_wallet" documentation = "https://docs.rs/bdk_wallet" description = "A modern, lightweight, descriptor-based wallet library" diff --git a/wallet/examples/n_keychains.rs b/wallet/examples/n_keychains.rs new file mode 100644 index 00000000..2249c8bc --- /dev/null +++ b/wallet/examples/n_keychains.rs @@ -0,0 +1,48 @@ +use bdk_chain::DescriptorId; +use bdk_wallet::Wallet; +use bdk_wallet::bitcoin::Network; +use bdk_wallet::KeychainKind; +use bdk_wallet::keyring::KeyRing; + +const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; +const OTHER_DESC_21: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/21/*)"; +const OTHER_DESC_31: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/33/*)"; +const OTHER_DESC_41: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/44/*)"; + +fn main() -> Result<(), anyhow::Error> { + // Create a keyring with a single, default descriptor (aka the KeychainKind::External from the 1.2.0 API) + let mut keyring = KeyRing::new(EXTERNAL_DESC, Network::Testnet); + + // Add 3 new custom descriptors + keyring.add_other_descriptor(OTHER_DESC_21); + keyring.add_other_descriptor(OTHER_DESC_31); + keyring.add_other_descriptor(OTHER_DESC_41); + + let keychain_ids: Vec = keyring.list_keychain_ids(); + println!("{:?}", keychain_ids); + + // Create the wallet and peek addresses on each of the descriptors + let mut wallet: Wallet = Wallet::create(keyring).create_wallet_no_persist()?; + let address_1 = wallet.peek_address(KeychainKind::Default, 0).unwrap(); + let address_2 = wallet.peek_address(KeychainKind::Other(keychain_ids[1]), 0).unwrap(); + let address_3 = wallet.peek_address(KeychainKind::Other(keychain_ids[2]), 0).unwrap(); + let address_4 = wallet.peek_address(KeychainKind::Other(keychain_ids[3]), 0).unwrap(); + + println!("Address 1 {:?} at index {:?} on keychain {:?}", address_1.address, address_1.index, address_1.keychain); + println!("Address 2 {:?} at index {:?} on keychain {:?}", address_2.address, address_2.index, address_2.keychain); + println!("Address 3 {:?} at index {:?} on keychain {:?}", address_3.address, address_3.index, address_3.keychain); + println!("Address 4 {:?} at index {:?} on keychain {:?}", address_4.address, address_4.index, address_4.keychain); + + let balance = wallet.balance(); + println!("Balance {:?}", balance); + + let revealed_address_1 = wallet.reveal_next_address(KeychainKind::Default); + let revealed_address_2 = wallet.reveal_next_address(KeychainKind::Default); + println!("Revealed next address {:?}", revealed_address_1); + println!("Revealed next address {:?}", revealed_address_2); + + // Will error out because there is no change keychain defined + // wallet.reveal_next_address(KeychainKind::Change).unwrap(); + + Ok(()) +} diff --git a/wallet/src/descriptor/template.rs b/wallet/src/descriptor/template.rs index ee9ec9ae..a8de5ef3 100644 --- a/wallet/src/descriptor/template.rs +++ b/wallet/src/descriptor/template.rs @@ -23,11 +23,17 @@ use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap}; use crate::descriptor::DescriptorError; use crate::keys::{DerivableKey, IntoDescriptorKey, ValidNetworks}; use crate::wallet::utils::SecpCtx; -use crate::{descriptor, KeychainKind}; +use crate::descriptor; /// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks); +#[derive(Clone, Debug)] +pub enum TemplateKeychainKind { + External, + Internal, +} + /// Trait for descriptor templates that can be built into a full descriptor /// /// Since [`IntoWalletDescriptor`] is implemented for any [`DescriptorTemplate`], they can also be @@ -232,7 +238,7 @@ impl> DescriptorTemplate for P2TR { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip44>(pub K, pub KeychainKind); +pub struct Bip44>(pub K, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip44 { fn build(self, network: Network) -> Result { @@ -271,7 +277,7 @@ impl> DescriptorTemplate for Bip44 { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); +pub struct Bip44Public>(pub K, pub bip32::Fingerprint, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip44Public { fn build(self, network: Network) -> Result { @@ -309,7 +315,7 @@ impl> DescriptorTemplate for Bip44Public { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip49>(pub K, pub KeychainKind); +pub struct Bip49>(pub K, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip49 { fn build(self, network: Network) -> Result { @@ -348,7 +354,7 @@ impl> DescriptorTemplate for Bip49 { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); +pub struct Bip49Public>(pub K, pub bip32::Fingerprint, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip49Public { fn build(self, network: Network) -> Result { @@ -386,7 +392,7 @@ impl> DescriptorTemplate for Bip49Public { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip84>(pub K, pub KeychainKind); +pub struct Bip84>(pub K, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip84 { fn build(self, network: Network) -> Result { @@ -425,7 +431,7 @@ impl> DescriptorTemplate for Bip84 { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); +pub struct Bip84Public>(pub K, pub bip32::Fingerprint, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip84Public { fn build(self, network: Network) -> Result { @@ -463,7 +469,7 @@ impl> DescriptorTemplate for Bip84Public { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip86>(pub K, pub KeychainKind); +pub struct Bip86>(pub K, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip86 { fn build(self, network: Network) -> Result { @@ -502,7 +508,7 @@ impl> DescriptorTemplate for Bip86 { /// # Ok::<_, Box>(()) /// ``` #[derive(Debug, Clone)] -pub struct Bip86Public>(pub K, pub bip32::Fingerprint, pub KeychainKind); +pub struct Bip86Public>(pub K, pub bip32::Fingerprint, pub TemplateKeychainKind); impl> DescriptorTemplate for Bip86Public { fn build(self, network: Network) -> Result { @@ -521,7 +527,7 @@ macro_rules! expand_make_bipxx { pub(super) fn make_bipxx_private>( bip: u32, key: K, - keychain: KeychainKind, + keychain: TemplateKeychainKind, network: Network, ) -> Result, DescriptorError> { let mut derivation_path = alloc::vec::Vec::with_capacity(4); @@ -538,10 +544,10 @@ macro_rules! expand_make_bipxx { derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?); match keychain { - KeychainKind::External => { + TemplateKeychainKind::External => { derivation_path.push(bip32::ChildNumber::from_normal_idx(0)?) } - KeychainKind::Internal => { + TemplateKeychainKind::Internal => { derivation_path.push(bip32::ChildNumber::from_normal_idx(1)?) } }; @@ -554,12 +560,12 @@ macro_rules! expand_make_bipxx { bip: u32, key: K, parent_fingerprint: bip32::Fingerprint, - keychain: KeychainKind, + keychain: TemplateKeychainKind, network: Network, ) -> Result, DescriptorError> { let derivation_path: bip32::DerivationPath = match keychain { - KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(), - KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(), + TemplateKeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(), + TemplateKeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(), }; let source_path = bip32::DerivationPath::from(vec![ diff --git a/wallet/src/test_utils.rs b/wallet/src/test_utils.rs index 4807dc5d..d8c2e4ee 100644 --- a/wallet/src/test_utils.rs +++ b/wallet/src/test_utils.rs @@ -17,122 +17,122 @@ use crate::{KeychainKind, Update, Wallet}; /// 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()) -} +// 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) -} +// 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) -} +// pub fn get_funded_wallet_wpkh() -> (Wallet, Txid) { +// let (desc, change_desc) = get_test_wpkh_and_change_desc(); +// get_funded_wallet(desc, change_desc) +// } /// `wpkh` single key descriptor pub fn get_test_wpkh() -> &'static str { @@ -246,29 +246,29 @@ 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: u64) -> 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, - }, - ) -} +// pub fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> 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: u64, - receive_to: impl Into, -) -> OutPoint { - let addr = wallet.next_unused_address(KeychainKind::External).address; - receive_output_to_address(wallet, addr, value, receive_to) -} +// pub fn receive_output( +// wallet: &mut Wallet, +// value: u64, +// 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( diff --git a/wallet/src/types.rs b/wallet/src/types.rs index 54ddfa26..776a69f5 100644 --- a/wallet/src/types.rs +++ b/wallet/src/types.rs @@ -10,40 +10,46 @@ // licenses. use alloc::boxed::Box; -use chain::{ChainPosition, ConfirmationBlockTime}; -use core::convert::AsRef; - +use chain::{ChainPosition, ConfirmationBlockTime, DescriptorId}; +use std::hash::{Hash, Hasher}; use bitcoin::transaction::{OutPoint, Sequence, TxOut}; -use bitcoin::{psbt, Weight}; - +use bitcoin::{psbt, Network, Weight}; use serde::{Deserialize, Serialize}; /// Types of keychains -#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub enum KeychainKind { - /// External keychain, used for deriving recipient addresses. - External = 0, - /// Internal keychain, used for deriving change addresses. - Internal = 1, -} +// #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +// pub enum KeychainKind { +// /// External keychain, used for deriving recipient addresses. +// External = 0, +// /// Internal keychain, used for deriving change addresses. +// Internal = 1, +// } -impl KeychainKind { - /// Return [`KeychainKind`] as a byte - pub fn as_byte(&self) -> u8 { - match self { - KeychainKind::External => b'e', - KeychainKind::Internal => b'i', - } - } -} +// TODO #226: Figure out why these are there and implement them on the new KeychainKind. +// impl KeychainKind { +// /// Return [`KeychainKind`] as a byte +// pub fn as_byte(&self) -> u8 { +// match self { +// KeychainKind::External => b'e', +// KeychainKind::Internal => b'i', +// } +// } +// } +// +// impl AsRef<[u8]> for KeychainKind { +// fn as_ref(&self) -> &[u8] { +// match self { +// KeychainKind::External => b"e", +// KeychainKind::Internal => b"i", +// } +// } +// } -impl AsRef<[u8]> for KeychainKind { - fn as_ref(&self) -> &[u8] { - match self { - KeychainKind::External => b"e", - KeychainKind::Internal => b"i", - } - } +#[derive(Clone, Debug, Copy, Eq, Ord, PartialEq, Hash, Serialize, Deserialize, PartialOrd)] +pub enum KeychainKind { + Default, + Change, + Other(DescriptorId), } /// An unspent output owned by a [`Wallet`]. diff --git a/wallet/src/wallet/error.rs b/wallet/src/wallet/error.rs index adce5b18..6e680e08 100644 --- a/wallet/src/wallet/error.rs +++ b/wallet/src/wallet/error.rs @@ -43,6 +43,12 @@ impl fmt::Display for MiniscriptPsbtError { #[cfg(feature = "std")] impl std::error::Error for MiniscriptPsbtError {} +#[derive(Debug)] +pub enum KeychainNotInKeyRingError { + NoChangeKeychain, + KeychainNotFound(KeychainKind), +} + #[derive(Debug)] /// Error returned from [`TxBuilder::finish`] /// diff --git a/wallet/src/wallet/export.rs b/wallet/src/wallet/export.rs index cbbee2e2..e5bbd2cb 100644 --- a/wallet/src/wallet/export.rs +++ b/wallet/src/wallet/export.rs @@ -112,55 +112,55 @@ impl FullyNodedExport { /// /// 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) - } + // 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( diff --git a/wallet/src/wallet/keyring.rs b/wallet/src/wallet/keyring.rs new file mode 100644 index 00000000..39e6e81d --- /dev/null +++ b/wallet/src/wallet/keyring.rs @@ -0,0 +1,90 @@ +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2025 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. + +use std::prelude::rust_2021::Vec; +use bitcoin::Network; +use chain::{DescriptorExt, DescriptorId}; +use miniscript::{Descriptor, DescriptorPublicKey}; +use crate::descriptor::IntoWalletDescriptor; +use crate::{DescriptorToExtract, KeychainKind}; +use crate::wallet::make_descriptor_to_extract; +use crate::wallet::utils::SecpCtx; + +/// A `WalletKeychain` is mostly a descriptor with metadata associated with it. It states whether the +/// keychain is the default keychain for the wallet, and provides an identifier for it which can be +/// used for retrieval. +pub type WalletKeychain = (KeychainKind, Descriptor); + +#[derive(Debug, Clone)] +pub struct KeyRing { + keychains: Vec, + network: Network, +} + +impl KeyRing { + pub fn new( + default_descriptor: D, + network: Network, + ) -> Self { + let secp = SecpCtx::new(); + let descriptor_to_extract: DescriptorToExtract = make_descriptor_to_extract(default_descriptor); + let public_descriptor: (Descriptor, _) = descriptor_to_extract(&secp, network).unwrap(); + let wallet_keychain = (KeychainKind::Default, public_descriptor.0); + + KeyRing { + keychains: vec![wallet_keychain], + network, + } + } + + // TODO #226: This needs to never fail because there is always a default keychain. + pub fn get_default_keychain(&self) -> WalletKeychain { + self.keychains.iter().find(|keychain| matches!(keychain.0, KeychainKind::Default)).unwrap().clone() + } + + pub fn get_change_keychain(&self) -> Option { + self.keychains.iter().find(|keychain| matches!(keychain.0, KeychainKind::Change)).cloned() + } + + pub fn add_other_descriptor( + &mut self, + other_descriptor: D + ) -> &mut KeyRing { + let secp = SecpCtx::new(); + let descriptor_to_extract: DescriptorToExtract = make_descriptor_to_extract(other_descriptor); + let public_descriptor = descriptor_to_extract(&secp, self.network).unwrap(); + let descriptor_id = public_descriptor.0.descriptor_id(); + + let wallet_keychain = ((KeychainKind::Other(descriptor_id)), public_descriptor.0); + + self.keychains.push(wallet_keychain); + self + } + + pub fn list_keychains(&self) -> &Vec { + &self.keychains + } + + pub fn list_keychain_ids(&self) -> Vec { + self.keychains + .iter() + .map(|keychain| match keychain.0 { + KeychainKind::Other(descriptor_id) => descriptor_id, + KeychainKind::Default => keychain.1.descriptor_id(), + KeychainKind::Change => keychain.1.descriptor_id(), + }) + .collect() + } + + pub fn network(&self) -> Network { + self.network + } +} diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index 277df8c0..2e0fb234 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -20,7 +20,8 @@ use alloc::{ vec::Vec, }; use core::{cmp::Ordering, fmt, mem, ops::Deref}; - +use std::arch::aarch64::__crc32ch; +use std::cmp::PartialEq; use bdk_chain::{ indexed_tx_graph, indexer::keychain_txout::KeychainTxOutIndex, @@ -58,6 +59,7 @@ mod persisted; pub mod signer; pub mod tx_builder; pub(crate) mod utils; +pub mod keyring; use crate::collections::{BTreeMap, HashMap, HashSet}; use crate::descriptor::{ @@ -77,10 +79,14 @@ use crate::wallet::{ // re-exports pub use bdk_chain::Balance; +use chain::DescriptorId; pub use changeset::ChangeSet; pub use params::*; pub use persisted::*; pub use utils::IsDust; +use crate::error::KeychainNotInKeyRingError; +use crate::error::KeychainNotInKeyRingError::KeychainNotFound; +use crate::keyring::KeyRing; /// A Bitcoin wallet /// @@ -108,6 +114,7 @@ pub struct Wallet { stage: ChangeSet, network: Network, secp: SecpCtx, + key_ring: KeyRing } /// An update to [`Wallet`]. @@ -281,53 +288,6 @@ impl std::error::Error for ApplyBlockError {} 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. @@ -336,13 +296,14 @@ impl Wallet { /// /// ```rust /// # use bdk_wallet::Wallet; + /// # use bdk_wallet::keyring::KeyRing; /// # 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/*)"; + /// # const MY_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; + /// // Create a KeyRing + /// let mut key_ring = KeyRing::new(MY_DESCRIPTOR, Network::Signet); /// // Create a non-persisted wallet. - /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC) - /// .network(Network::Testnet) + /// let wallet = Wallet::create(key_ring) /// .create_wallet_no_persist()?; /// /// // Create a wallet that is persisted to SQLite database. @@ -350,22 +311,18 @@ impl Wallet { /// # 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) + /// let wallet = Wallet::create(key_ring) /// .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) + pub fn create(key_ring: KeyRing) -> CreateParams { + CreateParams::new(key_ring) } - /// Create a new [`Wallet`] with given `params`. - /// - /// Refer to [`Wallet::create`] for more. + // /// 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; @@ -374,35 +331,43 @@ impl Wallet { .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)?; + // let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network)?; + // let (descriptor, mut descriptor_keymap) = (params.keychain_set.get_default_keychain().1.0)(&secp, network)?; + let descriptor = params.key_ring.get_default_keychain().1; 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 index = create_indexer(descriptor, change_descriptor, params.lookahead)?; - - let descriptor = index.get_descriptor(KeychainKind::External).cloned(); - let change_descriptor = index.get_descriptor(KeychainKind::Internal).cloned(); + // descriptor_keymap.extend(descriptor_keymap.clone()); + + let signers = Arc::new(SignersContainer::new()); + // let signers = Arc::new(SignersContainer::build( + // descriptor_keymap, + // &descriptor, + // &secp, + // )); + + // TODO: Handle change + let (change_descriptor, change_signers) = (None, Arc::new(SignersContainer::new())); + // 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 index = create_indexer(params.key_ring.clone(), params.lookahead)?; + + // TODO: Understand why we have the descriptors above but then request them from the indexer + // here. Is it just for sanity check? + let descriptor = index.get_descriptor(params.key_ring.get_default_keychain().0).cloned(); + // TODO: Doesn't work because get_change_keychain() returns an Option + // let change_descriptor = index.get_descriptor(params.keychain_set.get_change_keychain().0).cloned(); let indexed_graph = IndexedTxGraph::new(index); let indexed_graph_changeset = indexed_graph.initial_changeset(); @@ -423,6 +388,7 @@ impl Wallet { indexed_graph, stage, secp, + key_ring: params.key_ring, }) } @@ -472,151 +438,151 @@ impl 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 index = create_indexer(descriptor, change_descriptor, params.lookahead) - .map_err(LoadError::Descriptor)?; - - let mut indexed_graph = IndexedTxGraph::new(index); - indexed_graph.apply_changeset(changeset.indexer.into()); - indexed_graph.apply_changeset(changeset.tx_graph.into()); - - let stage = ChangeSet::default(); - - Ok(Some(Wallet { - signers, - change_signers, - chain, - indexed_graph, - stage, - network, - secp, - })) - } + // pub fn load() -> LoadParams { + // LoadParams::new() + // } + // + // /// Load [`Wallet`] from the given previously persisted [`ChangeSet`] and `params`. + // /// + // /// 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 index = create_indexer(descriptor, change_descriptor, params.lookahead) + // .map_err(LoadError::Descriptor)?; + // + // let mut indexed_graph = IndexedTxGraph::new(index); + // indexed_graph.apply_changeset(changeset.indexer.into()); + // indexed_graph.apply_changeset(changeset.tx_graph.into()); + // + // let stage = ChangeSet::default(); + // + // 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 { @@ -636,12 +602,16 @@ impl Wallet { /// /// This panics when the caller requests for an address of derivation index greater than the /// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index. - pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo { - let keychain = self.map_keychain(keychain); + pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> Result { + // TODO #266: Add test for when the keychain provided is not part of the key_ring + if !self.indexed_graph.index.keychains().any(|(k, _)| k == keychain) { + return Err(KeychainNotInKeyRingError::KeychainNotFound(keychain)); + } + let mut spk_iter = self .indexed_graph .index - .unbounded_spk_iter(keychain) + .unbounded_spk_iter(keychain.clone()) .expect("keychain must exist"); if !spk_iter.descriptor().has_wildcard() { index = 0; @@ -650,11 +620,11 @@ impl Wallet { .nth(index as usize) .expect("derivation index is out of bounds"); - AddressInfo { + Ok(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`. @@ -675,15 +645,19 @@ impl Wallet { /// .load_wallet(&mut conn) /// .expect("database is okay") /// .expect("database has data"); - /// let next_address = wallet.reveal_next_address(KeychainKind::External); + /// let next_address = wallet.reveal_next_address(KeychainKind::Default()); /// 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); + pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> Result { + // TODO #266: Add test for when the keychain provided is not part of the key_ring + if !self.indexed_graph.index.keychains().any(|(k, _)| k == keychain) { + return Err(KeychainNotInKeyRingError::KeychainNotFound(keychain)); + } + let index = &mut self.indexed_graph.index; let stage = &mut self.stage; @@ -693,12 +667,12 @@ impl Wallet { stage.merge(index_changeset.into()); - AddressInfo { + Ok(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 @@ -710,26 +684,26 @@ impl Wallet { /// /// **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, - }) - } + // 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. @@ -739,8 +713,12 @@ impl Wallet { /// /// **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); + pub fn next_unused_address(&mut self, keychain: KeychainKind) -> Result { + // TODO #266: Add test for when the keychain provided is not part of the key_ring + if !self.indexed_graph.index.keychains().any(|(k, _)| k == keychain) { + return Err(KeychainNotInKeyRingError::KeychainNotFound(keychain)); + } + let index = &mut self.indexed_graph.index; let ((index, spk), index_changeset) = index @@ -750,19 +728,25 @@ impl Wallet { self.stage .merge(indexed_tx_graph::ChangeSet::from(index_changeset).into()); - AddressInfo { + Ok(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) + pub fn mark_used(&mut self, keychain: KeychainKind, index: u32) -> Result { + // TODO #266: Add test for when the keychain provided is not part of the key_ring + if !self.indexed_graph.index.keychains().any(|(k, _)| k == keychain) { + return Err(KeychainNotInKeyRingError::KeychainNotFound(keychain)); + } + + self.indexed_graph.index.mark_used(keychain, index); + Ok(true) } /// Undoes the effect of [`mark_used`] and returns whether the `index` was inserted @@ -773,8 +757,13 @@ impl Wallet { /// 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) + pub fn unmark_used(&mut self, keychain: KeychainKind, index: u32) -> Result { + // TODO #266: Add test for when the keychain provided is not part of the key_ring + if !self.indexed_graph.index.keychains().any(|(k, _)| k == keychain) { + return Err(KeychainNotInKeyRingError::KeychainNotFound(keychain)); + } + self.indexed_graph.index.unmark_used(keychain, index); + Ok(true) } /// List addresses that are revealed but unused. @@ -782,20 +771,20 @@ impl Wallet { /// 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, - }) - } + // 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 { @@ -866,31 +855,30 @@ impl Wallet { /// 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") - } + // 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() - } + // 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(), + // 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. /// @@ -1119,53 +1107,54 @@ impl Wallet { /// Return the balance, separated into available, trusted-pending, untrusted-pending and immature /// values. + // TODO: The trust predicate here is a problem. 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, + |&(k, _), _| k.clone() == KeychainKind::Change, ) } /// 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); - } + // 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) - } - } + // 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); - } - } + // pub fn set_keymaps(&mut self, keymaps: impl IntoIterator) { + // for (keychain, keymap) in keymaps { + // self.set_keymap(keychain, keymap); + // } + // } /// Get the signers /// @@ -1188,8 +1177,9 @@ impl Wallet { /// ``` pub fn get_signers(&self, keychain: KeychainKind) -> Arc { match keychain { - KeychainKind::External => Arc::clone(&self.signers), - KeychainKind::Internal => Arc::clone(&self.change_signers), + KeychainKind::Default => Arc::clone(&self.signers), + KeychainKind::Change => Arc::clone(&self.change_signers), + KeychainKind::Other(_) => Arc::new(SignersContainer::new()) } } @@ -1229,329 +1219,329 @@ impl Wallet { } } - 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.values().cloned().collect(); - 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) { - let (_, index_changeset) = self - .indexed_graph - .index - .reveal_to_target(keychain, index) - .expect("must not be None"); - self.stage.merge(index_changeset.into()); - self.mark_used(keychain, index); - } - - Ok(psbt) - } + // 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.values().cloned().collect(); + // 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) { + // let (_, index_changeset) = self + // .indexed_graph + // .index + // .reveal_to_target(keychain, index) + // .expect("must not be None"); + // self.stage.merge(index_changeset.into()); + // self.mark_used(keychain, index); + // } + // + // Ok(psbt) + // } /// Bump the fee of a transaction previously created with this wallet. /// @@ -1594,155 +1584,155 @@ impl Wallet { /// # 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| { - chain_positions - .get(&txin.previous_output.txid) - .cloned() - .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output)) - .map(|chain_position| (prev_tx, chain_position)) - }) - .map(|(prev_tx, chain_position)| { - let txout = prev_tx.output[txin.previous_output.vout as usize].clone(); - match txout_index.index_of_spk(txout.script_pubkey.clone()) { - Some(&(keychain, derivation_index)) => ( - txin.previous_output, - WeightedUtxo { - satisfaction_weight: self - .public_descriptor(keychain) - .max_weight_to_satisfy() - .unwrap(), - utxo: Utxo::Local(LocalOutput { - outpoint: txin.previous_output, - txout: 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(), - ); - - ( - txin.previous_output, - WeightedUtxo { - utxo: Utxo::Foreign { - outpoint: txin.previous_output, - sequence: txin.sequence, - psbt_input: Box::new(psbt::Input { - witness_utxo: txout - .script_pubkey - .witness_version() - .map(|_| 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(), - }) - } + // 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) + // .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| { + // chain_positions + // .get(&txin.previous_output.txid) + // .cloned() + // .ok_or(BuildFeeBumpError::UnknownUtxo(txin.previous_output)) + // .map(|chain_position| (prev_tx, chain_position)) + // }) + // .map(|(prev_tx, chain_position)| { + // let txout = prev_tx.output[txin.previous_output.vout as usize].clone(); + // match txout_index.index_of_spk(txout.script_pubkey.clone()) { + // Some(&(keychain, derivation_index)) => ( + // txin.previous_output, + // WeightedUtxo { + // satisfaction_weight: self + // .public_descriptor(keychain) + // .max_weight_to_satisfy() + // .unwrap(), + // utxo: Utxo::Local(LocalOutput { + // outpoint: txin.previous_output, + // txout: 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(), + // ); + // + // ( + // txin.previous_output, + // WeightedUtxo { + // utxo: Utxo::Foreign { + // outpoint: txin.previous_output, + // sequence: txin.sequence, + // psbt_input: Box::new(psbt::Input { + // witness_utxo: txout + // .script_pubkey + // .witness_version() + // .map(|_| 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. @@ -1821,18 +1811,18 @@ impl Wallet { } /// 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, - ) - } + // 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`. /// @@ -1975,28 +1965,28 @@ impl Wallet { } /// The index of the next address that you would get if you were to ask the wallet for a new address - pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 { - self.indexed_graph - .index - .next_index(self.map_keychain(keychain)) - .expect("keychain must exist") - .0 - } + // 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); - } - } - } + // 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 @@ -2009,52 +1999,51 @@ impl Wallet { /// 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 { - 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| !params.utxos.contains_key(&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 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 { + // 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(), + // 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| !params.utxos.contains_key(&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, @@ -2367,42 +2356,14 @@ impl Wallet { self.stage.merge(indexed_graph_changeset.into()); } - /// Apply evictions of the given txids with their associated timestamps. - /// - /// This means that any pending unconfirmed tx in this set will no longer be canonical by - /// default. Note that an evicted tx can become canonical if it is later seen again or - /// observed on-chain. - /// - /// This stages the changes which need to be persisted. - 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(); + // /// 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`]. - 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 + KeychainKind::Default } else { keychain } @@ -2535,35 +2496,45 @@ fn new_local_utxo( } fn create_indexer( - descriptor: ExtendedDescriptor, - change_descriptor: Option, + keyring: KeyRing, + // descriptor: ExtendedDescriptor, + // change_descriptor: Option, lookahead: u32, ) -> Result, DescriptorError> { let mut indexer = KeychainTxOutIndex::::new(lookahead); // let (descriptor, keymap) = descriptor; // let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp)); - assert!(indexer - .insert_descriptor(KeychainKind::External, descriptor) - .expect("first descriptor introduced must succeed")); + let default_keychain = keyring.get_default_keychain().clone(); + // TODO: KeyRings always have at least one descriptor, so I think we don't need this assert + // assert!(indexer + // .insert_descriptor(default_keychain.0, default_keychain.1.0) + // .expect("first descriptor introduced must succeed")); + + // TODO: I'm here, May 2 + keyring.list_keychains().iter().for_each(|k| { + indexer.insert_descriptor(k.0, k.1.clone()).expect("This should work"); + dbg!("Inserting descriptor into indexer"); + }); // let (descriptor, keymap) = change_descriptor; // let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp)); - if let Some(change_descriptor) = change_descriptor { - assert!(indexer - .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") - } - } - })?); - } + // TODO: Handle change + // if let Some(change_descriptor) = change_descriptor { + // assert!(indexer + // .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") + // } + // } + // })?); + // } Ok(indexer) } diff --git a/wallet/src/wallet/params.rs b/wallet/src/wallet/params.rs index 7cf3bdd2..dae54a6d 100644 --- a/wallet/src/wallet/params.rs +++ b/wallet/src/wallet/params.rs @@ -3,12 +3,7 @@ use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD; use bitcoin::{BlockHash, Network}; use miniscript::descriptor::KeyMap; -use crate::{ - descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor}, - utils::SecpCtx, - AsyncWalletPersister, CreateWithPersistError, KeychainKind, LoadWithPersistError, Wallet, - WalletPersister, -}; +use crate::{descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor}, utils::SecpCtx, AsyncWalletPersister, CreateWithPersistError, wallet::keyring::KeyRing, LoadWithPersistError, Wallet, WalletPersister}; use super::{ChangeSet, LoadError, PersistedWallet}; @@ -16,13 +11,13 @@ use super::{ChangeSet, LoadError, PersistedWallet}; /// /// The better option would be to do `Box`, but we cannot due to Rust's /// [object safety rules](https://doc.rust-lang.org/reference/items/traits.html#object-safety). -type DescriptorToExtract = Box< +pub type DescriptorToExtract = Box< dyn FnOnce(&SecpCtx, Network) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> + Send + 'static, >; -fn make_descriptor_to_extract(descriptor: D) -> DescriptorToExtract +pub(crate) fn make_descriptor_to_extract(descriptor: D) -> DescriptorToExtract where D: IntoWalletDescriptor + Send + 'static, { @@ -32,68 +27,40 @@ where /// Parameters for [`Wallet::create`] or [`PersistedWallet::create`]. #[must_use] pub struct CreateParams { - pub(crate) descriptor: DescriptorToExtract, - pub(crate) descriptor_keymap: KeyMap, - pub(crate) change_descriptor: Option, - pub(crate) change_descriptor_keymap: KeyMap, + pub(crate) key_ring: KeyRing, pub(crate) network: Network, pub(crate) genesis_hash: Option, pub(crate) lookahead: u32, } impl CreateParams { - /// Construct parameters with provided `descriptor`. - /// - /// Default values: - /// * `change_descriptor` = `None` - /// * `network` = [`Network::Bitcoin`] - /// * `genesis_hash` = `None` - /// * `lookahead` = [`DEFAULT_LOOKAHEAD`] - /// - /// Use this method only when building a wallet with a single descriptor. See - /// also [`Wallet::create_single`]. - pub fn new_single(descriptor: D) -> Self { - Self { - descriptor: make_descriptor_to_extract(descriptor), - descriptor_keymap: KeyMap::default(), - change_descriptor: None, - change_descriptor_keymap: KeyMap::default(), - network: Network::Bitcoin, - genesis_hash: None, - lookahead: DEFAULT_LOOKAHEAD, - } - } - - /// Construct parameters with provided `descriptor` and `change_descriptor`. + /// Construct parameters with provided `KeyRing`. /// /// Default values: - /// * `network` = [`Network::Bitcoin`] + /// * `network` = Provided by your KeyRing /// * `genesis_hash` = `None` /// * `lookahead` = [`DEFAULT_LOOKAHEAD`] - pub fn new( - descriptor: D, - change_descriptor: D, + pub fn new( + key_ring: KeyRing, ) -> Self { + let network = key_ring.network(); Self { - descriptor: make_descriptor_to_extract(descriptor), - descriptor_keymap: KeyMap::default(), - change_descriptor: Some(make_descriptor_to_extract(change_descriptor)), - change_descriptor_keymap: KeyMap::default(), - network: Network::Bitcoin, + key_ring, + network, genesis_hash: None, lookahead: DEFAULT_LOOKAHEAD, } } - /// Extend the given `keychain`'s `keymap`. - pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self { - match keychain { - KeychainKind::External => &mut self.descriptor_keymap, - KeychainKind::Internal => &mut self.change_descriptor_keymap, - } - .extend(keymap); - self - } + // /// Extend the given `keychain`'s `keymap`. + // pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self { + // match keychain { + // KeychainKind::External => &mut self.descriptor_keymap, + // KeychainKind::Internal => &mut self.change_descriptor_keymap, + // } + // .extend(keymap); + // self + // } /// Set `network`. pub fn network(mut self, network: Network) -> Self { @@ -177,14 +144,14 @@ impl LoadParams { } /// Extend the given `keychain`'s `keymap`. - pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self { - match keychain { - KeychainKind::External => &mut self.descriptor_keymap, - KeychainKind::Internal => &mut self.change_descriptor_keymap, - } - .extend(keymap); - self - } + // pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self { + // match keychain { + // KeychainKind::External => &mut self.descriptor_keymap, + // KeychainKind::Internal => &mut self.change_descriptor_keymap, + // } + // .extend(keymap); + // self + // } /// Checks the `expected_descriptor` matches exactly what is loaded for `keychain`. /// @@ -192,17 +159,17 @@ impl LoadParams { /// /// You must also specify [`extract_keys`](Self::extract_keys) if you wish to add a signer /// for an expected descriptor containing secrets. - pub fn descriptor(mut self, keychain: KeychainKind, expected_descriptor: Option) -> Self - where - D: IntoWalletDescriptor + Send + 'static, - { - let expected = expected_descriptor.map(|d| make_descriptor_to_extract(d)); - match keychain { - KeychainKind::External => self.check_descriptor = Some(expected), - KeychainKind::Internal => self.check_change_descriptor = Some(expected), - } - self - } + // pub fn descriptor(mut self, keychain: KeychainKind, expected_descriptor: Option) -> Self + // where + // D: IntoWalletDescriptor + Send + 'static, + // { + // let expected = expected_descriptor.map(|d| make_descriptor_to_extract(d)); + // match keychain { + // KeychainKind::External => self.check_descriptor = Some(expected), + // KeychainKind::Internal => self.check_change_descriptor = Some(expected), + // } + // self + // } /// Checks that the given network matches the one loaded from persistence. pub fn check_network(mut self, network: Network) -> Self { @@ -234,32 +201,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/wallet/src/wallet/persisted.rs b/wallet/src/wallet/persisted.rs index 71236989..6d53de66 100644 --- a/wallet/src/wallet/persisted.rs +++ b/wallet/src/wallet/persisted.rs @@ -160,20 +160,20 @@ impl PersistedWallet

{ } /// 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) - } + // 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`. /// @@ -219,22 +219,22 @@ impl PersistedWallet

{ } /// 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) - } + // 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`. /// diff --git a/wallet/src/wallet/tx_builder.rs b/wallet/src/wallet/tx_builder.rs index 7d693761..8fb95929 100644 --- a/wallet/src/wallet/tx_builder.rs +++ b/wallet/src/wallet/tx_builder.rs @@ -253,19 +253,19 @@ impl<'a, Cs> TxBuilder<'a, Cs> { /// /// # 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 - } + // 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. /// @@ -273,40 +273,40 @@ impl<'a, Cs> TxBuilder<'a, Cs> { /// /// 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_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> { - let utxo_batch = outpoints - .iter() - .map(|outpoint| { - self.wallet - .get_utxo(*outpoint) - .ok_or(AddUtxoError::UnknownUtxo(*outpoint)) - .map(|output| { - ( - *outpoint, - WeightedUtxo { - satisfaction_weight: self - .wallet - .public_descriptor(output.keychain) - .max_weight_to_satisfy() - .unwrap(), - utxo: Utxo::Local(output), - }, - ) - }) - }) - .collect::, AddUtxoError>>()?; - self.params.utxos.extend(utxo_batch); - - Ok(self) - } + // pub fn add_utxos(&mut self, outpoints: &[OutPoint]) -> Result<&mut Self, AddUtxoError> { + // let utxo_batch = outpoints + // .iter() + // .map(|outpoint| { + // self.wallet + // .get_utxo(*outpoint) + // .ok_or(AddUtxoError::UnknownUtxo(*outpoint)) + // .map(|output| { + // ( + // *outpoint, + // WeightedUtxo { + // satisfaction_weight: self + // .wallet + // .public_descriptor(output.keychain) + // .max_weight_to_satisfy() + // .unwrap(), + // utxo: Utxo::Local(output), + // }, + // ) + // }) + // }) + // .collect::, AddUtxoError>>()?; + // self.params.utxos.extend(utxo_batch); + // + // 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]) - } + // 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. /// @@ -669,34 +669,34 @@ impl<'a, Cs> TxBuilder<'a, Cs> { } 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) - } + // /// 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)] @@ -820,6 +820,7 @@ impl TxOrdering { } } +// TODO: This doesn't make as much sense if you have N keychains. Needs reworking. /// Policy regarding the use of change outputs when creating a transaction #[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] pub enum ChangeSpendPolicy { @@ -836,8 +837,8 @@ impl ChangeSpendPolicy { pub(crate) fn is_satisfied_by(&self, utxo: &LocalOutput) -> bool { match self { ChangeSpendPolicy::ChangeAllowed => true, - ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal, - ChangeSpendPolicy::ChangeForbidden => utxo.keychain == KeychainKind::External, + ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Change, + ChangeSpendPolicy::ChangeForbidden => utxo.keychain == KeychainKind::Default, } } }