') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); [Early Draft][Splicing] Interactive tx negotiation, with a single ChannelContext by optout21 · Pull Request #3715 · lightningdevkit/rust-lightning · GitHub
Skip to content

[Early Draft][Splicing] Interactive tx negotiation, with a single ChannelContext - #3715

Closed
optout21 wants to merge 6 commits into
lightningdevkit:mainfrom
optout21:splice-dual-tx3
Closed

[Early Draft][Splicing] Interactive tx negotiation, with a single ChannelContext#3715
optout21 wants to merge 6 commits into
lightningdevkit:mainfrom
optout21:splice-dual-tx3

Conversation

@optout21

@optout21optout21 commented Apr 8, 2025

Copy link
Copy Markdown
Contributor

This is a continuation of #3444, but without duplication of ChannelContext.
A RefundingV2(RefundingChannel) phase is introduced, where RefundingChannel acts both as funded and pending channel. It has:

  • the already funded channel (FundedChannel)
  • impllements PendingV2ChannelTrait, a new trait also implemented by PendingV2, so it can act as a Pending channel. RefundingChannel also has similar fields to PendingV2Channel, except for context, which is reused from the funded channel.

Related PRs:

Supersedes #3630 .

@ldk-reviews-bot

ldk-reviews-bot commented Apr 8, 2025

Copy link
Copy Markdown

👋 I see @wpaulino was un-assigned.
If you'd like another reviewer assignemnt, please click here.

Comment on lines +1494 to 1519
pub fn tx_add_input(&mut self, msg: &msgs::TxAddInput) -> InteractiveTxMessageSendResult {
match &mut self.phase {
ChannelPhase::UnfundedV2(chan) => chan.tx_add_input(msg),
#[cfg(splicing)]
ChannelPhase::RefundingV2(chan) => chan.tx_add_input(msg),
_ => panic!("Got tx_add_input in an invalid phase"),
}
}

pub fn tx_add_output(&mut self, msg: &msgs::TxAddOutput) -> InteractiveTxMessageSendResult {
match &mut self.phase {
ChannelPhase::UnfundedV2(chan) => chan.tx_add_output(msg),
#[cfg(splicing)]
ChannelPhase::RefundingV2(chan) => chan.tx_add_output(msg),
_ => panic!("Got tx_add_output in an invalid phase"),
}
}

pub fn tx_complete(&mut self, msg: &msgs::TxComplete) -> HandleTxCompleteResult {
match &mut self.phase {
ChannelPhase::UnfundedV2(chan) => chan.tx_complete(msg),
#[cfg(splicing)]
ChannelPhase::RefundingV2(chan) => chan.tx_complete(msg),
_ => panic!("Got tx_complete in an invalid phase"),
}
}

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.

I don't think we can panic in these?

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 aim of this PR is first to get the big picture right, conceptually, and not to work out all the details at once. Of course this has to be refined.

Comment on lines 1289 to 1305
pub fn as_funded(&self) -> Option<&FundedChannel<SP>> {
if let ChannelPhase::Funded(channel) = &self.phase {
Some(channel)
} else {
None
match &self.phase {
ChannelPhase::Funded(channel) => Some(&channel),
#[cfg(splicing)]
ChannelPhase::RefundingV2(channel) => Some(&channel.funded_channel),
_ => None,
}
}

pub fn as_funded_mut(&mut self) -> Option<&mut FundedChannel<SP>> {
if let ChannelPhase::Funded(channel) = &mut self.phase {
Some(channel)
} else {
None
match &mut self.phase {
ChannelPhase::Funded(channel) => Some(channel),
#[cfg(splicing)]
ChannelPhase::RefundingV2(channel) => Some(&mut channel.funded_channel),
_ => None,
}
}

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.

Hmmm... can we do this? For instance, internal_update_add_htlc calls as_funded_mut. Presumably, for the RefundingV2 case, the channel would be in quiescence, so the check in internal_update_add_htlc would prevent this from being an issue. So maybe this is desirable if we need to cover both cases elsewhere. IIUC, we would be in quiescence if and only if we are in ChannelPhase::RefundingV2. cc: @wpaulino

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.

Touchy question. If the phase when the new funding has been negotiated but not yet locked is not represented by RefundingV2, then you are probably right.
However, currently as_funded is used in many cases, such as listing channels or force close.

let (pending_splice_post, post_funding, dual_funding_context, unfunded_context) =
prev_chan.splice_init(msg, our_funding_contribution)?;

let _res = self.phase_from_funded_to_splice(post_funding, dual_funding_context, unfunded_context, pending_splice_post)?;

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.

We should just inline phase_from_funded_to_splice in the two places it is used rather than introducing unreachables. There isn't much happening in there to justify refactoring it into a method, IMO.

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.

I consider these concerns low-level code improvement, not in focus in this PR. Being in separate functions increases visibility. Should be addressed in non-draft PR.

// let (signing_session, holder_commitment_point, commitment_signed, event) =
let (commitment_signed, event) =
chan.funding_tx_constructed(signing_session, &&logger)?;
let _res = self.phase_from_splice_to_funded()?;

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.

Why swallow the error?

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.

No error is swallowed here, ? is used. the return value is nothing (()). Or do you mean something else?

let (pending_splice_post, post_funding, dual_funding_context, unfunded_context, our_funding_contribution) =
prev_chan.splice_ack(msg)?;

let _res = self.phase_from_funded_to_splice(post_funding, dual_funding_context, unfunded_context, pending_splice_post)?;

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.

Similarly, why swallow the error here and in splice_init?

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.

No error is swallowed here, ? is used. the return value is nothing (()). Or do you mean something else?

@ldk-reviews-bot

Copy link
Copy Markdown

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

@jkczyz
jkczyz requested a review from wpaulinoApril 8, 2025 23:18
@wpaulino
wpaulino removed their request for review April 10, 2025 00:42
@optout21

Copy link
Copy Markdown
ContributorAuthor

Continued in #3736, closing this one

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@optout21@ldk-reviews-bot@jkczyz