Skip to content

Commit 77f6a8f

Browse files
whfuynTheBlueMatt
authored andcommitted
Prune locktimed packages when inputs are spent
We have to prune locktimed packages when their inputs are spent, otherwise the notification of the watched outputs might be missed. This can lead to locktimed packages with spent inputs being added back to the pending claim requests in the future, and they are never cleaned up until node restart. Resolves: #3859 Conflicts resolved in: * lightning/src/ln/functional_tests.rs due to upstream changes of removed code * lightning/src/ln/monitor_tests.rs due to trivial upstream changes
1 parent c9efeff commit 77f6a8f

File tree

3 files changed

+39
-39
lines changed

3 files changed

+39
-39
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ pub struct OnchainTxHandler<ChannelSigner: EcdsaChannelSigner> {
278278
#[cfg(not(test))]
279279
claimable_outpoints: HashMap<BitcoinOutPoint, (ClaimId, u32)>,
280280

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

283286
onchain_events_awaiting_threshold_conf: Vec<OnchainEventEntry>,
@@ -969,6 +972,17 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
969972
panic!("Inconsistencies between pending_claim_requests map and claimable_outpoints map");
970973
}
971974
}
975+
976+
// Also remove/split any locktimed packages whose inputs have been spent by this transaction.
977+
self.locktimed_packages.retain(|_locktime, packages|{
978+
packages.retain_mut(|package| {
979+
if let Some(p) = package.split_package(&inp.previous_output) {
980+
claimed_outputs_material.push(p);
981+
}
982+
!package.outpoints().is_empty()
983+
});
984+
!packages.is_empty()
985+
});
972986
}
973987
for package in claimed_outputs_material.drain(..) {
974988
let entry = OnchainEventEntry {
@@ -1104,6 +1118,13 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
11041118
//- resurect outpoint back in its claimable set and regenerate tx
11051119
match entry.event {
11061120
OnchainEvent::ContentiousOutpoint { package } => {
1121+
// We pass 0 to `package_locktime` to get the actual required locktime.
1122+
let package_locktime = package.package_locktime(0);
1123+
if package_locktime >= height {
1124+
self.locktimed_packages.entry(package_locktime).or_default().push(package);
1125+
continue;
1126+
}
1127+
11071128
if let Some(pending_claim) = self.claimable_outpoints.get(package.outpoints()[0]) {
11081129
if let Some(request) = self.pending_claim_requests.get_mut(&pending_claim.0) {
11091130
assert!(request.merge_package(package, height).is_ok());

lightning/src/ln/functional_tests.rs

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3330,33 +3330,9 @@ fn test_htlc_on_chain_success() {
33303330
_ => panic!("Unexpected event"),
33313331
}
33323332

3333-
macro_rules! check_tx_local_broadcast {
3334-
($node: expr, $htlc_offered: expr, $commitment_tx: expr) => { {
3335-
let mut node_txn = $node.tx_broadcaster.txn_broadcasted.lock().unwrap();
3336-
// HTLC timeout claims for non-anchor channels are only aggregated when claimed from the
3337-
// remote commitment transaction.
3338-
if $htlc_offered {
3339-
assert_eq!(node_txn.len(), 2);
3340-
for tx in node_txn.iter() {
3341-
check_spends!(tx, $commitment_tx);
3342-
assert_ne!(tx.lock_time, LockTime::ZERO);
3343-
assert_eq!(tx.input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
3344-
assert!(tx.output[0].script_pubkey.is_p2wsh()); // revokeable output
3345-
}
3346-
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
3347-
} else {
3348-
assert_eq!(node_txn.len(), 1);
3349-
check_spends!(node_txn[0], $commitment_tx);
3350-
assert_ne!(node_txn[0].lock_time, LockTime::ZERO);
3351-
assert_eq!(node_txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
3352-
assert!(node_txn[0].output[0].script_pubkey.is_p2wpkh()); // direct payment
3353-
assert_ne!(node_txn[0].input[0].previous_output, node_txn[0].input[1].previous_output);
3354-
}
3355-
node_txn.clear();
3356-
} }
3357-
}
3358-
// nodes[1] now broadcasts its own timeout-claim of the output that nodes[2] just claimed via success.
3359-
check_tx_local_broadcast!(nodes[1], false, commitment_tx[0]);
3333+
// nodes[1] does not broadcast its own timeout-claim of the output as nodes[2] just claimed it
3334+
// via success.
3335+
assert!(nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
33603336

33613337
// Broadcast legit commitment tx from A on B's chain
33623338
// Broadcast preimage tx by B on offered output from A commitment tx on A's chain
@@ -3417,7 +3393,17 @@ fn test_htlc_on_chain_success() {
34173393
_ => panic!("Unexpected event"),
34183394
}
34193395
}
3420-
check_tx_local_broadcast!(nodes[0], true, node_a_commitment_tx[0]);
3396+
// HTLC timeout claims for non-anchor channels are only aggregated when claimed from the
3397+
// remote commitment transaction.
3398+
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcast();
3399+
assert_eq!(node_txn.len(), 2);
3400+
for tx in node_txn.iter() {
3401+
check_spends!(tx, node_a_commitment_tx[0]);
3402+
assert_ne!(tx.lock_time, LockTime::ZERO);
3403+
assert_eq!(tx.input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
3404+
assert!(tx.output[0].script_pubkey.is_p2wsh()); // revokeable output
3405+
}
3406+
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
34213407
}
34223408

34233409
fn do_test_htlc_on_chain_timeout(connect_style: ConnectStyle) {

lightning/src/ln/monitor_tests.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -711,8 +711,9 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
711711
test_spendable_output(&nodes[0], &remote_txn[0], false);
712712
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
713713

714-
// After broadcasting the HTLC claim transaction, node A will still consider the HTLC
715-
// possibly-claimable up to ANTI_REORG_DELAY, at which point it will drop it.
714+
// After confirming the HTLC claim transaction, node A will no longer attempt to claim said
715+
// HTLC, unless the transaction is reorged. However, we'll still report a
716+
// `MaybeTimeoutClaimableHTLC` balance for it until we reach `ANTI_REORG_DELAY` confirmations.
716717
mine_transaction(&nodes[0], &b_broadcast_txn[0]);
717718
if prev_commitment_tx {
718719
expect_payment_path_successful!(nodes[0]);
@@ -728,18 +729,10 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
728729
// When the HTLC timeout output is spendable in the next block, A should broadcast it
729730
connect_blocks(&nodes[0], htlc_cltv_timeout - nodes[0].best_block_info().1);
730731
let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
731-
// Aggregated claim transaction.
732732
assert_eq!(a_broadcast_txn.len(), 1);
733733
check_spends!(a_broadcast_txn[0], remote_txn[0]);
734-
assert_eq!(a_broadcast_txn[0].input.len(), 2);
735-
assert_ne!(a_broadcast_txn[0].input[0].previous_output.vout, a_broadcast_txn[0].input[1].previous_output.vout);
736-
// a_broadcast_txn [0] and [1] should spend the HTLC outputs of the commitment tx
737-
assert!(a_broadcast_txn[0].input.iter().any(|input| remote_txn[0].output[input.previous_output.vout as usize].value.to_sat() == 3_000));
734+
assert_eq!(a_broadcast_txn[0].input.len(), 1);
738735
assert!(a_broadcast_txn[0].input.iter().any(|input| remote_txn[0].output[input.previous_output.vout as usize].value.to_sat() == 4_000));
739-
740-
// Confirm node B's claim for node A to remove that claim from the aggregated claim transaction.
741-
mine_transaction(&nodes[0], &b_broadcast_txn[0]);
742-
let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
743736
let a_htlc_timeout_tx = a_broadcast_txn.into_iter().last().unwrap();
744737

745738
// Once the HTLC-Timeout transaction confirms, A will no longer consider the HTLC

0 commit comments

Comments
 (0)