Skip to content

Commit d35c1f4

Browse files
committed
Convert BumpTransactionEventHandler to async
1 parent 0c76ed8 commit d35c1f4

File tree

7 files changed

+198
-95
lines changed

7 files changed

+198
-95
lines changed

lightning/src/events/bump_transaction.rs

Lines changed: 131 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::sign::{
3030
};
3131
use crate::sign::ecdsa::EcdsaChannelSigner;
3232
use crate::sync::Mutex;
33+
use crate::util::async_poll::{AsyncResult, MaybeSync, MaybeSend};
3334
use crate::util::logger::Logger;
3435

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

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

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

392-
impl<W: Deref, L: Deref> Wallet<W, L>
451+
impl<W: Deref + MaybeSync + MaybeSend, L: Deref + MaybeSync + MaybeSend> Wallet<W, L>
393452
where
394-
W::Target: WalletSource,
395-
L::Target: Logger
453+
W::Target: WalletSource + MaybeSend,
454+
L::Target: Logger + MaybeSend
396455
{
397456
/// Returns a new instance backed by the given [`WalletSource`] that serves as an implementation
398457
/// of [`CoinSelectionSource`].
@@ -408,7 +467,7 @@ where
408467
/// `tolerate_high_network_feerates` is set, we'll attempt to spend UTXOs that contribute at
409468
/// least 1 satoshi at the current feerate, otherwise, we'll only attempt to spend those which
410469
/// contribute at least twice their fee.
411-
fn select_confirmed_utxos_internal(
470+
async fn select_confirmed_utxos_internal(
412471
&self, utxos: &[Utxo], claim_id: ClaimId, force_conflicting_utxo_spend: bool,
413472
tolerate_high_network_feerates: bool, target_feerate_sat_per_1000_weight: u32,
414473
preexisting_tx_weight: u64, input_amount_sat: Amount, target_amount_sat: Amount,
@@ -461,7 +520,7 @@ where
461520
}
462521

463522
let remaining_amount = selected_amount - target_amount_sat - total_fees;
464-
let change_script = self.source.get_change_script()?;
523+
let change_script = self.source.get_change_script().await?;
465524
let change_output_fee = fee_for_weight(
466525
target_feerate_sat_per_1000_weight,
467526
(8 /* value */ + change_script.consensus_encode(&mut sink()).unwrap() as u64) *
@@ -482,23 +541,29 @@ where
482541
}
483542
}
484543

485-
impl<W: Deref, L: Deref> CoinSelectionSource for Wallet<W, L>
544+
impl<W: Deref + MaybeSync + MaybeSend, L: Deref + MaybeSync + MaybeSend> CoinSelectionSource for Wallet<W, L>
486545
where
487-
W::Target: WalletSource,
488-
L::Target: Logger
546+
W::Target: WalletSource + MaybeSend + MaybeSync,
547+
L::Target: Logger + MaybeSend + MaybeSync,
489548
{
490-
fn select_confirmed_utxos(
491-
&self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &[TxOut],
549+
fn select_confirmed_utxos<'a>(
550+
&'a self, claim_id: ClaimId, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
492551
target_feerate_sat_per_1000_weight: u32,
493-
) -> Result<CoinSelection, ()> {
494-
let utxos = self.source.list_confirmed_utxos()?;
552+
) -> AsyncResult<'a, CoinSelection> {
553+
Box::pin(async move {
554+
let utxos = self.source.list_confirmed_utxos().await?;
495555
// TODO: Use fee estimation utils when we upgrade to bitcoin v0.30.0.
496556
const BASE_TX_SIZE: u64 = 4 /* version */ + 1 /* input count */ + 1 /* output count */ + 4 /* locktime */;
497-
let total_output_size: u64 = must_pay_to.iter().map(|output|
498-
8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64
499-
).sum();
500-
let total_satisfaction_weight: u64 = must_spend.iter().map(|input| input.satisfaction_weight).sum();
501-
let total_input_weight = (BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight;
557+
let total_output_size: u64 = must_pay_to
558+
.iter()
559+
.map(
560+
|output| 8 /* value */ + 1 /* script len */ + output.script_pubkey.len() as u64,
561+
)
562+
.sum();
563+
let total_satisfaction_weight: u64 =
564+
must_spend.iter().map(|input| input.satisfaction_weight).sum();
565+
let total_input_weight =
566+
(BASE_INPUT_WEIGHT * must_spend.len() as u64) + total_satisfaction_weight;
502567

503568
let preexisting_tx_weight = 2 /* segwit marker & flag */ + total_input_weight +
504569
((BASE_TX_SIZE + total_output_size) * WITNESS_SCALE_FACTOR as u64);
@@ -524,16 +589,17 @@ where
524589
preexisting_tx_weight,
525590
input_amount_sat,
526591
target_amount_sat,
527-
);
592+
).await;
528593
if attempt.is_ok() {
529594
return attempt;
530595
}
531596
last_err = Some(attempt);
532597
}
533598
last_err.unwrap()
599+
})
534600
}
535601

536-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
602+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
537603
self.source.sign_psbt(psbt)
538604
}
539605
}
@@ -645,8 +711,11 @@ where
645711
log_debug!(self.logger, "Performing coin selection for commitment package (commitment and anchor transaction) targeting {} sat/kW",
646712
package_target_feerate_sat_per_1000_weight);
647713
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
648-
claim_id, must_spend, &[], package_target_feerate_sat_per_1000_weight,
649-
)?;
714+
claim_id,
715+
must_spend,
716+
&[],
717+
package_target_feerate_sat_per_1000_weight,
718+
).await?;
650719

651720
let mut anchor_tx = Transaction {
652721
version: Version::TWO,
@@ -699,7 +768,7 @@ where
699768
}
700769

701770
log_debug!(self.logger, "Signing anchor transaction {}", anchor_txid);
702-
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt)?;
771+
anchor_tx = self.utxo_source.sign_psbt(anchor_psbt).await?;
703772

704773
let signer = self.signer_provider.derive_channel_signer(anchor_descriptor.channel_derivation_parameters.keys_id);
705774
let channel_parameters = &anchor_descriptor.channel_derivation_parameters.transaction_parameters;
@@ -767,8 +836,11 @@ where
767836
let must_spend_amount = must_spend.iter().map(|input| input.previous_utxo.value.to_sat()).sum::<u64>();
768837

769838
let coin_selection: CoinSelection = self.utxo_source.select_confirmed_utxos(
770-
claim_id, must_spend, &htlc_tx.output, target_feerate_sat_per_1000_weight,
771-
)?;
839+
claim_id,
840+
must_spend,
841+
&htlc_tx.output,
842+
target_feerate_sat_per_1000_weight,
843+
).await?;
772844

773845
#[cfg(debug_assertions)]
774846
let total_satisfaction_weight = must_spend_satisfaction_weight +
@@ -800,7 +872,7 @@ where
800872
let unsigned_tx_weight = htlc_psbt.unsigned_tx.weight().to_wu() - (htlc_psbt.unsigned_tx.input.len() as u64 * EMPTY_SCRIPT_SIG_WEIGHT);
801873

802874
log_debug!(self.logger, "Signing HTLC transaction {}", htlc_psbt.unsigned_tx.compute_txid());
803-
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt)?;
875+
htlc_tx = self.utxo_source.sign_psbt(htlc_psbt).await?;
804876

805877
let mut signers = BTreeMap::new();
806878
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
@@ -843,11 +915,17 @@ where
843915
log_info!(self.logger, "Handling channel close bump (claim_id = {}, commitment_txid = {})",
844916
log_bytes!(claim_id.0), commitment_tx.compute_txid());
845917
if let Err(_) = self.handle_channel_close(
846-
*claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,
847-
*commitment_tx_fee_satoshis, anchor_descriptor,
918+
*claim_id,
919+
*package_target_feerate_sat_per_1000_weight,
920+
commitment_tx,
921+
*commitment_tx_fee_satoshis,
922+
anchor_descriptor,
848923
).await {
849-
log_error!(self.logger, "Failed bumping commitment transaction fee for {}",
850-
commitment_tx.compute_txid());
924+
log_error!(
925+
self.logger,
926+
"Failed bumping commitment transaction fee for {}",
927+
commitment_tx.compute_txid()
928+
);
851929
}
852930
}
853931
BumpTransactionEvent::HTLCResolution {
@@ -856,10 +934,16 @@ where
856934
log_info!(self.logger, "Handling HTLC bump (claim_id = {}, htlcs_to_claim = {})",
857935
log_bytes!(claim_id.0), log_iter!(htlc_descriptors.iter().map(|d| d.outpoint())));
858936
if let Err(_) = self.handle_htlc_resolution(
859-
*claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time,
937+
*claim_id,
938+
*target_feerate_sat_per_1000_weight,
939+
htlc_descriptors,
940+
*tx_lock_time,
860941
).await {
861-
log_error!(self.logger, "Failed bumping HTLC transaction fee for commitment {}",
862-
htlc_descriptors[0].commitment_txid);
942+
log_error!(
943+
self.logger,
944+
"Failed bumping HTLC transaction fee for commitment {}",
945+
htlc_descriptors[0].commitment_txid
946+
);
863947
}
864948
}
865949
}
@@ -886,30 +970,29 @@ mod tests {
886970
expected_selects: Mutex<Vec<(u64, u64, u32, CoinSelection)>>,
887971
}
888972
impl CoinSelectionSource for TestCoinSelectionSource {
889-
fn select_confirmed_utxos(
890-
&self,
891-
_claim_id: ClaimId,
892-
must_spend: Vec<Input>,
893-
_must_pay_to: &[TxOut],
894-
target_feerate_sat_per_1000_weight: u32
895-
) -> Result<CoinSelection, ()> {
973+
fn select_confirmed_utxos<'a>(
974+
&'a self, _claim_id: ClaimId, must_spend: Vec<Input>, _must_pay_to: &'a [TxOut],
975+
target_feerate_sat_per_1000_weight: u32,
976+
) -> AsyncResult<'a, CoinSelection> {
896977
let mut expected_selects = self.expected_selects.lock().unwrap();
897978
let (weight, value, feerate, res) = expected_selects.remove(0);
898979
assert_eq!(must_spend.len(), 1);
899980
assert_eq!(must_spend[0].satisfaction_weight, weight);
900981
assert_eq!(must_spend[0].previous_utxo.value.to_sat(), value);
901982
assert_eq!(target_feerate_sat_per_1000_weight, feerate);
983+
Box::pin(async move {
902984
Ok(res)
985+
})
903986
}
904-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
987+
fn sign_psbt<'a>(&'a self, psbt: Psbt) -> AsyncResult<'a, Transaction> {
905988
let mut tx = psbt.unsigned_tx;
906989
for input in tx.input.iter_mut() {
907990
if input.previous_output.txid != Txid::from_byte_array([44; 32]) {
908991
// Channel output, add a realistic size witness to make the assertions happy
909992
input.witness = Witness::from_slice(&[vec![42; 162]]);
910993
}
911994
}
912-
Ok(tx)
995+
Box::pin(async move {Ok(tx)})
913996
}
914997
}
915998

lightning/src/ln/async_signer_tests.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 3 deletions
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

Lines changed: 2 additions & 1 deletion
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};
@@ -3138,6 +3138,7 @@ async fn test_multiple_package_conflicts_internal() {
31383138
let mut tx = nodes[2].tx_broadcaster.txn_broadcast();
31393139
assert_eq!(tx.len(), 1);
31403140
let conflict_tx = tx.pop().unwrap();
3141+
31413142
assert_eq!(conflict_tx.input.len(), 3);
31423143
assert_eq!(conflict_tx.input[0].previous_output.txid, node2_commit_tx.compute_txid());
31433144
assert_eq!(conflict_tx.input[1].previous_output.txid, node2_commit_tx.compute_txid());

0 commit comments

Comments
 (0)