mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-21 22:57:10 +02:00
contractcourt: store anchor resolutions on disk
This commit is contained in:
@@ -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.
|
||||||
|
@@ -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
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user