Skip to content

Commit 3ed04aa

Browse files
committed
f: Expand test coverage
1 parent 2f1244f commit 3ed04aa

File tree

3 files changed

+265
-17
lines changed

3 files changed

+265
-17
lines changed

lightning/src/ln/functional_test_utils.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,30 @@ pub fn provide_anchor_reserves<'a, 'b, 'c>(nodes: &[Node<'a, 'b, 'c>]) -> Transa
424424
tx
425425
}
426426

427+
pub fn provide_anchor_utxo_reserves<'a, 'b, 'c>(
428+
nodes: &[Node<'a, 'b, 'c>], utxos: usize, amount: Amount,
429+
) -> Transaction {
430+
let mut output = Vec::with_capacity(nodes.len());
431+
for node in nodes {
432+
let script_pubkey = node.wallet_source.get_change_script().unwrap();
433+
for _ in 0..utxos {
434+
output.push(TxOut { value: amount, script_pubkey: script_pubkey.clone() });
435+
}
436+
}
437+
let tx = Transaction {
438+
version: TxVersion::TWO,
439+
lock_time: LockTime::from_height(nodes[0].best_block_info().1).unwrap(),
440+
input: vec![TxIn { ..Default::default() }],
441+
output,
442+
};
443+
let height = nodes[0].best_block_info().1 + 1;
444+
let block = create_dummy_block(nodes[0].best_block_hash(), height, vec![tx.clone()]);
445+
for node in nodes {
446+
do_connect_block_with_consistency_checks(node, block.clone(), false);
447+
}
448+
tx
449+
}
450+
427451
pub fn disconnect_blocks<'a, 'b, 'c, 'd>(node: &'a Node<'b, 'c, 'd>, count: u32) {
428452
call_claimable_balances(node);
429453
eprintln!(

lightning/src/ln/zero_fee_commitment_tests.rs

Lines changed: 237 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
use crate::events::{ClosureReason, Event};
22
use crate::ln::chan_utils;
3+
use crate::ln::chan_utils::{
4+
BASE_INPUT_WEIGHT, BASE_TX_SIZE, EMPTY_SCRIPT_SIG_WEIGHT, EMPTY_WITNESS_WEIGHT,
5+
P2WSH_TXOUT_WEIGHT, SEGWIT_MARKER_FLAG_WEIGHT, TRUC_CHILD_MAX_WEIGHT,
6+
};
37
use crate::ln::functional_test_utils::*;
48
use crate::ln::msgs::BaseMessageHandler;
9+
use crate::prelude::*;
10+
11+
use bitcoin::constants::WITNESS_SCALE_FACTOR;
12+
use bitcoin::Amount;
513

614
#[test]
715
fn test_p2a_anchor_values_under_trims_and_rounds() {
@@ -97,6 +105,20 @@ fn test_p2a_anchor_values_under_trims_and_rounds() {
97105

98106
#[test]
99107
fn test_htlc_claim_chunking() {
108+
// Assert we split an overall HolderHTLCOutput claim into constituent
109+
// HTLC claim transactions such that each transaction is under TRUC_MAX_WEIGHT.
110+
// Assert we reduce the number of HTLCs in a batch transaction by 2 if the
111+
// coin selection algorithm fails to meet the target weight.
112+
// Assert the claim_id of the first batch transaction is the claim
113+
// id assigned to the overall claim.
114+
// Assert we give up bumping a HTLC transaction once the batch size is
115+
// 0 or smaller.
116+
//
117+
// Route a bunch of HTLCs, force close the channel, assert two HTLC transactions
118+
// get broadcasted, confirm only one of them, assert a new one gets broadcasted
119+
// to sweep the remaining HTLCs, confirm a block without that transaction while
120+
// dropping all available coin selection utxos, and give up creating another
121+
// HTLC transaction when handling the third HTLC bump.
100122
let chanmon_cfgs = create_chanmon_cfgs(2);
101123
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
102124
let mut user_cfg = test_default_channel_config();
@@ -109,7 +131,7 @@ fn test_htlc_claim_chunking() {
109131
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &configs);
110132
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
111133

112-
let coinbase_tx = provide_anchor_reserves(&nodes);
134+
let coinbase_tx = provide_anchor_utxo_reserves(&nodes, 50, Amount::from_sat(500));
113135

114136
const CHAN_CAPACITY: u64 = 10_000_000;
115137
let (_, _, chan_id, _funding_tx) = create_announced_chan_between_nodes_with_value(
@@ -160,10 +182,10 @@ fn test_htlc_claim_chunking() {
160182
check_spends!(htlc_claims[0], node_1_commit_tx[0], coinbase_tx);
161183
check_spends!(htlc_claims[1], node_1_commit_tx[0], coinbase_tx);
162184

163-
assert_eq!(htlc_claims[0].input.len(), 60);
164-
assert_eq!(htlc_claims[0].output.len(), 60);
165-
assert_eq!(htlc_claims[1].input.len(), 17);
166-
assert_eq!(htlc_claims[1].output.len(), 17);
185+
assert_eq!(htlc_claims[0].input.len(), 71);
186+
assert_eq!(htlc_claims[0].output.len(), 51);
187+
assert_eq!(htlc_claims[1].input.len(), 34);
188+
assert_eq!(htlc_claims[1].output.len(), 24);
167189

168190
check_closed_broadcast!(nodes[0], true);
169191
check_added_monitors!(nodes[0], 1);
@@ -201,19 +223,217 @@ fn test_htlc_claim_chunking() {
201223

202224
let fresh_htlc_claims = nodes[1].tx_broadcaster.txn_broadcast();
203225
assert_eq!(fresh_htlc_claims.len(), 1);
204-
check_spends!(fresh_htlc_claims[0], node_1_commit_tx[0], htlc_claims[0]);
205-
assert_eq!(fresh_htlc_claims[0].input.len(), 17);
206-
assert_eq!(fresh_htlc_claims[0].output.len(), 17);
226+
check_spends!(fresh_htlc_claims[0], node_1_commit_tx[0], coinbase_tx);
227+
// We are targeting a higher feerate here,
228+
// so we need more utxos here compared to `htlc_claims[1]` above.
229+
assert_eq!(fresh_htlc_claims[0].input.len(), 37);
230+
assert_eq!(fresh_htlc_claims[0].output.len(), 25);
231+
232+
let log_entries = nodes[1].logger.lines.lock().unwrap();
233+
let batch_tx_id_assignments: Vec<_> = log_entries
234+
.keys()
235+
.map(|key| &key.1)
236+
.filter(|log_msg| log_msg.contains("Batch transaction assigned to UTXO id"))
237+
.collect();
238+
assert_eq!(batch_tx_id_assignments.len(), 7);
239+
240+
let mut unique_claim_ids: Vec<(&str, u8)> = Vec::new();
241+
for claim_id in batch_tx_id_assignments
242+
.iter()
243+
.map(|assignment| assignment.split_whitespace().nth(6).unwrap())
244+
{
245+
if let Some((_, count)) = unique_claim_ids.iter_mut().find(|(id, _count)| &claim_id == id) {
246+
*count += 1;
247+
} else {
248+
unique_claim_ids.push((claim_id, 1));
249+
}
250+
}
251+
unique_claim_ids.sort_unstable_by_key(|(_id, count)| *count);
252+
assert_eq!(unique_claim_ids.len(), 2);
253+
let (og_claim_id, og_claim_id_count) = unique_claim_ids.pop().unwrap();
254+
assert_eq!(og_claim_id_count, 6);
255+
assert_eq!(unique_claim_ids.pop().unwrap().1, 1);
207256

208-
let log_entries = &nodes[1].logger.lines.lock().unwrap();
209-
let mut keys: Vec<_> = log_entries
257+
let handling_htlc_bumps: Vec<_> = log_entries
210258
.keys()
211-
.filter(|key| key.1.contains("Batch transaction assigned to UTXO id"))
212-
.map(|key| key.1.split_whitespace().nth(6))
259+
.map(|key| &key.1)
260+
.filter(|log_msg| log_msg.contains("Handling HTLC bump"))
261+
.map(|log_msg| {
262+
log_msg
263+
.split_whitespace()
264+
.nth(5)
265+
.unwrap()
266+
.trim_matches(|c: char| c.is_ascii_punctuation())
267+
})
213268
.collect();
214-
assert_eq!(keys.len(), 3);
215-
keys.sort_unstable();
216-
// Assert that the fresh HTLC claim has the same `ClaimId` as the first chunk of the first HTLC claim.
217-
// Also assert that the second chunk in the first HTLC claim has a different `ClaimId` as the first chunk.
218-
assert!(keys[0] == keys[1] && keys[1] != keys[2] || keys[0] != keys[1] && keys[1] == keys[2]);
269+
assert_eq!(handling_htlc_bumps.len(), 2);
270+
assert_eq!(handling_htlc_bumps[0], og_claim_id);
271+
assert_eq!(handling_htlc_bumps[1], og_claim_id);
272+
273+
let mut batch_sizes: Vec<u8> = batch_tx_id_assignments
274+
.iter()
275+
.map(|assignment| assignment.split_whitespace().nth(8).unwrap().parse().unwrap())
276+
.collect();
277+
batch_sizes.sort_unstable();
278+
batch_sizes.reverse();
279+
assert_eq!(batch_sizes.len(), 7);
280+
assert_eq!(batch_sizes.pop().unwrap(), 24);
281+
assert_eq!(batch_sizes.pop().unwrap(), 24);
282+
for i in (51..=59).step_by(2) {
283+
assert_eq!(batch_sizes.pop().unwrap(), i);
284+
}
285+
drop(log_entries);
286+
287+
nodes[1].wallet_source.clear_utxos();
288+
nodes[1].chain_monitor.chain_monitor.rebroadcast_pending_claims();
289+
290+
let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
291+
assert_eq!(events.len(), 1);
292+
match events.pop().unwrap() {
293+
Event::BumpTransaction(bump_event) => {
294+
nodes[1].bump_tx_handler.handle_event(&bump_event);
295+
},
296+
_ => panic!("Unexpected event"),
297+
}
298+
299+
nodes[1].logger.assert_log(
300+
"lightning::events::bump_transaction",
301+
format!(
302+
"Failed bumping HTLC transaction fee for commitment {}",
303+
node_1_commit_tx[0].compute_txid()
304+
),
305+
1,
306+
);
307+
}
308+
309+
#[test]
310+
fn test_anchor_tx_too_big() {
311+
// Assert all V3 anchor tx transactions are below TRUC_CHILD_MAX_WEIGHT.
312+
//
313+
// Provide a bunch of small utxos, fail to bump the commitment,
314+
// then provide a single big-value utxo, and successfully broadcast
315+
// the commitment.
316+
const FEERATE: u32 = 500;
317+
let chanmon_cfgs = create_chanmon_cfgs(2);
318+
{
319+
let mut feerate_lock = chanmon_cfgs[1].fee_estimator.sat_per_kw.lock().unwrap();
320+
*feerate_lock = FEERATE;
321+
}
322+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
323+
let mut user_cfg = test_default_channel_config();
324+
user_cfg.channel_handshake_config.our_htlc_minimum_msat = 1;
325+
user_cfg.channel_handshake_config.negotiate_anchor_zero_fee_commitments = true;
326+
user_cfg.channel_handshake_config.our_max_accepted_htlcs = 114;
327+
user_cfg.manually_accept_inbound_channels = true;
328+
329+
let configs = [Some(user_cfg.clone()), Some(user_cfg)];
330+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &configs);
331+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
332+
333+
let node_a_id = nodes[0].node.get_our_node_id();
334+
335+
let _coinbase_tx_a = provide_anchor_utxo_reserves(&nodes, 50, Amount::from_sat(500));
336+
337+
const CHAN_CAPACITY: u64 = 10_000_000;
338+
let (_, _, chan_id, _funding_tx) = create_announced_chan_between_nodes_with_value(
339+
&nodes,
340+
0,
341+
1,
342+
CHAN_CAPACITY,
343+
(CHAN_CAPACITY / 2) * 1000,
344+
);
345+
346+
let mut node_1_preimages = Vec::new();
347+
const NONDUST_HTLC_AMT_MSAT: u64 = 1_000_000;
348+
for _ in 0..50 {
349+
let (preimage, payment_hash, _, _) =
350+
route_payment(&nodes[0], &[&nodes[1]], NONDUST_HTLC_AMT_MSAT);
351+
node_1_preimages.push((preimage, payment_hash));
352+
}
353+
let commitment_tx = get_local_commitment_txn!(nodes[1], chan_id).pop().unwrap();
354+
let commitment_txid = commitment_tx.compute_txid();
355+
356+
let message = "Channel force-closed".to_owned();
357+
nodes[1]
358+
.node
359+
.force_close_broadcasting_latest_txn(&chan_id, &node_a_id, message.clone())
360+
.unwrap();
361+
check_added_monitors(&nodes[1], 1);
362+
check_closed_broadcast!(nodes[1], true);
363+
364+
let reason = ClosureReason::HolderForceClosed { broadcasted_latest_txn: Some(true), message };
365+
check_closed_event!(nodes[1], 1, reason, [node_a_id], CHAN_CAPACITY);
366+
367+
let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
368+
assert_eq!(events.len(), 1);
369+
match events.pop().unwrap() {
370+
Event::BumpTransaction(bump_event) => {
371+
nodes[1].bump_tx_handler.handle_event(&bump_event);
372+
},
373+
_ => panic!("Unexpected event"),
374+
}
375+
assert!(nodes[1].tx_broadcaster.txn_broadcast().is_empty());
376+
let max_coin_selection_weight = TRUC_CHILD_MAX_WEIGHT
377+
- BASE_TX_SIZE * WITNESS_SCALE_FACTOR as u64
378+
- SEGWIT_MARKER_FLAG_WEIGHT
379+
- BASE_INPUT_WEIGHT
380+
- EMPTY_SCRIPT_SIG_WEIGHT
381+
- EMPTY_WITNESS_WEIGHT
382+
- P2WSH_TXOUT_WEIGHT;
383+
nodes[1].logger.assert_log(
384+
"lightning::events::bump_transaction",
385+
format!(
386+
"Insufficient funds to meet target feerate {} sat/kW while remaining under {} WU",
387+
FEERATE, max_coin_selection_weight
388+
),
389+
4,
390+
);
391+
nodes[1].logger.assert_log(
392+
"lightning::events::bump_transaction",
393+
format!("Failed bumping commitment transaction fee for {}", commitment_txid),
394+
1,
395+
);
396+
397+
let coinbase_tx_b = provide_anchor_reserves(&nodes);
398+
399+
nodes[1].chain_monitor.chain_monitor.rebroadcast_pending_claims();
400+
401+
let mut events = nodes[1].chain_monitor.chain_monitor.get_and_clear_pending_events();
402+
assert_eq!(events.len(), 1);
403+
match events.pop().unwrap() {
404+
Event::BumpTransaction(bump_event) => {
405+
nodes[1].bump_tx_handler.handle_event(&bump_event);
406+
},
407+
_ => panic!("Unexpected event"),
408+
}
409+
let txns = nodes[1].tx_broadcaster.txn_broadcast();
410+
assert_eq!(txns.len(), 2);
411+
check_spends!(txns[1], txns[0], coinbase_tx_b);
412+
assert!(txns[1].weight().to_wu() < TRUC_CHILD_MAX_WEIGHT);
413+
414+
assert_eq!(txns[0].compute_txid(), commitment_txid);
415+
assert_eq!(txns[1].input.len(), 2);
416+
assert_eq!(txns[1].output.len(), 1);
417+
nodes[1].logger.assert_log(
418+
"lightning::events::bump_transaction",
419+
format!(
420+
"Insufficient funds to meet target feerate {} sat/kW while remaining under {} WU",
421+
FEERATE, max_coin_selection_weight
422+
),
423+
4,
424+
);
425+
nodes[1].logger.assert_log(
426+
"lightning::events::bump_transaction",
427+
format!("Failed bumping commitment transaction fee for {}", txns[0].compute_txid()),
428+
1,
429+
);
430+
nodes[1].logger.assert_log(
431+
"lightning::events::bump_transaction",
432+
format!(
433+
"Broadcasting anchor transaction {} to bump channel close with txid {}",
434+
txns[1].compute_txid(),
435+
txns[0].compute_txid()
436+
),
437+
1,
438+
);
219439
}

lightning/src/util/test_utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,6 +2155,10 @@ impl TestWalletSource {
21552155
self.utxos.lock().unwrap().retain(|utxo| utxo.outpoint != outpoint);
21562156
}
21572157

2158+
pub fn clear_utxos(&self) {
2159+
self.utxos.lock().unwrap().clear();
2160+
}
2161+
21582162
pub fn sign_tx(
21592163
&self, mut tx: Transaction,
21602164
) -> Result<Transaction, bitcoin::sighash::P2wpkhError> {

0 commit comments

Comments
 (0)