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. |   type to mark for a missing client. | ||||||
| - Upgrade `tokio` to `1.0`. | - 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 | 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. | 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_utxo` | ||||||
| - Added `Wallet::get_descriptor_for_keychain` | - 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 | ### CLI | ||||||
| #### Changed | #### Changed | ||||||
| - Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository | - 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 super::{Blockchain, Capability, ConfigurableBlockchain, Progress}; | ||||||
| use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | ||||||
| use crate::error::Error; | use crate::error::Error; | ||||||
| use crate::types::{KeychainKind, TransactionDetails, UTXO}; | use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; | ||||||
| use crate::FeeRate; | use crate::FeeRate; | ||||||
| 
 | 
 | ||||||
| use peer::*; | use peer::*; | ||||||
| @ -194,7 +194,7 @@ impl CompactFiltersBlockchain { | |||||||
|                 database.get_path_from_script_pubkey(&output.script_pubkey)? |                 database.get_path_from_script_pubkey(&output.script_pubkey)? | ||||||
|             { |             { | ||||||
|                 debug!("{} output #{} is mine, adding utxo", tx.txid(), i); |                 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), |                     outpoint: OutPoint::new(tx.txid(), i as u32), | ||||||
|                     txout: output.clone(), |                     txout: output.clone(), | ||||||
|                     keychain, |                     keychain, | ||||||
|  | |||||||
| @ -34,7 +34,7 @@ use bitcoin::{BlockHeader, OutPoint, Script, Transaction, Txid}; | |||||||
| use super::*; | use super::*; | ||||||
| use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; | ||||||
| use crate::error::Error; | use crate::error::Error; | ||||||
| use crate::types::{KeychainKind, TransactionDetails, UTXO}; | use crate::types::{KeychainKind, LocalUtxo, TransactionDetails}; | ||||||
| use crate::wallet::time::Instant; | use crate::wallet::time::Instant; | ||||||
| use crate::wallet::utils::ChunksIterator; | 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
 |         // 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)? { |         if let Some((keychain, _child)) = db.get_path_from_script_pubkey(&output.script_pubkey)? { | ||||||
|             debug!("{} output #{} is mine, adding utxo", txid, i); |             debug!("{} output #{} is mine, adding utxo", txid, i); | ||||||
|             updates.set_utxo(&UTXO { |             updates.set_utxo(&LocalUtxo { | ||||||
|                 outpoint: OutPoint::new(tx.txid(), i as u32), |                 outpoint: OutPoint::new(tx.txid(), i as u32), | ||||||
|                 txout: output.clone(), |                 txout: output.clone(), | ||||||
|                 keychain, |                 keychain, | ||||||
|  | |||||||
| @ -133,7 +133,7 @@ impl BatchOperations for AnyDatabase { | |||||||
|             child |             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) |         impl_inner_method!(AnyDatabase, self, set_utxo, utxo) | ||||||
|     } |     } | ||||||
|     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { |     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { | ||||||
| @ -165,7 +165,7 @@ impl BatchOperations for AnyDatabase { | |||||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error> { |     ) -> Result<Option<(KeychainKind, u32)>, Error> { | ||||||
|         impl_inner_method!(AnyDatabase, self, del_path_from_script_pubkey, script) |         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) |         impl_inner_method!(AnyDatabase, self, del_utxo, outpoint) | ||||||
|     } |     } | ||||||
|     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { |     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> { |     fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> { | ||||||
|         impl_inner_method!(AnyDatabase, self, iter_script_pubkeys, keychain) |         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) |         impl_inner_method!(AnyDatabase, self, iter_utxos) | ||||||
|     } |     } | ||||||
|     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> { |     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> { | ||||||
| @ -230,7 +230,7 @@ impl Database for AnyDatabase { | |||||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error> { |     ) -> Result<Option<(KeychainKind, u32)>, Error> { | ||||||
|         impl_inner_method!(AnyDatabase, self, get_path_from_script_pubkey, script) |         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) |         impl_inner_method!(AnyDatabase, self, get_utxo, outpoint) | ||||||
|     } |     } | ||||||
|     fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> { |     fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> { | ||||||
| @ -257,7 +257,7 @@ impl BatchOperations for AnyBatch { | |||||||
|     ) -> Result<(), Error> { |     ) -> Result<(), Error> { | ||||||
|         impl_inner_method!(AnyBatch, self, set_script_pubkey, script, keychain, child) |         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) |         impl_inner_method!(AnyBatch, self, set_utxo, utxo) | ||||||
|     } |     } | ||||||
|     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { |     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { | ||||||
| @ -283,7 +283,7 @@ impl BatchOperations for AnyBatch { | |||||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error> { |     ) -> Result<Option<(KeychainKind, u32)>, Error> { | ||||||
|         impl_inner_method!(AnyBatch, self, del_path_from_script_pubkey, script) |         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) |         impl_inner_method!(AnyBatch, self, del_utxo, outpoint) | ||||||
|     } |     } | ||||||
|     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { |     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ macro_rules! impl_batch_operations { | |||||||
|             Ok(()) |             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 key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); | ||||||
|             let value = json!({ |             let value = json!({ | ||||||
|                 "t": utxo.txout, |                 "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 key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||||
|             let res = self.remove(key); |             let res = self.remove(key); | ||||||
|             let res = $process_delete!(res); |             let res = $process_delete!(res); | ||||||
| @ -132,7 +132,7 @@ macro_rules! impl_batch_operations { | |||||||
|                     let txout = serde_json::from_value(val["t"].take())?; |                     let txout = serde_json::from_value(val["t"].take())?; | ||||||
|                     let keychain = serde_json::from_value(val["i"].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() |             .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(); |         let key = MapKey::UTXO(None).as_map_key(); | ||||||
|         self.scan_prefix(key) |         self.scan_prefix(key) | ||||||
|             .map(|x| -> Result<_, Error> { |             .map(|x| -> Result<_, Error> { | ||||||
| @ -245,7 +245,7 @@ impl Database for Tree { | |||||||
|                 let txout = serde_json::from_value(val["t"].take())?; |                 let txout = serde_json::from_value(val["t"].take())?; | ||||||
|                 let keychain = serde_json::from_value(val["i"].take())?; |                 let keychain = serde_json::from_value(val["i"].take())?; | ||||||
| 
 | 
 | ||||||
|                 Ok(UTXO { |                 Ok(LocalUtxo { | ||||||
|                     outpoint, |                     outpoint, | ||||||
|                     txout, |                     txout, | ||||||
|                     keychain, |                     keychain, | ||||||
| @ -305,7 +305,7 @@ impl Database for Tree { | |||||||
|             .transpose() |             .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(); |         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||||
|         self.get(key)? |         self.get(key)? | ||||||
|             .map(|b| -> Result<_, Error> { |             .map(|b| -> Result<_, Error> { | ||||||
| @ -313,7 +313,7 @@ impl Database for Tree { | |||||||
|                 let txout = serde_json::from_value(val["t"].take())?; |                 let txout = serde_json::from_value(val["t"].take())?; | ||||||
|                 let keychain = serde_json::from_value(val["i"].take())?; |                 let keychain = serde_json::from_value(val["i"].take())?; | ||||||
| 
 | 
 | ||||||
|                 Ok(UTXO { |                 Ok(LocalUtxo { | ||||||
|                     outpoint: *outpoint, |                     outpoint: *outpoint, | ||||||
|                     txout, |                     txout, | ||||||
|                     keychain, |                     keychain, | ||||||
|  | |||||||
| @ -157,7 +157,7 @@ impl BatchOperations for MemoryDatabase { | |||||||
|         Ok(()) |         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 key = MapKey::UTXO(Some(&utxo.outpoint)).as_map_key(); | ||||||
|         self.map |         self.map | ||||||
|             .insert(key, Box::new((utxo.txout.clone(), utxo.keychain))); |             .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 key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||||
|         let res = self.map.remove(&key); |         let res = self.map.remove(&key); | ||||||
|         self.deleted_keys.push(key); |         self.deleted_keys.push(key); | ||||||
| @ -232,7 +232,7 @@ impl BatchOperations for MemoryDatabase { | |||||||
|             None => Ok(None), |             None => Ok(None), | ||||||
|             Some(b) => { |             Some(b) => { | ||||||
|                 let (txout, keychain) = b.downcast_ref().cloned().unwrap(); |                 let (txout, keychain) = b.downcast_ref().cloned().unwrap(); | ||||||
|                 Ok(Some(UTXO { |                 Ok(Some(LocalUtxo { | ||||||
|                     outpoint: *outpoint, |                     outpoint: *outpoint, | ||||||
|                     txout, |                     txout, | ||||||
|                     keychain, |                     keychain, | ||||||
| @ -316,14 +316,14 @@ impl Database for MemoryDatabase { | |||||||
|             .collect() |             .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(); |         let key = MapKey::UTXO(None).as_map_key(); | ||||||
|         self.map |         self.map | ||||||
|             .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) |             .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) | ||||||
|             .map(|(k, v)| { |             .map(|(k, v)| { | ||||||
|                 let outpoint = deserialize(&k[1..]).unwrap(); |                 let outpoint = deserialize(&k[1..]).unwrap(); | ||||||
|                 let (txout, keychain) = v.downcast_ref().cloned().unwrap(); |                 let (txout, keychain) = v.downcast_ref().cloned().unwrap(); | ||||||
|                 Ok(UTXO { |                 Ok(LocalUtxo { | ||||||
|                     outpoint, |                     outpoint, | ||||||
|                     txout, |                     txout, | ||||||
|                     keychain, |                     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(); |         let key = MapKey::UTXO(Some(outpoint)).as_map_key(); | ||||||
|         Ok(self.map.get(&key).map(|b| { |         Ok(self.map.get(&key).map(|b| { | ||||||
|             let (txout, keychain) = b.downcast_ref().cloned().unwrap(); |             let (txout, keychain) = b.downcast_ref().cloned().unwrap(); | ||||||
|             UTXO { |             LocalUtxo { | ||||||
|                 outpoint: *outpoint, |                 outpoint: *outpoint, | ||||||
|                 txout, |                 txout, | ||||||
|                 keychain, |                 keychain, | ||||||
| @ -502,7 +502,7 @@ macro_rules! populate_test_db { | |||||||
| 
 | 
 | ||||||
|         db.set_tx(&tx_details).unwrap(); |         db.set_tx(&tx_details).unwrap(); | ||||||
|         for (vout, out) in tx.output.iter().enumerate() { |         for (vout, out) in tx.output.iter().enumerate() { | ||||||
|             db.set_utxo(&UTXO { |             db.set_utxo(&LocalUtxo { | ||||||
|                 txout: out.clone(), |                 txout: out.clone(), | ||||||
|                 outpoint: OutPoint { |                 outpoint: OutPoint { | ||||||
|                     txid, |                     txid, | ||||||
|  | |||||||
| @ -64,8 +64,8 @@ pub trait BatchOperations { | |||||||
|         keychain: KeychainKind, |         keychain: KeychainKind, | ||||||
|         child: u32, |         child: u32, | ||||||
|     ) -> Result<(), Error>; |     ) -> Result<(), Error>; | ||||||
|     /// Store a [`UTXO`]
 |     /// Store a [`LocalUtxo`]
 | ||||||
|     fn set_utxo(&mut self, utxo: &UTXO) -> Result<(), Error>; |     fn set_utxo(&mut self, utxo: &LocalUtxo) -> Result<(), Error>; | ||||||
|     /// Store a raw transaction
 |     /// Store a raw transaction
 | ||||||
|     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>; |     fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error>; | ||||||
|     /// Store the metadata of a transaction
 |     /// Store the metadata of a transaction
 | ||||||
| @ -85,8 +85,8 @@ pub trait BatchOperations { | |||||||
|         &mut self, |         &mut self, | ||||||
|         script: &Script, |         script: &Script, | ||||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error>; |     ) -> Result<Option<(KeychainKind, u32)>, Error>; | ||||||
|     /// Delete a [`UTXO`] given its [`OutPoint`]
 |     /// Delete a [`LocalUtxo`] given its [`OutPoint`]
 | ||||||
|     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>; |     fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>; | ||||||
|     /// Delete a raw transaction given its [`Txid`]
 |     /// Delete a raw transaction given its [`Txid`]
 | ||||||
|     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>; |     fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error>; | ||||||
|     /// Delete the metadata of a transaction and optionally the raw transaction itself
 |     /// 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
 |     /// Return the list of script_pubkeys
 | ||||||
|     fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>; |     fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>; | ||||||
|     /// Return the list of [`UTXO`]s
 |     /// Return the list of [`LocalUtxo`]s
 | ||||||
|     fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>; |     fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error>; | ||||||
|     /// Return the list of raw transactions
 |     /// Return the list of raw transactions
 | ||||||
|     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>; |     fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>; | ||||||
|     /// Return the list of transactions metadata
 |     /// Return the list of transactions metadata
 | ||||||
| @ -134,8 +134,8 @@ pub trait Database: BatchOperations { | |||||||
|         &self, |         &self, | ||||||
|         script: &Script, |         script: &Script, | ||||||
|     ) -> Result<Option<(KeychainKind, u32)>, Error>; |     ) -> Result<Option<(KeychainKind, u32)>, Error>; | ||||||
|     /// Fetch a [`UTXO`] given its [`OutPoint`]
 |     /// Fetch a [`LocalUtxo`] given its [`OutPoint`]
 | ||||||
|     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<UTXO>, Error>; |     fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error>; | ||||||
|     /// Fetch a raw transaction given its [`Txid`]
 |     /// Fetch a raw transaction given its [`Txid`]
 | ||||||
|     fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>; |     fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>; | ||||||
|     /// Fetch the transaction metadata and optionally also the raw transaction
 |     /// Fetch the transaction metadata and optionally also the raw transaction
 | ||||||
| @ -298,7 +298,7 @@ pub mod test { | |||||||
|             value: 133742, |             value: 133742, | ||||||
|             script_pubkey: script, |             script_pubkey: script, | ||||||
|         }; |         }; | ||||||
|         let utxo = UTXO { |         let utxo = LocalUtxo { | ||||||
|             txout, |             txout, | ||||||
|             outpoint, |             outpoint, | ||||||
|             keychain: KeychainKind::External, |             keychain: KeychainKind::External, | ||||||
|  | |||||||
							
								
								
									
										66
									
								
								src/types.rs
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								src/types.rs
									
									
									
									
									
								
							| @ -25,7 +25,7 @@ | |||||||
| use std::convert::AsRef; | use std::convert::AsRef; | ||||||
| 
 | 
 | ||||||
| use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; | use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; | ||||||
| use bitcoin::hash_types::Txid; | use bitcoin::{hash_types::Txid, util::psbt}; | ||||||
| 
 | 
 | ||||||
| use serde::{Deserialize, Serialize}; | 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)] | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] | ||||||
| pub struct UTXO { | pub struct LocalUtxo { | ||||||
|     /// Reference to a transaction output
 |     /// Reference to a transaction output
 | ||||||
|     pub outpoint: OutPoint, |     pub outpoint: OutPoint, | ||||||
|     /// Transaction output
 |     /// Transaction output
 | ||||||
| @ -101,6 +103,64 @@ pub struct UTXO { | |||||||
|     pub keychain: KeychainKind, |     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
 | /// A wallet transaction
 | ||||||
| #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)] | ||||||
| pub struct TransactionDetails { | pub struct TransactionDetails { | ||||||
|  | |||||||
| @ -50,8 +50,8 @@ | |||||||
| //!     fn coin_select(
 | //!     fn coin_select(
 | ||||||
| //!         &self,
 | //!         &self,
 | ||||||
| //!         database: &D,
 | //!         database: &D,
 | ||||||
| //!         required_utxos: Vec<(UTXO, usize)>,
 | //!         required_utxos: Vec<WeightedUtxo>,
 | ||||||
| //!         optional_utxos: Vec<(UTXO, usize)>,
 | //!         optional_utxos: Vec<WeightedUtxo>,
 | ||||||
| //!         fee_rate: FeeRate,
 | //!         fee_rate: FeeRate,
 | ||||||
| //!         amount_needed: u64,
 | //!         amount_needed: u64,
 | ||||||
| //!         fee_amount: f32,
 | //!         fee_amount: f32,
 | ||||||
| @ -60,11 +60,10 @@ | |||||||
| //!         let mut additional_weight = 0;
 | //!         let mut additional_weight = 0;
 | ||||||
| //!         let all_utxos_selected = required_utxos
 | //!         let all_utxos_selected = required_utxos
 | ||||||
| //!             .into_iter().chain(optional_utxos)
 | //!             .into_iter().chain(optional_utxos)
 | ||||||
| //!             .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), (utxo, weight)| {
 | //!             .scan((&mut selected_amount, &mut additional_weight), |(selected_amount, additional_weight), weighted_utxo| {
 | ||||||
| //!                 **selected_amount += utxo.txout.value;
 | //!                 **selected_amount += weighted_utxo.utxo.txout().value;
 | ||||||
| //!                 **additional_weight += TXIN_BASE_WEIGHT + weight;
 | //!                 **additional_weight += TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight;
 | ||||||
| //!
 | //!                 Some(weighted_utxo.utxo)
 | ||||||
| //!                 Some(utxo)
 |  | ||||||
| //!             })
 | //!             })
 | ||||||
| //!             .collect::<Vec<_>>();
 | //!             .collect::<Vec<_>>();
 | ||||||
| //!         let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
 | //!         let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
 | ||||||
| @ -75,7 +74,6 @@ | |||||||
| //!
 | //!
 | ||||||
| //!         Ok(CoinSelectionResult {
 | //!         Ok(CoinSelectionResult {
 | ||||||
| //!             selected: all_utxos_selected,
 | //!             selected: all_utxos_selected,
 | ||||||
| //!             selected_amount,
 |  | ||||||
| //!             fee_amount: fee_amount + additional_fees,
 | //!             fee_amount: fee_amount + additional_fees,
 | ||||||
| //!         })
 | //!         })
 | ||||||
| //!     }
 | //!     }
 | ||||||
| @ -97,9 +95,9 @@ | |||||||
| //! # Ok::<(), bdk::Error>(())
 | //! # Ok::<(), bdk::Error>(())
 | ||||||
| //! ```
 | //! ```
 | ||||||
| 
 | 
 | ||||||
| use crate::database::Database; | use crate::types::FeeRate; | ||||||
| use crate::error::Error; | use crate::{database::Database, WeightedUtxo}; | ||||||
| use crate::types::{FeeRate, UTXO}; | use crate::{error::Error, Utxo}; | ||||||
| 
 | 
 | ||||||
| use rand::seq::SliceRandom; | use rand::seq::SliceRandom; | ||||||
| #[cfg(not(test))] | #[cfg(not(test))] | ||||||
| @ -122,13 +120,29 @@ pub(crate) const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4 + 1) * 4; | |||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct CoinSelectionResult { | pub struct CoinSelectionResult { | ||||||
|     /// List of outputs selected for use as inputs
 |     /// List of outputs selected for use as inputs
 | ||||||
|     pub selected: Vec<UTXO>, |     pub selected: Vec<Utxo>, | ||||||
|     /// Sum of the selected inputs' value
 |  | ||||||
|     pub selected_amount: u64, |  | ||||||
|     /// Total fee amount in satoshi
 |     /// Total fee amount in satoshi
 | ||||||
|     pub fee_amount: f32, |     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
 | /// Trait for generalized coin selection algorithms
 | ||||||
| ///
 | ///
 | ||||||
| /// This trait can be implemented to make the [`Wallet`](super::Wallet) use a customized coin
 | /// 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( |     fn coin_select( | ||||||
|         &self, |         &self, | ||||||
|         database: &D, |         database: &D, | ||||||
|         required_utxos: Vec<(UTXO, usize)>, |         required_utxos: Vec<WeightedUtxo>, | ||||||
|         optional_utxos: Vec<(UTXO, usize)>, |         optional_utxos: Vec<WeightedUtxo>, | ||||||
|         fee_rate: FeeRate, |         fee_rate: FeeRate, | ||||||
|         amount_needed: u64, |         amount_needed: u64, | ||||||
|         fee_amount: f32, |         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
 | /// 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.
 | /// from the largest ones until the required amount is reached.
 | ||||||
| #[derive(Debug, Default)] | #[derive(Debug, Default, Clone, Copy)] | ||||||
| pub struct LargestFirstCoinSelection; | pub struct LargestFirstCoinSelection; | ||||||
| 
 | 
 | ||||||
| impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | ||||||
|     fn coin_select( |     fn coin_select( | ||||||
|         &self, |         &self, | ||||||
|         _database: &D, |         _database: &D, | ||||||
|         required_utxos: Vec<(UTXO, usize)>, |         required_utxos: Vec<WeightedUtxo>, | ||||||
|         mut optional_utxos: Vec<(UTXO, usize)>, |         mut optional_utxos: Vec<WeightedUtxo>, | ||||||
|         fee_rate: FeeRate, |         fee_rate: FeeRate, | ||||||
|         amount_needed: u64, |         amount_needed: u64, | ||||||
|         mut fee_amount: f32, |         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,
 |         // We put the "required UTXOs" first and make sure the optional UTXOs are sorted,
 | ||||||
|         // initially smallest to largest, before being reversed with `.rev()`.
 |         // initially smallest to largest, before being reversed with `.rev()`.
 | ||||||
|         let utxos = { |         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 |             required_utxos | ||||||
|                 .into_iter() |                 .into_iter() | ||||||
|                 .map(|utxo| (true, utxo)) |                 .map(|utxo| (true, utxo)) | ||||||
| @ -201,18 +215,19 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | |||||||
|         let selected = utxos |         let selected = utxos | ||||||
|             .scan( |             .scan( | ||||||
|                 (&mut selected_amount, &mut fee_amount), |                 (&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) { |                     if must_use || **selected_amount < amount_needed + (fee_amount.ceil() as u64) { | ||||||
|                         **fee_amount += calc_fee_bytes(TXIN_BASE_WEIGHT + weight); |                         **fee_amount += | ||||||
|                         **selected_amount += utxo.txout.value; |                             calc_fee_bytes(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight); | ||||||
|  |                         **selected_amount += weighted_utxo.utxo.txout().value; | ||||||
| 
 | 
 | ||||||
|                         log::debug!( |                         log::debug!( | ||||||
|                             "Selected {}, updated fee_amount = `{}`", |                             "Selected {}, updated fee_amount = `{}`", | ||||||
|                             utxo.outpoint, |                             weighted_utxo.utxo.outpoint(), | ||||||
|                             fee_amount |                             fee_amount | ||||||
|                         ); |                         ); | ||||||
| 
 | 
 | ||||||
|                         Some(utxo) |                         Some(weighted_utxo.utxo) | ||||||
|                     } else { |                     } else { | ||||||
|                         None |                         None | ||||||
|                     } |                     } | ||||||
| @ -231,7 +246,6 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | |||||||
|         Ok(CoinSelectionResult { |         Ok(CoinSelectionResult { | ||||||
|             selected, |             selected, | ||||||
|             fee_amount, |             fee_amount, | ||||||
|             selected_amount, |  | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -239,9 +253,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection { | |||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| // Adds fee information to an UTXO.
 | // Adds fee information to an UTXO.
 | ||||||
| struct OutputGroup { | struct OutputGroup { | ||||||
|     utxo: UTXO, |     weighted_utxo: WeightedUtxo, | ||||||
|     // weight needed to satisfy the UTXO, as described in `Descriptor::max_satisfaction_weight`
 |  | ||||||
|     satisfaction_weight: usize, |  | ||||||
|     // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
 |     // Amount of fees for spending a certain utxo, calculated using a certain FeeRate
 | ||||||
|     fee: f32, |     fee: f32, | ||||||
|     // The effective value of the UTXO, i.e., the utxo value minus the fee for spending it
 |     // 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 { | impl OutputGroup { | ||||||
|     fn new(utxo: UTXO, satisfaction_weight: usize, fee_rate: FeeRate) -> Self { |     fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self { | ||||||
|         let fee = (TXIN_BASE_WEIGHT + satisfaction_weight) as f32 / 4.0 * fee_rate.as_sat_vb(); |         let fee = (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as f32 / 4.0 | ||||||
|         let effective_value = utxo.txout.value as i64 - fee.ceil() as i64; |             * fee_rate.as_sat_vb(); | ||||||
|  |         let effective_value = weighted_utxo.utxo.txout().value as i64 - fee.ceil() as i64; | ||||||
|         OutputGroup { |         OutputGroup { | ||||||
|             utxo, |             weighted_utxo, | ||||||
|             satisfaction_weight, |  | ||||||
|             effective_value, |             effective_value, | ||||||
|             fee, |             fee, | ||||||
|         } |         } | ||||||
| @ -291,8 +303,8 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection { | |||||||
|     fn coin_select( |     fn coin_select( | ||||||
|         &self, |         &self, | ||||||
|         _database: &D, |         _database: &D, | ||||||
|         required_utxos: Vec<(UTXO, usize)>, |         required_utxos: Vec<WeightedUtxo>, | ||||||
|         optional_utxos: Vec<(UTXO, usize)>, |         optional_utxos: Vec<WeightedUtxo>, | ||||||
|         fee_rate: FeeRate, |         fee_rate: FeeRate, | ||||||
|         amount_needed: u64, |         amount_needed: u64, | ||||||
|         fee_amount: f32, |         fee_amount: f32, | ||||||
| @ -300,7 +312,7 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection { | |||||||
|         // Mapping every (UTXO, usize) to an output group
 |         // Mapping every (UTXO, usize) to an output group
 | ||||||
|         let required_utxos: Vec<OutputGroup> = required_utxos |         let required_utxos: Vec<OutputGroup> = required_utxos | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) |             .map(|u| OutputGroup::new(u, fee_rate)) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
|         // Mapping every (UTXO, usize) to an output group.
 |         // 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
 |         // adding them is more than their value
 | ||||||
|         let optional_utxos: Vec<OutputGroup> = optional_utxos |         let optional_utxos: Vec<OutputGroup> = optional_utxos | ||||||
|             .into_iter() |             .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) |             .filter(|u| u.effective_value > 0) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
| @ -507,14 +519,12 @@ impl BranchAndBoundCoinSelection { | |||||||
|         fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>(); |         fee_amount += selected_utxos.iter().map(|u| u.fee).sum::<f32>(); | ||||||
|         let selected = selected_utxos |         let selected = selected_utxos | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|u| u.utxo) |             .map(|u| u.weighted_utxo.utxo) | ||||||
|             .collect::<Vec<_>>(); |             .collect::<Vec<_>>(); | ||||||
|         let selected_amount = selected.iter().map(|u| u.txout.value).sum(); |  | ||||||
| 
 | 
 | ||||||
|         CoinSelectionResult { |         CoinSelectionResult { | ||||||
|             selected, |             selected, | ||||||
|             fee_amount, |             fee_amount, | ||||||
|             selected_amount, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -535,10 +545,11 @@ mod test { | |||||||
| 
 | 
 | ||||||
|     const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2; |     const P2WPKH_WITNESS_SIZE: usize = 73 + 33 + 2; | ||||||
| 
 | 
 | ||||||
|     fn get_test_utxos() -> Vec<(UTXO, usize)> { |     fn get_test_utxos() -> Vec<WeightedUtxo> { | ||||||
|         vec![ |         vec![ | ||||||
|             ( |             WeightedUtxo { | ||||||
|                 UTXO { |                 satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||||
|  |                 utxo: Utxo::Local(LocalUtxo { | ||||||
|                     outpoint: OutPoint::from_str( |                     outpoint: OutPoint::from_str( | ||||||
|                         "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", |                         "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", | ||||||
|                     ) |                     ) | ||||||
| @ -548,11 +559,11 @@ mod test { | |||||||
|                         script_pubkey: Script::new(), |                         script_pubkey: Script::new(), | ||||||
|                     }, |                     }, | ||||||
|                     keychain: KeychainKind::External, |                     keychain: KeychainKind::External, | ||||||
|                 }, |                 }), | ||||||
|                 P2WPKH_WITNESS_SIZE, |             }, | ||||||
|             ), |             WeightedUtxo { | ||||||
|             ( |                 satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||||
|                 UTXO { |                 utxo: Utxo::Local(LocalUtxo { | ||||||
|                     outpoint: OutPoint::from_str( |                     outpoint: OutPoint::from_str( | ||||||
|                         "65d92ddff6b6dc72c89624a6491997714b90f6004f928d875bc0fd53f264fa85:0", |                         "65d92ddff6b6dc72c89624a6491997714b90f6004f928d875bc0fd53f264fa85:0", | ||||||
|                     ) |                     ) | ||||||
| @ -562,17 +573,17 @@ mod test { | |||||||
|                         script_pubkey: Script::new(), |                         script_pubkey: Script::new(), | ||||||
|                     }, |                     }, | ||||||
|                     keychain: KeychainKind::Internal, |                     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(); |         let mut res = Vec::new(); | ||||||
|         for _ in 0..utxos_number { |         for _ in 0..utxos_number { | ||||||
|             res.push(( |             res.push(WeightedUtxo { | ||||||
|                 UTXO { |                 satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||||
|  |                 utxo: Utxo::Local(LocalUtxo { | ||||||
|                     outpoint: OutPoint::from_str( |                     outpoint: OutPoint::from_str( | ||||||
|                         "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", |                         "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", | ||||||
|                     ) |                     ) | ||||||
| @ -582,16 +593,16 @@ mod test { | |||||||
|                         script_pubkey: Script::new(), |                         script_pubkey: Script::new(), | ||||||
|                     }, |                     }, | ||||||
|                     keychain: KeychainKind::External, |                     keychain: KeychainKind::External, | ||||||
|                 }, |                 }), | ||||||
|                 P2WPKH_WITNESS_SIZE, |             }); | ||||||
|             )); |  | ||||||
|         } |         } | ||||||
|         res |         res | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<(UTXO, usize)> { |     fn generate_same_value_utxos(utxos_value: u64, utxos_number: usize) -> Vec<WeightedUtxo> { | ||||||
|         let utxo = ( |         let utxo = WeightedUtxo { | ||||||
|             UTXO { |             satisfaction_weight: P2WPKH_WITNESS_SIZE, | ||||||
|  |             utxo: Utxo::Local(LocalUtxo { | ||||||
|                 outpoint: OutPoint::from_str( |                 outpoint: OutPoint::from_str( | ||||||
|                     "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", |                     "ebd9813ecebc57ff8f30797de7c205e3c7498ca950ea4341ee51a685ff2fa30a:0", | ||||||
|                 ) |                 ) | ||||||
| @ -601,18 +612,18 @@ mod test { | |||||||
|                     script_pubkey: Script::new(), |                     script_pubkey: Script::new(), | ||||||
|                 }, |                 }, | ||||||
|                 keychain: KeychainKind::External, |                 keychain: KeychainKind::External, | ||||||
|             }, |             }), | ||||||
|             P2WPKH_WITNESS_SIZE, |         }; | ||||||
|         ); |  | ||||||
|         vec![utxo; utxos_number] |         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); |         let utxos_picked_len = rng.gen_range(2, utxos.len() / 2); | ||||||
|         utxos.shuffle(&mut rng); |         utxos.shuffle(&mut rng); | ||||||
|         utxos[..utxos_picked_len] |         utxos[..utxos_picked_len] | ||||||
|             .iter() |             .iter() | ||||||
|             .fold(0, |acc, x| acc + x.0.txout.value) |             .map(|u| u.utxo.txout().value) | ||||||
|  |             .sum() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[test] |     #[test] | ||||||
| @ -632,7 +643,7 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         assert_eq!(result.selected.len(), 2); |         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); |         assert_eq!(result.fee_amount, 186.0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -653,7 +664,7 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         assert_eq!(result.selected.len(), 2); |         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); |         assert_eq!(result.fee_amount, 186.0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -674,7 +685,7 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         assert_eq!(result.selected.len(), 1); |         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); |         assert_eq!(result.fee_amount, 118.0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -734,7 +745,7 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         assert_eq!(result.selected.len(), 3); |         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); |         assert_eq!(result.fee_amount, 254.0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -755,7 +766,7 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         assert_eq!(result.selected.len(), 2); |         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); |         assert_eq!(result.fee_amount, 186.0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -812,7 +823,7 @@ mod test { | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|         assert_eq!(result.selected.len(), 1); |         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 input_size = (TXIN_BASE_WEIGHT as f32) / 4.0 + P2WPKH_WITNESS_SIZE as f32 / 4.0; | ||||||
|         let epsilon = 0.5; |         let epsilon = 0.5; | ||||||
|         assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon); |         assert!((1.0 - (result.fee_amount / input_size)).abs() < epsilon); | ||||||
| @ -837,7 +848,7 @@ mod test { | |||||||
|                     0.0, |                     0.0, | ||||||
|                 ) |                 ) | ||||||
|                 .unwrap(); |                 .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 fee_rate = FeeRate::from_sat_per_vb(10.0); | ||||||
|         let utxos: Vec<OutputGroup> = get_test_utxos() |         let utxos: Vec<OutputGroup> = get_test_utxos() | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) |             .map(|u| OutputGroup::new(u, fee_rate)) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
|         let curr_available_value = utxos |         let curr_available_value = utxos | ||||||
| @ -875,7 +886,7 @@ mod test { | |||||||
|         let fee_rate = FeeRate::from_sat_per_vb(10.0); |         let fee_rate = FeeRate::from_sat_per_vb(10.0); | ||||||
|         let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000) |         let utxos: Vec<OutputGroup> = generate_same_value_utxos(100_000, 100_000) | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) |             .map(|u| OutputGroup::new(u, fee_rate)) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
|         let curr_available_value = utxos |         let curr_available_value = utxos | ||||||
| @ -908,7 +919,7 @@ mod test { | |||||||
| 
 | 
 | ||||||
|         let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) |         let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) |             .map(|u| OutputGroup::new(u, fee_rate)) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
|         let curr_value = 0; |         let curr_value = 0; | ||||||
| @ -933,7 +944,7 @@ mod test { | |||||||
|             ) |             ) | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|         assert_eq!(result.fee_amount, 186.0); |         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
 |     // 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 { |         for _ in 0..200 { | ||||||
|             let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40) |             let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40) | ||||||
|                 .into_iter() |                 .into_iter() | ||||||
|                 .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) |                 .map(|u| OutputGroup::new(u, fee_rate)) | ||||||
|                 .collect(); |                 .collect(); | ||||||
| 
 | 
 | ||||||
|             let curr_value = 0; |             let curr_value = 0; | ||||||
| @ -969,7 +980,7 @@ mod test { | |||||||
|                     0.0, |                     0.0, | ||||||
|                 ) |                 ) | ||||||
|                 .unwrap(); |                 .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 fee_rate = FeeRate::from_sat_per_vb(1.0); | ||||||
|         let utxos: Vec<OutputGroup> = utxos |         let utxos: Vec<OutputGroup> = utxos | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|u| OutputGroup::new(u.0, u.1, fee_rate)) |             .map(|u| OutputGroup::new(u, fee_rate)) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
|         let result = BranchAndBoundCoinSelection::default().single_random_draw( |         let result = BranchAndBoundCoinSelection::default().single_random_draw( | ||||||
| @ -994,7 +1005,7 @@ mod test { | |||||||
|             50.0, |             50.0, | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         assert!(result.selected_amount > target_amount); |         assert!(result.selected_amount() > target_amount); | ||||||
|         assert_eq!( |         assert_eq!( | ||||||
|             result.fee_amount, |             result.fee_amount, | ||||||
|             50.0 + result.selected.len() as f32 * 68.0 |             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
 |     /// Note that this methods only operate on the internal database, which first needs to be
 | ||||||
|     /// [`Wallet::sync`] manually.
 |     /// [`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() |         self.database.borrow().iter_utxos() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
 |     /// Returns the `UTXO` owned by this wallet corresponding to `outpoint` if it exists in the
 | ||||||
|     /// wallet's database.
 |     /// 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) |         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
 |             params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
 | ||||||
|         )?; |         )?; | ||||||
| 
 | 
 | ||||||
|         let coin_selection::CoinSelectionResult { |         let coin_selection = coin_selection.coin_select( | ||||||
|             selected, |  | ||||||
|             selected_amount, |  | ||||||
|             mut fee_amount, |  | ||||||
|         } = coin_selection.coin_select( |  | ||||||
|             self.database.borrow().deref(), |             self.database.borrow().deref(), | ||||||
|             required_utxos, |             required_utxos, | ||||||
|             optional_utxos, |             optional_utxos, | ||||||
| @ -525,10 +521,13 @@ where | |||||||
|             outgoing, |             outgoing, | ||||||
|             fee_amount, |             fee_amount, | ||||||
|         )?; |         )?; | ||||||
|         tx.input = selected |         let mut fee_amount = coin_selection.fee_amount; | ||||||
|  | 
 | ||||||
|  |         tx.input = coin_selection | ||||||
|  |             .selected | ||||||
|             .iter() |             .iter() | ||||||
|             .map(|u| bitcoin::TxIn { |             .map(|u| bitcoin::TxIn { | ||||||
|                 previous_output: u.outpoint, |                 previous_output: u.outpoint(), | ||||||
|                 script_sig: Script::default(), |                 script_sig: Script::default(), | ||||||
|                 sequence: n_sequence, |                 sequence: n_sequence, | ||||||
|                 witness: vec![], |                 witness: vec![], | ||||||
| @ -550,9 +549,8 @@ where | |||||||
|                 Some(change_output) |                 Some(change_output) | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| 
 |  | ||||||
|         let mut fee_amount = fee_amount.ceil() as u64; |         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 { |         match change_output { | ||||||
|             None if change_val.is_dust() => { |             None if change_val.is_dust() => { | ||||||
| @ -588,14 +586,15 @@ where | |||||||
|         params.ordering.sort_tx(&mut tx); |         params.ordering.sort_tx(&mut tx); | ||||||
| 
 | 
 | ||||||
|         let txid = tx.txid(); |         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 { |         let transaction_details = TransactionDetails { | ||||||
|             transaction: None, |             transaction: None, | ||||||
|             txid, |             txid, | ||||||
|             timestamp: time::get_timestamp(), |             timestamp: time::get_timestamp(), | ||||||
|             received, |             received, | ||||||
|             sent: selected_amount, |             sent, | ||||||
|             fees: fee_amount, |             fees: fee_amount, | ||||||
|             height: None, |             height: None, | ||||||
|         }; |         }; | ||||||
| @ -699,13 +698,16 @@ where | |||||||
|                     } |                     } | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 let utxo = UTXO { |                 let utxo = LocalUtxo { | ||||||
|                     outpoint: txin.previous_output, |                     outpoint: txin.previous_output, | ||||||
|                     txout, |                     txout, | ||||||
|                     keychain, |                     keychain, | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 Ok((utxo, weight)) |                 Ok(WeightedUtxo { | ||||||
|  |                     satisfaction_weight: weight, | ||||||
|  |                     utxo: Utxo::Local(utxo), | ||||||
|  |                 }) | ||||||
|             }) |             }) | ||||||
|             .collect::<Result<Vec<_>, _>>()?; |             .collect::<Result<Vec<_>, _>>()?; | ||||||
| 
 | 
 | ||||||
| @ -1016,7 +1018,7 @@ where | |||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_available_utxos(&self) -> Result<Vec<(UTXO, usize)>, Error> { |     fn get_available_utxos(&self) -> Result<Vec<(LocalUtxo, usize)>, Error> { | ||||||
|         Ok(self |         Ok(self | ||||||
|             .list_unspent()? |             .list_unspent()? | ||||||
|             .into_iter() |             .into_iter() | ||||||
| @ -1039,18 +1041,18 @@ where | |||||||
|         &self, |         &self, | ||||||
|         change_policy: tx_builder::ChangeSpendPolicy, |         change_policy: tx_builder::ChangeSpendPolicy, | ||||||
|         unspendable: &HashSet<OutPoint>, |         unspendable: &HashSet<OutPoint>, | ||||||
|         manually_selected: Vec<(UTXO, usize)>, |         manually_selected: Vec<WeightedUtxo>, | ||||||
|         must_use_all_available: bool, |         must_use_all_available: bool, | ||||||
|         manual_only: bool, |         manual_only: bool, | ||||||
|         must_only_use_confirmed_tx: 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
 |         //    must_spend <- manually selected utxos
 | ||||||
|         //    may_spend  <- all other available utxos
 |         //    may_spend  <- all other available utxos
 | ||||||
|         let mut may_spend = self.get_available_utxos()?; |         let mut may_spend = self.get_available_utxos()?; | ||||||
|         may_spend.retain(|may_spend| { |         may_spend.retain(|may_spend| { | ||||||
|             manually_selected |             manually_selected | ||||||
|                 .iter() |                 .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() |                 .is_none() | ||||||
|         }); |         }); | ||||||
|         let mut must_spend = manually_selected; |         let mut must_spend = manually_selected; | ||||||
| @ -1088,6 +1090,14 @@ where | |||||||
|             retain |             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 { |         if must_use_all_available { | ||||||
|             must_spend.append(&mut may_spend); |             must_spend.append(&mut may_spend); | ||||||
|         } |         } | ||||||
| @ -1098,7 +1108,7 @@ where | |||||||
|     fn complete_transaction( |     fn complete_transaction( | ||||||
|         &self, |         &self, | ||||||
|         tx: Transaction, |         tx: Transaction, | ||||||
|         selected: Vec<UTXO>, |         selected: Vec<Utxo>, | ||||||
|         params: TxParams, |         params: TxParams, | ||||||
|     ) -> Result<PSBT, Error> { |     ) -> Result<PSBT, Error> { | ||||||
|         use bitcoin::util::psbt::serialize::Serialize; |         use bitcoin::util::psbt::serialize::Serialize; | ||||||
| @ -1131,9 +1141,9 @@ where | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let lookup_output = selected |         let mut lookup_output = selected | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|utxo| (utxo.outpoint, utxo)) |             .map(|utxo| (utxo.outpoint(), utxo)) | ||||||
|             .collect::<HashMap<_, _>>(); |             .collect::<HashMap<_, _>>(); | ||||||
| 
 | 
 | ||||||
|         // add metadata for the inputs
 |         // add metadata for the inputs
 | ||||||
| @ -1142,7 +1152,7 @@ where | |||||||
|             .iter_mut() |             .iter_mut() | ||||||
|             .zip(psbt.global.unsigned_tx.input.iter()) |             .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, |                 Some(utxo) => utxo, | ||||||
|                 None => continue, |                 None => continue, | ||||||
|             }; |             }; | ||||||
| @ -1153,32 +1163,50 @@ where | |||||||
|                 psbt_input.sighash_type = Some(sighash_type); |                 psbt_input.sighash_type = Some(sighash_type); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Try to find the prev_script in our db to figure out if this is internal or external,
 |             match utxo { | ||||||
|             // and the derivation index
 |                 Utxo::Local(utxo) => { | ||||||
|             let (keychain, child) = match self |                     // Try to find the prev_script in our db to figure out if this is internal or external,
 | ||||||
|                 .database |                     // and the derivation index
 | ||||||
|                 .borrow() |                     let (keychain, child) = match self | ||||||
|                 .get_path_from_script_pubkey(&utxo.txout.script_pubkey)? |                         .database | ||||||
|             { |                         .borrow() | ||||||
|                 Some(x) => x, |                         .get_path_from_script_pubkey(&utxo.txout.script_pubkey)? | ||||||
|                 None => continue, |                     { | ||||||
|             }; |                         Some(x) => x, | ||||||
|  |                         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); |                     let derived_descriptor = desc.as_derived(child, &self.secp); | ||||||
|             psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?; |                     psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?; | ||||||
| 
 | 
 | ||||||
|             psbt_input.redeem_script = derived_descriptor.psbt_redeem_script(); |                     psbt_input.redeem_script = derived_descriptor.psbt_redeem_script(); | ||||||
|             psbt_input.witness_script = derived_descriptor.psbt_witness_script(); |                     psbt_input.witness_script = derived_descriptor.psbt_witness_script(); | ||||||
| 
 | 
 | ||||||
|             let prev_output = input.previous_output; |                     let prev_output = input.previous_output; | ||||||
|             if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? { |                     if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? { | ||||||
|                 if desc.is_witness() { |                         if desc.is_witness() { | ||||||
|                     psbt_input.witness_utxo = |                             psbt_input.witness_utxo = | ||||||
|                         Some(prev_tx.output[prev_output.vout as usize].clone()); |                                 Some(prev_tx.output[prev_output.vout as usize].clone()); | ||||||
|  |                         } | ||||||
|  |                         if !desc.is_witness() || params.force_non_witness_utxo { | ||||||
|  |                             psbt_input.non_witness_utxo = Some(prev_tx); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|                 if !desc.is_witness() || params.force_non_witness_utxo { |                 Utxo::Foreign { | ||||||
|                     psbt_input.non_witness_utxo = Some(prev_tx); |                     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; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -1348,7 +1376,7 @@ where | |||||||
| mod test { | mod test { | ||||||
|     use std::str::FromStr; |     use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
|     use bitcoin::Network; |     use bitcoin::{util::psbt, Network}; | ||||||
| 
 | 
 | ||||||
|     use crate::database::memory::MemoryDatabase; |     use crate::database::memory::MemoryDatabase; | ||||||
|     use crate::database::Database; |     use crate::database::Database; | ||||||
| @ -2237,6 +2265,187 @@ mod test { | |||||||
|         assert_eq!(psbt.global.unknown.get(&psbt_key), Some(&value_bytes)); |         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] |     #[test] | ||||||
|     #[should_panic(
 |     #[should_panic(
 | ||||||
|         expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")" |         expected = "MissingKeyOrigin(\"tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3\")" | ||||||
|  | |||||||
| @ -54,15 +54,15 @@ use std::collections::HashSet; | |||||||
| use std::default::Default; | use std::default::Default; | ||||||
| use std::marker::PhantomData; | 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 bitcoin::{OutPoint, Script, SigHashType, Transaction}; | ||||||
| 
 | 
 | ||||||
| use miniscript::descriptor::DescriptorTrait; | use miniscript::descriptor::DescriptorTrait; | ||||||
| 
 | 
 | ||||||
| use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; | use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; | ||||||
| use crate::{database::BatchDatabase, Error, Wallet}; | use crate::{database::BatchDatabase, Error, Utxo, Wallet}; | ||||||
| use crate::{ | use crate::{ | ||||||
|     types::{FeeRate, KeychainKind, UTXO}, |     types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo}, | ||||||
|     TransactionDetails, |     TransactionDetails, | ||||||
| }; | }; | ||||||
| /// Context in which the [`TxBuilder`] is valid
 | /// Context in which the [`TxBuilder`] is valid
 | ||||||
| @ -129,7 +129,7 @@ impl TxBuilderContext for BumpFee {} | |||||||
| /// [`build_fee_bump`]: Wallet::build_fee_bump
 | /// [`build_fee_bump`]: Wallet::build_fee_bump
 | ||||||
| /// [`finish`]: Self::finish
 | /// [`finish`]: Self::finish
 | ||||||
| /// [`coin_selection`]: Self::coin_selection
 | /// [`coin_selection`]: Self::coin_selection
 | ||||||
| #[derive(Clone, Debug)] | #[derive(Debug)] | ||||||
| pub struct TxBuilder<'a, B, D, Cs, Ctx> { | pub struct TxBuilder<'a, B, D, Cs, Ctx> { | ||||||
|     pub(crate) wallet: &'a Wallet<B, D>, |     pub(crate) wallet: &'a Wallet<B, D>, | ||||||
|     // params and coin_selection are Options not becasue they are optionally set (they are always
 |     // 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) fee_policy: Option<FeePolicy>, | ||||||
|     pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, |     pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, | ||||||
|     pub(crate) external_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) unspendable: HashSet<OutPoint>, | ||||||
|     pub(crate) manually_selected_only: bool, |     pub(crate) manually_selected_only: bool, | ||||||
|     pub(crate) sighash: Option<SigHashType>, |     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
 | // methods supported by both contexts, for any CoinSelectionAlgorithm
 | ||||||
| impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> | impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> | ||||||
|     TxBuilder<'a, B, D, Cs, Ctx> |     TxBuilder<'a, B, D, Cs, Ctx> | ||||||
| @ -286,7 +297,10 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte | |||||||
|         for utxo in utxos { |         for utxo in utxos { | ||||||
|             let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); |             let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); | ||||||
|             let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap(); |             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) |         Ok(self) | ||||||
| @ -300,6 +314,84 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte | |||||||
|         self.add_utxos(&[outpoint]) |         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`].
 |     /// Only spend utxos added by [`add_utxo`].
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// The wallet will **not** add additional utxos to the transaction even if they are needed to
 |     /// 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 { | impl ChangeSpendPolicy { | ||||||
|     pub(crate) fn is_satisfied_by(&self, utxo: &UTXO) -> bool { |     pub(crate) fn is_satisfied_by(&self, utxo: &LocalUtxo) -> bool { | ||||||
|         match self { |         match self { | ||||||
|             ChangeSpendPolicy::ChangeAllowed => true, |             ChangeSpendPolicy::ChangeAllowed => true, | ||||||
|             ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal, |             ChangeSpendPolicy::OnlyChange => utxo.keychain == KeychainKind::Internal, | ||||||
| @ -709,9 +801,9 @@ mod test { | |||||||
|         assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE])); |         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![ |         vec![ | ||||||
|             UTXO { |             LocalUtxo { | ||||||
|                 outpoint: OutPoint { |                 outpoint: OutPoint { | ||||||
|                     txid: Default::default(), |                     txid: Default::default(), | ||||||
|                     vout: 0, |                     vout: 0, | ||||||
| @ -719,7 +811,7 @@ mod test { | |||||||
|                 txout: Default::default(), |                 txout: Default::default(), | ||||||
|                 keychain: KeychainKind::External, |                 keychain: KeychainKind::External, | ||||||
|             }, |             }, | ||||||
|             UTXO { |             LocalUtxo { | ||||||
|                 outpoint: OutPoint { |                 outpoint: OutPoint { | ||||||
|                     txid: Default::default(), |                     txid: Default::default(), | ||||||
|                     vout: 1, |                     vout: 1, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user