Skip to content

Commit 7e4598d

Browse files
authored
Merge pull request #3923 from TheBlueMatt/2025-07-3860-plus-plus
Locktimed packages fixes
2 parents 70cfd9c + d7726ef commit 7e4598d

File tree

7 files changed

+433
-235
lines changed

7 files changed

+433
-235
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3514,23 +3514,26 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
35143514
(payment_preimage.clone(), payment_info.clone().into_iter().collect())
35153515
});
35163516

3517-
let confirmed_spend_txid = self.funding_spend_confirmed.or_else(|| {
3518-
self.onchain_events_awaiting_threshold_conf.iter().find_map(|event| match event.event {
3519-
OnchainEvent::FundingSpendConfirmation { .. } => Some(event.txid),
3520-
_ => None,
3521-
})
3522-
});
3523-
let confirmed_spend_txid = if let Some(txid) = confirmed_spend_txid {
3524-
txid
3525-
} else {
3526-
return;
3527-
};
3517+
let confirmed_spend_info = self.funding_spend_confirmed
3518+
.map(|txid| (txid, None))
3519+
.or_else(|| {
3520+
self.onchain_events_awaiting_threshold_conf.iter().find_map(|event| match event.event {
3521+
OnchainEvent::FundingSpendConfirmation { .. } => Some((event.txid, Some(event.height))),
3522+
_ => None,
3523+
})
3524+
});
3525+
let (confirmed_spend_txid, confirmed_spend_height) =
3526+
if let Some((txid, height)) = confirmed_spend_info {
3527+
(txid, height)
3528+
} else {
3529+
return;
3530+
};
35283531

35293532
// If the channel is force closed, try to claim the output from this preimage.
35303533
// First check if a counterparty commitment transaction has been broadcasted:
35313534
macro_rules! claim_htlcs {
35323535
($commitment_number: expr, $txid: expr, $htlcs: expr) => {
3533-
let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None, $htlcs);
3536+
let (htlc_claim_reqs, _) = self.get_counterparty_output_claim_info($commitment_number, $txid, None, $htlcs, confirmed_spend_height);
35343537
let conf_target = self.closure_conf_target();
35353538
self.onchain_tx_handler.update_claims_view_from_requests(
35363539
htlc_claim_reqs, self.best_block.height, self.best_block.height, broadcaster,
@@ -4230,6 +4233,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
42304233
per_commitment_point, per_commitment_key, outp.value,
42314234
self.funding.channel_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx(),
42324235
self.funding.channel_parameters.clone(),
4236+
height,
42334237
);
42344238
let justice_package = PackageTemplate::build_package(
42354239
commitment_txid, idx as u32,
@@ -4254,6 +4258,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
42544258
let revk_htlc_outp = RevokedHTLCOutput::build(
42554259
per_commitment_point, per_commitment_key, htlc.clone(),
42564260
self.funding.channel_parameters.clone(),
4261+
height,
42574262
);
42584263
let counterparty_spendable_height = if htlc.offered {
42594264
htlc.cltv_expiry
@@ -4308,7 +4313,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
43084313
(htlc, htlc_source.as_ref().map(|htlc_source| htlc_source.as_ref()))
43094314
), logger);
43104315
let (htlc_claim_reqs, counterparty_output_info) =
4311-
self.get_counterparty_output_claim_info(commitment_number, commitment_txid, Some(tx), per_commitment_option);
4316+
self.get_counterparty_output_claim_info(commitment_number, commitment_txid, Some(tx), per_commitment_option, Some(height));
43124317
to_counterparty_output_info = counterparty_output_info;
43134318
for req in htlc_claim_reqs {
43144319
claimable_outpoints.push(req);
@@ -4320,8 +4325,11 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
43204325

43214326
/// Returns the HTLC claim package templates and the counterparty output info
43224327
#[rustfmt::skip]
4323-
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>>)>>)
4324-
-> (Vec<PackageTemplate>, CommitmentTxCounterpartyOutputInfo) {
4328+
fn get_counterparty_output_claim_info(
4329+
&self, commitment_number: u64, commitment_txid: Txid, tx: Option<&Transaction>,
4330+
per_commitment_option: Option<&Vec<(HTLCOutputInCommitment, Option<Box<HTLCSource>>)>>,
4331+
confirmation_height: Option<u32>,
4332+
) -> (Vec<PackageTemplate>, CommitmentTxCounterpartyOutputInfo) {
43254333
let mut claimable_outpoints = Vec::new();
43264334
let mut to_counterparty_output_info: CommitmentTxCounterpartyOutputInfo = None;
43274335

@@ -4378,15 +4386,19 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
43784386
let counterparty_htlc_outp = if htlc.offered {
43794387
PackageSolvingData::CounterpartyOfferedHTLCOutput(
43804388
CounterpartyOfferedHTLCOutput::build(
4381-
*per_commitment_point, preimage.unwrap(), htlc.clone(),
4389+
*per_commitment_point, preimage.unwrap(),
4390+
htlc.clone(),
43824391
self.funding.channel_parameters.clone(),
4392+
confirmation_height,
43834393
)
43844394
)
43854395
} else {
43864396
PackageSolvingData::CounterpartyReceivedHTLCOutput(
43874397
CounterpartyReceivedHTLCOutput::build(
4388-
*per_commitment_point, htlc.clone(),
4398+
*per_commitment_point,
4399+
htlc.clone(),
43894400
self.funding.channel_parameters.clone(),
4401+
confirmation_height,
43904402
)
43914403
)
43924404
};
@@ -4430,6 +4442,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
44304442
let revk_outp = RevokedOutput::build(
44314443
per_commitment_point, per_commitment_key, tx.output[idx].value, false,
44324444
self.funding.channel_parameters.clone(),
4445+
height,
44334446
);
44344447
let justice_package = PackageTemplate::build_package(
44354448
htlc_txid, idx as u32, PackageSolvingData::RevokedOutput(revk_outp),
@@ -4511,7 +4524,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
45114524
.expect("Expected transaction output index for non-dust HTLC");
45124525
PackageTemplate::build_package(
45134526
tx.txid(), transaction_output_index,
4514-
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build(htlc_descriptor)),
4527+
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build(htlc_descriptor, conf_height)),
45154528
counterparty_spendable_height,
45164529
)
45174530
})
@@ -4691,7 +4704,7 @@ impl<Signer: EcdsaChannelSigner> ChannelMonitorImpl<Signer> {
46914704
let txid = self.funding.current_holder_commitment_tx.trust().txid();
46924705
let vout = htlc_descriptor.htlc.transaction_output_index
46934706
.expect("Expected transaction output index for non-dust HTLC");
4694-
let htlc_output = HolderHTLCOutput::build(htlc_descriptor);
4707+
let htlc_output = HolderHTLCOutput::build(htlc_descriptor, 0);
46954708
if let Some(htlc_tx) = htlc_output.get_maybe_signed_htlc_tx(
46964709
&mut self.onchain_tx_handler, &::bitcoin::OutPoint { txid, vout },
46974710
) {

lightning/src/chain/onchaintx.rs

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ pub struct OnchainTxHandler<ChannelSigner: EcdsaChannelSigner> {
269269
#[cfg(not(any(test, feature = "_test_utils")))]
270270
claimable_outpoints: HashMap<BitcoinOutPoint, (ClaimId, u32)>,
271271

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

274277
onchain_events_awaiting_threshold_conf: Vec<OnchainEventEntry>,
@@ -886,9 +889,10 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
886889
// Because fuzzing can cause hash collisions, we can end up with conflicting claim
887890
// ids here, so we only assert when not fuzzing.
888891
debug_assert!(cfg!(fuzzing) || self.pending_claim_requests.get(&claim_id).is_none());
889-
for k in req.outpoints() {
890-
log_info!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
891-
self.claimable_outpoints.insert(k.clone(), (claim_id, conf_height));
892+
for (k, outpoint_confirmation_height) in req.outpoints_and_creation_heights() {
893+
let creation_height = outpoint_confirmation_height.unwrap_or(conf_height);
894+
log_info!(logger, "Registering claiming request for {}:{}, which exists as of height {creation_height}", k.txid, k.vout);
895+
self.claimable_outpoints.insert(k.clone(), (claim_id, creation_height));
892896
}
893897
self.pending_claim_requests.insert(claim_id, req);
894898
}
@@ -994,6 +998,17 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
994998
panic!("Inconsistencies between pending_claim_requests map and claimable_outpoints map");
995999
}
9961000
}
1001+
1002+
// Also remove/split any locktimed packages whose inputs have been spent by this transaction.
1003+
self.locktimed_packages.retain(|_locktime, packages|{
1004+
packages.retain_mut(|package| {
1005+
if let Some(p) = package.split_package(&inp.previous_output) {
1006+
claimed_outputs_material.push(p);
1007+
}
1008+
!package.outpoints().is_empty()
1009+
});
1010+
!packages.is_empty()
1011+
});
9971012
}
9981013
for package in claimed_outputs_material.drain(..) {
9991014
let entry = OnchainEventEntry {
@@ -1135,6 +1150,13 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
11351150
//- resurect outpoint back in its claimable set and regenerate tx
11361151
match entry.event {
11371152
OnchainEvent::ContentiousOutpoint { package } => {
1153+
// We pass 0 to `package_locktime` to get the actual required locktime.
1154+
let package_locktime = package.package_locktime(0);
1155+
if package_locktime >= height {
1156+
self.locktimed_packages.entry(package_locktime).or_default().push(package);
1157+
continue;
1158+
}
1159+
11381160
if let Some(pending_claim) = self.claimable_outpoints.get(package.outpoints()[0]) {
11391161
if let Some(request) = self.pending_claim_requests.get_mut(&pending_claim.0) {
11401162
assert!(request.merge_package(package, height).is_ok());
@@ -1358,19 +1380,21 @@ mod tests {
13581380
holder_commit_txid,
13591381
htlc.transaction_output_index.unwrap(),
13601382
PackageSolvingData::HolderHTLCOutput(HolderHTLCOutput::build(HTLCDescriptor {
1361-
channel_derivation_parameters: ChannelDerivationParameters {
1362-
value_satoshis: tx_handler.channel_value_satoshis,
1363-
keys_id: tx_handler.channel_keys_id,
1364-
transaction_parameters: tx_handler.channel_transaction_parameters.clone(),
1383+
channel_derivation_parameters: ChannelDerivationParameters {
1384+
value_satoshis: tx_handler.channel_value_satoshis,
1385+
keys_id: tx_handler.channel_keys_id,
1386+
transaction_parameters: tx_handler.channel_transaction_parameters.clone(),
1387+
},
1388+
commitment_txid: holder_commit_txid,
1389+
per_commitment_number: holder_commit.commitment_number(),
1390+
per_commitment_point: holder_commit.per_commitment_point(),
1391+
feerate_per_kw: holder_commit.feerate_per_kw(),
1392+
htlc: htlc.clone(),
1393+
preimage: None,
1394+
counterparty_sig: *counterparty_sig,
13651395
},
1366-
commitment_txid: holder_commit_txid,
1367-
per_commitment_number: holder_commit.commitment_number(),
1368-
per_commitment_point: holder_commit.per_commitment_point(),
1369-
feerate_per_kw: holder_commit.feerate_per_kw(),
1370-
htlc: htlc.clone(),
1371-
preimage: None,
1372-
counterparty_sig: *counterparty_sig,
1373-
})),
1396+
0
1397+
)),
13741398
0,
13751399
));
13761400
}

0 commit comments

Comments
 (0)