Skip to content

Commit 2e74643

Browse files
committed
Generate ClaimEvent for HolderHTLCOutput inputs from anchor channels
1 parent 1eca282 commit 2e74643

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
@@ -2371,6 +2371,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
23712371
pending_htlcs,
23722372
}));
23732373
},
2374+
_ => {},
23742375
}
23752376
}
23762377
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;
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;
@@ -170,6 +172,17 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
170172
}
171173
}
172174

175+
#[cfg(anchors)]
176+
/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
177+
pub(crate) struct ExternalHTLCClaim {
178+
pub(crate) per_commitment_number: u64,
179+
pub(crate) htlc: HTLCOutputInCommitment,
180+
pub(crate) preimage: Option<PaymentPreimage>,
181+
pub(crate) counterparty_base_htlc_key: PublicKey,
182+
pub(crate) counterparty_base_revocation_key: PublicKey,
183+
pub(crate) counterparty_sig: Signature,
184+
}
185+
173186
// Represents the different types of claims for which events are yielded externally to satisfy said
174187
// claims.
175188
#[cfg(anchors)]
@@ -181,6 +194,11 @@ pub(crate) enum ClaimEvent {
181194
commitment_tx: Transaction,
182195
anchor_output_idx: u32,
183196
},
197+
BumpHTLC {
198+
target_feerate_sat_per_1000_weight: u32,
199+
tx_template: Transaction,
200+
htlcs: Vec<ExternalHTLCClaim>,
201+
},
184202
}
185203

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

480517
let transaction = cached_request.finalize_malleable_package(self, output_value, self.destination_script.clone(), logger).unwrap();
481518
log_trace!(logger, "...with timer {} and feerate {}", new_timer.unwrap(), new_feerate);
482519
assert!(predicted_weight >= transaction.weight());
483-
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)))
520+
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)));
484521
}
485522
} else {
486523
// Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
@@ -536,7 +573,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
536573
debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding");
537574
None
538575
},
539-
});
576+
})
540577
}
541578
None
542579
}
@@ -621,6 +658,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
621658
log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints());
622659
let txid = match claim_event {
623660
ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(),
661+
ClaimEvent::BumpHTLC { ref tx_template, .. } => tx_template.txid(),
624662
};
625663
self.pending_claim_events.insert(txid, claim_event);
626664
txid
@@ -646,14 +684,33 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
646684
// outpoints to know if transaction is the original claim or a bumped one issued
647685
// by us.
648686
let mut set_equality = true;
649-
if request.outpoints().len() != tx.input.len() {
650-
set_equality = false;
687+
if !request.requires_external_funding() ||
688+
(request.requires_external_funding() && !request.is_malleable())
689+
{
690+
// If the claim does not require external funds to be allocated through
691+
// additional inputs we can simply check the inputs in order as they
692+
// cannot change under us.
693+
if request.outpoints().len() != tx.input.len() {
694+
set_equality = false;
695+
} else {
696+
for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
697+
if **claim_inp != tx_inp.previous_output {
698+
set_equality = false;
699+
}
700+
}
701+
}
651702
} else {
652-
for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
653-
if **claim_inp != tx_inp.previous_output {
654-
set_equality = false;
703+
// Otherwise, we'll do a linear search for each input (we don't expect
704+
// large input sets to exist) to ensure the request's input set is fully
705+
// spent to be resilient against the external claim reordering inputs.
706+
let mut spends_all_inputs = true;
707+
for request_input in request.outpoints() {
708+
if tx.input.iter().find(|input| input.previous_output == *request_input).is_none() {
709+
spends_all_inputs = false;
710+
break;
655711
}
656712
}
713+
set_equality = spends_all_inputs;
657714
}
658715

659716
macro_rules! clean_claim_request_after_safety_delay {
@@ -956,6 +1013,40 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
9561013
htlc_tx
9571014
}
9581015

1016+
#[cfg(anchors)]
1017+
pub(crate) fn unsigned_htlc_tx(
1018+
&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>
1019+
) -> Option<(Transaction, ExternalHTLCClaim)> {
1020+
let find_htlc = |holder_commitment: &HolderCommitmentTransaction| -> Option<(Transaction, ExternalHTLCClaim)> {
1021+
let trusted_tx = holder_commitment.trust();
1022+
if outp.txid != trusted_tx.txid() {
1023+
return None;
1024+
}
1025+
trusted_tx.htlcs().iter().enumerate()
1026+
.find(|(_, htlc)| if let Some(output_index) = htlc.transaction_output_index {
1027+
output_index == outp.vout
1028+
} else {
1029+
false
1030+
})
1031+
.map(|(htlc_idx, _)| {
1032+
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
1033+
let channel_params = self.channel_transaction_parameters.as_holder_broadcastable();
1034+
let (htlc_tx, htlc) = trusted_tx.unsigned_htlc_tx(&channel_params, htlc_idx, preimage);
1035+
(htlc_tx, ExternalHTLCClaim {
1036+
per_commitment_number: trusted_tx.commitment_number(),
1037+
htlc,
1038+
preimage: *preimage,
1039+
counterparty_base_htlc_key: channel_params.countersignatory_pubkeys().htlc_basepoint,
1040+
counterparty_base_revocation_key: channel_params.countersignatory_pubkeys().revocation_basepoint,
1041+
counterparty_sig: counterparty_htlc_sig,
1042+
})
1043+
})
1044+
};
1045+
// Check if the HTLC spends from the current holder commitment or the previous one otherwise.
1046+
find_htlc(&self.holder_commitment)
1047+
.or(self.prev_holder_commitment.as_ref().map(|c| find_htlc(c)).flatten())
1048+
}
1049+
9591050
pub(crate) fn opt_anchors(&self) -> bool {
9601051
self.channel_transaction_parameters.opt_anchors.is_some()
9611052
}

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)