Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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" + '
Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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('^' + ".*" + ' Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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('^' + ".*" + ' Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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" + ' Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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('^' + ".*" + ' Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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('^' + ".*" + ' Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz
, '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); } })(); })(); Clean up and more liberally free holding cell HTLCs by TheBlueMatt · Pull Request #756 · lightningdevkit/rust-lightning · GitHub
Skip to content

Clean up and more liberally free holding cell HTLCs - #756

Closed
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd
Closed

Clean up and more liberally free holding cell HTLCs#756
TheBlueMatt wants to merge 13 commits into
lightningdevkit:mainfrom
TheBlueMatt:2020-11-holding-cell-clear-mon-upd

Conversation

@TheBlueMatt

Copy link
Copy Markdown
Collaborator

This is effectively a superset of #755 (with the same commit) and an alternate approach to #754, instead holding onto the holding cell HTLCs and supporting handling them correctly in response to a channel_reestablish message. This was more of a slap-together thing, and still needs testing, but seeking concept ACKs on the restructure and approach (also to moving towards addressing #661).

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

@valentinewallace are you happier with this?

@TheBlueMattTheBlueMatt added this to the 0.0.13 milestone Nov 21, 2020
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from 4a8bec0 to 2eb9070CompareNovember 24, 2020 01:20
@valentinewallace

valentinewallace commented Nov 28, 2020

Copy link
Copy Markdown
Contributor

In terms of:

an alternate approach to #754

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect," (aka mainly just 54a601c) then I'm conceptACK. Still have to review the other parts of the PR in detail but that lgtm approach-wise!

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

If it would be fair to summarize this approach as: "don't fail back holding cell add HTLCs on disconnect,"

Correct.

then I'm conceptACK

Alright, I'll try to clean this up and get the macros to build on pre-NLL rust.

Edit: fwiw, lnd also doesn't fail back HTLCs on disconnect afaict.

Right, I presume most others don't, of course this is just on holding-cell stuff so its a really strange edge-case anyway.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch 2 times, most recently from f31497a to 5552b67CompareDecember 15, 2020 22:00
@TheBlueMatt
TheBlueMatt marked this pull request as ready for review December 15, 2020 22:00
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

This has been updated to build on pre-NLL rust and rebased.

@codecov

codecovBot commented Dec 15, 2020

Copy link
Copy Markdown

Codecov Report

Merging #756 (7f7fee5) into main (beb88e6) will increase coverage by 1.49%.
The diff coverage is 92.85%.

Impacted file tree graph

@@ Coverage Diff @@## main #756 +/- ##
==========================================
+ Coverage 91.00% 92.50% +1.49% 
==========================================
Files 48 45 -3 Lines 25483 30571 +5088 ==========================================
+ Hits 23192 28279 +5087 - Misses 2291 2292 +1 
Impacted FilesCoverage Δ
lightning/src/ln/functional_test_utils.rs94.97% <ø> (ø)
lightning/src/ln/channel.rs92.13% <92.59%> (+4.32%)⬆️
lightning/src/ln/channelmanager.rs89.64% <93.93%> (+4.40%)⬆️
lightning-block-sync/src/lib.rs0.00% <0.00%> (-95.38%)⬇️
lightning/src/ln/wire.rs59.24% <0.00%> (-4.87%)⬇️
lightning/src/util/ser.rs90.32% <0.00%> (-0.30%)⬇️
lightning/src/ln/features.rs98.76% <0.00%> (-0.08%)⬇️
lightning/src/chain/mod.rs100.00% <0.00%> (ø)
lightning/src/util/errors.rs71.42% <0.00%> (ø)
lightning/src/chain/chainmonitor.rs94.11% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9fba7c9...76db5db. Read the comment docs.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 5552b67 to 393a420CompareJanuary 27, 2021 19:16
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Rebased without changes (just include conflicts).

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment threadlightning/src/ln/channel.rs Outdated
Comment on lines +2300 to +2367
/// In some cases, this may generate a monitor update, resulting in a call to the
/// `chain::Watch`'s `update_channel` method for the same channel monitor which is being
/// notified of a successful update here. Because of this, please be very careful with
/// reentrancy bugs! It is incredibly easy to write an implementation of `update_channel` which
/// will take a lock which is also held when calling this method.
///

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.

Is this situation any different from other pub methods that call update_channel?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Only in the sense that we're updating precisely the ChannelMonitor (via update_channel) that we're being notified has been successfully updated. I'm not quite sure how to better capture this in the docs here, but its rather easy (as we do in tests), to hit this case (but only rarely because you only ever get the callbacks rarely).

struct TestChainMonitor {
monitor_state: Mutex<HashMap<OutPoint, StateInformation>>,
}
impl TestChainMonitor {
fn finish_async_update() {
let monitor_state = self.monitor_state.lock().unwrap();
...
channel_manager.channel_monitor_updated(...);
}
}
impl chain::Watch for TestChainMonitor {
fn update_channel(&self, funding_txo: OutPoint, update: channelmonitor::ChannelMonitorUpdate) -> Result<(), channelmonitor::ChannelMonitorUpdateErr> {
let this_monitor_state = self.monitor_state.lock().unwrap();
...
start_async_update(update);
Err(TemporaryFailure)
}
....
}

Comment threadlightning/src/ln/channelmanager.rs Outdated
Comment on lines 927 to 945
}
if let Some((id, _)) = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding) {
nodes[1].channel_monitor_updated(&chan_1_funding, *id);
{
let mon_id = monitor_b.latest_monitors.lock().unwrap().get(&chan_1_funding).map(|(id, _)| *id);
if let Some(id) = mon_id {
nodes[1].channel_monitor_updated(&chan_1_funding, id);
}
}

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.

Was the lock held outside of this scope and thus causing a deadlock?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Yes, latest_monitors is locked again inside update_channel, which can be called eventually by the channel_monitor_updated call.

@valentinewallacevalentinewallace 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.

I'm conceptACK on this approach, and in favor of opening an issue or so to look into a more events-based approach that's less risky but requires more refactoring (as discussed offline).

It'd be nice to add a test for the fuzz failure if it's not too difficult.

Otherwise, this is looking pretty good to me. Most of the changes are super mechanical.

Comment threadlightning/src/ln/channelmanager.rs Outdated
($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

This line also adds reentrancy risk, right? (in addition to the reentrancy risk of freeing the holding cell HTLCs)

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I don't believe so, at least not from the PoV of calling user code - handle_error shouldn't ever call user code directly, only pushing events onto the message/regular event queues.

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Ah! I see your point. Tracing it back a bit, I think the only way we have an Err in res here is if we break the handle_chan_restoration_locked loop with a handle_monitor_err result, which is precisely only if the monitor update that we generated failed.

@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from ed1194c to 7f7fee5CompareFebruary 21, 2021 02:05
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

I'll finish working on some tests today, given it seems likely we'll move in this direction, at least in the short term.

@TheBlueMattTheBlueMatt modified the milestones: 0.0.13, 0.0.14Feb 26, 2021
The channel restoration code in channel monitor updating and peer
reconnection both do incredibly similar things, and there is
little reason to have them be separate. Sadly because they require
holding a lock with a reference to elements in the lock, its not
practical to make them utility functions, so instead we introduce
a two-step macro here which will eventually be used for both.
Because we still support pre-NLL Rust, the macro has to be in two
parts - one which runs with the channel_state lock, and one which
does not.
This mostly swaps some Vecs that can only ever contain one element
for Options.
If there is no pending channel update messages when monitor updating
is restored (though there may be an RAA to send), and we're
connected to our peer and not awaiting a remote RAA, we need to
free anything in our holding cell.
Without this, chanmon_fail_consistency was able to find a stuck
condition where we sit on an HTLC failure in our holding cell and
don't ever handle it (at least until we have other actions to take
which empty the holding cell).
Still, this approach sucks - it introduces reentrancy in a
particularly dangerous form:
a) we re-enter user code around monitor updates while being called
from user code around monitor updates, making deadlocks very
likely (in fact, our current tests have a bug here!),
b) the re-entrancy only occurs in a very rare case, making it
likely users will not hit it in testing, only deadlocking in
production.
I'm not entirely sure what the alternative is, however - we could
move to a world where we poll for holding cell events that can be
freed on our 1-minute-timer, but we still have a super rare
reentrancy case, just in timer_chan_freshness_every_min() instead.
This merges the code for restoring channel functionality between
channel monitor updating restored and peer reconnection, reducing
redundant code.
Previously, if we get a temporary monitor update failure while
there were HTLCs pending forwarding in the holding cell, we'd clear
them and fail them all backwards. This makes sense if temporary
failures are rare, but in an async environment, temporary monitor
update failures may be the normal case. In such a world, this
results in potentially a lot of spurious HTLC forwarding failures
(which is the topic of lightningdevkit#661).
As pointed out by Jeff, using a return struct instead of an
incredibly-long tuple improves readability in several places.
60d83ef introduced reentrancy when
calling channel_monitor_updated. This commit fixes the
chanmon_consistency fuzzer to no longer deadlock as a result of this
reentrancy.
Our fuzz tests previously only printed the log output of the first
fuzz test case to fail. This commit changes that (with lots of
auto-generated updates) to ensure we print all log outputs.
These should never appear, so consider them a fuzzer fail case.
Because of the merge between peer reconnection and channel monitor
updating channel restoration code, we now sometimes generate
(somewhat spurious) announcement signatures when restoring channel
monitor updating. This should not result in a fuzzing failure.
If the fuzz target is failing due to a channel force-close, the
immediately-visible error is that we're signing a stale state. This
is because the ChannelMonitorUpdateStep::ChannelForceClosed event
results in a signature in the test clone which was deserialized
using a OnlyReadsKeysInterface. Instead, we need to deserialize
using the full KeysInterface instance.
@TheBlueMatt
TheBlueMattforce-pushed the 2020-11-holding-cell-clear-mon-upd branch from 7f7fee5 to 76db5dbCompareMarch 1, 2021 02:04
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Added a test which I think demonstrates the specific issue well (while also getting good coverage).

($self: ident, $locked_res: expr, $pending_failures: expr, $forwarding_failures: expr) => { {
let (htlc_forwards, funding_broadcast_safe, res, channel_id, counterparty_node_id) = $locked_res;

let _ = handle_error!($self, res, counterparty_node_id);

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.

Oh, it's hard to untangle the macros, but handle_error does call finish_force_close, which looks like it can call update_channel.

if commitment_update.is_none() && self.channel_state & (ChannelState::AwaitingRemoteRevoke as u32) == 0 {
order = RAACommitmentOrder::RevokeAndACKFirst;

let (update_opt, mut failed_htlcs) = self.free_holding_cell_htlcs(logger).unwrap();

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.

Since we now have the whole process_background_events thing, I wonder if it'd ease the reentrancy concerns a bit to shift this call over there?

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

I'm not a fan of using that for this because its hooked on the one minute timer which may take a while...however, I think it makes sense in get_and_clear_pending_msg_events. Going to open a new PR with that.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@TheBlueMatt@valentinewallace@jkczyz