Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Don't rebroadcast redundant closing TX upon restart by tnull · Pull Request #2001 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't rebroadcast redundant closing TX upon restart by tnull · Pull Request #2001 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', '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('^' + ".*" + ' Don't rebroadcast redundant closing TX upon restart by tnull · Pull Request #2001 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino
, '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" + ' Don't rebroadcast redundant closing TX upon restart by tnull · Pull Request #2001 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino
, '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('^' + ".*" + ' Don't rebroadcast redundant closing TX upon restart by tnull · Pull Request #2001 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino
, '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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Don't rebroadcast redundant closing TX upon restart by tnull · Pull Request #2001 · lightningdevkit/rust-lightning · GitHub
Skip to content

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

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

Don't rebroadcast redundant closing TX upon restart - #2001

Closed
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart
Closed

Don't rebroadcast redundant closing TX upon restart#2001
tnull wants to merge 4 commits into
lightningdevkit:mainfrom
tnull:2023-02-no-rebroadcast-chanman-restart

Conversation

@tnull

@tnulltnull commented Feb 1, 2023

Copy link
Copy Markdown
Contributor

As of #1922, we don't rebroadcast the latest holder commitment transactions in ChannelMonitor::update_monitor() anymore if we had previously seen a closing tx on-chain. However, we still do so upon restarting / deserialization of ChannelManager.

With this change, we now also refrain from rebroadcasting upon restarting.

Let me know if there is a reason we always need to rebroadcast immediately.

While we don't rebroadcast the latest holder commitment transactions in
`ChannelMonitor::update_monitor()` anymore if we had previously seen a
closing tx on-chain, we still do so upon restarting / deserlization of
`ChannelManager`
With this change, we now also refrain from rebroadcasting upon
restarting.
@tnull
tnull requested a review from wpaulinoFebruary 1, 2023 17:20
@tnulltnull changed the title Don't rebroadcast closing TX upon restartDon't rebroadcast redundant closing TX upon restartFeb 1, 2023
@BitcoinZavior

Copy link
Copy Markdown

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

Base: 90.90% // Head: 90.88% // Decreases project coverage by -0.03%⚠️

Coverage data is based on head (b7462fd) compared to base (c59150a).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head b7462fd differs from pull request most recent head 65a1cfa. Consider uploading reports for the commit 65a1cfa to get more accurate results

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

Additional details and impacted files
@@ Coverage Diff @@## main #2001 +/- ##
==========================================
- Coverage 90.90% 90.88% -0.03% 
==========================================
Files 99 99 Lines 52570 52576 +6 Branches 52570 52576 +6 ==========================================
- Hits 47789 47783 -6 - Misses 4781 4793 +12 
Impacted FilesCoverage Δ
lightning/src/chain/channelmonitor.rs91.04% <100.00%> (+0.02%)⬆️
lightning/src/ln/channelmanager.rs87.28% <100.00%> (ø)
lightning/src/chain/onchaintx.rs94.92% <0.00%> (-0.82%)⬇️
lightning/src/ln/functional_tests.rs96.95% <0.00%> (-0.13%)⬇️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@wpaulinowpaulino 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, just one minor comment

Comment threadlightning/src/ln/channelmanager.rs Outdated
@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Because this broadcast is on init, its likely we have a stale view of the chain. What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Thanks @tnull this should resolve the issue when initialising a node from an existing config after closing the channel

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Because this broadcast is on init, its likely we have a stale view of the chain.

Agreed.

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed. Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

I'm curious what this issue is - transactions may fail to broadcast for any number of reasons, it shouldn't be an error to see a transaction which fails to enter the mempool.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

@wpaulino

Copy link
Copy Markdown
Contributor

What happens (and maybe its worth adding a test) if we start thinking a channel's funding output is spent (with 1 conf) but then after running for a second we see a reorg and now its not spent? We'd need to rebroadcast our local commitment tx in that case.

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

We def. need to rebroadcast the commitment tx in case the original closing tx is reorged out, as even before #1922 our redundant broadcast would have failed.

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

Relying on rebroadcasting on restart doesn't work, as it may be a long time before the node is restarted. I hope we do this right anyways, but currently fail to see where we do it. Will see to include a fix and test.

Yea, that's true. we should handle this explicitly, no complaints here.

Ah, when restarting LDK Node nodes we'd hit an unreachable codepath due to the runtime not being available when ChannelManager gets read. I now simply removed the panic as we currently ignore broadcast failures anyways, and we'll look into caching+rebroadcasting strategies soon.

Heh, right, can't do that :). I guess if we fix this properly we can change ChannelManager to not broadcast during read, which would be nice for issues like this.

@tnull

tnull commented Feb 1, 2023

Copy link
Copy Markdown
ContributorAuthor

Wait, why would it have failed? The bitcoind we're sending to should be synced, hopefully, even if we aren't.

For one broadcast to everything besides a local bitcoind instance's mempool could be pretty unreliable. My main point however was that any redundant broadcasts would just fail, and broadcasting once more on restart really doesn' t protect us at all from a reorg happening just after having read ChannelManager: in both cases (avoiding redundancy or just having it fail), we'd need to be able to react to reorgs ASAP.

@wpaulino

Copy link
Copy Markdown
Contributor

Ah, good point. I guess here we'd only want to avoid the broadcast once we've seen a commitment onchain with >= 6 confs, i.e., once funding_spend_confirmed is set, since we assume reorgs will not be longer than that.

Not sure why the restart case would be that different from #1922? After the restart we to sync to chain tip anyways and then need to react to any reorgs encountered?

Just noting we could avoid the broadcast on restart if the commitment is confirmed past our expectation for reorgs. However, I agree we should avoid broadcasting at all during restart and should only broadcast if it's not confirmed anymore after we detect a reorg and we're synced.

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

@tnull

tnull commented Feb 2, 2023

Copy link
Copy Markdown
ContributorAuthor

So it sounds like we're all on the same page. IMO we should hold this until we can remove the broadcast-during-deser entirely with a complete fix on the monitor side.

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine, as it would do the right thing on block_{connected, disconnected}, i.e., that the main thing missing here would be to add a test case that asserts this. Any thoughts on that?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

After having a look at the code and speaking to @wpaulino offline I was under the impression that our current rebroadcasting logic in OnchainTxHandler should handle this just fine

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed? If we do, we should just remove the during-deser broadcast entirely, replacing it with a close after we get going.

@wpaulino

Copy link
Copy Markdown
Contributor

Hmm, we have a bunch of logic under cfg(anchors) to bump commitment txn, but I don't immediately see where we rebroadcast commitment txn for channels that are closed?

When generating the claim for an untractable package, we just broadcast the transaction as shown below:

returnSome((None,0,OnchainClaim::Tx(tx)));

broadcaster.broadcast_transaction(&tx);

broadcaster.broadcast_transaction(&bump_tx);

broadcaster.broadcast_transaction(&bump_tx);

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

@wpaulino

Copy link
Copy Markdown
Contributor

The channel's already closed at this point though, so hasn't one already been generated?

@tnull

tnull commented Feb 6, 2023

Copy link
Copy Markdown
ContributorAuthor

Ah, okay, sorry, I had missed where we do HolderFundingOutput packages. So ISTM we shouldn't be changing the logic to broadcast_if_unspent here, but instead just generate a ChannelMonitorUpdateStep::ChannelForceClosed for processing later.

Mh, that def. makes more sense to me, but don't we still only want to create a redundant ChannelForceClosed if we hadn't seen a closing on chain?

Nvm, checking that it's not redundant would happen anyways in this case. So question would indeed be if we really need to regenerate the update step at all?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

The channel's already closed at this point though, so hasn't one already been generated?

I'm not sure? The point of this check is to be a belt-and-suspenders check that the monitor knows the channel is closed if the manager thinks it is. I'm not at all confident there isn't some race case on write order that would break that.

@wpaulino

Copy link
Copy Markdown
Contributor

After reviewing #1897, I see there's a chance of this happening if the ChannelManager is persisted, but the ChannelMonitorUpdate isn't and we crash. As of that PR and it's follow-up work, we'll queue and persist the ChannelMonitorUpdates within each Channel, so once we apply those updates we should be safe then?

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

I don't think so? We could have a channel be created, persist the monitor, update the channel once or twice, force-close the channel, fail to update the monitor, and crash, all without having persisted the ChannelManager. In that case we probably still need this to work.

@tnull

tnull commented Mar 7, 2023

Copy link
Copy Markdown
ContributorAuthor

Closing this as superseded by #2059.

@tnulltnull closed this Mar 7, 2023
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.

5 participants

@tnull@BitcoinZavior@codecov-commenter@TheBlueMatt@wpaulino