2019-03-07 02:22:46 +00:00
|
|
|
package loop
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/sha256"
|
|
|
|
"errors"
|
|
|
|
"testing"
|
|
|
|
|
2019-03-25 10:06:16 +00:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
2020-06-23 09:49:43 +00:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2019-03-06 20:13:50 +00:00
|
|
|
"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"
|
2020-08-11 17:19:07 +00:00
|
|
|
"github.com/lightninglabs/loop/swap"
|
2019-03-06 23:29:44 +00:00
|
|
|
"github.com/lightninglabs/loop/test"
|
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 (
|
2019-03-25 10:06:16 +00:00
|
|
|
testAddr, _ = btcutil.NewAddressScriptHash(
|
|
|
|
[]byte{123}, &chaincfg.TestNet3Params,
|
|
|
|
)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
testRequest = &OutRequest{
|
2019-03-06 20:13:50 +00:00
|
|
|
Amount: btcutil.Amount(50000),
|
|
|
|
DestAddr: testAddr,
|
|
|
|
MaxMinerFee: 50000,
|
2020-08-04 14:37:24 +00:00
|
|
|
HtlcConfirmations: defaultConfirmations,
|
2019-03-06 20:13:50 +00:00
|
|
|
SweepConfTarget: 2,
|
|
|
|
MaxSwapFee: 1050,
|
|
|
|
MaxPrepayAmount: 100,
|
|
|
|
MaxPrepayRoutingFee: 75000,
|
|
|
|
MaxSwapRoutingFee: 70000,
|
2020-11-06 09:43:03 +00:00
|
|
|
Initiator: "test",
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
swapInvoiceDesc = "swap"
|
|
|
|
prepayInvoiceDesc = "prepay"
|
2020-08-04 18:28:06 +00:00
|
|
|
|
|
|
|
defaultConfirmations = int32(loopdb.DefaultLoopOutHtlcConfirmations)
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
|
2020-08-04 14:37:24 +00:00
|
|
|
// TestSuccess tests the loop out happy flow, using a custom htlc confirmation
|
|
|
|
// target.
|
2019-03-06 20:13:50 +00:00
|
|
|
func TestSuccess(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
ctx := createClientTestContext(t, nil)
|
|
|
|
|
2020-08-04 14:37:24 +00:00
|
|
|
req := *testRequest
|
|
|
|
req.HtlcConfirmations = 2
|
|
|
|
|
2020-04-22 11:52:05 +00:00
|
|
|
// Initiate loop out.
|
2020-08-04 14:37:24 +00:00
|
|
|
info, err := ctx.swapClient.LoopOut(context.Background(), &req)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.assertStored()
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StateInitiated)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
|
|
|
|
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
|
|
|
|
|
2020-04-22 11:52:05 +00:00
|
|
|
// Expect client to register for conf.
|
2020-08-04 14:37:24 +00:00
|
|
|
confIntent := ctx.AssertRegisterConf(false, req.HtlcConfirmations)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2020-06-30 11:45:12 +00:00
|
|
|
testSuccess(ctx, testRequest.Amount, info.SwapHash,
|
2019-03-06 20:13:50 +00:00
|
|
|
signalPrepaymentResult, signalSwapPaymentResult, false,
|
2020-08-11 17:19:07 +00:00
|
|
|
confIntent, swap.HtlcV2,
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestFailOffchain tests the handling of swap for which the server failed the
|
|
|
|
// payments.
|
|
|
|
func TestFailOffchain(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
ctx := createClientTestContext(t, nil)
|
|
|
|
|
2020-06-30 11:45:12 +00:00
|
|
|
_, err := ctx.swapClient.LoopOut(context.Background(), testRequest)
|
2019-03-06 20:13:50 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.assertStored()
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StateInitiated)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
|
|
|
|
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
|
|
|
|
|
2020-08-04 18:28:06 +00:00
|
|
|
ctx.AssertRegisterConf(false, defaultConfirmations)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
signalSwapPaymentResult(
|
|
|
|
errors.New(lndclient.PaymentResultUnknownPaymentHash),
|
|
|
|
)
|
|
|
|
signalPrepaymentResult(
|
|
|
|
errors.New(lndclient.PaymentResultUnknownPaymentHash),
|
|
|
|
)
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StateFailOffchainPayments)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStoreFinished(loopdb.StateFailOffchainPayments)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
ctx.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestWrongAmount asserts that the client checks the server invoice amounts.
|
|
|
|
func TestFailWrongAmount(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
test := func(t *testing.T, modifier func(*serverMock),
|
|
|
|
expectedErr error) {
|
|
|
|
|
|
|
|
ctx := createClientTestContext(t, nil)
|
|
|
|
|
|
|
|
// Modify mock for this subtest.
|
|
|
|
modifier(ctx.serverMock)
|
|
|
|
|
2020-06-30 11:45:12 +00:00
|
|
|
_, err := ctx.swapClient.LoopOut(
|
2019-03-06 20:13:50 +00:00
|
|
|
context.Background(), testRequest,
|
|
|
|
)
|
|
|
|
if err != expectedErr {
|
|
|
|
t.Fatalf("Expected %v, but got %v", expectedErr, err)
|
|
|
|
}
|
|
|
|
ctx.finish()
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("swap fee too high", func(t *testing.T) {
|
|
|
|
test(t, func(m *serverMock) {
|
|
|
|
m.swapInvoiceAmt += 10
|
|
|
|
}, ErrSwapFeeTooHigh)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("prepay amount too high", func(t *testing.T) {
|
|
|
|
test(t, func(m *serverMock) {
|
|
|
|
// Keep total swap fee unchanged, but increase prepaid
|
|
|
|
// portion.
|
|
|
|
m.swapInvoiceAmt -= 10
|
|
|
|
m.prepayInvoiceAmt += 10
|
|
|
|
}, ErrPrepayAmountTooHigh)
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestResume tests that swaps in various states are properly resumed after a
|
|
|
|
// restart.
|
|
|
|
func TestResume(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
2020-08-04 14:37:24 +00:00
|
|
|
defaultConfs := loopdb.DefaultLoopOutHtlcConfirmations
|
|
|
|
|
2020-08-11 17:19:07 +00:00
|
|
|
storedVersion := []loopdb.ProtocolVersion{
|
|
|
|
loopdb.ProtocolVersionUnrecorded,
|
|
|
|
loopdb.ProtocolVersionHtlcV2,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, version := range storedVersion {
|
|
|
|
version := version
|
|
|
|
|
|
|
|
t.Run(version.String(), func(t *testing.T) {
|
|
|
|
t.Run("not expired", func(t *testing.T) {
|
|
|
|
testResume(
|
|
|
|
t, defaultConfs, false, false, true,
|
|
|
|
version,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
t.Run("not expired, custom confirmations",
|
|
|
|
func(t *testing.T) {
|
|
|
|
testResume(
|
|
|
|
t, 3, false, false, true,
|
|
|
|
version,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
t.Run("expired not revealed", func(t *testing.T) {
|
|
|
|
testResume(
|
|
|
|
t, defaultConfs, true, false, false,
|
|
|
|
version,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
t.Run("expired revealed", func(t *testing.T) {
|
|
|
|
testResume(
|
|
|
|
t, defaultConfs, true, true, true,
|
|
|
|
version,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
2020-08-04 14:37:24 +00:00
|
|
|
func testResume(t *testing.T, confs uint32, expired, preimageRevealed,
|
2020-08-11 17:19:07 +00:00
|
|
|
expectSuccess bool, protocolVersion loopdb.ProtocolVersion) {
|
2020-08-04 14:37:24 +00:00
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
preimage := testPreimage
|
|
|
|
hash := sha256.Sum256(preimage[:])
|
|
|
|
|
|
|
|
dest := test.GetDestAddr(t, 0)
|
|
|
|
|
|
|
|
amt := btcutil.Amount(50000)
|
|
|
|
|
|
|
|
swapPayReq, err := getInvoice(hash, amt, swapInvoiceDesc)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
prePayReq, err := getInvoice(hash, 100, prepayInvoiceDesc)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, senderPubKey := test.CreateKey(1)
|
|
|
|
var senderKey [33]byte
|
|
|
|
copy(senderKey[:], senderPubKey.SerializeCompressed())
|
|
|
|
|
|
|
|
_, receiverPubKey := test.CreateKey(2)
|
|
|
|
var receiverKey [33]byte
|
|
|
|
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
|
|
|
|
|
2020-06-23 10:09:47 +00:00
|
|
|
update := loopdb.LoopEvent{
|
|
|
|
SwapStateData: loopdb.SwapStateData{
|
|
|
|
State: loopdb.StateInitiated,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
if preimageRevealed {
|
2020-06-23 10:09:47 +00:00
|
|
|
update.State = loopdb.StatePreimageRevealed
|
2020-06-23 09:49:43 +00:00
|
|
|
update.HtlcTxHash = &chainhash.Hash{1, 2, 6}
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
2020-06-23 10:09:47 +00:00
|
|
|
|
2020-08-04 14:37:24 +00:00
|
|
|
// Create a pending swap with our custom number of confirmations.
|
2019-03-07 04:32:24 +00:00
|
|
|
pendingSwap := &loopdb.LoopOut{
|
|
|
|
Contract: &loopdb.LoopOutContract{
|
2019-03-06 20:13:50 +00:00
|
|
|
DestAddr: dest,
|
|
|
|
SwapInvoice: swapPayReq,
|
|
|
|
SweepConfTarget: 2,
|
2020-08-04 14:37:24 +00:00
|
|
|
HtlcConfirmations: confs,
|
2019-03-06 20:13:50 +00:00
|
|
|
MaxSwapRoutingFee: 70000,
|
2019-03-12 15:09:57 +00:00
|
|
|
PrepayInvoice: prePayReq,
|
2019-03-07 04:32:24 +00:00
|
|
|
SwapContract: loopdb.SwapContract{
|
2019-03-06 20:13:50 +00:00
|
|
|
Preimage: preimage,
|
|
|
|
AmountRequested: amt,
|
|
|
|
CltvExpiry: 744,
|
|
|
|
ReceiverKey: receiverKey,
|
|
|
|
SenderKey: senderKey,
|
|
|
|
MaxSwapFee: 60000,
|
|
|
|
MaxMinerFee: 50000,
|
2020-08-11 17:19:07 +00:00
|
|
|
ProtocolVersion: protocolVersion,
|
2019-03-06 20:13:50 +00:00
|
|
|
},
|
|
|
|
},
|
2019-03-12 15:09:57 +00:00
|
|
|
Loop: loopdb.Loop{
|
2020-06-23 10:09:47 +00:00
|
|
|
Events: []*loopdb.LoopEvent{&update},
|
|
|
|
Hash: hash,
|
2019-03-06 20:13:50 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if expired {
|
|
|
|
// Set cltv expiry so that it has already expired at the test
|
|
|
|
// block height.
|
|
|
|
pendingSwap.Contract.CltvExpiry = 610
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx := createClientTestContext(t, []*loopdb.LoopOut{pendingSwap})
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
if preimageRevealed {
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StatePreimageRevealed)
|
2019-03-06 20:13:50 +00:00
|
|
|
} else {
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StateInitiated)
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
|
|
|
|
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
|
|
|
|
|
2020-08-04 14:37:24 +00:00
|
|
|
// Expect client to register for our expected number of confirmations.
|
|
|
|
confIntent := ctx.AssertRegisterConf(preimageRevealed, int32(confs))
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2020-08-11 17:19:07 +00:00
|
|
|
// Assert that the loopout htlc equals to the expected one.
|
|
|
|
scriptVersion := GetHtlcScriptVersion(protocolVersion)
|
|
|
|
htlc, err := swap.NewHtlc(
|
|
|
|
scriptVersion, pendingSwap.Contract.CltvExpiry, senderKey,
|
|
|
|
receiverKey, hash, swap.HtlcP2WSH, &chaincfg.TestNet3Params,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, htlc.PkScript, confIntent.PkScript)
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
signalSwapPaymentResult(nil)
|
|
|
|
signalPrepaymentResult(nil)
|
|
|
|
|
|
|
|
if !expectSuccess {
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StateFailTimeout)
|
|
|
|
ctx.assertStoreFinished(loopdb.StateFailTimeout)
|
2019-03-06 20:13:50 +00:00
|
|
|
ctx.finish()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because there is no reliable payment yet, an invoice is assumed to be
|
|
|
|
// paid after resume.
|
|
|
|
|
|
|
|
testSuccess(ctx, amt, hash,
|
|
|
|
func(r error) {},
|
|
|
|
func(r error) {},
|
|
|
|
preimageRevealed,
|
2020-08-11 17:19:07 +00:00
|
|
|
confIntent, scriptVersion,
|
2019-03-06 20:13:50 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testSuccess(ctx *testContext, amt btcutil.Amount, hash lntypes.Hash,
|
|
|
|
signalPrepaymentResult, signalSwapPaymentResult func(error),
|
2020-08-11 17:19:07 +00:00
|
|
|
preimageRevealed bool, confIntent *test.ConfRegistration,
|
|
|
|
scriptVersion swap.ScriptVersion) {
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
htlcOutpoint := ctx.publishHtlc(confIntent.PkScript, amt)
|
|
|
|
|
|
|
|
signalPrepaymentResult(nil)
|
|
|
|
|
|
|
|
ctx.AssertRegisterSpendNtfn(confIntent.PkScript)
|
|
|
|
|
2020-06-02 07:31:39 +00:00
|
|
|
// Assert that a call to track payment was sent, and respond with status
|
|
|
|
// in flight so that our swap will push its preimage to the server.
|
|
|
|
ctx.trackPayment(lnrpc.Payment_IN_FLIGHT)
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
// Publish tick.
|
|
|
|
ctx.expiryChan <- testTime
|
|
|
|
|
2020-01-15 08:55:22 +00:00
|
|
|
// Expect a signing request.
|
|
|
|
<-ctx.Lnd.SignOutputRawChannel
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
if !preimageRevealed {
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StatePreimageRevealed)
|
2019-03-06 20:13:50 +00:00
|
|
|
ctx.assertStorePreimageReveal()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expect client on-chain sweep of HTLC.
|
|
|
|
sweepTx := ctx.ReceiveTx()
|
|
|
|
|
|
|
|
if !bytes.Equal(sweepTx.TxIn[0].PreviousOutPoint.Hash[:],
|
|
|
|
htlcOutpoint.Hash[:]) {
|
|
|
|
ctx.T.Fatalf("client not sweeping from htlc tx")
|
|
|
|
}
|
|
|
|
|
2020-08-11 17:19:07 +00:00
|
|
|
preImageIndex := 1
|
|
|
|
if scriptVersion == swap.HtlcV2 {
|
|
|
|
preImageIndex = 0
|
|
|
|
}
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
// Check preimage.
|
2020-08-11 17:19:07 +00:00
|
|
|
clientPreImage := sweepTx.TxIn[0].Witness[preImageIndex]
|
2019-03-06 20:13:50 +00:00
|
|
|
clientPreImageHash := sha256.Sum256(clientPreImage)
|
|
|
|
if clientPreImageHash != hash {
|
|
|
|
ctx.T.Fatalf("incorrect preimage")
|
|
|
|
}
|
|
|
|
|
2020-06-02 07:31:39 +00:00
|
|
|
// Since we successfully published our sweep, we expect the preimage to
|
|
|
|
// have been pushed to our mock server.
|
|
|
|
preimage, err := lntypes.MakePreimage(clientPreImage)
|
|
|
|
require.NoError(ctx.T, err)
|
|
|
|
|
|
|
|
ctx.assertPreimagePush(preimage)
|
|
|
|
|
2019-03-06 20:13:50 +00:00
|
|
|
// Simulate server pulling payment.
|
|
|
|
signalSwapPaymentResult(nil)
|
|
|
|
|
|
|
|
ctx.NotifySpend(sweepTx, 0)
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStatus(loopdb.StateSuccess)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
ctx.assertStoreFinished(loopdb.StateSuccess)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
ctx.finish()
|
|
|
|
}
|