Skip to content

Commit 6c726e1

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 8789c80 commit 6c726e1

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

895-
locktime
878+
if let Some(signed_locktime) = self.signed_locktime() {
879+
debug_assert!(signed_locktime >= minimum_locktime);
880+
signed_locktime
881+
} else {
882+
core::cmp::max(current_height, minimum_locktime)
883+
}
896884
}
897885
pub(crate) fn package_weight(&self, destination_script: &Script) -> u64 {
898886
let mut inputs_weight = 0;
@@ -1217,7 +1205,7 @@ where
12171205
F::Target: FeeEstimator,
12181206
{
12191207
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
1220-
let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) =
1208+
let (new_fee, new_feerate) = if let Some((new_fee, new_feerate)) =
12211209
compute_fee_from_spent_amounts(input_amounts, predicted_weight, conf_target, fee_estimator, logger)
12221210
{
12231211
match feerate_strategy {

0 commit comments

Comments
 (0)