1316 lines
35 KiB
Go
1316 lines
35 KiB
Go
package contractcourt
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/channeldb/kvdb"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type mockWitnessBeacon struct {
|
|
preImageUpdates chan lntypes.Preimage
|
|
newPreimages chan []lntypes.Preimage
|
|
lookupPreimage map[lntypes.Hash]lntypes.Preimage
|
|
}
|
|
|
|
func newMockWitnessBeacon() *mockWitnessBeacon {
|
|
return &mockWitnessBeacon{
|
|
preImageUpdates: make(chan lntypes.Preimage, 1),
|
|
newPreimages: make(chan []lntypes.Preimage),
|
|
lookupPreimage: make(map[lntypes.Hash]lntypes.Preimage),
|
|
}
|
|
}
|
|
|
|
func (m *mockWitnessBeacon) SubscribeUpdates() *WitnessSubscription {
|
|
return &WitnessSubscription{
|
|
WitnessUpdates: m.preImageUpdates,
|
|
CancelSubscription: func() {},
|
|
}
|
|
}
|
|
|
|
func (m *mockWitnessBeacon) LookupPreimage(payhash lntypes.Hash) (lntypes.Preimage, bool) {
|
|
preimage, ok := m.lookupPreimage[payhash]
|
|
if !ok {
|
|
return lntypes.Preimage{}, false
|
|
}
|
|
return preimage, true
|
|
}
|
|
|
|
func (m *mockWitnessBeacon) AddPreimages(preimages ...lntypes.Preimage) error {
|
|
m.newPreimages <- preimages
|
|
return nil
|
|
}
|
|
|
|
// TestHtlcTimeoutResolver tests that the timeout resolver properly handles all
|
|
// variations of possible local+remote spends.
|
|
func TestHtlcTimeoutResolver(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
fakePreimageBytes := bytes.Repeat([]byte{1}, lntypes.HashSize)
|
|
|
|
var (
|
|
htlcOutpoint wire.OutPoint
|
|
fakePreimage lntypes.Preimage
|
|
)
|
|
fakeSignDesc := &input.SignDescriptor{
|
|
Output: &wire.TxOut{},
|
|
}
|
|
|
|
copy(fakePreimage[:], fakePreimageBytes)
|
|
|
|
signer := &mock.DummySigner{}
|
|
sweepTx := &wire.MsgTx{
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: htlcOutpoint,
|
|
Witness: [][]byte{{0x01}},
|
|
},
|
|
},
|
|
}
|
|
fakeTimeout := int32(5)
|
|
|
|
templateTx := &wire.MsgTx{
|
|
TxIn: []*wire.TxIn{
|
|
{
|
|
PreviousOutPoint: htlcOutpoint,
|
|
},
|
|
},
|
|
}
|
|
|
|
testCases := []struct {
|
|
// name is a human readable description of the test case.
|
|
name string
|
|
|
|
// remoteCommit denotes if the commitment broadcast was the
|
|
// remote commitment or not.
|
|
remoteCommit bool
|
|
|
|
// timeout denotes if the HTLC should be let timeout, or if the
|
|
// "remote" party should sweep it on-chain. This also affects
|
|
// what type of resolution message we expect.
|
|
timeout bool
|
|
|
|
// txToBroadcast is a function closure that should generate the
|
|
// transaction that should spend the HTLC output. Test authors
|
|
// can use this to customize the witness used when spending to
|
|
// trigger various redemption cases.
|
|
txToBroadcast func() (*wire.MsgTx, error)
|
|
|
|
// outcome is the resolver outcome that we expect to be reported
|
|
// once the contract is fully resolved.
|
|
outcome channeldb.ResolverOutcome
|
|
}{
|
|
// Remote commitment is broadcast, we time out the HTLC on
|
|
// chain, and should expect a fail HTLC resolution.
|
|
{
|
|
name: "timeout remote tx",
|
|
remoteCommit: true,
|
|
timeout: true,
|
|
txToBroadcast: func() (*wire.MsgTx, error) {
|
|
witness, err := input.ReceiverHtlcSpendTimeout(
|
|
signer, fakeSignDesc, sweepTx,
|
|
fakeTimeout,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// To avoid triggering the race detector by
|
|
// setting the witness the second time this
|
|
// method is called during tests, we return
|
|
// immediately if the witness is already set
|
|
// correctly.
|
|
if reflect.DeepEqual(
|
|
templateTx.TxIn[0].Witness, witness,
|
|
) {
|
|
return templateTx, nil
|
|
}
|
|
templateTx.TxIn[0].Witness = witness
|
|
return templateTx, nil
|
|
},
|
|
outcome: channeldb.ResolverOutcomeTimeout,
|
|
},
|
|
|
|
// Our local commitment is broadcast, we timeout the HTLC and
|
|
// still expect an HTLC fail resolution.
|
|
{
|
|
name: "timeout local tx",
|
|
remoteCommit: false,
|
|
timeout: true,
|
|
txToBroadcast: func() (*wire.MsgTx, error) {
|
|
witness, err := input.SenderHtlcSpendTimeout(
|
|
&mock.DummySignature{}, txscript.SigHashAll,
|
|
signer, fakeSignDesc, sweepTx,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// To avoid triggering the race detector by
|
|
// setting the witness the second time this
|
|
// method is called during tests, we return
|
|
// immediately if the witness is already set
|
|
// correctly.
|
|
if reflect.DeepEqual(
|
|
templateTx.TxIn[0].Witness, witness,
|
|
) {
|
|
return templateTx, nil
|
|
}
|
|
|
|
templateTx.TxIn[0].Witness = witness
|
|
|
|
// Set the outpoint to be on our commitment, since
|
|
// we need to claim in two stages.
|
|
templateTx.TxIn[0].PreviousOutPoint = testChanPoint1
|
|
return templateTx, nil
|
|
},
|
|
outcome: channeldb.ResolverOutcomeTimeout,
|
|
},
|
|
|
|
// The remote commitment is broadcast, they sweep with the
|
|
// pre-image, we should get a settle HTLC resolution.
|
|
{
|
|
name: "success remote tx",
|
|
remoteCommit: true,
|
|
timeout: false,
|
|
txToBroadcast: func() (*wire.MsgTx, error) {
|
|
witness, err := input.ReceiverHtlcSpendRedeem(
|
|
&mock.DummySignature{}, txscript.SigHashAll,
|
|
fakePreimageBytes, signer, fakeSignDesc,
|
|
sweepTx,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// To avoid triggering the race detector by
|
|
// setting the witness the second time this
|
|
// method is called during tests, we return
|
|
// immediately if the witness is already set
|
|
// correctly.
|
|
if reflect.DeepEqual(
|
|
templateTx.TxIn[0].Witness, witness,
|
|
) {
|
|
return templateTx, nil
|
|
}
|
|
|
|
templateTx.TxIn[0].Witness = witness
|
|
return templateTx, nil
|
|
},
|
|
outcome: channeldb.ResolverOutcomeClaimed,
|
|
},
|
|
|
|
// The local commitment is broadcast, they sweep it with a
|
|
// timeout from the output, and we should still get the HTLC
|
|
// settle resolution back.
|
|
{
|
|
name: "success local tx",
|
|
remoteCommit: false,
|
|
timeout: false,
|
|
txToBroadcast: func() (*wire.MsgTx, error) {
|
|
witness, err := input.SenderHtlcSpendRedeem(
|
|
signer, fakeSignDesc, sweepTx,
|
|
fakePreimageBytes,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// To avoid triggering the race detector by
|
|
// setting the witness the second time this
|
|
// method is called during tests, we return
|
|
// immediately if the witness is already set
|
|
// correctly.
|
|
if reflect.DeepEqual(
|
|
templateTx.TxIn[0].Witness, witness,
|
|
) {
|
|
return templateTx, nil
|
|
}
|
|
|
|
templateTx.TxIn[0].Witness = witness
|
|
return templateTx, nil
|
|
},
|
|
outcome: channeldb.ResolverOutcomeClaimed,
|
|
},
|
|
}
|
|
|
|
notifier := &mock.ChainNotifier{
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
witnessBeacon := newMockWitnessBeacon()
|
|
|
|
for _, testCase := range testCases {
|
|
t.Logf("Running test case: %v", testCase.name)
|
|
|
|
checkPointChan := make(chan struct{}, 1)
|
|
incubateChan := make(chan struct{}, 1)
|
|
resolutionChan := make(chan ResolutionMsg, 1)
|
|
reportChan := make(chan *channeldb.ResolverReport)
|
|
|
|
chainCfg := ChannelArbitratorConfig{
|
|
ChainArbitratorConfig: ChainArbitratorConfig{
|
|
Notifier: notifier,
|
|
PreimageDB: witnessBeacon,
|
|
IncubateOutputs: func(wire.OutPoint,
|
|
*lnwallet.OutgoingHtlcResolution,
|
|
*lnwallet.IncomingHtlcResolution,
|
|
uint32) error {
|
|
|
|
incubateChan <- struct{}{}
|
|
return nil
|
|
},
|
|
DeliverResolutionMsg: func(msgs ...ResolutionMsg) error {
|
|
if len(msgs) != 1 {
|
|
return fmt.Errorf("expected 1 "+
|
|
"resolution msg, instead got %v",
|
|
len(msgs))
|
|
}
|
|
|
|
resolutionChan <- msgs[0]
|
|
return nil
|
|
},
|
|
},
|
|
PutResolverReport: func(_ kvdb.RwTx,
|
|
_ *channeldb.ResolverReport) error {
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cfg := ResolverConfig{
|
|
ChannelArbitratorConfig: chainCfg,
|
|
Checkpoint: func(_ ContractResolver,
|
|
reports ...*channeldb.ResolverReport) error {
|
|
|
|
checkPointChan <- struct{}{}
|
|
|
|
// Send all of our reports into the channel.
|
|
for _, report := range reports {
|
|
reportChan <- report
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
resolver := &htlcTimeoutResolver{
|
|
htlcResolution: lnwallet.OutgoingHtlcResolution{
|
|
ClaimOutpoint: testChanPoint2,
|
|
SweepSignDesc: *fakeSignDesc,
|
|
},
|
|
contractResolverKit: *newContractResolverKit(
|
|
cfg,
|
|
),
|
|
htlc: channeldb.HTLC{
|
|
Amt: testHtlcAmt,
|
|
},
|
|
}
|
|
|
|
var reports []*channeldb.ResolverReport
|
|
|
|
// If the test case needs the remote commitment to be
|
|
// broadcast, then we'll set the timeout commit to a fake
|
|
// transaction to force the code path.
|
|
if !testCase.remoteCommit {
|
|
timeoutTx, err := testCase.txToBroadcast()
|
|
require.NoError(t, err)
|
|
|
|
resolver.htlcResolution.SignedTimeoutTx = timeoutTx
|
|
|
|
if testCase.timeout {
|
|
timeoutTxID := timeoutTx.TxHash()
|
|
reports = append(reports, &channeldb.ResolverReport{
|
|
OutPoint: timeoutTx.TxIn[0].PreviousOutPoint,
|
|
Amount: testHtlcAmt.ToSatoshis(),
|
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
|
ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
|
|
SpendTxID: &timeoutTxID,
|
|
})
|
|
}
|
|
}
|
|
|
|
// With all the setup above complete, we can initiate the
|
|
// resolution process, and the bulk of our test.
|
|
var wg sync.WaitGroup
|
|
resolveErr := make(chan error, 1)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
_, err := resolver.Resolve()
|
|
if err != nil {
|
|
resolveErr <- err
|
|
}
|
|
}()
|
|
|
|
// At the output isn't yet in the nursery, we expect that we
|
|
// should receive an incubation request.
|
|
select {
|
|
case <-incubateChan:
|
|
case err := <-resolveErr:
|
|
t.Fatalf("unable to resolve HTLC: %v", err)
|
|
case <-time.After(time.Second * 5):
|
|
t.Fatalf("failed to receive incubation request")
|
|
}
|
|
|
|
// Next, the resolver should request a spend notification for
|
|
// the direct HTLC output. We'll use the txToBroadcast closure
|
|
// for the test case to generate the transaction that we'll
|
|
// send to the resolver.
|
|
spendingTx, err := testCase.txToBroadcast()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate tx: %v", err)
|
|
}
|
|
spendTxHash := spendingTx.TxHash()
|
|
|
|
select {
|
|
case notifier.SpendChan <- &chainntnfs.SpendDetail{
|
|
SpendingTx: spendingTx,
|
|
SpenderTxHash: &spendTxHash,
|
|
}:
|
|
case <-time.After(time.Second * 5):
|
|
t.Fatalf("failed to request spend ntfn")
|
|
}
|
|
|
|
if !testCase.timeout {
|
|
// If the resolver should settle now, then we'll
|
|
// extract the pre-image to be extracted and the
|
|
// resolution message sent.
|
|
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 := <-resolutionChan:
|
|
// Once again, the pre-images should match up.
|
|
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")
|
|
}
|
|
} else {
|
|
|
|
// Otherwise, the HTLC should now timeout. First, we
|
|
// should get a resolution message with a populated
|
|
// failure message.
|
|
select {
|
|
case resolutionMsg := <-resolutionChan:
|
|
if resolutionMsg.Failure == nil {
|
|
t.Fatalf("expected failure resolution msg")
|
|
}
|
|
case <-time.After(time.Second * 5):
|
|
t.Fatalf("resolution not sent")
|
|
}
|
|
|
|
// We should also get another request for the spend
|
|
// notification of the second-level transaction to
|
|
// indicate that it's been swept by the nursery, but
|
|
// only if this is a local commitment transaction.
|
|
if !testCase.remoteCommit {
|
|
select {
|
|
case notifier.SpendChan <- &chainntnfs.SpendDetail{
|
|
SpendingTx: spendingTx,
|
|
SpenderTxHash: &spendTxHash,
|
|
}:
|
|
case <-time.After(time.Second * 5):
|
|
t.Fatalf("failed to request spend ntfn")
|
|
}
|
|
}
|
|
}
|
|
|
|
// In any case, before the resolver exits, it should checkpoint
|
|
// its final state.
|
|
select {
|
|
case <-checkPointChan:
|
|
case err := <-resolveErr:
|
|
t.Fatalf("unable to resolve HTLC: %v", err)
|
|
case <-time.After(time.Second * 5):
|
|
t.Fatalf("check point not received")
|
|
}
|
|
|
|
// Add a report to our set of expected reports with the outcome
|
|
// that the test specifies (either success or timeout).
|
|
spendTxID := spendingTx.TxHash()
|
|
amt := btcutil.Amount(fakeSignDesc.Output.Value)
|
|
|
|
reports = append(reports, &channeldb.ResolverReport{
|
|
OutPoint: testChanPoint2,
|
|
Amount: amt,
|
|
ResolverType: channeldb.ResolverTypeOutgoingHtlc,
|
|
ResolverOutcome: testCase.outcome,
|
|
SpendTxID: &spendTxID,
|
|
})
|
|
|
|
for _, report := range reports {
|
|
assertResolverReport(t, reportChan, report)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Finally, the resolver should be marked as resolved.
|
|
if !resolver.resolved {
|
|
t.Fatalf("resolver should be marked as resolved")
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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:])
|
|
}
|
|
}
|