Skip to content

Commit 991d505

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 8f3eea5 commit 991d505

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,
@@ -481,7 +540,7 @@ where
481540
}
482541

483542
let remaining_amount = selected_amount - target_amount_sat - total_fees;
484-
let change_script = self.source.get_change_script()?;
543+
let change_script = self.source.get_change_script().await?;
485544
let change_output_fee = fee_for_weight(
486545
target_feerate_sat_per_1000_weight,
487546
(8 /* value */ + change_script.consensus_encode(&mut sink()).unwrap() as u64)
@@ -500,60 +559,67 @@ where
500559
}
501560
}
502561

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

529-
let configs = [(false, false), (false, true), (true, false), (true, true)];
530-
for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs {
531-
log_debug!(
592+
let configs = [(false, false), (false, true), (true, false), (true, true)];
593+
for (force_conflicting_utxo_spend, tolerate_high_network_feerates) in configs {
594+
log_debug!(
532595
self.logger,
533596
"Attempting coin selection targeting {} sat/kW (force_conflicting_utxo_spend = {}, tolerate_high_network_feerates = {})",
534597
target_feerate_sat_per_1000_weight,
535598
force_conflicting_utxo_spend,
536599
tolerate_high_network_feerates
537600
);
538-
let attempt = self.select_confirmed_utxos_internal(
539-
&utxos,
540-
claim_id,
541-
force_conflicting_utxo_spend,
542-
tolerate_high_network_feerates,
543-
target_feerate_sat_per_1000_weight,
544-
preexisting_tx_weight,
545-
input_amount_sat,
546-
target_amount_sat,
547-
);
548-
if attempt.is_ok() {
549-
return attempt;
601+
let attempt = self
602+
.select_confirmed_utxos_internal(
603+
&utxos,
604+
claim_id,
605+
force_conflicting_utxo_spend,
606+
tolerate_high_network_feerates,
607+
target_feerate_sat_per_1000_weight,
608+
preexisting_tx_weight,
609+
input_amount_sat,
610+
target_amount_sat,
611+
)
612+
.await;
613+
if attempt.is_ok() {
614+
return attempt;
615+
}
550616
}
551-
}
552-
Err(())
617+
Err(())
618+
})
553619
}
554620

555-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
556-
self.source.sign_psbt(psbt)
621+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
622+
Box::pin(async move { self.source.sign_psbt(psbt).await })
557623
}
558624
}
559625

@@ -659,12 +725,15 @@ where
659725

660726
log_debug!(self.logger, "Performing coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
661727
package_target_feerate_sat_per_1000_weight);
662-
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
663-
claim_id,
664-
must_spend,
665-
&[],
666-
package_target_feerate_sat_per_1000_weight,
667-
)?;
728+
let coin_selection: CoinSelection = self
729+
.utxo_source
730+
.select_confirmed_utxos(
731+
claim_id,
732+
must_spend,
733+
&[],
734+
package_target_feerate_sat_per_1000_weight,
735+
)
736+
.await?;
668737

669738
let mut anchor_tx = Transaction {
670739
version: Version::TWO,
@@ -730,7 +799,7 @@ where
730799
}
731800

732801
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
733-
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?;
802+
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt).await?;
734803

735804
let signer = self
736805
.signer_provider
@@ -818,12 +887,15 @@ where
818887
let must_spend_amount =
819888
must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::<u64>();
820889

821-
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
822-
claim_id,
823-
must_spend,
824-
&htlc_tx.output,
825-
target_feerate_sat_per_1000_weight,
826-
)?;
890+
let coin_selection: CoinSelection = self
891+
.utxo_source
892+
.select_confirmed_utxos(
893+
claim_id,
894+
must_spend,
895+
&htlc_tx.output,
896+
target_feerate_sat_per_1000_weight,
897+
)
898+
.await?;
827899

828900
#[cfg(debug_assertions)]
829901
let input_satisfaction_weight: u64 =
@@ -867,7 +939,7 @@ where
867939
"Signing HTLC transaction {}",
868940
htlc_psbt.unsigned_tx.compute_txid()
869941
);
870-
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?;
942+
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?;
871943

872944
let mut signers = BTreeMap::new();
873945
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
@@ -992,27 +1064,27 @@ mod tests {
9921064
expected_selects: Mutex<Vec<(u64, u64, u32, CoinSelection)>>,
9931065
}
9941066
impl CoinSelectionSource for TestCoinSelectionSource {
995-
fn select_confirmed_utxos(
996-
&self, _claim_id: ClaimId, must_spend: Vec<Input>, _must_pay_to: &[TxOut],
1067+
fn select_confirmed_utxos<'a>(
1068+
&'a self, _claim_id: ClaimId, must_spend: Vec<Input>, _must_pay_to: &'a [TxOut],
9971069
target_feerate_sat_per_1000_weight: u32,
998-
) -> Result<CoinSelection, ()> {
1070+
) -> AsyncResult<'a, CoinSelection> {
9991071
let mut expected_selects = self.expected_selects.lock().unwrap();
10001072
let (weight, value, feerate, res) = expected_selects.remove(0);
10011073
assert_eq!(must_spend.len(), 1);
10021074
assert_eq!(must_spend[0].satisfaction_weight, weight);
10031075
assert_eq!(must_spend[0].previous_utxo.value.to_sat(), value);
10041076
assert_eq!(target_feerate_sat_per_1000_weight, feerate);
1005-
Ok(res)
1077+
Box::pin(async move { Ok(res) })
10061078
}
1007-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
1079+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
10081080
let mut tx = psbt.unsigned_tx;
10091081
for input in tx.input.iter_mut() {
10101082
if input.previous_output.txid != Txid::from_byte_array([44; 32]) {
10111083
// Channel output, add a realistic size witness to make the assertions happy
10121084
input.witness = Witness::from_slice(&[vec![42; 162]]);
10131085
}
10141086
}
1015-
Ok(tx)
1087+
Box::pin(async move { Ok(tx) })
10161088
}
10171089
}
10181090

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::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
2020
use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA};
@@ -470,7 +470,7 @@ pub struct Node<'chan_man, 'node_cfg: 'chan_man, 'chan_mon_cfg: 'node_cfg> {
470470
pub wallet_source: Arc<test_utils::TestWalletSource>,
471471
pub bump_tx_handler: BumpTransactionEventHandler<
472472
&'chan_mon_cfg test_utils::TestBroadcaster,
473-
Arc<Wallet<Arc<test_utils::TestWalletSource>, &'chan_mon_cfg test_utils::TestLogger>>,
473+
Arc<Wallet<Arc<WalletSourceSyncWrapper<Arc<test_utils::TestWalletSource>>>, &'chan_mon_cfg test_utils::TestLogger>>,
474474
&'chan_mon_cfg test_utils::TestKeysInterface,
475475
&'chan_mon_cfg test_utils::TestLogger,
476476
>,
@@ -3416,6 +3416,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
34163416
);
34173417
let gossip_sync = P2PGossipSync::new(cfgs[i].network_graph.as_ref(), None, cfgs[i].logger);
34183418
let wallet_source = Arc::new(test_utils::TestWalletSource::new(SecretKey::from_slice(&[i as u8 + 1; 32]).unwrap()));
3419+
let wallet_source_async = Arc::new(WalletSourceSyncWrapper::new(wallet_source.clone()));
34193420
nodes.push(Node{
34203421
chain_source: cfgs[i].chain_source, tx_broadcaster: cfgs[i].tx_broadcaster,
34213422
fee_estimator: cfgs[i].fee_estimator, router: &cfgs[i].router,
@@ -3429,7 +3430,7 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
34293430
override_init_features: Rc::clone(&cfgs[i].override_init_features),
34303431
wallet_source: Arc::clone(&wallet_source),
34313432
bump_tx_handler: BumpTransactionEventHandler::new(
3432-
cfgs[i].tx_broadcaster, Arc::new(Wallet::new(Arc::clone(&wallet_source), cfgs[i].logger)),
3433+
cfgs[i].tx_broadcaster, Arc::new(Wallet::new(wallet_source_async, cfgs[i].logger)),
34333434
&cfgs[i].keys_manager, cfgs[i].logger,
34343435
),
34353436
})

lightning/src/ln/functional_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM
1919
use crate::chain::transaction::OutPoint;
2020
use crate::ln::onion_utils::LocalHTLCFailureReason;
2121
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider};
22-
use crate::events::bump_transaction::WalletSource;
22+
use crate::events::bump_transaction::WalletSourceSync;
2323
use crate::events::{Event, FundingInfo, PathFailure, PaymentPurpose, ClosureReason, HTLCHandlingFailureType, PaymentFailureReason};
2424
use crate::ln::types::ChannelId;
2525
use crate::types::payment::{PaymentPreimage, PaymentSecret, PaymentHash};

0 commit comments

Comments
 (0)