@@ -6424,3 +6424,132 @@ fn test_bump_penalty_txn_on_revoked_commitment() {
6424
6424
nodes[ 1 ] . node . get_and_clear_pending_events ( ) ;
6425
6425
nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
6426
6426
}
6427
+
6428
+ #[ test]
6429
+ fn test_bump_penalty_txn_on_revoked_htlcs ( ) {
6430
+ // In case of penalty txn with too low feerates for getting into mempools, RBF-bump them to sure
6431
+ // we're able to claim outputs on revoked HTLC transactions before timelocks expiration
6432
+
6433
+ let nodes = create_network ( 2 , & [ None , None ] ) ;
6434
+
6435
+ let chan = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1000000 , 59000000 , LocalFeatures :: new ( ) , LocalFeatures :: new ( ) ) ;
6436
+ // Lock HTLC in both directions
6437
+ let payment_preimage = route_payment ( & nodes[ 0 ] , & vec ! ( & nodes[ 1 ] ) [ ..] , 3_000_000 ) . 0 ;
6438
+ route_payment ( & nodes[ 1 ] , & vec ! ( & nodes[ 0 ] ) [ ..] , 3_000_000 ) . 0 ;
6439
+
6440
+ let revoked_local_txn = nodes[ 1 ] . node . channel_state . lock ( ) . unwrap ( ) . by_id . get ( & chan. 2 ) . unwrap ( ) . last_local_commitment_txn . clone ( ) ;
6441
+ assert_eq ! ( revoked_local_txn[ 0 ] . input. len( ) , 1 ) ;
6442
+ assert_eq ! ( revoked_local_txn[ 0 ] . input[ 0 ] . previous_output. txid, chan. 3 . txid( ) ) ;
6443
+
6444
+ // Revoke local commitment tx
6445
+ claim_payment ( & nodes[ 0 ] , & vec ! ( & nodes[ 1 ] ) [ ..] , payment_preimage, 3_000_000 ) ;
6446
+
6447
+ let header = BlockHeader { version : 0x20000000 , prev_blockhash : Default :: default ( ) , merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6448
+ // B will generate both revoked HTLC-timeout/HTLC-preimage txn from revoked commitment tx
6449
+ nodes[ 1 ] . block_notifier . block_connected ( & Block { header, txdata : vec ! [ revoked_local_txn[ 0 ] . clone( ) ] } , 1 ) ;
6450
+ check_closed_broadcast ! ( nodes[ 1 ] ) ;
6451
+
6452
+ let revoked_htlc_txn = nodes[ 1 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6453
+ assert_eq ! ( revoked_htlc_txn. len( ) , 6 ) ;
6454
+ if revoked_htlc_txn[ 0 ] . input [ 0 ] . witness . last ( ) . unwrap ( ) . len ( ) == ACCEPTED_HTLC_SCRIPT_WEIGHT {
6455
+ assert_eq ! ( revoked_htlc_txn[ 0 ] . input. len( ) , 1 ) ;
6456
+ check_spends ! ( revoked_htlc_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6457
+ assert_eq ! ( revoked_htlc_txn[ 1 ] . input. len( ) , 1 ) ;
6458
+ assert_eq ! ( revoked_htlc_txn[ 1 ] . input[ 0 ] . witness. last( ) . unwrap( ) . len( ) , OFFERED_HTLC_SCRIPT_WEIGHT ) ;
6459
+ check_spends ! ( revoked_htlc_txn[ 1 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6460
+ } else {
6461
+ assert_eq ! ( revoked_htlc_txn[ 1 ] . input. len( ) , 1 ) ;
6462
+ check_spends ! ( revoked_htlc_txn[ 1 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6463
+ assert_eq ! ( revoked_htlc_txn[ 0 ] . input. len( ) , 1 ) ;
6464
+ assert_eq ! ( revoked_htlc_txn[ 0 ] . input[ 0 ] . witness. last( ) . unwrap( ) . len( ) , OFFERED_HTLC_SCRIPT_WEIGHT ) ;
6465
+ check_spends ! ( revoked_htlc_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6466
+ }
6467
+
6468
+ // Broadcast set of revoked txn on A
6469
+ let header_129 = connect_blocks ( & nodes[ 0 ] . block_notifier , 128 , 1 , true , header. bitcoin_hash ( ) ) ;
6470
+ let header_130 = BlockHeader { version : 0x20000000 , prev_blockhash : header_129, merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6471
+ 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 ) ;
6472
+ let first;
6473
+ let second;
6474
+ let feerate_1;
6475
+ let feerate_2;
6476
+ {
6477
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6478
+ 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
6479
+ // Verify claim tx are spending revoked HTLC txn
6480
+ assert_eq ! ( node_txn[ 7 ] . input. len( ) , 1 ) ;
6481
+ assert_eq ! ( node_txn[ 7 ] . output. len( ) , 1 ) ;
6482
+ check_spends ! ( node_txn[ 7 ] , revoked_htlc_txn[ 0 ] . clone( ) ) ;
6483
+ first = node_txn[ 7 ] . txid ( ) ;
6484
+ assert_eq ! ( node_txn[ 8 ] . input. len( ) , 1 ) ;
6485
+ assert_eq ! ( node_txn[ 8 ] . output. len( ) , 1 ) ;
6486
+ check_spends ! ( node_txn[ 8 ] , revoked_htlc_txn[ 1 ] . clone( ) ) ;
6487
+ second = node_txn[ 8 ] . txid ( ) ;
6488
+ // Store both feerates for later comparison
6489
+ let fee_1 = revoked_htlc_txn[ 0 ] . output [ 0 ] . value - node_txn[ 7 ] . output [ 0 ] . value ;
6490
+ feerate_1 = fee_1 * 1000 / node_txn[ 7 ] . get_weight ( ) as u64 ;
6491
+ let fee_2 = revoked_htlc_txn[ 1 ] . output [ 0 ] . value - node_txn[ 8 ] . output [ 0 ] . value ;
6492
+ feerate_2 = fee_2 * 1000 / node_txn[ 8 ] . get_weight ( ) as u64 ;
6493
+ node_txn. clear ( ) ;
6494
+ }
6495
+
6496
+ // Connect three more block to see if bumped penalty are issued for HTLC txn
6497
+ let header_133 = connect_blocks ( & nodes[ 0 ] . block_notifier , 3 , 130 , true , header_130. bitcoin_hash ( ) ) ;
6498
+ let node_txn = {
6499
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6500
+ 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
6501
+ check_spends ! ( node_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6502
+ check_spends ! ( node_txn[ 1 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6503
+ assert_eq ! ( node_txn[ 2 ] . input. len( ) , 1 ) ;
6504
+ assert_eq ! ( node_txn[ 2 ] . output. len( ) , 1 ) ;
6505
+ assert_eq ! ( node_txn[ 3 ] . input. len( ) , 1 ) ;
6506
+ assert_eq ! ( node_txn[ 3 ] . output. len( ) , 1 ) ;
6507
+ // Verify bumped tx is different and 25% bump heuristic
6508
+ if node_txn[ 2 ] . input [ 0 ] . previous_output . txid == revoked_htlc_txn[ 0 ] . txid ( ) {
6509
+ check_spends ! ( node_txn[ 2 ] , revoked_htlc_txn[ 0 ] . clone( ) ) ;
6510
+ assert_ne ! ( first, node_txn[ 2 ] . txid( ) ) ;
6511
+ let fee = revoked_htlc_txn[ 0 ] . output [ 0 ] . value - node_txn[ 2 ] . output [ 0 ] . value ;
6512
+ let new_feerate = fee * 1000 / node_txn[ 2 ] . get_weight ( ) as u64 ;
6513
+ assert ! ( new_feerate * 100 > feerate_1 * 125 ) ;
6514
+
6515
+ check_spends ! ( node_txn[ 3 ] , revoked_htlc_txn[ 1 ] . clone( ) ) ;
6516
+ assert_ne ! ( second, node_txn[ 3 ] . txid( ) ) ;
6517
+ let fee = revoked_htlc_txn[ 1 ] . output [ 0 ] . value - node_txn[ 3 ] . output [ 0 ] . value ;
6518
+ let new_feerate = fee * 1000 / node_txn[ 3 ] . get_weight ( ) as u64 ;
6519
+ assert ! ( new_feerate * 100 > feerate_2 * 125 ) ;
6520
+ } else if node_txn[ 2 ] . input [ 0 ] . previous_output . txid == revoked_htlc_txn[ 1 ] . txid ( ) {
6521
+ check_spends ! ( node_txn[ 2 ] , revoked_htlc_txn[ 1 ] . clone( ) ) ;
6522
+ assert_ne ! ( second, node_txn[ 2 ] . txid( ) ) ;
6523
+ let fee = revoked_htlc_txn[ 1 ] . output [ 0 ] . value - node_txn[ 2 ] . output [ 0 ] . value ;
6524
+ let new_feerate = fee * 1000 / node_txn[ 2 ] . get_weight ( ) as u64 ;
6525
+ assert ! ( new_feerate * 100 > feerate_2 * 125 ) ;
6526
+
6527
+ check_spends ! ( node_txn[ 3 ] , revoked_htlc_txn[ 0 ] . clone( ) ) ;
6528
+ assert_ne ! ( first, node_txn[ 3 ] . txid( ) ) ;
6529
+ let fee = revoked_htlc_txn[ 0 ] . output [ 0 ] . value - node_txn[ 3 ] . output [ 0 ] . value ;
6530
+ let new_feerate = fee * 1000 / node_txn[ 3 ] . get_weight ( ) as u64 ;
6531
+ assert ! ( new_feerate * 100 > feerate_1 * 125 ) ;
6532
+ } else { assert ! ( false ) }
6533
+ let txn = vec ! [ node_txn[ 0 ] . clone( ) , node_txn[ 1 ] . clone( ) , node_txn[ 2 ] . clone( ) , node_txn[ 3 ] . clone( ) ] ;
6534
+ node_txn. clear ( ) ;
6535
+ txn
6536
+ } ;
6537
+ // Broadcast claim txn and confirm blocks to avoid further bumps on this outputs
6538
+ let header_134 = BlockHeader { version : 0x20000000 , prev_blockhash : header_133, merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6539
+ nodes[ 0 ] . block_notifier . block_connected ( & Block { header : header_134, txdata : node_txn } , 134 ) ;
6540
+ connect_blocks ( & nodes[ 0 ] . block_notifier , 6 , 134 , true , header_134. bitcoin_hash ( ) ) ;
6541
+ {
6542
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6543
+ node_txn. clear ( ) ;
6544
+ }
6545
+
6546
+ // Connect few more blocks and check only penalty transaction for to_local output have been issued
6547
+ connect_blocks ( & nodes[ 0 ] . block_notifier , 5 , 140 , true , header_134. bitcoin_hash ( ) ) ;
6548
+ {
6549
+ let mut node_txn = nodes[ 0 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6550
+ assert_eq ! ( node_txn. len( ) , 1 ) ;
6551
+ check_spends ! ( node_txn[ 0 ] , revoked_local_txn[ 0 ] . clone( ) ) ;
6552
+ node_txn. clear ( ) ;
6553
+ }
6554
+ check_closed_broadcast ! ( nodes[ 0 ] ) ;
6555
+ }
0 commit comments