Skip to content

Commit

Permalink
Specify a dust threshold for coin selection
Browse files Browse the repository at this point in the history
- Prevents spending any dust ouputs using a conservative threshold. This is necessary because we don't have a way to track unconfirmed space outputs yet.

- Simplify coin selection further removing the needing for adding foreign utxos as we have switched to just two set of descriptors.
  • Loading branch information
buffrr committed Nov 8, 2024
1 parent 11d5062 commit e2256c0
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 20 deletions.
20 changes: 17 additions & 3 deletions node/src/wallets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ impl RpcWallet {
balance,
dust: unspent
.into_iter()
.filter(|output| output.is_spaceout)
.filter(|output| output.is_spaceout ||
output.output.txout.value <= SpacesAwareCoinSelection::DUST_THRESHOLD)
.map(|output| output.output.txout.value)
.sum(),
};
Expand Down Expand Up @@ -361,13 +362,17 @@ impl RpcWallet {
wallet: &mut SpacesWallet,
state: &mut LiveSnapshot,
) -> anyhow::Result<SpacesAwareCoinSelection> {
// exclude all space outs
// Filters out all "space outs" from the selection.
// Note: This exclusion only applies to confirmed space outs; unconfirmed ones are not excluded.
// In practice, this should be fine since Spaces coin selection skips dust by default,
// so explicitly excluding space outs may be redundant.
let excluded = Self::list_unspent(wallet, state)?
.into_iter()
.filter(|out| out.is_spaceout)
.map(|out| out.output.outpoint)
.collect::<Vec<_>>();
Ok(SpacesAwareCoinSelection::new(Vec::new(), excluded))

Ok(SpacesAwareCoinSelection::new(excluded))
}

fn list_unspent(
Expand Down Expand Up @@ -437,6 +442,15 @@ impl RpcWallet {
store: &mut LiveSnapshot,
tx: RpcWalletTxBuilder,
) -> anyhow::Result<WalletResponse> {
if let Some(dust) = tx.dust {
if dust > SpacesAwareCoinSelection::DUST_THRESHOLD {
// Allowing higher dust may space outs to be accidentally
// spent during coin selection
return Err(anyhow!("dust cannot be higher than {}",
SpacesAwareCoinSelection::DUST_THRESHOLD));
}
}

let fee_rate = match tx.fee_rate.as_ref() {
None => match Self::estimate_fee_rate(source) {
None => return Err(anyhow!("could not estimate fee rate")),
Expand Down
30 changes: 13 additions & 17 deletions wallet/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{
cell::RefCell,
cmp::min,
collections::BTreeMap,
default::Default,
Expand Down Expand Up @@ -235,7 +234,7 @@ impl<'a, Cs: CoinSelectionAlgorithm> TxBuilderSpacesUtils<'a, Cs> for TxBuilder<
placeholder.auction.outpoint.vout as u8,
&offer,
)?)
.expect("compressed psbt script bytes");
.expect("compressed psbt script bytes");

let carrier = ScriptBuf::new_op_return(&compressed_psbt);

Expand Down Expand Up @@ -895,23 +894,25 @@ impl Builder {
}
}

/// A coin selection algorithm that guarantees required utxos are ordered first
/// appends any funding/change outputs to the end of the selected utxos
/// also enables adding additional optional foreign utxos for funding
/// A coin selection algorithm that :
/// 1. Guarantees required utxos are ordered first appending
/// any funding/change outputs to the end of the selected utxos.
/// 2. Excludes all dust outputs to avoid accidentally spending space utxos
/// 3. Enables adding additional output exclusions
#[derive(Debug, Clone)]
pub struct SpacesAwareCoinSelection {
pub default_algorithm: DefaultCoinSelectionAlgorithm,
// Additional UTXOs to fund the transaction
pub other_optional_utxos: RefCell<Vec<WeightedUtxo>>,
// Exclude outputs
pub exclude_outputs: Vec<OutPoint>,
}

impl SpacesAwareCoinSelection {
pub fn new(optional_utxos: Vec<WeightedUtxo>, excluded: Vec<OutPoint>) -> Self {
// Will skip any outputs with value less than the dust threshold
// to avoid accidentally spending space outputs
pub const DUST_THRESHOLD: Amount = Amount::from_sat(1200);
pub fn new(excluded: Vec<OutPoint>) -> Self {
Self {
default_algorithm: DefaultCoinSelectionAlgorithm::default(),
other_optional_utxos: RefCell::new(optional_utxos),
exclude_outputs: excluded,
}
}
Expand All @@ -931,12 +932,10 @@ impl CoinSelectionAlgorithm for SpacesAwareCoinSelection {
.map(|w| w.utxo.clone())
.collect::<Vec<_>>();

// Extend optional outputs
optional_utxos.extend(self.other_optional_utxos.borrow().iter().cloned());

// Filter out excluded outputs
// Filter out UTXOs that are either explicitly excluded or below the dust threshold
optional_utxos.retain(|weighted_utxo| {
!self
weighted_utxo.utxo.txout().value > SpacesAwareCoinSelection::DUST_THRESHOLD
&& !self
.exclude_outputs
.contains(&weighted_utxo.utxo.outpoint())
});
Expand All @@ -952,9 +951,6 @@ impl CoinSelectionAlgorithm for SpacesAwareCoinSelection {
let mut optional = Vec::with_capacity(result.selected.len() - required.len());
for utxo in result.selected.drain(..) {
if !required.iter().any(|u| u == &utxo) {
self.other_optional_utxos
.borrow_mut()
.retain(|x| x.utxo != utxo);
optional.push(utxo);
}
}
Expand Down

0 comments on commit e2256c0

Please sign in to comment.