Skip to content

Commit d0a0121

Browse files
committed
Add TxBuilder::build_commitment_transaction
1 parent 9ec7e52 commit d0a0121

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
@@ -647,6 +647,23 @@ impl HTLCOutputInCommitment {
647647
&& self.cltv_expiry == other.cltv_expiry
648648
&& self.payment_hash == other.payment_hash
649649
}
650+
651+
pub(crate) fn is_dust(
652+
&self, feerate_per_kw: u32, broadcaster_dust_limit_sat: u64, features: &ChannelTypeFeatures,
653+
) -> bool {
654+
let htlc_tx_fee_sat = if features.supports_anchors_zero_fee_htlc_tx() {
655+
0
656+
} else {
657+
let htlc_tx_weight = if self.offered {
658+
htlc_timeout_tx_weight(features)
659+
} else {
660+
htlc_success_tx_weight(features)
661+
};
662+
// As required by the spec, round down
663+
feerate_per_kw as u64 * htlc_tx_weight / 1000
664+
};
665+
self.amount_msat / 1000 < broadcaster_dust_limit_sat + htlc_tx_fee_sat
666+
}
650667
}
651668

652669
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
/// The total fee included in the commitment transaction
11371138
pub total_fee_sat: u64,
@@ -4564,16 +4565,9 @@ where
45644565
let broadcaster_dust_limit_sat = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis };
45654566
let feerate_per_kw = self.get_commitment_feerate(funding, generated_by_local);
45664567

4567-
let stats = self.build_commitment_stats(funding, local, generated_by_local, None, None);
4568-
let CommitmentStats {
4569-
total_fee_sat,
4570-
local_balance_before_fee_msat,
4571-
remote_balance_before_fee_msat
4572-
} = stats;
4573-
45744568
let num_htlcs = self.pending_inbound_htlcs.len() + self.pending_outbound_htlcs.len();
45754569
let mut htlcs_included: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(num_htlcs);
4576-
let mut nondust_htlcs: Vec<HTLCOutputInCommitment> = Vec::with_capacity(num_htlcs);
4570+
let mut value_to_self_msat_offset = 0;
45774571

45784572
log_trace!(logger, "Building commitment transaction number {} (really {} xor {}) for channel {} for {}, generated by {} with fee {}...",
45794573
commitment_number, (INITIAL_COMMITMENT_NUMBER - commitment_number),
@@ -4596,13 +4590,7 @@ where
45964590
macro_rules! add_htlc_output {
45974591
($htlc: expr, $outbound: expr, $source: expr) => {
45984592
let htlc_in_tx = get_htlc_in_commitment!($htlc, $outbound == local);
4599-
htlcs_included.push((htlc_in_tx.clone(), $source));
4600-
if $htlc.is_dust(local, feerate_per_kw, broadcaster_dust_limit_sat, funding.get_channel_type()) {
4601-
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);
4602-
} else {
4603-
log_trace!(logger, " ...including {} {} HTLC {} (hash {}) with value {}", if $outbound { "outbound" } else { "inbound" }, $htlc.state, $htlc.htlc_id, $htlc.payment_hash, $htlc.amount_msat);
4604-
nondust_htlcs.push(htlc_in_tx);
4605-
}
4593+
htlcs_included.push((htlc_in_tx, $source));
46064594
}
46074595
}
46084596

@@ -4611,11 +4599,13 @@ where
46114599

46124600
for htlc in self.pending_inbound_htlcs.iter() {
46134601
if htlc.state.included_in_commitment(generated_by_local) {
4602+
log_trace!(logger, " ...including inbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
46144603
add_htlc_output!(htlc, false, None);
46154604
} else {
46164605
log_trace!(logger, " ...not including inbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
46174606
if let Some(preimage) = htlc.state.preimage() {
46184607
inbound_htlc_preimages.push(preimage);
4608+
value_to_self_msat_offset += htlc.amount_msat as i64;
46194609
}
46204610
}
46214611
};
@@ -4625,53 +4615,37 @@ where
46254615
outbound_htlc_preimages.push(preimage);
46264616
}
46274617
if htlc.state.included_in_commitment(generated_by_local) {
4618+
log_trace!(logger, " ...including outbound {} HTLC {} (hash {}) with value {}", htlc.state, htlc.htlc_id, htlc.payment_hash, htlc.amount_msat);
46284619
add_htlc_output!(htlc, true, Some(&htlc.source));
46294620
} else {
46304621
log_trace!(logger, " ...not including outbound HTLC {} (hash {}) with value {} due to state ({})", htlc.htlc_id, htlc.payment_hash, htlc.amount_msat, htlc.state);
4622+
if htlc.state.preimage().is_some() {
4623+
value_to_self_msat_offset -= htlc.amount_msat as i64;
4624+
}
46314625
}
46324626
};
46334627

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

4641-
let (value_to_self, value_to_remote) = if funding.is_outbound() {
4642-
((local_balance_before_fee_msat / 1000).saturating_sub(total_fee_sat), remote_balance_before_fee_msat / 1000)
4643-
} else {
4644-
(local_balance_before_fee_msat / 1000, (remote_balance_before_fee_msat / 1000).saturating_sub(total_fee_sat))
4645-
};
4633+
// TODO: When MSRV >= 1.66.0, use u64::checked_add_signed
4634+
let value_to_self_with_offset_msat = (funding.value_to_self_msat as i64 + value_to_self_msat_offset).try_into().unwrap();
46464635

4647-
let mut to_broadcaster_value_sat = if local { value_to_self } else { value_to_remote };
4648-
let mut to_countersignatory_value_sat = if local { value_to_remote } else { value_to_self };
4649-
4650-
if to_broadcaster_value_sat >= broadcaster_dust_limit_sat {
4651-
log_trace!(logger, " ...including {} output with value {}", if local { "to_local" } else { "to_remote" }, to_broadcaster_value_sat);
4652-
} else {
4653-
to_broadcaster_value_sat = 0;
4654-
}
4655-
4656-
if to_countersignatory_value_sat >= broadcaster_dust_limit_sat {
4657-
log_trace!(logger, " ...including {} output with value {}", if local { "to_remote" } else { "to_local" }, to_countersignatory_value_sat);
4658-
} else {
4659-
to_countersignatory_value_sat = 0;
4660-
}
4661-
4662-
let channel_parameters =
4663-
if local { funding.channel_transaction_parameters.as_holder_broadcastable() }
4664-
else { funding.channel_transaction_parameters.as_counterparty_broadcastable() };
4665-
let tx = CommitmentTransaction::new(
4636+
let (tx, stats) = SpecTxBuilder {}.build_commitment_transaction(
4637+
local,
46664638
commitment_number,
46674639
per_commitment_point,
4668-
to_broadcaster_value_sat,
4669-
to_countersignatory_value_sat,
4670-
feerate_per_kw,
4671-
nondust_htlcs,
4672-
&channel_parameters,
4640+
&funding.channel_transaction_parameters,
46734641
&self.secp_ctx,
4642+
value_to_self_with_offset_msat,
4643+
htlcs_included.iter().map(|(htlc, _source)| htlc).cloned().collect(),
4644+
feerate_per_kw,
4645+
broadcaster_dust_limit_sat,
4646+
logger,
46744647
);
4648+
debug_assert_eq!(stats, self.build_commitment_stats(funding, local, generated_by_local, None, None));
46754649

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