@@ -24,6 +24,7 @@ use crate::ln::chan_utils::{
24
24
} ;
25
25
use crate :: events:: Event ;
26
26
use crate :: prelude:: HashMap ;
27
+ use crate :: sync:: Mutex ;
27
28
use crate :: util:: logger:: Logger ;
28
29
29
30
use bitcoin:: { OutPoint , PackedLockTime , PubkeyHash , Sequence , Script , Transaction , Txid , TxIn , TxOut , Witness , WPubkeyHash } ;
@@ -368,7 +369,8 @@ pub struct CoinSelection {
368
369
369
370
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
370
371
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
371
- /// which most wallets should be able to satisfy.
372
+ /// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
373
+ /// which can provide a default implementation of this trait when used with [`Wallet`].
372
374
pub trait CoinSelectionSource {
373
375
/// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
374
376
/// available to spend. Implementations are free to pick their coin selection algorithm of
@@ -406,6 +408,148 @@ pub trait CoinSelectionSource {
406
408
fn sign_tx ( & self , tx : & mut Transaction ) -> Result < ( ) , ( ) > ;
407
409
}
408
410
411
+ /// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
412
+ /// provide a default implementation to [`CoinSelectionSource`].
413
+ pub trait WalletSource {
414
+ /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
415
+ fn list_confirmed_utxos ( & self ) -> Result < Vec < Utxo > , ( ) > ;
416
+ /// Returns a script to use for change above dust resulting from a successful coin selection
417
+ /// attempt.
418
+ fn get_change_script ( & self ) -> Result < Script , ( ) > ;
419
+ /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
420
+ /// the transaction known to the wallet (i.e., any provided via
421
+ /// [`WalletSource::list_confirmed_utxos`]).
422
+ fn sign_tx ( & self , tx : & mut Transaction ) -> Result < ( ) , ( ) > ;
423
+ }
424
+
425
+ /// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
426
+ /// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double
427
+ /// spends may happen.
428
+ pub struct Wallet < W : Deref > where W :: Target : WalletSource {
429
+ source : W ,
430
+ // TODO: Do we care about cleaning this up once the UTXOs have a confirmed spend? We can do so
431
+ // by checking whether any UTXOs that exist in the map are no longer returned in
432
+ // `list_confirmed_utxos`.
433
+ locked_utxos : Mutex < HashMap < OutPoint , ClaimId > > ,
434
+ }
435
+
436
+ impl < W : Deref > Wallet < W > where W :: Target : WalletSource {
437
+ /// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
438
+ /// of [`CoinSelectionSource`].
439
+ pub fn new ( source : W ) -> Self {
440
+ Self { source, locked_utxos : Mutex :: new ( HashMap :: new ( ) ) }
441
+ }
442
+
443
+ /// Performs coin selection on the set of UTXOs obtained from
444
+ /// [`WalletSource::list_confirmed_utxos`]. Its algorithm can be described as "smallest
445
+ /// above-dust-after-spend first", with a slight twist: we may skip UTXOs that are above dust at
446
+ /// the target feerate after having spent them in a separate claim transaction if
447
+ /// `force_conflicting_utxo_spend` is unset to avoid producing conflicting transactions. If
448
+ /// `tolerate_high_network_feerates` is set, we'll attempt to spend UTXOs that contribute at
449
+ /// least 1 satoshi at the current feerate, otherwise, we'll only attempt to spend those which
450
+ /// contribute at least twice their fee.
451
+ fn select_confirmed_utxos_internal (
452
+ & self , utxos : & [ Utxo ] , claim_id : ClaimId , force_conflicting_utxo_spend : bool ,
453
+ tolerate_high_network_feerates : bool , target_feerate_sat_per_1000_weight : u32 ,
454
+ preexisting_tx_weight : u64 , target_amount_sat : u64 ,
455
+ ) -> Result < CoinSelection , ( ) > {
456
+ let mut locked_utxos = self . locked_utxos . lock ( ) . unwrap ( ) ;
457
+ let mut eligible_utxos = utxos. iter ( ) . filter_map ( |utxo| {
458
+ if let Some ( utxo_claim_id) = locked_utxos. get ( & utxo. outpoint ) {
459
+ if * utxo_claim_id != claim_id && !force_conflicting_utxo_spend {
460
+ return None ;
461
+ }
462
+ }
463
+ let fee_to_spend_utxo = fee_for_weight (
464
+ target_feerate_sat_per_1000_weight, BASE_INPUT_WEIGHT as u64 + utxo. satisfaction_weight ,
465
+ ) ;
466
+ let should_spend = if tolerate_high_network_feerates {
467
+ utxo. output . value > fee_to_spend_utxo
468
+ } else {
469
+ utxo. output . value >= fee_to_spend_utxo * 2
470
+ } ;
471
+ if should_spend {
472
+ Some ( ( utxo, fee_to_spend_utxo) )
473
+ } else {
474
+ None
475
+ }
476
+ } ) . collect :: < Vec < _ > > ( ) ;
477
+ eligible_utxos. sort_unstable_by_key ( |( utxo, _) | utxo. output . value ) ;
478
+
479
+ let mut selected_amount = 0 ;
480
+ let mut total_fees = fee_for_weight ( target_feerate_sat_per_1000_weight, preexisting_tx_weight) ;
481
+ let mut selected_utxos = Vec :: new ( ) ;
482
+ for ( utxo, fee_to_spend_utxo) in eligible_utxos {
483
+ if selected_amount >= target_amount_sat + total_fees {
484
+ break ;
485
+ }
486
+ selected_amount += utxo. output . value ;
487
+ total_fees += fee_to_spend_utxo;
488
+ selected_utxos. push ( utxo. clone ( ) ) ;
489
+ }
490
+ if selected_amount < target_amount_sat + total_fees {
491
+ return Err ( ( ) ) ;
492
+ }
493
+ for utxo in & selected_utxos {
494
+ locked_utxos. insert ( utxo. outpoint , claim_id) ;
495
+ }
496
+ core:: mem:: drop ( locked_utxos) ;
497
+
498
+ let remaining_amount = selected_amount - target_amount_sat - total_fees;
499
+ let change_script = self . source . get_change_script ( ) ?;
500
+ let change_output_fee = fee_for_weight (
501
+ target_feerate_sat_per_1000_weight,
502
+ ( 8 /* value */ + change_script. consensus_encode ( & mut sink ( ) ) . unwrap ( ) as u64 ) *
503
+ WITNESS_SCALE_FACTOR as u64 ,
504
+ ) ;
505
+ let change_output_amount = remaining_amount. saturating_sub ( change_output_fee) ;
506
+ let change_output = if change_output_amount < change_script. dust_value ( ) . to_sat ( ) {
507
+ None
508
+ } else {
509
+ Some ( TxOut { script_pubkey : change_script, value : change_output_amount } )
510
+ } ;
511
+
512
+ Ok ( CoinSelection {
513
+ confirmed_utxos : selected_utxos,
514
+ change_output,
515
+ } )
516
+ }
517
+ }
518
+
519
+ impl < W : Deref > CoinSelectionSource for Wallet < W > where W :: Target : WalletSource {
520
+ fn select_confirmed_utxos (
521
+ & self , claim_id : ClaimId , must_spend : & [ Input ] , must_pay_to : & [ TxOut ] ,
522
+ target_feerate_sat_per_1000_weight : u32 ,
523
+ ) -> Result < CoinSelection , ( ) > {
524
+ let utxos = self . source . list_confirmed_utxos ( ) ?;
525
+ // TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
526
+ const BASE_TX_SIZE : u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */ ;
527
+ let total_output_size: u64 = must_pay_to. iter ( ) . map ( |output|
528
+ 8 /* value */ + 1 /* script len */ + output. script_pubkey . len ( ) as u64
529
+ ) . sum ( ) ;
530
+ let total_satisfaction_weight: u64 = must_spend. iter ( ) . map ( |input| input. satisfaction_weight ) . sum ( ) ;
531
+ let total_input_weight = ( BASE_INPUT_WEIGHT * must_spend. len ( ) as u64 ) + total_satisfaction_weight;
532
+
533
+ let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight +
534
+ ( ( BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64 ) ;
535
+ let target_amount_sat = must_pay_to. iter ( ) . map ( |output| output. value ) . sum ( ) ;
536
+ let do_coin_selection = |force_conflicting_utxo_spend : bool , tolerate_high_network_feerates : bool | {
537
+ self . select_confirmed_utxos_internal (
538
+ & utxos, claim_id, force_conflicting_utxo_spend, tolerate_high_network_feerates,
539
+ target_feerate_sat_per_1000_weight, preexisting_tx_weight, target_amount_sat,
540
+ )
541
+ } ;
542
+ do_coin_selection ( false , false )
543
+ . or_else ( |_| do_coin_selection ( false , true ) )
544
+ . or_else ( |_| do_coin_selection ( true , false ) )
545
+ . or_else ( |_| do_coin_selection ( true , true ) )
546
+ }
547
+
548
+ fn sign_tx ( & self , tx : & mut Transaction ) -> Result < ( ) , ( ) > {
549
+ self . source . sign_tx ( tx)
550
+ }
551
+ }
552
+
409
553
/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
410
554
/// [`CoinSelectionSource`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or
411
555
/// Replace-By-Fee (RBF).
0 commit comments