Skip to content

Commit 3877da9

Browse files
committed
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 c48e0a8 commit 3877da9

File tree

3 files changed

+197
-51
lines changed

3 files changed

+197
-51
lines changed

lightning/src/chain/onchaintx.rs

Lines changed: 20 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,12 @@ 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+
let package_locktime = package.package_locktime(height);
1153+
if package_locktime >= height {
1154+
self.locktimed_packages.entry(package_locktime).or_default().push(package);
1155+
continue;
1156+
}
1157+
11381158
if let Some(pending_claim) = self.claimable_outpoints.get(package.outpoints()[0]) {
11391159
if let Some(request) = self.pending_claim_requests.get_mut(&pending_claim.0) {
11401160
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: 163 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,9 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
732732
test_spendable_output(&nodes[0], &remote_txn[0], false);
733733
assert!(nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events().is_empty());
734734

735-
// After broadcasting the HTLC claim transaction, node A will still consider the HTLC
736-
// possibly-claimable up to ANTI_REORG_DELAY, at which point it will drop it.
735+
// After confirming the HTLC claim transaction, node A will no longer attempt to claim said
736+
// HTLC, unless the transaction is reorged. However, we'll still report a
737+
// `MaybeTimeoutClaimableHTLC` balance for it until we reach `ANTI_REORG_DELAY` confirmations.
737738
mine_transaction(&nodes[0], &b_broadcast_txn[0]);
738739
if prev_commitment_tx {
739740
expect_payment_path_successful!(nodes[0]);
@@ -749,18 +750,10 @@ fn do_test_claim_value_force_close(anchors: bool, prev_commitment_tx: bool) {
749750
// When the HTLC timeout output is spendable in the next block, A should broadcast it
750751
connect_blocks(&nodes[0], htlc_cltv_timeout - nodes[0].best_block_info().1);
751752
let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
752-
// Aggregated claim transaction.
753753
assert_eq!(a_broadcast_txn.len(), 1);
754754
check_spends!(a_broadcast_txn[0], remote_txn[0]);
755-
assert_eq!(a_broadcast_txn[0].input.len(), 2);
756-
assert_ne!(a_broadcast_txn[0].input[0].previous_output.vout, a_broadcast_txn[0].input[1].previous_output.vout);
757-
// a_broadcast_txn [0] and [1] should spend the HTLC outputs of the commitment tx
758-
assert!(a_broadcast_txn[0].input.iter().any(|input| remote_txn[0].output[input.previous_output.vout as usize].value.to_sat() == 3_000));
755+
assert_eq!(a_broadcast_txn[0].input.len(), 1);
759756
assert!(a_broadcast_txn[0].input.iter().any(|input| remote_txn[0].output[input.previous_output.vout as usize].value.to_sat() == 4_000));
760-
761-
// Confirm node B's claim for node A to remove that claim from the aggregated claim transaction.
762-
mine_transaction(&nodes[0], &b_broadcast_txn[0]);
763-
let a_broadcast_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
764757
let a_htlc_timeout_tx = a_broadcast_txn.into_iter().next_back().unwrap();
765758

766759
// Once the HTLC-Timeout transaction confirms, A will no longer consider the HTLC
@@ -3328,3 +3321,162 @@ fn test_update_replay_panics() {
33283321
monitor.update_monitor(&updates[2], &nodes[1].tx_broadcaster, &nodes[1].fee_estimator, &nodes[1].logger).unwrap();
33293322
monitor.update_monitor(&updates[3], &nodes[1].tx_broadcaster, &nodes[1].fee_estimator, &nodes[1].logger).unwrap();
33303323
}
3324+
3325+
#[test]
3326+
pub fn test_pruned_locktimed_packages_recovery_after_block_disconnected() {
3327+
use crate::events::bump_transaction::sync::WalletSourceSync;
3328+
3329+
// ====== TEST SETUP ======
3330+
let chanmon_cfgs = create_chanmon_cfgs(2);
3331+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
3332+
3333+
let mut user_cfg = test_default_channel_config();
3334+
user_cfg.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
3335+
user_cfg.manually_accept_inbound_channels = true;
3336+
3337+
let configs = [Some(user_cfg.clone()), Some(user_cfg.clone())];
3338+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &configs);
3339+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
3340+
3341+
let node_a_id = nodes[0].node.get_our_node_id();
3342+
let node_b_id = nodes[1].node.get_our_node_id();
3343+
3344+
// Since we're using anchor channels, make sure each node has a UTXO for paying fees.
3345+
let coinbase_tx = Transaction {
3346+
version: Version::TWO,
3347+
lock_time: LockTime::ZERO,
3348+
input: vec![TxIn { ..Default::default() }],
3349+
output: vec![
3350+
TxOut {
3351+
value: Amount::ONE_BTC,
3352+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
3353+
},
3354+
TxOut {
3355+
value: Amount::ONE_BTC,
3356+
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
3357+
},
3358+
],
3359+
};
3360+
nodes[0].wallet_source.add_utxo(
3361+
bitcoin::OutPoint { txid: coinbase_tx.compute_txid(), vout: 0 },
3362+
coinbase_tx.output[0].value,
3363+
);
3364+
nodes[1].wallet_source.add_utxo(
3365+
bitcoin::OutPoint { txid: coinbase_tx.compute_txid(), vout: 1 },
3366+
coinbase_tx.output[1].value,
3367+
);
3368+
3369+
const CHAN_CAPACITY: u64 = 10_000_000;
3370+
let (_, _, channel_id, funding_tx) =
3371+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, CHAN_CAPACITY, 0);
3372+
3373+
// Ensure all nodes are at the same initial height.
3374+
let node_max_height = nodes.iter().map(|node| node.best_block_info().1).max().unwrap();
3375+
for node in &nodes {
3376+
let blocks_to_mine = node_max_height - node.best_block_info().1;
3377+
if blocks_to_mine > 0 {
3378+
connect_blocks(node, blocks_to_mine);
3379+
}
3380+
}
3381+
3382+
// ====== TEST PROCESS ======
3383+
3384+
// Route HTLC 1 from A to B.
3385+
let (preimage_1, payment_hash_1, ..) = route_payment(&nodes[0], &[&nodes[1]], 1_000_000);
3386+
3387+
// Node B claims HTLC 1.
3388+
nodes[1].node.claim_funds(preimage_1);
3389+
expect_payment_claimed!(nodes[1], payment_hash_1, 1_000_000);
3390+
check_added_monitors(&nodes[1], 1);
3391+
let _ = get_htlc_update_msgs(&nodes[1], &node_a_id);
3392+
3393+
// Force close the channel by broadcasting node B's commitment tx.
3394+
let node_b_commit_tx = get_local_commitment_txn!(nodes[1], channel_id);
3395+
assert_eq!(node_b_commit_tx.len(), 1);
3396+
let node_b_commit_tx = &node_b_commit_tx[0];
3397+
check_spends!(node_b_commit_tx, funding_tx);
3398+
3399+
let htlc_1_locktime = nodes[0].best_block_info().1 + 1 + TEST_FINAL_CLTV;
3400+
mine_transaction(&nodes[0], node_b_commit_tx);
3401+
check_closed_event(
3402+
&nodes[0],
3403+
1,
3404+
ClosureReason::CommitmentTxConfirmed,
3405+
false,
3406+
&[node_b_id],
3407+
CHAN_CAPACITY,
3408+
);
3409+
check_closed_broadcast!(nodes[0], true);
3410+
check_added_monitors(&nodes[0], 1);
3411+
3412+
mine_transaction(&nodes[1], node_b_commit_tx);
3413+
check_closed_event(
3414+
&nodes[1],
3415+
1,
3416+
ClosureReason::CommitmentTxConfirmed,
3417+
false,
3418+
&[node_a_id],
3419+
CHAN_CAPACITY,
3420+
);
3421+
check_closed_broadcast!(nodes[1], true);
3422+
check_added_monitors(&nodes[1], 1);
3423+
3424+
// Node B generate HTLC 1 claim tx.
3425+
let process_bump_event = |node: &Node| {
3426+
let events = node.chain_monitor.chain_monitor.get_and_clear_pending_events();
3427+
assert_eq!(events.len(), 1);
3428+
let bump_event = match &events[0] {
3429+
Event::BumpTransaction(bump_event) => bump_event,
3430+
e => panic!("Unexepected event: {:#?}", e),
3431+
};
3432+
node.bump_tx_handler.handle_event(bump_event);
3433+
3434+
let mut tx = node.tx_broadcaster.txn_broadcast();
3435+
assert_eq!(tx.len(), 1);
3436+
tx.pop().unwrap()
3437+
};
3438+
let bs_htlc_1_claim_tx = process_bump_event(&nodes[1]);
3439+
3440+
let get_locktimed_packages = || {
3441+
let monitor = nodes[0].chain_monitor.chain_monitor.get_monitor(channel_id).unwrap();
3442+
let onchain_tx_handler = &monitor.inner.lock().unwrap().onchain_tx_handler;
3443+
onchain_tx_handler.locktimed_packages.clone()
3444+
};
3445+
3446+
let locktimed_packages = get_locktimed_packages();
3447+
let htlc_1_locktimed_package = {
3448+
let packages = locktimed_packages.get(&htlc_1_locktime)
3449+
.expect("HTLC 1 locktimed package should exist");
3450+
assert_eq!(packages.len(), 1, "HTLC 1 locktimed package should have only one package");
3451+
packages.first().unwrap().clone()
3452+
};
3453+
3454+
// HTLC 1 claim tx confirmed - Node A should prune its claim request from locktimed HTLC packages.
3455+
mine_transaction(&nodes[0], &bs_htlc_1_claim_tx);
3456+
let locktimed_packages = get_locktimed_packages();
3457+
assert!(locktimed_packages.is_empty(), "locktimed packages should be pruned");
3458+
3459+
// Disconnect the block containing HTLC 1 claim tx to simulate a reorg. Node A should recover
3460+
// the pruned locktimed package.
3461+
disconnect_blocks(&nodes[0], 1);
3462+
let locktimed_packages = get_locktimed_packages();
3463+
let recovered_htlc_1_locktimed_package = {
3464+
let packages = locktimed_packages.get(&htlc_1_locktime)
3465+
.expect("HTLC 1 locktimed package should be recovered");
3466+
assert_eq!(packages.len(), 1, "HTLC 1 locktimed package should have only one package");
3467+
packages.first().unwrap().clone()
3468+
};
3469+
assert!(recovered_htlc_1_locktimed_package == htlc_1_locktimed_package,
3470+
"Recovered HTLC 1 locktimed package should match the original one");
3471+
3472+
// HTLC 1 locktime expires.
3473+
connect_blocks(&nodes[0], TEST_FINAL_CLTV);
3474+
// HTLC 1 timeout tx should be broadcasted.
3475+
let mut txs = nodes[0].tx_broadcaster.txn_broadcast();
3476+
assert_eq!(txs.len(), 1);
3477+
check_spends!(txs[0], node_b_commit_tx);
3478+
3479+
// PaymentSent and PaymentPathSuccessful events.
3480+
let events = nodes[0].node.get_and_clear_pending_events();
3481+
assert_eq!(events.len(), 2);
3482+
}

0 commit comments

Comments
 (0)