diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 25482ead..845da0f0 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -45,7 +45,6 @@ use log::{debug, error, info, trace}; pub mod address_validator; pub mod coin_selection; pub mod export; -mod rbf; pub mod signer; pub mod time; pub mod tx_builder; @@ -355,6 +354,7 @@ where &builder.utxos, builder.send_all, builder.manually_selected_only, + false, // we don't mind using unconfirmed outputs here, hopefully coin selection will sort this out? )?; let coin_selection::CoinSelectionResult { @@ -604,18 +604,15 @@ where .cloned() .collect::>(); - let (must_use_utxos, may_use_utxos) = self.get_must_may_use_utxos( + let (mut must_use_utxos, may_use_utxos) = self.get_must_may_use_utxos( builder.change_policy, &builder.unspendable, &builder_extra_utxos[..], false, // when doing bump_fee `send_all` does not mean use all available utxos builder.manually_selected_only, + true, // we only want confirmed transactions for RBF )?; - let mut must_use_utxos = - rbf::filter_available(self.database.borrow().deref(), must_use_utxos.into_iter())?; - let may_use_utxos = - rbf::filter_available(self.database.borrow().deref(), may_use_utxos.into_iter())?; must_use_utxos.append(&mut original_utxos); let amount_needed = tx.output.iter().fold(0, |acc, out| acc + out.value); @@ -995,6 +992,7 @@ where manually_selected: &[OutPoint], must_use_all_available: bool, manual_only: bool, + must_only_use_confirmed_tx: bool, ) -> Result<(Vec<(UTXO, usize)>, Vec<(UTXO, usize)>), Error> { // must_spend <- manually selected utxos // may_spend <- all other available utxos @@ -1022,8 +1020,31 @@ where return Ok((must_spend, vec![])); } + let satisfies_confirmed = match must_only_use_confirmed_tx { + true => { + let database = self.database.borrow_mut(); + may_spend + .iter() + .map(|u| { + database + .get_tx(&u.0.outpoint.txid, true) + .map(|tx| match tx { + None => false, + Some(tx) => tx.height.is_some(), + }) + }) + .collect::, _>>()? + } + false => vec![true; may_spend.len()], + }; + + let mut i = 0; may_spend.retain(|u| { - change_policy.is_satisfied_by(&u.0) && !unspendable.contains(&u.0.outpoint) + let retain = change_policy.is_satisfied_by(&u.0) + && !unspendable.contains(&u.0.outpoint) + && satisfies_confirmed[i]; + i += 1; + retain }); if must_use_all_available { diff --git a/src/wallet/rbf.rs b/src/wallet/rbf.rs deleted file mode 100644 index 6080641e..00000000 --- a/src/wallet/rbf.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Magical Bitcoin Library -// Written in 2020 by -// Alekos Filini -// -// Copyright (c) 2020 Magical Bitcoin -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -use crate::database::Database; -use crate::error::Error; -use crate::types::*; - -/// Filters unspent utxos -pub(super) fn filter_available, D: Database>( - database: &D, - iter: I, -) -> Result, Error> { - Ok(iter - .map(|(utxo, weight)| { - Ok(match database.get_tx(&utxo.outpoint.txid, true)? { - None => None, - Some(tx) if tx.height.is_none() => None, - Some(_) => Some((utxo, weight)), - }) - }) - .collect::, Error>>()? - .into_iter() - .filter_map(|x| x) - .collect()) -} - -#[cfg(test)] -mod test { - use std::str::FromStr; - - use bitcoin::{OutPoint, Transaction, TxIn, TxOut, Txid}; - - use super::*; - use crate::database::{BatchOperations, MemoryDatabase}; - - fn add_transaction( - database: &mut MemoryDatabase, - spend: Vec, - outputs: Vec, - ) -> Txid { - let tx = Transaction { - version: 1, - lock_time: 0, - input: spend - .iter() - .cloned() - .map(|previous_output| TxIn { - previous_output, - ..Default::default() - }) - .collect(), - output: outputs - .iter() - .cloned() - .map(|value| TxOut { - value, - ..Default::default() - }) - .collect(), - }; - let txid = tx.txid(); - - for input in &spend { - database.del_utxo(input).unwrap(); - } - for vout in 0..outputs.len() { - database - .set_utxo(&UTXO { - txout: tx.output[vout].clone(), - outpoint: OutPoint { - txid, - vout: vout as u32, - }, - is_internal: true, - }) - .unwrap(); - } - database - .set_tx(&TransactionDetails { - txid, - transaction: Some(tx), - height: None, - ..Default::default() - }) - .unwrap(); - - txid - } - - #[test] - fn test_filter_available() { - let mut database = MemoryDatabase::new(); - add_transaction( - &mut database, - vec![OutPoint::from_str( - "aad194c72fd5cfd16d23da9462930ca91e35df1cfee05242b62f4034f50c3d41:5", - ) - .unwrap()], - vec![50_000], - ); - - let filtered = filter_available( - &database, - database - .iter_utxos() - .unwrap() - .into_iter() - .map(|utxo| (utxo, 0)), - ) - .unwrap(); - assert_eq!(filtered, &[]); - } -}