Skip to content

Commit 6aa0848

Browse files
committed
Add TxBuilder::build_commitment_transaction
1 parent a3903dd commit 6aa0848

File tree

3 files changed

+182
-51
lines changed

3 files changed

+182
-51
lines changed

lightning/src/ln/chan_utils.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,21 @@ impl HTLCOutputInCommitment {
622622
&& self.cltv_expiry == other.cltv_expiry
623623
&& self.payment_hash == other.payment_hash
624624
}
625+
626+
pub(crate) fn is_dust(&self, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, features: &ChannelTypeFeatures) -> bool {
627+
let htlc_tx_fee_sat = if features.supports_anchors_zero_fee_htlc_tx() {
628+
0
629+
} else {
630+
let htlc_tx_weight = if self.offered {
631+
htlc_timeout_tx_weight(features)
632+
} else {
633+
htlc_success_tx_weight(features)
634+
};
635+
// As required by the spec, round down
636+
feerate_per_kw as u64 * htlc_tx_weight / 1000
637+
};
638+
self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat
639+
}
625640
}
626641

627642
impl_writeable_tlv_based!(HTLCOutputInCommitment, {

lightning/src/ln/channel.rs

Lines changed: 24 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,7 @@ struct CommitmentData<'a> {
988988
}
989989

990990
/// A struct gathering stats on a commitment transaction, either local or remote.
991+
#[derive(Debug, PartialEq)]
991992
pub(crate) struct CommitmentStats {
992993
pub(crate) total_fee_sat: u64, // the total fee included in the transaction
993994
pub(crate) local_balance_before_fee_msat: u64, // local balance before fees *not* considering dust limits
@@ -3965,16 +3966,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
39653966
let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis };
39663967
let feerate_per_kw = self.get_commitment_feerate(funding, generated_by_local);
39673968

3968-
let stats = self.build_commitment_stats(funding, local, generated_by_local, None, None);
3969-
let CommitmentStats {
3970-
total_fee_sat,
3971-
local_balance_before_fee_msat,
3972-
remote_balance_before_fee_msat
3973-
} = stats;
3974-
39753969
let num_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len();
39763970
let mut htlcs_included: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(num_htlcs);
3977-
let mut nondust_htlcs: Vec<HTLCOutputInCommitment> = Vec::with_capacity(num_htlcs);
3971+
let mut value_to_self_msat_offset = 0;
39783972

39793973
log_trace!(logger, "Building commitment transaction number {} (really {} xor {}) for channel {} for {}, generated by {} with fee {}...",
39803974
commitment_number, (INITIAL_COMMITMENT_NUMBER - commitment_number),
@@ -3997,13 +3991,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
39973991
macro_rules! add_htlc_output {
39983992
($htlc: expr, $outbound: expr, $source: expr) => {
39993993
let htlc_in_tx = get_htlc_in_commitment!($htlc, $outbound == local);
4000-
htlcs_included.push((htlc_in_tx.clone(), $source));
4001-
if $htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_sat, funding.get_channel_type()) {
4002-
log_trace!(logger, " ...including {} {} dust HTLC {} (hash {}) with value {} due to dust limit", if $outbound { "outbound" } else { "inbound" }, $htlc.state, $htlc.htlc_id, $htlc.payment_hash, $htlc.amount_msat);
4003-
} else {
4004-
log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $htlc.state, $htlc.htlc_id, $htlc.payment_hash, $htlc.amount_msat);
4005-
nondust_htlcs.push(htlc_in_tx);
4006-
}
3994+
htlcs_included.push((htlc_in_tx, $source));
40073995
}
40083996
}
40093997

@@ -4012,11 +4000,13 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40124000

40134001
for htlc in self.pending_inbound_htlcs.iter() {
40144002
if htlc.state.included_in_commitment(generated_by_local) {
4003+
log_trace!(logger, " ...including inbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
40154004
add_htlc_output!(htlc, false, None);
40164005
} else {
40174006
log_trace!(logger, " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
40184007
if let Some(preimage) = htlc.state.preimage() {
40194008
inbound_htlc_preimages.push(preimage);
4009+
value_to_self_msat_offset += htlc.amount_msat as i64;
40204010
}
40214011
}
40224012
};
@@ -4026,53 +4016,37 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40264016
outbound_htlc_preimages.push(preimage);
40274017
}
40284018
if htlc.state.included_in_commitment(generated_by_local) {
4019+
log_trace!(logger, " ...including outbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
40294020
add_htlc_output!(htlc, true, Some(&htlc.source));
40304021
} else {
40314022
log_trace!(logger, " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
4023+
if htlc.state.preimage().is_some() {
4024+
value_to_self_msat_offset -= htlc.amount_msat as i64;
4025+
}
40324026
}
40334027
};
40344028

4035-
// We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
4036-
// than or equal to the sum of `total_fee_sat` and `total_anchors_sat`.
4029+
// # Panics
40374030
//
4038-
// This is because when the remote party sends an `update_fee` message, we build the new
4039-
// commitment transaction *before* checking whether the remote party's balance is enough to
4040-
// cover the total fee and the anchors.
4041-
4042-
let (value_to_self, value_to_remote) = if funding.is_outbound() {
4043-
((local_balance_before_fee_msat / 1000).saturating_sub(total_fee_sat), remote_balance_before_fee_msat / 1000)
4044-
} else {
4045-
(local_balance_before_fee_msat / 1000, (remote_balance_before_fee_msat / 1000).saturating_sub(total_fee_sat))
4046-
};
4047-
4048-
let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote };
4049-
let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self };
4050-
4051-
if to_broadcaster_value_sat >= broadcaster_dust_limit_sat {
4052-
log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, to_broadcaster_value_sat);
4053-
} else {
4054-
to_broadcaster_value_sat = 0;
4055-
}
4056-
4057-
if to_countersignatory_value_sat >= broadcaster_dust_limit_sat {
4058-
log_trace!(logger, " ...including {} output with value {}", if local { "to_remote" } else { "to_local" }, to_countersignatory_value_sat);
4059-
} else {
4060-
to_countersignatory_value_sat = 0;
4061-
}
4031+
// While we expect `value_to_self_msat_offset` to be negative in some cases, the local
4032+
// balance MUST remain greater than or equal to 0.
4033+
4034+
// TODO: When MSRV >= 1.66.0, use u64::checked_add_signed
4035+
let value_to_self_with_offset_msat = (funding.value_to_self_msat as i64 + value_to_self_msat_offset).try_into().unwrap();
40624036

4063-
let channel_parameters =
4064-
if local { funding.channel_transaction_parameters.as_holder_broadcastable() }
4065-
else { funding.channel_transaction_parameters.as_counterparty_broadcastable() };
4066-
let tx = CommitmentTransaction::new(
4037+
let (tx, stats) = SpecTxBuilder {}.build_commitment_transaction(
4038+
local,
40674039
commitment_number,
40684040
per_commitment_point,
4069-
to_broadcaster_value_sat,
4070-
to_countersignatory_value_sat,
4071-
feerate_per_kw,
4072-
nondust_htlcs,
4073-
&channel_parameters,
4041+
&funding.channel_transaction_parameters,
40744042
&self.secp_ctx,
4043+
value_to_self_with_offset_msat,
4044+
htlcs_included.iter().map(|(htlc, _source)| htlc).cloned().collect(),
4045+
feerate_per_kw,
4046+
broadcaster_dust_limit_sat,
4047+
logger,
40754048
);
4049+
debug_assert_eq!(stats, self.build_commitment_stats(funding, local, generated_by_local, None, None));
40764050

40774051
// This populates the HTLC-source table with the indices from the HTLCs in the commitment
40784052
// transaction.

lightning/src/sign/tx_builder.rs

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
//! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type
22
3-
use crate::ln::chan_utils::commit_tx_fee_sat;
3+
use core::ops::Deref;
4+
5+
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
6+
7+
use crate::ln::chan_utils::{
8+
commit_tx_fee_sat, ChannelTransactionParameters, CommitmentTransaction, HTLCOutputInCommitment,
9+
};
410
use crate::ln::channel::{CommitmentStats, ANCHOR_OUTPUT_VALUE_SATOSHI};
511
use crate::prelude::*;
612
use crate::types::features::ChannelTypeFeatures;
13+
use crate::util::logger::Logger;
714

815
pub(crate) trait TxBuilder {
916
fn build_commitment_stats(
1017
&self, is_outbound_from_holder: bool, feerate_per_kw: u32, nondust_htlc_count: usize,
1118
value_to_self_after_htlcs: u64, value_to_remote_after_htlcs: u64,
1219
channel_type: &ChannelTypeFeatures,
1320
) -> CommitmentStats;
21+
fn build_commitment_transaction<L: Deref>(
22+
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
23+
channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
24+
value_to_self_msat: u64, htlcs_in_tx: Vec<HTLCOutputInCommitment>, feerate_per_kw: u32,
25+
broadcaster_dust_limit_satoshis: u64, logger: &L,
26+
) -> (CommitmentTransaction, CommitmentStats)
27+
where
28+
L::Target: Logger;
1429
}
1530

1631
#[derive(Clone, Debug, Default)]
@@ -33,6 +48,13 @@ impl TxBuilder for SpecTxBuilder {
3348
let mut local_balance_before_fee_msat = value_to_self_after_htlcs;
3449
let mut remote_balance_before_fee_msat = value_to_remote_after_htlcs;
3550

51+
// We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
52+
// than or equal to `total_anchors_sat`.
53+
//
54+
// This is because when the remote party sends an `update_fee` message, we build the new
55+
// commitment transaction *before* checking whether the remote party's balance is enough to
56+
// cover the total anchor sum.
57+
3658
if is_outbound_from_holder {
3759
local_balance_before_fee_msat =
3860
local_balance_before_fee_msat.saturating_sub(total_anchors_sat * 1000);
@@ -47,4 +69,124 @@ impl TxBuilder for SpecTxBuilder {
4769
remote_balance_before_fee_msat,
4870
}
4971
}
72+
fn build_commitment_transaction<L: Deref>(
73+
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
74+
channel_parameters: &ChannelTransactionParameters, secp_ctx: &Secp256k1<secp256k1::All>,
75+
value_to_self_msat: u64, mut htlcs_in_tx: Vec<HTLCOutputInCommitment>, feerate_per_kw: u32,
76+
broadcaster_dust_limit_satoshis: u64, logger: &L,
77+
) -> (CommitmentTransaction, CommitmentStats)
78+
where
79+
L::Target: Logger,
80+
{
81+
let mut local_htlc_total_msat = 0;
82+
let mut remote_htlc_total_msat = 0;
83+
84+
// Trim dust htlcs
85+
htlcs_in_tx.retain(|htlc| {
86+
if htlc.offered == local {
87+
// This is an outbound htlc
88+
local_htlc_total_msat += htlc.amount_msat;
89+
} else {
90+
remote_htlc_total_msat += htlc.amount_msat;
91+
}
92+
if htlc.is_dust(
93+
feerate_per_kw,
94+
broadcaster_dust_limit_satoshis,
95+
&channel_parameters.channel_type_features,
96+
) {
97+
log_trace!(
98+
logger,
99+
" ...trimming {} HTLC with value {}sat, hash {}, due to dust limit {}",
100+
if htlc.offered == local { "outbound" } else { "inbound" },
101+
htlc.amount_msat / 1000,
102+
htlc.payment_hash,
103+
broadcaster_dust_limit_satoshis
104+
);
105+
false
106+
} else {
107+
true
108+
}
109+
});
110+
111+
// # Panics
112+
//
113+
// The value going to each party MUST be 0 or positive, even if all HTLCs pending in the
114+
// commitment clear by failure.
115+
116+
let stats = self.build_commitment_stats(
117+
channel_parameters.is_outbound_from_holder,
118+
feerate_per_kw,
119+
htlcs_in_tx.len(),
120+
value_to_self_msat.checked_sub(local_htlc_total_msat).unwrap(),
121+
(channel_parameters.channel_value_satoshis * 1000)
122+
.checked_sub(value_to_self_msat)
123+
.unwrap()
124+
.checked_sub(remote_htlc_total_msat)
125+
.unwrap(),
126+
&channel_parameters.channel_type_features,
127+
);
128+
129+
// We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
130+
// than or equal to `total_fee_sat`.
131+
//
132+
// This is because when the remote party sends an `update_fee` message, we build the new
133+
// commitment transaction *before* checking whether the remote party's balance is enough to
134+
// cover the total fee.
135+
136+
let (value_to_self, value_to_remote) = if channel_parameters.is_outbound_from_holder {
137+
(
138+
(stats.local_balance_before_fee_msat / 1000).saturating_sub(stats.total_fee_sat),
139+
stats.remote_balance_before_fee_msat / 1000,
140+
)
141+
} else {
142+
(
143+
stats.local_balance_before_fee_msat / 1000,
144+
(stats.remote_balance_before_fee_msat / 1000).saturating_sub(stats.total_fee_sat),
145+
)
146+
};
147+
148+
let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote };
149+
let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self };
150+
151+
if to_broadcaster_value_sat >= broadcaster_dust_limit_satoshis {
152+
log_trace!(
153+
logger,
154+
" ...including {} output with value {}",
155+
if local { "to_local" } else { "to_remote" },
156+
to_broadcaster_value_sat
157+
);
158+
} else {
159+
to_broadcaster_value_sat = 0;
160+
}
161+
162+
if to_countersignatory_value_sat >= broadcaster_dust_limit_satoshis {
163+
log_trace!(
164+
logger,
165+
" ...including {} output with value {}",
166+
if local { "to_remote" } else { "to_local" },
167+
to_countersignatory_value_sat
168+
);
169+
} else {
170+
to_countersignatory_value_sat = 0;
171+
}
172+
173+
let directed_parameters = if local {
174+
channel_parameters.as_holder_broadcastable()
175+
} else {
176+
channel_parameters.as_counterparty_broadcastable()
177+
};
178+
179+
let tx = CommitmentTransaction::new(
180+
commitment_number,
181+
per_commitment_point,
182+
to_broadcaster_value_sat,
183+
to_countersignatory_value_sat,
184+
feerate_per_kw,
185+
htlcs_in_tx,
186+
&directed_parameters,
187+
secp_ctx,
188+
);
189+
190+
(tx, stats)
191+
}
50192
}

0 commit comments

Comments
 (0)