Skip to content

Commit 6fd5e5b

Browse files
committed
Convert BumpTransactionEventHandler to async
Changes the CoinSelectionSource and WalletSource traits to be async and provides WalletSourceSyncWrapper to as a helper for users that want to implement a sync wallet source. TestWalletSource is kept sync, to prevent a cascade of async conversions in tests.
1 parent 86bf6d1 commit 6fd5e5b

7 files changed

+180
-88
lines changed

lightning/src/events/bump_transaction.rs

+144-72
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::sign::{
3030
ChannelDerivationParameters, HTLCDescriptor, SignerProvider, P2WPKH_WITNESS_WEIGHT,
3131
};
3232
use crate::sync::Mutex;
33+
use crate::util::async_poll::{AsyncResult, MaybeSend, MaybeSync};
3334
use crate::util::logger::Logger;
3435

3536
use bitcoin::amount::Amount;
@@ -346,21 +347,38 @@ pub trait CoinSelectionSource {
346347
/// other claims, implementations must be willing to double spend their UTXOs. The choice of
347348
/// which UTXOs to double spend is left to the implementation, but it must strive to keep the
348349
/// set of other claims being double spent to a minimum.
349-
fn select_confirmed_utxos(
350-
&self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
350+
fn select_confirmed_utxos<'a>(
351+
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
351352
target_feerate_sat_per_1000_weight: u32,
352-
) -> Result<CoinSelection, ()>;
353+
) -> AsyncResult<'a, CoinSelection>;
353354
/// Signs and provides the full witness for all inputs within the transaction known to the
354355
/// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]).
355356
///
356357
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
357358
/// unsigned transaction and then sign it with your wallet.
358-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
359+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction>;
359360
}
360361

361362
/// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
362363
/// provide a default implementation to [`CoinSelectionSource`].
363364
pub trait WalletSource {
365+
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
366+
fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>>;
367+
/// Returns a script to use for change above dust resulting from a successful coin selection
368+
/// attempt.
369+
fn get_change_script<'a>(&self) -> AsyncResult<'a, ScriptBuf>;
370+
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
371+
/// the transaction known to the wallet (i.e., any provided via
372+
/// [`WalletSource::list_confirmed_utxos`]).
373+
///
374+
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
375+
/// unsigned transaction and then sign it with your wallet.
376+
fn sign_psbt<'a>(&self, psbt: Psbt) -> AsyncResult<'a, Transaction>;
377+
}
378+
379+
/// A synchronous version of the [`WalletSource`] trait. Implementations of this trait should be wrapped in
380+
/// WalletSourceSyncWrapper for it to be used within rust-lightning.
381+
pub trait WalletSourceSync {
364382
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
365383
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
366384
/// Returns a script to use for change above dust resulting from a successful coin selection
@@ -375,13 +393,54 @@ pub trait WalletSource {
375393
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
376394
}
377395

396+
/// A wrapper around [`WalletSourceSync`] to allow for async calls.
397+
pub struct WalletSourceSyncWrapper<T: Deref>(T)
398+
where
399+
T::Target: WalletSourceSync;
400+
401+
impl<T: Deref> WalletSourceSyncWrapper<T>
402+
where
403+
T::Target: WalletSourceSync,
404+
{
405+
/// Creates a new [`WalletSourceSyncWrapper`].
406+
pub fn new(source: T) -> Self {
407+
Self(source)
408+
}
409+
}
410+
impl<T: Deref> WalletSource for WalletSourceSyncWrapper<T>
411+
where
412+
T::Target: WalletSourceSync,
413+
{
414+
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend. Wraps
415+
/// [`WalletSourceSync::list_confirmed_utxos`].
416+
fn list_confirmed_utxos<'a>(&'a self) -> AsyncResult<'a, Vec<Utxo>> {
417+
let utxos = self.0.list_confirmed_utxos();
418+
Box::pin(async move { utxos })
419+
}
420+
421+
/// Returns a script to use for change above dust resulting from a successful coin selection attempt. Wraps
422+
/// [`WalletSourceSync::get_change_script`].
423+
fn get_change_script<'a>(&self) -> AsyncResult<'a, ScriptBuf> {
424+
let script = self.0.get_change_script();
425+
Box::pin(async move { script })
426+
}
427+
428+
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within the transaction
429+
/// known to the wallet (i.e., any provided via [`WalletSource::list_confirmed_utxos`]). Wraps
430+
/// [`WalletSourceSync::sign_psbt`].
431+
fn sign_psbt<'a>(&self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
432+
let signed_psbt = self.0.sign_psbt(psbt);
433+
Box::pin(async move { signed_psbt })
434+
}
435+
}
436+
378437
/// A wrapper over [`WalletSource`] that implements [`CoinSelection`] by preferring UTXOs that would
379438
/// avoid conflicting double spends. If not enough UTXOs are available to do so, conflicting double
380439
/// spends may happen.
381-
pub struct Wallet<W: Deref, L: Deref>
440+
pub struct Wallet<W: Deref + MaybeSync + MaybeSend, L: Deref + MaybeSync + MaybeSend>
382441
where
383-
W::Target: WalletSource,
384-
L::Target: Logger,
442+
W::Target: WalletSource + MaybeSend,
443+
L::Target: Logger + MaybeSend,
385444
{
386445
source: W,
387446
logger: L,
@@ -391,10 +450,10 @@ where
391450
locked_utxos: Mutex<HashMap<OutPoint, ClaimId>>,
392451
}
393452

394-
impl<W: Deref, L: Deref> Wallet<W, L>
453+
impl<W: Deref + MaybeSync + MaybeSend, L: Deref + MaybeSync + MaybeSend> Wallet<W, L>
395454
where
396-
W::Target: WalletSource,
397-
L::Target: Logger,
455+
W::Target: WalletSource + MaybeSend,
456+
L::Target: Logger + MaybeSend,
398457
{
399458
/// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
400459
/// of [`CoinSelectionSource`].
@@ -410,7 +469,7 @@ where
410469
/// `tolerate_high_network_feerates` is set, we'll attempt to spend UTXOs that contribute at
411470
/// least 1 satoshi at the current feerate, otherwise, we'll only attempt to spend those which
412471
/// contribute at least twice their fee.
413-
fn select_confirmed_utxos_internal(
472+
async fn select_confirmed_utxos_internal(
414473
&self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool,
415474
tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32,
416475
preexisting_tx_weight: u64, input_amount_sat: Amount, target_amount_sat: Amount,
@@ -484,7 +543,7 @@ where
484543
}
485544

486545
let remaining_amount = selected_amount - target_amount_sat - total_fees;
487-
let change_script = self.source.get_change_script()?;
546+
let change_script = self.source.get_change_script().await?;
488547
let change_output_fee = fee_for_weight(
489548
target_feerate_sat_per_1000_weight,
490549
(8 /* value */ + change_script.consensus_encode(&mut sink()).unwrap() as u64)
@@ -503,60 +562,67 @@ where
503562
}
504563
}
505564

506-
impl<W: Deref, L: Deref> CoinSelectionSource for Wallet<W, L>
565+
impl<W: Deref + MaybeSync + MaybeSend, L: Deref + MaybeSync + MaybeSend> CoinSelectionSource
566+
for Wallet<W, L>
507567
where
508-
W::Target: WalletSource,
509-
L::Target: Logger,
568+
W::Target: WalletSource + MaybeSend + MaybeSync,
569+
L::Target: Logger + MaybeSend + MaybeSync,
510570
{
511-
fn select_confirmed_utxos(
512-
&self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
571+
fn select_confirmed_utxos<'a>(
572+
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
513573
target_feerate_sat_per_1000_weight: u32,
514-
) -> Result<CoinSelection, ()> {
515-
let utxos = self.source.list_confirmed_utxos()?;
516-
// TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
517-
const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */;
518-
let total_output_size: u64 = must_pay_to
519-
.iter()
520-
.map(|output| 8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64)
521-
.sum();
522-
let total_satisfaction_weight: u64 =
523-
must_spend.iter().map(|input| input.satisfaction_weight).sum();
524-
let total_input_weight =
525-
(BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight;
526-
527-
let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight +
574+
) -> AsyncResult<'a, CoinSelection> {
575+
Box::pin(async move {
576+
let utxos = self.source.list_confirmed_utxos().await?;
577+
// TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
578+
const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */;
579+
let total_output_size: u64 = must_pay_to
580+
.iter()
581+
.map(
582+
|output| 8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64,
583+
)
584+
.sum();
585+
let total_satisfaction_weight: u64 =
586+
must_spend.iter().map(|input| input.satisfaction_weight).sum();
587+
let total_input_weight =
588+
(BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight;
589+
590+
let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight +
528591
((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64);
529-
let input_amount_sat = must_spend.iter().map(|input| input.previous_utxo.value).sum();
530-
let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum();
592+
let input_amount_sat = must_spend.iter().map(|input| input.previous_utxo.value).sum();
593+
let target_amount_sat = must_pay_to.iter().map(|output| output.value).sum();
531594

532-
let configs = [(false, false), (false, true), (true, false), (true, true)];
533-
for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs {
534-
log_debug!(
595+
let configs = [(false, false), (false, true), (true, false), (true, true)];
596+
for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs {
597+
log_debug!(
535598
self.logger,
536599
"Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
537600
target_feerate_sat_per_1000_weight,
538601
force_conflicting_utxo_spend,
539602
tolerate_high_network_feerates
540603
);
541-
let attempt = self.select_confirmed_utxos_internal(
542-
&utxos,
543-
claim_id,
544-
force_conflicting_utxo_spend,
545-
tolerate_high_network_feerates,
546-
target_feerate_sat_per_1000_weight,
547-
preexisting_tx_weight,
548-
input_amount_sat,
549-
target_amount_sat,
550-
);
551-
if attempt.is_ok() {
552-
return attempt;
604+
let attempt = self
605+
.select_confirmed_utxos_internal(
606+
&utxos,
607+
claim_id,
608+
force_conflicting_utxo_spend,
609+
tolerate_high_network_feerates,
610+
target_feerate_sat_per_1000_weight,
611+
preexisting_tx_weight,
612+
input_amount_sat,
613+
target_amount_sat,
614+
)
615+
.await;
616+
if attempt.is_ok() {
617+
return attempt;
618+
}
553619
}
554-
}
555-
Err(())
620+
Err(())
621+
})
556622
}
557623

558-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
559-
self.source.sign_psbt(psbt)
624+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
625+
Box::pin(async move { self.source.sign_psbt(psbt).await })
560626
}
561627
}
562628

@@ -662,12 +728,15 @@ where
662728

663729
log_debug!(self.logger, "Performing coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
664730
package_target_feerate_sat_per_1000_weight);
665-
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
666-
claim_id,
667-
must_spend,
668-
&[],
669-
package_target_feerate_sat_per_1000_weight,
670-
)?;
731+
let coin_selection: CoinSelection = self
732+
.utxo_source
733+
.select_confirmed_utxos(
734+
claim_id,
735+
must_spend,
736+
&[],
737+
package_target_feerate_sat_per_1000_weight,
738+
)
739+
.await?;
671740

672741
let mut anchor_tx = Transaction {
673742
version: Version::TWO,
@@ -733,7 +802,7 @@ where
733802
}
734803

735804
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
736-
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?;
805+
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt).await?;
737806

738807
let signer = self
739808
.signer_provider
@@ -821,12 +890,15 @@ where
821890
let must_spend_amount =
822891
must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::<u64>();
823892

824-
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
825-
claim_id,
826-
must_spend,
827-
&htlc_tx.output,
828-
target_feerate_sat_per_1000_weight,
829-
)?;
893+
let coin_selection: CoinSelection = self
894+
.utxo_source
895+
.select_confirmed_utxos(
896+
claim_id,
897+
must_spend,
898+
&htlc_tx.output,
899+
target_feerate_sat_per_1000_weight,
900+
)
901+
.await?;
830902

831903
#[cfg(debug_assertions)]
832904
let input_satisfaction_weight: u64 =
@@ -870,7 +942,7 @@ where
870942
"Signing HTLC transaction {}",
871943
htlc_psbt.unsigned_tx.compute_txid()
872944
);
873-
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?;
945+
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?;
874946

875947
let mut signers = BTreeMap::new();
876948
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
@@ -993,27 +1065,27 @@ mod tests {
9931065
expected_selects: Mutex<Vec<(u64, u64, u32, CoinSelection)>>,
9941066
}
9951067
impl CoinSelectionSource for TestCoinSelectionSource {
996-
fn select_confirmed_utxos(
997-
&self, _claim_id: ClaimId, must_spend: Vec<Input>, _must_pay_to: &[TxOut],
1068+
fn select_confirmed_utxos<'a>(
1069+
&'a self, _claim_id: ClaimId, must_spend: Vec<Input>, _must_pay_to: &'a [TxOut],
9981070
target_feerate_sat_per_1000_weight: u32,
999-
) -> Result<CoinSelection, ()> {
1071+
) -> AsyncResult<'a, CoinSelection> {
10001072
let mut expected_selects = self.expected_selects.lock().unwrap();
10011073
let (weight, value, feerate, res) = expected_selects.remove(0);
10021074
assert_eq!(must_spend.len(), 1);
10031075
assert_eq!(must_spend[0].satisfaction_weight, weight);
10041076
assert_eq!(must_spend[0].previous_utxo.value.to_sat(), value);
10051077
assert_eq!(target_feerate_sat_per_1000_weight, feerate);
1006-
Ok(res)
1078+
Box::pin(async move { Ok(res) })
10071079
}
1008-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
1080+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
10091081
let mut tx = psbt.unsigned_tx;
10101082
for input in tx.input.iter_mut() {
10111083
if input.previous_output.txid != Txid::from_byte_array([44; 32]) {
10121084
// Channel output, add a realistic size witness to make the assertions happy
10131085
input.witness = Witness::from_slice(&[vec![42; 162]]);
10141086
}
10151087
}
1016-
Ok(tx)
1088+
Box::pin(async move { Ok(tx) })
10171089
}
10181090
}
10191091

lightning/src/ln/async_signer_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use bitcoin::transaction::Version;
1818

1919
use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
2020
use crate::chain::ChannelMonitorUpdateStatus;
21-
use crate::events::bump_transaction::WalletSource;
21+
use crate::events::bump_transaction::WalletSourceSync;
2222
use crate::events::{ClosureReason, Event};
2323
use crate::ln::chan_utils::ClosingTransaction;
2424
use crate::ln::channel::DISCONNECT_PEER_AWAITING_RESPONSE_TICKS;

lightning/src/ln/functional_test_utils.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch
1414
use crate::chain::channelmonitor::ChannelMonitor;
1515
use crate::chain::transaction::OutPoint;
1616
use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCHandlingFailureType, PaidBolt12Invoice, PathFailure, PaymentFailureReason, PaymentPurpose};
17-
use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource};
17+
use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSourceSync, WalletSourceSyncWrapper};
1818
use crate::ln::types::ChannelId;
1919
use crate::types::features::ChannelTypeFeatures;
2020
use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
@@ -472,7 +472,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
472472
pub wallet_source: Arc<test_utils::TestWalletSource>,
473473
pub bump_tx_handler: BumpTransactionEventHandler<
474474
&'chan_mon_cfg test_utils::TestBroadcaster,
475-
Arc<Wallet<Arc<test_utils::TestWalletSource>, &'chan_mon_cfg test_utils::TestLogger>>,
475+
Arc<Wallet<Arc<WalletSourceSyncWrapper<Arc<test_utils::TestWalletSource>>>, &'chan_mon_cfg test_utils::TestLogger>>,
476476
&'chan_mon_cfg test_utils::TestKeysInterface,
477477
&'chan_mon_cfg test_utils::TestLogger,
478478
>,
@@ -3422,6 +3422,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
34223422
);
34233423
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
34243424
let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
3425+
let wallet_source_async = Arc::new(WalletSourceSyncWrapper::new(wallet_source.clone()));
34253426
nodes.push(Node{
34263427
chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
34273428
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
@@ -3435,7 +3436,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
34353436
override_init_features: Rc::clone(&cfgs[i].override_init_features),
34363437
wallet_source: Arc::clone(&wallet_source),
34373438
bump_tx_handler: BumpTransactionEventHandler::new(
3438-
cfgs[i].tx_broadcaster, Arc::new(Wallet::new(Arc::clone(&wallet_source), cfgs[i].logger)),
3439+
cfgs[i].tx_broadcaster, Arc::new(Wallet::new(wallet_source_async, cfgs[i].logger)),
34393440
&cfgs[i].keys_manager, cfgs[i].logger,
34403441
),
34413442
})

lightning/src/ln/functional_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::chain::channelmonitor::{
2020
};
2121
use crate::chain::transaction::OutPoint;
2222
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
23-
use crate::events::bump_transaction::WalletSource;
23+
use crate::events::bump_transaction::WalletSourceSync;
2424
use crate::events::{
2525
ClosureReason, Event, FundingInfo, HTLCHandlingFailureType, PathFailure, PaymentFailureReason,
2626
PaymentPurpose,

0 commit comments

Comments
 (0)