lightning/sign/
tx_builder.rs

1//! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type
2#![allow(dead_code)]
3
4use core::cmp;
5use core::ops::Deref;
6
7use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
8
9use crate::ln::chan_utils::{
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,
13};
14use crate::ln::channel::{CommitmentStats, ANCHOR_OUTPUT_VALUE_SATOSHI};
15use crate::prelude::*;
16use crate::types::features::ChannelTypeFeatures;
17use crate::util::logger::Logger;
18
19pub(crate) struct HTLCAmountDirection {
20	pub outbound: bool,
21	pub amount_msat: u64,
22}
23
24impl 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
37pub(crate) struct NextCommitmentStats {
38	pub is_outbound_from_holder: bool,
39	pub inbound_htlcs_count: usize,
40	pub inbound_htlcs_value_msat: u64,
41	pub holder_balance_before_fee_msat: u64,
42	pub counterparty_balance_before_fee_msat: u64,
43	pub nondust_htlc_count: usize,
44	pub commit_tx_fee_sat: u64,
45	pub dust_exposure_msat: u64,
46	pub extra_accepted_htlc_dust_exposure_msat: u64,
47}
48
49impl NextCommitmentStats {
50	pub(crate) fn get_holder_counterparty_balances_incl_fee_msat(&self) -> Result<(u64, u64), ()> {
51		if self.is_outbound_from_holder {
52			Ok((
53				self.holder_balance_before_fee_msat
54					.checked_sub(self.commit_tx_fee_sat * 1000)
55					.ok_or(())?,
56				self.counterparty_balance_before_fee_msat,
57			))
58		} else {
59			Ok((
60				self.holder_balance_before_fee_msat,
61				self.counterparty_balance_before_fee_msat
62					.checked_sub(self.commit_tx_fee_sat * 1000)
63					.ok_or(())?,
64			))
65		}
66	}
67}
68
69fn commit_plus_htlc_tx_fees_msat(
70	local: bool, next_commitment_htlcs: &[HTLCAmountDirection], dust_buffer_feerate: u32,
71	feerate: u32, broadcaster_dust_limit_satoshis: u64, channel_type: &ChannelTypeFeatures,
72) -> (u64, u64) {
73	let accepted_nondust_htlcs = next_commitment_htlcs
74		.iter()
75		.filter(|htlc| {
76			htlc.outbound != local
77				&& !htlc.is_dust(
78					local,
79					dust_buffer_feerate,
80					broadcaster_dust_limit_satoshis,
81					channel_type,
82				)
83		})
84		.count();
85	let offered_nondust_htlcs = next_commitment_htlcs
86		.iter()
87		.filter(|htlc| {
88			htlc.outbound == local
89				&& !htlc.is_dust(
90					local,
91					dust_buffer_feerate,
92					broadcaster_dust_limit_satoshis,
93					channel_type,
94				)
95		})
96		.count();
97
98	let commitment_fee_sat =
99		commit_tx_fee_sat(feerate, accepted_nondust_htlcs + offered_nondust_htlcs, channel_type);
100	let second_stage_fees_sat =
101		htlc_tx_fees_sat(feerate, accepted_nondust_htlcs, offered_nondust_htlcs, channel_type);
102	let total_fees_msat = (commitment_fee_sat + second_stage_fees_sat) * 1000;
103
104	let extra_accepted_htlc_commitment_fee_sat = commit_tx_fee_sat(
105		feerate,
106		accepted_nondust_htlcs + 1 + offered_nondust_htlcs,
107		channel_type,
108	);
109	let extra_accepted_htlc_second_stage_fees_sat =
110		htlc_tx_fees_sat(feerate, accepted_nondust_htlcs + 1, offered_nondust_htlcs, channel_type);
111	let extra_accepted_htlc_total_fees_msat =
112		(extra_accepted_htlc_commitment_fee_sat + extra_accepted_htlc_second_stage_fees_sat) * 1000;
113
114	(total_fees_msat, extra_accepted_htlc_total_fees_msat)
115}
116
117fn subtract_addl_outputs(
118	is_outbound_from_holder: bool, value_to_self_after_htlcs_msat: u64,
119	value_to_remote_after_htlcs_msat: u64, channel_type: &ChannelTypeFeatures,
120) -> Result<(u64, u64), ()> {
121	let total_anchors_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() {
122		ANCHOR_OUTPUT_VALUE_SATOSHI * 2
123	} else {
124		0
125	};
126
127	// We MUST use checked subs here, as the funder's balance is not guaranteed to be greater
128	// than or equal to `total_anchors_sat`.
129	//
130	// This is because when the remote party sends an `update_fee` message, we build the new
131	// commitment transaction *before* checking whether the remote party's balance is enough to
132	// cover the total anchor sum.
133
134	if is_outbound_from_holder {
135		Ok((
136			value_to_self_after_htlcs_msat.checked_sub(total_anchors_sat * 1000).ok_or(())?,
137			value_to_remote_after_htlcs_msat,
138		))
139	} else {
140		Ok((
141			value_to_self_after_htlcs_msat,
142			value_to_remote_after_htlcs_msat.checked_sub(total_anchors_sat * 1000).ok_or(())?,
143		))
144	}
145}
146
147fn get_dust_buffer_feerate(feerate_per_kw: u32) -> u32 {
148	// When calculating our exposure to dust HTLCs, we assume that the channel feerate
149	// may, at any point, increase by at least 10 sat/vB (i.e 2530 sat/kWU) or 25%,
150	// whichever is higher. This ensures that we aren't suddenly exposed to significantly
151	// more dust balance if the feerate increases when we have several HTLCs pending
152	// which are near the dust limit.
153	let feerate_plus_quarter = feerate_per_kw.checked_mul(1250).map(|v| v / 1000);
154	cmp::max(feerate_per_kw.saturating_add(2530), feerate_plus_quarter.unwrap_or(u32::MAX))
155}
156
157pub(crate) trait TxBuilder {
158	fn get_next_commitment_stats(
159		&self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64,
160		value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection],
161		addl_nondust_htlc_count: usize, feerate_per_kw: u32,
162		dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
163		channel_type: &ChannelTypeFeatures,
164	) -> Result<NextCommitmentStats, ()>;
165	fn commit_tx_fee_sat(
166		&self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures,
167	) -> u64;
168	fn subtract_non_htlc_outputs(
169		&self, is_outbound_from_holder: bool, value_to_self_after_htlcs: u64,
170		value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
171	) -> (u64, u64);
172	fn build_commitment_transaction<L: Deref>(
173		&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
174		channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
175		value_to_self_msat: u64, htlcs_in_tx: Vec<HTLCOutputInCommitment>, feerate_per_kw: u32,
176		broadcaster_dust_limit_satoshis: u64, logger: &L,
177	) -> (CommitmentTransaction, CommitmentStats)
178	where
179		L::Target: Logger;
180}
181
182pub(crate) struct SpecTxBuilder {}
183
184impl TxBuilder for SpecTxBuilder {
185	fn get_next_commitment_stats(
186		&self, local: bool, is_outbound_from_holder: bool, channel_value_satoshis: u64,
187		value_to_holder_msat: u64, next_commitment_htlcs: &[HTLCAmountDirection],
188		addl_nondust_htlc_count: usize, feerate_per_kw: u32,
189		dust_exposure_limiting_feerate: Option<u32>, broadcaster_dust_limit_satoshis: u64,
190		channel_type: &ChannelTypeFeatures,
191	) -> Result<NextCommitmentStats, ()> {
192		let excess_feerate =
193			feerate_per_kw.saturating_sub(dust_exposure_limiting_feerate.unwrap_or(feerate_per_kw));
194		if channel_type.supports_anchor_zero_fee_commitments() {
195			debug_assert_eq!(feerate_per_kw, 0);
196			debug_assert_eq!(excess_feerate, 0);
197			debug_assert_eq!(addl_nondust_htlc_count, 0);
198		}
199
200		// Calculate inbound htlc count
201		let inbound_htlcs_count =
202			next_commitment_htlcs.iter().filter(|htlc| !htlc.outbound).count();
203
204		// Calculate balances after htlcs
205		let value_to_counterparty_msat =
206			(channel_value_satoshis * 1000).checked_sub(value_to_holder_msat).ok_or(())?;
207		let outbound_htlcs_value_msat: u64 = next_commitment_htlcs
208			.iter()
209			.filter_map(|htlc| htlc.outbound.then_some(htlc.amount_msat))
210			.sum();
211		let inbound_htlcs_value_msat: u64 = next_commitment_htlcs
212			.iter()
213			.filter_map(|htlc| (!htlc.outbound).then_some(htlc.amount_msat))
214			.sum();
215		let value_to_holder_after_htlcs_msat =
216			value_to_holder_msat.checked_sub(outbound_htlcs_value_msat).ok_or(())?;
217		let value_to_counterparty_after_htlcs_msat =
218			value_to_counterparty_msat.checked_sub(inbound_htlcs_value_msat).ok_or(())?;
219
220		// Subtract the anchors from the channel funder
221		let (holder_balance_before_fee_msat, counterparty_balance_before_fee_msat) =
222			subtract_addl_outputs(
223				is_outbound_from_holder,
224				value_to_holder_after_htlcs_msat,
225				value_to_counterparty_after_htlcs_msat,
226				channel_type,
227			)?;
228
229		// Increment the feerate by a buffer to calculate dust exposure
230		let dust_buffer_feerate = get_dust_buffer_feerate(feerate_per_kw);
231
232		// Calculate fees on commitment transaction
233		let nondust_htlc_count = next_commitment_htlcs
234			.iter()
235			.filter(|htlc| {
236				!htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_satoshis, channel_type)
237			})
238			.count();
239		let commit_tx_fee_sat = commit_tx_fee_sat(
240			feerate_per_kw,
241			nondust_htlc_count + addl_nondust_htlc_count,
242			channel_type,
243		);
244
245		// Calculate dust exposure on commitment transaction
246		let dust_exposure_msat = next_commitment_htlcs
247			.iter()
248			.filter_map(|htlc| {
249				htlc.is_dust(
250					local,
251					dust_buffer_feerate,
252					broadcaster_dust_limit_satoshis,
253					channel_type,
254				)
255				.then_some(htlc.amount_msat)
256			})
257			.sum();
258
259		// Add any excess fees to dust exposure on counterparty transactions
260		let (dust_exposure_msat, extra_accepted_htlc_dust_exposure_msat) = if local {
261			(dust_exposure_msat, dust_exposure_msat)
262		} else {
263			let (excess_fees_msat, extra_accepted_htlc_excess_fees_msat) =
264				commit_plus_htlc_tx_fees_msat(
265					local,
266					&next_commitment_htlcs,
267					dust_buffer_feerate,
268					excess_feerate,
269					broadcaster_dust_limit_satoshis,
270					channel_type,
271				);
272			(
273				dust_exposure_msat + excess_fees_msat,
274				dust_exposure_msat + extra_accepted_htlc_excess_fees_msat,
275			)
276		};
277
278		Ok(NextCommitmentStats {
279			is_outbound_from_holder,
280			inbound_htlcs_count,
281			inbound_htlcs_value_msat,
282			holder_balance_before_fee_msat,
283			counterparty_balance_before_fee_msat,
284			nondust_htlc_count: nondust_htlc_count + addl_nondust_htlc_count,
285			commit_tx_fee_sat,
286			dust_exposure_msat,
287			extra_accepted_htlc_dust_exposure_msat,
288		})
289	}
290	fn commit_tx_fee_sat(
291		&self, feerate_per_kw: u32, nondust_htlc_count: usize, channel_type: &ChannelTypeFeatures,
292	) -> u64 {
293		commit_tx_fee_sat(feerate_per_kw, nondust_htlc_count, channel_type)
294	}
295	fn subtract_non_htlc_outputs(
296		&self, is_outbound_from_holder: bool, value_to_self_after_htlcs: u64,
297		value_to_remote_after_htlcs: u64, channel_type: &ChannelTypeFeatures,
298	) -> (u64, u64) {
299		let total_anchors_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() {
300			ANCHOR_OUTPUT_VALUE_SATOSHI * 2
301		} else {
302			0
303		};
304
305		let mut local_balance_before_fee_msat = value_to_self_after_htlcs;
306		let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs;
307
308		// We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
309		// than or equal to `total_anchors_sat`.
310		//
311		// This is because when the remote party sends an `update_fee` message, we build the new
312		// commitment transaction *before* checking whether the remote party's balance is enough to
313		// cover the total anchor sum.
314
315		if is_outbound_from_holder {
316			local_balance_before_fee_msat =
317				local_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000);
318		} else {
319			remote_balance_before_fee_msat =
320				remote_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000);
321		}
322
323		(local_balance_before_fee_msat, remote_balance_before_fee_msat)
324	}
325	fn build_commitment_transaction<L: Deref>(
326		&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
327		channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
328		value_to_self_msat: u64, mut htlcs_in_tx: Vec<HTLCOutputInCommitment>, feerate_per_kw: u32,
329		broadcaster_dust_limit_satoshis: u64, logger: &L,
330	) -> (CommitmentTransaction, CommitmentStats)
331	where
332		L::Target: Logger,
333	{
334		let mut local_htlc_total_msat = 0;
335		let mut remote_htlc_total_msat = 0;
336		let channel_type = &channel_parameters.channel_type_features;
337
338		let is_dust = |offered: bool, amount_msat: u64| -> bool {
339			let htlc_tx_fee_sat = if channel_type.supports_anchors_zero_fee_htlc_tx() {
340				0
341			} else {
342				let htlc_tx_weight = if offered {
343					htlc_timeout_tx_weight(channel_type)
344				} else {
345					htlc_success_tx_weight(channel_type)
346				};
347				// As required by the spec, round down
348				feerate_per_kw as u64 * htlc_tx_weight / 1000
349			};
350			amount_msat / 1000 < broadcaster_dust_limit_satoshis + htlc_tx_fee_sat
351		};
352
353		// Trim dust htlcs
354		htlcs_in_tx.retain(|htlc| {
355			if htlc.offered == local {
356				// This is an outbound htlc
357				local_htlc_total_msat += htlc.amount_msat;
358			} else {
359				remote_htlc_total_msat += htlc.amount_msat;
360			}
361			if is_dust(htlc.offered, htlc.amount_msat) {
362				log_trace!(
363					logger,
364					"   ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}",
365					if htlc.offered == local { "outbound" } else { "inbound" },
366					htlc.amount_msat / 1000,
367					htlc.payment_hash,
368					broadcaster_dust_limit_satoshis
369				);
370				false
371			} else {
372				true
373			}
374		});
375
376		// # Panics
377		//
378		// The value going to each party MUST be 0 or positive, even if all HTLCs pending in the
379		// commitment clear by failure.
380
381		let commit_tx_fee_sat = self.commit_tx_fee_sat(
382			feerate_per_kw,
383			htlcs_in_tx.len(),
384			&channel_parameters.channel_type_features,
385		);
386		let value_to_self_after_htlcs_msat =
387			value_to_self_msat.checked_sub(local_htlc_total_msat).unwrap();
388		let value_to_remote_after_htlcs_msat = (channel_parameters.channel_value_satoshis * 1000)
389			.checked_sub(value_to_self_msat)
390			.unwrap()
391			.checked_sub(remote_htlc_total_msat)
392			.unwrap();
393		let (local_balance_before_fee_msat, remote_balance_before_fee_msat) = self
394			.subtract_non_htlc_outputs(
395				channel_parameters.is_outbound_from_holder,
396				value_to_self_after_htlcs_msat,
397				value_to_remote_after_htlcs_msat,
398				&channel_parameters.channel_type_features,
399			);
400
401		// We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
402		// than or equal to `commit_tx_fee_sat`.
403		//
404		// This is because when the remote party sends an `update_fee` message, we build the new
405		// commitment transaction *before* checking whether the remote party's balance is enough to
406		// cover the total fee.
407
408		let (value_to_self, value_to_remote) = if channel_parameters.is_outbound_from_holder {
409			(
410				(local_balance_before_fee_msat / 1000).saturating_sub(commit_tx_fee_sat),
411				remote_balance_before_fee_msat / 1000,
412			)
413		} else {
414			(
415				local_balance_before_fee_msat / 1000,
416				(remote_balance_before_fee_msat / 1000).saturating_sub(commit_tx_fee_sat),
417			)
418		};
419
420		let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote };
421		let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self };
422
423		if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis {
424			log_trace!(
425				logger,
426				"   ...including {} output with value {}",
427				if local { "to_local" } else { "to_remote" },
428				to_broadcaster_value_sat
429			);
430		} else {
431			to_broadcaster_value_sat = 0;
432		}
433
434		if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis {
435			log_trace!(
436				logger,
437				"   ...including {} output with value {}",
438				if local { "to_remote" } else { "to_local" },
439				to_countersignatory_value_sat
440			);
441		} else {
442			to_countersignatory_value_sat = 0;
443		}
444
445		let directed_parameters = if local {
446			channel_parameters.as_holder_broadcastable()
447		} else {
448			channel_parameters.as_counterparty_broadcastable()
449		};
450		let tx = CommitmentTransaction::new(
451			commitment_number,
452			per_commitment_point,
453			to_broadcaster_value_sat,
454			to_countersignatory_value_sat,
455			feerate_per_kw,
456			htlcs_in_tx,
457			&directed_parameters,
458			secp_ctx,
459		);
460
461		(
462			tx,
463			CommitmentStats {
464				commit_tx_fee_sat,
465				local_balance_before_fee_msat,
466				remote_balance_before_fee_msat,
467			},
468		)
469	}
470}