Skip to content

Commit 2ac0971

Browse files
committed
Re-work PackageSolvingData::absolute_tx_timelock
Previously, this would return the earliest height the output could be confirmed, which seems to no longer be useful. The only use of the method was to determine whether we should delay a package to a future block. Instead, we choose to return the absolute locktime the transaction spending the output should have, which better corresponds to the method name and still supports the delay functionality mentioned. Doing so also allows us to expose the locktime required for HTLC transactions we need to broadcast based on our own commitments for anchor channels.
1 parent 31e78ff commit 2ac0971

File tree

2 files changed

+51
-18
lines changed

2 files changed

+51
-18
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,16 +654,17 @@ impl<ChannelSigner: WriteableEcdsaChannelSigner> OnchainTxHandler<ChannelSigner>
654654
.find(|locked_package| locked_package.outpoints() == req.outpoints());
655655
if let Some(package) = timelocked_equivalent_package {
656656
log_info!(logger, "Ignoring second claim for outpoint {}:{}, we already have one which we're waiting on a timelock at {} for.",
657-
req.outpoints()[0].txid, req.outpoints()[0].vout, package.package_timelock());
657+
req.outpoints()[0].txid, req.outpoints()[0].vout, package.package_locktime(cur_height));
658658
continue;
659659
}
660660

661-
if req.package_timelock() > cur_height + 1 {
662-
log_info!(logger, "Delaying claim of package until its timelock at {} (current height {}), the following outpoints are spent:", req.package_timelock(), cur_height);
661+
let package_locktime = req.package_locktime(cur_height);
662+
if package_locktime > cur_height + 1 {
663+
log_info!(logger, "Delaying claim of package until its timelock at {} (current height {}), the following outpoints are spent:", package_locktime, cur_height);
663664
for outpoint in req.outpoints() {
664665
log_info!(logger, " Outpoint {}", outpoint);
665666
}
666-
self.locktimed_packages.entry(req.package_timelock()).or_insert(Vec::new()).push(req);
667+
self.locktimed_packages.entry(package_locktime).or_insert(Vec::new()).push(req);
667668
continue;
668669
}
669670

lightning/src/chain/package.rs

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -460,18 +460,23 @@ impl PackageSolvingData {
460460
_ => { panic!("API Error!"); }
461461
}
462462
}
463-
fn absolute_tx_timelock(&self, output_conf_height: u32) -> u32 {
464-
// Get the absolute timelock at which this output can be spent given the height at which
465-
// this output was confirmed. We use `output_conf_height + 1` as a safe default as we can
466-
// be confirmed in the next block and transactions with time lock `current_height + 1`
467-
// always propagate.
463+
fn absolute_tx_timelock(&self, current_height: u32) -> u32 {
464+
// We use `current_height + 1` as our default locktime to discourage fee sniping and because
465+
// transactions with it always propagate.
468466
let absolute_timelock = match self {
469-
PackageSolvingData::RevokedOutput(_) => output_conf_height + 1,
470-
PackageSolvingData::RevokedHTLCOutput(_) => output_conf_height + 1,
471-
PackageSolvingData::CounterpartyOfferedHTLCOutput(_) => output_conf_height + 1,
472-
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => cmp::max(outp.htlc.cltv_expiry, output_conf_height + 1),
473-
PackageSolvingData::HolderHTLCOutput(ref outp) => cmp::max(outp.cltv_expiry, output_conf_height + 1),
474-
PackageSolvingData::HolderFundingOutput(_) => output_conf_height + 1,
467+
PackageSolvingData::RevokedOutput(_) => current_height + 1,
468+
PackageSolvingData::RevokedHTLCOutput(_) => current_height + 1,
469+
PackageSolvingData::CounterpartyOfferedHTLCOutput(_) => current_height + 1,
470+
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => cmp::max(outp.htlc.cltv_expiry, current_height + 1),
471+
// HTLC timeout/success transactions rely on a fixed timelock due to the counterparty's
472+
// signature.
473+
PackageSolvingData::HolderHTLCOutput(ref outp) => {
474+
if outp.preimage.is_some() {
475+
debug_assert_eq!(outp.cltv_expiry, 0);
476+
}
477+
outp.cltv_expiry
478+
},
479+
PackageSolvingData::HolderFundingOutput(_) => current_height + 1,
475480
};
476481
absolute_timelock
477482
}
@@ -638,9 +643,36 @@ impl PackageTemplate {
638643
}
639644
amounts
640645
}
641-
pub(crate) fn package_timelock(&self) -> u32 {
642-
self.inputs.iter().map(|(_, outp)| outp.absolute_tx_timelock(self.height_original))
643-
.max().expect("There must always be at least one output to spend in a PackageTemplate")
646+
pub(crate) fn package_locktime(&self, current_height: u32) -> u32 {
647+
let locktime = self.inputs.iter().map(|(_, outp)| outp.absolute_tx_timelock(current_height))
648+
.max().expect("There must always be at least one output to spend in a PackageTemplate");
649+
650+
// If we ever try to aggregate a `HolderHTLCOutput`s with another output type, we'll likely
651+
// end up with an incorrect transaction locktime since the counterparty has included it in
652+
// its HTLC signature. This should never happen unless we decide to aggregate outputs across
653+
// different channel commitments.
654+
#[cfg(debug_assertions)] {
655+
if self.inputs.iter().any(|(_, outp)|
656+
if let PackageSolvingData::HolderHTLCOutput(outp) = outp {
657+
outp.preimage.is_some()
658+
} else {
659+
false
660+
}
661+
) {
662+
debug_assert_eq!(locktime, 0);
663+
};
664+
for timeout_htlc_expiry in self.inputs.iter().filter_map(|(_, outp)|
665+
if let PackageSolvingData::HolderHTLCOutput(outp) = outp {
666+
if outp.preimage.is_none() {
667+
Some(outp.cltv_expiry)
668+
} else { None }
669+
} else { None }
670+
) {
671+
debug_assert_eq!(locktime, timeout_htlc_expiry);
672+
}
673+
}
674+
675+
locktime
644676
}
645677
pub(crate) fn package_weight(&self, destination_script: &Script) -> usize {
646678
let mut inputs_weight = 0;

0 commit comments

Comments
 (0)