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,
9- ChannelTransactionParameters , CommitmentTransaction , HTLCOutputInCommitment ,
10+ commit_tx_fee_sat, htlc_success_tx_weight, htlc_timeout_tx_weight, htlc_tx_fees_sat,
11+ second_stage_tx_fees_sat, ChannelTransactionParameters , CommitmentTransaction ,
12+ HTLCOutputInCommitment ,
1013} ;
1114use crate :: ln:: channel:: { CommitmentStats , ANCHOR_OUTPUT_VALUE_SATOSHI } ;
1215use crate :: prelude:: * ;
1316use crate :: types:: features:: ChannelTypeFeatures ;
1417use crate :: util:: logger:: Logger ;
1518
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 ( success_tx_fee_sat, timeout_tx_fee_sat) =
30+ second_stage_tx_fees_sat ( channel_type, feerate_per_kw) ;
31+ let htlc_tx_fee_sat =
32+ if self . outbound == local { timeout_tx_fee_sat } else { success_tx_fee_sat } ;
33+ self . amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
34+ }
35+ }
36+
37+ pub ( crate ) struct NextCommitmentStats {
38+ pub inbound_htlcs_count : usize ,
39+ pub inbound_htlcs_value_msat : u64 ,
40+ pub holder_balance_before_fee_msat : Option < u64 > ,
41+ pub counterparty_balance_before_fee_msat : Option < u64 > ,
42+ pub nondust_htlc_count : usize ,
43+ pub commit_tx_fee_sat : u64 ,
44+ pub dust_exposure_msat : u64 ,
45+ // If the counterparty sets a feerate on the channel in excess of our dust_exposure_limiting_feerate,
46+ // this should be set to the dust exposure that would result from us adding an additional nondust outbound
47+ // htlc on the counterparty's commitment transaction.
48+ pub extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat : Option < u64 > ,
49+ }
50+
51+ #[ rustfmt:: skip]
52+ fn excess_fees_on_counterparty_tx_dust_exposure_msat (
53+ next_commitment_htlcs : & [ HTLCAmountDirection ] , dust_buffer_feerate : u32 ,
54+ excess_feerate : u32 , counterparty_dust_limit_satoshis : u64 , mut on_counterparty_tx_dust_exposure_msat : u64 ,
55+ channel_type : & ChannelTypeFeatures ,
56+ ) -> ( u64 , u64 ) {
57+
58+ let on_counterparty_tx_accepted_nondust_htlcs = next_commitment_htlcs. iter ( ) . filter ( |htlc| htlc. outbound && !htlc. is_dust ( false , dust_buffer_feerate, counterparty_dust_limit_satoshis, channel_type) ) . count ( ) ;
59+ let on_counterparty_tx_offered_nondust_htlcs = next_commitment_htlcs. iter ( ) . filter ( |htlc| !htlc. outbound && !htlc. is_dust ( false , dust_buffer_feerate, counterparty_dust_limit_satoshis, channel_type) ) . count ( ) ;
60+
61+ 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) ;
62+ 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) ;
63+
64+ 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) ;
65+ 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) ;
66+
67+ 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 ;
68+ on_counterparty_tx_dust_exposure_msat += ( commit_tx_fee_sat + htlc_tx_fees_sat) * 1000 ;
69+
70+ (
71+ on_counterparty_tx_dust_exposure_msat,
72+ extra_htlc_dust_exposure_msat,
73+ )
74+ }
75+
76+ fn subtract_addl_outputs (
77+ is_outbound_from_holder : bool , value_to_self_after_htlcs_msat : Option < u64 > ,
78+ value_to_remote_after_htlcs_msat : Option < u64 > , channel_type : & ChannelTypeFeatures ,
79+ ) -> ( Option < u64 > , Option < u64 > ) {
80+ let total_anchors_sat = if channel_type. supports_anchors_zero_fee_htlc_tx ( ) {
81+ ANCHOR_OUTPUT_VALUE_SATOSHI * 2
82+ } else {
83+ 0
84+ } ;
85+
86+ // We MUST use checked subs here, as the funder's balance is not guaranteed to be greater
87+ // than or equal to `total_anchors_sat`.
88+ //
89+ // This is because when the remote party sends an `update_fee` message, we build the new
90+ // commitment transaction *before* checking whether the remote party's balance is enough to
91+ // cover the total anchor sum.
92+
93+ let local_balance_before_fee_msat = if is_outbound_from_holder {
94+ value_to_self_after_htlcs_msat
95+ . and_then ( |balance_msat| balance_msat. checked_sub ( total_anchors_sat * 1000 ) )
96+ } else {
97+ value_to_self_after_htlcs_msat
98+ } ;
99+
100+ let remote_balance_before_fee_msat = if !is_outbound_from_holder {
101+ value_to_remote_after_htlcs_msat
102+ . and_then ( |balance_msat| balance_msat. checked_sub ( total_anchors_sat * 1000 ) )
103+ } else {
104+ value_to_remote_after_htlcs_msat
105+ } ;
106+
107+ ( local_balance_before_fee_msat, remote_balance_before_fee_msat)
108+ }
109+
110+ fn get_dust_buffer_feerate ( feerate_per_kw : u32 ) -> u32 {
111+ // When calculating our exposure to dust HTLCs, we assume that the channel feerate
112+ // may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
113+ // whichever is higher. This ensures that we aren't suddenly exposed to significantly
114+ // more dust balance if the feerate increases when we have several HTLCs pending
115+ // which are near the dust limit.
116+ let feerate_plus_quarter = feerate_per_kw. checked_mul ( 1250 ) . map ( |v| v / 1000 ) ;
117+ cmp:: max ( feerate_per_kw. saturating_add ( 2530 ) , feerate_plus_quarter. unwrap_or ( u32:: MAX ) )
118+ }
119+
16120pub ( crate ) trait TxBuilder {
121+ fn get_next_commitment_stats (
122+ & self , local : bool , is_outbound_from_holder : bool , channel_value_satoshis : u64 ,
123+ value_to_holder_msat : u64 , next_commitment_htlcs : & [ HTLCAmountDirection ] ,
124+ addl_nondust_htlc_count : usize , feerate_per_kw : u32 ,
125+ dust_exposure_limiting_feerate : Option < u32 > , broadcaster_dust_limit_satoshis : u64 ,
126+ channel_type : & ChannelTypeFeatures ,
127+ ) -> NextCommitmentStats ;
17128 fn commit_tx_fee_sat (
18129 & self , feerate_per_kw : u32 , nondust_htlc_count : usize , channel_type : & ChannelTypeFeatures ,
19130 ) -> u64 ;
@@ -25,7 +136,7 @@ pub(crate) trait TxBuilder {
25136 & self , local : bool , commitment_number : u64 , per_commitment_point : & PublicKey ,
26137 channel_parameters : & ChannelTransactionParameters , secp_ctx : & Secp256k1 < secp256k1:: All > ,
27138 value_to_self_msat : u64 , htlcs_in_tx : Vec < HTLCOutputInCommitment > , feerate_per_kw : u32 ,
28- broadcaster_dust_limit_sat : u64 , logger : & L ,
139+ broadcaster_dust_limit_satoshis : u64 , logger : & L ,
29140 ) -> ( CommitmentTransaction , CommitmentStats )
30141 where
31142 L :: Target : Logger ;
@@ -34,6 +145,115 @@ pub(crate) trait TxBuilder {
34145pub ( crate ) struct SpecTxBuilder { }
35146
36147impl TxBuilder for SpecTxBuilder {
148+ fn get_next_commitment_stats (
149+ & self , local : bool , is_outbound_from_holder : bool , channel_value_satoshis : u64 ,
150+ value_to_holder_msat : u64 , next_commitment_htlcs : & [ HTLCAmountDirection ] ,
151+ addl_nondust_htlc_count : usize , feerate_per_kw : u32 ,
152+ dust_exposure_limiting_feerate : Option < u32 > , broadcaster_dust_limit_satoshis : u64 ,
153+ channel_type : & ChannelTypeFeatures ,
154+ ) -> NextCommitmentStats {
155+ let excess_feerate_opt =
156+ feerate_per_kw. checked_sub ( dust_exposure_limiting_feerate. unwrap_or ( 0 ) ) ;
157+ // Dust exposure is only decoupled from feerate for zero fee commitment channels.
158+ let is_zero_fee_comm = channel_type. supports_anchor_zero_fee_commitments ( ) ;
159+ debug_assert_eq ! ( is_zero_fee_comm, dust_exposure_limiting_feerate. is_none( ) ) ;
160+ if is_zero_fee_comm {
161+ debug_assert_eq ! ( feerate_per_kw, 0 ) ;
162+ debug_assert_eq ! ( excess_feerate_opt, Some ( 0 ) ) ;
163+ debug_assert_eq ! ( addl_nondust_htlc_count, 0 ) ;
164+ }
165+
166+ // Calculate inbound htlc count
167+ let inbound_htlcs_count =
168+ next_commitment_htlcs. iter ( ) . filter ( |htlc| !htlc. outbound ) . count ( ) ;
169+
170+ // Calculate balances after htlcs
171+ let value_to_counterparty_msat = ( channel_value_satoshis * 1000 )
172+ . checked_sub ( value_to_holder_msat)
173+ . expect ( "value_to_holder_msat outgrew the value of the channel!" ) ;
174+ let outbound_htlcs_value_msat: u64 = next_commitment_htlcs
175+ . iter ( )
176+ . filter_map ( |htlc| htlc. outbound . then_some ( htlc. amount_msat ) )
177+ . sum ( ) ;
178+ let inbound_htlcs_value_msat: u64 = next_commitment_htlcs
179+ . iter ( )
180+ . filter_map ( |htlc| ( !htlc. outbound ) . then_some ( htlc. amount_msat ) )
181+ . sum ( ) ;
182+ // Note there is no guarantee that the subtractions of the HTLC amounts don't
183+ // overflow, so we do not panic. Instead, we return `None` to signal an overflow
184+ // to channel, and let channel take the appropriate action.
185+ let value_to_holder_after_htlcs_msat =
186+ value_to_holder_msat. checked_sub ( outbound_htlcs_value_msat) ;
187+ let value_to_counterparty_after_htlcs_msat =
188+ value_to_counterparty_msat. checked_sub ( inbound_htlcs_value_msat) ;
189+
190+ // Subtract the anchors from the channel funder
191+ let ( holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) =
192+ subtract_addl_outputs (
193+ is_outbound_from_holder,
194+ value_to_holder_after_htlcs_msat,
195+ value_to_counterparty_after_htlcs_msat,
196+ channel_type,
197+ ) ;
198+
199+ // Increment the feerate by a buffer to calculate dust exposure
200+ let dust_buffer_feerate = get_dust_buffer_feerate ( feerate_per_kw) ;
201+
202+ // Calculate fees on commitment transaction
203+ let nondust_htlc_count = next_commitment_htlcs
204+ . iter ( )
205+ . filter ( |htlc| {
206+ !htlc. is_dust ( local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type)
207+ } )
208+ . count ( ) ;
209+ let commit_tx_fee_sat = commit_tx_fee_sat (
210+ feerate_per_kw,
211+ nondust_htlc_count + addl_nondust_htlc_count,
212+ channel_type,
213+ ) ;
214+
215+ // Calculate dust exposure on commitment transaction
216+ let dust_exposure_msat = next_commitment_htlcs
217+ . iter ( )
218+ . filter_map ( |htlc| {
219+ htlc. is_dust (
220+ local,
221+ dust_buffer_feerate,
222+ broadcaster_dust_limit_satoshis,
223+ channel_type,
224+ )
225+ . then_some ( htlc. amount_msat )
226+ } )
227+ . sum ( ) ;
228+
229+ // Count the excess fees on the counterparty's transaction as dust
230+ let ( dust_exposure_msat, extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat) =
231+ if let ( Some ( excess_feerate) , false ) = ( excess_feerate_opt, local) {
232+ let ( dust_exposure_msat, extra_nondust_htlc_exposure_msat) =
233+ excess_fees_on_counterparty_tx_dust_exposure_msat (
234+ & next_commitment_htlcs,
235+ dust_buffer_feerate,
236+ excess_feerate,
237+ broadcaster_dust_limit_satoshis,
238+ dust_exposure_msat,
239+ channel_type,
240+ ) ;
241+ ( dust_exposure_msat, Some ( extra_nondust_htlc_exposure_msat) )
242+ } else {
243+ ( dust_exposure_msat, None )
244+ } ;
245+
246+ NextCommitmentStats {
247+ inbound_htlcs_count,
248+ inbound_htlcs_value_msat,
249+ holder_balance_before_fee_msat,
250+ counterparty_balance_before_fee_msat,
251+ nondust_htlc_count : nondust_htlc_count + addl_nondust_htlc_count,
252+ commit_tx_fee_sat,
253+ dust_exposure_msat,
254+ extra_nondust_htlc_on_counterparty_tx_dust_exposure_msat,
255+ }
256+ }
37257 fn commit_tx_fee_sat (
38258 & self , feerate_per_kw : u32 , nondust_htlc_count : usize , channel_type : & ChannelTypeFeatures ,
39259 ) -> u64 {
@@ -74,7 +294,7 @@ impl TxBuilder for SpecTxBuilder {
74294 & self , local : bool , commitment_number : u64 , per_commitment_point : & PublicKey ,
75295 channel_parameters : & ChannelTransactionParameters , secp_ctx : & Secp256k1 < secp256k1:: All > ,
76296 value_to_self_msat : u64 , mut htlcs_in_tx : Vec < HTLCOutputInCommitment > , feerate_per_kw : u32 ,
77- broadcaster_dust_limit_sat : u64 , logger : & L ,
297+ broadcaster_dust_limit_satoshis : u64 , logger : & L ,
78298 ) -> ( CommitmentTransaction , CommitmentStats )
79299 where
80300 L :: Target : Logger ,
@@ -95,7 +315,7 @@ impl TxBuilder for SpecTxBuilder {
95315 // As required by the spec, round down
96316 feerate_per_kw as u64 * htlc_tx_weight / 1000
97317 } ;
98- amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat
318+ amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
99319 } ;
100320
101321 // Trim dust htlcs
@@ -107,7 +327,7 @@ impl TxBuilder for SpecTxBuilder {
107327 remote_htlc_total_msat += htlc. amount_msat ;
108328 }
109329 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 ) ;
330+ 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 ) ;
111331 false
112332 } else {
113333 true
@@ -142,13 +362,13 @@ impl TxBuilder for SpecTxBuilder {
142362 let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote } ;
143363 let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self } ;
144364
145- if to_broadcaster_value_sat >= broadcaster_dust_limit_sat {
365+ if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis {
146366 log_trace ! ( logger, " ...including {} output with value {}" , if local { "to_local" } else { "to_remote" } , to_broadcaster_value_sat) ;
147367 } else {
148368 to_broadcaster_value_sat = 0 ;
149369 }
150370
151- if to_countersignatory_value_sat >= broadcaster_dust_limit_sat {
371+ if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis {
152372 log_trace ! ( logger, " ...including {} output with value {}" , if local { "to_remote" } else { "to_local" } , to_countersignatory_value_sat) ;
153373 } else {
154374 to_countersignatory_value_sat = 0 ;
0 commit comments