Skip to content

Commit b56dde5

Browse files
committed
Generate ClaimEvent for HolderHTLCOutput inputs from anchor channels
1 parent 708c21d commit b56dde5

File tree

4 files changed

+159
-17
lines changed

4 files changed

+159
-17
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2378,6 +2378,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
23782378
pending_htlcs,
23792379
}));
23802380
},
2381+
_ => {},
23812382
}
23822383
}
23832384
ret

lightning/src/chain/onchaintx.rs

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ use bitcoin::blockdata::script::Script;
1818

1919
use bitcoin::hash_types::{Txid, BlockHash};
2020

21+
#[cfg(anchors)]
22+
use bitcoin::secp256k1::PublicKey;
2123
use bitcoin::secp256k1::{Secp256k1, ecdsa::Signature};
2224
use bitcoin::secp256k1;
2325

2426
use crate::ln::msgs::DecodeError;
2527
use crate::ln::PaymentPreimage;
2628
#[cfg(anchors)]
27-
use crate::ln::chan_utils;
29+
use crate::ln::chan_utils::{self, HTLCOutputInCommitment};
2830
use crate::ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction};
2931
#[cfg(anchors)]
3032
use crate::chain::chaininterface::ConfirmationTarget;
@@ -174,6 +176,17 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
174176
}
175177
}
176178

179+
#[cfg(anchors)]
180+
/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
181+
pub(crate) struct ExternalHTLCClaim {
182+
pub(crate) per_commitment_number: u64,
183+
pub(crate) htlc: HTLCOutputInCommitment,
184+
pub(crate) preimage: Option<PaymentPreimage>,
185+
pub(crate) counterparty_base_htlc_key: PublicKey,
186+
pub(crate) counterparty_base_revocation_key: PublicKey,
187+
pub(crate) counterparty_sig: Signature,
188+
}
189+
177190
// Represents the different types of claims for which events are yielded externally to satisfy said
178191
// claims.
179192
#[cfg(anchors)]
@@ -185,6 +198,11 @@ pub(crate) enum ClaimEvent {
185198
commitment_tx: Transaction,
186199
anchor_output_idx: u32,
187200
},
201+
BumpHTLC {
202+
target_feerate_sat_per_1000_weight: u32,
203+
tx_template: Transaction,
204+
htlcs: Vec<ExternalHTLCClaim>,
205+
},
188206
}
189207

190208
/// Represents the different ways an output can be claimed (i.e., spent to an address under our
@@ -476,15 +494,34 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
476494
// didn't receive confirmation of it before, or not enough reorg-safe depth on top of it).
477495
let new_timer = Some(cached_request.get_height_timer(cur_height));
478496
if cached_request.is_malleable() {
497+
#[cfg(anchors)]
498+
{ // Attributes are not allowed on if expressions on our current MSRV of 1.41.
499+
if cached_request.requires_external_funding() {
500+
let target_feerate_sat_per_1000_weight = cached_request
501+
.compute_package_feerate(fee_estimator, ConfirmationTarget::HighPriority);
502+
let (tx_template, htlcs) = cached_request.construct_malleable_package_with_external_funding(self);
503+
return Some((
504+
new_timer,
505+
target_feerate_sat_per_1000_weight as u64,
506+
OnchainClaim::Event(ClaimEvent::BumpHTLC {
507+
target_feerate_sat_per_1000_weight,
508+
tx_template,
509+
htlcs,
510+
}),
511+
));
512+
}
513+
}
514+
479515
let predicted_weight = cached_request.package_weight(&self.destination_script);
480-
if let Some((output_value, new_feerate)) =
481-
cached_request.compute_package_output(predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger) {
516+
if let Some((output_value, new_feerate)) = cached_request.compute_package_output(
517+
predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger,
518+
) {
482519
assert!(new_feerate != 0);
483520

484521
let transaction = cached_request.finalize_malleable_package(self, output_value, self.destination_script.clone(), logger).unwrap();
485522
log_trace!(logger, "...with timer {} and feerate {}", new_timer.unwrap(), new_feerate);
486523
assert!(predicted_weight >= transaction.weight());
487-
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)))
524+
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)));
488525
}
489526
} else {
490527
// Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
@@ -540,7 +577,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
540577
debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding");
541578
None
542579
},
543-
});
580+
})
544581
}
545582
None
546583
}
@@ -630,6 +667,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
630667
log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints());
631668
let txid = match claim_event {
632669
ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(),
670+
ClaimEvent::BumpHTLC { ref tx_template, .. } => tx_template.txid(),
633671
};
634672
self.pending_claim_events.insert(txid, claim_event);
635673
txid
@@ -673,14 +711,33 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
673711
// outpoints to know if transaction is the original claim or a bumped one issued
674712
// by us.
675713
let mut set_equality = true;
676-
if request.outpoints().len() != tx.input.len() {
677-
set_equality = false;
714+
if !request.requires_external_funding() ||
715+
(request.requires_external_funding() && !request.is_malleable())
716+
{
717+
// If the claim does not require external funds to be allocated through
718+
// additional inputs we can simply check the inputs in order as they
719+
// cannot change under us.
720+
if request.outpoints().len() != tx.input.len() {
721+
set_equality = false;
722+
} else {
723+
for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
724+
if **claim_inp != tx_inp.previous_output {
725+
set_equality = false;
726+
}
727+
}
728+
}
678729
} else {
679-
for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
680-
if **claim_inp != tx_inp.previous_output {
681-
set_equality = false;
730+
// Otherwise, we'll do a linear search for each input (we don't expect
731+
// large input sets to exist) to ensure the request's input set is fully
732+
// spent to be resilient against the external claim reordering inputs.
733+
let mut spends_all_inputs = true;
734+
for request_input in request.outpoints() {
735+
if tx.input.iter().find(|input| input.previous_output == *request_input).is_none() {
736+
spends_all_inputs = false;
737+
break;
682738
}
683739
}
740+
set_equality = spends_all_inputs;
684741
}
685742

686743
macro_rules! clean_claim_request_after_safety_delay {
@@ -985,6 +1042,40 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
9851042
htlc_tx
9861043
}
9871044

1045+
#[cfg(anchors)]
1046+
pub(crate) fn unsigned_htlc_tx(
1047+
&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>
1048+
) -> Option<(Transaction, ExternalHTLCClaim)> {
1049+
let find_htlc = |holder_commitment: &HolderCommitmentTransaction| -> Option<(Transaction, ExternalHTLCClaim)> {
1050+
let trusted_tx = holder_commitment.trust();
1051+
if outp.txid != trusted_tx.txid() {
1052+
return None;
1053+
}
1054+
trusted_tx.htlcs().iter().enumerate()
1055+
.find(|(_, htlc)| if let Some(output_index) = htlc.transaction_output_index {
1056+
output_index == outp.vout
1057+
} else {
1058+
false
1059+
})
1060+
.map(|(htlc_idx, _)| {
1061+
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
1062+
let channel_params = self.channel_transaction_parameters.as_holder_broadcastable();
1063+
let (htlc_tx, htlc) = trusted_tx.unsigned_htlc_tx(&channel_params, htlc_idx, preimage);
1064+
(htlc_tx, ExternalHTLCClaim {
1065+
per_commitment_number: trusted_tx.commitment_number(),
1066+
htlc,
1067+
preimage: *preimage,
1068+
counterparty_base_htlc_key: channel_params.countersignatory_pubkeys().htlc_basepoint,
1069+
counterparty_base_revocation_key: channel_params.countersignatory_pubkeys().revocation_basepoint,
1070+
counterparty_sig: counterparty_htlc_sig,
1071+
})
1072+
})
1073+
};
1074+
// Check if the HTLC spends from the current holder commitment or the previous one otherwise.
1075+
find_htlc(&self.holder_commitment)
1076+
.or(self.prev_holder_commitment.as_ref().map(|c| find_htlc(c)).flatten())
1077+
}
1078+
9881079
pub(crate) fn opt_anchors(&self) -> bool {
9891080
self.channel_transaction_parameters.opt_anchors.is_some()
9901081
}

lightning/src/chain/package.rs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use crate::ln::chan_utils;
2626
use crate::ln::msgs::DecodeError;
2727
use crate::chain::chaininterface::{FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
2828
use crate::chain::keysinterface::Sign;
29+
#[cfg(anchors)]
30+
use crate::chain::onchaintx::ExternalHTLCClaim;
2931
use crate::chain::onchaintx::OnchainTxHandler;
3032
use crate::util::byte_utils;
3133
use crate::util::logger::Logger;
@@ -453,8 +455,13 @@ impl PackageSolvingData {
453455
}
454456
fn get_finalized_tx<Signer: Sign>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<Transaction> {
455457
match self {
456-
PackageSolvingData::HolderHTLCOutput(ref outp) => { return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage); }
457-
PackageSolvingData::HolderFundingOutput(ref outp) => { return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript)); }
458+
PackageSolvingData::HolderHTLCOutput(ref outp) => {
459+
debug_assert!(!outp.opt_anchors());
460+
return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage);
461+
}
462+
PackageSolvingData::HolderFundingOutput(ref outp) => {
463+
return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript));
464+
}
458465
_ => { panic!("API Error!"); }
459466
}
460467
}
@@ -654,6 +661,31 @@ impl PackageTemplate {
654661
let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR;
655662
inputs_weight + witnesses_weight + transaction_weight + output_weight
656663
}
664+
#[cfg(anchors)]
665+
pub(crate) fn construct_malleable_package_with_external_funding<Signer: Sign>(
666+
&self, onchain_handler: &mut OnchainTxHandler<Signer>,
667+
) -> (Transaction, Vec<ExternalHTLCClaim>) {
668+
debug_assert!(self.requires_external_funding());
669+
let mut aggregate_tx = Transaction {
670+
version: 2,
671+
lock_time: PackedLockTime(self.package_timelock()),
672+
input: Vec::with_capacity(self.inputs.len()),
673+
output: Vec::with_capacity(self.inputs.len()),
674+
};
675+
let mut htlcs = Vec::with_capacity(self.inputs.len());
676+
self.inputs.iter().for_each(|input| match input.1 {
677+
PackageSolvingData::HolderHTLCOutput(ref outp) => {
678+
debug_assert!(outp.opt_anchors());
679+
onchain_handler.unsigned_htlc_tx(&input.0, &outp.preimage).map(|(mut htlc_tx, htlc)| {
680+
aggregate_tx.input.push(htlc_tx.input.pop().unwrap());
681+
aggregate_tx.output.push(htlc_tx.output.pop().unwrap());
682+
htlcs.push(htlc)
683+
});
684+
}
685+
_ => debug_assert!(false, "Expected HolderHTLCOutputs to not be aggregated with other input types"),
686+
});
687+
(aggregate_tx, htlcs)
688+
}
657689
pub(crate) fn finalize_malleable_package<L: Deref, Signer: Sign>(
658690
&self, onchain_handler: &mut OnchainTxHandler<Signer>, value: u64, destination_script: Script, logger: &L
659691
) -> Option<Transaction> where L::Target: Logger {

lightning/src/ln/chan_utils.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,19 +1533,37 @@ impl<'a> TrustedCommitmentTransaction<'a> {
15331533
Ok(ret)
15341534
}
15351535

1536-
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC transaction signature.
1537-
pub(crate) fn get_signed_htlc_tx(&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize, counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>) -> Transaction {
1536+
/// Builds the unsigned HTLC transaction for a HTLC with output index `htlc_index` within
1537+
/// the commitment transaction.
1538+
pub(crate) fn unsigned_htlc_tx(
1539+
&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize,
1540+
preimage: &Option<PaymentPreimage>
1541+
) -> (Transaction, HTLCOutputInCommitment) {
15381542
let inner = self.inner;
15391543
let keys = &inner.keys;
15401544
let txid = inner.built.txid;
1541-
let this_htlc = &inner.htlcs[htlc_index];
1545+
let this_htlc = inner.htlcs[htlc_index].clone();
15421546
assert!(this_htlc.transaction_output_index.is_some());
15431547
// if we don't have preimage for an HTLC-Success, we can't generate an HTLC transaction.
15441548
if !this_htlc.offered && preimage.is_none() { unreachable!(); }
15451549
// Further, we should never be provided the preimage for an HTLC-Timeout transaction.
15461550
if this_htlc.offered && preimage.is_some() { unreachable!(); }
1547-
1548-
let mut htlc_tx = build_htlc_transaction(&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc, self.opt_anchors(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key);
1551+
let htlc_tx = build_htlc_transaction(
1552+
&txid, inner.feerate_per_kw, channel_parameters.contest_delay(), &this_htlc,
1553+
self.opt_anchors(), &keys.broadcaster_delayed_payment_key, &keys.revocation_key,
1554+
);
1555+
(htlc_tx, this_htlc)
1556+
}
1557+
1558+
/// Gets a signed HTLC transaction given a preimage (for !htlc.offered) and the holder HTLC
1559+
/// transaction signature.
1560+
pub(crate) fn get_signed_htlc_tx(
1561+
&self, channel_parameters: &DirectedChannelTransactionParameters, htlc_index: usize,
1562+
counterparty_signature: &Signature, signature: &Signature, preimage: &Option<PaymentPreimage>
1563+
) -> Transaction {
1564+
let keys = &&self.inner.keys;
1565+
let this_htlc = &self.inner.htlcs[htlc_index];
1566+
let mut htlc_tx = self.unsigned_htlc_tx(channel_parameters, htlc_index, preimage).0;
15491567

15501568
let htlc_redeemscript = get_htlc_redeemscript_with_explicit_keys(&this_htlc, self.opt_anchors(), &keys.broadcaster_htlc_key, &keys.countersignatory_htlc_key, &keys.revocation_key);
15511569

0 commit comments

Comments
 (0)