Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Include interactive funding candidates on broadcast - #4570

Merged
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type
May 7, 2026
Merged

Include interactive funding candidates on broadcast#4570
TheBlueMatt merged 2 commits into
lightningdevkit:mainfrom
jkczyz:2026-04-splice-transaction-type

Conversation

@jkczyz

@jkczyzjkczyz commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Include every negotiated funding candidate (original + RBF replacements) at broadcast time so downstream consumers can update their own state tracking from the broadcaster callback. The local contribution data isn't recoverable from the on-chain transaction, and the replaced txids aren't either, so the broadcaster callback is the only place where this information can be observed.

  • TransactionType::Splice is replaced with TransactionType::InteractiveFunding { candidates: Vec<FundingCandidate> }. Each FundingCandidate carries its txid and the channels participating in it; each ChannelFunding carries the channel id, counterparty node id, purpose (Establishment / Splice), and the local node's contribution for this candidate. The list covers every negotiated candidate in order — original first, then each RBF replacement — letting downstream reconcile any historical txid, not just the immediate predecessor. The alternative (reacting to SplicePending) was worse: that event races with BDK wallet sync and doesn't carry the replaced txid.
  • The new variant is structured to be forward-compatible with batches and V2 (dual-funded) channel establishment, neither of which is implemented today. For the current single-channel splice path, each candidate's channels list has length 1.
  • FundingCandidate, ChannelFunding, and FundingPurpose implement Writeable / Readable so downstream can persist them directly without mirroring.
  • FundingContribution gains public getters for estimated_fee, inputs, and max_feerate; feerate is elevated from pub(super) to pub. Combined with existing value_added, outputs, change_output, this exposes everything a downstream consumer needs without reaching into the raw transaction.

@ldk-reviews-bot

ldk-reviews-bot commented Apr 17, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @TheBlueMatt as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@ldk-claude-review-bot

ldk-claude-review-bot commented Apr 17, 2026

Copy link
Copy Markdown
Collaborator

I've thoroughly reviewed every file and hunk in this diff, cross-referencing my prior review context and verifying the key invariants.

Verification performed:

  • Contribution offset math (channel.rs:9386-9401): Correct for all cases. saturating_sub handles the (impossible) case where contributions.len() > negotiated_candidates.len() gracefully. The checked_sub(contrib_offset) + .get(j) properly maps leading no-contribution rounds to None and trailing contribution rounds to the correct index.
  • Hash derive chain: Complete. FundingContribution (newly Hash) → FundingTxInput (ConfirmedUtxo, newly Hash) → Utxo (already Hash) → bitcoin::Transaction (implements Hash in bitcoin 0.32.x). All intermediate types in the chain implement Hash.
  • All TransactionType::Splice match sites: Properly updated to TransactionType::InteractiveFunding. No orphaned references remain.
  • Test assertions in sign_interactive_funding_tx_with_acceptor_contribution: The assert_broadcast closure correctly validates candidate chain structure, replaced-txid contract (via checked_sub(2)), contribution presence, and purpose for both initiator and acceptor across all callers.
  • New public getters (estimated_fee, inputs, feerate, max_feerate): Straightforward field accessors with no logic errors. feerate elevation from pub(super) to pub is a strict visibility widening with no impact on existing callers.
  • All test callers pass correct expected_replaced_txid values: None for first-splice, Some(prior_tx.compute_txid()) for RBF rounds.

Prior review comment status:

  • channel.rs:7256 — 0-conf rebroadcast type mismatch: Pre-existing issue, still applicable (outside this PR's scope).
  • chaininterface.rs:177 — Dead code TLV serialization impls: Still applicable (minor).
  • channel.rs:9395filter_map silently dropping candidates: Resolved (now uses .map() + .expect()).
  • chaininterface.rs:123 — Grammar nit ("An" → "A"): Resolved.

No new issues found.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

1 similar comment
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +7241 to +7256
if !self.context.is_manual_broadcast
&& !waiting_for_batch
&& self.funding.get_funding_tx_confirmation_height().is_none()
{
if let Some(tx) = &self.funding.funding_transaction {
return Some((
tx.clone(),
TransactionType::Funding {
channels: vec![(
self.context.counterparty_node_id,
self.context.channel_id,
)],
},
));
}
}

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.

Bug: After 0-conf splice promotion, self.pending_splice is None (cleared at maybe_promote_splice_funding line 11680) and self.funding holds the splice's FundingScope with funding_tx_confirmation_height == 0 and funding_transaction = Some(splice_tx). This initial-funding branch will match:

  • is_manual_broadcast = false (normal channel)
  • waiting_for_batch = false (channel is ChannelReady)
  • get_funding_tx_confirmation_height().is_none() = true (height is 0)
  • funding_transaction = Some(splice_tx)

...and return TransactionType::Funding wrapping the splice transaction. The splice branch below is unreachable because self.pending_splice is None.

The test test_splice_rebroadcast_uses_splice_type_after_0conf_promotion (added in this PR) expects TransactionType::Splice with the original contribution preserved, but this code would produce TransactionType::Funding. That test will fail.

A possible fix: check self.funding.channel_transaction_parameters.splice_parent_funding_txid — it is Some(...) for promoted splices and None for initial funding. You'd also need to persist the contribution and replaced_txid metadata somewhere that survives promotion (e.g., on FundingScope or ChannelContext), since pending_splice is cleared.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Discussed offline. Dropped the commit to retry for now. We may do this later in ChannelMonitor where we'll store the FundingContribution.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from dbaf6d9 to 86dcedeCompareApril 22, 2026 18:36

@TheBlueMattTheBlueMatt left a comment

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.

Needs rebase, also ci is very sad.

@jkczyzjkczyz self-assigned this Apr 23, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 86dcede to dacf092CompareApril 23, 2026 21:42
@jkczyz

Copy link
Copy Markdown
ContributorAuthor

Rebased. Compilation failures were from new tests in main.

@jkczyz
jkczyz requested a review from TheBlueMattApril 23, 2026 22:17
@codecov

codecovBot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.28571% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (42e198c) to head (1d36f7b).
⚠️ Report is 28 commits behind head on main.

Files with missing linesPatch %Lines
lightning/src/ln/funding.rs25.00%9 Missing ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4570 +/- ##
==========================================
- Coverage 87.15% 86.16% -0.99% 
==========================================
Files 161 156 -5 Lines 109251 108669 -582 Branches 109251 108669 -582 ==========================================
- Hits 95215 93638 -1577 - Misses 11560 12420 +860 - Partials 2476 2611 +135 
FlagCoverage Δ
fuzzing-fake-hashes?
fuzzing-real-hashes?
tests86.16% <74.28%> (-0.06%)⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 3rd Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 4th Reminder

Hey @TheBlueMatt@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

@tnulltnullApr 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, what happens when we make multiple subsequent RBFs? Could/should we make this a Vec and track all prior conflicting Txids to make sure we're not losing any intermediary step when tracking these events?

Also, when integrating BDK's RBF events in LDK Node we found that it would be nice if the API clearly defined an ordering for the replaced_txids, i.e., so we can always identify the 'original' txid (under which we started tracking a certain payment). That is, if we make this a Vec it would be great if the API could guarantee that replaced_txids[0] is always the 'original'.

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Ok, discussed a bit offline. Ideally, we'll want to use a format in LDK Node that is compatible with batching. I've pushed a change that refactors TransactionType::Splice to TransactionType::InteractiveFunding, which supports batches of both v2 channel establishment and splices in the future. It also contains all previous attempts (including contributions) along with the corresponding txid instead of replaced_txid. Including simplifies the downstream handling. Then in 0.4 we can add re-broadcasts.

See how it's used in LDK Node here: lightningdevkit/ldk-node#888. LMK what you think.

TheBlueMatt
TheBlueMatt previously approved these changes Apr 28, 2026

@TheBlueMattTheBlueMatt left a comment

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.

Changes LGTM, no strong opinion on the API, if it works for LDK Node alright, though I agree exposing all the previous RBFs might be nice.

contribution: Option<FundingContribution>,
/// For an RBF replacement, the txid of the prior negotiated splice candidate being
/// replaced. `None` for the first splice attempt.
replaced_txid: Option<Txid>,

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.

That seems reasonable, though things get hairy if we have multiple splice-RBFs that conflict across channels. ISTM LDK Node might need to track that somehow, but maybe we can just avoid ever building such things?

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 5th Reminder

Hey @wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from 925b58d to 4b82770CompareApril 29, 2026 21:13
@jkczyz
jkczyz requested review from TheBlueMatt and tnullApril 29, 2026 22:35
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

CI is very sad but feel free to squash.

Comment threadlightning/src/ln/channel.rs Outdated
@jkczyzjkczyz changed the title Add splice details to TransactionType::SpliceInclude interactive funding candidates on broadcastApr 30, 2026
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 741430b to 8c3d58fCompareApril 30, 2026 19:01
Comment threadlightning/src/chain/chaininterface.rs Outdated
@jkczyz
jkczyz requested a review from TheBlueMattApril 30, 2026 19:16
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch 2 times, most recently from f245f31 to d94e8fbCompareApril 30, 2026 19:26
TheBlueMatt
TheBlueMatt previously approved these changes Apr 30, 2026
@ldk-reviews-bot

Copy link
Copy Markdown

🔔 6th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 1st Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 7th Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

@ldk-reviews-bot

Copy link
Copy Markdown

🔔 2nd Reminder

Hey @tnull@wpaulino! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

tnull
tnull previously approved these changes May 5, 2026

@tnulltnull left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, one non-blocking question.

Comment threadlightning/src/chain/chaininterface.rs
Comment threadlightning/src/chain/chaininterface.rs
@jkczyz
jkczyz dismissed stale reviews from tnull and TheBlueMatt via 6e1894bMay 5, 2026 17:46
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from d94e8fb to 6e1894bCompareMay 5, 2026 17:46
tnull
tnull previously approved these changes May 5, 2026
.get_funding_txid()
.expect("negotiated candidates should have a funding txid");
let contribution = i
.checked_sub(contrib_offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't the offset always 0 here? We're currently exchanging tx_signatures, which means we can't have a different negotiation in progress and the current one we're exchanging signatures for has already been tracked as a candidate above.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The offset is for indexing into contributions. We may not have contributed to earlier rounds, so contributions may have fewer elements than negotiated_candidates.

Comment threadlightning/src/chain/chaininterface.rs
jkczyzand others added 2 commits May 6, 2026 11:01
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>
Add public getters for `estimated_fee`, `inputs`, and `max_feerate`, and
elevate `feerate` from `pub(super)` to `pub`. Together with the existing
`value_added`, `outputs`, and `change_output`, this gives downstream
consumers of `TransactionType::Splice` (notably LDK Node, which updates
`PaymentDetails` from the broadcast callback) the data they need
without reaching into the raw transaction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jkczyz
jkczyzforce-pushed the 2026-04-splice-transaction-type branch from 6e1894b to 1d36f7bCompareMay 6, 2026 16:02
@jkczyz
jkczyz requested review from TheBlueMatt and wpaulinoMay 6, 2026 16:02
@TheBlueMatt
TheBlueMatt merged commit 38f5651 into lightningdevkit:mainMay 7, 2026
23 of 25 checks passed
@jkczyzjkczyz mentioned this pull request Jun 15, 2026
50 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants

@jkczyz@ldk-reviews-bot@ldk-claude-review-bot@TheBlueMatt@tnull@wpaulino