614884dcb8
This commit adds two tests to check that a) the correct deadline is used given different HTLC sets and b) when sweeping anchors the correct deadlines are used.
379 lines
9.4 KiB
Go
379 lines
9.4 KiB
Go
package contractcourt
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/sweep"
|
|
)
|
|
|
|
type commitSweepResolverTestContext struct {
|
|
resolver *commitSweepResolver
|
|
notifier *mock.ChainNotifier
|
|
sweeper *mockSweeper
|
|
resolverResultChan chan resolveResult
|
|
t *testing.T
|
|
}
|
|
|
|
func newCommitSweepResolverTestContext(t *testing.T,
|
|
resolution *lnwallet.CommitOutputResolution) *commitSweepResolverTestContext {
|
|
|
|
notifier := &mock.ChainNotifier{
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
|
|
sweeper := newMockSweeper()
|
|
|
|
checkPointChan := make(chan struct{}, 1)
|
|
|
|
chainCfg := ChannelArbitratorConfig{
|
|
ChainArbitratorConfig: ChainArbitratorConfig{
|
|
Notifier: notifier,
|
|
Sweeper: sweeper,
|
|
},
|
|
PutResolverReport: func(_ kvdb.RwTx,
|
|
_ *channeldb.ResolverReport) error {
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cfg := ResolverConfig{
|
|
ChannelArbitratorConfig: chainCfg,
|
|
Checkpoint: func(_ ContractResolver,
|
|
_ ...*channeldb.ResolverReport) error {
|
|
|
|
checkPointChan <- struct{}{}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
resolver := newCommitSweepResolver(
|
|
*resolution, 0, wire.OutPoint{}, cfg,
|
|
)
|
|
|
|
return &commitSweepResolverTestContext{
|
|
resolver: resolver,
|
|
notifier: notifier,
|
|
sweeper: sweeper,
|
|
t: t,
|
|
}
|
|
}
|
|
|
|
func (i *commitSweepResolverTestContext) resolve() {
|
|
// Start resolver.
|
|
i.resolverResultChan = make(chan resolveResult, 1)
|
|
go func() {
|
|
nextResolver, err := i.resolver.Resolve()
|
|
i.resolverResultChan <- resolveResult{
|
|
nextResolver: nextResolver,
|
|
err: err,
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (i *commitSweepResolverTestContext) notifyEpoch(height int32) {
|
|
i.notifier.EpochChan <- &chainntnfs.BlockEpoch{
|
|
Height: height,
|
|
}
|
|
}
|
|
|
|
func (i *commitSweepResolverTestContext) waitForResult() {
|
|
i.t.Helper()
|
|
|
|
result := <-i.resolverResultChan
|
|
if result.err != nil {
|
|
i.t.Fatal(result.err)
|
|
}
|
|
|
|
if result.nextResolver != nil {
|
|
i.t.Fatal("expected no next resolver")
|
|
}
|
|
}
|
|
|
|
type mockSweeper struct {
|
|
sweptInputs chan input.Input
|
|
updatedInputs chan wire.OutPoint
|
|
sweepTx *wire.MsgTx
|
|
sweepErr error
|
|
createSweepTxChan chan *wire.MsgTx
|
|
|
|
deadlines []int
|
|
}
|
|
|
|
func newMockSweeper() *mockSweeper {
|
|
return &mockSweeper{
|
|
sweptInputs: make(chan input.Input, 3),
|
|
updatedInputs: make(chan wire.OutPoint),
|
|
sweepTx: &wire.MsgTx{},
|
|
createSweepTxChan: make(chan *wire.MsgTx),
|
|
deadlines: []int{},
|
|
}
|
|
}
|
|
|
|
func (s *mockSweeper) SweepInput(input input.Input, params sweep.Params) (
|
|
chan sweep.Result, error) {
|
|
|
|
s.sweptInputs <- input
|
|
|
|
// Update the deadlines used if it's set.
|
|
if params.Fee.ConfTarget != 0 {
|
|
s.deadlines = append(s.deadlines, int(params.Fee.ConfTarget))
|
|
}
|
|
|
|
result := make(chan sweep.Result, 1)
|
|
result <- sweep.Result{
|
|
Tx: s.sweepTx,
|
|
Err: s.sweepErr,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *mockSweeper) CreateSweepTx(inputs []input.Input, feePref sweep.FeePreference,
|
|
currentBlockHeight uint32) (*wire.MsgTx, error) {
|
|
|
|
// We will wait for the test to supply the sweep tx to return.
|
|
sweepTx := <-s.createSweepTxChan
|
|
return sweepTx, nil
|
|
}
|
|
|
|
func (s *mockSweeper) RelayFeePerKW() chainfee.SatPerKWeight {
|
|
return 253
|
|
}
|
|
|
|
func (s *mockSweeper) UpdateParams(input wire.OutPoint,
|
|
params sweep.ParamsUpdate) (chan sweep.Result, error) {
|
|
|
|
s.updatedInputs <- input
|
|
|
|
result := make(chan sweep.Result, 1)
|
|
result <- sweep.Result{
|
|
Tx: s.sweepTx,
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
var _ UtxoSweeper = &mockSweeper{}
|
|
|
|
// TestCommitSweepResolverNoDelay tests resolution of a direct commitment output
|
|
// unencumbered by a time lock.
|
|
func TestCommitSweepResolverNoDelay(t *testing.T) {
|
|
t.Parallel()
|
|
defer timeout(t)()
|
|
|
|
res := lnwallet.CommitOutputResolution{
|
|
SelfOutputSignDesc: input.SignDescriptor{
|
|
Output: &wire.TxOut{
|
|
Value: 100,
|
|
},
|
|
WitnessScript: []byte{0},
|
|
},
|
|
}
|
|
|
|
ctx := newCommitSweepResolverTestContext(t, &res)
|
|
|
|
// Replace our checkpoint with one which will push reports into a
|
|
// channel for us to consume. We replace this function on the resolver
|
|
// itself because it is created by the test context.
|
|
reportChan := make(chan *channeldb.ResolverReport)
|
|
ctx.resolver.Checkpoint = func(_ ContractResolver,
|
|
reports ...*channeldb.ResolverReport) error {
|
|
|
|
// Send all of our reports into the channel.
|
|
for _, report := range reports {
|
|
reportChan <- report
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
ctx.resolve()
|
|
|
|
spendTx := &wire.MsgTx{}
|
|
spendHash := spendTx.TxHash()
|
|
ctx.notifier.ConfChan <- &chainntnfs.TxConfirmation{
|
|
Tx: spendTx,
|
|
}
|
|
|
|
// No csv delay, so the input should be swept immediately.
|
|
<-ctx.sweeper.sweptInputs
|
|
|
|
amt := btcutil.Amount(res.SelfOutputSignDesc.Output.Value)
|
|
expectedReport := &channeldb.ResolverReport{
|
|
OutPoint: wire.OutPoint{},
|
|
Amount: amt,
|
|
ResolverType: channeldb.ResolverTypeCommit,
|
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
|
|
SpendTxID: &spendHash,
|
|
}
|
|
|
|
assertResolverReport(t, reportChan, expectedReport)
|
|
|
|
ctx.waitForResult()
|
|
}
|
|
|
|
// testCommitSweepResolverDelay tests resolution of a direct commitment output
|
|
// that is encumbered by a time lock. sweepErr indicates whether the local node
|
|
// fails to sweep the output.
|
|
func testCommitSweepResolverDelay(t *testing.T, sweepErr error) {
|
|
defer timeout(t)()
|
|
|
|
const sweepProcessInterval = 100 * time.Millisecond
|
|
amt := int64(100)
|
|
outpoint := wire.OutPoint{
|
|
Index: 5,
|
|
}
|
|
res := lnwallet.CommitOutputResolution{
|
|
SelfOutputSignDesc: input.SignDescriptor{
|
|
Output: &wire.TxOut{
|
|
Value: amt,
|
|
},
|
|
WitnessScript: []byte{0},
|
|
},
|
|
MaturityDelay: 3,
|
|
SelfOutPoint: outpoint,
|
|
}
|
|
|
|
ctx := newCommitSweepResolverTestContext(t, &res)
|
|
|
|
// Replace our checkpoint with one which will push reports into a
|
|
// channel for us to consume. We replace this function on the resolver
|
|
// itself because it is created by the test context.
|
|
reportChan := make(chan *channeldb.ResolverReport)
|
|
ctx.resolver.Checkpoint = func(_ ContractResolver,
|
|
reports ...*channeldb.ResolverReport) error {
|
|
|
|
// Send all of our reports into the channel.
|
|
for _, report := range reports {
|
|
reportChan <- report
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Setup whether we expect the sweeper to receive a sweep error in this
|
|
// test case.
|
|
ctx.sweeper.sweepErr = sweepErr
|
|
|
|
report := ctx.resolver.report()
|
|
expectedReport := ContractReport{
|
|
Outpoint: outpoint,
|
|
Type: ReportOutputUnencumbered,
|
|
Amount: btcutil.Amount(amt),
|
|
LimboBalance: btcutil.Amount(amt),
|
|
}
|
|
if *report != expectedReport {
|
|
t.Fatalf("unexpected resolver report. want=%v got=%v",
|
|
expectedReport, report)
|
|
}
|
|
|
|
ctx.resolve()
|
|
|
|
ctx.notifier.ConfChan <- &chainntnfs.TxConfirmation{
|
|
BlockHeight: testInitialBlockHeight - 1,
|
|
}
|
|
|
|
// Allow resolver to process confirmation.
|
|
time.Sleep(sweepProcessInterval)
|
|
|
|
// Expect report to be updated.
|
|
report = ctx.resolver.report()
|
|
if report.MaturityHeight != testInitialBlockHeight+2 {
|
|
t.Fatal("report maturity height incorrect")
|
|
}
|
|
|
|
// Notify initial block height. The csv lock is still in effect, so we
|
|
// don't expect any sweep to happen yet.
|
|
ctx.notifyEpoch(testInitialBlockHeight)
|
|
|
|
select {
|
|
case <-ctx.sweeper.sweptInputs:
|
|
t.Fatal("no sweep expected")
|
|
case <-time.After(sweepProcessInterval):
|
|
}
|
|
|
|
// A new block arrives. The commit tx confirmed at height -1 and the csv
|
|
// is 3, so a spend will be valid in the first block after height +1.
|
|
ctx.notifyEpoch(testInitialBlockHeight + 1)
|
|
|
|
<-ctx.sweeper.sweptInputs
|
|
|
|
// Set the resolution report outcome based on whether our sweep
|
|
// succeeded.
|
|
outcome := channeldb.ResolverOutcomeClaimed
|
|
if sweepErr != nil {
|
|
outcome = channeldb.ResolverOutcomeUnclaimed
|
|
}
|
|
sweepTx := ctx.sweeper.sweepTx.TxHash()
|
|
|
|
assertResolverReport(t, reportChan, &channeldb.ResolverReport{
|
|
OutPoint: outpoint,
|
|
ResolverType: channeldb.ResolverTypeCommit,
|
|
ResolverOutcome: outcome,
|
|
Amount: btcutil.Amount(amt),
|
|
SpendTxID: &sweepTx,
|
|
})
|
|
|
|
ctx.waitForResult()
|
|
|
|
// If this test case generates a sweep error, we don't expect to be
|
|
// able to recover anything. This might happen if the local commitment
|
|
// output was swept by a justice transaction by the remote party.
|
|
expectedRecoveredBalance := btcutil.Amount(amt)
|
|
if sweepErr != nil {
|
|
expectedRecoveredBalance = 0
|
|
}
|
|
|
|
report = ctx.resolver.report()
|
|
expectedReport = ContractReport{
|
|
Outpoint: outpoint,
|
|
Type: ReportOutputUnencumbered,
|
|
Amount: btcutil.Amount(amt),
|
|
MaturityHeight: testInitialBlockHeight + 2,
|
|
RecoveredBalance: expectedRecoveredBalance,
|
|
}
|
|
if *report != expectedReport {
|
|
t.Fatalf("unexpected resolver report. want=%v got=%v",
|
|
expectedReport, report)
|
|
}
|
|
|
|
}
|
|
|
|
// TestCommitSweepResolverDelay tests resolution of a direct commitment output
|
|
// that is encumbered by a time lock.
|
|
func TestCommitSweepResolverDelay(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
sweepErr error
|
|
}{{
|
|
name: "success",
|
|
sweepErr: nil,
|
|
}, {
|
|
name: "remote spend",
|
|
sweepErr: sweep.ErrRemoteSpend,
|
|
}}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
ok := t.Run(tc.name, func(t *testing.T) {
|
|
testCommitSweepResolverDelay(t, tc.sweepErr)
|
|
})
|
|
if !ok {
|
|
break
|
|
}
|
|
}
|
|
}
|