Skip to content

Cut 0.1.5 #3935

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 14 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
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
# 0.1.5 - Jul XXX, 2025 - "Async Path Reduction"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs a date.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add in a followup, these things always seem to be late so I think I might start adding them in followups.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that follow up never happend?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its in #3936


## Performance Improvements
* `NetworkGraph`'s expensive internal consistency checks have now been
disabled in debug builds in addition to release builds (#3687).

## Bug Fixes
* Pathfinding which results in a multi-path payment is now substantially
smarter, using fewer paths and better optimizing fees and successes (#3890).
* A counterparty delaying claiming multiple HTLCs with different expiries can
no longer cause our `ChannelMonitor` to continuously rebroadcast invalid
transactions or RBF bump attempts (#3923).
* Reorgs can no longer cause us to fail to claim HTLCs after a counterparty
delayed claiming multiple HTLCs with different expiries (#3923).
* Force-closing a channel while it is blocked on another channel's async
`ChannelMonitorUpdate` can no longer lead to a panic (#3858).
* `ChannelMonitorUpdate`s can no longer be released to storage too early when
doing async updates or on restart. This only impacts async
`ChannelMonitorUpdate` persistence and can lead to loss of funds only in rare
cases with `ChannelMonitorUpdate` persistence order inversions (#3907).

## Security
0.1.5 fixes a vulnerability which could allow a peer to overdraw their reserve
value, potentially cutting into commitment transaction fees on channels with a
low reserve.
* Due to a bug in checking whether an HTLC is dust during acceptance, near-dust
HTLCs were not counted towards the commitment transaction fee, but did
eventually contribute to it when we built a commitment transaction. This can
be used by a counterparty to overdraw their reserve value, or, for channels
with a low reserve value, cut into the commitment transaction fee (#3933).


# 0.1.4 - May 23, 2025 - "Careful Validation of Bogus States"

## Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion lightning/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "lightning"
version = "0.1.4"
version = "0.1.5"
authors = ["Matt Corallo"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/lightningdevkit/rust-lightning/"
Expand Down
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