1
1
//! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type
2
+ #![ allow( dead_code) ]
2
3
4
+ use core:: cmp;
3
5
use core:: ops:: Deref ;
4
6
5
7
use bitcoin:: secp256k1:: { self , PublicKey , Secp256k1 } ;
6
8
7
9
use crate :: ln:: chan_utils:: {
8
- commit_tx_fee_sat, htlc_success_tx_weight, htlc_timeout_tx_weight,
10
+ commit_tx_fee_sat, htlc_success_tx_weight, htlc_timeout_tx_weight, htlc_tx_fees_sat ,
9
11
ChannelTransactionParameters , CommitmentTransaction , HTLCOutputInCommitment ,
10
12
} ;
11
13
use crate :: ln:: channel:: { CommitmentStats , ANCHOR_OUTPUT_VALUE_SATOSHI } ;
12
14
use crate :: prelude:: * ;
13
15
use crate :: types:: features:: ChannelTypeFeatures ;
14
16
use crate :: util:: logger:: Logger ;
15
17
18
+ #[ cfg_attr( any( test, fuzzing) , derive( Clone , PartialEq , PartialOrd , Eq , Ord ) ) ]
19
+ pub ( crate ) struct HTLCAmountDirection {
20
+ pub outbound : bool ,
21
+ pub amount_msat : u64 ,
22
+ }
23
+
24
+ impl HTLCAmountDirection {
25
+ fn is_dust (
26
+ & self , local : bool , feerate_per_kw : u32 , broadcaster_dust_limit_satoshis : u64 ,
27
+ channel_type : & ChannelTypeFeatures ,
28
+ ) -> bool {
29
+ let htlc_tx_fee_sat = if channel_type. supports_anchors_zero_fee_htlc_tx ( ) {
30
+ 0
31
+ } else {
32
+ let htlc_tx_weight = if self . outbound == local {
33
+ htlc_timeout_tx_weight ( channel_type)
34
+ } else {
35
+ htlc_success_tx_weight ( channel_type)
36
+ } ;
37
+ // As required by the spec, round down
38
+ feerate_per_kw as u64 * htlc_tx_weight / 1000
39
+ } ;
40
+ self . amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
41
+ }
42
+ }
43
+
44
+ pub ( crate ) struct NextCommitmentStats {
45
+ pub next_commitment_htlcs : Vec < HTLCAmountDirection > ,
46
+ pub inbound_htlcs_count : usize ,
47
+ pub inbound_htlcs_value_msat : u64 ,
48
+ pub holder_balance_msat : Option < u64 > ,
49
+ pub counterparty_balance_msat : Option < u64 > ,
50
+ pub commit_tx_fee_sat : u64 ,
51
+ pub dust_exposure_msat : u64 ,
52
+ // If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate,
53
+ // this should be set to the dust exposure that would result from us adding an additional nondust outbound
54
+ // htlc on the counterparty's commitment transaction.
55
+ pub extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat : Option < u64 > ,
56
+ }
57
+
58
+ #[ rustfmt:: skip]
59
+ fn excess_fees_on_counterparty_tx_dust_exposure_msat (
60
+ next_commitment_htlcs : & [ HTLCAmountDirection ] , dust_buffer_feerate : u32 ,
61
+ excess_feerate : u32 , broadcaster_dust_limit_satoshis : u64 , mut on_counterparty_tx_dust_exposure_msat : u64 ,
62
+ channel_type : & ChannelTypeFeatures ,
63
+ ) -> ( u64 , u64 ) {
64
+
65
+ let on_counterparty_tx_accepted_nondust_htlcs = next_commitment_htlcs. iter ( ) . filter ( |htlc| !htlc. is_dust ( false , dust_buffer_feerate, broadcaster_dust_limit_satoshis, channel_type) && htlc. outbound ) . count ( ) ;
66
+ let on_counterparty_tx_offered_nondust_htlcs = next_commitment_htlcs. iter ( ) . filter ( |htlc| !htlc. is_dust ( false , dust_buffer_feerate, broadcaster_dust_limit_satoshis, channel_type) && !htlc. outbound ) . count ( ) ;
67
+
68
+ let extra_htlc_commit_tx_fee_sat = commit_tx_fee_sat ( excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1 + on_counterparty_tx_offered_nondust_htlcs, channel_type) ;
69
+ let extra_htlc_htlc_tx_fees_sat = htlc_tx_fees_sat ( excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + 1 , on_counterparty_tx_offered_nondust_htlcs, channel_type) ;
70
+
71
+ let commit_tx_fee_sat = commit_tx_fee_sat ( excess_feerate, on_counterparty_tx_accepted_nondust_htlcs + on_counterparty_tx_offered_nondust_htlcs, channel_type) ;
72
+ let htlc_tx_fees_sat = htlc_tx_fees_sat ( excess_feerate, on_counterparty_tx_accepted_nondust_htlcs, on_counterparty_tx_offered_nondust_htlcs, channel_type) ;
73
+
74
+ let extra_htlc_dust_exposure_msat = on_counterparty_tx_dust_exposure_msat + ( extra_htlc_commit_tx_fee_sat + extra_htlc_htlc_tx_fees_sat) * 1000 ;
75
+ on_counterparty_tx_dust_exposure_msat += ( commit_tx_fee_sat + htlc_tx_fees_sat) * 1000 ;
76
+
77
+ (
78
+ on_counterparty_tx_dust_exposure_msat,
79
+ extra_htlc_dust_exposure_msat,
80
+ )
81
+ }
82
+
83
+ fn subtract_addl_outputs (
84
+ is_outbound_from_holder : bool , value_to_self_after_htlcs : Option < u64 > ,
85
+ value_to_remote_after_htlcs : Option < u64 > , channel_type : & ChannelTypeFeatures ,
86
+ ) -> ( Option < u64 > , Option < u64 > ) {
87
+ let total_anchors_sat = if channel_type. supports_anchors_zero_fee_htlc_tx ( ) {
88
+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
89
+ } else {
90
+ 0
91
+ } ;
92
+
93
+ let mut local_balance_before_fee_msat = value_to_self_after_htlcs;
94
+ let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs;
95
+
96
+ // We MUST use checked subs here, as the funder's balance is not guaranteed to be greater
97
+ // than or equal to `total_anchors_sat`.
98
+ //
99
+ // This is because when the remote party sends an `update_fee` message, we build the new
100
+ // commitment transaction *before* checking whether the remote party's balance is enough to
101
+ // cover the total anchor sum.
102
+
103
+ if is_outbound_from_holder {
104
+ local_balance_before_fee_msat = local_balance_before_fee_msat
105
+ . and_then ( |balance_msat| balance_msat. checked_sub ( total_anchors_sat * 1000 ) ) ;
106
+ } else {
107
+ remote_balance_before_fee_msat = remote_balance_before_fee_msat
108
+ . and_then ( |balance_msat| balance_msat. checked_sub ( total_anchors_sat * 1000 ) ) ;
109
+ }
110
+
111
+ ( local_balance_before_fee_msat, remote_balance_before_fee_msat)
112
+ }
113
+
114
+ fn get_dust_buffer_feerate ( feerate_per_kw : u32 ) -> u32 {
115
+ // When calculating our exposure to dust HTLCs, we assume that the channel feerate
116
+ // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
117
+ // whichever is higher. This ensures that we aren't suddenly exposed to significantly
118
+ // more dust balance if the feerate increases when we have several HTLCs pending
119
+ // which are near the dust limit.
120
+ let feerate_plus_quarter = feerate_per_kw. checked_mul ( 1250 ) . map ( |v| v / 1000 ) ;
121
+ cmp:: max ( feerate_per_kw. saturating_add ( 2530 ) , feerate_plus_quarter. unwrap_or ( u32:: MAX ) )
122
+ }
123
+
16
124
pub ( crate ) trait TxBuilder {
125
+ fn get_next_commitment_stats (
126
+ & self , local : bool , is_outbound_from_holder : bool , channel_value_satoshis : u64 ,
127
+ value_to_holder_msat : u64 , next_commitment_htlcs : Vec < HTLCAmountDirection > ,
128
+ addl_nondust_htlc_count : usize , feerate_per_kw : u32 ,
129
+ dust_exposure_limiting_feerate : Option < u32 > , broadcaster_dust_limit_satoshis : u64 ,
130
+ channel_type : & ChannelTypeFeatures ,
131
+ ) -> NextCommitmentStats ;
17
132
fn commit_tx_fee_sat (
18
133
& self , feerate_per_kw : u32 , nondust_htlc_count : usize , channel_type : & ChannelTypeFeatures ,
19
134
) -> u64 ;
@@ -25,7 +140,7 @@ pub(crate) trait TxBuilder {
25
140
& self , local : bool , commitment_number : u64 , per_commitment_point : & PublicKey ,
26
141
channel_parameters : & ChannelTransactionParameters , secp_ctx : & Secp256k1 < secp256k1:: All > ,
27
142
value_to_self_msat : u64 , htlcs_in_tx : Vec < HTLCOutputInCommitment > , feerate_per_kw : u32 ,
28
- broadcaster_dust_limit_sat : u64 , logger : & L ,
143
+ broadcaster_dust_limit_satoshis : u64 , logger : & L ,
29
144
) -> ( CommitmentTransaction , CommitmentStats )
30
145
where
31
146
L :: Target : Logger ;
@@ -34,6 +149,117 @@ pub(crate) trait TxBuilder {
34
149
pub ( crate ) struct SpecTxBuilder { }
35
150
36
151
impl TxBuilder for SpecTxBuilder {
152
+ fn get_next_commitment_stats (
153
+ & self , local : bool , is_outbound_from_holder : bool , channel_value_satoshis : u64 ,
154
+ value_to_holder_msat : u64 , next_commitment_htlcs : Vec < HTLCAmountDirection > ,
155
+ addl_nondust_htlc_count : usize , feerate_per_kw : u32 ,
156
+ dust_exposure_limiting_feerate : Option < u32 > , broadcaster_dust_limit_satoshis : u64 ,
157
+ channel_type : & ChannelTypeFeatures ,
158
+ ) -> NextCommitmentStats {
159
+ let excess_feerate_opt =
160
+ feerate_per_kw. checked_sub ( dust_exposure_limiting_feerate. unwrap_or ( 0 ) ) ;
161
+ // Dust exposure is only decoupled from feerate for zero fee commitment channels.
162
+ let is_zero_fee_comm = channel_type. supports_anchor_zero_fee_commitments ( ) ;
163
+ debug_assert_eq ! ( is_zero_fee_comm, dust_exposure_limiting_feerate. is_none( ) ) ;
164
+ if is_zero_fee_comm {
165
+ debug_assert_eq ! ( feerate_per_kw, 0 ) ;
166
+ debug_assert_eq ! ( excess_feerate_opt, Some ( 0 ) ) ;
167
+ debug_assert_eq ! ( addl_nondust_htlc_count, 0 ) ;
168
+ }
169
+
170
+ // Calculate inbound htlc count
171
+ let inbound_htlcs_count =
172
+ next_commitment_htlcs. iter ( ) . filter ( |htlc| !htlc. outbound ) . count ( ) ;
173
+
174
+ // Calculate balances after htlcs
175
+ let value_to_counterparty_msat = channel_value_satoshis * 1000 - value_to_holder_msat;
176
+ let outbound_htlcs_value_msat: u64 = next_commitment_htlcs
177
+ . iter ( )
178
+ . filter_map ( |htlc| htlc. outbound . then_some ( htlc. amount_msat ) )
179
+ . sum ( ) ;
180
+ let inbound_htlcs_value_msat: u64 = next_commitment_htlcs
181
+ . iter ( )
182
+ . filter_map ( |htlc| ( !htlc. outbound ) . then_some ( htlc. amount_msat ) )
183
+ . sum ( ) ;
184
+ let value_to_holder_after_htlcs =
185
+ value_to_holder_msat. checked_sub ( outbound_htlcs_value_msat) ;
186
+ let value_to_counterparty_after_htlcs =
187
+ value_to_counterparty_msat. checked_sub ( inbound_htlcs_value_msat) ;
188
+
189
+ // Subtract the anchors from the channel funder
190
+ let ( holder_balance_msat, counterparty_balance_msat) = subtract_addl_outputs (
191
+ is_outbound_from_holder,
192
+ value_to_holder_after_htlcs,
193
+ value_to_counterparty_after_htlcs,
194
+ channel_type,
195
+ ) ;
196
+
197
+ // Increment the feerate by a buffer to calculate dust exposure
198
+ let dust_buffer_feerate = get_dust_buffer_feerate ( feerate_per_kw) ;
199
+
200
+ // Calculate fees on commitment transaction
201
+ let nondust_htlc_count = next_commitment_htlcs
202
+ . iter ( )
203
+ . filter ( |htlc| {
204
+ !htlc. is_dust ( local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type)
205
+ } )
206
+ . count ( ) ;
207
+ let commit_tx_fee_sat = commit_tx_fee_sat (
208
+ feerate_per_kw,
209
+ nondust_htlc_count + addl_nondust_htlc_count,
210
+ channel_type,
211
+ ) ;
212
+
213
+ // Calculate dust exposure on commitment transaction
214
+ let dust_exposure_msat = next_commitment_htlcs
215
+ . iter ( )
216
+ . filter_map ( |htlc| {
217
+ htlc. is_dust (
218
+ local,
219
+ dust_buffer_feerate,
220
+ broadcaster_dust_limit_satoshis,
221
+ channel_type,
222
+ )
223
+ . then_some ( htlc. amount_msat )
224
+ } )
225
+ . sum ( ) ;
226
+
227
+ // Count the excess fees on the counterparty's transaction as dust
228
+ if let ( Some ( excess_feerate) , false ) = ( excess_feerate_opt, local) {
229
+ let ( dust_exposure_msat, extra_htlc_dust_exposure_msat) =
230
+ excess_fees_on_counterparty_tx_dust_exposure_msat (
231
+ & next_commitment_htlcs,
232
+ dust_buffer_feerate,
233
+ excess_feerate,
234
+ broadcaster_dust_limit_satoshis,
235
+ dust_exposure_msat,
236
+ channel_type,
237
+ ) ;
238
+ NextCommitmentStats {
239
+ inbound_htlcs_count,
240
+ inbound_htlcs_value_msat,
241
+ next_commitment_htlcs,
242
+ holder_balance_msat,
243
+ counterparty_balance_msat,
244
+ commit_tx_fee_sat,
245
+ dust_exposure_msat,
246
+ extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat : Some (
247
+ extra_htlc_dust_exposure_msat,
248
+ ) ,
249
+ }
250
+ } else {
251
+ NextCommitmentStats {
252
+ inbound_htlcs_count,
253
+ inbound_htlcs_value_msat,
254
+ next_commitment_htlcs,
255
+ holder_balance_msat,
256
+ counterparty_balance_msat,
257
+ commit_tx_fee_sat,
258
+ dust_exposure_msat,
259
+ extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat : None ,
260
+ }
261
+ }
262
+ }
37
263
fn commit_tx_fee_sat (
38
264
& self , feerate_per_kw : u32 , nondust_htlc_count : usize , channel_type : & ChannelTypeFeatures ,
39
265
) -> u64 {
@@ -74,7 +300,7 @@ impl TxBuilder for SpecTxBuilder {
74
300
& self , local : bool , commitment_number : u64 , per_commitment_point : & PublicKey ,
75
301
channel_parameters : & ChannelTransactionParameters , secp_ctx : & Secp256k1 < secp256k1:: All > ,
76
302
value_to_self_msat : u64 , mut htlcs_in_tx : Vec < HTLCOutputInCommitment > , feerate_per_kw : u32 ,
77
- broadcaster_dust_limit_sat : u64 , logger : & L ,
303
+ broadcaster_dust_limit_satoshis : u64 , logger : & L ,
78
304
) -> ( CommitmentTransaction , CommitmentStats )
79
305
where
80
306
L :: Target : Logger ,
@@ -95,7 +321,7 @@ impl TxBuilder for SpecTxBuilder {
95
321
// As required by the spec, round down
96
322
feerate_per_kw as u64 * htlc_tx_weight / 1000
97
323
} ;
98
- amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat
324
+ amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
99
325
} ;
100
326
101
327
// Trim dust htlcs
@@ -107,7 +333,7 @@ impl TxBuilder for SpecTxBuilder {
107
333
remote_htlc_total_msat += htlc. amount_msat ;
108
334
}
109
335
if is_dust ( htlc. offered , htlc. amount_msat ) {
110
- log_trace ! ( logger, " ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}" , if htlc. offered == local { "outbound" } else { "inbound" } , htlc. amount_msat / 1000 , htlc. payment_hash, broadcaster_dust_limit_sat ) ;
336
+ log_trace ! ( logger, " ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}" , if htlc. offered == local { "outbound" } else { "inbound" } , htlc. amount_msat / 1000 , htlc. payment_hash, broadcaster_dust_limit_satoshis ) ;
111
337
false
112
338
} else {
113
339
true
@@ -142,13 +368,13 @@ impl TxBuilder for SpecTxBuilder {
142
368
let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote } ;
143
369
let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self } ;
144
370
145
- if to_broadcaster_value_sat >= broadcaster_dust_limit_sat {
371
+ if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis {
146
372
log_trace ! ( logger, " ...including {} output with value {}" , if local { "to_local" } else { "to_remote" } , to_broadcaster_value_sat) ;
147
373
} else {
148
374
to_broadcaster_value_sat = 0 ;
149
375
}
150
376
151
- if to_countersignatory_value_sat >= broadcaster_dust_limit_sat {
377
+ if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis {
152
378
log_trace ! ( logger, " ...including {} output with value {}" , if local { "to_remote" } else { "to_local" } , to_countersignatory_value_sat) ;
153
379
} else {
154
380
to_countersignatory_value_sat = 0 ;
0 commit comments