Merge commit 'refs/pull/279/head' of github.com:bitcoindevkit/bdk
This commit is contained in:
		
						commit
						e0183ed5c7
					
				| @ -54,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||||
|   type to mark for a missing client. | ||||
| - Upgrade `tokio` to `1.0`. | ||||
| 
 | ||||
| #### Transaction Creation Overhaul | ||||
| ### Transaction Creation Overhaul | ||||
| 
 | ||||
| The `TxBuilder` is now created from the `build_tx` or `build_fee_bump` functions on wallet and the | ||||
| final transaction is created by calling `finish` on the builder. | ||||
| @ -65,6 +65,13 @@ final transaction is created by calling `finish` on the builder. | ||||
| - Added `Wallet::get_utxo` | ||||
| - Added `Wallet::get_descriptor_for_keychain` | ||||
| 
 | ||||
| ### `add_foreign_utxo` | ||||
| 
 | ||||
| - Renamed `UTXO` to `LocalUtxo` | ||||
| - Added `WeightedUtxo` to replace floating `(UTXO, usize)`. | ||||
| - Added `Utxo` enum to incorporate both local utxos and foreign utxos | ||||
| - Added `TxBuilder::add_foreign_utxo` which allows adding a utxo external to the wallet. | ||||
| 
 | ||||
| ### CLI | ||||
| #### Changed | ||||
| - Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository | ||||
|  | ||||
| @ -83,7 +83,7 @@ mod sync; | ||||
| use super::{Blockchain, Capability, ConfigurableBlockchain, Progress}; | ||||
| use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | ||||
| use crate::error::Error; | ||||
| use crate::types::{KeychainKind, TransactionDetails, UTXO}; | ||||
| use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; | ||||
| use crate::FeeRate; | ||||
| 
 | ||||
| use peer::*; | ||||
| @ -194,7 +194,7 @@ impl CompactFiltersBlockchain { | ||||
|                 database.get_path_from_script_pubkey(&output.script_pubkey)? | ||||
|             { | ||||
|                 debug!("{} output #{} is mine, adding utxo", tx.txid(), i); | ||||
|                 updates.set_utxo(&UTXO { | ||||
|                 updates.set_utxo(&LocalUtxo { | ||||
|                     outpoint: OutPoint::new(tx.txid(), i as u32), | ||||
|                     txout: output.clone(), | ||||
|                     keychain, | ||||
|  | ||||
| @ -34,7 +34,7 @@ use bitcoin::{BlockHeader, OutPoint, Script, Transaction, Txid}; | ||||
| use super::*; | ||||
| use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | ||||
| use crate::error::Error; | ||||
| use crate::types::{KeychainKind, TransactionDetails, UTXO}; | ||||
| use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; | ||||
| use crate::wallet::time::Instant; | ||||
| use crate::wallet::utils::ChunksIterator; | ||||
| 
 | ||||
| @ -353,7 +353,7 @@ fn save_transaction_details_and_utxos<D: BatchDatabase>( | ||||
|         // this output is ours, we have a path to derive it
 | ||||
|         if let Some((keychain, _child)) = db.get_path_from_script_pubkey(&output.script_pubkey)? { | ||||
|             debug!("{} output #{} is mine, adding utxo", txid, i); | ||||
|             updates.set_utxo(&UTXO { | ||||
|             updates.set_utxo(&LocalUtxo { | ||||
|                 outpoint: OutPoint::new(tx.txid(), i as u32), | ||||
|                 txout: output.clone(), | ||||
|                 keychain, | ||||
|  | ||||
| @ -133,7 +133,7 @@ impl BatchOperations for AnyDatabase { | ||||
|             child | ||||
|         ) | ||||
|     } | ||||
|     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { | ||||
|     fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, set_utxo, utxo) | ||||
|     } | ||||
|     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { | ||||
| @ -165,7 +165,7 @@ impl BatchOperations for AnyDatabase { | ||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, del_path_from_script_pubkey, script) | ||||
|     } | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, del_utxo, outpoint) | ||||
|     } | ||||
|     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { | ||||
| @ -201,7 +201,7 @@ impl Database for AnyDatabase { | ||||
|     fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, iter_script_pubkeys, keychain) | ||||
|     } | ||||
|     fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> { | ||||
|     fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, iter_utxos) | ||||
|     } | ||||
|     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> { | ||||
| @ -230,7 +230,7 @@ impl Database for AnyDatabase { | ||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, get_path_from_script_pubkey, script) | ||||
|     } | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         impl_inner_method!(AnyDatabase, self, get_utxo, outpoint) | ||||
|     } | ||||
|     fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> { | ||||
| @ -257,7 +257,7 @@ impl BatchOperations for AnyBatch { | ||||
|     ) -> Result<(), Error> { | ||||
|         impl_inner_method!(AnyBatch, self, set_script_pubkey, script, keychain, child) | ||||
|     } | ||||
|     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { | ||||
|     fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> { | ||||
|         impl_inner_method!(AnyBatch, self, set_utxo, utxo) | ||||
|     } | ||||
|     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { | ||||
| @ -283,7 +283,7 @@ impl BatchOperations for AnyBatch { | ||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error> { | ||||
|         impl_inner_method!(AnyBatch, self, del_path_from_script_pubkey, script) | ||||
|     } | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         impl_inner_method!(AnyBatch, self, del_utxo, outpoint) | ||||
|     } | ||||
|     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { | ||||
|  | ||||
| @ -51,7 +51,7 @@ macro_rules! impl_batch_operations { | ||||
|             Ok(()) | ||||
|         } | ||||
| 
 | ||||
|         fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { | ||||
|         fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> { | ||||
|             let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); | ||||
|             let value = json!({ | ||||
|                 "t": utxo.txout, | ||||
| @ -120,7 +120,7 @@ macro_rules! impl_batch_operations { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|         fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|             let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||
|             let res = self.remove(key); | ||||
|             let res = $process_delete!(res); | ||||
| @ -132,7 +132,7 @@ macro_rules! impl_batch_operations { | ||||
|                     let txout = serde_json::from_value(val["t"].take())?; | ||||
|                     let keychain = serde_json::from_value(val["i"].take())?; | ||||
| 
 | ||||
|                     Ok(Some(UTXO { outpoint: outpoint.clone(), txout, keychain })) | ||||
|                     Ok(Some(LocalUtxo { outpoint: outpoint.clone(), txout, keychain })) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -234,7 +234,7 @@ impl Database for Tree { | ||||
|             .collect() | ||||
|     } | ||||
| 
 | ||||
|     fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> { | ||||
|     fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> { | ||||
|         let key = MapKey::UTXO(None).as_map_key(); | ||||
|         self.scan_prefix(key) | ||||
|             .map(|x| -> Result<_, Error> { | ||||
| @ -245,7 +245,7 @@ impl Database for Tree { | ||||
|                 let txout = serde_json::from_value(val["t"].take())?; | ||||
|                 let keychain = serde_json::from_value(val["i"].take())?; | ||||
| 
 | ||||
|                 Ok(UTXO { | ||||
|                 Ok(LocalUtxo { | ||||
|                     outpoint, | ||||
|                     txout, | ||||
|                     keychain, | ||||
| @ -305,7 +305,7 @@ impl Database for Tree { | ||||
|             .transpose() | ||||
|     } | ||||
| 
 | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||
|         self.get(key)? | ||||
|             .map(|b| -> Result<_, Error> { | ||||
| @ -313,7 +313,7 @@ impl Database for Tree { | ||||
|                 let txout = serde_json::from_value(val["t"].take())?; | ||||
|                 let keychain = serde_json::from_value(val["i"].take())?; | ||||
| 
 | ||||
|                 Ok(UTXO { | ||||
|                 Ok(LocalUtxo { | ||||
|                     outpoint: *outpoint, | ||||
|                     txout, | ||||
|                     keychain, | ||||
|  | ||||
| @ -157,7 +157,7 @@ impl BatchOperations for MemoryDatabase { | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error> { | ||||
|     fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error> { | ||||
|         let key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); | ||||
|         self.map | ||||
|             .insert(key, Box::new((utxo.txout.clone(), utxo.keychain))); | ||||
| @ -223,7 +223,7 @@ impl BatchOperations for MemoryDatabase { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||
|         let res = self.map.remove(&key); | ||||
|         self.deleted_keys.push(key); | ||||
| @ -232,7 +232,7 @@ impl BatchOperations for MemoryDatabase { | ||||
|             None => Ok(None), | ||||
|             Some(b) => { | ||||
|                 let (txout, keychain) = b.downcast_ref().cloned().unwrap(); | ||||
|                 Ok(Some(UTXO { | ||||
|                 Ok(Some(LocalUtxo { | ||||
|                     outpoint: *outpoint, | ||||
|                     txout, | ||||
|                     keychain, | ||||
| @ -316,14 +316,14 @@ impl Database for MemoryDatabase { | ||||
|             .collect() | ||||
|     } | ||||
| 
 | ||||
|     fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> { | ||||
|     fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> { | ||||
|         let key = MapKey::UTXO(None).as_map_key(); | ||||
|         self.map | ||||
|             .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) | ||||
|             .map(|(k, v)| { | ||||
|                 let outpoint = deserialize(&k[1..]).unwrap(); | ||||
|                 let (txout, keychain) = v.downcast_ref().cloned().unwrap(); | ||||
|                 Ok(UTXO { | ||||
|                 Ok(LocalUtxo { | ||||
|                     outpoint, | ||||
|                     txout, | ||||
|                     keychain, | ||||
| @ -382,11 +382,11 @@ impl Database for MemoryDatabase { | ||||
|         })) | ||||
|     } | ||||
| 
 | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||
|         Ok(self.map.get(&key).map(|b| { | ||||
|             let (txout, keychain) = b.downcast_ref().cloned().unwrap(); | ||||
|             UTXO { | ||||
|             LocalUtxo { | ||||
|                 outpoint: *outpoint, | ||||
|                 txout, | ||||
|                 keychain, | ||||
| @ -502,7 +502,7 @@ macro_rules! populate_test_db { | ||||
| 
 | ||||
|         db.set_tx(&tx_details).unwrap(); | ||||
|         for (vout, out) in tx.output.iter().enumerate() { | ||||
|             db.set_utxo(&UTXO { | ||||
|             db.set_utxo(&LocalUtxo { | ||||
|                 txout: out.clone(), | ||||
|                 outpoint: OutPoint { | ||||
|                     txid, | ||||
|  | ||||
| @ -64,8 +64,8 @@ pub trait BatchOperations { | ||||
|         keychain: KeychainKind, | ||||
|         child: u32, | ||||
|     ) -> Result<(), Error>; | ||||
|     /// Store a [`UTXO`]
 | ||||
|     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>; | ||||
|     /// Store a [`LocalUtxo`]
 | ||||
|     fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error>; | ||||
|     /// Store a raw transaction
 | ||||
|     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>; | ||||
|     /// Store the metadata of a transaction
 | ||||
| @ -85,8 +85,8 @@ pub trait BatchOperations { | ||||
|         &mut self, | ||||
|         script: &Script, | ||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error>; | ||||
|     /// Delete a [`UTXO`] given its [`OutPoint`]
 | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>; | ||||
|     /// Delete a [`LocalUtxo`] given its [`OutPoint`]
 | ||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>; | ||||
|     /// Delete a raw transaction given its [`Txid`]
 | ||||
|     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>; | ||||
|     /// Delete the metadata of a transaction and optionally the raw transaction itself
 | ||||
| @ -116,8 +116,8 @@ pub trait Database: BatchOperations { | ||||
| 
 | ||||
|     /// Return the list of script_pubkeys
 | ||||
|     fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>; | ||||
|     /// Return the list of [`UTXO`]s
 | ||||
|     fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>; | ||||
|     /// Return the list of [`LocalUtxo`]s
 | ||||
|     fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error>; | ||||
|     /// Return the list of raw transactions
 | ||||
|     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>; | ||||
|     /// Return the list of transactions metadata
 | ||||
| @ -134,8 +134,8 @@ pub trait Database: BatchOperations { | ||||
|         &self, | ||||
|         script: &Script, | ||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error>; | ||||
|     /// Fetch a [`UTXO`] given its [`OutPoint`]
 | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>; | ||||
|     /// Fetch a [`LocalUtxo`] given its [`OutPoint`]
 | ||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>; | ||||
|     /// Fetch a raw transaction given its [`Txid`]
 | ||||
|     fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>; | ||||
|     /// Fetch the transaction metadata and optionally also the raw transaction
 | ||||
| @ -298,7 +298,7 @@ pub mod test { | ||||
|             value: 133742, | ||||
|             script_pubkey: script, | ||||
|         }; | ||||
|         let utxo = UTXO { | ||||
|         let utxo = LocalUtxo { | ||||
|             txout, | ||||
|             outpoint, | ||||
|             keychain: KeychainKind::External, | ||||
|  | ||||
							
								
								
									
										66
									
								
								src/types.rs
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								src/types.rs
									
									
									
									
									
								
							| @ -25,7 +25,7 @@ | ||||
| use std::convert::AsRef; | ||||
| 
 | ||||
| use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; | ||||
| use bitcoin::hash_types::Txid; | ||||
| use bitcoin::{hash_types::Txid, util::psbt}; | ||||
| 
 | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| @ -90,9 +90,11 @@ impl std::default::Default for FeeRate { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A wallet unspent output
 | ||||
| /// An unspent output owned by a [`Wallet`].
 | ||||
| ///
 | ||||
| /// [`Wallet`]: crate::Wallet
 | ||||
| #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] | ||||
| pub struct UTXO { | ||||
| pub struct LocalUtxo { | ||||
|     /// Reference to a transaction output
 | ||||
|     pub outpoint: OutPoint, | ||||
|     /// Transaction output
 | ||||
| @ -101,6 +103,64 @@ pub struct UTXO { | ||||
|     pub keychain: KeychainKind, | ||||
| } | ||||
| 
 | ||||
| /// A [`Utxo`] with its `satisfaction_weight`.
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct WeightedUtxo { | ||||
|     /// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
 | ||||
|     /// properly maintain the feerate when adding this input to a transaction during coin selection.
 | ||||
|     ///
 | ||||
|     /// [weight units]: https://en.bitcoin.it/wiki/Weight_units
 | ||||
|     pub satisfaction_weight: usize, | ||||
|     /// The UTXO
 | ||||
|     pub utxo: Utxo, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| /// An unspent transaction output (UTXO).
 | ||||
| pub enum Utxo { | ||||
|     /// A UTXO owned by the local wallet.
 | ||||
|     Local(LocalUtxo), | ||||
|     /// A UTXO owned by another wallet.
 | ||||
|     Foreign { | ||||
|         /// The location of the output.
 | ||||
|         outpoint: OutPoint, | ||||
|         /// The information about the input we require to add it to a PSBT.
 | ||||
|         // Box it to stop the type being too big.
 | ||||
|         psbt_input: Box<psbt::Input>, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| impl Utxo { | ||||
|     /// Get the location of the UTXO
 | ||||
|     pub fn outpoint(&self) -> OutPoint { | ||||
|         match &self { | ||||
|             Utxo::Local(local) => local.outpoint, | ||||
|             Utxo::Foreign { outpoint, .. } => *outpoint, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Get the `TxOut` of the UTXO
 | ||||
|     pub fn txout(&self) -> &TxOut { | ||||
|         match &self { | ||||
|             Utxo::Local(local) => &local.txout, | ||||
|             Utxo::Foreign { | ||||
|                 outpoint, | ||||
|                 psbt_input, | ||||
|             } => { | ||||
|                 if let Some(prev_tx) = &psbt_input.non_witness_utxo { | ||||
|                     return &prev_tx.output[outpoint.vout as usize]; | ||||
|                 } | ||||
| 
 | ||||
|                 if let Some(txout) = &psbt_input.witness_utxo { | ||||
|                     return &txout; | ||||
|                 } | ||||
| 
 | ||||
|                 unreachable!("Foreign UTXOs will always have one of these set") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A wallet transaction
 | ||||
| #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] | ||||
| pub struct TransactionDetails { | ||||
|  | ||||
| @ -50,8 +50,8 @@ | ||||
| //!     fn coin_select(
 | ||||
| //!         &self,
 | ||||
| //!         database: &D,
 | ||||
| //!         required_utxos: Vec<(UTXO, usize)>,
 | ||||
| //!         optional_utxos: Vec<(UTXO, usize)>,
 | ||||
| //!         required_utxos: Vec<WeightedUtxo>,
 | ||||
| //!         optional_utxos: Vec<WeightedUtxo>,
 | ||||
| //!         fee_rate: FeeRate,
 | ||||
| //!         amount_needed: u64,
 | ||||
| //!         fee_amount: f32,
 | ||||
| @ -60,11 +60,10 @@ | ||||
| //!         let mut additional_weight = 0;
 | ||||
| //!         let all_utxos_selected = required_utxos
 | ||||
| //!             .into_iter().chain(optional_utxos)
 | ||||
| //!             .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), (utxo, weight)| {
 | ||||
| //!                 **selected_amount += utxo.txout.value;
 | ||||
| //!                 **additional_weight += TXIN_BASE_WEIGHT + weight;
 | ||||
| //!
 | ||||
| //!                 Some(utxo)
 | ||||
| //!             .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), weighted_utxo| {
 | ||||
| //!                 **selected_amount += weighted_utxo.utxo.txout().value;
 | ||||
| //!                 **additional_weight += TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight;
 | ||||
| //!                 Some(weighted_utxo.utxo)
 | ||||
| //!             })
 | ||||
| //!             .collect::<Vec<_>>();
 | ||||
| //!         let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
 | ||||
| @ -75,7 +74,6 @@ | ||||
| //!
 | ||||
| //!         Ok(CoinSelectionResult {
 | ||||
| //!             selected: all_utxos_selected,
 | ||||
| //!             selected_amount,
 | ||||
| //!             fee_amount: fee_amount + additional_fees,
 | ||||
| //!         })
 | ||||
| //!     }
 | ||||
| @ -97,9 +95,9 @@ | ||||
| //! # Ok::<(), bdk::Error>(())
 | ||||
| //! ```
 | ||||
| 
 | ||||
| use crate::database::Database; | ||||
| use crate::error::Error; | ||||
| use crate::types::{FeeRate, UTXO}; | ||||
| use crate::types::FeeRate; | ||||
| use crate::{database::Database, WeightedUtxo}; | ||||
| use crate::{error::Error, Utxo}; | ||||
| 
 | ||||
| use rand::seq::SliceRandom; | ||||
| #[cfg(not(test))] | ||||
| @ -122,13 +120,29 @@ pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4; | ||||
| #[derive(Debug)] | ||||
| pub struct CoinSelectionResult { | ||||
|     /// List of outputs selected for use as inputs
 | ||||
|     pub selected: Vec<UTXO>, | ||||
|     /// Sum of the selected inputs' value
 | ||||
|     pub selected_amount: u64, | ||||
|     pub selected: Vec<Utxo>, | ||||
|     /// Total fee amount in satoshi
 | ||||
|     pub fee_amount: f32, | ||||
| } | ||||
| 
 | ||||
| impl CoinSelectionResult { | ||||
|     /// The total value of the inputs selected.
 | ||||
|     pub fn selected_amount(&self) -> u64 { | ||||
|         self.selected.iter().map(|u| u.txout().value).sum() | ||||
|     } | ||||
| 
 | ||||
|     /// The total value of the inputs selected from the local wallet.
 | ||||
|     pub fn local_selected_amount(&self) -> u64 { | ||||
|         self.selected | ||||
|             .iter() | ||||
|             .filter_map(|u| match u { | ||||
|                 Utxo::Local(_) => Some(u.txout().value), | ||||
|                 _ => None, | ||||
|             }) | ||||
|             .sum() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Trait for generalized coin selection algorithms
 | ||||
| ///
 | ||||
| /// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin
 | ||||
| @ -151,8 +165,8 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug { | ||||
|     fn coin_select( | ||||
|         &self, | ||||
|         database: &D, | ||||
|         required_utxos: Vec<(UTXO, usize)>, | ||||
|         optional_utxos: Vec<(UTXO, usize)>, | ||||
|         required_utxos: Vec<WeightedUtxo>, | ||||
|         optional_utxos: Vec<WeightedUtxo>, | ||||
|         fee_rate: FeeRate, | ||||
|         amount_needed: u64, | ||||
|         fee_amount: f32, | ||||
| @ -163,15 +177,15 @@ pub trait CoinSelectionAlgorithm<D: Database>: std::fmt::Debug { | ||||
| ///
 | ||||
| /// This coin selection algorithm sorts the available UTXOs by value and then picks them starting
 | ||||
| /// from the largest ones until the required amount is reached.
 | ||||
| #[derive(Debug, Default)] | ||||
| #[derive(Debug, Default, Clone, Copy)] | ||||
| pub struct LargestFirstCoinSelection; | ||||
| 
 | ||||
| impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | ||||
|     fn coin_select( | ||||
|         &self, | ||||
|         _database: &D, | ||||
|         required_utxos: Vec<(UTXO, usize)>, | ||||
|         mut optional_utxos: Vec<(UTXO, usize)>, | ||||
|         required_utxos: Vec<WeightedUtxo>, | ||||
|         mut optional_utxos: Vec<WeightedUtxo>, | ||||
|         fee_rate: FeeRate, | ||||
|         amount_needed: u64, | ||||
|         mut fee_amount: f32, | ||||
| @ -188,7 +202,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | ||||
|         // We put the "required UTXOs" first and make sure the optional UTXOs are sorted,
 | ||||
|         // initially smallest to largest, before being reversed with `.rev()`.
 | ||||
|         let utxos = { | ||||
|             optional_utxos.sort_unstable_by_key(|(utxo, _)| utxo.txout.value); | ||||
|             optional_utxos.sort_unstable_by_key(|wu| wu.utxo.txout().value); | ||||
|             required_utxos | ||||
|                 .into_iter() | ||||
|                 .map(|utxo| (true, utxo)) | ||||
| @ -201,18 +215,19 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | ||||
|         let selected = utxos | ||||
|             .scan( | ||||
|                 (&mut selected_amount, &mut fee_amount), | ||||
|                 |(selected_amount, fee_amount), (must_use, (utxo, weight))| { | ||||
|                 |(selected_amount, fee_amount), (must_use, weighted_utxo)| { | ||||
|                     if must_use || **selected_amount < amount_needed + (fee_amount.ceil() as u64) { | ||||
|                         **fee_amount += calc_fee_bytes(TXIN_BASE_WEIGHT + weight); | ||||
|                         **selected_amount += utxo.txout.value; | ||||
|                         **fee_amount += | ||||
|                             calc_fee_bytes(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight); | ||||
|                         **selected_amount += weighted_utxo.utxo.txout().value; | ||||
| 
 | ||||
|                         log::debug!( | ||||
|                             "Selected {}, updated fee_amount = `{}`", | ||||
|                             utxo.outpoint, | ||||
|                             weighted_utxo.utxo.outpoint(), | ||||
|                             fee_amount | ||||
|                         ); | ||||
| 
 | ||||
|                         Some(utxo) | ||||
|                         Some(weighted_utxo.utxo) | ||||
|                     } else { | ||||
|                         None | ||||
|                     } | ||||
| @ -231,7 +246,6 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | ||||
|         Ok(CoinSelectionResult { | ||||
|             selected, | ||||
|             fee_amount, | ||||
|             selected_amount, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @ -239,9 +253,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | ||||
| #[derive(Debug, Clone)] | ||||
| // Adds fee information to an UTXO.
 | ||||
| struct OutputGroup { | ||||
|     utxo: UTXO, | ||||
|     // weight needed to satisfy the UTXO, as described in `Descriptor::max_satisfaction_weight`
 | ||||
|     satisfaction_weight: usize, | ||||
|     weighted_utxo: WeightedUtxo, | ||||
|     // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
 | ||||
|     fee: f32, | ||||
|     // The effective value of the UTXO, i.e., the utxo value minus the fee for spending it
 | ||||
| @ -249,12 +261,12 @@ struct OutputGroup { | ||||
| } | ||||
| 
 | ||||
| impl OutputGroup { | ||||
|     fn new(utxo: UTXO, satisfaction_weight: usize, fee_rate: FeeRate) -> Self { | ||||
|         let fee = (TXIN_BASE_WEIGHT + satisfaction_weight) as f32 / 4.0 * fee_rate.as_sat_vb(); | ||||
|         let effective_value = utxo.txout.value as i64 - fee.ceil() as i64; | ||||
|     fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self { | ||||
|         let fee = (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as f32 / 4.0 | ||||
|             * fee_rate.as_sat_vb(); | ||||
|         let effective_value = weighted_utxo.utxo.txout().value as i64 - fee.ceil() as i64; | ||||
|         OutputGroup { | ||||
|             utxo, | ||||
|             satisfaction_weight, | ||||
|             weighted_utxo, | ||||
|             effective_value, | ||||
|             fee, | ||||
|         } | ||||
| @ -291,8 +303,8 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection { | ||||
|     fn coin_select( | ||||
|         &self, | ||||
|         _database: &D, | ||||
|         required_utxos: Vec<(UTXO, usize)>, | ||||
|         optional_utxos: Vec<(UTXO, usize)>, | ||||
|         required_utxos: Vec<WeightedUtxo>, | ||||
|         optional_utxos: Vec<WeightedUtxo>, | ||||
|         fee_rate: FeeRate, | ||||
|         amount_needed: u64, | ||||
|         fee_amount: f32, | ||||
| @ -300,7 +312,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection { | ||||
|         // Mapping every (UTXO, usize) to an output group
 | ||||
|         let required_utxos: Vec<OutputGroup> = required_utxos | ||||
|             .into_iter() | ||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|             .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|             .collect(); | ||||
| 
 | ||||
|         // Mapping every (UTXO, usize) to an output group.
 | ||||
| @ -308,7 +320,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection { | ||||
|         // adding them is more than their value
 | ||||
|         let optional_utxos: Vec<OutputGroup> = optional_utxos | ||||
|             .into_iter() | ||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|             .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|             .filter(|u| u.effective_value > 0) | ||||
|             .collect(); | ||||
| 
 | ||||
| @ -507,14 +519,12 @@ impl BranchAndBoundCoinSelection { | ||||
|         fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>(); | ||||
|         let selected = selected_utxos | ||||
|             .into_iter() | ||||
|             .map(|u| u.utxo) | ||||
|             .map(|u| u.weighted_utxo.utxo) | ||||
|             .collect::<Vec<_>>(); | ||||
|         let selected_amount = selected.iter().map(|u| u.txout.value).sum(); | ||||
| 
 | ||||
|         CoinSelectionResult { | ||||
|             selected, | ||||
|             fee_amount, | ||||
|             selected_amount, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -535,10 +545,11 @@ mod test { | ||||
| 
 | ||||
|     const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2; | ||||
| 
 | ||||
|     fn get_test_utxos() -> Vec<(UTXO, usize)> { | ||||
|     fn get_test_utxos() -> Vec<WeightedUtxo> { | ||||
|         vec![ | ||||
|             ( | ||||
|                 UTXO { | ||||
|             WeightedUtxo { | ||||
|                 satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||
|                 utxo: Utxo::Local(LocalUtxo { | ||||
|                     outpoint: OutPoint::from_str( | ||||
|                         "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", | ||||
|                     ) | ||||
| @ -548,11 +559,11 @@ mod test { | ||||
|                         script_pubkey: Script::new(), | ||||
|                     }, | ||||
|                     keychain: KeychainKind::External, | ||||
|                 }), | ||||
|             }, | ||||
|                 P2WPKH_WITNESS_SIZE, | ||||
|             ), | ||||
|             ( | ||||
|                 UTXO { | ||||
|             WeightedUtxo { | ||||
|                 satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||
|                 utxo: Utxo::Local(LocalUtxo { | ||||
|                     outpoint: OutPoint::from_str( | ||||
|                         "65d92ddff6b6dc72c89624a6491997714b90f6004f928d875bc0fd53f264fa85:0", | ||||
|                     ) | ||||
| @ -562,17 +573,17 @@ mod test { | ||||
|                         script_pubkey: Script::new(), | ||||
|                     }, | ||||
|                     keychain: KeychainKind::Internal, | ||||
|                 }), | ||||
|             }, | ||||
|                 P2WPKH_WITNESS_SIZE, | ||||
|             ), | ||||
|         ] | ||||
|     } | ||||
| 
 | ||||
|     fn generate_random_utxos(rng: &mut StdRng, utxos_number: usize) -> Vec<(UTXO, usize)> { | ||||
|     fn generate_random_utxos(rng: &mut StdRng, utxos_number: usize) -> Vec<WeightedUtxo> { | ||||
|         let mut res = Vec::new(); | ||||
|         for _ in 0..utxos_number { | ||||
|             res.push(( | ||||
|                 UTXO { | ||||
|             res.push(WeightedUtxo { | ||||
|                 satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||
|                 utxo: Utxo::Local(LocalUtxo { | ||||
|                     outpoint: OutPoint::from_str( | ||||
|                         "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", | ||||
|                     ) | ||||
| @ -582,16 +593,16 @@ mod test { | ||||
|                         script_pubkey: Script::new(), | ||||
|                     }, | ||||
|                     keychain: KeychainKind::External, | ||||
|                 }, | ||||
|                 P2WPKH_WITNESS_SIZE, | ||||
|             )); | ||||
|                 }), | ||||
|             }); | ||||
|         } | ||||
|         res | ||||
|     } | ||||
| 
 | ||||
|     fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<(UTXO, usize)> { | ||||
|         let utxo = ( | ||||
|             UTXO { | ||||
|     fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> { | ||||
|         let utxo = WeightedUtxo { | ||||
|             satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||
|             utxo: Utxo::Local(LocalUtxo { | ||||
|                 outpoint: OutPoint::from_str( | ||||
|                     "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", | ||||
|                 ) | ||||
| @ -601,18 +612,18 @@ mod test { | ||||
|                     script_pubkey: Script::new(), | ||||
|                 }, | ||||
|                 keychain: KeychainKind::External, | ||||
|             }, | ||||
|             P2WPKH_WITNESS_SIZE, | ||||
|         ); | ||||
|             }), | ||||
|         }; | ||||
|         vec![utxo; utxos_number] | ||||
|     } | ||||
| 
 | ||||
|     fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<(UTXO, usize)>) -> u64 { | ||||
|     fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<WeightedUtxo>) -> u64 { | ||||
|         let utxos_picked_len = rng.gen_range(2, utxos.len() / 2); | ||||
|         utxos.shuffle(&mut rng); | ||||
|         utxos[..utxos_picked_len] | ||||
|             .iter() | ||||
|             .fold(0, |acc, x| acc + x.0.txout.value) | ||||
|             .map(|u| u.utxo.txout().value) | ||||
|             .sum() | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
| @ -632,7 +643,7 @@ mod test { | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         assert_eq!(result.selected.len(), 2); | ||||
|         assert_eq!(result.selected_amount, 300_000); | ||||
|         assert_eq!(result.selected_amount(), 300_000); | ||||
|         assert_eq!(result.fee_amount, 186.0); | ||||
|     } | ||||
| 
 | ||||
| @ -653,7 +664,7 @@ mod test { | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         assert_eq!(result.selected.len(), 2); | ||||
|         assert_eq!(result.selected_amount, 300_000); | ||||
|         assert_eq!(result.selected_amount(), 300_000); | ||||
|         assert_eq!(result.fee_amount, 186.0); | ||||
|     } | ||||
| 
 | ||||
| @ -674,7 +685,7 @@ mod test { | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         assert_eq!(result.selected.len(), 1); | ||||
|         assert_eq!(result.selected_amount, 200_000); | ||||
|         assert_eq!(result.selected_amount(), 200_000); | ||||
|         assert_eq!(result.fee_amount, 118.0); | ||||
|     } | ||||
| 
 | ||||
| @ -734,7 +745,7 @@ mod test { | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         assert_eq!(result.selected.len(), 3); | ||||
|         assert_eq!(result.selected_amount, 300_000); | ||||
|         assert_eq!(result.selected_amount(), 300_000); | ||||
|         assert_eq!(result.fee_amount, 254.0); | ||||
|     } | ||||
| 
 | ||||
| @ -755,7 +766,7 @@ mod test { | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         assert_eq!(result.selected.len(), 2); | ||||
|         assert_eq!(result.selected_amount, 300_000); | ||||
|         assert_eq!(result.selected_amount(), 300_000); | ||||
|         assert_eq!(result.fee_amount, 186.0); | ||||
|     } | ||||
| 
 | ||||
| @ -812,7 +823,7 @@ mod test { | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         assert_eq!(result.selected.len(), 1); | ||||
|         assert_eq!(result.selected_amount, 100_000); | ||||
|         assert_eq!(result.selected_amount(), 100_000); | ||||
|         let input_size = (TXIN_BASE_WEIGHT as f32) / 4.0 + P2WPKH_WITNESS_SIZE as f32 / 4.0; | ||||
|         let epsilon = 0.5; | ||||
|         assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon); | ||||
| @ -837,7 +848,7 @@ mod test { | ||||
|                     0.0, | ||||
|                 ) | ||||
|                 .unwrap(); | ||||
|             assert_eq!(result.selected_amount, target_amount); | ||||
|             assert_eq!(result.selected_amount(), target_amount); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -847,7 +858,7 @@ mod test { | ||||
|         let fee_rate = FeeRate::from_sat_per_vb(10.0); | ||||
|         let utxos: Vec<OutputGroup> = get_test_utxos() | ||||
|             .into_iter() | ||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|             .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let curr_available_value = utxos | ||||
| @ -875,7 +886,7 @@ mod test { | ||||
|         let fee_rate = FeeRate::from_sat_per_vb(10.0); | ||||
|         let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000) | ||||
|             .into_iter() | ||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|             .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let curr_available_value = utxos | ||||
| @ -908,7 +919,7 @@ mod test { | ||||
| 
 | ||||
|         let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) | ||||
|             .into_iter() | ||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|             .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let curr_value = 0; | ||||
| @ -933,7 +944,7 @@ mod test { | ||||
|             ) | ||||
|             .unwrap(); | ||||
|         assert_eq!(result.fee_amount, 186.0); | ||||
|         assert_eq!(result.selected_amount, 100_000); | ||||
|         assert_eq!(result.selected_amount(), 100_000); | ||||
|     } | ||||
| 
 | ||||
|     // TODO: bnb() function should be optimized, and this test should be done with more utxos
 | ||||
| @ -946,7 +957,7 @@ mod test { | ||||
|         for _ in 0..200 { | ||||
|             let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40) | ||||
|                 .into_iter() | ||||
|                 .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|                 .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|                 .collect(); | ||||
| 
 | ||||
|             let curr_value = 0; | ||||
| @ -969,7 +980,7 @@ mod test { | ||||
|                     0.0, | ||||
|                 ) | ||||
|                 .unwrap(); | ||||
|             assert_eq!(result.selected_amount, target_amount); | ||||
|             assert_eq!(result.selected_amount(), target_amount); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -983,7 +994,7 @@ mod test { | ||||
|         let fee_rate = FeeRate::from_sat_per_vb(1.0); | ||||
|         let utxos: Vec<OutputGroup> = utxos | ||||
|             .into_iter() | ||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) | ||||
|             .map(|u| OutputGroup::new(u, fee_rate)) | ||||
|             .collect(); | ||||
| 
 | ||||
|         let result = BranchAndBoundCoinSelection::default().single_random_draw( | ||||
| @ -994,7 +1005,7 @@ mod test { | ||||
|             50.0, | ||||
|         ); | ||||
| 
 | ||||
|         assert!(result.selected_amount > target_amount); | ||||
|         assert!(result.selected_amount() > target_amount); | ||||
|         assert_eq!( | ||||
|             result.fee_amount, | ||||
|             50.0 + result.selected.len() as f32 * 68.0 | ||||
|  | ||||
| @ -199,13 +199,13 @@ where | ||||
|     ///
 | ||||
|     /// Note that this methods only operate on the internal database, which first needs to be
 | ||||
|     /// [`Wallet::sync`] manually.
 | ||||
|     pub fn list_unspent(&self) -> Result<Vec<UTXO>, Error> { | ||||
|     pub fn list_unspent(&self) -> Result<Vec<LocalUtxo>, Error> { | ||||
|         self.database.borrow().iter_utxos() | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
 | ||||
|     /// wallet's database.
 | ||||
|     pub fn get_utxo(&self, outpoint: OutPoint) -> Result<Option<UTXO>, Error> { | ||||
|     pub fn get_utxo(&self, outpoint: OutPoint) -> Result<Option<LocalUtxo>, Error> { | ||||
|         self.database.borrow().get_utxo(&outpoint) | ||||
|     } | ||||
| 
 | ||||
| @ -513,11 +513,7 @@ where | ||||
|             params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
 | ||||
|         )?; | ||||
| 
 | ||||
|         let coin_selection::CoinSelectionResult { | ||||
|             selected, | ||||
|             selected_amount, | ||||
|             mut fee_amount, | ||||
|         } = coin_selection.coin_select( | ||||
|         let coin_selection = coin_selection.coin_select( | ||||
|             self.database.borrow().deref(), | ||||
|             required_utxos, | ||||
|             optional_utxos, | ||||
| @ -525,10 +521,13 @@ where | ||||
|             outgoing, | ||||
|             fee_amount, | ||||
|         )?; | ||||
|         tx.input = selected | ||||
|         let mut fee_amount = coin_selection.fee_amount; | ||||
| 
 | ||||
|         tx.input = coin_selection | ||||
|             .selected | ||||
|             .iter() | ||||
|             .map(|u| bitcoin::TxIn { | ||||
|                 previous_output: u.outpoint, | ||||
|                 previous_output: u.outpoint(), | ||||
|                 script_sig: Script::default(), | ||||
|                 sequence: n_sequence, | ||||
|                 witness: vec![], | ||||
| @ -550,9 +549,8 @@ where | ||||
|                 Some(change_output) | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         let mut fee_amount = fee_amount.ceil() as u64; | ||||
|         let change_val = (selected_amount - outgoing).saturating_sub(fee_amount); | ||||
|         let change_val = (coin_selection.selected_amount() - outgoing).saturating_sub(fee_amount); | ||||
| 
 | ||||
|         match change_output { | ||||
|             None if change_val.is_dust() => { | ||||
| @ -588,14 +586,15 @@ where | ||||
|         params.ordering.sort_tx(&mut tx); | ||||
| 
 | ||||
|         let txid = tx.txid(); | ||||
|         let psbt = self.complete_transaction(tx, selected, params)?; | ||||
|         let sent = coin_selection.local_selected_amount(); | ||||
|         let psbt = self.complete_transaction(tx, coin_selection.selected, params)?; | ||||
| 
 | ||||
|         let transaction_details = TransactionDetails { | ||||
|             transaction: None, | ||||
|             txid, | ||||
|             timestamp: time::get_timestamp(), | ||||
|             received, | ||||
|             sent: selected_amount, | ||||
|             sent, | ||||
|             fees: fee_amount, | ||||
|             height: None, | ||||
|         }; | ||||
| @ -699,13 +698,16 @@ where | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 let utxo = UTXO { | ||||
|                 let utxo = LocalUtxo { | ||||
|                     outpoint: txin.previous_output, | ||||
|                     txout, | ||||
|                     keychain, | ||||
|                 }; | ||||
| 
 | ||||
|                 Ok((utxo, weight)) | ||||
|                 Ok(WeightedUtxo { | ||||
|                     satisfaction_weight: weight, | ||||
|                     utxo: Utxo::Local(utxo), | ||||
|                 }) | ||||
|             }) | ||||
|             .collect::<Result<Vec<_>, _>>()?; | ||||
| 
 | ||||
| @ -1016,7 +1018,7 @@ where | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn get_available_utxos(&self) -> Result<Vec<(UTXO, usize)>, Error> { | ||||
|     fn get_available_utxos(&self) -> Result<Vec<(LocalUtxo, usize)>, Error> { | ||||
|         Ok(self | ||||
|             .list_unspent()? | ||||
|             .into_iter() | ||||
| @ -1039,18 +1041,18 @@ where | ||||
|         &self, | ||||
|         change_policy: tx_builder::ChangeSpendPolicy, | ||||
|         unspendable: &HashSet<OutPoint>, | ||||
|         manually_selected: Vec<(UTXO, usize)>, | ||||
|         manually_selected: Vec<WeightedUtxo>, | ||||
|         must_use_all_available: bool, | ||||
|         manual_only: bool, | ||||
|         must_only_use_confirmed_tx: bool, | ||||
|     ) -> Result<(Vec<(UTXO, usize)>, Vec<(UTXO, usize)>), Error> { | ||||
|     ) -> Result<(Vec<WeightedUtxo>, Vec<WeightedUtxo>), Error> { | ||||
|         //    must_spend <- manually selected utxos
 | ||||
|         //    may_spend  <- all other available utxos
 | ||||
|         let mut may_spend = self.get_available_utxos()?; | ||||
|         may_spend.retain(|may_spend| { | ||||
|             manually_selected | ||||
|                 .iter() | ||||
|                 .find(|manually_selected| manually_selected.0.outpoint == may_spend.0.outpoint) | ||||
|                 .find(|manually_selected| manually_selected.utxo.outpoint() == may_spend.0.outpoint) | ||||
|                 .is_none() | ||||
|         }); | ||||
|         let mut must_spend = manually_selected; | ||||
| @ -1088,6 +1090,14 @@ where | ||||
|             retain | ||||
|         }); | ||||
| 
 | ||||
|         let mut may_spend = may_spend | ||||
|             .into_iter() | ||||
|             .map(|(local_utxo, satisfaction_weight)| WeightedUtxo { | ||||
|                 satisfaction_weight, | ||||
|                 utxo: Utxo::Local(local_utxo), | ||||
|             }) | ||||
|             .collect(); | ||||
| 
 | ||||
|         if must_use_all_available { | ||||
|             must_spend.append(&mut may_spend); | ||||
|         } | ||||
| @ -1098,7 +1108,7 @@ where | ||||
|     fn complete_transaction( | ||||
|         &self, | ||||
|         tx: Transaction, | ||||
|         selected: Vec<UTXO>, | ||||
|         selected: Vec<Utxo>, | ||||
|         params: TxParams, | ||||
|     ) -> Result<PSBT, Error> { | ||||
|         use bitcoin::util::psbt::serialize::Serialize; | ||||
| @ -1131,9 +1141,9 @@ where | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let lookup_output = selected | ||||
|         let mut lookup_output = selected | ||||
|             .into_iter() | ||||
|             .map(|utxo| (utxo.outpoint, utxo)) | ||||
|             .map(|utxo| (utxo.outpoint(), utxo)) | ||||
|             .collect::<HashMap<_, _>>(); | ||||
| 
 | ||||
|         // add metadata for the inputs
 | ||||
| @ -1142,7 +1152,7 @@ where | ||||
|             .iter_mut() | ||||
|             .zip(psbt.global.unsigned_tx.input.iter()) | ||||
|         { | ||||
|             let utxo = match lookup_output.get(&input.previous_output) { | ||||
|             let utxo = match lookup_output.remove(&input.previous_output) { | ||||
|                 Some(utxo) => utxo, | ||||
|                 None => continue, | ||||
|             }; | ||||
| @ -1153,6 +1163,8 @@ where | ||||
|                 psbt_input.sighash_type = Some(sighash_type); | ||||
|             } | ||||
| 
 | ||||
|             match utxo { | ||||
|                 Utxo::Local(utxo) => { | ||||
|                     // Try to find the prev_script in our db to figure out if this is internal or external,
 | ||||
|                     // and the derivation index
 | ||||
|                     let (keychain, child) = match self | ||||
| @ -1164,7 +1176,7 @@ where | ||||
|                         None => continue, | ||||
|                     }; | ||||
| 
 | ||||
|             let (desc, _) = self._get_descriptor_for_keychain(keychain); | ||||
|                     let desc = self.get_descriptor_for_keychain(keychain); | ||||
|                     let derived_descriptor = desc.as_derived(child, &self.secp); | ||||
|                     psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?; | ||||
| 
 | ||||
| @ -1182,6 +1194,22 @@ where | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 Utxo::Foreign { | ||||
|                     psbt_input: foreign_psbt_input, | ||||
|                     outpoint, | ||||
|                 } => { | ||||
|                     if params.force_non_witness_utxo | ||||
|                         && foreign_psbt_input.non_witness_utxo.is_none() | ||||
|                     { | ||||
|                         return Err(Error::Generic(format!( | ||||
|                             "Missing non_witness_utxo on foreign utxo {}", | ||||
|                             outpoint | ||||
|                         ))); | ||||
|                     } | ||||
|                     *psbt_input = *foreign_psbt_input; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // probably redundant but it doesn't hurt...
 | ||||
|         self.add_input_hd_keypaths(&mut psbt)?; | ||||
| @ -1348,7 +1376,7 @@ where | ||||
| mod test { | ||||
|     use std::str::FromStr; | ||||
| 
 | ||||
|     use bitcoin::Network; | ||||
|     use bitcoin::{util::psbt, Network}; | ||||
| 
 | ||||
|     use crate::database::memory::MemoryDatabase; | ||||
|     use crate::database::Database; | ||||
| @ -2237,6 +2265,187 @@ mod test { | ||||
|         assert_eq!(psbt.global.unknown.get(&psbt_key), Some(&value_bytes)); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_add_foreign_utxo() { | ||||
|         let (wallet1, _, _) = get_funded_wallet(get_test_wpkh()); | ||||
|         let (wallet2, _, _) = | ||||
|             get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); | ||||
|         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); | ||||
|         let utxo = wallet2.list_unspent().unwrap().remove(0); | ||||
|         let foreign_utxo_satisfaction = wallet2 | ||||
|             .get_descriptor_for_keychain(KeychainKind::External) | ||||
|             .max_satisfaction_weight() | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         let psbt_input = psbt::Input { | ||||
|             witness_utxo: Some(utxo.txout.clone()), | ||||
|             ..Default::default() | ||||
|         }; | ||||
| 
 | ||||
|         let mut builder = wallet1.build_tx(); | ||||
|         builder | ||||
|             .add_recipient(addr.script_pubkey(), 60_000) | ||||
|             .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) | ||||
|             .unwrap(); | ||||
|         let (psbt, details) = builder.finish().unwrap(); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             details.sent - details.received, | ||||
|             10_000 + details.fees, | ||||
|             "we should have only net spent ~10_000" | ||||
|         ); | ||||
| 
 | ||||
|         assert!( | ||||
|             psbt.global | ||||
|                 .unsigned_tx | ||||
|                 .input | ||||
|                 .iter() | ||||
|                 .find(|input| input.previous_output == utxo.outpoint) | ||||
|                 .is_some(), | ||||
|             "foreign_utxo should be in there" | ||||
|         ); | ||||
| 
 | ||||
|         let (psbt, finished) = wallet1.sign(psbt, None).unwrap(); | ||||
| 
 | ||||
|         assert!( | ||||
|             !finished, | ||||
|             "only one of the inputs should have been signed so far" | ||||
|         ); | ||||
| 
 | ||||
|         let (_, finished) = wallet2.sign(psbt, None).unwrap(); | ||||
|         assert!(finished, "all the inputs should have been signed now"); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic(expected = "Generic(\"Foreign utxo missing witness_utxo or non_witness_utxo\")")] | ||||
|     fn test_add_foreign_utxo_invalid_psbt_input() { | ||||
|         let (wallet, _, _) = get_funded_wallet(get_test_wpkh()); | ||||
|         let mut builder = wallet.build_tx(); | ||||
|         let outpoint = wallet.list_unspent().unwrap()[0].outpoint; | ||||
|         let foreign_utxo_satisfaction = wallet | ||||
|             .get_descriptor_for_keychain(KeychainKind::External) | ||||
|             .max_satisfaction_weight() | ||||
|             .unwrap(); | ||||
|         builder | ||||
|             .add_foreign_utxo(outpoint, psbt::Input::default(), foreign_utxo_satisfaction) | ||||
|             .unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_add_foreign_utxo_where_outpoint_doesnt_match_psbt_input() { | ||||
|         let (wallet1, _, txid1) = get_funded_wallet(get_test_wpkh()); | ||||
|         let (wallet2, _, txid2) = | ||||
|             get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); | ||||
| 
 | ||||
|         let utxo2 = wallet2.list_unspent().unwrap().remove(0); | ||||
|         let tx1 = wallet1 | ||||
|             .database | ||||
|             .borrow() | ||||
|             .get_tx(&txid1, true) | ||||
|             .unwrap() | ||||
|             .unwrap() | ||||
|             .transaction | ||||
|             .unwrap(); | ||||
|         let tx2 = wallet2 | ||||
|             .database | ||||
|             .borrow() | ||||
|             .get_tx(&txid2, true) | ||||
|             .unwrap() | ||||
|             .unwrap() | ||||
|             .transaction | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         let satisfaction_weight = wallet2 | ||||
|             .get_descriptor_for_keychain(KeychainKind::External) | ||||
|             .max_satisfaction_weight() | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         let mut builder = wallet1.build_tx(); | ||||
|         assert!( | ||||
|             builder | ||||
|                 .add_foreign_utxo( | ||||
|                     utxo2.outpoint, | ||||
|                     psbt::Input { | ||||
|                         non_witness_utxo: Some(tx1), | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                     satisfaction_weight | ||||
|                 ) | ||||
|                 .is_err(), | ||||
|             "should fail when outpoint doesn't match psbt_input" | ||||
|         ); | ||||
|         assert!( | ||||
|             builder | ||||
|                 .add_foreign_utxo( | ||||
|                     utxo2.outpoint, | ||||
|                     psbt::Input { | ||||
|                         non_witness_utxo: Some(tx2), | ||||
|                         ..Default::default() | ||||
|                     }, | ||||
|                     satisfaction_weight | ||||
|                 ) | ||||
|                 .is_ok(), | ||||
|             "shoulld be ok when outpoint does match psbt_input" | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn test_add_foreign_utxo_force_non_witness_utxo() { | ||||
|         let (wallet1, _, _) = get_funded_wallet(get_test_wpkh()); | ||||
|         let (wallet2, _, txid2) = | ||||
|             get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)"); | ||||
|         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap(); | ||||
|         let utxo2 = wallet2.list_unspent().unwrap().remove(0); | ||||
| 
 | ||||
|         let satisfaction_weight = wallet2 | ||||
|             .get_descriptor_for_keychain(KeychainKind::External) | ||||
|             .max_satisfaction_weight() | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         let mut builder = wallet1.build_tx(); | ||||
|         builder | ||||
|             .add_recipient(addr.script_pubkey(), 60_000) | ||||
|             .force_non_witness_utxo(); | ||||
| 
 | ||||
|         { | ||||
|             let mut builder = builder.clone(); | ||||
|             let psbt_input = psbt::Input { | ||||
|                 witness_utxo: Some(utxo2.txout.clone()), | ||||
|                 ..Default::default() | ||||
|             }; | ||||
|             builder | ||||
|                 .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) | ||||
|                 .unwrap(); | ||||
|             assert!( | ||||
|                 builder.finish().is_err(), | ||||
|                 "psbt_input with witness_utxo should fail with only witness_utxo" | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         { | ||||
|             let mut builder = builder.clone(); | ||||
|             let tx2 = wallet2 | ||||
|                 .database | ||||
|                 .borrow() | ||||
|                 .get_tx(&txid2, true) | ||||
|                 .unwrap() | ||||
|                 .unwrap() | ||||
|                 .transaction | ||||
|                 .unwrap(); | ||||
|             let psbt_input = psbt::Input { | ||||
|                 non_witness_utxo: Some(tx2), | ||||
|                 ..Default::default() | ||||
|             }; | ||||
|             builder | ||||
|                 .add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight) | ||||
|                 .unwrap(); | ||||
|             assert!( | ||||
|                 builder.finish().is_ok(), | ||||
|                 "psbt_input with non_witness_utxo should succeed with force_non_witness_utxo" | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     #[should_panic(
 | ||||
|         expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")" | ||||
|  | ||||
| @ -54,15 +54,15 @@ use std::collections::HashSet; | ||||
| use std::default::Default; | ||||
| use std::marker::PhantomData; | ||||
| 
 | ||||
| use bitcoin::util::psbt::PartiallySignedTransaction as PSBT; | ||||
| use bitcoin::util::psbt::{self, PartiallySignedTransaction as PSBT}; | ||||
| use bitcoin::{OutPoint, Script, SigHashType, Transaction}; | ||||
| 
 | ||||
| use miniscript::descriptor::DescriptorTrait; | ||||
| 
 | ||||
| use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; | ||||
| use crate::{database::BatchDatabase, Error, Wallet}; | ||||
| use crate::{database::BatchDatabase, Error, Utxo, Wallet}; | ||||
| use crate::{ | ||||
|     types::{FeeRate, KeychainKind, UTXO}, | ||||
|     types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo}, | ||||
|     TransactionDetails, | ||||
| }; | ||||
| /// Context in which the [`TxBuilder`] is valid
 | ||||
| @ -129,7 +129,7 @@ impl TxBuilderContext for BumpFee {} | ||||
| /// [`build_fee_bump`]: Wallet::build_fee_bump
 | ||||
| /// [`finish`]: Self::finish
 | ||||
| /// [`coin_selection`]: Self::coin_selection
 | ||||
| #[derive(Clone, Debug)] | ||||
| #[derive(Debug)] | ||||
| pub struct TxBuilder<'a, B, D, Cs, Ctx> { | ||||
|     pub(crate) wallet: &'a Wallet<B, D>, | ||||
|     // params and coin_selection are Options not becasue they are optionally set (they are always
 | ||||
| @ -150,7 +150,7 @@ pub(crate) struct TxParams { | ||||
|     pub(crate) fee_policy: Option<FeePolicy>, | ||||
|     pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, | ||||
|     pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>, | ||||
|     pub(crate) utxos: Vec<(UTXO, usize)>, | ||||
|     pub(crate) utxos: Vec<WeightedUtxo>, | ||||
|     pub(crate) unspendable: HashSet<OutPoint>, | ||||
|     pub(crate) manually_selected_only: bool, | ||||
|     pub(crate) sighash: Option<SigHashType>, | ||||
| @ -183,6 +183,17 @@ impl std::default::Default for FeePolicy { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> { | ||||
|     fn clone(&self) -> Self { | ||||
|         TxBuilder { | ||||
|             wallet: self.wallet, | ||||
|             params: self.params.clone(), | ||||
|             coin_selection: self.coin_selection.clone(), | ||||
|             phantom: PhantomData, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // methods supported by both contexts, for any CoinSelectionAlgorithm
 | ||||
| impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> | ||||
|     TxBuilder<'a, B, D, Cs, Ctx> | ||||
| @ -286,7 +297,10 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte | ||||
|         for utxo in utxos { | ||||
|             let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); | ||||
|             let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap(); | ||||
|             self.params.utxos.push((utxo, satisfaction_weight)); | ||||
|             self.params.utxos.push(WeightedUtxo { | ||||
|                 satisfaction_weight, | ||||
|                 utxo: Utxo::Local(utxo), | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         Ok(self) | ||||
| @ -300,6 +314,84 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte | ||||
|         self.add_utxos(&[outpoint]) | ||||
|     } | ||||
| 
 | ||||
|     /// Add a foreign UTXO i.e. a UTXO not owned by this wallet.
 | ||||
|     ///
 | ||||
|     /// At a minimum to add a foreign UTXO we need:
 | ||||
|     ///
 | ||||
|     /// 1. `outpoint`: To add it to the raw transaction.
 | ||||
|     /// 2. `psbt_input`: To know the value.
 | ||||
|     /// 3. `satisfaction_weight`: To know how much weight/vbytes the input will add to the transaction for fee calculation.
 | ||||
|     ///
 | ||||
|     /// There are several security concerns about adding foregin UTXOs that application
 | ||||
|     /// developers should consider. First, how do you know the value of the input is correct? If a
 | ||||
|     /// `non_witness_utxo` is provided in the `psbt_input` then this method implicitly verifies the
 | ||||
|     /// value by checking it against the transaction. If only a `witness_utxo` is provided then this
 | ||||
|     /// method doesn't verify the value but just takes it as a given -- it is up to you to check
 | ||||
|     /// that whoever sent you the `input_psbt` was not lying!
 | ||||
|     ///
 | ||||
|     /// Secondly, you must somehow provide `satisfaction_weight` of the input. Depending on your
 | ||||
|     /// application it may be important that this be known precisely. If not, a malicious
 | ||||
|     /// counterparty may fool you into putting in a value that is too low, giving the transaction a
 | ||||
|     /// lower than expected feerate. They could also fool you into putting a value that is too high
 | ||||
|     /// causing you to pay a fee that is too high. The party who is broadcasting the transaction can
 | ||||
|     /// of course check the real input weight matches the expected weight prior to broadcasting.
 | ||||
|     ///
 | ||||
|     /// To guarantee the `satisfaction_weight` is correct, you can require the party providing the
 | ||||
|     /// `psbt_input` provide a miniscript descriptor for the input so you can check it against the
 | ||||
|     /// `script_pubkey` and then ask it for the [`max_satisfaction_weight`].
 | ||||
|     ///
 | ||||
|     /// This is an **EXPERIMENTAL** feature, API and other major changes are expected.
 | ||||
|     ///
 | ||||
|     /// # Errors
 | ||||
|     ///
 | ||||
|     /// This method returns errors in the following circumstances:
 | ||||
|     ///
 | ||||
|     /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
 | ||||
|     /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
 | ||||
|     ///
 | ||||
|     /// Note if you set [`force_non_witness_utxo`] any `psbt_input` you pass to this method must
 | ||||
|     /// have `non_witness_utxo` set otherwise you will get an error when [`finish`] is called.
 | ||||
|     ///
 | ||||
|     /// [`force_non_witness_utxo`]: Self::force_non_witness_utxo
 | ||||
|     /// [`finish`]: Self::finish
 | ||||
|     /// [`max_satisfaction_weight`]: miniscript::Descriptor::max_satisfaction_weight
 | ||||
|     pub fn add_foreign_utxo( | ||||
|         &mut self, | ||||
|         outpoint: OutPoint, | ||||
|         psbt_input: psbt::Input, | ||||
|         satisfaction_weight: usize, | ||||
|     ) -> Result<&mut Self, Error> { | ||||
|         if psbt_input.witness_utxo.is_none() { | ||||
|             match psbt_input.non_witness_utxo.as_ref() { | ||||
|                 Some(tx) => { | ||||
|                     if tx.txid() != outpoint.txid { | ||||
|                         return Err(Error::Generic( | ||||
|                             "Foreign utxo outpoint does not match PSBT input".into(), | ||||
|                         )); | ||||
|                     } | ||||
|                     if tx.output.len() <= outpoint.vout as usize { | ||||
|                         return Err(Error::InvalidOutpoint(outpoint)); | ||||
|                     } | ||||
|                 } | ||||
|                 None => { | ||||
|                     return Err(Error::Generic( | ||||
|                         "Foreign utxo missing witness_utxo or non_witness_utxo".into(), | ||||
|                     )) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         self.params.utxos.push(WeightedUtxo { | ||||
|             satisfaction_weight, | ||||
|             utxo: Utxo::Foreign { | ||||
|                 outpoint, | ||||
|                 psbt_input: Box::new(psbt_input), | ||||
|             }, | ||||
|         }); | ||||
| 
 | ||||
|         Ok(self) | ||||
|     } | ||||
| 
 | ||||
|     /// Only spend utxos added by [`add_utxo`].
 | ||||
|     ///
 | ||||
|     /// The wallet will **not** add additional utxos to the transaction even if they are needed to
 | ||||
| @ -618,7 +710,7 @@ impl Default for ChangeSpendPolicy { | ||||
| } | ||||
| 
 | ||||
| impl ChangeSpendPolicy { | ||||
|     pub(crate) fn is_satisfied_by(&self, utxo: &UTXO) -> bool { | ||||
|     pub(crate) fn is_satisfied_by(&self, utxo: &LocalUtxo) -> bool { | ||||
|         match self { | ||||
|             ChangeSpendPolicy::ChangeAllowed => true, | ||||
|             ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal, | ||||
| @ -709,9 +801,9 @@ mod test { | ||||
|         assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE])); | ||||
|     } | ||||
| 
 | ||||
|     fn get_test_utxos() -> Vec<UTXO> { | ||||
|     fn get_test_utxos() -> Vec<LocalUtxo> { | ||||
|         vec![ | ||||
|             UTXO { | ||||
|             LocalUtxo { | ||||
|                 outpoint: OutPoint { | ||||
|                     txid: Default::default(), | ||||
|                     vout: 0, | ||||
| @ -719,7 +811,7 @@ mod test { | ||||
|                 txout: Default::default(), | ||||
|                 keychain: KeychainKind::External, | ||||
|             }, | ||||
|             UTXO { | ||||
|             LocalUtxo { | ||||
|                 outpoint: OutPoint { | ||||
|                     txid: Default::default(), | ||||
|                     vout: 1, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user