@@ -6364,3 +6364,132 @@ fn test_bump_penalty_txn_on_revoked_commitment() {
6364
6364
nodes[ 1 ] . node . get_and_clear_pending_events ( ) ;
6365
6365
nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
6366
6366
}
6367
+
6368
+ #[ test]
6369
+ fn test_bump_penalty_txn_on_revoked_htlcs ( ) {
6370
+ // In case of penalty txn with too low feerates for getting into mempools, RBF-bump them to sure
6371
+ // we're able to claim outputs on revoked HTLC transactions before timelocks expiration
6372
+
6373
+ let nodes = create_network ( 2 , & [ None , None ] ) ;
6374
+
6375
+ let chan = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1000000 , 59000000 , LocalFeatures :: new ( ) , LocalFeatures :: new ( ) ) ;
6376
+ // Lock HTLC in both directions
6377
+ let payment_preimage = route_payment ( & nodes[ 0 ] , & vec ! ( & nodes[ 1 ] ) [ ..] , 3_000_000 ) . 0 ;
6378
+ route_payment ( & nodes[ 1 ] , & vec ! ( & nodes[ 0 ] ) [ ..] , 3_000_000 ) . 0 ;
6379
+
6380
+ let revoked_local_txn = nodes[ 1 ] . node . channel_state . lock ( ) . unwrap ( ) . by_id . get ( & chan. 2 ) . unwrap ( ) . last_local_commitment_txn . clone ( ) ;
6381
+ assert_eq ! ( revoked_local_txn[ 0 ] . input. len( ) , 1 ) ;
6382
+ assert_eq ! ( revoked_local_txn[ 0 ] . input[ 0 ] . previous_output. txid, chan. 3 . txid( ) ) ;
6383
+
6384
+ // Revoke local commitment tx
6385
+ claim_payment ( & nodes[ 0 ] , & vec ! ( & nodes[ 1 ] ) [ ..] , payment_preimage, 3_000_000 ) ;
6386
+
6387
+ let header = BlockHeader { version : 0x20000000 , prev_blockhash : Default :: default ( ) , merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6388
+ // B will generate both revoked HTLC-timeout/HTLC-preimage txn from revoked commitment tx
6389
+ nodes[ 1 ] . block_notifier . block_connected ( & Block { header, txdata : vec ! [ revoked_local_txn[ 0 ] . clone( ) ] } , 1 ) ;
6390
+ check_closed_broadcast ! ( nodes[ 1 ] ) ;
6391
+
6392
+ let revoked_htlc_txn = nodes[ 1 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6393
+ assert_eq ! ( revoked_htlc_txn. len( ) , 6 ) ;
6394
+ if revoked_htlc_txn[ 0 ] . input [ 0 ] . witness . last ( ) . unwrap ( ) . len ( ) == ACCEPTED_HTLC_SCRIPT_WEIGHT {
6395
+ assert_eq ! ( revoked_htlc_txn[ 0 ] . input. len( ) , 1 ) ;
6396
+ check_spends ! ( revoked_htlc_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6397
+ assert_eq ! ( revoked_htlc_txn[ 1 ] . input. len( ) , 1 ) ;
6398
+ assert_eq ! ( revoked_htlc_txn[ 1 ] . input[ 0 ] . witness. last( ) . unwrap( ) . len( ) , OFFERED_HTLC_SCRIPT_WEIGHT ) ;
6399
+ check_spends ! ( revoked_htlc_txn[ 1 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6400
+ } else {
6401
+ assert_eq ! ( revoked_htlc_txn[ 1 ] . input. len( ) , 1 ) ;
6402
+ check_spends ! ( revoked_htlc_txn[ 1 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6403
+ assert_eq ! ( revoked_htlc_txn[ 0 ] . input. len( ) , 1 ) ;
6404
+ assert_eq ! ( revoked_htlc_txn[ 0 ] . input[ 0 ] . witness. last( ) . unwrap( ) . len( ) , OFFERED_HTLC_SCRIPT_WEIGHT ) ;
6405
+ check_spends ! ( revoked_htlc_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6406
+ }
6407
+
6408
+ // Broadcast set of revoked txn on A
6409
+ let header_129 = connect_blocks ( & nodes[ 0 ] . block_notifier , 128 , 1 , true , header. bitcoin_hash ( ) ) ;
6410
+ let header_130 = BlockHeader { version : 0x20000000 , prev_blockhash : header_129, merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6411
+ nodes[ 0 ] . block_notifier . block_connected ( & Block { header : header_130, txdata : vec ! [ revoked_local_txn[ 0 ] . clone( ) , revoked_htlc_txn[ 0 ] . clone( ) , revoked_htlc_txn[ 1 ] . clone( ) ] } , 130 ) ;
6412
+ let first;
6413
+ let second;
6414
+ let feerate_1;
6415
+ let feerate_2;
6416
+ {
6417
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6418
+ assert_eq ! ( node_txn. len( ) , 9 ) ; // 3 penalty txn on revoked commitment tx * 2 (block-rescan) + A commitment tx + 2 penalty tnx on revoked HTLC txn
6419
+ // Verify claim tx are spending revoked HTLC txn
6420
+ assert_eq ! ( node_txn[ 7 ] . input. len( ) , 1 ) ;
6421
+ assert_eq ! ( node_txn[ 7 ] . output. len( ) , 1 ) ;
6422
+ check_spends ! ( node_txn[ 7 ] , revoked_htlc_txn[ 0 ] . clone( ) ) ;
6423
+ first = node_txn[ 7 ] . txid ( ) ;
6424
+ assert_eq ! ( node_txn[ 8 ] . input. len( ) , 1 ) ;
6425
+ assert_eq ! ( node_txn[ 8 ] . output. len( ) , 1 ) ;
6426
+ check_spends ! ( node_txn[ 8 ] , revoked_htlc_txn[ 1 ] . clone( ) ) ;
6427
+ second = node_txn[ 8 ] . txid ( ) ;
6428
+ // Store both feerates for later comparison
6429
+ let fee_1 = revoked_htlc_txn[ 0 ] . output [ 0 ] . value - node_txn[ 7 ] . output [ 0 ] . value ;
6430
+ feerate_1 = fee_1 * 1000 / node_txn[ 7 ] . get_weight ( ) as u64 ;
6431
+ let fee_2 = revoked_htlc_txn[ 1 ] . output [ 0 ] . value - node_txn[ 8 ] . output [ 0 ] . value ;
6432
+ feerate_2 = fee_2 * 1000 / node_txn[ 8 ] . get_weight ( ) as u64 ;
6433
+ node_txn. clear ( ) ;
6434
+ }
6435
+
6436
+ // Connect three more block to see if bumped penalty are issued for HTLC txn
6437
+ let header_133 = connect_blocks ( & nodes[ 0 ] . block_notifier , 3 , 130 , true , header_130. bitcoin_hash ( ) ) ;
6438
+ let node_txn = {
6439
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6440
+ assert_eq ! ( node_txn. len( ) , 4 ) ; // 2 first tx : bumped claiming txn on Revoked Commitment Tx + 2 last tx : bumped claiming txn on Revoked HTLCs Txn
6441
+ check_spends ! ( node_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6442
+ check_spends ! ( node_txn[ 1 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6443
+ assert_eq ! ( node_txn[ 2 ] . input. len( ) , 1 ) ;
6444
+ assert_eq ! ( node_txn[ 2 ] . output. len( ) , 1 ) ;
6445
+ assert_eq ! ( node_txn[ 3 ] . input. len( ) , 1 ) ;
6446
+ assert_eq ! ( node_txn[ 3 ] . output. len( ) , 1 ) ;
6447
+ // Verify bumped tx is different and 25% bump heuristic
6448
+ if node_txn[ 2 ] . input [ 0 ] . previous_output . txid == revoked_htlc_txn[ 0 ] . txid ( ) {
6449
+ check_spends ! ( node_txn[ 2 ] , revoked_htlc_txn[ 0 ] . clone( ) ) ;
6450
+ assert_ne ! ( first, node_txn[ 2 ] . txid( ) ) ;
6451
+ let fee = revoked_htlc_txn[ 0 ] . output [ 0 ] . value - node_txn[ 2 ] . output [ 0 ] . value ;
6452
+ let new_feerate = fee * 1000 / node_txn[ 2 ] . get_weight ( ) as u64 ;
6453
+ assert ! ( new_feerate * 100 > feerate_1 * 125 ) ;
6454
+
6455
+ check_spends ! ( node_txn[ 3 ] , revoked_htlc_txn[ 1 ] . clone( ) ) ;
6456
+ assert_ne ! ( second, node_txn[ 3 ] . txid( ) ) ;
6457
+ let fee = revoked_htlc_txn[ 1 ] . output [ 0 ] . value - node_txn[ 3 ] . output [ 0 ] . value ;
6458
+ let new_feerate = fee * 1000 / node_txn[ 3 ] . get_weight ( ) as u64 ;
6459
+ assert ! ( new_feerate * 100 > feerate_2 * 125 ) ;
6460
+ } else if node_txn[ 2 ] . input [ 0 ] . previous_output . txid == revoked_htlc_txn[ 1 ] . txid ( ) {
6461
+ check_spends ! ( node_txn[ 2 ] , revoked_htlc_txn[ 1 ] . clone( ) ) ;
6462
+ assert_ne ! ( second, node_txn[ 2 ] . txid( ) ) ;
6463
+ let fee = revoked_htlc_txn[ 1 ] . output [ 0 ] . value - node_txn[ 2 ] . output [ 0 ] . value ;
6464
+ let new_feerate = fee * 1000 / node_txn[ 2 ] . get_weight ( ) as u64 ;
6465
+ assert ! ( new_feerate * 100 > feerate_2 * 125 ) ;
6466
+
6467
+ check_spends ! ( node_txn[ 3 ] , revoked_htlc_txn[ 0 ] . clone( ) ) ;
6468
+ assert_ne ! ( first, node_txn[ 3 ] . txid( ) ) ;
6469
+ let fee = revoked_htlc_txn[ 0 ] . output [ 0 ] . value - node_txn[ 3 ] . output [ 0 ] . value ;
6470
+ let new_feerate = fee * 1000 / node_txn[ 3 ] . get_weight ( ) as u64 ;
6471
+ assert ! ( new_feerate * 100 > feerate_1 * 125 ) ;
6472
+ } else { assert ! ( false ) }
6473
+ let txn = vec ! [ node_txn[ 0 ] . clone( ) , node_txn[ 1 ] . clone( ) , node_txn[ 2 ] . clone( ) , node_txn[ 3 ] . clone( ) ] ;
6474
+ node_txn. clear ( ) ;
6475
+ txn
6476
+ } ;
6477
+ // Broadcast claim txn and confirm blocks to avoid further bumps on this outputs
6478
+ let header_134 = BlockHeader { version : 0x20000000 , prev_blockhash : header_133, merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6479
+ nodes[ 0 ] . block_notifier . block_connected ( & Block { header : header_134, txdata : node_txn } , 134 ) ;
6480
+ connect_blocks ( & nodes[ 0 ] . block_notifier , 6 , 134 , true , header_134. bitcoin_hash ( ) ) ;
6481
+ {
6482
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6483
+ node_txn. clear ( ) ;
6484
+ }
6485
+
6486
+ // Connect few more blocks and check only penalty transaction for to_local output have been issued
6487
+ connect_blocks ( & nodes[ 0 ] . block_notifier , 5 , 140 , true , header_134. bitcoin_hash ( ) ) ;
6488
+ {
6489
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6490
+ assert_eq ! ( node_txn. len( ) , 1 ) ;
6491
+ check_spends ! ( node_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6492
+ node_txn. clear ( ) ;
6493
+ }
6494
+ check_closed_broadcast ! ( nodes[ 0 ] ) ;
6495
+ }
0 commit comments