Skip to content

Commit 2f19710

Browse files
committed
Add TxBuilder::build_commitment_transaction
1 parent ad1778a commit 2f19710

File tree

3 files changed

+176
-50
lines changed

3 files changed

+176
-50
lines changed

lightning/src/ln/chan_utils.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,23 @@ impl HTLCOutputInCommitment {
654654
&& self.cltv_expiry == other.cltv_expiry
655655
&& self.payment_hash == other.payment_hash
656656
}
657+
658+
pub(crate) fn is_dust(
659+
&self, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, features: &ChannelTypeFeatures,
660+
) -> bool {
661+
let htlc_tx_fee_sat = if features.supports_anchors_zero_fee_htlc_tx() {
662+
0
663+
} else {
664+
let htlc_tx_weight = if self.offered {
665+
htlc_timeout_tx_weight(features)
666+
} else {
667+
htlc_success_tx_weight(features)
668+
};
669+
// As required by the spec, round down
670+
feerate_per_kw as u64 * htlc_tx_weight / 1000
671+
};
672+
self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat
673+
}
657674
}
658675

659676
impl_writeable_tlv_based!(HTLCOutputInCommitment, {

lightning/src/ln/channel.rs

Lines changed: 23 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,7 @@ struct CommitmentData<'a> {
11321132
}
11331133

11341134
/// A struct gathering stats on a commitment transaction, either local or remote.
1135+
#[derive(Debug, PartialEq)]
11351136
pub(crate) struct CommitmentStats {
11361137
pub(crate) total_fee_sat: u64, // the total fee included in the transaction
11371138
pub(crate) local_balance_before_fee_msat: u64, // local balance before fees and anchors *not* considering dust limits
@@ -4570,16 +4571,9 @@ where
45704571
let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis };
45714572
let feerate_per_kw = self.get_commitment_feerate(funding, generated_by_local);
45724573

4573-
let stats = self.build_commitment_stats(funding, local, generated_by_local, None, None);
4574-
let CommitmentStats {
4575-
total_fee_sat,
4576-
local_balance_before_fee_msat,
4577-
remote_balance_before_fee_msat
4578-
} = stats;
4579-
45804574
let num_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len();
45814575
let mut htlcs_included: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(num_htlcs);
4582-
let mut nondust_htlcs: Vec<HTLCOutputInCommitment> = Vec::with_capacity(num_htlcs);
4576+
let mut value_to_self_msat_offset = 0;
45834577

45844578
log_trace!(logger, "Building commitment transaction number {} (really {} xor {}) for channel {} for {}, generated by {} with fee {}...",
45854579
commitment_number, (INITIAL_COMMITMENT_NUMBER - commitment_number),
@@ -4602,13 +4596,7 @@ where
46024596
macro_rules! add_htlc_output {
46034597
($htlc: expr, $outbound: expr, $source: expr) => {
46044598
let htlc_in_tx = get_htlc_in_commitment!($htlc, $outbound == local);
4605-
htlcs_included.push((htlc_in_tx.clone(), $source));
4606-
if $htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_sat, funding.get_channel_type()) {
4607-
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);
4608-
} else {
4609-
log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $htlc.state, $htlc.htlc_id, $htlc.payment_hash, $htlc.amount_msat);
4610-
nondust_htlcs.push(htlc_in_tx);
4611-
}
4599+
htlcs_included.push((htlc_in_tx, $source));
46124600
}
46134601
}
46144602

@@ -4617,11 +4605,13 @@ where
46174605

46184606
for htlc in self.pending_inbound_htlcs.iter() {
46194607
if htlc.state.included_in_commitment(generated_by_local) {
4608+
log_trace!(logger, " ...including inbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
46204609
add_htlc_output!(htlc, false, None);
46214610
} else {
46224611
log_trace!(logger, " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
46234612
if let Some(preimage) = htlc.state.preimage() {
46244613
inbound_htlc_preimages.push(preimage);
4614+
value_to_self_msat_offset += htlc.amount_msat as i64;
46254615
}
46264616
}
46274617
};
@@ -4631,53 +4621,37 @@ where
46314621
outbound_htlc_preimages.push(preimage);
46324622
}
46334623
if htlc.state.included_in_commitment(generated_by_local) {
4624+
log_trace!(logger, " ...including outbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
46344625
add_htlc_output!(htlc, true, Some(&htlc.source));
46354626
} else {
46364627
log_trace!(logger, " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
4628+
if htlc.state.preimage().is_some() {
4629+
value_to_self_msat_offset -= htlc.amount_msat as i64;
4630+
}
46374631
}
46384632
};
46394633

4640-
// We MUST use saturating subs here, as the funder's balance is not guaranteed to be greater
4641-
// than or equal to the sum of `total_fee_sat` and `total_anchors_sat`.
4634+
// # Panics
46424635
//
4643-
// This is because when the remote party sends an `update_fee` message, we build the new
4644-
// commitment transaction *before* checking whether the remote party's balance is enough to
4645-
// cover the total fee and the anchors.
4636+
// While we expect `value_to_self_msat_offset` to be negative in some cases, the local
4637+
// balance MUST remain greater than or equal to 0.
46464638

4647-
let (value_to_self, value_to_remote) = if funding.is_outbound() {
4648-
((local_balance_before_fee_msat / 1000).saturating_sub(total_fee_sat), remote_balance_before_fee_msat / 1000)
4649-
} else {
4650-
(local_balance_before_fee_msat / 1000, (remote_balance_before_fee_msat / 1000).saturating_sub(total_fee_sat))
4651-
};
4652-
4653-
let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote };
4654-
let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self };
4655-
4656-
if to_broadcaster_value_sat >= broadcaster_dust_limit_sat {
4657-
log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, to_broadcaster_value_sat);
4658-
} else {
4659-
to_broadcaster_value_sat = 0;
4660-
}
4661-
4662-
if to_countersignatory_value_sat >= broadcaster_dust_limit_sat {
4663-
log_trace!(logger, " ...including {} output with value {}", if local { "to_remote" } else { "to_local" }, to_countersignatory_value_sat);
4664-
} else {
4665-
to_countersignatory_value_sat = 0;
4666-
}
4639+
// TODO: When MSRV >= 1.66.0, use u64::checked_add_signed
4640+
let value_to_self_with_offset_msat = (funding.value_to_self_msat as i64 + value_to_self_msat_offset).try_into().unwrap();
46674641

4668-
let channel_parameters =
4669-
if local { funding.channel_transaction_parameters.as_holder_broadcastable() }
4670-
else { funding.channel_transaction_parameters.as_counterparty_broadcastable() };
4671-
let tx = CommitmentTransaction::new(
4642+
let (tx, stats) = SpecTxBuilder {}.build_commitment_transaction(
4643+
local,
46724644
commitment_number,
46734645
per_commitment_point,
4674-
to_broadcaster_value_sat,
4675-
to_countersignatory_value_sat,
4676-
feerate_per_kw,
4677-
nondust_htlcs,
4678-
&channel_parameters,
4646+
&funding.channel_transaction_parameters,
46794647
&self.secp_ctx,
4648+
value_to_self_with_offset_msat,
4649+
htlcs_included.iter().map(|(htlc, _source)| htlc).cloned().collect(),
4650+
feerate_per_kw,
4651+
broadcaster_dust_limit_sat,
4652+
logger,
46804653
);
4654+
debug_assert_eq!(stats, self.build_commitment_stats(funding, local, generated_by_local, None, None));
46814655

46824656
// This populates the HTLC-source table with the indices from the HTLCs in the commitment
46834657
// 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)