Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
73 changes: 65 additions & 8 deletions lightning/src/chain/chaininterface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@

use core::{cmp, ops::Deref};

use crate::ln::funding::FundingContribution;
use crate::ln::types::ChannelId;
use crate::prelude::*;

use bitcoin::hash_types::Txid;
use bitcoin::secp256k1::PublicKey;
use bitcoin::transaction::Transaction;

/// Represents the class of transaction being broadcast.
///
/// This is used to provide context about the type of transaction being broadcast, which may be
/// useful for logging, filtering, or prioritization purposes.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TransactionType {
/// A funding transaction establishing a new channel.
///
Expand Down Expand Up @@ -104,19 +106,74 @@ pub enum TransactionType {
/// A single sweep transaction may aggregate outputs from multiple channels.
channels: Vec<(PublicKey, ChannelId)>,
},
/// A splice transaction modifying an existing channel's funding.
/// An interactively-negotiated funding transaction.
///
/// A transaction of this type will be broadcast as a result of a [`ChannelManager::splice_channel`] operation.
/// A transaction of this type will be broadcast as a result of a
/// [`ChannelManager::splice_channel`] operation, or (once supported) V2 (dual-funded) channel
/// establishment. The same variant is used for batches of either or both.
///
/// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel
Splice {
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The ID of the channel being spliced.
channel_id: ChannelId,
InteractiveFunding {
/// Every negotiated candidate for this funding in order: the original negotiation
/// followed by any RBF replacements. The last entry is the candidate being broadcast.
candidates: Vec<FundingCandidate>,
},
}

/// A single negotiated candidate within a [`TransactionType::InteractiveFunding`] broadcast.
///
/// The candidate is identified by its [`Txid`] and lists the channels participating in it. A
/// single candidate funds more than one channel only when batching splices and/or V2 channel
/// openings (not yet implemented).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FundingCandidate {
/// The txid of this candidate.
pub txid: Txid,
/// The channels participating in this candidate.
pub channels: Vec<ChannelFunding>,
}

/// Information about a single channel's participation in a [`FundingCandidate`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelFunding {
/// The `node_id` of the channel counterparty.
pub counterparty_node_id: PublicKey,
/// The ID of the channel.
pub channel_id: ChannelId,
/// Whether this channel is being newly established or is an existing channel being spliced.
pub purpose: FundingPurpose,
/// The local node's contribution to this channel in this candidate, or `None` if we did
/// not contribute (e.g., a pure acceptor with zero value added, or a leading RBF round
/// before we began contributing).
pub contribution: Option<FundingContribution>,
}

/// The role of a channel within a [`FundingCandidate`].
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FundingPurpose {
/// The channel is being newly established (V2 dual-funded open).
Establishment,
/// An existing channel is being spliced.
Splice,
}

impl_writeable_tlv_based!(FundingCandidate, {
(1, txid, required),
(3, channels, required_vec),
});

impl_writeable_tlv_based!(ChannelFunding, {
(1, counterparty_node_id, required),
(3, channel_id, required),
(5, purpose, required),
(7, contribution, option),
});

impl_writeable_tlv_based_enum!(FundingPurpose,
(0, Establishment) => {},
(2, Splice) => {},
);
Comment on lines +160 to +175
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: these three serialization impls (FundingCandidate, ChannelFunding, FundingPurpose) appear to be dead code — TransactionType is never serialized (no Writeable/Readable impl), and these types are only used inside TransactionType::InteractiveFunding, which is purely in-memory and passed through BroadcasterInterface::broadcast_transactions as a reference.

If these are forward-looking (e.g., for future persistence or cross-process messaging), a brief comment explaining the intent would help. Otherwise they could be removed to avoid confusion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need these for downstream


// TODO: Define typed abstraction over feerates to handle their conversions.
pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
(fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())
Expand Down
35 changes: 30 additions & 5 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use bitcoin::{secp256k1, sighash, FeeRate, Sequence, TxIn};

use crate::blinded_path::message::BlindedMessagePath;
use crate::chain::chaininterface::{
ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, TransactionType,
ChannelFunding, ConfirmationTarget, FeeEstimator, FundingCandidate, FundingPurpose,
LowerBoundedFeeEstimator, TransactionType,
};
use crate::chain::channelmonitor::{
ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, CommitmentHTLCData,
Expand Down Expand Up @@ -9382,10 +9383,34 @@ where
);
}

let tx_type = TransactionType::Splice {
counterparty_node_id: self.context.counterparty_node_id,
channel_id: self.context.channel_id,
};
let contrib_offset = pending_splice
.negotiated_candidates
.len()
.saturating_sub(pending_splice.contributions.len());
let candidates = pending_splice
.negotiated_candidates
.iter()
.enumerate()
.map(|(i, funding)| {
let txid = funding
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)
.and_then(|j| pending_splice.contributions.get(j))
.cloned();
FundingCandidate {
txid,
channels: vec![ChannelFunding {
counterparty_node_id: self.context.counterparty_node_id,
channel_id: self.context.channel_id,
purpose: FundingPurpose::Splice,
contribution,
}],
}
})
.collect();
let tx_type = TransactionType::InteractiveFunding { candidates };
funding_tx_signed.funding_tx = Some((funding_tx, tx_type));
funding_tx_signed.splice_negotiated = Some(splice_negotiated);
funding_tx_signed.splice_locked = splice_locked;
Expand Down
2 changes: 1 addition & 1 deletion lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11124,7 +11124,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
} else if let Some((splice_tx, tx_type)) = funding_tx_signed
.as_mut()
.and_then(|v| v.funding_tx.take())
.filter(|(_, tx_type)| matches!(tx_type, TransactionType::Splice { .. }))
.filter(|(_, tx_type)| matches!(tx_type, TransactionType::InteractiveFunding { .. }))
{
log_info!(logger, "Broadcasting signed splice transaction with txid {}", splice_tx.compute_txid());
self.tx_broadcaster.broadcast_transactions(&[(&splice_tx, tx_type)]);
Expand Down
25 changes: 21 additions & 4 deletions lightning/src/ln/funding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,10 +578,6 @@ impl_writeable_tlv_based!(FundingContribution, {
});

impl FundingContribution {
pub(super) fn feerate(&self) -> FeeRate {
self.feerate
}

pub(super) fn is_splice(&self) -> bool {
self.is_splice
}
Expand Down Expand Up @@ -610,6 +606,16 @@ impl FundingContribution {
.unwrap_or(Amount::ZERO)
}

/// Returns the estimated on-chain fee this contribution is responsible for paying.
pub fn estimated_fee(&self) -> Amount {
self.estimated_fee
}

/// Returns the inputs included in this contribution.
pub fn inputs(&self) -> &[FundingTxInput] {
&self.inputs
}

/// Returns the outputs (e.g., withdrawal destinations) included in this contribution.
///
/// This does not include the change output; see [`FundingContribution::change_output`].
Expand All @@ -625,6 +631,17 @@ impl FundingContribution {
self.change_output.as_ref()
}

/// Returns the fee rate used to select `inputs` (the minimum feerate).
pub fn feerate(&self) -> FeeRate {
self.feerate
}

/// Returns the maximum fee rate this contribution will accept as acceptor before rejecting
/// the splice.
pub fn max_feerate(&self) -> FeeRate {
self.max_feerate
}

/// Tries to satisfy a new request using only this contribution's existing inputs.
///
/// For input-backed contributions, this reuses the current inputs, adjusts the explicit
Expand Down
Loading
Loading