@@ -6127,3 +6127,104 @@ fn test_data_loss_protect() {
6127
6127
assert_eq ! ( spend_txn. len( ) , 1 ) ;
6128
6128
check_spends ! ( spend_txn[ 0 ] , node_txn[ 0 ] . clone( ) ) ;
6129
6129
}
6130
+
6131
+ #[ test]
6132
+ fn test_bump_penalty_txn_on_commitment ( ) {
6133
+ // In case of penalty txn with too low feerates for getting into mempools, RBF-bump them to be sure
6134
+ // we're able to claim outputs on revoked commitment transaction before timelocks expiration
6135
+
6136
+ let nodes = create_network ( 2 , & [ None , None ] ) ;
6137
+
6138
+ let chan = create_announced_chan_between_nodes_with_value ( & nodes, 0 , 1 , 1000000 , 59000000 , LocalFeatures :: new ( ) , LocalFeatures :: new ( ) ) ;
6139
+ let payment_preimage = route_payment ( & nodes[ 0 ] , & vec ! ( & nodes[ 1 ] ) [ ..] , 3000000 ) . 0 ;
6140
+ route_payment ( & nodes[ 1 ] , & vec ! ( & nodes[ 0 ] ) [ ..] , 3000000 ) . 0 ;
6141
+ let revoked_txn = nodes[ 0 ] . node . channel_state . lock ( ) . unwrap ( ) . by_id . get ( & chan. 2 ) . unwrap ( ) . last_local_commitment_txn . clone ( ) ;
6142
+ // Revoked commitment txn with 4 outputs : to_local, to_remote, 1 outgoing HTLC, 1 incoming HTLC
6143
+ assert_eq ! ( revoked_txn[ 0 ] . output. len( ) , 4 ) ;
6144
+ assert_eq ! ( revoked_txn[ 0 ] . input. len( ) , 1 ) ;
6145
+ assert_eq ! ( revoked_txn[ 0 ] . input[ 0 ] . previous_output. txid, chan. 3 . txid( ) ) ;
6146
+ let revoked_txid = revoked_txn[ 0 ] . txid ( ) ;
6147
+
6148
+ let mut penalty_sum = 0 ;
6149
+ for outp in revoked_txn[ 0 ] . output . iter ( ) {
6150
+ if outp. script_pubkey . is_v0_p2wsh ( ) {
6151
+ penalty_sum += outp. value ;
6152
+ }
6153
+ }
6154
+
6155
+ // Actually revoke tx by claiming a HTLC
6156
+ claim_payment ( & nodes[ 0 ] , & vec ! ( & nodes[ 1 ] ) [ ..] , payment_preimage) ;
6157
+ let header = BlockHeader { version : 0x20000000 , prev_blockhash : Default :: default ( ) , merkle_root : Default :: default ( ) , time : 42 , bits : 42 , nonce : 42 } ;
6158
+ nodes[ 1 ] . chain_monitor . block_connected_with_filtering ( & Block { header, txdata : vec ! [ revoked_txn[ 0 ] . clone( ) ] } , 1 ) ;
6159
+
6160
+ // One or more justice tx should have been broadcast, check it
6161
+ let penalty_1;
6162
+ let feerate_1;
6163
+ {
6164
+ let mut node_txn = nodes[ 1 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6165
+ assert_eq ! ( node_txn. len( ) , 4 ) ; // justice tx (broadcasted from ChannelMonitor) * 2 (block-reparsing) + local commitment tx + local HTLC-timeout (broadcasted from ChannelManager)
6166
+ assert_eq ! ( node_txn[ 0 ] , node_txn[ 3 ] ) ;
6167
+ assert_eq ! ( node_txn[ 0 ] . input. len( ) , 3 ) ; // Penalty txn claims to_local, offered_htlc and received_htlc outputs
6168
+ assert_eq ! ( node_txn[ 0 ] . output. len( ) , 1 ) ;
6169
+ check_spends ! ( node_txn[ 0 ] , revoked_txn[ 0 ] . clone( ) ) ;
6170
+ let fee_1 = penalty_sum - node_txn[ 0 ] . output [ 0 ] . value ;
6171
+ feerate_1 = fee_1 * 1000 / node_txn[ 0 ] . get_weight ( ) as u64 ;
6172
+ penalty_1 = node_txn[ 0 ] . txid ( ) ;
6173
+ node_txn. clear ( ) ;
6174
+ } ;
6175
+
6176
+ // After exhaustion of height timer, a new bumped justice tx should have been broadcast, check it
6177
+ let header = connect_blocks ( & nodes[ 1 ] . chain_monitor , 15 , 1 , true , header. bitcoin_hash ( ) ) ;
6178
+ let mut penalty_2 = penalty_1;
6179
+ let mut feerate_2 = 0 ;
6180
+ {
6181
+ let mut node_txn = nodes[ 1 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6182
+ assert_eq ! ( node_txn. len( ) , 1 ) ;
6183
+ if node_txn[ 0 ] . input [ 0 ] . previous_output . txid == revoked_txid {
6184
+ assert_eq ! ( node_txn[ 0 ] . input. len( ) , 3 ) ; // Penalty txn claims to_local, offered_htlc and received_htlc outputs
6185
+ assert_eq ! ( node_txn[ 0 ] . output. len( ) , 1 ) ;
6186
+ check_spends ! ( node_txn[ 0 ] , revoked_txn[ 0 ] . clone( ) ) ;
6187
+ penalty_2 = node_txn[ 0 ] . txid ( ) ;
6188
+ // Verify new bumped tx is different from last claiming transaction, we don't want spurrious rebroadcast
6189
+ assert_ne ! ( penalty_2, penalty_1) ;
6190
+ let fee_2 = penalty_sum - node_txn[ 0 ] . output [ 0 ] . value ;
6191
+ feerate_2 = fee_2 * 1000 / node_txn[ 0 ] . get_weight ( ) as u64 ;
6192
+ // Verify 25% bump heuristic
6193
+ assert ! ( feerate_2 * 100 >= feerate_1 * 125 ) ;
6194
+ node_txn. clear ( ) ;
6195
+ }
6196
+ }
6197
+ assert_ne ! ( feerate_2, 0 ) ;
6198
+
6199
+ // After exhaustion of height timer for a 2nd time, a new bumped justice tx should have been broadcast, check it
6200
+ connect_blocks ( & nodes[ 1 ] . chain_monitor , 15 , 16 , true , header) ;
6201
+ let penalty_3;
6202
+ let mut feerate_3 = 0 ;
6203
+ {
6204
+ let mut node_txn = nodes[ 1 ] . tx_broadcaster . txn_broadcasted . lock ( ) . unwrap ( ) ;
6205
+ assert_eq ! ( node_txn. len( ) , 1 ) ;
6206
+ if node_txn[ 0 ] . input [ 0 ] . previous_output . txid == revoked_txid {
6207
+ assert_eq ! ( node_txn[ 0 ] . input. len( ) , 3 ) ; // Penalty txn claims to_local, offered_htlc and received_htlc outputs
6208
+ assert_eq ! ( node_txn[ 0 ] . output. len( ) , 1 ) ;
6209
+ check_spends ! ( node_txn[ 0 ] , revoked_txn[ 0 ] . clone( ) ) ;
6210
+ penalty_3 = node_txn[ 0 ] . txid ( ) ;
6211
+ // Verify new bumped tx is different from last claiming transaction, we don't want spurrious rebroadcast
6212
+ assert_ne ! ( penalty_3, penalty_2) ;
6213
+ let fee_3 = penalty_sum - node_txn[ 0 ] . output [ 0 ] . value ;
6214
+ feerate_3 = fee_3 * 1000 / node_txn[ 0 ] . get_weight ( ) as u64 ;
6215
+ // Verify 25% bump heuristic
6216
+ assert ! ( feerate_3 * 100 >= feerate_2 * 125 ) ;
6217
+ node_txn. clear ( ) ;
6218
+ }
6219
+ }
6220
+ assert_ne ! ( feerate_3, 0 ) ;
6221
+
6222
+ nodes[ 1 ] . node . get_and_clear_pending_events ( ) ;
6223
+ nodes[ 1 ] . node . get_and_clear_pending_msg_events ( ) ;
6224
+
6225
+ //TODO: verify with heuristic HighPriority spike
6226
+
6227
+ //TODO: shuffle min relay fee
6228
+
6229
+ //TODO: test bumping txn on revoked HTLC-Success/HTLC-Timeout
6230
+ }
0 commit comments