11use crate :: events:: { ClosureReason , Event } ;
22use 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+ } ;
37use crate :: ln:: functional_test_utils:: * ;
48use crate :: ln:: msgs:: BaseMessageHandler ;
9+ use crate :: prelude:: * ;
10+
11+ use bitcoin:: constants:: WITNESS_SCALE_FACTOR ;
12+ use bitcoin:: Amount ;
513
614#[ test]
715fn test_p2a_anchor_values_under_trims_and_rounds ( ) {
@@ -97,6 +105,20 @@ fn test_p2a_anchor_values_under_trims_and_rounds() {
97105
98106#[ test]
99107fn 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}
0 commit comments