contractcourt/htlc_timeout_test: expand timeout tests
This commit is contained in:
parent
4992e41439
commit
bb406c82a9
@ -3,7 +3,6 @@ package contractcourt
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -22,30 +21,41 @@ import (
|
|||||||
|
|
||||||
var testHtlcAmt = lnwire.MilliSatoshi(200000)
|
var testHtlcAmt = lnwire.MilliSatoshi(200000)
|
||||||
|
|
||||||
type htlcSuccessResolverTestContext struct {
|
type htlcResolverTestContext struct {
|
||||||
resolver *htlcSuccessResolver
|
resolver ContractResolver
|
||||||
|
|
||||||
|
checkpoint func(_ ContractResolver,
|
||||||
|
_ ...*channeldb.ResolverReport) error
|
||||||
|
|
||||||
notifier *mock.ChainNotifier
|
notifier *mock.ChainNotifier
|
||||||
resolverResultChan chan resolveResult
|
resolverResultChan chan resolveResult
|
||||||
t *testing.T
|
resolutionChan chan ResolutionMsg
|
||||||
|
|
||||||
|
t *testing.T
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHtlcSuccessResolverTextContext(t *testing.T, checkpoint io.Reader) *htlcSuccessResolverTestContext {
|
func newHtlcResolverTestContext(t *testing.T,
|
||||||
|
newResolver func(htlc channeldb.HTLC,
|
||||||
|
cfg ResolverConfig) ContractResolver) *htlcResolverTestContext {
|
||||||
|
|
||||||
notifier := &mock.ChainNotifier{
|
notifier := &mock.ChainNotifier{
|
||||||
EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
|
EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
|
||||||
SpendChan: make(chan *chainntnfs.SpendDetail, 1),
|
SpendChan: make(chan *chainntnfs.SpendDetail, 1),
|
||||||
ConfChan: make(chan *chainntnfs.TxConfirmation, 1),
|
ConfChan: make(chan *chainntnfs.TxConfirmation, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPointChan := make(chan struct{}, 1)
|
testCtx := &htlcResolverTestContext{
|
||||||
|
checkpoint: nil,
|
||||||
testCtx := &htlcSuccessResolverTestContext{
|
notifier: notifier,
|
||||||
notifier: notifier,
|
resolutionChan: make(chan ResolutionMsg, 1),
|
||||||
t: t,
|
t: t,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
witnessBeacon := newMockWitnessBeacon()
|
||||||
chainCfg := ChannelArbitratorConfig{
|
chainCfg := ChannelArbitratorConfig{
|
||||||
ChainArbitratorConfig: ChainArbitratorConfig{
|
ChainArbitratorConfig: ChainArbitratorConfig{
|
||||||
Notifier: notifier,
|
Notifier: notifier,
|
||||||
|
PreimageDB: witnessBeacon,
|
||||||
PublishTx: func(_ *wire.MsgTx, _ string) error {
|
PublishTx: func(_ *wire.MsgTx, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -54,6 +64,16 @@ func newHtlcSuccessResolverTextContext(t *testing.T, checkpoint io.Reader) *htlc
|
|||||||
*lnwallet.IncomingHtlcResolution, uint32) error {
|
*lnwallet.IncomingHtlcResolution, uint32) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
DeliverResolutionMsg: func(msgs ...ResolutionMsg) error {
|
||||||
|
if len(msgs) != 1 {
|
||||||
|
return fmt.Errorf("expected 1 "+
|
||||||
|
"resolution msg, instead got %v",
|
||||||
|
len(msgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
testCtx.resolutionChan <- msgs[0]
|
||||||
|
return nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
PutResolverReport: func(_ kvdb.RwTx,
|
PutResolverReport: func(_ kvdb.RwTx,
|
||||||
report *channeldb.ResolverReport) error {
|
report *channeldb.ResolverReport) error {
|
||||||
@ -61,43 +81,31 @@ func newHtlcSuccessResolverTextContext(t *testing.T, checkpoint io.Reader) *htlc
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// Since we want to replace this checkpoint method later in the test,
|
||||||
|
// we wrap the call to it in a closure. The linter will complain about
|
||||||
|
// this so set nolint directive.
|
||||||
|
checkpointFunc := func(c ContractResolver, // nolint
|
||||||
|
r ...*channeldb.ResolverReport) error {
|
||||||
|
return testCtx.checkpoint(c, r...)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := ResolverConfig{
|
cfg := ResolverConfig{
|
||||||
ChannelArbitratorConfig: chainCfg,
|
ChannelArbitratorConfig: chainCfg,
|
||||||
Checkpoint: func(_ ContractResolver,
|
Checkpoint: checkpointFunc,
|
||||||
_ ...*channeldb.ResolverReport) error {
|
|
||||||
|
|
||||||
checkPointChan <- struct{}{}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
htlc := channeldb.HTLC{
|
htlc := channeldb.HTLC{
|
||||||
RHash: testResHash,
|
RHash: testResHash,
|
||||||
OnionBlob: testOnionBlob,
|
OnionBlob: testOnionBlob,
|
||||||
Amt: testHtlcAmt,
|
Amt: testHtlcAmt,
|
||||||
}
|
}
|
||||||
if checkpoint != nil {
|
|
||||||
var err error
|
|
||||||
testCtx.resolver, err = newSuccessResolverFromReader(checkpoint, cfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
testCtx.resolver.Supplement(htlc)
|
testCtx.resolver = newResolver(htlc, cfg)
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
testCtx.resolver = &htlcSuccessResolver{
|
|
||||||
contractResolverKit: *newContractResolverKit(cfg),
|
|
||||||
htlcResolution: lnwallet.IncomingHtlcResolution{},
|
|
||||||
htlc: htlc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return testCtx
|
return testCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *htlcSuccessResolverTestContext) resolve() {
|
func (i *htlcResolverTestContext) resolve() {
|
||||||
// Start resolver.
|
// Start resolver.
|
||||||
i.resolverResultChan = make(chan resolveResult, 1)
|
i.resolverResultChan = make(chan resolveResult, 1)
|
||||||
go func() {
|
go func() {
|
||||||
@ -109,7 +117,7 @@ func (i *htlcSuccessResolverTestContext) resolve() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *htlcSuccessResolverTestContext) waitForResult() {
|
func (i *htlcResolverTestContext) waitForResult() {
|
||||||
i.t.Helper()
|
i.t.Helper()
|
||||||
|
|
||||||
result := <-i.resolverResultChan
|
result := <-i.resolverResultChan
|
||||||
@ -152,11 +160,12 @@ func TestHtlcSuccessSingleStage(t *testing.T) {
|
|||||||
{
|
{
|
||||||
// We send a confirmation for our sweep tx to indicate
|
// We send a confirmation for our sweep tx to indicate
|
||||||
// that our sweep succeeded.
|
// that our sweep succeeded.
|
||||||
preCheckpoint: func(ctx *htlcSuccessResolverTestContext,
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
_ bool) error {
|
_ bool) error {
|
||||||
// The resolver will create and publish a sweep
|
// The resolver will create and publish a sweep
|
||||||
// tx.
|
// tx.
|
||||||
ctx.resolver.Sweeper.(*mockSweeper).
|
resolver := ctx.resolver.(*htlcSuccessResolver)
|
||||||
|
resolver.Sweeper.(*mockSweeper).
|
||||||
createSweepTxChan <- sweepTx
|
createSweepTxChan <- sweepTx
|
||||||
|
|
||||||
// Confirm the sweep, which should resolve it.
|
// Confirm the sweep, which should resolve it.
|
||||||
@ -242,7 +251,7 @@ func TestHtlcSuccessSecondStageResolution(t *testing.T) {
|
|||||||
// It will then wait for the Nursery to spend the
|
// It will then wait for the Nursery to spend the
|
||||||
// output. We send a spend notification for our output
|
// output. We send a spend notification for our output
|
||||||
// to resolve our htlc.
|
// to resolve our htlc.
|
||||||
preCheckpoint: func(ctx *htlcSuccessResolverTestContext,
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
_ bool) error {
|
_ bool) error {
|
||||||
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
SpendingTx: sweepTx,
|
SpendingTx: sweepTx,
|
||||||
@ -361,11 +370,11 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
|
|||||||
{
|
{
|
||||||
// The HTLC output on the commitment should be offered
|
// The HTLC output on the commitment should be offered
|
||||||
// to the sweeper. We'll notify that it gets spent.
|
// to the sweeper. We'll notify that it gets spent.
|
||||||
preCheckpoint: func(ctx *htlcSuccessResolverTestContext,
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
_ bool) error {
|
_ bool) error {
|
||||||
|
|
||||||
inp := <-ctx.resolver.Sweeper.(*mockSweeper).
|
resolver := ctx.resolver.(*htlcSuccessResolver)
|
||||||
sweptInputs
|
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
|
||||||
op := inp.OutPoint()
|
op := inp.OutPoint()
|
||||||
if *op != commitOutpoint {
|
if *op != commitOutpoint {
|
||||||
return fmt.Errorf("outpoint %v swept, "+
|
return fmt.Errorf("outpoint %v swept, "+
|
||||||
@ -389,7 +398,7 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
|
|||||||
{
|
{
|
||||||
// The resolver will wait for the second-level's CSV
|
// The resolver will wait for the second-level's CSV
|
||||||
// lock to expire.
|
// lock to expire.
|
||||||
preCheckpoint: func(ctx *htlcSuccessResolverTestContext,
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
resumed bool) error {
|
resumed bool) error {
|
||||||
|
|
||||||
// If we are resuming from a checkpoint, we
|
// If we are resuming from a checkpoint, we
|
||||||
@ -410,8 +419,8 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
|
|||||||
|
|
||||||
// We expect it to sweep the second-level
|
// We expect it to sweep the second-level
|
||||||
// transaction we notfied about above.
|
// transaction we notfied about above.
|
||||||
inp := <-ctx.resolver.Sweeper.(*mockSweeper).
|
resolver := ctx.resolver.(*htlcSuccessResolver)
|
||||||
sweptInputs
|
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
|
||||||
op := inp.OutPoint()
|
op := inp.OutPoint()
|
||||||
exp := wire.OutPoint{
|
exp := wire.OutPoint{
|
||||||
Hash: reSignedHash,
|
Hash: reSignedHash,
|
||||||
@ -451,7 +460,7 @@ type checkpoint struct {
|
|||||||
// preCheckpoint is a method that will be called before we reach the
|
// preCheckpoint is a method that will be called before we reach the
|
||||||
// checkpoint, to carry out any needed operations to drive the resolver
|
// checkpoint, to carry out any needed operations to drive the resolver
|
||||||
// in this stage.
|
// in this stage.
|
||||||
preCheckpoint func(*htlcSuccessResolverTestContext, bool) error
|
preCheckpoint func(*htlcResolverTestContext, bool) error
|
||||||
|
|
||||||
// data we expect the resolver to be checkpointed with next.
|
// data we expect the resolver to be checkpointed with next.
|
||||||
incubating bool
|
incubating bool
|
||||||
@ -471,8 +480,15 @@ func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
|
|||||||
// We first run the resolver from start to finish, ensuring it gets
|
// We first run the resolver from start to finish, ensuring it gets
|
||||||
// checkpointed at every expected stage. We store the checkpointed data
|
// checkpointed at every expected stage. We store the checkpointed data
|
||||||
// for the next portion of the test.
|
// for the next portion of the test.
|
||||||
ctx := newHtlcSuccessResolverTextContext(t, nil)
|
ctx := newHtlcResolverTestContext(t,
|
||||||
ctx.resolver.htlcResolution = resolution
|
func(htlc channeldb.HTLC, cfg ResolverConfig) ContractResolver {
|
||||||
|
return &htlcSuccessResolver{
|
||||||
|
contractResolverKit: *newContractResolverKit(cfg),
|
||||||
|
htlc: htlc,
|
||||||
|
htlcResolution: resolution,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
checkpointedState := runFromCheckpoint(t, ctx, checkpoints)
|
checkpointedState := runFromCheckpoint(t, ctx, checkpoints)
|
||||||
|
|
||||||
@ -480,8 +496,18 @@ func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
|
|||||||
// run the test from that checkpoint.
|
// run the test from that checkpoint.
|
||||||
for i := range checkpointedState {
|
for i := range checkpointedState {
|
||||||
cp := bytes.NewReader(checkpointedState[i])
|
cp := bytes.NewReader(checkpointedState[i])
|
||||||
ctx := newHtlcSuccessResolverTextContext(t, cp)
|
ctx := newHtlcResolverTestContext(t,
|
||||||
ctx.resolver.htlcResolution = resolution
|
func(htlc channeldb.HTLC, cfg ResolverConfig) ContractResolver {
|
||||||
|
resolver, err := newSuccessResolverFromReader(cp, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.Supplement(htlc)
|
||||||
|
resolver.htlcResolution = resolution
|
||||||
|
return resolver
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
// Run from the given checkpoint, ensuring we'll hit the rest.
|
// Run from the given checkpoint, ensuring we'll hit the rest.
|
||||||
_ = runFromCheckpoint(t, ctx, checkpoints[i+1:])
|
_ = runFromCheckpoint(t, ctx, checkpoints[i+1:])
|
||||||
@ -490,7 +516,7 @@ func testHtlcSuccess(t *testing.T, resolution lnwallet.IncomingHtlcResolution,
|
|||||||
|
|
||||||
// runFromCheckpoint executes the Resolve method on the success resolver, and
|
// runFromCheckpoint executes the Resolve method on the success resolver, and
|
||||||
// asserts that it checkpoints itself according to the expected checkpoints.
|
// asserts that it checkpoints itself according to the expected checkpoints.
|
||||||
func runFromCheckpoint(t *testing.T, ctx *htlcSuccessResolverTestContext,
|
func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext,
|
||||||
expectedCheckpoints []checkpoint) [][]byte {
|
expectedCheckpoints []checkpoint) [][]byte {
|
||||||
|
|
||||||
defer timeout(t)()
|
defer timeout(t)()
|
||||||
@ -501,25 +527,34 @@ func runFromCheckpoint(t *testing.T, ctx *htlcSuccessResolverTestContext,
|
|||||||
// checkpointed state and reports are equal to what we expect.
|
// checkpointed state and reports are equal to what we expect.
|
||||||
nextCheckpoint := 0
|
nextCheckpoint := 0
|
||||||
checkpointChan := make(chan struct{})
|
checkpointChan := make(chan struct{})
|
||||||
ctx.resolver.Checkpoint = func(resolver ContractResolver,
|
ctx.checkpoint = func(resolver ContractResolver,
|
||||||
reports ...*channeldb.ResolverReport) error {
|
reports ...*channeldb.ResolverReport) error {
|
||||||
|
|
||||||
if nextCheckpoint >= len(expectedCheckpoints) {
|
if nextCheckpoint >= len(expectedCheckpoints) {
|
||||||
t.Fatal("did not expect more checkpoints")
|
t.Fatal("did not expect more checkpoints")
|
||||||
}
|
}
|
||||||
|
|
||||||
h := resolver.(*htlcSuccessResolver)
|
var resolved, incubating bool
|
||||||
cp := expectedCheckpoints[nextCheckpoint]
|
if h, ok := resolver.(*htlcSuccessResolver); ok {
|
||||||
|
resolved = h.resolved
|
||||||
if h.resolved != cp.resolved {
|
incubating = h.outputIncubating
|
||||||
t.Fatalf("expected checkpoint to be resolve=%v, had %v",
|
}
|
||||||
cp.resolved, h.resolved)
|
if h, ok := resolver.(*htlcTimeoutResolver); ok {
|
||||||
|
resolved = h.resolved
|
||||||
|
incubating = h.outputIncubating
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(h.outputIncubating, cp.incubating) {
|
cp := expectedCheckpoints[nextCheckpoint]
|
||||||
|
|
||||||
|
if resolved != cp.resolved {
|
||||||
|
t.Fatalf("expected checkpoint to be resolve=%v, had %v",
|
||||||
|
cp.resolved, resolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(incubating, cp.incubating) {
|
||||||
t.Fatalf("expected checkpoint to be have "+
|
t.Fatalf("expected checkpoint to be have "+
|
||||||
"incubating=%v, had %v", cp.incubating,
|
"incubating=%v, had %v", cp.incubating,
|
||||||
h.outputIncubating)
|
incubating)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
@ -481,3 +482,834 @@ func TestHtlcTimeoutResolver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: the following tests essentially checks many of the same scenarios as
|
||||||
|
// the test above, but they expand on it by checking resuming from checkpoints
|
||||||
|
// at every stage.
|
||||||
|
|
||||||
|
// TestHtlcTimeoutSingleStage tests a remote commitment confirming, and the
|
||||||
|
// local node sweeping the HTLC output directly after timeout.
|
||||||
|
func TestHtlcTimeoutSingleStage(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 3}
|
||||||
|
|
||||||
|
sweepTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{{}},
|
||||||
|
TxOut: []*wire.TxOut{{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// singleStageResolution is a resolution for a htlc on the remote
|
||||||
|
// party's commitment.
|
||||||
|
singleStageResolution := lnwallet.OutgoingHtlcResolution{
|
||||||
|
ClaimOutpoint: commitOutpoint,
|
||||||
|
SweepSignDesc: testSignDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
sweepTxid := sweepTx.TxHash()
|
||||||
|
claim := &channeldb.ResolverReport{
|
||||||
|
OutPoint: commitOutpoint,
|
||||||
|
Amount: btcutil.Amount(testSignDesc.Output.Value),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
|
||||||
|
SpendTxID: &sweepTxid,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkpoints := []checkpoint{
|
||||||
|
{
|
||||||
|
// Output should be handed off to the nursery.
|
||||||
|
incubating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We send a confirmation the sweep tx from published
|
||||||
|
// by the nursery.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
_ bool) error {
|
||||||
|
// The nursery will create and publish a sweep
|
||||||
|
// tx.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: sweepTx,
|
||||||
|
SpenderTxHash: &sweepTxid,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resolver should deliver a failure
|
||||||
|
// resolition message (indicating we
|
||||||
|
// successfully timed out the HTLC).
|
||||||
|
select {
|
||||||
|
case resolutionMsg := <-ctx.resolutionChan:
|
||||||
|
if resolutionMsg.Failure == nil {
|
||||||
|
t.Fatalf("expected failure resolution msg")
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("resolution not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
|
||||||
|
// After the sweep has confirmed, we expect the
|
||||||
|
// checkpoint to be resolved, and with the above
|
||||||
|
// report.
|
||||||
|
incubating: true,
|
||||||
|
resolved: true,
|
||||||
|
reports: []*channeldb.ResolverReport{
|
||||||
|
claim,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHtlcTimeout(
|
||||||
|
t, singleStageResolution, checkpoints,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHtlcTimeoutSecondStage tests a local commitment being confirmed, and the
|
||||||
|
// local node claiming the HTLC output using the second-level timeout tx.
|
||||||
|
func TestHtlcTimeoutSecondStage(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 2}
|
||||||
|
htlcOutpoint := wire.OutPoint{Index: 3}
|
||||||
|
|
||||||
|
sweepTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{{}},
|
||||||
|
TxOut: []*wire.TxOut{{}},
|
||||||
|
}
|
||||||
|
sweepHash := sweepTx.TxHash()
|
||||||
|
|
||||||
|
timeoutTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: commitOutpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 111,
|
||||||
|
PkScript: []byte{0xaa, 0xaa},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := &mock.DummySigner{}
|
||||||
|
witness, err := input.SenderHtlcSpendTimeout(
|
||||||
|
&mock.DummySignature{}, txscript.SigHashAll,
|
||||||
|
signer, &testSignDesc, timeoutTx,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
timeoutTx.TxIn[0].Witness = witness
|
||||||
|
|
||||||
|
timeoutTxid := timeoutTx.TxHash()
|
||||||
|
|
||||||
|
// twoStageResolution is a resolution for a htlc on the local
|
||||||
|
// party's commitment.
|
||||||
|
twoStageResolution := lnwallet.OutgoingHtlcResolution{
|
||||||
|
ClaimOutpoint: htlcOutpoint,
|
||||||
|
SignedTimeoutTx: timeoutTx,
|
||||||
|
SweepSignDesc: testSignDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
firstStage := &channeldb.ResolverReport{
|
||||||
|
OutPoint: commitOutpoint,
|
||||||
|
Amount: testHtlcAmt.ToSatoshis(),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
|
||||||
|
SpendTxID: &timeoutTxid,
|
||||||
|
}
|
||||||
|
|
||||||
|
secondState := &channeldb.ResolverReport{
|
||||||
|
OutPoint: htlcOutpoint,
|
||||||
|
Amount: btcutil.Amount(testSignDesc.Output.Value),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
|
||||||
|
SpendTxID: &sweepHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkpoints := []checkpoint{
|
||||||
|
{
|
||||||
|
// Output should be handed off to the nursery.
|
||||||
|
incubating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We send a confirmation for our sweep tx to indicate
|
||||||
|
// that our sweep succeeded.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
_ bool) error {
|
||||||
|
// The nursery will publish the timeout tx.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: timeoutTx,
|
||||||
|
SpenderTxHash: &timeoutTxid,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resolver should deliver a failure
|
||||||
|
// resolution message (indicating we
|
||||||
|
// successfully timed out the HTLC).
|
||||||
|
select {
|
||||||
|
case resolutionMsg := <-ctx.resolutionChan:
|
||||||
|
if resolutionMsg.Failure == nil {
|
||||||
|
t.Fatalf("expected failure resolution msg")
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * 1):
|
||||||
|
t.Fatalf("resolution not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliver spend of timeout tx.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: sweepTx,
|
||||||
|
SpenderTxHash: &sweepHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
|
||||||
|
// After the sweep has confirmed, we expect the
|
||||||
|
// checkpoint to be resolved, and with the above
|
||||||
|
// reports.
|
||||||
|
incubating: true,
|
||||||
|
resolved: true,
|
||||||
|
reports: []*channeldb.ResolverReport{
|
||||||
|
firstStage, secondState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHtlcTimeout(
|
||||||
|
t, twoStageResolution, checkpoints,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHtlcTimeoutSingleStageRemoteSpend tests that when a local commitment
|
||||||
|
// confirms, and the remote spends the HTLC output directly, we detect this and
|
||||||
|
// extract the preimage.
|
||||||
|
func TestHtlcTimeoutSingleStageRemoteSpend(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 2}
|
||||||
|
htlcOutpoint := wire.OutPoint{Index: 3}
|
||||||
|
|
||||||
|
spendTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{{}},
|
||||||
|
TxOut: []*wire.TxOut{{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize)
|
||||||
|
var fakePreimage lntypes.Preimage
|
||||||
|
copy(fakePreimage[:], fakePreimageBytes)
|
||||||
|
|
||||||
|
signer := &mock.DummySigner{}
|
||||||
|
witness, err := input.SenderHtlcSpendRedeem(
|
||||||
|
signer, &testSignDesc, spendTx,
|
||||||
|
fakePreimageBytes,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
spendTx.TxIn[0].Witness = witness
|
||||||
|
|
||||||
|
spendTxHash := spendTx.TxHash()
|
||||||
|
|
||||||
|
timeoutTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: commitOutpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 123,
|
||||||
|
PkScript: []byte{0xff, 0xff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutWitness, err := input.SenderHtlcSpendTimeout(
|
||||||
|
&mock.DummySignature{}, txscript.SigHashAll,
|
||||||
|
signer, &testSignDesc, timeoutTx,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
timeoutTx.TxIn[0].Witness = timeoutWitness
|
||||||
|
|
||||||
|
// twoStageResolution is a resolution for a htlc on the local
|
||||||
|
// party's commitment.
|
||||||
|
twoStageResolution := lnwallet.OutgoingHtlcResolution{
|
||||||
|
ClaimOutpoint: htlcOutpoint,
|
||||||
|
SignedTimeoutTx: timeoutTx,
|
||||||
|
SweepSignDesc: testSignDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
claim := &channeldb.ResolverReport{
|
||||||
|
OutPoint: htlcOutpoint,
|
||||||
|
Amount: btcutil.Amount(testSignDesc.Output.Value),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
|
||||||
|
SpendTxID: &spendTxHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkpoints := []checkpoint{
|
||||||
|
{
|
||||||
|
// Output should be handed off to the nursery.
|
||||||
|
incubating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We send a spend notification for a remote spend with
|
||||||
|
// the preimage.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
_ bool) error {
|
||||||
|
|
||||||
|
witnessBeacon := ctx.resolver.(*htlcTimeoutResolver).PreimageDB.(*mockWitnessBeacon)
|
||||||
|
|
||||||
|
// The remote spends the output direcly with
|
||||||
|
// the preimage.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: spendTx,
|
||||||
|
SpenderTxHash: &spendTxHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should extract the preimage.
|
||||||
|
select {
|
||||||
|
case newPreimage := <-witnessBeacon.newPreimages:
|
||||||
|
if newPreimage[0] != fakePreimage {
|
||||||
|
t.Fatalf("wrong pre-image: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
fakePreimage, newPreimage)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("pre-image not added")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we should get a resolution message
|
||||||
|
// with the pre-image set within the message.
|
||||||
|
select {
|
||||||
|
case resolutionMsg := <-ctx.resolutionChan:
|
||||||
|
if *resolutionMsg.PreImage != fakePreimage {
|
||||||
|
t.Fatalf("wrong pre-image: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
fakePreimage, resolutionMsg.PreImage)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("resolution not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
|
||||||
|
// After the success tx has confirmed, we expect the
|
||||||
|
// checkpoint to be resolved, and with the above
|
||||||
|
// report.
|
||||||
|
incubating: true,
|
||||||
|
resolved: true,
|
||||||
|
reports: []*channeldb.ResolverReport{
|
||||||
|
claim,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHtlcTimeout(
|
||||||
|
t, twoStageResolution, checkpoints,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHtlcTimeoutSecondStageRemoteSpend tests that when a remite commitment
|
||||||
|
// confirms, and the remote spends the output using the success tx, we
|
||||||
|
// properly detect this and extract the preimage.
|
||||||
|
func TestHtlcTimeoutSecondStageRemoteSpend(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 2}
|
||||||
|
|
||||||
|
remoteSuccessTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: commitOutpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize)
|
||||||
|
var fakePreimage lntypes.Preimage
|
||||||
|
copy(fakePreimage[:], fakePreimageBytes)
|
||||||
|
|
||||||
|
signer := &mock.DummySigner{}
|
||||||
|
witness, err := input.ReceiverHtlcSpendRedeem(
|
||||||
|
&mock.DummySignature{}, txscript.SigHashAll,
|
||||||
|
fakePreimageBytes, signer,
|
||||||
|
&testSignDesc, remoteSuccessTx,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
remoteSuccessTx.TxIn[0].Witness = witness
|
||||||
|
successTxid := remoteSuccessTx.TxHash()
|
||||||
|
|
||||||
|
// singleStageResolution allwoing the local node to sweep HTLC output
|
||||||
|
// directly from the remote commitment after timeout.
|
||||||
|
singleStageResolution := lnwallet.OutgoingHtlcResolution{
|
||||||
|
ClaimOutpoint: commitOutpoint,
|
||||||
|
SweepSignDesc: testSignDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
claim := &channeldb.ResolverReport{
|
||||||
|
OutPoint: commitOutpoint,
|
||||||
|
Amount: btcutil.Amount(testSignDesc.Output.Value),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
|
||||||
|
SpendTxID: &successTxid,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkpoints := []checkpoint{
|
||||||
|
{
|
||||||
|
// Output should be handed off to the nursery.
|
||||||
|
incubating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We send a confirmation for the remote's second layer
|
||||||
|
// success transcation.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
_ bool) error {
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: remoteSuccessTx,
|
||||||
|
SpenderTxHash: &successTxid,
|
||||||
|
}
|
||||||
|
|
||||||
|
witnessBeacon := ctx.resolver.(*htlcTimeoutResolver).PreimageDB.(*mockWitnessBeacon)
|
||||||
|
|
||||||
|
// We expect the preimage to be extracted,
|
||||||
|
select {
|
||||||
|
case newPreimage := <-witnessBeacon.newPreimages:
|
||||||
|
if newPreimage[0] != fakePreimage {
|
||||||
|
t.Fatalf("wrong pre-image: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
fakePreimage, newPreimage)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("pre-image not added")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we should get a resolution message with the
|
||||||
|
// pre-image set within the message.
|
||||||
|
select {
|
||||||
|
case resolutionMsg := <-ctx.resolutionChan:
|
||||||
|
if *resolutionMsg.PreImage != fakePreimage {
|
||||||
|
t.Fatalf("wrong pre-image: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
fakePreimage, resolutionMsg.PreImage)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("resolution not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
|
||||||
|
// After the sweep has confirmed, we expect the
|
||||||
|
// checkpoint to be resolved, and with the above
|
||||||
|
// report.
|
||||||
|
incubating: true,
|
||||||
|
resolved: true,
|
||||||
|
reports: []*channeldb.ResolverReport{
|
||||||
|
claim,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHtlcTimeout(
|
||||||
|
t, singleStageResolution, checkpoints,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHtlcTimeoutSecondStageSweeper tests that for anchor channels, when a
|
||||||
|
// local commitment confirms, the timeout tx is handed to the sweeper to claim
|
||||||
|
// the HTLC output.
|
||||||
|
func TestHtlcTimeoutSecondStageSweeper(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 2}
|
||||||
|
htlcOutpoint := wire.OutPoint{Index: 3}
|
||||||
|
|
||||||
|
sweepTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{{}},
|
||||||
|
TxOut: []*wire.TxOut{{}},
|
||||||
|
}
|
||||||
|
sweepHash := sweepTx.TxHash()
|
||||||
|
|
||||||
|
timeoutTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: commitOutpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 123,
|
||||||
|
PkScript: []byte{0xff, 0xff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set the timeout witness since the script is used when subscribing
|
||||||
|
// to spends.
|
||||||
|
signer := &mock.DummySigner{}
|
||||||
|
timeoutWitness, err := input.SenderHtlcSpendTimeout(
|
||||||
|
&mock.DummySignature{}, txscript.SigHashAll,
|
||||||
|
signer, &testSignDesc, timeoutTx,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
timeoutTx.TxIn[0].Witness = timeoutWitness
|
||||||
|
|
||||||
|
reSignedTimeoutTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
Hash: chainhash.Hash{0xaa, 0xbb},
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timeoutTx.TxIn[0],
|
||||||
|
{
|
||||||
|
PreviousOutPoint: wire.OutPoint{
|
||||||
|
Hash: chainhash.Hash{0xaa, 0xbb},
|
||||||
|
Index: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 111,
|
||||||
|
PkScript: []byte{0xaa, 0xaa},
|
||||||
|
},
|
||||||
|
timeoutTx.TxOut[0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reSignedHash := reSignedTimeoutTx.TxHash()
|
||||||
|
reSignedOutPoint := wire.OutPoint{
|
||||||
|
Hash: reSignedHash,
|
||||||
|
Index: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
// twoStageResolution is a resolution for a htlc on the local
|
||||||
|
// party's commitment, where the timout tx can be re-signed.
|
||||||
|
twoStageResolution := lnwallet.OutgoingHtlcResolution{
|
||||||
|
ClaimOutpoint: htlcOutpoint,
|
||||||
|
SignedTimeoutTx: timeoutTx,
|
||||||
|
SignDetails: &input.SignDetails{
|
||||||
|
SignDesc: testSignDesc,
|
||||||
|
PeerSig: testSig,
|
||||||
|
},
|
||||||
|
SweepSignDesc: testSignDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
firstStage := &channeldb.ResolverReport{
|
||||||
|
OutPoint: commitOutpoint,
|
||||||
|
Amount: testHtlcAmt.ToSatoshis(),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
|
||||||
|
SpendTxID: &reSignedHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
secondState := &channeldb.ResolverReport{
|
||||||
|
OutPoint: reSignedOutPoint,
|
||||||
|
Amount: btcutil.Amount(testSignDesc.Output.Value),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeTimeout,
|
||||||
|
SpendTxID: &sweepHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkpoints := []checkpoint{
|
||||||
|
{
|
||||||
|
// The output should be given to the sweeper.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
_ bool) error {
|
||||||
|
|
||||||
|
resolver := ctx.resolver.(*htlcTimeoutResolver)
|
||||||
|
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
|
||||||
|
op := inp.OutPoint()
|
||||||
|
if *op != commitOutpoint {
|
||||||
|
return fmt.Errorf("outpoint %v swept, "+
|
||||||
|
"expected %v", op,
|
||||||
|
commitOutpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulat the sweeper spending using the
|
||||||
|
// re-signed timeout tx.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: reSignedTimeoutTx,
|
||||||
|
SpenderInputIndex: 1,
|
||||||
|
SpenderTxHash: &reSignedHash,
|
||||||
|
SpendingHeight: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// incubating=true is used to signal that the
|
||||||
|
// second-level transaction was confirmed.
|
||||||
|
incubating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We send a confirmation for our sweep tx to indicate
|
||||||
|
// that our sweep succeeded.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
resumed bool) error {
|
||||||
|
|
||||||
|
// If we are resuming from a checkpoing, we
|
||||||
|
// expect the resolver to re-subscribe to a
|
||||||
|
// spend, hence we must resend it.
|
||||||
|
if resumed {
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: reSignedTimeoutTx,
|
||||||
|
SpenderInputIndex: 1,
|
||||||
|
SpenderTxHash: &reSignedHash,
|
||||||
|
SpendingHeight: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The resolver should deliver a failure
|
||||||
|
// resolution message (indicating we
|
||||||
|
// successfully timed out the HTLC).
|
||||||
|
select {
|
||||||
|
case resolutionMsg := <-ctx.resolutionChan:
|
||||||
|
if resolutionMsg.Failure == nil {
|
||||||
|
t.Fatalf("expected failure resolution msg")
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * 1):
|
||||||
|
t.Fatalf("resolution not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mimic CSV lock expiring.
|
||||||
|
ctx.notifier.EpochChan <- &chainntnfs.BlockEpoch{
|
||||||
|
Height: 13,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The timout tx output should now be given to
|
||||||
|
// the sweeper.
|
||||||
|
resolver := ctx.resolver.(*htlcTimeoutResolver)
|
||||||
|
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
|
||||||
|
op := inp.OutPoint()
|
||||||
|
exp := wire.OutPoint{
|
||||||
|
Hash: reSignedHash,
|
||||||
|
Index: 1,
|
||||||
|
}
|
||||||
|
if *op != exp {
|
||||||
|
return fmt.Errorf("wrong outpoint swept")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify about the spend, which should resolve
|
||||||
|
// the resolver.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: sweepTx,
|
||||||
|
SpenderTxHash: &sweepHash,
|
||||||
|
SpendingHeight: 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
|
||||||
|
// After the sweep has confirmed, we expect the
|
||||||
|
// checkpoint to be resolved, and with the above
|
||||||
|
// reports.
|
||||||
|
incubating: true,
|
||||||
|
resolved: true,
|
||||||
|
reports: []*channeldb.ResolverReport{
|
||||||
|
firstStage,
|
||||||
|
secondState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHtlcTimeout(
|
||||||
|
t, twoStageResolution, checkpoints,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHtlcTimeoutSecondStageSweeperRemoteSpend tests that if a local timeout
|
||||||
|
// tx is offered to the sweeper, but the output is swept by the remote node, we
|
||||||
|
// properly detect this and extract the preimage.
|
||||||
|
func TestHtlcTimeoutSecondStageSweeperRemoteSpend(t *testing.T) {
|
||||||
|
commitOutpoint := wire.OutPoint{Index: 2}
|
||||||
|
htlcOutpoint := wire.OutPoint{Index: 3}
|
||||||
|
|
||||||
|
timeoutTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{
|
||||||
|
{
|
||||||
|
PreviousOutPoint: commitOutpoint,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TxOut: []*wire.TxOut{
|
||||||
|
{
|
||||||
|
Value: 123,
|
||||||
|
PkScript: []byte{0xff, 0xff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// We set the timeout witness since the script is used when subscribing
|
||||||
|
// to spends.
|
||||||
|
signer := &mock.DummySigner{}
|
||||||
|
timeoutWitness, err := input.SenderHtlcSpendTimeout(
|
||||||
|
&mock.DummySignature{}, txscript.SigHashAll,
|
||||||
|
signer, &testSignDesc, timeoutTx,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
timeoutTx.TxIn[0].Witness = timeoutWitness
|
||||||
|
|
||||||
|
spendTx := &wire.MsgTx{
|
||||||
|
TxIn: []*wire.TxIn{{}},
|
||||||
|
TxOut: []*wire.TxOut{{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize)
|
||||||
|
var fakePreimage lntypes.Preimage
|
||||||
|
copy(fakePreimage[:], fakePreimageBytes)
|
||||||
|
|
||||||
|
witness, err := input.SenderHtlcSpendRedeem(
|
||||||
|
signer, &testSignDesc, spendTx,
|
||||||
|
fakePreimageBytes,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
spendTx.TxIn[0].Witness = witness
|
||||||
|
|
||||||
|
spendTxHash := spendTx.TxHash()
|
||||||
|
|
||||||
|
// twoStageResolution is a resolution for a htlc on the local
|
||||||
|
// party's commitment, where the timout tx can be re-signed.
|
||||||
|
twoStageResolution := lnwallet.OutgoingHtlcResolution{
|
||||||
|
ClaimOutpoint: htlcOutpoint,
|
||||||
|
SignedTimeoutTx: timeoutTx,
|
||||||
|
SignDetails: &input.SignDetails{
|
||||||
|
SignDesc: testSignDesc,
|
||||||
|
PeerSig: testSig,
|
||||||
|
},
|
||||||
|
SweepSignDesc: testSignDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
claim := &channeldb.ResolverReport{
|
||||||
|
OutPoint: htlcOutpoint,
|
||||||
|
Amount: btcutil.Amount(testSignDesc.Output.Value),
|
||||||
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
||||||
|
ResolverOutcome: channeldb.ResolverOutcomeClaimed,
|
||||||
|
SpendTxID: &spendTxHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
checkpoints := []checkpoint{
|
||||||
|
{
|
||||||
|
// The output should be given to the sweeper.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
_ bool) error {
|
||||||
|
|
||||||
|
resolver := ctx.resolver.(*htlcTimeoutResolver)
|
||||||
|
inp := <-resolver.Sweeper.(*mockSweeper).sweptInputs
|
||||||
|
op := inp.OutPoint()
|
||||||
|
if *op != commitOutpoint {
|
||||||
|
return fmt.Errorf("outpoint %v swept, "+
|
||||||
|
"expected %v", op,
|
||||||
|
commitOutpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emulate the remote sweeping the output with the preimage.
|
||||||
|
// re-signed timeout tx.
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: spendTx,
|
||||||
|
SpenderTxHash: &spendTxHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
// incubating=true is used to signal that the
|
||||||
|
// second-level transaction was confirmed.
|
||||||
|
incubating: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// We send a confirmation for our sweep tx to indicate
|
||||||
|
// that our sweep succeeded.
|
||||||
|
preCheckpoint: func(ctx *htlcResolverTestContext,
|
||||||
|
resumed bool) error {
|
||||||
|
|
||||||
|
// If we are resuming from a checkpoing, we
|
||||||
|
// expect the resolver to re-subscribe to a
|
||||||
|
// spend, hence we must resend it.
|
||||||
|
if resumed {
|
||||||
|
fmt.Println("resumed")
|
||||||
|
ctx.notifier.SpendChan <- &chainntnfs.SpendDetail{
|
||||||
|
SpendingTx: spendTx,
|
||||||
|
SpenderTxHash: &spendTxHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
witnessBeacon := ctx.resolver.(*htlcTimeoutResolver).PreimageDB.(*mockWitnessBeacon)
|
||||||
|
|
||||||
|
// We should extract the preimage.
|
||||||
|
select {
|
||||||
|
case newPreimage := <-witnessBeacon.newPreimages:
|
||||||
|
if newPreimage[0] != fakePreimage {
|
||||||
|
t.Fatalf("wrong pre-image: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
fakePreimage, newPreimage)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("pre-image not added")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we should get a resolution message
|
||||||
|
// with the pre-image set within the message.
|
||||||
|
select {
|
||||||
|
case resolutionMsg := <-ctx.resolutionChan:
|
||||||
|
if *resolutionMsg.PreImage != fakePreimage {
|
||||||
|
t.Fatalf("wrong pre-image: "+
|
||||||
|
"expected %v, got %v",
|
||||||
|
fakePreimage, resolutionMsg.PreImage)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
t.Fatalf("resolution not sent")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
|
||||||
|
// After the sweep has confirmed, we expect the
|
||||||
|
// checkpoint to be resolved, and with the above
|
||||||
|
// reports.
|
||||||
|
incubating: true,
|
||||||
|
resolved: true,
|
||||||
|
reports: []*channeldb.ResolverReport{
|
||||||
|
claim,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testHtlcTimeout(
|
||||||
|
t, twoStageResolution, checkpoints,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHtlcTimeout(t *testing.T, resolution lnwallet.OutgoingHtlcResolution,
|
||||||
|
checkpoints []checkpoint) {
|
||||||
|
|
||||||
|
defer timeout(t)()
|
||||||
|
|
||||||
|
// We first run the resolver from start to finish, ensuring it gets
|
||||||
|
// checkpointed at every expected stage. We store the checkpointed data
|
||||||
|
// for the next portion of the test.
|
||||||
|
ctx := newHtlcResolverTestContext(t,
|
||||||
|
func(htlc channeldb.HTLC, cfg ResolverConfig) ContractResolver {
|
||||||
|
return &htlcTimeoutResolver{
|
||||||
|
contractResolverKit: *newContractResolverKit(cfg),
|
||||||
|
htlc: htlc,
|
||||||
|
htlcResolution: resolution,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
checkpointedState := runFromCheckpoint(t, ctx, checkpoints)
|
||||||
|
|
||||||
|
// Now, from every checkpoint created, we re-create the resolver, and
|
||||||
|
// run the test from that checkpoint.
|
||||||
|
for i := range checkpointedState {
|
||||||
|
cp := bytes.NewReader(checkpointedState[i])
|
||||||
|
ctx := newHtlcResolverTestContext(t,
|
||||||
|
func(htlc channeldb.HTLC, cfg ResolverConfig) ContractResolver {
|
||||||
|
resolver, err := newTimeoutResolverFromReader(cp, cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.Supplement(htlc)
|
||||||
|
resolver.htlcResolution = resolution
|
||||||
|
return resolver
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run from the given checkpoint, ensuring we'll hit the rest.
|
||||||
|
_ = runFromCheckpoint(t, ctx, checkpoints[i+1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user