Skip to content

0.1.5 backports #3932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 26 additions & 21 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3008,23 +3008,26 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
(payment_preimage.clone(), payment_info.clone().into_iter().collect())
});

let confirmed_spend_txid = self.funding_spend_confirmed.or_else(|| {
self.onchain_events_awaiting_threshold_conf.iter().find_map(|event| match event.event {
OnchainEvent::FundingSpendConfirmation { .. } => Some(event.txid),
_ => None,
})
});
let confirmed_spend_txid = if let Some(txid) = confirmed_spend_txid {
txid
} else {
return;
};
let confirmed_spend_info = self.funding_spend_confirmed
.map(|txid| (txid, None))
.or_else(|| {
self.onchain_events_awaiting_threshold_conf.iter().find_map(|event| match event.event {
OnchainEvent::FundingSpendConfirmation { .. } => Some((event.txid, Some(event.height))),
_ => None,
})
});
let (confirmed_spend_txid, confirmed_spend_height) =
if let Some((txid, height)) = confirmed_spend_info {
(txid, height)
} else {
return;
};

// If the channel is force closed, try to claim the output from this preimage.
// First check if a counterparty commitment transaction has been broadcasted:
macro_rules! claim_htlcs {
($commitment_number: expr, $txid: expr, $htlcs: expr) => {
let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None, $htlcs);
let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None, $htlcs, confirmed_spend_height);
let conf_target = self.closure_conf_target();
self.onchain_tx_handler.update_claims_view_from_requests(htlc_claim_reqs, self.best_block.height, self.best_block.height, broadcaster, conf_target, fee_estimator, logger);
}
Expand Down Expand Up @@ -3542,7 +3545,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
// First, process non-htlc outputs (to_holder & to_counterparty)
for (idx, outp) in tx.output.iter().enumerate() {
if outp.script_pubkey == revokeable_p2wsh {
let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx());
let revk_outp = RevokedOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, outp.value, self.counterparty_commitment_params.on_counterparty_tx_csv, self.onchain_tx_handler.channel_type_features().supports_anchors_zero_fee_htlc_tx(), height);
let justice_package = PackageTemplate::build_package(
commitment_txid, idx as u32,
PackageSolvingData::RevokedOutput(revk_outp),
Expand All @@ -3563,7 +3566,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
// per_commitment_data is corrupt or our commitment signing key leaked!
return (claimable_outpoints, to_counterparty_output_info);
}
let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), &self.onchain_tx_handler.channel_transaction_parameters.channel_type_features);
let revk_htlc_outp = RevokedHTLCOutput::build(per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key, self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key, htlc.amount_msat / 1000, htlc.clone(), &self.onchain_tx_handler.channel_transaction_parameters.channel_type_features, height);
let counterparty_spendable_height = if htlc.offered {
htlc.cltv_expiry
} else {
Expand Down Expand Up @@ -3617,7 +3620,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
(htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref()))
), logger);
let (htlc_claim_reqs, counterparty_output_info) =
self.get_counterparty_output_claim_info(commitment_number, commitment_txid, Some(tx), per_commitment_option);
self.get_counterparty_output_claim_info(commitment_number, commitment_txid, Some(tx), per_commitment_option, Some(height));
to_counterparty_output_info = counterparty_output_info;
for req in htlc_claim_reqs {
claimable_outpoints.push(req);
Expand All @@ -3628,7 +3631,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
}

/// Returns the HTLC claim package templates and the counterparty output info
fn get_counterparty_output_claim_info(&self, commitment_number: u64, commitment_txid: Txid, tx: Option<&Transaction>, per_commitment_option: Option<&Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>>)
fn get_counterparty_output_claim_info(&self, commitment_number: u64, commitment_txid: Txid, tx: Option<&Transaction>, per_commitment_option: Option<&Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>>, confirmation_height: Option<u32>)
-> (Vec<PackageTemplate>, CommitmentTxCounterpartyOutputInfo) {
let mut claimable_outpoints = Vec::new();
let mut to_counterparty_output_info: CommitmentTxCounterpartyOutputInfo = None;
Expand Down Expand Up @@ -3688,13 +3691,15 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
CounterpartyOfferedHTLCOutput::build(*per_commitment_point,
self.counterparty_commitment_params.counterparty_delayed_payment_base_key,
self.counterparty_commitment_params.counterparty_htlc_base_key,
preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.channel_type_features().clone()))
preimage.unwrap(), htlc.clone(), self.onchain_tx_handler.channel_type_features().clone(),
confirmation_height))
} else {
PackageSolvingData::CounterpartyReceivedHTLCOutput(
CounterpartyReceivedHTLCOutput::build(*per_commitment_point,
self.counterparty_commitment_params.counterparty_delayed_payment_base_key,
self.counterparty_commitment_params.counterparty_htlc_base_key,
htlc.clone(), self.onchain_tx_handler.channel_type_features().clone()))
htlc.clone(), self.onchain_tx_handler.channel_type_features().clone(),
confirmation_height))
};
let counterparty_package = PackageTemplate::build_package(commitment_txid, transaction_output_index, counterparty_htlc_outp, htlc.cltv_expiry);
claimable_outpoints.push(counterparty_package);
Expand Down Expand Up @@ -3736,7 +3741,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
per_commitment_point, self.counterparty_commitment_params.counterparty_delayed_payment_base_key,
self.counterparty_commitment_params.counterparty_htlc_base_key, per_commitment_key,
tx.output[idx].value, self.counterparty_commitment_params.on_counterparty_tx_csv,
false
false, height,
);
let justice_package = PackageTemplate::build_package(
htlc_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp),
Expand Down Expand Up @@ -3765,7 +3770,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
if let Some(transaction_output_index) = htlc.transaction_output_index {
let (htlc_output, counterparty_spendable_height) = if htlc.offered {
let htlc_output = HolderHTLCOutput::build_offered(
htlc.amount_msat, htlc.cltv_expiry, self.onchain_tx_handler.channel_type_features().clone()
htlc.amount_msat, htlc.cltv_expiry, self.onchain_tx_handler.channel_type_features().clone(), conf_height
);
(htlc_output, conf_height)
} else {
Expand All @@ -3776,7 +3781,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
continue;
};
let htlc_output = HolderHTLCOutput::build_accepted(
payment_preimage, htlc.amount_msat, self.onchain_tx_handler.channel_type_features().clone()
payment_preimage, htlc.amount_msat, self.onchain_tx_handler.channel_type_features().clone(), conf_height
);
(htlc_output, htlc.cltv_expiry)
};
Expand Down
29 changes: 26 additions & 3 deletions lightning/src/chain/onchaintx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ pub struct OnchainTxHandler<ChannelSigner: EcdsaChannelSigner> {
#[cfg(not(test))]
claimable_outpoints: HashMap<BitcoinOutPoint, (ClaimId, u32)>,

#[cfg(any(test, feature = "_test_utils"))]
pub(crate) locktimed_packages: BTreeMap<u32, Vec<PackageTemplate>>,
#[cfg(not(any(test, feature = "_test_utils")))]
locktimed_packages: BTreeMap<u32, Vec<PackageTemplate>>,

onchain_events_awaiting_threshold_conf: Vec<OnchainEventEntry>,
Expand Down Expand Up @@ -862,9 +865,10 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
// Because fuzzing can cause hash collisions, we can end up with conflicting claim
// ids here, so we only assert when not fuzzing.
debug_assert!(cfg!(fuzzing) || self.pending_claim_requests.get(&claim_id).is_none());
for k in req.outpoints() {
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
self.claimable_outpoints.insert(k.clone(), (claim_id, conf_height));
for (k, outpoint_confirmation_height) in req.outpoints_and_creation_heights() {
let creation_height = outpoint_confirmation_height.unwrap_or(conf_height);
log_info!(logger, "Registering claiming request for {}:{}, which exists as of height {creation_height}", k.txid, k.vout);
self.claimable_outpoints.insert(k.clone(), (claim_id, creation_height));
}
self.pending_claim_requests.insert(claim_id, req);
}
Expand Down Expand Up @@ -969,6 +973,17 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
panic!("Inconsistencies between pending_claim_requests map and claimable_outpoints map");
}
}

// Also remove/split any locktimed packages whose inputs have been spent by this transaction.
self.locktimed_packages.retain(|_locktime, packages|{
packages.retain_mut(|package| {
if let Some(p) = package.split_package(&inp.previous_output) {
claimed_outputs_material.push(p);
}
!package.outpoints().is_empty()
});
!packages.is_empty()
});
}
for package in claimed_outputs_material.drain(..) {
let entry = OnchainEventEntry {
Expand Down Expand Up @@ -1104,6 +1119,13 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
//- resurect outpoint back in its claimable set and regenerate tx
match entry.event {
OnchainEvent::ContentiousOutpoint { package } => {
// We pass 0 to `package_locktime` to get the actual required locktime.
let package_locktime = package.package_locktime(0);
if package_locktime >= height {
self.locktimed_packages.entry(package_locktime).or_default().push(package);
continue;
}

if let Some(pending_claim) = self.claimable_outpoints.get(package.outpoints()[0]) {
if let Some(request) = self.pending_claim_requests.get_mut(&pending_claim.0) {
assert!(request.merge_package(package, height).is_ok());
Expand Down Expand Up @@ -1408,6 +1430,7 @@ mod tests {
htlc.amount_msat,
htlc.cltv_expiry,
ChannelTypeFeatures::only_static_remote_key(),
0,
)),
0,
));
Expand Down
Loading
Loading