diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index f1786441c..38ed88d38 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -23,6 +23,12 @@ type LocalUnilateralCloseInfo struct { *lnwallet.LocalForceCloseSummary } +// CooperativeCloseInfo encapsulates all the informnation we need to act +// on a cooperative close that gets confirmed. +type CooperativeCloseInfo struct { + *channeldb.ChannelCloseSummary +} + // ChainEventSubscription is a struct that houses a subscription to be notified // for any on-chain events related to a channel. There are three types of // possible on-chain events: a cooperative channel closure, a unilateral @@ -42,9 +48,7 @@ type ChainEventSubscription struct { // CooperativeClosure is a signal that will be sent upon once a // cooperative channel closure has been detected confirmed. - // - // TODO(roasbeef): or something else - CooperativeClosure chan struct{} + CooperativeClosure chan *CooperativeCloseInfo // ContractBreach is a channel that will be sent upon if we detect a // contract breach. The struct sent across the channel contains all the @@ -232,7 +236,7 @@ func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription { ChanPoint: c.cfg.chanState.FundingOutpoint, RemoteUnilateralClosure: make(chan *lnwallet.UnilateralCloseSummary, 1), LocalUnilateralClosure: make(chan *LocalUnilateralCloseInfo, 1), - CooperativeClosure: make(chan struct{}, 1), + CooperativeClosure: make(chan *CooperativeCloseInfo, 1), ContractBreach: make(chan *lnwallet.BreachRetribution, 1), Cancel: func() { c.Lock() @@ -511,26 +515,24 @@ func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDet SettledBalance: localAmt, CloseType: channeldb.CooperativeClose, ShortChanID: c.cfg.chanState.ShortChanID(), - IsPending: false, + IsPending: true, RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation, RemoteNextRevocation: c.cfg.chanState.RemoteNextRevocation, LocalChanConfig: c.cfg.chanState.LocalChanCfg, } - err := c.cfg.chanState.CloseChannel(closeSummary) - if err != nil && err != channeldb.ErrNoActiveChannels && - err != channeldb.ErrNoChanDBExists { - return fmt.Errorf("unable to close chan state: %v", err) + + // Create a summary of all the information needed to handle the + // cooperative closure. + closeInfo := &CooperativeCloseInfo{ + ChannelCloseSummary: closeSummary, } - log.Infof("closeObserver: ChannelPoint(%v) is fully "+ - "closed, at height: %v", - c.cfg.chanState.FundingOutpoint, - commitSpend.SpendingHeight) - + // With the event processed, we'll now notify all subscribers of the + // event. c.Lock() for _, sub := range c.clientSubscriptions { select { - case sub.CooperativeClosure <- struct{}{}: + case sub.CooperativeClosure <- closeInfo: case <-c.quit: c.Unlock() return fmt.Errorf("exiting") diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 8a2403e5a..4ff382824 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -338,6 +338,10 @@ const ( // localCloseTrigger is a transition trigger driven by our commitment // being confirmed. localCloseTrigger + + // coopCloseTrigger is a transition trigger driven by a cooperative + // close transaction being confirmed. + coopCloseTrigger ) // String returns a human readable string describing the passed @@ -356,6 +360,9 @@ func (t transitionTrigger) String() string { case localCloseTrigger: return "localCloseTrigger" + case coopCloseTrigger: + return "coopCloseTrigger" + default: return "unknown trigger" } @@ -421,10 +428,16 @@ func (c *ChannelArbitrator) stateStep(triggerHeight uint32, case userTrigger: nextState = StateBroadcastCommit + // If the trigger is a cooperative close being confirmed, then + // we can go straight to StateFullyResolved, as there won't be + // any contracts to resolve. + case coopCloseTrigger: + nextState = StateFullyResolved + // Otherwise, if this state advance was triggered by a // commitment being confirmed on chain, then we'll jump // straight to the state where the contract has already been - // closed. + // closed, and we will inspect the set of unresolved contracts. case localCloseTrigger: log.Errorf("ChannelArbitrator(%v): unexpected local "+ "commitment confirmed while in StateDefault", @@ -1372,16 +1385,28 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { // We've cooperatively closed the channel, so we're no longer // needed. We'll mark the channel as resolved and exit. - case <-c.cfg.ChainEvents.CooperativeClosure: - log.Infof("ChannelArbitrator(%v) closing due to co-op "+ - "closure", c.cfg.ChanPoint) + case closeInfo := <-c.cfg.ChainEvents.CooperativeClosure: + log.Infof("ChannelArbitrator(%v) marking channel "+ + "cooperatively closed", c.cfg.ChanPoint) - if err := c.cfg.MarkChannelResolved(); err != nil { - log.Errorf("Unable to mark contract "+ - "resolved: %v", err) + err := c.cfg.MarkChannelClosed( + closeInfo.ChannelCloseSummary, + ) + if err != nil { + log.Errorf("unable to mark channel closed: "+ + "%v", err) + return } - return + // We'll now advance our state machine until it reaches + // a terminal state, and the channel is marked resolved. + _, _, err = c.advanceState( + closeInfo.CloseHeight, coopCloseTrigger, + ) + if err != nil { + log.Errorf("unable to advance state: %v", err) + return + } // We have broadcasted our commitment, and it is now confirmed // on-chain. @@ -1440,9 +1465,6 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) { HtlcResolutions: *uniClosure.HtlcResolutions, } - // TODO(roasbeef): modify signal to also detect - // cooperative closures? - // As we're now acting upon an event triggered by the // broadcast of the remote commitment transaction, // we'll swap out our active HTLC set with the set