Skip to content

Commit 21be9c5

Browse files
committed
Add a test case for the issues fixed in the previous few commits
This adds a single test which exercises both the ability to prune locktimed packages when inputs are spent as well as the creation-height tracking for locktimed packages.
1 parent 9a95544 commit 21be9c5

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

lightning/src/ln/reorg_tests.rs

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::events::{Event, ClosureReason, HTLCHandlingFailureType};
1919
use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, Init, MessageSendEvent};
2020
use crate::ln::types::ChannelId;
2121
use crate::sign::OutputSpender;
22+
use crate::types::payment::PaymentHash;
2223
use crate::types::string::UntrustedString;
2324
use crate::util::ser::Writeable;
2425

@@ -899,3 +900,219 @@ fn test_retries_own_commitment_broadcast_after_reorg() {
899900
do_test_retries_own_commitment_broadcast_after_reorg(true, false);
900901
do_test_retries_own_commitment_broadcast_after_reorg(true, true);
901902
}
903+
904+
fn do_test_split_htlc_expiry_tracking(use_third_htlc: bool, reorg_out: bool) {
905+
// Previously, we had a bug where if there were two HTLCs which expired at different heights,
906+
// and a counterparty commitment transaction confirmed spending both of them, we'd continually
907+
// rebroadcast attempted HTLC claims against the higher-expiry HTLC forever.
908+
let chanmon_cfgs = create_chanmon_cfgs(2);
909+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
910+
911+
// This test relies on being able to consolidate HTLC claims into a single transaction, which
912+
// requires anchors:
913+
let mut config = test_default_channel_config();
914+
config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
915+
config.manually_accept_inbound_channels = true;
916+
917+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(config.clone()), Some(config)]);
918+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
919+
920+
let coinbase_tx = provide_anchor_reserves(&nodes);
921+
922+
let node_a_id = nodes[0].node.get_our_node_id();
923+
let node_b_id = nodes[1].node.get_our_node_id();
924+
925+
let (_, _, chan_id, funding_tx) =
926+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0);
927+
928+
// Route two non-dust HTLCs with different expiry, with a third having the same expiry as the
929+
// second if `use_third_htlc` is set.
930+
let (preimage_a, payment_hash_a, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000_000);
931+
connect_blocks(&nodes[0], 2);
932+
connect_blocks(&nodes[1], 2);
933+
let (preimage_b, payment_hash_b, ..) = route_payment(&nodes[0], &[&nodes[1]], 100_000_000);
934+
let payment_hash_c = if use_third_htlc {
935+
route_payment(&nodes[0], &[&nodes[1]], 100_000_000).1
936+
} else {
937+
PaymentHash([0; 32])
938+
};
939+
940+
// First disconnect peers so that we don't have to deal with messages:
941+
nodes[0].node.peer_disconnected(node_b_id);
942+
nodes[1].node.peer_disconnected(node_a_id);
943+
944+
// Give node B preimages so that it will claim the first two HTLCs on-chain.
945+
nodes[1].node.claim_funds(preimage_a);
946+
expect_payment_claimed!(nodes[1], payment_hash_a, 100_000_000);
947+
nodes[1].node.claim_funds(preimage_b);
948+
expect_payment_claimed!(nodes[1], payment_hash_b, 100_000_000);
949+
check_added_monitors(&nodes[1], 2);
950+
951+
let err = "Channel force-closed".to_string();
952+
953+
// Force-close and fetch node B's commitment transaction and the transaction claiming the first
954+
// two HTLCs.
955+
nodes[1].node.force_close_broadcasting_latest_txn(&chan_id, &node_a_id, err).unwrap();
956+
check_closed_broadcast(&nodes[1], 1, true);
957+
check_added_monitors(&nodes[1], 1);
958+
let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true) };
959+
check_closed_event(&nodes[1], 1, reason, false, &[node_a_id], 10_000_000);
960+
961+
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
962+
assert_eq!(txn.len(), 1);
963+
let commitment_tx = txn.pop().unwrap();
964+
check_spends!(commitment_tx, funding_tx);
965+
966+
mine_transaction(&nodes[0], &commitment_tx);
967+
check_closed_broadcast(&nodes[0], 1, true);
968+
let reason = ClosureReason::CommitmentTxConfirmed;
969+
check_closed_event(&nodes[0], 1, reason, false, &[node_b_id], 10_000_000);
970+
check_added_monitors(&nodes[0], 1);
971+
972+
mine_transaction(&nodes[1], &commitment_tx);
973+
let mut bump_events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
974+
assert_eq!(bump_events.len(), 1);
975+
match bump_events.pop().unwrap() {
976+
Event::BumpTransaction(bump_event) => {
977+
nodes[1].bump_tx_handler.handle_event(&bump_event);
978+
},
979+
ev => panic!("Unexpected event {ev:?}"),
980+
}
981+
982+
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
983+
if nodes[1].connect_style.borrow().updates_best_block_first() {
984+
assert_eq!(txn.len(), 2, "{txn:?}");
985+
check_spends!(txn[0], funding_tx);
986+
} else {
987+
assert_eq!(txn.len(), 1, "{txn:?}");
988+
}
989+
let bs_htlc_spend_tx = txn.pop().unwrap();
990+
check_spends!(bs_htlc_spend_tx, commitment_tx, coinbase_tx);
991+
992+
// Now connect blocks until the first HTLC expires
993+
assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
994+
connect_blocks(&nodes[0], TEST_FINAL_CLTV - 2);
995+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
996+
assert_eq!(txn.len(), 1);
997+
let as_first_htlc_spend_tx = txn.pop().unwrap();
998+
check_spends!(as_first_htlc_spend_tx, commitment_tx);
999+
1000+
// But confirm B's dual-HTLC-claim transaction instead. A should now have nothing to broadcast
1001+
// as the third HTLC (if there is one) won't expire for another block.
1002+
mine_transaction(&nodes[0], &bs_htlc_spend_tx);
1003+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
1004+
assert_eq!(txn.len(), 0);
1005+
1006+
let sent_events = nodes[0].node.get_and_clear_pending_events();
1007+
assert_eq!(sent_events.len(), 4, "{sent_events:?}");
1008+
let mut found_expected_events = [false, false, false, false];
1009+
for event in sent_events {
1010+
match event {
1011+
Event::PaymentSent { payment_hash, .. }|Event::PaymentPathSuccessful { payment_hash: Some(payment_hash), .. } => {
1012+
let path_success = matches!(event, Event::PaymentPathSuccessful { .. });
1013+
if payment_hash == payment_hash_a {
1014+
found_expected_events[0 + if path_success { 1 } else { 0 }] = true;
1015+
} else if payment_hash == payment_hash_b {
1016+
found_expected_events[2 + if path_success { 1 } else { 0 }] = true;
1017+
} else {
1018+
panic!("Wrong payment hash {event:?}");
1019+
}
1020+
},
1021+
_ => panic!("Wrong event {event:?}"),
1022+
}
1023+
}
1024+
assert_eq!(found_expected_events, [true, true, true, true]);
1025+
1026+
// However if we connect one more block the third HTLC will time out and A should claim it
1027+
connect_blocks(&nodes[0], 1);
1028+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
1029+
if use_third_htlc {
1030+
assert_eq!(txn.len(), 1);
1031+
let as_third_htlc_spend_tx = txn.pop().unwrap();
1032+
check_spends!(as_third_htlc_spend_tx, commitment_tx);
1033+
// Previously, node A would generate a bogus claim here, trying to claim both HTLCs B and C in
1034+
// one transaction, so we check that the single input being spent was not already spent in node
1035+
// B's HTLC claim transaction.
1036+
assert_eq!(as_third_htlc_spend_tx.input.len(), 1, "{as_third_htlc_spend_tx:?}");
1037+
for spent_input in bs_htlc_spend_tx.input.iter() {
1038+
let third_htlc_vout = as_third_htlc_spend_tx.input[0].previous_output.vout;
1039+
assert_ne!(third_htlc_vout, spent_input.previous_output.vout);
1040+
}
1041+
1042+
mine_transaction(&nodes[0], &as_third_htlc_spend_tx);
1043+
1044+
assert_eq!(&nodes[0].node.get_and_clear_pending_events(), &[]);
1045+
} else {
1046+
assert_eq!(txn.len(), 0);
1047+
// Connect a block so that both cases end with the same height
1048+
connect_blocks(&nodes[0], 1);
1049+
}
1050+
1051+
// At this point all HTLCs have been resolved and no further transactions should be generated.
1052+
// We connect blocks until one block before `bs_htlc_spend_tx` reaches `ANTI_REORG_DELAY`
1053+
// confirmations.
1054+
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 4);
1055+
let mut txn = nodes[0].tx_broadcaster.txn_broadcast();
1056+
assert_eq!(txn.len(), 0);
1057+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
1058+
1059+
if reorg_out {
1060+
// Reorg out bs_htlc_spend_tx, letting node A the claim all the HTLCs instead.
1061+
disconnect_blocks(&nodes[0], ANTI_REORG_DELAY - 2);
1062+
assert_eq!(nodes[0].tx_broadcaster.txn_broadcast().len(), 0);
1063+
1064+
// As soon as bs_htlc_spend_tx is disconnected
1065+
disconnect_blocks(&nodes[0], 1);
1066+
let balances = nodes[0].chain_monitor.chain_monitor.get_claimable_balances(&[]);
1067+
assert_eq!(balances.len(), if use_third_htlc { 3 } else { 2 });
1068+
1069+
connect_blocks(&nodes[0], 100);
1070+
let txn = nodes[0].tx_broadcaster.txn_broadcast();
1071+
let mut claiming_outpoints = new_hash_set();
1072+
for tx in txn.iter() {
1073+
for input in tx.input.iter() {
1074+
claiming_outpoints.insert(input.previous_output);
1075+
}
1076+
}
1077+
assert_eq!(claiming_outpoints.len(), if use_third_htlc { 3 } else { 2 });
1078+
} else {
1079+
// Connect a final block, which puts `bs_htlc_spend_tx` at `ANTI_REORG_DELAY` and we wipe
1080+
// the claimable balances for the first two HTLCs.
1081+
connect_blocks(&nodes[0], 1);
1082+
let balances = nodes[0].chain_monitor.chain_monitor.get_claimable_balances(&[]);
1083+
assert_eq!(balances.len(), if use_third_htlc { 1 } else { 0 });
1084+
1085+
// Connect two more blocks to get `as_third_htlc_spend_tx` to `ANTI_REORG_DELAY` confs.
1086+
connect_blocks(&nodes[0], 2);
1087+
if use_third_htlc {
1088+
let failed_events = nodes[0].node.get_and_clear_pending_events();
1089+
assert_eq!(failed_events.len(), 2);
1090+
let mut found_expected_events = [false, false];
1091+
for event in failed_events {
1092+
match event {
1093+
Event::PaymentFailed { payment_hash: Some(payment_hash), .. }|Event::PaymentPathFailed { payment_hash, .. } => {
1094+
let path_failed = matches!(event, Event::PaymentPathFailed { .. });
1095+
if payment_hash == payment_hash_c {
1096+
found_expected_events[if path_failed { 1 } else { 0 }] = true;
1097+
} else {
1098+
panic!("Wrong payment hash {event:?}");
1099+
}
1100+
},
1101+
_ => panic!("Wrong event {event:?}"),
1102+
}
1103+
}
1104+
assert_eq!(found_expected_events, [true, true]);
1105+
}
1106+
1107+
// Further, there should be no spendable balances.
1108+
assert!(nodes[0].chain_monitor.chain_monitor.get_claimable_balances(&[]).is_empty());
1109+
}
1110+
}
1111+
1112+
#[test]
1113+
fn test_split_htlc_expiry_tracking() {
1114+
do_test_split_htlc_expiry_tracking(true, true);
1115+
do_test_split_htlc_expiry_tracking(false, true);
1116+
do_test_split_htlc_expiry_tracking(true, false);
1117+
do_test_split_htlc_expiry_tracking(false, false);
1118+
}

0 commit comments

Comments
 (0)