2019-03-07 02:22:46 +00:00
|
|
|
package loop
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
2020-06-17 20:25:57 +00:00
|
|
|
"github.com/lightninglabs/lndclient"
|
2019-03-07 04:32:24 +00:00
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
2019-10-09 10:36:16 +00:00
|
|
|
"github.com/lightninglabs/loop/swap"
|
2019-03-06 23:29:44 +00:00
|
|
|
"github.com/lightninglabs/loop/sweep"
|
|
|
|
"github.com/lightninglabs/loop/test"
|
2019-03-06 20:13:50 +00:00
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
2020-06-02 07:31:39 +00:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
2019-03-06 20:13:50 +00:00
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
2020-06-02 07:31:39 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
testPreimage = lntypes.Preimage([32]byte{
|
|
|
|
1, 1, 1, 1, 2, 2, 2, 2,
|
|
|
|
3, 3, 3, 3, 4, 4, 4, 4,
|
|
|
|
1, 1, 1, 1, 2, 2, 2, 2,
|
|
|
|
3, 3, 3, 3, 4, 4, 4, 4,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
// testContext contains functionality to support client unit tests.
|
|
|
|
type testContext struct {
|
|
|
|
test.Context
|
|
|
|
|
|
|
|
serverMock *serverMock
|
|
|
|
swapClient *Client
|
|
|
|
statusChan chan SwapInfo
|
|
|
|
store *storeMock
|
|
|
|
expiryChan chan time.Time
|
|
|
|
runErr chan error
|
|
|
|
stop func()
|
|
|
|
}
|
|
|
|
|
|
|
|
func newSwapClient(config *clientConfig) *Client {
|
|
|
|
sweeper := &sweep.Sweeper{
|
|
|
|
Lnd: config.LndServices,
|
|
|
|
}
|
|
|
|
|
|
|
|
lndServices := config.LndServices
|
|
|
|
|
|
|
|
executor := newExecutor(&executorConfig{
|
|
|
|
lnd: lndServices,
|
|
|
|
store: config.Store,
|
|
|
|
sweeper: sweeper,
|
|
|
|
createExpiryTimer: config.CreateExpiryTimer,
|
2021-05-24 06:40:14 +00:00
|
|
|
cancelSwap: config.Server.CancelLoopOutSwap,
|
2019-03-06 20:13:50 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
return &Client{
|
|
|
|
errChan: make(chan error),
|
|
|
|
clientConfig: *config,
|
|
|
|
lndServices: lndServices,
|
|
|
|
sweeper: sweeper,
|
|
|
|
executor: executor,
|
|
|
|
resumeReady: make(chan struct{}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func createClientTestContext(t *testing.T,
|
2019-03-07 04:32:24 +00:00
|
|
|
pendingSwaps []*loopdb.LoopOut) *testContext {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
clientLnd := test.NewMockLnd()
|
2020-05-29 09:27:47 +00:00
|
|
|
serverMock := newServerMock(clientLnd)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
store := newStoreMock(t)
|
|
|
|
for _, s := range pendingSwaps {
|
2019-03-07 04:32:24 +00:00
|
|
|
store.loopOutSwaps[s.Hash] = s.Contract
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-05-15 11:55:41 +00:00
|
|
|
updates := []loopdb.SwapStateData{}
|
2019-03-06 20:13:50 +00:00
|
|
|
for _, e := range s.Events {
|
2019-05-15 11:55:41 +00:00
|
|
|
updates = append(updates, e.SwapStateData)
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
2019-03-07 04:32:24 +00:00
|
|
|
store.loopOutUpdates[s.Hash] = updates
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
expiryChan := make(chan time.Time)
|
|
|
|
timerFactory := func(expiry time.Duration) <-chan time.Time {
|
|
|
|
return expiryChan
|
|
|
|
}
|
|
|
|
|
|
|
|
swapClient := newSwapClient(&clientConfig{
|
|
|
|
LndServices: &clientLnd.LndServices,
|
|
|
|
Server: serverMock,
|
|
|
|
Store: store,
|
|
|
|
CreateExpiryTimer: timerFactory,
|
|
|
|
})
|
|
|
|
|
|
|
|
statusChan := make(chan SwapInfo)
|
|
|
|
|
|
|
|
ctx := &testContext{
|
|
|
|
Context: test.NewContext(
|
|
|
|
t,
|
|
|
|
clientLnd,
|
|
|
|
),
|
|
|
|
swapClient: swapClient,
|
|
|
|
statusChan: statusChan,
|
|
|
|
expiryChan: expiryChan,
|
|
|
|
store: store,
|
|
|
|
serverMock: serverMock,
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.runErr = make(chan error)
|
|
|
|
runCtx, stop := context.WithCancel(context.Background())
|
|
|
|
ctx.stop = stop
|
|
|
|
|
|
|
|
go func() {
|
2019-03-12 15:10:37 +00:00
|
|
|
err := swapClient.Run(
|
2019-03-06 20:13:50 +00:00
|
|
|
runCtx,
|
|
|
|
statusChan,
|
|
|
|
)
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Errorf("client run: %v", err)
|
2019-03-12 15:10:37 +00:00
|
|
|
ctx.runErr <- err
|
2019-03-06 20:13:50 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
return ctx
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *testContext) finish() {
|
|
|
|
ctx.stop()
|
|
|
|
select {
|
|
|
|
case err := <-ctx.runErr:
|
|
|
|
if err != nil {
|
|
|
|
ctx.T.Fatal(err)
|
|
|
|
}
|
|
|
|
case <-time.After(test.Timeout):
|
|
|
|
ctx.T.Fatal("client not stopping")
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.assertIsDone()
|
|
|
|
}
|
|
|
|
|
2019-03-07 02:22:46 +00:00
|
|
|
// notifyHeight notifies swap client of the arrival of a new block and
|
|
|
|
// waits for the notification to be processed by selecting on a dedicated
|
|
|
|
// test channel.
|
2019-03-06 20:13:50 +00:00
|
|
|
func (ctx *testContext) notifyHeight(height int32) {
|
|
|
|
ctx.T.Helper()
|
|
|
|
|
|
|
|
if err := ctx.Lnd.NotifyHeight(height); err != nil {
|
|
|
|
ctx.T.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *testContext) assertIsDone() {
|
|
|
|
if err := ctx.Lnd.IsDone(); err != nil {
|
|
|
|
ctx.T.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ctx.store.isDone(); err != nil {
|
|
|
|
ctx.T.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.statusChan:
|
|
|
|
ctx.T.Fatalf("not all status updates read")
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *testContext) assertStored() {
|
|
|
|
ctx.T.Helper()
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.store.assertLoopOutStored()
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ctx *testContext) assertStorePreimageReveal() {
|
|
|
|
ctx.T.Helper()
|
|
|
|
|
|
|
|
ctx.store.assertStorePreimageReveal()
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
func (ctx *testContext) assertStoreFinished(expectedResult loopdb.SwapState) {
|
2019-03-06 20:13:50 +00:00
|
|
|
ctx.T.Helper()
|
|
|
|
|
|
|
|
ctx.store.assertStoreFinished(expectedResult)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
func (ctx *testContext) assertStatus(expectedState loopdb.SwapState) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
ctx.T.Helper()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case update := <-ctx.statusChan:
|
2019-10-09 10:36:16 +00:00
|
|
|
if update.SwapType != swap.TypeOut {
|
2019-03-06 20:13:50 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if update.State == expectedState {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case <-time.After(test.Timeout):
|
|
|
|
ctx.T.Fatalf("expected status %v not "+
|
|
|
|
"received in time", expectedState)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-07 02:22:46 +00:00
|
|
|
func (ctx *testContext) publishHtlc(script []byte,
|
|
|
|
amt btcutil.Amount) wire.OutPoint {
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
// Create the htlc tx.
|
|
|
|
htlcTx := wire.MsgTx{}
|
|
|
|
htlcTx.AddTxIn(&wire.TxIn{
|
|
|
|
PreviousOutPoint: wire.OutPoint{},
|
|
|
|
})
|
|
|
|
htlcTx.AddTxOut(&wire.TxOut{
|
|
|
|
PkScript: script,
|
|
|
|
Value: int64(amt),
|
|
|
|
})
|
|
|
|
|
|
|
|
htlcTxHash := htlcTx.TxHash()
|
|
|
|
|
|
|
|
// Signal client that script has been published.
|
|
|
|
select {
|
|
|
|
case ctx.Lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
|
|
|
Tx: &htlcTx,
|
|
|
|
}:
|
|
|
|
case <-time.After(test.Timeout):
|
|
|
|
ctx.T.Fatalf("htlc confirmed not consumed")
|
|
|
|
}
|
|
|
|
|
|
|
|
return wire.OutPoint{
|
|
|
|
Hash: htlcTxHash,
|
|
|
|
Index: 0,
|
|
|
|
}
|
|
|
|
}
|
2020-06-02 07:31:39 +00:00
|
|
|
|
|
|
|
// trackPayment asserts that a call to track payment was sent and sends the
|
|
|
|
// status provided into the updates channel.
|
|
|
|
func (ctx *testContext) trackPayment(status lnrpc.Payment_PaymentStatus) {
|
|
|
|
trackPayment := ctx.Context.AssertTrackPayment()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case trackPayment.Updates <- lndclient.PaymentStatus{
|
|
|
|
State: status,
|
|
|
|
}:
|
|
|
|
|
|
|
|
case <-time.After(test.Timeout):
|
|
|
|
ctx.T.Fatalf("could not send payment update")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// assertPreimagePush asserts that we made an attempt to push our preimage to
|
|
|
|
// the server.
|
|
|
|
func (ctx *testContext) assertPreimagePush(preimage lntypes.Preimage) {
|
|
|
|
select {
|
|
|
|
case pushedPreimage := <-ctx.serverMock.preimagePush:
|
|
|
|
require.Equal(ctx.T, preimage, pushedPreimage)
|
|
|
|
|
|
|
|
case <-time.After(test.Timeout):
|
|
|
|
ctx.T.Fatalf("preimage not pushed")
|
|
|
|
}
|
|
|
|
}
|