-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[bugfix] Wait for confirmation before marking channel cooperatively closed #1248
Conversation
This PR breaks the integration tests as is. |
I'm not sure what this is attempting to fix, as we'll mark it fully closed on confirmation, but still want the RPC layer to reflect that the channel is about to be closed. |
This commit stops the chan closer from sending the potential coop close transactions to the chainwatcher, as this is no longer needed. The chainwatcher recently was modified to watch for any potential close, and will because of this handle the close regardless of which one appears in chain. When the chancloser broadcast the final close transaction, we mark it as CommitmentBroadcasted in the database.
Removes CooperativeCloseCtx and methods.
This commit makes the dispatchCooperativeClose method mark the channel fully closed directly, without registering for confirmation notifications first. We can do this as recent changes to the contractcourt changed the definition of a closed channel in the database to have had its closing tx confirmed, and we only dispatch the cooperative close once the transaction has 1 confirmation. We also rename the markChanClosed method to notifyChanClosed, to more clearly indicate that the ChainArbitrator no longer has to mark the channel fully closed in the database.
We no longer have to mark the channel as fully closed in the database, as it is done directly in the chainWatcher. Instead, we stop the watcher and delete it from the set of active watchers.
9bf4977
to
74205a6
Compare
While working on fixing the integration tests, I realized that the |
@halseth you never responded to the initial comment stating that the initial version of this PR wasn't necessary. Atm, we always mark a channel as closed after we broadcast the co-op close transaction. Only once it's confirmed do we mark it as fully closed. |
@@ -1671,7 +1644,6 @@ func (p *peer) handleLocalCloseReq(req *htlcswitch.ChanClose) { | |||
req.TargetFeePerKw, | |||
uint32(startingHeight), | |||
req, | |||
closeCtx, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// TODO(roasbeef): don't need, ChainWatcher will handle | ||
|
||
c.state = closeFinished | ||
if c.cfg.channel.MarkCommitmentBroadcasted(); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one side-effect of doing this, is that for a co-op close, we won't show what the proper final balances are (to account for the negotiated fees) until the closing transaction is fully closed.
@@ -751,147 +743,3 @@ func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail | |||
|
|||
return nil | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay code deletion!
@@ -465,53 +468,18 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet | |||
SettledBalance: localAmt, | |||
CloseType: channeldb.CooperativeClose, | |||
ShortChanID: c.cfg.chanState.ShortChanID(), | |||
IsPending: true, | |||
IsPending: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why false? It's fully closed here as this will only be dispatched once the co-op close transaction hits the chain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!IsPending
means that it is fully closed.
markChanClosed: func() error { | ||
// TODO(roasbeef): also need to pass in log? | ||
return c.resolveContract(chanPoint, nil) | ||
notifyChanClosed: func() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the chain watcher mark the channel as fully closed? Even in that case, the resolveContract
methods needs to be called in order to stop the channel arbitrator as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is being marked fully closed when IsPending
is set to false
in the chain watcher.
AFAICT the resolveContract
only sets IsPending = false
and stops the chain watcher, which we still do here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It also deletes the state of the chain arbitrator itself. As is now, this state will be left lingering after the channel has been closed cooperatively.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still can't see where this is happening (arbLog
will always be nil
):
func (c *ChainArbitrator) resolveContract(chanPoint wire.OutPoint,
arbLog ArbitratorLog) error {
log.Infof("Marking ChannelPoint(%v) fully resolved", chanPoint)
// First, we'll we'll mark the channel as fully closed from the PoV of
// the channel source.
err := c.chanSource.MarkChanFullyClosed(&chanPoint)
if err != nil {
log.Errorf("ChainArbitrator: unable to mark ChannelPoint(%v) "+
"fully closed: %v", chanPoint, err)
return err
}
if arbLog != nil {
// Once this has been marked as resolved, we'll wipe the log
// that the channel arbitrator was using to store its
// persistent state. We do this after marking the channel
// resolved, as otherwise, the arbitrator would be re-created,
// and think it was starting from the default state.
if err := arbLog.WipeHistory(); err != nil {
return err
}
}
c.Lock()
delete(c.activeChannels, chanPoint)
chainWatcher, ok := c.activeWatchers[chanPoint]
if ok {
chainWatcher.Stop()
}
c.Unlock()
return nil
}
To answer the original question: This would cause problems in the (unlikely) case where a different commit then what we broadcasted was confirmed, but more importantly if lnd/contractcourt/chain_arbitrator.go Line 319 in 5c42a88
For channels "pending close" we would start a restricted watcher: lnd/contractcourt/chain_arbitrator.go Line 371 in 5c42a88
Since we are only fetching channels from the A channel in the "waiting close" state is reflected in the RPC layer, since we have no certainty what kind of close it is going to go through. State machine is as follows:
==== closed chan bucket =====
|
The pending state definitin in ChannelCloseSummary was slightly changed in such a way that channels that has had their commitment broadcasted now is no longer considered "pending close". They now instead stay in the open chan bucket with the ChanStatus "CommitmentBroadcasted" until their commitment is confirmed. This commit updates the IsPending godoc to reflect this.
Ahh, I see what you're saying: although we expect a co-op close transaction to land in the chain, it may be the case that a force close transaction actually does instead. We look in the pending channel bucket (not the open channel bucket), so we would create an arbitrator in this limited form, but if a user attempted to force close again, then a panic would occur as the pointer wasn't set. |
markChanClosed: func() error { | ||
// TODO(roasbeef): also need to pass in log? | ||
return c.resolveContract(chanPoint, nil) | ||
notifyChanClosed: func() error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It also deletes the state of the chain arbitrator itself. As is now, this state will be left lingering after the channel has been closed cooperatively.
Actually |
Yep |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🔥
This PR fixes an issue where we could go through a cooperative close, and marking the channel as closed in the DB before it was confirmed. This would lead to potential issues as we wouldn't start the necessary submodules to watch for the final close on restarts.
In #1017 we changed the definition of a closed channel to be a channel that has had its closing tx confirmed. This PR makes this true also for cooperative closes.