Skip to content

Commit d0847bd

Browse files
committed
Generate ClaimEvent for HolderHTLCOutput inputs from anchor channels
1 parent 6874126 commit d0847bd

File tree

3 files changed

+137
-15
lines changed

3 files changed

+137
-15
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,6 +2425,7 @@ impl<Signer: Sign> ChannelMonitorImpl<Signer> {
24252425
pending_htlcs,
24262426
}));
24272427
},
2428+
_ => {},
24282429
}
24292430
}
24302431
ret

lightning/src/chain/onchaintx.rs

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::chain::keysinterface::BaseSign;
2525
use crate::ln::msgs::DecodeError;
2626
use crate::ln::PaymentPreimage;
2727
#[cfg(anchors)]
28-
use crate::ln::chan_utils;
28+
use crate::ln::chan_utils::{self, HTLCOutputInCommitment};
2929
use crate::ln::chan_utils::{ChannelTransactionParameters, HolderCommitmentTransaction};
3030
#[cfg(anchors)]
3131
use crate::chain::chaininterface::ConfirmationTarget;
@@ -174,6 +174,16 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
174174
}
175175
}
176176

177+
#[cfg(anchors)]
178+
/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
179+
pub(crate) struct ExternalHTLCClaim {
180+
pub(crate) commitment_txid: Txid,
181+
pub(crate) per_commitment_number: u64,
182+
pub(crate) htlc: HTLCOutputInCommitment,
183+
pub(crate) preimage: Option<PaymentPreimage>,
184+
pub(crate) counterparty_sig: Signature,
185+
}
186+
177187
// Represents the different types of claims for which events are yielded externally to satisfy said
178188
// claims.
179189
#[cfg(anchors)]
@@ -185,6 +195,12 @@ pub(crate) enum ClaimEvent {
185195
commitment_tx: Transaction,
186196
anchor_output_idx: u32,
187197
},
198+
/// Event yielded to signal that the commitment transaction has confirmed and its HTLCs must be
199+
/// resolved by broadcasting a transaction with sufficient fee to claim them.
200+
BumpHTLC {
201+
target_feerate_sat_per_1000_weight: u32,
202+
htlcs: Vec<ExternalHTLCClaim>,
203+
},
188204
}
189205

190206
/// Represents the different ways an output can be claimed (i.e., spent to an address under our
@@ -488,15 +504,36 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
488504
// didn't receive confirmation of it before, or not enough reorg-safe depth on top of it).
489505
let new_timer = Some(cached_request.get_height_timer(cur_height));
490506
if cached_request.is_malleable() {
507+
#[cfg(anchors)]
508+
{ // Attributes are not allowed on if expressions on our current MSRV of 1.41.
509+
if cached_request.requires_external_funding() {
510+
let target_feerate_sat_per_1000_weight = cached_request
511+
.compute_package_feerate(fee_estimator, ConfirmationTarget::HighPriority);
512+
if let Some(htlcs) = cached_request.construct_malleable_package_with_external_funding(self) {
513+
return Some((
514+
new_timer,
515+
target_feerate_sat_per_1000_weight as u64,
516+
OnchainClaim::Event(ClaimEvent::BumpHTLC {
517+
target_feerate_sat_per_1000_weight,
518+
htlcs,
519+
}),
520+
));
521+
} else {
522+
return None;
523+
}
524+
}
525+
}
526+
491527
let predicted_weight = cached_request.package_weight(&self.destination_script);
492-
if let Some((output_value, new_feerate)) =
493-
cached_request.compute_package_output(predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger) {
528+
if let Some((output_value, new_feerate)) = cached_request.compute_package_output(
529+
predicted_weight, self.destination_script.dust_value().to_sat(), fee_estimator, logger,
530+
) {
494531
assert!(new_feerate != 0);
495532

496533
let transaction = cached_request.finalize_malleable_package(self, output_value, self.destination_script.clone(), logger).unwrap();
497534
log_trace!(logger, "...with timer {} and feerate {}", new_timer.unwrap(), new_feerate);
498535
assert!(predicted_weight >= transaction.weight());
499-
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)))
536+
return Some((new_timer, new_feerate, OnchainClaim::Tx(transaction)));
500537
}
501538
} else {
502539
// Untractable packages cannot have their fees bumped through Replace-By-Fee. Some
@@ -552,7 +589,7 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
552589
debug_assert!(false, "Only HolderFundingOutput inputs should be untractable and require external funding");
553590
None
554591
},
555-
});
592+
})
556593
}
557594
None
558595
}
@@ -640,10 +677,20 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
640677
#[cfg(anchors)]
641678
OnchainClaim::Event(claim_event) => {
642679
log_info!(logger, "Yielding onchain event to spend inputs {:?}", req.outpoints());
643-
let txid = match claim_event {
644-
ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid(),
680+
let package_id = match claim_event {
681+
ClaimEvent::BumpCommitment { ref commitment_tx, .. } => commitment_tx.txid().into_inner(),
682+
ClaimEvent::BumpHTLC { ref htlcs, .. } => {
683+
// Use the same construction as a lightning channel id to generate
684+
// the package id for this request based on the first HTLC. It
685+
// doesn't matter what we use as long as it's unique per request.
686+
let mut package_id = [0; 32];
687+
package_id[..].copy_from_slice(&htlcs[0].commitment_txid[..]);
688+
let htlc_output_index = htlcs[0].htlc.transaction_output_index.unwrap();
689+
package_id[30] ^= ((htlc_output_index >> 8) & 0xff) as u8;
690+
package_id[31] ^= ((htlc_output_index >> 0) & 0xff) as u8;
691+
package_id
692+
},
645693
};
646-
let package_id = txid.into_inner();
647694
self.pending_claim_events.insert(package_id, claim_event);
648695
package_id
649696
},
@@ -686,14 +733,31 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
686733
// outpoints to know if transaction is the original claim or a bumped one issued
687734
// by us.
688735
let mut set_equality = true;
689-
if request.outpoints().len() != tx.input.len() {
690-
set_equality = false;
736+
if !request.requires_external_funding() || !request.is_malleable() {
737+
// If the claim does not require external funds to be allocated through
738+
// additional inputs we can simply check the inputs in order as they
739+
// cannot change under us.
740+
if request.outpoints().len() != tx.input.len() {
741+
set_equality = false;
742+
} else {
743+
for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
744+
if **claim_inp != tx_inp.previous_output {
745+
set_equality = false;
746+
}
747+
}
748+
}
691749
} else {
692-
for (claim_inp, tx_inp) in request.outpoints().iter().zip(tx.input.iter()) {
693-
if **claim_inp != tx_inp.previous_output {
694-
set_equality = false;
750+
// Otherwise, we'll do a linear search for each input (we don't expect
751+
// large input sets to exist) to ensure the request's input set is fully
752+
// spent to be resilient against the external claim reordering inputs.
753+
let mut spends_all_inputs = true;
754+
for request_input in request.outpoints() {
755+
if tx.input.iter().find(|input| input.previous_output == *request_input).is_none() {
756+
spends_all_inputs = false;
757+
break;
695758
}
696759
}
760+
set_equality = spends_all_inputs;
697761
}
698762

699763
macro_rules! clean_claim_request_after_safety_delay {
@@ -999,6 +1063,37 @@ impl<ChannelSigner: Sign> OnchainTxHandler<ChannelSigner> {
9991063
htlc_tx
10001064
}
10011065

1066+
#[cfg(anchors)]
1067+
pub(crate) fn generate_external_htlc_claim(
1068+
&mut self, outp: &::bitcoin::OutPoint, preimage: &Option<PaymentPreimage>
1069+
) -> Option<ExternalHTLCClaim> {
1070+
let find_htlc = |holder_commitment: &HolderCommitmentTransaction| -> Option<ExternalHTLCClaim> {
1071+
let trusted_tx = holder_commitment.trust();
1072+
if outp.txid != trusted_tx.txid() {
1073+
return None;
1074+
}
1075+
trusted_tx.htlcs().iter().enumerate()
1076+
.find(|(_, htlc)| if let Some(output_index) = htlc.transaction_output_index {
1077+
output_index == outp.vout
1078+
} else {
1079+
false
1080+
})
1081+
.map(|(htlc_idx, htlc)| {
1082+
let counterparty_htlc_sig = holder_commitment.counterparty_htlc_sigs[htlc_idx];
1083+
ExternalHTLCClaim {
1084+
commitment_txid: trusted_tx.txid(),
1085+
per_commitment_number: trusted_tx.commitment_number(),
1086+
htlc: htlc.clone(),
1087+
preimage: *preimage,
1088+
counterparty_sig: counterparty_htlc_sig,
1089+
}
1090+
})
1091+
};
1092+
// Check if the HTLC spends from the current holder commitment or the previous one otherwise.
1093+
find_htlc(&self.holder_commitment)
1094+
.or_else(|| self.prev_holder_commitment.as_ref().map(|c| find_htlc(c)).flatten())
1095+
}
1096+
10021097
pub(crate) fn opt_anchors(&self) -> bool {
10031098
self.channel_transaction_parameters.opt_anchors.is_some()
10041099
}

lightning/src/chain/package.rs

Lines changed: 28 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::logger::Logger;
3133
use crate::util::ser::{Readable, Writer, Writeable};
@@ -448,8 +450,13 @@ impl PackageSolvingData {
448450
}
449451
fn get_finalized_tx<Signer: Sign>(&self, outpoint: &BitcoinOutPoint, onchain_handler: &mut OnchainTxHandler<Signer>) -> Option<Transaction> {
450452
match self {
451-
PackageSolvingData::HolderHTLCOutput(ref outp) => { return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage); }
452-
PackageSolvingData::HolderFundingOutput(ref outp) => { return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript)); }
453+
PackageSolvingData::HolderHTLCOutput(ref outp) => {
454+
debug_assert!(!outp.opt_anchors());
455+
return onchain_handler.get_fully_signed_htlc_tx(outpoint, &outp.preimage);
456+
}
457+
PackageSolvingData::HolderFundingOutput(ref outp) => {
458+
return Some(onchain_handler.get_fully_signed_holder_tx(&outp.funding_redeemscript));
459+
}
453460
_ => { panic!("API Error!"); }
454461
}
455462
}
@@ -649,6 +656,25 @@ impl PackageTemplate {
649656
let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR;
650657
inputs_weight + witnesses_weight + transaction_weight + output_weight
651658
}
659+
#[cfg(anchors)]
660+
pub(crate) fn construct_malleable_package_with_external_funding<Signer: Sign>(
661+
&self, onchain_handler: &mut OnchainTxHandler<Signer>,
662+
) -> Option<Vec<ExternalHTLCClaim>> {
663+
debug_assert!(self.requires_external_funding());
664+
let mut htlcs: Option<Vec<ExternalHTLCClaim>> = None;
665+
for (previous_output, input) in &self.inputs {
666+
match input {
667+
PackageSolvingData::HolderHTLCOutput(ref outp) => {
668+
debug_assert!(outp.opt_anchors());
669+
onchain_handler.generate_external_htlc_claim(&previous_output, &outp.preimage).map(|htlc| {
670+
htlcs.get_or_insert_with(|| Vec::with_capacity(self.inputs.len())).push(htlc);
671+
});
672+
}
673+
_ => debug_assert!(false, "Expected HolderHTLCOutputs to not be aggregated with other input types"),
674+
}
675+
}
676+
htlcs
677+
}
652678
pub(crate) fn finalize_malleable_package<L: Deref, Signer: Sign>(
653679
&self, onchain_handler: &mut OnchainTxHandler<Signer>, value: u64, destination_script: Script, logger: &L
654680
) -> Option<Transaction> where L::Target: Logger {

0 commit comments

Comments
 (0)