Skip to content

Commit 0d2ac33

Browse files
jkczyzclaude
andcommitted
Expose interactive funding candidates on broadcast
Replace TransactionType::Splice with TransactionType::InteractiveFunding so downstream consumers can update their own state tracking from the broadcast callback. The local contribution data isn't recoverable from the on-chain transaction, so the broadcast must surface it directly. Each candidate carries the participating channels and their local contributions; the broadcast lists every negotiated candidate — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. The new types are Writeable/Readable so downstream can persist them directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 42e198c commit 0d2ac33

6 files changed

Lines changed: 225 additions & 73 deletions

File tree

lightning/src/chain/chaininterface.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
1616
use core::{cmp, ops::Deref};
1717

18+
use crate::ln::funding::FundingContribution;
1819
use crate::ln::types::ChannelId;
1920
use crate::prelude::*;
2021

22+
use bitcoin::hash_types::Txid;
2123
use bitcoin::secp256k1::PublicKey;
2224
use bitcoin::transaction::Transaction;
2325

@@ -104,19 +106,76 @@ pub enum TransactionType {
104106
/// A single sweep transaction may aggregate outputs from multiple channels.
105107
channels: Vec<(PublicKey, ChannelId)>,
106108
},
107-
/// A splice transaction modifying an existing channel's funding.
109+
/// An interactively-negotiated funding transaction.
108110
///
109-
/// A transaction of this type will be broadcast as a result of a [`ChannelManager::splice_channel`] operation.
111+
/// A transaction of this type will be broadcast as a result of a
112+
/// [`ChannelManager::splice_channel`] operation, or (once supported) V2 (dual-funded) channel
113+
/// establishment. The same variant is used for batches of either or both.
110114
///
111115
/// [`ChannelManager::splice_channel`]: crate::ln::channelmanager::ChannelManager::splice_channel
112-
Splice {
113-
/// The `node_id` of the channel counterparty.
114-
counterparty_node_id: PublicKey,
115-
/// The ID of the channel being spliced.
116-
channel_id: ChannelId,
116+
InteractiveFunding {
117+
/// Every negotiated candidate for this funding in order: the original negotiation
118+
/// followed by any RBF replacements. The last entry is the candidate being broadcast.
119+
candidates: Vec<FundingCandidate>,
117120
},
118121
}
119122

123+
/// A single negotiated candidate within a [`TransactionType::InteractiveFunding`] broadcast.
124+
///
125+
/// The candidate is identified by its [`Txid`] and lists the channels participating in it. A
126+
/// single candidate funds more than one channel only when batching splices and/or V2 channel
127+
/// openings (not yet implemented).
128+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
129+
pub struct FundingCandidate {
130+
/// The txid of this candidate.
131+
pub txid: Txid,
132+
/// The channels participating in this candidate.
133+
pub channels: Vec<ChannelFunding>,
134+
}
135+
136+
/// Information about a single channel's participation in a [`FundingCandidate`].
137+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
138+
pub struct ChannelFunding {
139+
/// The `node_id` of the channel counterparty.
140+
pub counterparty_node_id: PublicKey,
141+
/// The ID of the channel.
142+
pub channel_id: ChannelId,
143+
/// Whether this channel is being newly established or is an existing channel being spliced.
144+
pub purpose: FundingPurpose,
145+
/// The local node's contribution to this channel in this candidate, or `None` if we did
146+
/// not contribute (e.g., a pure acceptor with zero value added, or a leading RBF round
147+
/// before we began contributing).
148+
pub contribution: Option<FundingContribution>,
149+
}
150+
151+
/// The role of a channel within a [`FundingCandidate`].
152+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
153+
pub enum FundingPurpose {
154+
/// The channel is being newly established (V2 dual-funded open).
155+
Establishment,
156+
/// An existing channel is being spliced.
157+
Splice,
158+
}
159+
160+
// Needed so downstream consumers can persist these without needing to define wrapper types
161+
// mirroring the type structure.
162+
impl_writeable_tlv_based!(FundingCandidate, {
163+
(1, txid, required),
164+
(3, channels, required_vec),
165+
});
166+
167+
impl_writeable_tlv_based!(ChannelFunding, {
168+
(1, counterparty_node_id, required),
169+
(3, channel_id, required),
170+
(5, purpose, required),
171+
(7, contribution, option),
172+
});
173+
174+
impl_writeable_tlv_based_enum!(FundingPurpose,
175+
(0, Establishment) => {},
176+
(2, Splice) => {},
177+
);
178+
120179
// TODO: Define typed abstraction over feerates to handle their conversions.
121180
pub(crate) fn compute_feerate_sat_per_1000_weight(fee_sat: u64, weight: u64) -> u32 {
122181
(fee_sat * 1000 / weight).try_into().unwrap_or(u32::max_value())

lightning/src/ln/channel.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ use bitcoin::{secp256k1, sighash, FeeRate, Sequence, TxIn};
2828

2929
use crate::blinded_path::message::BlindedMessagePath;
3030
use crate::chain::chaininterface::{
31-
ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator, TransactionType,
31+
ChannelFunding, ConfirmationTarget, FeeEstimator, FundingCandidate, FundingPurpose,
32+
LowerBoundedFeeEstimator, TransactionType,
3233
};
3334
use crate::chain::channelmonitor::{
3435
ChannelMonitor, ChannelMonitorUpdate, ChannelMonitorUpdateStep, CommitmentHTLCData,
@@ -9382,10 +9383,34 @@ where
93829383
);
93839384
}
93849385

9385-
let tx_type = TransactionType::Splice {
9386-
counterparty_node_id: self.context.counterparty_node_id,
9387-
channel_id: self.context.channel_id,
9388-
};
9386+
let contrib_offset = pending_splice
9387+
.negotiated_candidates
9388+
.len()
9389+
.saturating_sub(pending_splice.contributions.len());
9390+
let candidates = pending_splice
9391+
.negotiated_candidates
9392+
.iter()
9393+
.enumerate()
9394+
.map(|(i, funding)| {
9395+
let txid = funding
9396+
.get_funding_txid()
9397+
.expect("negotiated candidates should have a funding txid");
9398+
let contribution = i
9399+
.checked_sub(contrib_offset)
9400+
.and_then(|j| pending_splice.contributions.get(j))
9401+
.cloned();
9402+
FundingCandidate {
9403+
txid,
9404+
channels: vec![ChannelFunding {
9405+
counterparty_node_id: self.context.counterparty_node_id,
9406+
channel_id: self.context.channel_id,
9407+
purpose: FundingPurpose::Splice,
9408+
contribution,
9409+
}],
9410+
}
9411+
})
9412+
.collect();
9413+
let tx_type = TransactionType::InteractiveFunding { candidates };
93899414
funding_tx_signed.funding_tx = Some((funding_tx, tx_type));
93909415
funding_tx_signed.splice_negotiated = Some(splice_negotiated);
93919416
funding_tx_signed.splice_locked = splice_locked;

lightning/src/ln/channelmanager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11124,7 +11124,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/
1112411124
} else if let Some((splice_tx, tx_type)) = funding_tx_signed
1112511125
.as_mut()
1112611126
.and_then(|v| v.funding_tx.take())
11127-
.filter(|(_, tx_type)| matches!(tx_type, TransactionType::Splice { .. }))
11127+
.filter(|(_, tx_type)| matches!(tx_type, TransactionType::InteractiveFunding { .. }))
1112811128
{
1112911129
log_info!(logger, "Broadcasting signed splice transaction with txid {}", splice_tx.compute_txid());
1113011130
self.tx_broadcaster.broadcast_transactions(&[(&splice_tx, tx_type)]);

lightning/src/ln/funding.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ enum FundingInputs {
539539
}
540540

541541
/// The components of a funding transaction contributed by one party.
542-
#[derive(Debug, Clone, PartialEq, Eq)]
542+
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
543543
pub struct FundingContribution {
544544
/// The estimate fees responsible to be paid for the contribution.
545545
estimated_fee: Amount,

0 commit comments

Comments
 (0)