contractcourt: create breachResolver if BreachResolution present

Also transitions to the proper state based on if this is a legacy
breach in the channel arbitrator or a modern breach with a resolver.
This commit is contained in:
eugene 2022-01-13 14:55:55 -05:00
parent af03c8cb46
commit b7407882ac
2 changed files with 122 additions and 28 deletions

View File

@ -147,7 +147,7 @@ const (
// | | |
// | | |-> StateCommitmentBroadcasted: chain/user trigger
// | | |
// | | |-> StateContractClosed: local/remote close trigger
// | | |-> StateContractClosed: local/remote/breach close trigger
// | | | |
// | | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | | | |
@ -157,9 +157,9 @@ const (
// | | | |
// | | | |-> StateFullyResolved: contract resolutions empty
// | | |
// | | |-> StateFullyResolved: coop/breach close trigger
// | | |-> StateFullyResolved: coop/breach(legacy) close trigger
// | |
// | |-> StateContractClosed: local/remote close trigger
// | |-> StateContractClosed: local/remote/breach close trigger
// | | |
// | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | | |
@ -169,11 +169,11 @@ const (
// | | |
// | | |-> StateFullyResolved: contract resolutions empty
// | |
// | |-> StateFullyResolved: coop/breach close trigger
// | |-> StateFullyResolved: coop/breach(legacy) close trigger
// |
// |-> StateContractClosed: local/remote close trigger
// |-> StateContractClosed: local/remote/breach close trigger
// | |
// | |-> StateWaitingFullResolution: contract resolutions empty
// | |-> StateWaitingFullResolution: contract resolutions not empty
// | | |
// | | |-> StateWaitingFullResolution: contract resolutions not empty
// | | |
@ -181,7 +181,7 @@ const (
// | |
// | |-> StateFullyResolved: contract resolutions empty
// |
// |-> StateFullyResolved: coop/breach close trigger
// |-> StateFullyResolved: coop/breach(legacy) close trigger
// StateDefault is the default state. In this state, no major actions
// need to be executed.

View File

@ -747,8 +747,8 @@ const (
coopCloseTrigger
// breachCloseTrigger is a transition trigger driven by a remote breach
// being confirmed. In this case the channel arbitrator won't have to
// do anything, so we'll just clean up and exit gracefully.
// being confirmed. In this case the channel arbitrator will wait for
// the breacharbiter to finish and then clean up gracefully.
breachCloseTrigger
)
@ -852,9 +852,8 @@ func (c *ChannelArbitrator) stateStep(
// 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. The same is true in the case of a
// breach.
case coopCloseTrigger, breachCloseTrigger:
// any contracts to resolve.
case coopCloseTrigger:
nextState = StateFullyResolved
// Otherwise, if this state advance was triggered by a
@ -868,6 +867,14 @@ func (c *ChannelArbitrator) stateStep(
fallthrough
case remoteCloseTrigger:
nextState = StateContractClosed
case breachCloseTrigger:
nextContractState, err := c.checkLegacyBreach()
if nextContractState == StateError {
return nextContractState, nil, err
}
nextState = nextContractState
}
// If we're in this state, then we've decided to broadcast the
@ -890,7 +897,23 @@ func (c *ChannelArbitrator) stateStep(
c.cfg.ChanPoint, trigger, StateContractClosed)
return StateContractClosed, closeTx, nil
case coopCloseTrigger, breachCloseTrigger:
case breachCloseTrigger:
nextContractState, err := c.checkLegacyBreach()
if nextContractState == StateError {
log.Infof("ChannelArbitrator(%v): unable to "+
"advance breach close resolution: %v",
c.cfg.ChanPoint, nextContractState)
return StateError, closeTx, err
}
log.Infof("ChannelArbitrator(%v): detected %s close "+
"after closing channel, fast-forwarding to %s"+
" to resolve contract", c.cfg.ChanPoint,
trigger, nextContractState)
return nextContractState, closeTx, nil
case coopCloseTrigger:
log.Infof("ChannelArbitrator(%v): detected %s "+
"close after closing channel, fast-forwarding "+
"to %s to resolve contract",
@ -994,10 +1017,18 @@ func (c *ChannelArbitrator) stateStep(
case localCloseTrigger, remoteCloseTrigger:
nextState = StateContractClosed
// If a coop close or breach was confirmed, jump straight to
// the fully resolved state.
case coopCloseTrigger, breachCloseTrigger:
// If a coop close was confirmed, jump straight to the fully
// resolved state.
case coopCloseTrigger:
nextState = StateFullyResolved
case breachCloseTrigger:
nextContractState, err := c.checkLegacyBreach()
if nextContractState == StateError {
return nextContractState, closeTx, err
}
nextState = nextContractState
}
log.Infof("ChannelArbitrator(%v): trigger %v moving from "+
@ -1996,10 +2027,62 @@ func (c *ChannelArbitrator) prepContractResolutions(
commitHash := contractResolutions.CommitHash
failureMsg := &lnwire.FailPermanentChannelFailure{}
var htlcResolvers []ContractResolver
// We instantiate an anchor resolver if the commitment tx has an
// anchor.
if contractResolutions.AnchorResolution != nil {
anchorResolver := newAnchorResolver(
contractResolutions.AnchorResolution.AnchorSignDescriptor,
contractResolutions.AnchorResolution.CommitAnchor,
height, c.cfg.ChanPoint, resolverCfg,
)
htlcResolvers = append(htlcResolvers, anchorResolver)
}
// If this is a breach close, we'll create a breach resolver, determine
// the htlc's to fail back, and exit. This is done because the other
// steps taken for non-breach-closes do not matter for breach-closes.
if contractResolutions.BreachResolution != nil {
breachResolver := newBreachResolver(resolverCfg)
htlcResolvers = append(htlcResolvers, breachResolver)
// We'll use the CommitSet, we'll fail back all outgoing HTLC's
// that exist on either of the remote commitments. The map is
// used to deduplicate any shared htlc's.
remoteOutgoing := make(map[uint64]channeldb.HTLC)
for htlcSetKey, htlcs := range confCommitSet.HtlcSets {
if !htlcSetKey.IsRemote {
continue
}
for _, htlc := range htlcs {
if htlc.Incoming {
continue
}
remoteOutgoing[htlc.HtlcIndex] = htlc
}
}
// Now we'll loop over the map and create ResolutionMsgs for
// each of them.
for _, htlc := range remoteOutgoing {
failMsg := ResolutionMsg{
SourceChan: c.cfg.ShortChanID,
HtlcIndex: htlc.HtlcIndex,
Failure: failureMsg,
}
msgsToSend = append(msgsToSend, failMsg)
}
return htlcResolvers, msgsToSend, nil
}
// For each HTLC, we'll either act immediately, meaning we'll instantly
// fail the HTLC, or we'll act only once the transaction has been
// confirmed, in which case we'll need an HTLC resolver.
var htlcResolvers []ContractResolver
for htlcAction, htlcs := range htlcActions {
switch htlcAction {
@ -2145,17 +2228,6 @@ func (c *ChannelArbitrator) prepContractResolutions(
htlcResolvers = append(htlcResolvers, resolver)
}
// We instantiate an anchor resolver if the commitmentment tx has an
// anchor.
if contractResolutions.AnchorResolution != nil {
anchorResolver := newAnchorResolver(
contractResolutions.AnchorResolution.AnchorSignDescriptor,
contractResolutions.AnchorResolution.CommitAnchor,
height, c.cfg.ChanPoint, resolverCfg,
)
htlcResolvers = append(htlcResolvers, anchorResolver)
}
return htlcResolvers, msgsToSend, nil
}
@ -2661,3 +2733,25 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
}
}
}
// checkLegacyBreach returns StateFullyResolved if the channel was closed with
// a breach transaction before the channel arbitrator launched its own breach
// resolver. StateContractClosed is returned if this is a modern breach close
// with a breach resolver. StateError is returned if the log lookup failed.
func (c *ChannelArbitrator) checkLegacyBreach() (ArbitratorState, error) {
// A previous version of the channel arbitrator would make the breach
// close skip to StateFullyResolved. If there are no contract
// resolutions in the bolt arbitrator log, then this is an older breach
// close. Otherwise, if there are resolutions, the state should advance
// to StateContractClosed.
_, err := c.log.FetchContractResolutions()
if err == errNoResolutions {
// This is an older breach close still in the database.
return StateFullyResolved, nil
} else if err != nil {
return StateError, err
}
// This is a modern breach close with resolvers.
return StateContractClosed, nil
}