breacharbiter: split waitForSpendEvent

We split the method waitForSpendEvent into two, such that we can reuse
it in case the commitment is spent by various transactions.
This commit is contained in:
Johan T. Halseth 2021-02-15 13:51:07 +01:00
parent bca5839929
commit a6724c1088
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26

@ -318,24 +318,22 @@ func convertToSecondLevelRevoke(bo *breachedOutput, breachInfo *retributionInfo,
bo.outpoint) bo.outpoint)
} }
// spend is used to wrap the index of the retributionInfo output that gets
// spent together with the spend details.
type spend struct {
index int
detail *chainntnfs.SpendDetail
}
// waitForSpendEvent waits for any of the breached outputs to get spent, and // waitForSpendEvent waits for any of the breached outputs to get spent, and
// mutates the breachInfo to be able to sweep it. This method should be used // returns the spend details for those outputs. The spendNtfns map is a cache
// when we fail to publish the justice tx because of a double spend, indicating // used to store registered spend subscriptions, in case we must call this
// that the counter party has taken one of the breached outputs to the second // method multiple times.
// level. The spendNtfns map is a cache used to store registered spend
// subscriptions, in case we must call this method multiple times.
func (b *breachArbiter) waitForSpendEvent(breachInfo *retributionInfo, func (b *breachArbiter) waitForSpendEvent(breachInfo *retributionInfo,
spendNtfns map[wire.OutPoint]*chainntnfs.SpendEvent) error { spendNtfns map[wire.OutPoint]*chainntnfs.SpendEvent) ([]spend, error) {
inputs := breachInfo.breachedOutputs inputs := breachInfo.breachedOutputs
// spend is used to wrap the index of the output that gets spent
// together with the spend details.
type spend struct {
index int
detail *chainntnfs.SpendDetail
}
// We create a channel the first goroutine that gets a spend event can // We create a channel the first goroutine that gets a spend event can
// signal. We make it buffered in case multiple spend events come in at // signal. We make it buffered in case multiple spend events come in at
// the same time. // the same time.
@ -378,7 +376,7 @@ func (b *breachArbiter) waitForSpendEvent(breachInfo *retributionInfo,
// to avoid entering an infinite loop. // to avoid entering an infinite loop.
select { select {
case <-b.quit: case <-b.quit:
return errBrarShuttingDown return nil, errBrarShuttingDown
default: default:
continue continue
} }
@ -438,62 +436,75 @@ func (b *breachArbiter) waitForSpendEvent(breachInfo *retributionInfo,
// channel before ranging over its content. // channel before ranging over its content.
close(allSpends) close(allSpends)
doneOutputs := make(map[int]struct{}) // Gather all detected spends and return them.
var spends []spend
for s := range allSpends { for s := range allSpends {
breachedOutput := &inputs[s.index] breachedOutput := &inputs[s.index]
delete(spendNtfns, breachedOutput.outpoint) delete(spendNtfns, breachedOutput.outpoint)
switch breachedOutput.witnessType { spends = append(spends, s)
case input.HtlcAcceptedRevoke: }
fallthrough
case input.HtlcOfferedRevoke:
brarLog.Infof("Spend on second-level"+
"%s(%v) for ChannelPoint(%v) "+
"transitions to second-level output",
breachedOutput.witnessType,
breachedOutput.outpoint,
breachInfo.chanPoint)
// In this case we'll morph our initial revoke return spends, nil
// spend to instead point to the second level
// output, and update the sign descriptor in the
// process.
convertToSecondLevelRevoke(
breachedOutput, breachInfo, s.detail,
)
continue case <-b.quit:
} return nil, errBrarShuttingDown
}
}
brarLog.Infof("Spend on %s(%v) for ChannelPoint(%v) "+ // updateBreachInfo mutates the passed breachInfo by removing or converting any
"transitions output to terminal state, "+ // outputs among the spends.
"removing input from justice transaction", func updateBreachInfo(breachInfo *retributionInfo, spends []spend) {
inputs := breachInfo.breachedOutputs
doneOutputs := make(map[int]struct{})
for _, s := range spends {
breachedOutput := &inputs[s.index]
switch breachedOutput.witnessType {
case input.HtlcAcceptedRevoke:
fallthrough
case input.HtlcOfferedRevoke:
brarLog.Infof("Spend on second-level "+
"%s(%v) for ChannelPoint(%v) "+
"transitions to second-level output",
breachedOutput.witnessType, breachedOutput.witnessType,
breachedOutput.outpoint, breachInfo.chanPoint) breachedOutput.outpoint, breachInfo.chanPoint)
doneOutputs[s.index] = struct{}{} // In this case we'll morph our initial revoke
// spend to instead point to the second level
// output, and update the sign descriptor in the
// process.
convertToSecondLevelRevoke(
breachedOutput, breachInfo, s.detail,
)
continue
} }
// Filter the inputs for which we can no longer proceed. brarLog.Infof("Spend on %s(%v) for ChannelPoint(%v) "+
var nextIndex int "transitions output to terminal state, "+
for i := range inputs { "removing input from justice transaction",
if _, ok := doneOutputs[i]; ok { breachedOutput.witnessType,
continue breachedOutput.outpoint, breachInfo.chanPoint)
}
inputs[nextIndex] = inputs[i] doneOutputs[s.index] = struct{}{}
nextIndex++
}
// Update our remaining set of outputs before continuing with
// another attempt at publication.
breachInfo.breachedOutputs = inputs[:nextIndex]
case <-b.quit:
return errBrarShuttingDown
} }
return nil // Filter the inputs for which we can no longer proceed.
var nextIndex int
for i := range inputs {
if _, ok := doneOutputs[i]; ok {
continue
}
inputs[nextIndex] = inputs[i]
nextIndex++
}
// Update our remaining set of outputs before continuing with
// another attempt at publication.
breachInfo.breachedOutputs = inputs[:nextIndex]
} }
// exactRetribution is a goroutine which is executed once a contract breach has // exactRetribution is a goroutine which is executed once a contract breach has
@ -587,7 +598,9 @@ justiceTxBroadcast:
"attempting to craft new justice tx.") "attempting to craft new justice tx.")
finalTx = nil finalTx = nil
err := b.waitForSpendEvent(breachInfo, spendNtfns) spends, err := b.waitForSpendEvent(
breachInfo, spendNtfns,
)
if err != nil { if err != nil {
if err != errBrarShuttingDown { if err != errBrarShuttingDown {
brarLog.Errorf("error waiting for "+ brarLog.Errorf("error waiting for "+
@ -596,6 +609,7 @@ justiceTxBroadcast:
return return
} }
updateBreachInfo(breachInfo, spends)
if len(breachInfo.breachedOutputs) == 0 { if len(breachInfo.breachedOutputs) == 0 {
brarLog.Debugf("No more outputs to sweep for "+ brarLog.Debugf("No more outputs to sweep for "+
"breach, marking ChannelPoint(%v) "+ "breach, marking ChannelPoint(%v) "+