contractcourt: store anchor resolutions on disk
This commit is contained in:
parent
0a01d5d17c
commit
f5b20b7429
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user