contractcourt: store anchor resolutions on disk

This commit is contained in:
carla
2020-07-07 19:49:53 +02:00
parent 0a01d5d17c
commit f5b20b7429
4 changed files with 98 additions and 17 deletions

View File

@@ -46,11 +46,26 @@ var (
// ResolverType indicates the type of resolver that was resolved on chain. // ResolverType indicates the type of resolver that was resolved on chain.
type ResolverType uint8 type ResolverType uint8
const (
// ResolverTypeAnchor represents a resolver for an anchor output.
ResolverTypeAnchor ResolverType = 0
)
// ResolverOutcome indicates the outcome for the resolver that that the contract // ResolverOutcome indicates the outcome for the resolver that that the contract
// court reached. This state is not necessarily final, since htlcs on our own // court reached. This state is not necessarily final, since htlcs on our own
// commitment are resolved across two resolvers. // commitment are resolved across two resolvers.
type ResolverOutcome uint8 type ResolverOutcome uint8
const (
// ResolverOutcomeClaimed indicates that funds were claimed on chain.
ResolverOutcomeClaimed ResolverOutcome = 0
// ResolverOutcomeUnclaimed indicates that we did not claim our funds on
// chain. This may be the case for anchors that we did not sweep, or
// outputs that were not economical to sweep.
ResolverOutcomeUnclaimed ResolverOutcome = 1
)
// ResolverReport provides an account of the outcome of a resolver. This differs // ResolverReport provides an account of the outcome of a resolver. This differs
// from a ContractReport because it does not necessarily fully resolve the // from a ContractReport because it does not necessarily fully resolve the
// contract; each step of two stage htlc resolution is included. // contract; each step of two stage htlc resolution is included.

View File

@@ -5,8 +5,10 @@ import (
"io" "io"
"sync" "sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep" "github.com/lightningnetwork/lnd/sweep"
@@ -121,22 +123,27 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
} }
} }
var anchorRecovered bool var (
outcome channeldb.ResolverOutcome
spendTx *chainhash.Hash
)
select { select {
case sweepRes := <-resultChan: case sweepRes := <-resultChan:
switch sweepRes.Err { switch sweepRes.Err {
// Anchor was swept successfully. // Anchor was swept successfully.
case nil: case nil:
c.log.Debugf("anchor swept by tx %v", sweepTxID := sweepRes.Tx.TxHash()
sweepRes.Tx.TxHash())
anchorRecovered = true spendTx = &sweepTxID
outcome = channeldb.ResolverOutcomeClaimed
// Anchor was swept by someone else. This is possible after the // Anchor was swept by someone else. This is possible after the
// 16 block csv lock. // 16 block csv lock.
case sweep.ErrRemoteSpend: case sweep.ErrRemoteSpend:
c.log.Warnf("our anchor spent by someone else") c.log.Warnf("our anchor spent by someone else")
outcome = channeldb.ResolverOutcomeUnclaimed
// The sweeper gave up on sweeping the anchor. This happens // The sweeper gave up on sweeping the anchor. This happens
// after the maximum number of sweep attempts has been reached. // after the maximum number of sweep attempts has been reached.
@@ -147,6 +154,7 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
// We consider the anchor as being lost. // We consider the anchor as being lost.
case sweep.ErrTooManyAttempts: case sweep.ErrTooManyAttempts:
c.log.Warnf("anchor sweep abandoned") c.log.Warnf("anchor sweep abandoned")
outcome = channeldb.ResolverOutcomeUnclaimed
// An unexpected error occurred. // An unexpected error occurred.
default: default:
@@ -161,14 +169,17 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
// Update report to reflect that funds are no longer in limbo. // Update report to reflect that funds are no longer in limbo.
c.reportLock.Lock() c.reportLock.Lock()
if anchorRecovered { if outcome == channeldb.ResolverOutcomeClaimed {
c.currentReport.RecoveredBalance = c.currentReport.LimboBalance c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
} }
c.currentReport.LimboBalance = 0 c.currentReport.LimboBalance = 0
report := c.currentReport.resolverReport(
spendTx, channeldb.ResolverTypeAnchor, outcome,
)
c.reportLock.Unlock() c.reportLock.Unlock()
c.resolved = true c.resolved = true
return nil, nil return nil, c.PutResolverReport(nil, report)
} }
// Stop signals the resolver to cancel any current resolution processes, and // Stop signals the resolver to cancel any current resolution processes, and

View File

@@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"sync" "sync"
"testing" "testing"
"time" "time"
@@ -2109,6 +2110,15 @@ func TestChannelArbitratorAnchors(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to create ChannelArbitrator: %v", err) t.Fatalf("unable to create ChannelArbitrator: %v", err)
} }
// Replace our mocked put report function with one which will push
// reports into a channel for us to consume. We update this function
// because our resolver will be created from the existing chanArb cfg.
reports := make(chan *channeldb.ResolverReport)
chanArbCtx.chanArb.cfg.PutResolverReport = putResolverReportInChannel(
reports,
)
chanArb := chanArbCtx.chanArb chanArb := chanArbCtx.chanArb
chanArb.cfg.PreimageDB = newMockWitnessBeacon() chanArb.cfg.PreimageDB = newMockWitnessBeacon()
chanArb.cfg.Registry = &mockRegistry{} chanArb.cfg.Registry = &mockRegistry{}
@@ -2187,18 +2197,20 @@ func TestChannelArbitratorAnchors(t *testing.T) {
}, },
} }
anchorResolution := &lnwallet.AnchorResolution{
AnchorSignDescriptor: input.SignDescriptor{
Output: &wire.TxOut{
Value: 1,
},
},
}
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{ chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
SpendDetail: &chainntnfs.SpendDetail{}, SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{ LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: closeTx, CloseTx: closeTx,
HtlcResolutions: &lnwallet.HtlcResolutions{}, HtlcResolutions: &lnwallet.HtlcResolutions{},
AnchorResolution: &lnwallet.AnchorResolution{ AnchorResolution: anchorResolution,
AnchorSignDescriptor: input.SignDescriptor{
Output: &wire.TxOut{
Value: 1,
},
},
},
}, },
ChannelCloseSummary: &channeldb.ChannelCloseSummary{}, ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
CommitSet: CommitSet{ CommitSet: CommitSet{
@@ -2236,6 +2248,47 @@ func TestChannelArbitratorAnchors(t *testing.T) {
case <-time.After(5 * time.Second): case <-time.After(5 * time.Second):
t.Fatalf("contract was not resolved") t.Fatalf("contract was not resolved")
} }
anchorAmt := btcutil.Amount(
anchorResolution.AnchorSignDescriptor.Output.Value,
)
spendTx := chanArbCtx.sweeper.sweepTx.TxHash()
expectedReport := &channeldb.ResolverReport{
OutPoint: anchorResolution.CommitAnchor,
Amount: anchorAmt,
ResolverType: channeldb.ResolverTypeAnchor,
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
SpendTxID: &spendTx,
}
assertResolverReport(t, reports, expectedReport)
}
// putResolverReportInChannel returns a put report function which will pipe
// reports into the channel provided.
func putResolverReportInChannel(reports chan *channeldb.ResolverReport) func(
_ kvdb.RwTx, report *channeldb.ResolverReport) error {
return func(_ kvdb.RwTx, report *channeldb.ResolverReport) error {
reports <- report
return nil
}
}
// assertResolverReport checks that a set of reports only contains a single
// report, and that it is equal to the expected report passed in.
func assertResolverReport(t *testing.T, reports chan *channeldb.ResolverReport,
expected *channeldb.ResolverReport) {
select {
case report := <-reports:
if !reflect.DeepEqual(report, expected) {
t.Fatalf("expected: %v, got: %v", expected, report)
}
case <-time.After(defaultTimeout):
t.Fatalf("no reports present")
}
} }
type mockChannel struct { type mockChannel struct {

View File

@@ -104,6 +104,7 @@ func (i *commitSweepResolverTestContext) waitForResult() {
type mockSweeper struct { type mockSweeper struct {
sweptInputs chan input.Input sweptInputs chan input.Input
updatedInputs chan wire.OutPoint updatedInputs chan wire.OutPoint
sweepTx *wire.MsgTx
sweepErr error sweepErr error
} }
@@ -111,6 +112,7 @@ func newMockSweeper() *mockSweeper {
return &mockSweeper{ return &mockSweeper{
sweptInputs: make(chan input.Input), sweptInputs: make(chan input.Input),
updatedInputs: make(chan wire.OutPoint), updatedInputs: make(chan wire.OutPoint),
sweepTx: &wire.MsgTx{},
} }
} }
@@ -121,7 +123,7 @@ func (s *mockSweeper) SweepInput(input input.Input, params sweep.Params) (
result := make(chan sweep.Result, 1) result := make(chan sweep.Result, 1)
result <- sweep.Result{ result <- sweep.Result{
Tx: &wire.MsgTx{}, Tx: s.sweepTx,
Err: s.sweepErr, Err: s.sweepErr,
} }
return result, nil return result, nil
@@ -144,7 +146,7 @@ func (s *mockSweeper) UpdateParams(input wire.OutPoint,
result := make(chan sweep.Result, 1) result := make(chan sweep.Result, 1)
result <- sweep.Result{ result <- sweep.Result{
Tx: &wire.MsgTx{}, Tx: s.sweepTx,
} }
return result, nil return result, nil
} }