Skip to content

Commit 8307ce0

Browse files
committed
Refactor package_locktime in terms of signed and minimum locktimes
There are multiple factors affecting the locktime of a package: - HTLC transactions rely on a fixed timelock due to the counterparty's signature. - HTLC timeout claims on the counterparty's commitment transaction require satisfying a CLTV timelock. - The locktime can be set to the latest height to avoid fee sniping. These factors were combined in a single method, making the separate factors less clear.
1 parent eeb0261 commit 8307ce0

File tree

1 file changed

+31
-43
lines changed

1 file changed

+31
-43
lines changed

lightning/src/chain/package.rs

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -656,25 +656,26 @@ impl PackageSolvingData {
656656
_ => { panic!("API Error!"); }
657657
}
658658
}
659-
fn absolute_tx_timelock(&self, current_height: u32) -> u32 {
660-
// We use `current_height` as our default locktime to discourage fee sniping and because
661-
// transactions with it always propagate.
662-
let absolute_timelock = match self {
663-
PackageSolvingData::RevokedOutput(_) => current_height,
664-
PackageSolvingData::RevokedHTLCOutput(_) => current_height,
665-
PackageSolvingData::CounterpartyOfferedHTLCOutput(_) => current_height,
666-
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => cmp::max(outp.htlc.cltv_expiry, current_height),
667-
// HTLC timeout/success transactions rely on a fixed timelock due to the counterparty's
668-
// signature.
659+
/// Some output types are locked with CHECKLOCKTIMEVERIFY and the spending transaction must
660+
/// have a minimum locktime, which is returned here.
661+
fn minimum_locktime(&self) -> Option<u32> {
662+
match self {
663+
PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => Some(outp.htlc.cltv_expiry),
664+
_ => None,
665+
}
666+
}
667+
/// Some output types are pre-signed in such a way that the spending transaction must have an
668+
/// exact locktime. This returns that locktime for such outputs.
669+
fn signed_locktime(&self) -> Option<u32> {
670+
match self {
669671
PackageSolvingData::HolderHTLCOutput(ref outp) => {
670672
if outp.preimage.is_some() {
671673
debug_assert_eq!(outp.cltv_expiry, 0);
672674
}
673-
outp.cltv_expiry
675+
Some(outp.cltv_expiry)
674676
},
675-
PackageSolvingData::HolderFundingOutput(_) => current_height,
676-
};
677-
absolute_timelock
677+
_ => None,
678+
}
678679
}
679680

680681
fn map_output_type_flags(&self) -> (PackageMalleability, bool) {
@@ -860,36 +861,23 @@ impl PackageTemplate {
860861
}
861862
amounts
862863
}
863-
pub(crate) fn package_locktime(&self, current_height: u32) -> u32 {
864-
let locktime = self.inputs.iter().map(|(_, outp)| outp.absolute_tx_timelock(current_height))
865-
.max().expect("There must always be at least one output to spend in a PackageTemplate");
866-
867-
// If we ever try to aggregate a `HolderHTLCOutput`s with another output type, we'll likely
868-
// end up with an incorrect transaction locktime since the counterparty has included it in
869-
// its HTLC signature. This should never happen unless we decide to aggregate outputs across
870-
// different channel commitments.
871-
#[cfg(debug_assertions)] {
872-
if self.inputs.iter().any(|(_, outp)|
873-
if let PackageSolvingData::HolderHTLCOutput(outp) = outp {
874-
outp.preimage.is_some()
875-
} else {
876-
false
877-
}
878-
) {
879-
debug_assert_eq!(locktime, 0);
880-
};
881-
for timeout_htlc_expiry in self.inputs.iter().filter_map(|(_, outp)|
882-
if let PackageSolvingData::HolderHTLCOutput(outp) = outp {
883-
if outp.preimage.is_none() {
884-
Some(outp.cltv_expiry)
885-
} else { None }
886-
} else { None }
887-
) {
888-
debug_assert_eq!(locktime, timeout_htlc_expiry);
889-
}
864+
fn signed_locktime(&self) -> Option<u32> {
865+
let signed_locktime = self.inputs.iter().find_map(|(_, outp)| outp.signed_locktime());
866+
#[cfg(debug_assertions)]
867+
for (_, outp) in &self.inputs {
868+
debug_assert!(outp.signed_locktime().is_none() || outp.signed_locktime() == signed_locktime);
890869
}
870+
signed_locktime
871+
}
872+
pub(crate) fn package_locktime(&self, current_height: u32) -> u32 {
873+
let minimum_locktime = self.inputs.iter().filter_map(|(_, outp)| outp.minimum_locktime()).max().unwrap_or(0);
891874

892-
locktime
875+
if let Some(signed_locktime) = self.signed_locktime() {
876+
debug_assert!(signed_locktime >= minimum_locktime);
877+
signed_locktime
878+
} else {
879+
core::cmp::max(current_height, minimum_locktime)
880+
}
893881
}
894882
pub(crate) fn package_weight(&self, destination_script: &Script) -> u64 {
895883
let mut inputs_weight = 0;
@@ -1214,7 +1202,7 @@ where
12141202
F::Target: FeeEstimator,
12151203
{
12161204
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
1217-
let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) =
1205+
let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) =
12181206
compute_fee_from_spent_amounts(input_amounts, predicted_weight, conf_target, fee_estimator, logger)
12191207
{
12201208
match feerate_strategy {

0 commit comments

Comments
 (0)