Skip to content

Commit 92fe3aa

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
1 parent c915c99 commit 92fe3aa

File tree

3 files changed

+39
-51
lines changed

3 files changed

+39
-51
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 21 additions & 0 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>,
@@ -994,6 +997,17 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
994997
panic!("Inconsistencies between pending_claim_requests map and claimable_outpoints map");
995998
}
996999
}
1000+
1001+
// Also remove/split any locktimed packages whose inputs have been spent by this transaction.
1002+
self.locktimed_packages.retain(|_locktime, packages|{
1003+
packages.retain_mut(|package| {
1004+
if let Some(p) = package.split_package(&inp.previous_output) {
1005+
claimed_outputs_material.push(p);
1006+
}
1007+
!package.outpoints().is_empty()
1008+
});
1009+
!packages.is_empty()
1010+
});
9971011
}
9981012
for package in claimed_outputs_material.drain(..) {
9991013
let entry = OnchainEventEntry {
@@ -1135,6 +1149,13 @@ impl<ChannelSigner: EcdsaChannelSigner> OnchainTxHandler<ChannelSigner> {
11351149
//- resurect outpoint back in its claimable set and regenerate tx
11361150
match entry.event {
11371151
OnchainEvent::ContentiousOutpoint { package } => {
1152+
// We pass 0 to `package_locktime` to get the actual required locktime.
1153+
let package_locktime = package.package_locktime(0);
1154+
if package_locktime >= height {
1155+
self.locktimed_packages.entry(package_locktime).or_default().push(package);
1156+
continue;
1157+
}
1158+
11381159
if let Some(pending_claim) = self.claimable_outpoints.get(package.outpoints()[0]) {
11391160
if let Some(request) = self.pending_claim_requests.get_mut(&pending_claim.0) {
11401161
assert!(request.merge_package(package, height).is_ok());

lightning/src/ln/functional_tests.rs

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,45 +1961,9 @@ pub fn test_htlc_on_chain_success() {
19611961
_ => panic!("Unexpected event"),
19621962
}
19631963

1964-
macro_rules! check_tx_local_broadcast {
1965-
($node: expr, $htlc_offered: expr, $commitment_tx: expr) => {{
1966-
let mut node_txn = $node.tx_broadcaster.txn_broadcasted.lock().unwrap();
1967-
// HTLC timeout claims for non-anchor channels are only aggregated when claimed from the
1968-
// remote commitment transaction.
1969-
if $htlc_offered {
1970-
assert_eq!(node_txn.len(), 2);
1971-
for tx in node_txn.iter() {
1972-
check_spends!(tx, $commitment_tx);
1973-
assert_ne!(tx.lock_time, LockTime::ZERO);
1974-
assert_eq!(
1975-
tx.input[0].witness.last().unwrap().len(),
1976-
OFFERED_HTLC_SCRIPT_WEIGHT
1977-
);
1978-
assert!(tx.output[0].script_pubkey.is_p2wsh()); // revokeable output
1979-
}
1980-
assert_ne!(
1981-
node_txn[0].input[0].previous_output,
1982-
node_txn[1].input[0].previous_output
1983-
);
1984-
} else {
1985-
assert_eq!(node_txn.len(), 1);
1986-
check_spends!(node_txn[0], $commitment_tx);
1987-
assert_ne!(node_txn[0].lock_time, LockTime::ZERO);
1988-
assert_eq!(
1989-
node_txn[0].input[0].witness.last().unwrap().len(),
1990-
ACCEPTED_HTLC_SCRIPT_WEIGHT
1991-
);
1992-
assert!(node_txn[0].output[0].script_pubkey.is_p2wpkh()); // direct payment
1993-
assert_ne!(
1994-
node_txn[0].input[0].previous_output,
1995-
node_txn[0].input[1].previous_output
1996-
);
1997-
}
1998-
node_txn.clear();
1999-
}};
2000-
}
2001-
// nodes[1] now broadcasts its own timeout-claim of the output that nodes[2] just claimed via success.
2002-
check_tx_local_broadcast!(nodes[1], false, commitment_tx[0]);
1964+
// nodes[1] does not broadcast its own timeout-claim of the output as nodes[2] just claimed it
1965+
// via success.
1966+
assert!(nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().is_empty());
20031967

20041968
// Broadcast legit commitment tx from A on B's chain
20051969
// Broadcast preimage tx by B on offered output from A commitment tx on A's chain
@@ -2061,7 +2025,17 @@ pub fn test_htlc_on_chain_success() {
20612025
_ => panic!("Unexpected event"),
20622026
}
20632027
}
2064-
check_tx_local_broadcast!(nodes[0], true, node_a_commitment_tx[0]);
2028+
// HTLC timeout claims for non-anchor channels are only aggregated when claimed from the
2029+
// remote commitment transaction.
2030+
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcast();
2031+
assert_eq!(node_txn.len(), 2);
2032+
for tx in node_txn.iter() {
2033+
check_spends!(tx, node_a_commitment_tx[0]);
2034+
assert_ne!(tx.lock_time, LockTime::ZERO);
2035+
assert_eq!(tx.input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
2036+
assert!(tx.output[0].script_pubkey.is_p2wsh()); // revokeable output
2037+
}
2038+
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
20652039
}
20662040

20672041
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
@@ -717,8 +717,9 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
717717
test_spendable_output(&nodes[0], &remote_txn[0], false);
718718
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
719719

720-
// After broadcasting the HTLC claim transaction, node A will still consider the HTLC
721-
// possibly-claimable up to ANTI_REORG_DELAY, at which point it will drop it.
720+
// After confirming the HTLC claim transaction, node A will no longer attempt to claim said
721+
// HTLC, unless the transaction is reorged. However, we'll still report a
722+
// `MaybeTimeoutClaimableHTLC` balance for it until we reach `ANTI_REORG_DELAY` confirmations.
722723
mine_transaction(&nodes[0], &b_broadcast_txn[0]);
723724
if prev_commitment_tx {
724725
expect_payment_path_successful!(nodes[0]);
@@ -734,18 +735,10 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
734735
// When the HTLC timeout output is spendable in the next block, A should broadcast it
735736
connect_blocks(&nodes[0], htlc_cltv_timeout - nodes[0].best_block_info().1);
736737
let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
737-
// Aggregated claim transaction.
738738
assert_eq!(a_broadcast_txn.len(), 1);
739739
check_spends!(a_broadcast_txn[0], remote_txn[0]);
740-
assert_eq!(a_broadcast_txn[0].input.len(), 2);
741-
assert_ne!(a_broadcast_txn[0].input[0].previous_output.vout, a_broadcast_txn[0].input[1].previous_output.vout);
742-
// a_broadcast_txn [0] and [1] should spend the HTLC outputs of the commitment tx
743-
assert!(a_broadcast_txn[0].input.iter().any(|input| remote_txn[0].output[input.previous_output.vout as usize].value.to_sat() == 3_000));
740+
assert_eq!(a_broadcast_txn[0].input.len(), 1);
744741
assert!(a_broadcast_txn[0].input.iter().any(|input| remote_txn[0].output[input.previous_output.vout as usize].value.to_sat() == 4_000));
745-
746-
// Confirm node B's claim for node A to remove that claim from the aggregated claim transaction.
747-
mine_transaction(&nodes[0], &b_broadcast_txn[0]);
748-
let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
749742
let a_htlc_timeout_tx = a_broadcast_txn.into_iter().next_back().unwrap();
750743

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

0 commit comments

Comments
 (0)