Skip to content

Commit 14b83a3

Browse files
committed
Add TxBuilder::build_commitment_transaction
1 parent d62704b commit 14b83a3

File tree

3 files changed

+175
-51
lines changed

3 files changed

+175
-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
@@ -3969,16 +3970,9 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
39693970
let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis };
39703971
let feerate_per_kw = self.get_commitment_feerate(funding, generated_by_local);
39713972

3972-
let stats = self.build_commitment_stats(funding, local, generated_by_local, None, None);
3973-
let CommitmentStats {
3974-
total_fee_sat,
3975-
local_balance_before_fee_msat,
3976-
remote_balance_before_fee_msat
3977-
} = stats;
3978-
39793973
let num_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len();
39803974
let mut htlcs_included: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(num_htlcs);
3981-
let mut nondust_htlcs: Vec<HTLCOutputInCommitment> = Vec::with_capacity(num_htlcs);
3975+
let mut value_to_self_msat_offset = 0;
39823976

39833977
log_trace!(logger, "Building commitment transaction number {} (really {} xor {}) for channel {} for {}, generated by {} with fee {}...",
39843978
commitment_number, (INITIAL_COMMITMENT_NUMBER - commitment_number),
@@ -4001,13 +3995,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40013995
macro_rules! add_htlc_output {
40023996
($htlc: expr, $outbound: expr, $source: expr) => {
40033997
let htlc_in_tx = get_htlc_in_commitment!($htlc, $outbound == local);
4004-
htlcs_included.push((htlc_in_tx.clone(), $source));
4005-
if $htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_sat, funding.get_channel_type()) {
4006-
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);
4007-
} else {
4008-
log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $htlc.state, $htlc.htlc_id, $htlc.payment_hash, $htlc.amount_msat);
4009-
nondust_htlcs.push(htlc_in_tx);
4010-
}
3998+
htlcs_included.push((htlc_in_tx, $source));
40113999
}
40124000
}
40134001

@@ -4016,11 +4004,13 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40164004

40174005
for htlc in self.pending_inbound_htlcs.iter() {
40184006
if htlc.state.included_in_commitment(generated_by_local) {
4007+
log_trace!(logger, " ...including inbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
40194008
add_htlc_output!(htlc, false, None);
40204009
} else {
40214010
log_trace!(logger, " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
40224011
if let Some(preimage) = htlc.state.preimage() {
40234012
inbound_htlc_preimages.push(preimage);
4013+
value_to_self_msat_offset += htlc.amount_msat as i64;
40244014
}
40254015
}
40264016
};
@@ -4030,53 +4020,37 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
40304020
outbound_htlc_preimages.push(preimage);
40314021
}
40324022
if htlc.state.included_in_commitment(generated_by_local) {
4023+
log_trace!(logger, " ...including outbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
40334024
add_htlc_output!(htlc, true, Some(&htlc.source));
40344025
} else {
40354026
log_trace!(logger, " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
4027+
if htlc.state.preimage().is_some() {
4028+
value_to_self_msat_offset -= htlc.amount_msat as i64;
4029+
}
40364030
}
40374031
};
40384032

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

4067-
let channel_parameters =
4068-
if local { funding.channel_transaction_parameters.as_holder_broadcastable() }
4069-
else { funding.channel_transaction_parameters.as_counterparty_broadcastable() };
4070-
let tx = CommitmentTransaction::new(
4041+
let (tx, stats) = SpecTxBuilder {}.build_commitment_transaction(
4042+
local,
40714043
commitment_number,
40724044
per_commitment_point,
4073-
to_broadcaster_value_sat,
4074-
to_countersignatory_value_sat,
4075-
feerate_per_kw,
4076-
nondust_htlcs,
4077-
&channel_parameters,
4045+
&funding.channel_transaction_parameters,
40784046
&self.secp_ctx,
4047+
value_to_self_with_offset_msat,
4048+
htlcs_included.iter().map(|(htlc, _source)| htlc).cloned().collect(),
4049+
feerate_per_kw,
4050+
broadcaster_dust_limit_sat,
4051+
logger,
40794052
);
4053+
debug_assert_eq!(stats, self.build_commitment_stats(funding, local, generated_by_local, None, None));
40804054

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

lightning/src/sign/tx_builder.rs

Lines changed: 136 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)]
@@ -54,4 +69,124 @@ impl TxBuilder for SpecTxBuilder {
5469
remote_balance_before_fee_msat,
5570
}
5671
}
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+
}
57192
}

0 commit comments

Comments
 (0)