Skip to content

Commit 9a1997a

Browse files
committed
[Custom Transactions] Define the TxBuilder trait
This commit defines the `TxBuilder` trait to give users the ability to customize the build of the lightning commitment transactions. The `TxBuilder` trait has a single method, `build_commitment_transaction`, which builds the commitment transaction, and populates the output indices of the HTLCs passed in. Besides that, it is mostly a copy paste of chunks of code from `ChannelContext::build_commitment_transaction`.
1 parent 10b98fb commit 9a1997a

File tree

3 files changed

+200
-103
lines changed

3 files changed

+200
-103
lines changed

lightning/src/ln/channel.rs

Lines changed: 13 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -885,11 +885,11 @@ struct CommitmentData<'a> {
885885
}
886886

887887
/// A struct gathering stats on commitment transaction, either local or remote.
888-
struct CommitmentStats {
889-
tx: CommitmentTransaction, // the transaction info
890-
total_fee_sat: u64, // the total fee included in the transaction
891-
local_balance_msat: u64, // local balance before fees *not* considering dust limits
892-
remote_balance_msat: u64, // remote balance before fees *not* considering dust limits
888+
pub(crate) struct CommitmentStats {
889+
pub(crate) tx: CommitmentTransaction, // the transaction info
890+
pub(crate) total_fee_sat: u64, // the total fee included in the transaction
891+
pub(crate) local_balance_msat: u64, // local balance before fees *not* considering dust limits
892+
pub(crate) remote_balance_msat: u64, // remote balance before fees *not* considering dust limits
893893
}
894894

895895
/// Used when calculating whether we or the remote can afford an additional HTLC.
@@ -3609,8 +3609,6 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
36093609
let mut htlcs_in_tx: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::with_capacity(num_htlcs);
36103610

36113611
let broadcaster_dust_limit_satoshis = if local { self.holder_dust_limit_satoshis } else { self.counterparty_dust_limit_satoshis };
3612-
let mut remote_htlc_total_msat = 0;
3613-
let mut local_htlc_total_msat = 0;
36143612
let mut value_to_self_msat_offset = 0;
36153613

36163614
let mut feerate_per_kw = self.feerate_per_kw;
@@ -3710,93 +3708,12 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
37103708
}
37113709
}
37123710

3713-
// Trim dust htlcs
3714-
let mut included_non_dust_htlcs: Vec<_> = htlcs_in_tx.iter_mut().map(|(htlc, _)| htlc).collect();
3715-
included_non_dust_htlcs.retain(|htlc| {
3716-
let outbound = local == htlc.offered;
3717-
if outbound {
3718-
local_htlc_total_msat += htlc.amount_msat;
3719-
} else {
3720-
remote_htlc_total_msat += htlc.amount_msat;
3721-
}
3722-
let htlc_tx_fee = if self.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
3723-
0
3724-
} else {
3725-
feerate_per_kw as u64
3726-
* if htlc.offered {
3727-
chan_utils::htlc_timeout_tx_weight(self.get_channel_type())
3728-
} else {
3729-
chan_utils::htlc_success_tx_weight(self.get_channel_type())
3730-
} / 1000
3731-
};
3732-
if htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
3733-
log_trace!(
3734-
logger,
3735-
" ...creating output for {} non-dust HTLC (hash {}) with value {}",
3736-
if outbound { "outbound" } else { "inbound" },
3737-
htlc.payment_hash,
3738-
htlc.amount_msat
3739-
);
3740-
true
3741-
} else {
3742-
log_trace!(
3743-
logger,
3744-
" ...trimming {} HTLC (hash {}) with value {} due to dust limit",
3745-
if outbound { "outbound" } else { "inbound" },
3746-
htlc.payment_hash,
3747-
htlc.amount_msat
3748-
);
3749-
false
3750-
}
3751-
});
3752-
37533711
// TODO: When MSRV >= 1.66.0, use u64::checked_add_signed
3754-
let mut value_to_self_msat = u64::try_from(funding.value_to_self_msat as i64 + value_to_self_msat_offset).unwrap();
3755-
// Note that in case they have several just-awaiting-last-RAA fulfills in-progress (ie
3756-
// AwaitingRemoteRevokeToRemove or AwaitingRemovedRemoteRevoke) we may have allowed them to
3757-
// "violate" their reserve value by couting those against it. Thus, we have to do checked subtraction
3758-
// as otherwise we can overflow.
3759-
let mut value_to_remote_msat = u64::checked_sub(funding.get_value_satoshis() * 1000, value_to_self_msat).unwrap();
3760-
value_to_self_msat = u64::checked_sub(value_to_self_msat, local_htlc_total_msat).unwrap();
3761-
value_to_remote_msat = u64::checked_sub(value_to_remote_msat, remote_htlc_total_msat).unwrap();
3762-
3763-
let total_fee_sat = commit_tx_fee_sat(feerate_per_kw, included_non_dust_htlcs.len(), &funding.channel_transaction_parameters.channel_type_features);
3764-
let anchors_val = if funding.channel_transaction_parameters.channel_type_features.supports_anchors_zero_fee_htlc_tx() { ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 };
3765-
let (value_to_self, value_to_remote) = if funding.is_outbound() {
3766-
((value_to_self_msat / 1000).saturating_sub(anchors_val).saturating_sub(total_fee_sat), value_to_remote_msat / 1000)
3767-
} else {
3768-
(value_to_self_msat / 1000, (value_to_remote_msat / 1000).saturating_sub(anchors_val).saturating_sub(total_fee_sat))
3769-
};
3712+
let value_to_self_with_offset_msat = u64::try_from(funding.value_to_self_msat as i64 + value_to_self_msat_offset).unwrap();
37703713

3771-
let mut value_to_a = if local { value_to_self } else { value_to_remote };
3772-
let mut value_to_b = if local { value_to_remote } else { value_to_self };
3773-
3774-
if value_to_a >= broadcaster_dust_limit_satoshis {
3775-
log_trace!(logger, " ...creating {} output with value {}", if local { "to_local" } else { "to_remote" }, value_to_a);
3776-
} else {
3777-
log_trace!(logger, " ...trimming {} output with value {} due to dust limit", if local { "to_local" } else { "to_remote" }, value_to_a);
3778-
value_to_a = 0;
3779-
}
3780-
3781-
if value_to_b >= broadcaster_dust_limit_satoshis {
3782-
log_trace!(logger, " ...creating {} output with value {}", if local { "to_remote" } else { "to_local" }, value_to_b);
3783-
} else {
3784-
log_trace!(logger, " ...trimming {} output with value {} due to dust limit", if local { "to_remote" } else { "to_local" }, value_to_b);
3785-
value_to_b = 0;
3786-
}
3787-
3788-
let channel_parameters =
3789-
if local { funding.channel_transaction_parameters.as_holder_broadcastable() }
3790-
else { funding.channel_transaction_parameters.as_counterparty_broadcastable() };
3791-
let tx = CommitmentTransaction::new(commitment_number,
3792-
&per_commitment_point,
3793-
value_to_a,
3794-
value_to_b,
3795-
feerate_per_kw,
3796-
included_non_dust_htlcs,
3797-
&channel_parameters,
3798-
&self.secp_ctx,
3799-
);
3714+
use crate::sign::tx_builder::{TxBuilder, SpecTxBuilder};
3715+
let stats = TxBuilder::build_commitment_transaction(&SpecTxBuilder {}, local, commitment_number, per_commitment_point, &funding.channel_transaction_parameters, &self.secp_ctx,
3716+
funding.get_value_satoshis(), value_to_self_with_offset_msat, htlcs_in_tx.iter_mut().map(|(htlc, _)| htlc).collect(), feerate_per_kw, broadcaster_dust_limit_satoshis, logger);
38003717

38013718
#[cfg(debug_assertions)]
38023719
{
@@ -3807,10 +3724,10 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
38073724
} else {
38083725
funding.counterparty_max_commitment_tx_output.lock().unwrap()
38093726
};
3810-
debug_assert!(broadcaster_max_commitment_tx_output.0 <= value_to_self_msat || value_to_self_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap());
3811-
broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, value_to_self_msat);
3812-
debug_assert!(broadcaster_max_commitment_tx_output.1 <= value_to_remote_msat || value_to_remote_msat / 1000 >= funding.holder_selected_channel_reserve_satoshis);
3813-
broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, value_to_remote_msat);
3727+
debug_assert!(broadcaster_max_commitment_tx_output.0 <= stats.local_balance_msat || stats.local_balance_msat / 1000 >= funding.counterparty_selected_channel_reserve_satoshis.unwrap());
3728+
broadcaster_max_commitment_tx_output.0 = cmp::max(broadcaster_max_commitment_tx_output.0, stats.local_balance_msat);
3729+
debug_assert!(broadcaster_max_commitment_tx_output.1 <= stats.remote_balance_msat || stats.remote_balance_msat / 1000 >= funding.holder_selected_channel_reserve_satoshis);
3730+
broadcaster_max_commitment_tx_output.1 = cmp::max(broadcaster_max_commitment_tx_output.1, stats.remote_balance_msat);
38143731
}
38153732

38163733
htlcs_in_tx.sort_unstable_by(|(htlc_a, _), (htlc_b, _)| {
@@ -3822,13 +3739,6 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
38223739
}
38233740
});
38243741

3825-
let stats = CommitmentStats {
3826-
tx,
3827-
total_fee_sat,
3828-
local_balance_msat: value_to_self_msat,
3829-
remote_balance_msat: value_to_remote_msat,
3830-
};
3831-
38323742
CommitmentData {
38333743
stats,
38343744
htlcs_included: htlcs_in_tx,

lightning/src/sign/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ pub(crate) mod type_resolver;
7676
pub mod ecdsa;
7777
#[cfg(taproot)]
7878
pub mod taproot;
79+
pub(crate) mod tx_builder;
7980

8081
/// Information about a spendable output to a P2WSH script.
8182
///

lightning/src/sign/tx_builder.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//! Defines the `TxBuilder` trait, and the `SpecTxBuilder` type
2+
#![allow(dead_code)]
3+
#![allow(unused_variables)]
4+
5+
use core::ops::Deref;
6+
7+
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
8+
9+
use crate::ln::chan_utils::{
10+
self, ChannelTransactionParameters, CommitmentTransaction, HTLCOutputInCommitment,
11+
};
12+
use crate::ln::channel::{self, CommitmentStats};
13+
use crate::prelude::*;
14+
use crate::util::logger::Logger;
15+
16+
/// A trait for types that can build commitment transactions, both for the holder, and the counterparty.
17+
pub(crate) trait TxBuilder {
18+
/// Build a commitment transaction, and populate the elements of `htlcs_in_tx` with their output indices.
19+
/// Leave the output index of dust htlcs set to `None`.
20+
fn build_commitment_transaction<L: Deref>(
21+
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
22+
channel_transaction_parameters: &ChannelTransactionParameters,
23+
secp_ctx: &Secp256k1<secp256k1::All>, channel_value_satoshis: u64,
24+
value_to_self_with_offset_msat: u64, htlcs_in_tx: Vec<&mut HTLCOutputInCommitment>,
25+
feerate_per_kw: u32, broadcaster_dust_limit_satoshis: u64, logger: &L,
26+
) -> CommitmentStats
27+
where
28+
L::Target: Logger;
29+
}
30+
31+
/// A type that builds commitment transactions according to the Lightning Specification.
32+
#[derive(Clone, Debug, Default)]
33+
pub(crate) struct SpecTxBuilder {}
34+
35+
impl TxBuilder for SpecTxBuilder {
36+
fn build_commitment_transaction<L: Deref>(
37+
&self, local: bool, commitment_number: u64, per_commitment_point: &PublicKey,
38+
channel_transaction_parameters: &ChannelTransactionParameters,
39+
secp_ctx: &Secp256k1<secp256k1::All>, channel_value_satoshis: u64,
40+
value_to_self_with_offset_msat: u64, mut htlcs_in_tx: Vec<&mut HTLCOutputInCommitment>,
41+
feerate_per_kw: u32, broadcaster_dust_limit_satoshis: u64, logger: &L,
42+
) -> CommitmentStats
43+
where
44+
L::Target: Logger,
45+
{
46+
let mut remote_htlc_total_msat = 0;
47+
let mut local_htlc_total_msat = 0;
48+
49+
let channel_parameters = if local {
50+
channel_transaction_parameters.as_holder_broadcastable()
51+
} else {
52+
channel_transaction_parameters.as_counterparty_broadcastable()
53+
};
54+
let channel_type = channel_parameters.channel_type_features();
55+
56+
// Trim dust htlcs
57+
htlcs_in_tx.retain(|htlc| {
58+
let outbound = local == htlc.offered;
59+
if outbound {
60+
local_htlc_total_msat += htlc.amount_msat;
61+
} else {
62+
remote_htlc_total_msat += htlc.amount_msat;
63+
}
64+
let htlc_tx_fee = if channel_type.supports_anchors_zero_fee_htlc_tx() {
65+
0
66+
} else {
67+
feerate_per_kw as u64
68+
* if htlc.offered {
69+
chan_utils::htlc_timeout_tx_weight(&channel_type)
70+
} else {
71+
chan_utils::htlc_success_tx_weight(&channel_type)
72+
} / 1000
73+
};
74+
if htlc.amount_msat / 1000 >= broadcaster_dust_limit_satoshis + htlc_tx_fee {
75+
log_trace!(
76+
logger,
77+
" ...creating output for {} non-dust HTLC (hash {}) with value {}",
78+
if outbound { "outbound" } else { "inbound" },
79+
htlc.payment_hash,
80+
htlc.amount_msat
81+
);
82+
true
83+
} else {
84+
log_trace!(
85+
logger,
86+
" ...trimming {} HTLC (hash {}) with value {} due to dust limit",
87+
if outbound { "outbound" } else { "inbound" },
88+
htlc.payment_hash,
89+
htlc.amount_msat
90+
);
91+
false
92+
}
93+
});
94+
95+
// Note that in case they have several just-awaiting-last-RAA fulfills in-progress (ie
96+
// AwaitingRemoteRevokeToRemove or AwaitingRemovedRemoteRevoke) we may have allowed them to
97+
// "violate" their reserve value by couting those against it. Thus, we have to do checked subtraction
98+
// as otherwise we can overflow.
99+
let mut value_to_remote_msat =
100+
u64::checked_sub(channel_value_satoshis * 1000, value_to_self_with_offset_msat)
101+
.unwrap();
102+
let value_to_self_msat =
103+
u64::checked_sub(value_to_self_with_offset_msat, local_htlc_total_msat).unwrap();
104+
value_to_remote_msat =
105+
u64::checked_sub(value_to_remote_msat, remote_htlc_total_msat).unwrap();
106+
107+
let total_fee_sat =
108+
chan_utils::commit_tx_fee_sat(feerate_per_kw, htlcs_in_tx.len(), &channel_type);
109+
let anchors_val = if channel_type.supports_anchors_zero_fee_htlc_tx() {
110+
channel::ANCHOR_OUTPUT_VALUE_SATOSHI * 2
111+
} else {
112+
0
113+
};
114+
let (value_to_self, value_to_remote) =
115+
if channel_transaction_parameters.is_outbound_from_holder {
116+
(
117+
(value_to_self_msat / 1000)
118+
.saturating_sub(anchors_val)
119+
.saturating_sub(total_fee_sat),
120+
value_to_remote_msat / 1000,
121+
)
122+
} else {
123+
(
124+
value_to_self_msat / 1000,
125+
(value_to_remote_msat / 1000)
126+
.saturating_sub(anchors_val)
127+
.saturating_sub(total_fee_sat),
128+
)
129+
};
130+
131+
let mut value_to_a = if local { value_to_self } else { value_to_remote };
132+
let mut value_to_b = if local { value_to_remote } else { value_to_self };
133+
134+
if value_to_a >= broadcaster_dust_limit_satoshis {
135+
log_trace!(
136+
logger,
137+
" ...creating {} output with value {}",
138+
if local { "to_local" } else { "to_remote" },
139+
value_to_a
140+
);
141+
} else {
142+
log_trace!(
143+
logger,
144+
" ...trimming {} output with value {} due to dust limit",
145+
if local { "to_local" } else { "to_remote" },
146+
value_to_a
147+
);
148+
value_to_a = 0;
149+
}
150+
151+
if value_to_b >= broadcaster_dust_limit_satoshis {
152+
log_trace!(
153+
logger,
154+
" ...creating {} output with value {}",
155+
if local { "to_remote" } else { "to_local" },
156+
value_to_b
157+
);
158+
} else {
159+
log_trace!(
160+
logger,
161+
" ...trimming {} output with value {} due to dust limit",
162+
if local { "to_remote" } else { "to_local" },
163+
value_to_b
164+
);
165+
value_to_b = 0;
166+
}
167+
168+
let tx = CommitmentTransaction::new(
169+
commitment_number,
170+
per_commitment_point,
171+
value_to_a,
172+
value_to_b,
173+
feerate_per_kw,
174+
htlcs_in_tx,
175+
&channel_parameters,
176+
secp_ctx,
177+
);
178+
179+
CommitmentStats {
180+
tx,
181+
total_fee_sat,
182+
local_balance_msat: value_to_self_msat,
183+
remote_balance_msat: value_to_remote_msat,
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)