11//! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type
2+ #![ allow( dead_code) ]
23
4+ use core:: cmp;
35use core:: ops:: Deref ;
46
57use bitcoin:: secp256k1:: { self , PublicKey , Secp256k1 } ;
68
79use 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 ,
911 ChannelTransactionParameters , CommitmentTransaction , HTLCOutputInCommitment ,
1012} ;
1113use crate :: ln:: channel:: { CommitmentStats , ANCHOR_OUTPUT_VALUE_SATOSHI } ;
1214use crate :: prelude:: * ;
1315use crate :: types:: features:: ChannelTypeFeatures ;
1416use crate :: util:: logger:: Logger ;
1517
18+ #[ cfg( any( test, fuzzing) ) ]
19+ #[ derive( Clone , PartialEq , PartialOrd , Eq , Ord ) ]
20+ pub ( crate ) struct HTLCAmountDirection {
21+ pub outbound : bool ,
22+ pub amount_msat : u64 ,
23+ }
24+
25+ #[ cfg( not( any( test, fuzzing) ) ) ]
26+ pub ( crate ) struct HTLCAmountDirection {
27+ pub outbound : bool ,
28+ pub amount_msat : u64 ,
29+ }
30+
31+ impl HTLCAmountDirection {
32+ fn is_dust (
33+ & self , local : bool , feerate_per_kw : u32 , broadcaster_dust_limit_satoshis : u64 ,
34+ channel_type : & ChannelTypeFeatures ,
35+ ) -> bool {
36+ let htlc_tx_fee_sat = if channel_type. supports_anchors_zero_fee_htlc_tx ( ) {
37+ 0
38+ } else {
39+ let htlc_tx_weight = if self . outbound == local {
40+ htlc_timeout_tx_weight ( channel_type)
41+ } else {
42+ htlc_success_tx_weight ( channel_type)
43+ } ;
44+ // As required by the spec, round down
45+ feerate_per_kw as u64 * htlc_tx_weight / 1000
46+ } ;
47+ self . amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
48+ }
49+ }
50+
51+ pub ( crate ) struct NextCommitmentStats {
52+ pub next_commitment_htlcs : Vec < HTLCAmountDirection > ,
53+ pub holder_balance_msat : Option < u64 > ,
54+ pub counterparty_balance_msat : Option < u64 > ,
55+ pub commit_tx_fee_sat : u64 ,
56+ pub dust_exposure_msat : u64 ,
57+ // If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate,
58+ // this should be set to the dust exposure that would result from us adding an additional nondust outbound
59+ // htlc on the counterparty's commitment transaction.
60+ pub extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat : Option < u64 > ,
61+ }
62+
63+ fn on_holder_tx_dust_exposure_msat (
64+ next_commitment_htlcs : & [ HTLCAmountDirection ] , dust_buffer_feerate : u32 ,
65+ broadcaster_dust_limit_satoshis : u64 , channel_type : & ChannelTypeFeatures ,
66+ ) -> u64 {
67+ next_commitment_htlcs
68+ . iter ( )
69+ . filter_map ( |htlc| {
70+ htlc. is_dust ( true , dust_buffer_feerate, broadcaster_dust_limit_satoshis, channel_type)
71+ . then_some ( htlc. amount_msat )
72+ } )
73+ . sum ( )
74+ }
75+
76+ fn on_counterparty_tx_dust_exposure_msat (
77+ next_commitment_htlcs : & [ HTLCAmountDirection ] , dust_buffer_feerate : u32 ,
78+ excess_feerate_opt : Option < u32 > , broadcaster_dust_limit_satoshis : u64 ,
79+ channel_type : & ChannelTypeFeatures ,
80+ ) -> ( u64 , Option < u64 > ) {
81+ let mut on_counterparty_tx_dust_exposure_msat: u64 = next_commitment_htlcs
82+ . iter ( )
83+ . filter_map ( |htlc| {
84+ htlc. is_dust ( false , dust_buffer_feerate, broadcaster_dust_limit_satoshis, channel_type)
85+ . then_some ( htlc. amount_msat )
86+ } )
87+ . sum ( ) ;
88+
89+ #[ rustfmt:: skip]
90+ let extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat = excess_feerate_opt. map ( |excess_feerate| {
91+ 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 ( ) ;
92+ 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 ( ) ;
93+
94+ 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) ;
95+ 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) ;
96+
97+ 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) ;
98+ 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) ;
99+
100+ let extra_htlc_dust_exposure = on_counterparty_tx_dust_exposure_msat + ( extra_htlc_commit_tx_fee_sat + extra_htlc_htlc_tx_fees_sat) * 1000 ;
101+ on_counterparty_tx_dust_exposure_msat += ( commit_tx_fee_sat + htlc_tx_fees_sat) * 1000 ;
102+ extra_htlc_dust_exposure
103+ } ) ;
104+
105+ (
106+ on_counterparty_tx_dust_exposure_msat,
107+ extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat,
108+ )
109+ }
110+
111+ fn subtract_addl_outputs (
112+ is_outbound_from_holder : bool , value_to_self_after_htlcs : Option < u64 > ,
113+ value_to_remote_after_htlcs : Option < u64 > , channel_type : & ChannelTypeFeatures ,
114+ ) -> ( Option < u64 > , Option < u64 > ) {
115+ let total_anchors_sat = if channel_type. supports_anchors_zero_fee_htlc_tx ( ) {
116+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
117+ } else {
118+ 0
119+ } ;
120+
121+ let mut local_balance_before_fee_msat = value_to_self_after_htlcs;
122+ let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs;
123+
124+ // We MUST use checked subs here, as the funder's balance is not guaranteed to be greater
125+ // than or equal to `total_anchors_sat`.
126+ //
127+ // This is because when the remote party sends an `update_fee` message, we build the new
128+ // commitment transaction *before* checking whether the remote party's balance is enough to
129+ // cover the total anchor sum.
130+
131+ if is_outbound_from_holder {
132+ local_balance_before_fee_msat = local_balance_before_fee_msat
133+ . and_then ( |balance_msat| balance_msat. checked_sub ( total_anchors_sat * 1000 ) ) ;
134+ } else {
135+ remote_balance_before_fee_msat = remote_balance_before_fee_msat
136+ . and_then ( |balance_msat| balance_msat. checked_sub ( total_anchors_sat * 1000 ) ) ;
137+ }
138+
139+ ( local_balance_before_fee_msat, remote_balance_before_fee_msat)
140+ }
141+
142+ fn get_dust_buffer_feerate ( feerate_per_kw : u32 ) -> u32 {
143+ // When calculating our exposure to dust HTLCs, we assume that the channel feerate
144+ // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
145+ // whichever is higher. This ensures that we aren't suddenly exposed to significantly
146+ // more dust balance if the feerate increases when we have several HTLCs pending
147+ // which are near the dust limit.
148+ let feerate_plus_quarter = feerate_per_kw. checked_mul ( 1250 ) . map ( |v| v / 1000 ) ;
149+ cmp:: max ( feerate_per_kw. saturating_add ( 2530 ) , feerate_plus_quarter. unwrap_or ( u32:: MAX ) )
150+ }
151+
16152pub ( crate ) trait TxBuilder {
153+ fn get_next_commitment_stats (
154+ & self , local : bool , is_outbound_from_holder : bool , channel_value_satoshis : u64 ,
155+ value_to_holder_msat : u64 , next_commitment_htlcs : Vec < HTLCAmountDirection > ,
156+ nondust_htlcs : usize , feerate_per_kw : u32 , dust_exposure_limiting_feerate : Option < u32 > ,
157+ broadcaster_dust_limit_satoshis : u64 , channel_type : & ChannelTypeFeatures ,
158+ ) -> NextCommitmentStats ;
17159 fn commit_tx_fee_sat (
18160 & self , feerate_per_kw : u32 , nondust_htlc_count : usize , channel_type : & ChannelTypeFeatures ,
19161 ) -> u64 ;
@@ -25,7 +167,7 @@ pub(crate) trait TxBuilder {
25167 & self , local : bool , commitment_number : u64 , per_commitment_point : & PublicKey ,
26168 channel_parameters : & ChannelTransactionParameters , secp_ctx : & Secp256k1 < secp256k1:: All > ,
27169 value_to_self_msat : u64 , htlcs_in_tx : Vec < HTLCOutputInCommitment > , feerate_per_kw : u32 ,
28- broadcaster_dust_limit_sat : u64 , logger : & L ,
170+ broadcaster_dust_limit_satoshis : u64 , logger : & L ,
29171 ) -> ( CommitmentTransaction , CommitmentStats )
30172 where
31173 L :: Target : Logger ;
@@ -34,6 +176,117 @@ pub(crate) trait TxBuilder {
34176pub ( crate ) struct SpecTxBuilder { }
35177
36178impl TxBuilder for SpecTxBuilder {
179+ fn get_next_commitment_stats (
180+ & self , local : bool , is_outbound_from_holder : bool , channel_value_satoshis : u64 ,
181+ value_to_holder_msat : u64 , next_commitment_htlcs : Vec < HTLCAmountDirection > ,
182+ nondust_htlcs : usize , feerate_per_kw : u32 , dust_exposure_limiting_feerate : Option < u32 > ,
183+ broadcaster_dust_limit_satoshis : u64 , channel_type : & ChannelTypeFeatures ,
184+ ) -> NextCommitmentStats {
185+ let excess_feerate_opt =
186+ feerate_per_kw. checked_sub ( dust_exposure_limiting_feerate. unwrap_or ( 0 ) ) ;
187+ // Dust exposure is only decoupled from feerate for zero fee commitment channels.
188+ let is_zero_fee_comm = channel_type. supports_anchor_zero_fee_commitments ( ) ;
189+ debug_assert_eq ! ( is_zero_fee_comm, dust_exposure_limiting_feerate. is_none( ) ) ;
190+ if is_zero_fee_comm {
191+ debug_assert_eq ! ( feerate_per_kw, 0 ) ;
192+ debug_assert_eq ! ( excess_feerate_opt, Some ( 0 ) ) ;
193+ debug_assert_eq ! ( nondust_htlcs, 0 ) ;
194+ }
195+
196+ // Calculate balances after htlcs
197+ let value_to_counterparty_msat = channel_value_satoshis * 1000 - value_to_holder_msat;
198+ let outbound_htlcs_value_msat: u64 = next_commitment_htlcs
199+ . iter ( )
200+ . filter_map ( |htlc| htlc. outbound . then_some ( htlc. amount_msat ) )
201+ . sum ( ) ;
202+ let inbound_htlcs_value_msat: u64 = next_commitment_htlcs
203+ . iter ( )
204+ . filter_map ( |htlc| ( !htlc. outbound ) . then_some ( htlc. amount_msat ) )
205+ . sum ( ) ;
206+ let value_to_holder_after_htlcs =
207+ value_to_holder_msat. checked_sub ( outbound_htlcs_value_msat) ;
208+ let value_to_counterparty_after_htlcs =
209+ value_to_counterparty_msat. checked_sub ( inbound_htlcs_value_msat) ;
210+
211+ // Subtract the anchors from the channel funder
212+ let ( holder_balance_msat, counterparty_balance_msat) = subtract_addl_outputs (
213+ is_outbound_from_holder,
214+ value_to_holder_after_htlcs,
215+ value_to_counterparty_after_htlcs,
216+ channel_type,
217+ ) ;
218+
219+ // Increment the feerate by a buffer to calculate dust exposure
220+ let dust_buffer_feerate = get_dust_buffer_feerate ( feerate_per_kw) ;
221+
222+ if local {
223+ // Calculate fees and dust exposure on holder's commitment transaction
224+ let on_holder_htlc_count = next_commitment_htlcs
225+ . iter ( )
226+ . filter ( |htlc| {
227+ !htlc. is_dust (
228+ true ,
229+ feerate_per_kw,
230+ broadcaster_dust_limit_satoshis,
231+ channel_type,
232+ )
233+ } )
234+ . count ( ) ;
235+ let commit_tx_fee_sat = commit_tx_fee_sat (
236+ feerate_per_kw,
237+ on_holder_htlc_count + nondust_htlcs,
238+ channel_type,
239+ ) ;
240+ let on_holder_tx_dust_exposure_msat = on_holder_tx_dust_exposure_msat (
241+ & next_commitment_htlcs,
242+ dust_buffer_feerate,
243+ broadcaster_dust_limit_satoshis,
244+ channel_type,
245+ ) ;
246+ NextCommitmentStats {
247+ next_commitment_htlcs,
248+ holder_balance_msat,
249+ counterparty_balance_msat,
250+ commit_tx_fee_sat,
251+ dust_exposure_msat : on_holder_tx_dust_exposure_msat,
252+ extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat : None ,
253+ }
254+ } else {
255+ // Calculate fees and dust exposure on counterparty's commitment transaction
256+ let on_counterparty_htlc_count = next_commitment_htlcs
257+ . iter ( )
258+ . filter ( |htlc| {
259+ !htlc. is_dust (
260+ false ,
261+ feerate_per_kw,
262+ broadcaster_dust_limit_satoshis,
263+ channel_type,
264+ )
265+ } )
266+ . count ( ) ;
267+ let commit_tx_fee_sat = commit_tx_fee_sat (
268+ feerate_per_kw,
269+ on_counterparty_htlc_count + nondust_htlcs,
270+ channel_type,
271+ ) ;
272+ let ( dust_exposure_msat, extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat) =
273+ on_counterparty_tx_dust_exposure_msat (
274+ & next_commitment_htlcs,
275+ dust_buffer_feerate,
276+ excess_feerate_opt,
277+ broadcaster_dust_limit_satoshis,
278+ channel_type,
279+ ) ;
280+ NextCommitmentStats {
281+ next_commitment_htlcs,
282+ holder_balance_msat,
283+ counterparty_balance_msat,
284+ commit_tx_fee_sat,
285+ dust_exposure_msat,
286+ extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat,
287+ }
288+ }
289+ }
37290 fn commit_tx_fee_sat (
38291 & self , feerate_per_kw : u32 , nondust_htlc_count : usize , channel_type : & ChannelTypeFeatures ,
39292 ) -> u64 {
@@ -74,7 +327,7 @@ impl TxBuilder for SpecTxBuilder {
74327 & self , local : bool , commitment_number : u64 , per_commitment_point : & PublicKey ,
75328 channel_parameters : & ChannelTransactionParameters , secp_ctx : & Secp256k1 < secp256k1:: All > ,
76329 value_to_self_msat : u64 , mut htlcs_in_tx : Vec < HTLCOutputInCommitment > , feerate_per_kw : u32 ,
77- broadcaster_dust_limit_sat : u64 , logger : & L ,
330+ broadcaster_dust_limit_satoshis : u64 , logger : & L ,
78331 ) -> ( CommitmentTransaction , CommitmentStats )
79332 where
80333 L :: Target : Logger ,
@@ -95,7 +348,7 @@ impl TxBuilder for SpecTxBuilder {
95348 // As required by the spec, round down
96349 feerate_per_kw as u64 * htlc_tx_weight / 1000
97350 } ;
98- amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat
351+ amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
99352 } ;
100353
101354 // Trim dust htlcs
@@ -107,7 +360,7 @@ impl TxBuilder for SpecTxBuilder {
107360 remote_htlc_total_msat += htlc. amount_msat ;
108361 }
109362 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 ) ;
363+ 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 ) ;
111364 false
112365 } else {
113366 true
@@ -142,13 +395,13 @@ impl TxBuilder for SpecTxBuilder {
142395 let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote } ;
143396 let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self } ;
144397
145- if to_broadcaster_value_sat >= broadcaster_dust_limit_sat {
398+ if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis {
146399 log_trace ! ( logger, " ...including {} output with value {}" , if local { "to_local" } else { "to_remote" } , to_broadcaster_value_sat) ;
147400 } else {
148401 to_broadcaster_value_sat = 0 ;
149402 }
150403
151- if to_countersignatory_value_sat >= broadcaster_dust_limit_sat {
404+ if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis {
152405 log_trace ! ( logger, " ...including {} output with value {}" , if local { "to_remote" } else { "to_local" } , to_countersignatory_value_sat) ;
153406 } else {
154407 to_countersignatory_value_sat = 0 ;
0 commit comments