Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Custom Transactions] Define the TxBuilder trait #3606

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ca8c728
Provide built commitment transactions to `ChannelMonitor`
tankyleo Feb 25, 2025
3dcf40a
Relax the requirements of building a `CommitmentTransaction`
tankyleo Feb 27, 2025
2a82a1f
Remove redundant parameters from the `CommitmentTransaction` constructor
tankyleo Feb 26, 2025
a50d32d
Let `CommitmentTransaction` build the `TxCreationKeys` it will cache
tankyleo Feb 26, 2025
d60cfda
Begin removing per-commitment key derivations from channel objects
tankyleo Feb 26, 2025
df9a561
Remove `i64` casts in `ChannelContext::build_commitment_transaction`
tankyleo Feb 26, 2025
ca3fa4e
Clean up tautologies in `ChannelContext::build_commitment_transaction`
tankyleo Feb 26, 2025
570ca77
Reorder the operations in `ChannelContext::build_commitment_transaction`
tankyleo Feb 27, 2025
82b40fc
Remove redundant fields in `CommitmentStats`
tankyleo Feb 26, 2025
5c97de0
Tweak return values of `ChannelContext::build_commitment_transaction`
tankyleo Feb 27, 2025
42d5a79
[Custom Transactions] Define the `TxBuilder` trait
tankyleo Feb 26, 2025
300b396
f: Just handle new updates for now, but don't set them
tankyleo Mar 2, 2025
3318e4a
f: Remove parameter from `provide_initial_counterparty_commitment_tx`
tankyleo Mar 1, 2025
7c2d605
f: Remove all explicit `TxCreationKeys` in channel
tankyleo Mar 1, 2025
ad05a2c
f: Break up a long line
tankyleo Mar 1, 2025
338532f
f: Correct comments describing commitment structs
tankyleo Mar 2, 2025
87a35dd
f: Delete a big branch in non-dust htlc scan
tankyleo Mar 2, 2025
dcfb0f9
Remove unnecessary allocation in `send_commitment_no_state_update`
tankyleo Mar 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 19 additions & 53 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1968,8 +1968,7 @@ trait InitialRemoteCommitmentReceiver<SP: Deref> where SP::Target: SignerProvide
) -> Result<CommitmentTransaction, ChannelError> where L::Target: Logger {
let funding_script = self.context().get_funding_redeemscript();

let keys = self.context().build_holder_transaction_keys(holder_commitment_point.current_point());
let initial_commitment_tx = self.context().build_commitment_transaction(self.funding(), holder_commitment_point.transaction_number(), &keys, true, false, logger).tx;
let initial_commitment_tx = self.context().build_commitment_transaction(self.funding(), holder_commitment_point.transaction_number(), &holder_commitment_point.current_point(), true, false, logger).tx;
let trusted_tx = initial_commitment_tx.trust();
let initial_commitment_bitcoin_tx = trusted_tx.built_transaction();
let sighash = initial_commitment_bitcoin_tx.get_sighash_all(&funding_script, self.funding().channel_value_satoshis);
Expand Down Expand Up @@ -2006,8 +2005,7 @@ trait InitialRemoteCommitmentReceiver<SP: Deref> where SP::Target: SignerProvide
}
};
let context = self.context();
let counterparty_keys = context.build_remote_transaction_keys();
let counterparty_initial_commitment_tx = context.build_commitment_transaction(self.funding(), context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
let counterparty_initial_commitment_tx = context.build_commitment_transaction(self.funding(), context.cur_counterparty_commitment_transaction_number, &context.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx;
let counterparty_trusted_tx = counterparty_initial_commitment_tx.trust();
let counterparty_initial_bitcoin_tx = counterparty_trusted_tx.built_transaction();

Expand Down Expand Up @@ -3412,7 +3410,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
/// generated by the peer which proposed adding the HTLCs, and thus we need to understand both
/// which peer generated this transaction and "to whom" this transaction flows.
#[inline]
fn build_commitment_transaction<L: Deref>(&self, funding: &FundingScope, commitment_number: u64, keys: &TxCreationKeys, local: bool, generated_by_local: bool, logger: &L) -> CommitmentStats
fn build_commitment_transaction<L: Deref>(&self, funding: &FundingScope, commitment_number: u64, per_commitment_point: &PublicKey, local: bool, generated_by_local: bool, logger: &L) -> CommitmentStats
where L::Target: Logger
{
let mut included_dust_htlcs: Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)> = Vec::new();
Expand Down Expand Up @@ -3614,7 +3612,7 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
if local { self.channel_transaction_parameters.as_holder_broadcastable() }
else { self.channel_transaction_parameters.as_counterparty_broadcastable() };
let tx = CommitmentTransaction::new(commitment_number,
&keys.per_commitment_point,
&per_commitment_point,
value_to_a as u64,
value_to_b as u64,
feerate_per_kw,
Expand All @@ -3640,32 +3638,6 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
}
}

#[inline]
/// Creates a set of keys for build_commitment_transaction to generate a transaction which our
/// counterparty will sign (ie DO NOT send signatures over a transaction created by this to
/// our counterparty!)
/// The result is a transaction which we can revoke broadcastership of (ie a "local" transaction)
/// TODO Some magic rust shit to compile-time check this?
fn build_holder_transaction_keys(&self, per_commitment_point: PublicKey) -> TxCreationKeys {
let delayed_payment_base = &self.get_holder_pubkeys().delayed_payment_basepoint;
let htlc_basepoint = &self.get_holder_pubkeys().htlc_basepoint;
let counterparty_pubkeys = self.get_counterparty_pubkeys();

TxCreationKeys::derive_new(&self.secp_ctx, &per_commitment_point, delayed_payment_base, htlc_basepoint, &counterparty_pubkeys.revocation_basepoint, &counterparty_pubkeys.htlc_basepoint)
}

#[inline]
/// Creates a set of keys for build_commitment_transaction to generate a transaction which we
/// will sign and send to our counterparty.
/// If an Err is returned, it is a ChannelError::Close (for get_funding_created)
fn build_remote_transaction_keys(&self) -> TxCreationKeys {
let revocation_basepoint = &self.get_holder_pubkeys().revocation_basepoint;
let htlc_basepoint = &self.get_holder_pubkeys().htlc_basepoint;
let counterparty_pubkeys = self.get_counterparty_pubkeys();

TxCreationKeys::derive_new(&self.secp_ctx, &self.counterparty_cur_commitment_point.unwrap(), &counterparty_pubkeys.delayed_payment_basepoint, &counterparty_pubkeys.htlc_basepoint, revocation_basepoint, htlc_basepoint)
}

/// Gets the redeemscript for the funding transaction output (ie the funding transaction output
/// pays to get_funding_redeemscript().to_p2wsh()).
/// Panics if called before accept_channel/InboundV1Channel::new
Expand Down Expand Up @@ -4459,9 +4431,8 @@ impl<SP: Deref> ChannelContext<SP> where SP::Target: SignerProvider {
SP::Target: SignerProvider,
L::Target: Logger
{
let counterparty_keys = self.build_remote_transaction_keys();
let counterparty_initial_commitment_tx = self.build_commitment_transaction(
funding, self.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
funding, self.cur_counterparty_commitment_transaction_number, &self.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx;
match self.holder_signer {
// TODO (taproot|arik): move match into calling method for Taproot
ChannelSignerType::Ecdsa(ref ecdsa) => {
Expand Down Expand Up @@ -5454,9 +5425,7 @@ impl<SP: Deref> FundedChannel<SP> where

let funding_script = self.context.get_funding_redeemscript();

let keys = self.context.build_holder_transaction_keys(self.holder_commitment_point.current_point());

let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &keys, true, false, logger);
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &self.holder_commitment_point.current_point(), true, false, logger);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're already touching some of these long lines, let's try to break them up as we go to reduce the changes when we rustfmt this file.

let commitment_txid = {
let trusted_tx = commitment_stats.tx.trust();
let bitcoin_tx = trusted_tx.built_transaction();
Expand Down Expand Up @@ -5524,19 +5493,20 @@ impl<SP: Deref> FundedChannel<SP> where

let mut nondust_htlc_sources = Vec::with_capacity(htlcs_cloned.len());
let mut htlcs_and_sigs = Vec::with_capacity(htlcs_cloned.len());
let holder_keys = TxCreationKeys::from_channel_static_keys(&self.holder_commitment_point.current_point(), self.context.get_holder_pubkeys(), self.context.get_counterparty_pubkeys(), &self.context.secp_ctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to get these from the built commitment transaction above?

for (idx, (htlc, mut source_opt)) in htlcs_cloned.drain(..).enumerate() {
if let Some(_) = htlc.transaction_output_index {
let htlc_tx = chan_utils::build_htlc_transaction(&commitment_txid, commitment_stats.feerate_per_kw,
self.context.get_counterparty_selected_contest_delay().unwrap(), &htlc, &self.context.channel_type,
&keys.broadcaster_delayed_payment_key, &keys.revocation_key);
&holder_keys.broadcaster_delayed_payment_key, &holder_keys.revocation_key);

let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &keys);
let htlc_redeemscript = chan_utils::get_htlc_redeemscript(&htlc, &self.context.channel_type, &holder_keys);
let htlc_sighashtype = if self.context.channel_type.supports_anchors_zero_fee_htlc_tx() { EcdsaSighashType::SinglePlusAnyoneCanPay } else { EcdsaSighashType::All };
let htlc_sighash = hash_to_message!(&sighash::SighashCache::new(&htlc_tx).p2wsh_signature_hash(0, &htlc_redeemscript, htlc.to_bitcoin_amount(), htlc_sighashtype).unwrap()[..]);
log_trace!(logger, "Checking HTLC tx signature {} by key {} against tx {} (sighash {}) with redeemscript {} in channel {}.",
log_bytes!(msg.htlc_signatures[idx].serialize_compact()[..]), log_bytes!(keys.countersignatory_htlc_key.to_public_key().serialize()),
log_bytes!(msg.htlc_signatures[idx].serialize_compact()[..]), log_bytes!(holder_keys.countersignatory_htlc_key.to_public_key().serialize()),
encode::serialize_hex(&htlc_tx), log_bytes!(htlc_sighash[..]), encode::serialize_hex(&htlc_redeemscript), &self.context.channel_id());
if let Err(_) = self.context.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &keys.countersignatory_htlc_key.to_public_key()) {
if let Err(_) = self.context.secp_ctx.verify_ecdsa(&htlc_sighash, &msg.htlc_signatures[idx], &holder_keys.countersignatory_htlc_key.to_public_key()) {
return Err(ChannelError::close("Invalid HTLC tx signature from peer".to_owned()));
}
if !separate_nondust_htlc_sources {
Expand Down Expand Up @@ -6218,8 +6188,7 @@ impl<SP: Deref> FundedChannel<SP> where
// Before proposing a feerate update, check that we can actually afford the new fee.
let dust_exposure_limiting_feerate = self.context.get_dust_exposure_limiting_feerate(&fee_estimator);
let htlc_stats = self.context.get_pending_htlc_stats(Some(feerate_per_kw), dust_exposure_limiting_feerate);
let keys = self.context.build_holder_transaction_keys(self.holder_commitment_point.current_point());
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &keys, true, true, logger);
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.holder_commitment_point.transaction_number(), &self.holder_commitment_point.current_point(), true, true, logger);
let buffer_fee_msat = commit_tx_fee_sat(feerate_per_kw, commitment_stats.num_nondust_htlcs + htlc_stats.on_holder_tx_outbound_holding_cell_htlcs_count as usize + CONCURRENT_INBOUND_HTLC_FEE_BUFFER as usize, self.context.get_channel_type()) * 1000;
let holder_balance_msat = commitment_stats.local_balance_msat - htlc_stats.outbound_holding_cell_msat;
if holder_balance_msat < buffer_fee_msat + self.funding.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 {
Expand Down Expand Up @@ -6532,8 +6501,7 @@ impl<SP: Deref> FundedChannel<SP> where
self.holder_commitment_point.try_resolve_pending(&self.context.holder_signer, &self.context.secp_ctx, logger);
}
let funding_signed = if self.context.signer_pending_funding && !self.context.is_outbound() {
let counterparty_keys = self.context.build_remote_transaction_keys();
let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number + 1, &counterparty_keys, false, false, logger).tx;
let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number + 1, &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx;
self.context.get_funding_signed_msg(logger, counterparty_initial_commitment_tx)
} else { None };
// Provide a `channel_ready` message if we need to, but only if we're _not_ still pending
Expand Down Expand Up @@ -8469,8 +8437,7 @@ impl<SP: Deref> FundedChannel<SP> where
-> (Vec<(HTLCOutputInCommitment, Option<&HTLCSource>)>, CommitmentTransaction)
where L::Target: Logger
{
let counterparty_keys = self.context.build_remote_transaction_keys();
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger);
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger);
let counterparty_commitment_tx = commitment_stats.tx;

#[cfg(any(test, fuzzing))]
Expand Down Expand Up @@ -8501,8 +8468,7 @@ impl<SP: Deref> FundedChannel<SP> where
#[cfg(any(test, fuzzing))]
self.build_commitment_no_state_update(logger);

let counterparty_keys = self.context.build_remote_transaction_keys();
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, true, logger);
let commitment_stats = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &self.context.counterparty_cur_commitment_point.unwrap(), false, true, logger);
let counterparty_commitment_txid = commitment_stats.tx.trust().txid();

match &self.context.holder_signer {
Expand All @@ -8528,7 +8494,8 @@ impl<SP: Deref> FundedChannel<SP> where
encode::serialize_hex(&commitment_stats.tx.trust().built_transaction().transaction),
&counterparty_commitment_txid, encode::serialize_hex(&self.context.get_funding_redeemscript()),
log_bytes!(signature.serialize_compact()[..]), &self.context.channel_id());


let counterparty_keys = TxCreationKeys::from_channel_static_keys(&self.context.counterparty_cur_commitment_point.unwrap(), self.context.get_counterparty_pubkeys(), self.context.get_holder_pubkeys(), &self.context.secp_ctx);
for (ref htlc_sig, ref htlc) in htlc_signatures.iter().zip(htlcs) {
log_trace!(logger, "Signed remote HTLC tx {} with redeemscript {} with pubkey {} -> {} in channel {}",
encode::serialize_hex(&chan_utils::build_htlc_transaction(&counterparty_commitment_txid, commitment_stats.feerate_per_kw, self.context.get_holder_selected_contest_delay(), htlc, &self.context.channel_type, &counterparty_keys.broadcaster_delayed_payment_key, &counterparty_keys.revocation_key)),
Expand Down Expand Up @@ -8980,8 +8947,7 @@ impl<SP: Deref> OutboundV1Channel<SP> where SP::Target: SignerProvider {

/// Only allowed after [`ChannelContext::channel_transaction_parameters`] is set.
fn get_funding_created_msg<L: Deref>(&mut self, logger: &L) -> Option<msgs::FundingCreated> where L::Target: Logger {
let counterparty_keys = self.context.build_remote_transaction_keys();
let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &counterparty_keys, false, false, logger).tx;
let counterparty_initial_commitment_tx = self.context.build_commitment_transaction(&self.funding, self.context.cur_counterparty_commitment_transaction_number, &self.context.counterparty_cur_commitment_point.unwrap(), false, false, logger).tx;
let signature = match &self.context.holder_signer {
// TODO (taproot|arik): move match into calling method for Taproot
ChannelSignerType::Ecdsa(ecdsa) => {
Expand Down Expand Up @@ -11633,7 +11599,7 @@ mod tests {
$( { $htlc_idx: expr, $counterparty_htlc_sig_hex: expr, $htlc_sig_hex: expr, $htlc_tx_hex: expr } ), *
} ) => { {
let (commitment_tx, htlcs): (_, Vec<HTLCOutputInCommitment>) = {
let mut commitment_stats = chan.context.build_commitment_transaction(&chan.funding, 0xffffffffffff - 42, &keys, true, false, &logger);
let mut commitment_stats = chan.context.build_commitment_transaction(&chan.funding, 0xffffffffffff - 42, &per_commitment_point, true, false, &logger);

let htlcs = commitment_stats.htlcs_included.drain(..)
.filter_map(|(htlc, _)| if htlc.transaction_output_index.is_some() { Some(htlc) } else { None })
Expand Down