[wallet] Allow limiting the use of internal utxos in TxBuilder
This commit is contained in:
@@ -109,6 +109,7 @@ mod test {
|
||||
value: 100_000,
|
||||
script_pubkey: Script::new(),
|
||||
},
|
||||
is_internal: false,
|
||||
},
|
||||
UTXO {
|
||||
outpoint: OutPoint::from_str(
|
||||
@@ -119,6 +120,7 @@ mod test {
|
||||
value: 200_000,
|
||||
script_pubkey: Script::new(),
|
||||
},
|
||||
is_internal: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -217,8 +217,12 @@ where
|
||||
.max_satisfaction_weight(),
|
||||
);
|
||||
|
||||
let (available_utxos, use_all_utxos) =
|
||||
self.get_available_utxos(&builder.utxos, &builder.unspendable, builder.send_all)?;
|
||||
let (available_utxos, use_all_utxos) = self.get_available_utxos(
|
||||
builder.change_policy,
|
||||
&builder.utxos,
|
||||
&builder.unspendable,
|
||||
builder.send_all,
|
||||
)?;
|
||||
let coin_selection::CoinSelectionResult {
|
||||
txin,
|
||||
total_amount,
|
||||
@@ -646,11 +650,11 @@ where
|
||||
|
||||
fn get_available_utxos(
|
||||
&self,
|
||||
change_policy: tx_builder::ChangeSpendPolicy,
|
||||
utxo: &Option<Vec<OutPoint>>,
|
||||
unspendable: &Option<Vec<OutPoint>>,
|
||||
send_all: bool,
|
||||
) -> Result<(Vec<UTXO>, bool), Error> {
|
||||
// TODO: should we consider unconfirmed received rbf txs as "unspendable" too by default?
|
||||
let unspendable_set = match unspendable {
|
||||
None => HashSet::new(),
|
||||
Some(vec) => vec.into_iter().collect(),
|
||||
@@ -660,25 +664,28 @@ where
|
||||
// with manual coin selection we always want to spend all the selected utxos, no matter
|
||||
// what (even if they are marked as unspendable)
|
||||
Some(raw_utxos) => {
|
||||
// TODO: unwrap to remove
|
||||
let full_utxos: Vec<_> = raw_utxos
|
||||
let full_utxos = raw_utxos
|
||||
.iter()
|
||||
.map(|u| self.database.borrow().get_utxo(&u).unwrap())
|
||||
.collect();
|
||||
.map(|u| self.database.borrow().get_utxo(&u))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if !full_utxos.iter().all(|u| u.is_some()) {
|
||||
return Err(Error::UnknownUTXO);
|
||||
}
|
||||
|
||||
Ok((full_utxos.into_iter().map(|x| x.unwrap()).collect(), true))
|
||||
}
|
||||
// otherwise limit ourselves to the spendable utxos and the `send_all` setting
|
||||
None => Ok((
|
||||
self.list_unspent()?
|
||||
.into_iter()
|
||||
.filter(|u| !unspendable_set.contains(&u.outpoint))
|
||||
.collect(),
|
||||
send_all,
|
||||
)),
|
||||
// otherwise limit ourselves to the spendable utxos for the selected policy, and the `send_all` setting
|
||||
None => {
|
||||
let utxos = self.list_unspent()?.into_iter();
|
||||
let utxos = change_policy.filter_utxos(utxos).into_iter();
|
||||
|
||||
Ok((
|
||||
utxos
|
||||
.filter(|u| !unspendable_set.contains(&u.outpoint))
|
||||
.collect(),
|
||||
send_all,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use bitcoin::{Address, OutPoint, SigHashType, Transaction};
|
||||
|
||||
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
|
||||
use super::utils::FeeRate;
|
||||
use crate::types::UTXO;
|
||||
|
||||
// TODO: add a flag to ignore change outputs (make them unspendable)
|
||||
#[derive(Debug, Default)]
|
||||
@@ -20,6 +21,7 @@ pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
|
||||
pub(crate) locktime: Option<u32>,
|
||||
pub(crate) rbf: Option<u32>,
|
||||
pub(crate) version: Version,
|
||||
pub(crate) change_policy: ChangeSpendPolicy,
|
||||
pub(crate) coin_selection: Cs,
|
||||
}
|
||||
|
||||
@@ -59,11 +61,13 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
||||
self
|
||||
}
|
||||
|
||||
/// These have priority over the "unspendable" utxos
|
||||
pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
|
||||
self.utxos = Some(utxos);
|
||||
self
|
||||
}
|
||||
|
||||
/// This has priority over the "unspendable" utxos
|
||||
pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
|
||||
self.utxos.get_or_insert(vec![]).push(utxo);
|
||||
self
|
||||
@@ -108,6 +112,16 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn do_not_spend_change(mut self) -> Self {
|
||||
self.change_policy = ChangeSpendPolicy::ChangeForbidden;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn only_spend_change(mut self) -> Self {
|
||||
self.change_policy = ChangeSpendPolicy::OnlyChange;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
|
||||
TxBuilder {
|
||||
addressees: self.addressees,
|
||||
@@ -121,6 +135,7 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
|
||||
locktime: self.locktime,
|
||||
rbf: self.rbf,
|
||||
version: self.version,
|
||||
change_policy: self.change_policy,
|
||||
coin_selection,
|
||||
}
|
||||
}
|
||||
@@ -176,6 +191,29 @@ impl Default for Version {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ChangeSpendPolicy {
|
||||
ChangeAllowed,
|
||||
OnlyChange,
|
||||
ChangeForbidden,
|
||||
}
|
||||
|
||||
impl Default for ChangeSpendPolicy {
|
||||
fn default() -> Self {
|
||||
ChangeSpendPolicy::ChangeAllowed
|
||||
}
|
||||
}
|
||||
|
||||
impl ChangeSpendPolicy {
|
||||
pub(crate) fn filter_utxos<I: Iterator<Item = UTXO>>(&self, iter: I) -> Vec<UTXO> {
|
||||
match self {
|
||||
ChangeSpendPolicy::ChangeAllowed => iter.collect(),
|
||||
ChangeSpendPolicy::OnlyChange => iter.filter(|utxo| utxo.is_internal).collect(),
|
||||
ChangeSpendPolicy::ChangeForbidden => iter.filter(|utxo| !utxo.is_internal).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
const ORDERING_TEST_TX: &'static str = "0200000003c26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f54\
|
||||
|
||||
Reference in New Issue
Block a user