Skip to content

Commit e2256c0

Browse files
committed
Specify a dust threshold for coin selection
- 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.
1 parent 11d5062 commit e2256c0

File tree

2 files changed

+30
-20
lines changed

2 files changed

+30
-20
lines changed

node/src/wallets.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ impl RpcWallet {
154154
balance,
155155
dust: unspent
156156
.into_iter()
157-
.filter(|output| output.is_spaceout)
157+
.filter(|output| output.is_spaceout ||
158+
output.output.txout.value <= SpacesAwareCoinSelection::DUST_THRESHOLD)
158159
.map(|output| output.output.txout.value)
159160
.sum(),
160161
};
@@ -361,13 +362,17 @@ impl RpcWallet {
361362
wallet: &mut SpacesWallet,
362363
state: &mut LiveSnapshot,
363364
) -> anyhow::Result<SpacesAwareCoinSelection> {
364-
// exclude all space outs
365+
// Filters out all "space outs" from the selection.
366+
// Note: This exclusion only applies to confirmed space outs; unconfirmed ones are not excluded.
367+
// In practice, this should be fine since Spaces coin selection skips dust by default,
368+
// so explicitly excluding space outs may be redundant.
365369
let excluded = Self::list_unspent(wallet, state)?
366370
.into_iter()
367371
.filter(|out| out.is_spaceout)
368372
.map(|out| out.output.outpoint)
369373
.collect::<Vec<_>>();
370-
Ok(SpacesAwareCoinSelection::new(Vec::new(), excluded))
374+
375+
Ok(SpacesAwareCoinSelection::new(excluded))
371376
}
372377

373378
fn list_unspent(
@@ -437,6 +442,15 @@ impl RpcWallet {
437442
store: &mut LiveSnapshot,
438443
tx: RpcWalletTxBuilder,
439444
) -> anyhow::Result<WalletResponse> {
445+
if let Some(dust) = tx.dust {
446+
if dust > SpacesAwareCoinSelection::DUST_THRESHOLD {
447+
// Allowing higher dust may space outs to be accidentally
448+
// spent during coin selection
449+
return Err(anyhow!("dust cannot be higher than {}",
450+
SpacesAwareCoinSelection::DUST_THRESHOLD));
451+
}
452+
}
453+
440454
let fee_rate = match tx.fee_rate.as_ref() {
441455
None => match Self::estimate_fee_rate(source) {
442456
None => return Err(anyhow!("could not estimate fee rate")),

wallet/src/builder.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::{
2-
cell::RefCell,
32
cmp::min,
43
collections::BTreeMap,
54
default::Default,
@@ -235,7 +234,7 @@ impl<'a, Cs: CoinSelectionAlgorithm> TxBuilderSpacesUtils<'a, Cs> for TxBuilder<
235234
placeholder.auction.outpoint.vout as u8,
236235
&offer,
237236
)?)
238-
.expect("compressed psbt script bytes");
237+
.expect("compressed psbt script bytes");
239238

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

@@ -895,23 +894,25 @@ impl Builder {
895894
}
896895
}
897896

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

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

934-
// Extend optional outputs
935-
optional_utxos.extend(self.other_optional_utxos.borrow().iter().cloned());
936-
937-
// Filter out excluded outputs
935+
// Filter out UTXOs that are either explicitly excluded or below the dust threshold
938936
optional_utxos.retain(|weighted_utxo| {
939-
!self
937+
weighted_utxo.utxo.txout().value > SpacesAwareCoinSelection::DUST_THRESHOLD
938+
&& !self
940939
.exclude_outputs
941940
.contains(&weighted_utxo.utxo.outpoint())
942941
});
@@ -952,9 +951,6 @@ impl CoinSelectionAlgorithm for SpacesAwareCoinSelection {
952951
let mut optional = Vec::with_capacity(result.selected.len() - required.len());
953952
for utxo in result.selected.drain(..) {
954953
if !required.iter().any(|u| u == &utxo) {
955-
self.other_optional_utxos
956-
.borrow_mut()
957-
.retain(|x| x.utxo != utxo);
958954
optional.push(utxo);
959955
}
960956
}

0 commit comments

Comments
 (0)