contractcourt: store anchor resolutions on disk

This commit is contained in:
carla 2020-07-07 19:49:53 +02:00
parent 0a01d5d17c
commit f5b20b7429
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91
4 changed files with 98 additions and 17 deletions

@ -46,11 +46,26 @@ var (
// ResolverType indicates the type of resolver that was resolved on chain.
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
// court reached. This state is not necessarily final, since htlcs on our own
// commitment are resolved across two resolvers.
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
// from a ContractReport because it does not necessarily fully resolve the
// contract; each step of two stage htlc resolution is included.

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

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sync"
"testing"
"time"
@ -2109,6 +2110,15 @@ func TestChannelArbitratorAnchors(t *testing.T) {
if err != nil {
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.cfg.PreimageDB = newMockWitnessBeacon()
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{
SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: closeTx,
HtlcResolutions: &lnwallet.HtlcResolutions{},
AnchorResolution: &lnwallet.AnchorResolution{
AnchorSignDescriptor: input.SignDescriptor{
Output: &wire.TxOut{
Value: 1,
},
},
},
CloseTx: closeTx,
HtlcResolutions: &lnwallet.HtlcResolutions{},
AnchorResolution: anchorResolution,
},
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
CommitSet: CommitSet{
@ -2236,6 +2248,47 @@ func TestChannelArbitratorAnchors(t *testing.T) {
case <-time.After(5 * time.Second):
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 {

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