Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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" + '
Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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('^' + ".*" + ' Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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('^' + ".*" + ' Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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" + ' Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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('^' + ".*" + ' Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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('^' + ".*" + ' Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai
, '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); } })(); })(); Add ChannelManager support for monitor update failure in one place by TheBlueMatt · Pull Request #213 · lightningdevkit/rust-lightning · GitHub
Skip to content

Add ChannelManager support for monitor update failure in one place - #213

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause
Oct 23, 2018
Merged

Add ChannelManager support for monitor update failure in one place#213
TheBlueMatt merged 7 commits into
lightningdevkit:masterfrom
TheBlueMatt:2018-10-monitor-fail-pause

Conversation

@TheBlueMatt

@TheBlueMattTheBlueMatt commented Oct 18, 2018

Copy link
Copy Markdown
Collaborator

This is the first tiny step towards #61, mostly implementing the logic at the Channel level and getting the structure in place. It only actually adds support for failed ChannelMonitor updates in one place (send_payment's generated monitor update). I'm mostly PR'ing this cause it got large and I want to shift focus to 0.0.6 but don't want this to bitrot.

Based on #212.

You can tell how confident I am in this all being correct by the size of the tests...

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch 3 times, most recently from c884ffe to 6a90619CompareOctober 18, 2018 17:20
route: route.clone(),
session_priv: session_priv.clone(),
}, onion_packet).map_err(|he| APIError::ChannelUnavailable{err: he.err})?
};

@yuntaiyuntaiOct 19, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we could remove update and commitment_signed from the returns from chan.send_htlc_and_comit() and chan.get_last_commitment_update() here? seems it could simplify code here.

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.

Hmm, would have to think harder about that refactor, but I dont think it would help here - we have to get the new ChannelMonitor out of chan, then drop the chan/channel_state reference, then call add_update_monitor, then (possibly) call handle_monitor_update_fail either way, so even if we dont have to pass the commitment update up we'd still have (most of) the awkward structure here.

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.

The operation with add_update_monitor seems to require an individual lock for each channel and under which add_update_monitor is called. Plus, get_last_commitment_update(). Without the lock, it seems we may need a rollback logic on the channel state.

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.

Indeed, that's why its currently all under the channel_state lock (or did I misunderstand your point?).

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.

Yeah, basically the right point. Was thinking more like more fine-grained locks - perhaps 2 for the remote and the local state for each channel. And we don't generate messages if we can't update channel_monitor and the client API call is blocked (or fail/queue after try-lock).
For the receiving side, I guess the incoming messages are buffered (bounded sized buffer) while update_monitor() is on retry logic.
Wonder it's too pessimistic affecting perf/, but there is nothing much to do/progress without committing states to monitor?

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.

Yea, as I noted at #213 (comment) there's kinda two approaches that could have been taken here - I took the more complicated approach that doesnt require message queueing but could have just queued messages and handled the DoS concerns some other way. Eventually we should move towards per-channel locks or so to fix the paralellism issue here.

@ariard

Copy link
Copy Markdown

What is the whole thought process of moving the channel_monitor updates inside channel_state lock ? I mean it's to be sure that channel stuck to go forward if monitor fail to register update ?

Comment threadsrc/ln/msgs.rs Outdated

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be
/// variable at rumtime. In those cases, this enum is also returned.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*runtime

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 kinda prefer rum-time, but, ok, fixed :p

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ah nop, need to get out the 0.1 before to get there ;)

Comment threadsrc/ln/msgs.rs
}

/// For events which result in both a RevokeAndACK and a CommitmentUpdate, by default they should
/// be sent in the order they appear in the return value, however sometimes the order needs to be

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe precise an example in which case the order needs to be variable?

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.

Added.

Comment threadsrc/ln/channel.rs Outdated
cur_local_commitment_transaction_number: u64,
cur_remote_commitment_transaction_number: u64,
value_to_self_msat: u64, // Excluding all pending_htlcs, excluding fees
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*upon, *receipt

Comment threadsrc/ln/channel.rs Outdated
/// Unon recipt of a channel_reestablish we have to figure out whether to send a revoke_and_ack
/// first or a commitment update first. Generally, we prefer to send revoke_and_ack first, but
/// if we had a pending commitment update of our own waiting on a remote revoke when we
/// received the latest commitment update from the remote we have to make sure that gets resent

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*it gets?

@ariard

Copy link
Copy Markdown

Tested and reviewed the message ordering part, seems good, started to review the more consequential ChannelMonitor part, will finish later today/tomorrow

@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

The move inside the lock is, sadly, required to keep things consistent. Actually right now things are kinda fucked - message must be sent to the remote end in the order they are delivered, but we don't actually have any mechanism to enforce that when they are delivered in different ways - we either deliver messages by putting them in events (and assume that those events get processed before any further calls into ChannelManager generate messages for the same peer) or by retunring them. This could absolutely lead to bugs, but its made even worse by having callbacks into the user's code in the middle of this process, where those callbacks could absolutely take variable lenghts of time (eg if they're sending to a watchtower over the network especially). Moving them inside the lock at least helps address this, but all that to say I'm not too worried about breaking locking crap right now, cause it's gonna need to be re-written anyway.

That aside, there are kinda two ways I could have approached channel pausing - either a) store inbound/outbound messages for a given channel in some Vec in ChannelManager and don't deliver anything until the monitor is updated or b) the way its done here - deliver things and process them but don't generate responses until we get a monitor update in. Option (a) is way simpler, but also leads to DoS questions about peers sending us a bunch of messages that we can't process/check but keep around in memory until some time in the future. Option (b) is way more complicated, but I think overall (if its actually implementable without too much complexity blowup) a better approach.

Option (b) here, however, does mean that we have to call Channel::monitor_update_failed before any other calls into the same Channel happen after the call which resulted in a monitor updating fail. We'd have a similar issue for option (a) in terms of making sure further messages aren't sent to the peer, but it may be simpler.

Sorry, I guess I should have described a bunch of that in the PR description.

chan
};
mem::drop(channel_state_lock);
self.finish_force_close_channel(chan.force_shutdown());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

maybe we could modify finish_force_close_channel parameters to get an Option<channel_state> to avoid drop-then-get-same-lock again in case of htlc to fail backward ?

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.

Hmm, sadly fail_htlc_backwards_internal eats the lock so it still has to be taken multiple times inside the fail loop in finish_force_close_channel. If we move towards per-channel locks it'll be a different lock anyway, so that wont help.

Comment threadsrc/ln/channelmanager.rs
/// instead return PermanentFailure to force closure of the channel ASAP.
///
/// Even when a channel has been "frozen" updates to the ChannelMonitor can continue to occur
/// (eg if an inbound HTLC which we forwarded was claimed upstream resulting in us attempting

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hmm in case of "frozen" channel could we have a ChannelMonitor trying to claim ghost htlc outputs, and fail to do so, on remote commitment tx, this last one up-to-date without the concerned htlc output because we pass it the preimage ? Seems not to me because remote commitment tx needs commitment_signed but wonder if there is other weird cases like that.

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.

Not sure what you mean by "ghost htlc outputs"? The unupdated ChannelMonitor should ignore any offered HTLC outputs that it doesn't have the preimage for assuming the remote side is just gonna claim them via timeout, whereas our local/updated copy should do the claim.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It wasn't exactly the case I had in head, the one was what if we have a channel in ChannelMonitorUpdateErr::TemporaryFailure and we pass backward the preimage via claim_funds ? Harmless in fact because other node needs commitment_signed. Go ahead, I was just trying to speculate on weird on-chain consequences in case of frozen channel..

Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channel.rs Outdated
}
}
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Due to being in a post-monitor-failed state, we ensure that order is reset to normal after all freeze messages habe been sent" ? Sorry don't find the comment enough explicit..

Comment threadsrc/ln/channel.rs Outdated
self.cur_remote_commitment_transaction_number -= 1;
self.received_commitment_while_awaiting_raa = false;
if self.channel_state & (ChannelState::MonitorUpdateFailed as u32) == 0 {
// This is a response to our post-monitor-failed unfreeze messages...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same as above

@ariard

Copy link
Copy Markdown

Side-thought : if channel went to be unconfirmed due to a reorg maybe we should notify SimpleManyChannelMonitor to prune relative ChannelMonitor ?

Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channel.rs
Comment threadsrc/ln/channelmanager.rs
Comment threadsrc/ln/channelmanager.rs
@ariard

Copy link
Copy Markdown

Well, apart of do_test_monitor_temporary_update_fail (see comment) I've reviewed the whole. As a temporary solution waiting for fine-grained locks, I'm okay with this approach and the logic seems good to me. For the details please see the bunch of comments.

Option a) seems to me a second-best choice, yes per-channel locking need maybe more thought at first but once done it'll avoid us an intermediate logic layer between ChannelManager and PeerHandler to bufferize/prune/resend messages.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from 22d89eb to 71d666eCompareOctober 20, 2018 19:31
@TheBlueMatt

TheBlueMatt commented Oct 20, 2018

Copy link
Copy Markdown
CollaboratorAuthor

Re: side-note: Indeed, ChannelMonitor pruning should be implemented...somewhere. I dont believe we do any pruning at all right now?

Note that the (a)/(b) options above don't (really) have a ton to do with per-channel locking, only a question of whether we want an inbound-message-buffer in ChannelManager, mostly.

@TheBlueMatt
TheBlueMattforce-pushed the 2018-10-monitor-fail-pause branch from b30511d to 497643aCompareOctober 23, 2018 20:03
@TheBlueMatt

Copy link
Copy Markdown
CollaboratorAuthor

Gonna go ahead and take this to move #217/0.0.6 forward, though review of the test and any other bits that folks want to do post-merge is obviously also welcome.

@TheBlueMatt
TheBlueMatt merged commit 3bcd911 into lightningdevkit:masterOct 23, 2018
Comment threadsrc/ln/channel.rs
if (self.channel_state & (ChannelState::PeerDisconnected as u32)) == (ChannelState::PeerDisconnected as u32) {
panic!("Cannot create commitment tx while disconnected, as send_htlc will have returned an Err so a send_commitment precondition has been violated");
}
if (self.channel_state & (ChannelState::MonitorUpdateFailed as u32)) == (ChannelState::PeerDisconnected as u32) {

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.

oops?

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@ariard@yuntai