2019-03-12 15:10:37 +00:00
|
|
|
package loop
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-04-24 20:59:41 +00:00
|
|
|
"fmt"
|
2019-03-12 15:10:37 +00:00
|
|
|
"testing"
|
|
|
|
|
2022-03-14 12:36:02 +00:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2021-07-19 08:01:33 +00:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2019-03-12 15:10:37 +00:00
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
|
|
|
"github.com/lightninglabs/loop/test"
|
2024-01-18 11:26:14 +00:00
|
|
|
"github.com/lightninglabs/loop/utils"
|
2019-03-12 15:10:37 +00:00
|
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
2023-01-09 15:36:52 +00:00
|
|
|
invpkg "github.com/lightningnetwork/lnd/invoices"
|
2022-05-27 09:14:16 +00:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2020-06-25 10:35:22 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-03-12 15:10:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
testLoopInRequest = LoopInRequest{
|
|
|
|
Amount: btcutil.Amount(50000),
|
|
|
|
MaxSwapFee: btcutil.Amount(1000),
|
|
|
|
HtlcConfTarget: 2,
|
2020-11-06 09:43:03 +00:00
|
|
|
Initiator: "test",
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// TestLoopInSuccess tests the success scenario where the swap completes the
|
|
|
|
// happy flow.
|
|
|
|
func TestLoopInSuccess(t *testing.T) {
|
2022-04-24 20:59:41 +00:00
|
|
|
t.Run("stable protocol", func(t *testing.T) {
|
|
|
|
testLoopInSuccess(t)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("experimental protocol", func(t *testing.T) {
|
|
|
|
loopdb.EnableExperimentalProtocol()
|
|
|
|
defer loopdb.ResetCurrentProtocolVersion()
|
|
|
|
|
|
|
|
testLoopInSuccess(t)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func testLoopInSuccess(t *testing.T) {
|
2019-03-12 15:10:37 +00:00
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
ctx := newLoopInTestContext(t)
|
|
|
|
|
|
|
|
height := int32(600)
|
|
|
|
|
2020-06-08 10:53:07 +00:00
|
|
|
cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-05-27 09:14:16 +00:00
|
|
|
expectedLastHop := &route.Vertex{0x02}
|
|
|
|
|
|
|
|
req := &testLoopInRequest
|
|
|
|
req.LastHop = expectedLastHop
|
|
|
|
|
2020-06-30 12:06:36 +00:00
|
|
|
initResult, err := newLoopInSwap(
|
2019-03-12 15:10:37 +00:00
|
|
|
context.Background(), cfg,
|
2022-05-27 09:14:16 +00:00
|
|
|
height, req,
|
2019-03-12 15:10:37 +00:00
|
|
|
)
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
inSwap := initResult.swap
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInStored()
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
2022-04-24 20:59:41 +00:00
|
|
|
err := inSwap.execute(context.Background(), ctx.cfg, height)
|
2019-03-12 15:10:37 +00:00
|
|
|
if err != nil {
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Error(err)
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
|
2022-05-27 09:14:16 +00:00
|
|
|
swapInfo := <-ctx.statusChan
|
|
|
|
require.Equal(t, loopdb.StateInitiated, swapInfo.State)
|
|
|
|
|
|
|
|
// Check that the SwapInfo contains the provided last hop.
|
|
|
|
require.Equal(t, expectedLastHop, swapInfo.LastHop)
|
|
|
|
|
|
|
|
// Check that the SwapInfo does not contain an outgoing chan set.
|
|
|
|
require.Nil(t, swapInfo.OutgoingChanSet)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
ctx.assertState(loopdb.StateHtlcPublished)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
// Expect htlc to be published.
|
|
|
|
htlcTx := <-ctx.lnd.SendOutputsChannel
|
|
|
|
|
2021-03-03 09:21:24 +00:00
|
|
|
// We expect our cost to use the mock fee rate we set for our conf
|
|
|
|
// target.
|
|
|
|
cost := loopdb.SwapCost{
|
|
|
|
Onchain: getTxFee(&htlcTx, test.DefaultMockFee.FeePerKVByte()),
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expect the same state to be written again with the htlc tx hash
|
|
|
|
// and on chain fee.
|
2024-01-18 11:26:07 +00:00
|
|
|
state := ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2020-06-25 10:35:22 +00:00
|
|
|
require.NotNil(t, state.HtlcTxHash)
|
2021-03-03 09:21:24 +00:00
|
|
|
require.Equal(t, cost, state.Cost)
|
2020-06-25 10:35:22 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
// Expect register for htlc conf (only one, since the htlc is p2tr).
|
2020-04-24 15:35:32 +00:00
|
|
|
<-ctx.lnd.RegisterConfChannel
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
// Confirm htlc.
|
|
|
|
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
|
|
|
Tx: &htlcTx,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client starts listening for spend of htlc.
|
|
|
|
<-ctx.lnd.RegisterSpendChannel
|
|
|
|
|
|
|
|
// Client starts listening for swap invoice updates.
|
2021-06-07 08:56:34 +00:00
|
|
|
ctx.assertSubscribeInvoice(ctx.server.swapHash)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
// Server has already paid invoice before spending the htlc. Signal
|
|
|
|
// settled.
|
2023-01-09 15:36:52 +00:00
|
|
|
ctx.updateInvoiceState(49000, invpkg.ContractSettled)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2019-04-02 08:51:51 +00:00
|
|
|
// Swap is expected to move to the state InvoiceSettled
|
|
|
|
ctx.assertState(loopdb.StateInvoiceSettled)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateInvoiceSettled)
|
2019-04-02 08:51:51 +00:00
|
|
|
|
|
|
|
// Server spends htlc.
|
|
|
|
successTx := wire.MsgTx{}
|
2022-04-24 20:59:41 +00:00
|
|
|
witness, err := inSwap.htlc.GenSuccessWitness(
|
|
|
|
[]byte{}, inSwap.contract.Preimage,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-04-02 08:51:51 +00:00
|
|
|
successTx.AddTxIn(&wire.TxIn{
|
2022-04-24 20:59:41 +00:00
|
|
|
Witness: witness,
|
2019-04-02 08:51:51 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
|
|
|
|
SpendingTx: &successTx,
|
|
|
|
SpenderInputIndex: 0,
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
ctx.assertState(loopdb.StateSuccess)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateSuccess)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, <-errChan)
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-15 09:07:26 +00:00
|
|
|
// TestLoopInTimeout tests scenarios where the server doesn't sweep the htlc
|
2019-03-12 15:10:37 +00:00
|
|
|
// and the client is forced to reclaim the funds using the timeout tx.
|
|
|
|
func TestLoopInTimeout(t *testing.T) {
|
2020-01-15 09:07:26 +00:00
|
|
|
testAmt := int64(testLoopInRequest.Amount)
|
2022-04-24 20:59:41 +00:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
externalValue int64
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "internal htlc",
|
|
|
|
externalValue: 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "external htlc",
|
|
|
|
externalValue: testAmt,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "external htlc amount too high",
|
|
|
|
externalValue: testAmt + 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "external htlc amount too low",
|
|
|
|
externalValue: testAmt - 1,
|
|
|
|
},
|
|
|
|
}
|
2020-04-24 15:35:32 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
for _, next := range []bool{false, true} {
|
|
|
|
next := next
|
2020-04-24 15:35:32 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
for _, testCase := range testCases {
|
|
|
|
testCase := testCase
|
2020-04-24 15:35:32 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
name := testCase.name
|
|
|
|
if next {
|
|
|
|
name += " experimental protocol"
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
if next {
|
|
|
|
loopdb.EnableExperimentalProtocol()
|
|
|
|
defer loopdb.ResetCurrentProtocolVersion()
|
|
|
|
}
|
2020-04-24 15:35:32 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
testLoopInTimeout(t, testCase.externalValue)
|
2020-04-24 15:35:32 +00:00
|
|
|
})
|
2022-04-24 20:59:41 +00:00
|
|
|
}
|
2020-04-24 15:35:32 +00:00
|
|
|
}
|
2020-01-15 09:07:26 +00:00
|
|
|
}
|
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
func testLoopInTimeout(t *testing.T, externalValue int64) {
|
2019-03-12 15:10:37 +00:00
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
ctx := newLoopInTestContext(t)
|
|
|
|
|
|
|
|
height := int32(600)
|
|
|
|
|
2020-06-08 10:53:07 +00:00
|
|
|
cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2020-01-15 09:07:26 +00:00
|
|
|
req := testLoopInRequest
|
|
|
|
if externalValue != 0 {
|
|
|
|
req.ExternalHtlc = true
|
|
|
|
}
|
|
|
|
|
2020-06-30 12:06:36 +00:00
|
|
|
initResult, err := newLoopInSwap(
|
2019-03-12 15:10:37 +00:00
|
|
|
context.Background(), cfg,
|
2020-01-15 09:07:26 +00:00
|
|
|
height, &req,
|
2019-03-12 15:10:37 +00:00
|
|
|
)
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, err)
|
2022-04-24 20:59:41 +00:00
|
|
|
inSwap := initResult.swap
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInStored()
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
2022-04-24 20:59:41 +00:00
|
|
|
err := inSwap.execute(context.Background(), ctx.cfg, height)
|
2019-03-12 15:10:37 +00:00
|
|
|
if err != nil {
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Error(err)
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
ctx.assertState(loopdb.StateInitiated)
|
|
|
|
|
|
|
|
ctx.assertState(loopdb.StateHtlcPublished)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2021-03-03 09:21:24 +00:00
|
|
|
var (
|
|
|
|
htlcTx wire.MsgTx
|
|
|
|
cost loopdb.SwapCost
|
|
|
|
)
|
2022-04-24 20:59:41 +00:00
|
|
|
|
2020-01-15 09:07:26 +00:00
|
|
|
if externalValue == 0 {
|
|
|
|
// Expect htlc to be published.
|
|
|
|
htlcTx = <-ctx.lnd.SendOutputsChannel
|
2022-04-24 20:59:41 +00:00
|
|
|
|
2021-03-03 09:21:24 +00:00
|
|
|
cost = loopdb.SwapCost{
|
|
|
|
Onchain: getTxFee(
|
|
|
|
&htlcTx, test.DefaultMockFee.FeePerKVByte(),
|
|
|
|
),
|
|
|
|
}
|
2020-06-25 10:35:22 +00:00
|
|
|
|
2021-03-03 09:21:24 +00:00
|
|
|
// Expect the same state to be written again with the htlc tx
|
|
|
|
// hash and cost.
|
2024-01-18 11:26:07 +00:00
|
|
|
state := ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2020-06-25 10:35:22 +00:00
|
|
|
require.NotNil(t, state.HtlcTxHash)
|
2021-03-03 09:21:24 +00:00
|
|
|
require.Equal(t, cost, state.Cost)
|
2020-01-15 09:07:26 +00:00
|
|
|
} else {
|
|
|
|
// Create an external htlc publish tx.
|
2020-04-24 15:35:32 +00:00
|
|
|
var pkScript []byte
|
2022-04-24 20:59:41 +00:00
|
|
|
if !IsTaprootSwap(&inSwap.SwapContract) {
|
2022-11-04 15:06:22 +00:00
|
|
|
pkScript = inSwap.htlcP2WSH.PkScript
|
2020-04-24 15:35:32 +00:00
|
|
|
} else {
|
2022-04-24 20:59:41 +00:00
|
|
|
pkScript = inSwap.htlcP2TR.PkScript
|
2020-04-24 15:35:32 +00:00
|
|
|
}
|
2022-04-24 20:59:41 +00:00
|
|
|
|
2020-01-15 09:07:26 +00:00
|
|
|
htlcTx = wire.MsgTx{
|
|
|
|
TxOut: []*wire.TxOut{
|
|
|
|
{
|
2020-04-24 15:35:32 +00:00
|
|
|
PkScript: pkScript,
|
2020-01-15 09:07:26 +00:00
|
|
|
Value: externalValue,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
// Expect register for htlc conf.
|
|
|
|
<-ctx.lnd.RegisterConfChannel
|
2022-04-24 20:59:41 +00:00
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
// Confirm htlc.
|
|
|
|
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
|
|
|
Tx: &htlcTx,
|
|
|
|
}
|
|
|
|
|
2020-06-25 11:55:27 +00:00
|
|
|
// Assert that the swap is failed in case of an invalid amount.
|
|
|
|
invalidAmt := externalValue != 0 && externalValue != int64(req.Amount)
|
|
|
|
if invalidAmt {
|
|
|
|
ctx.assertState(loopdb.StateFailIncorrectHtlcAmt)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateFailIncorrectHtlcAmt)
|
2020-06-25 11:55:27 +00:00
|
|
|
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, <-errChan)
|
2020-06-25 11:55:27 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
// Client starts listening for spend of htlc.
|
|
|
|
<-ctx.lnd.RegisterSpendChannel
|
|
|
|
|
2019-04-02 08:51:51 +00:00
|
|
|
// Client starts listening for swap invoice updates.
|
2021-06-07 08:56:34 +00:00
|
|
|
ctx.assertSubscribeInvoice(ctx.server.swapHash)
|
2019-04-02 08:51:51 +00:00
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
// Let htlc expire.
|
2022-04-24 20:59:41 +00:00
|
|
|
ctx.blockEpochChan <- inSwap.LoopInContract.CltvExpiry
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2020-01-15 09:07:26 +00:00
|
|
|
// Expect a signing request for the htlc tx output value.
|
|
|
|
signReq := <-ctx.lnd.SignOutputRawChannel
|
2022-11-29 20:36:14 +00:00
|
|
|
require.Equal(
|
|
|
|
t, htlcTx.TxOut[0].Value,
|
|
|
|
signReq.SignDescriptors[0].Output.Value,
|
|
|
|
"invalid signing amount",
|
|
|
|
)
|
2020-01-15 08:55:22 +00:00
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
// Expect timeout tx to be published.
|
|
|
|
timeoutTx := <-ctx.lnd.TxPublishChannel
|
|
|
|
|
2021-03-03 09:21:24 +00:00
|
|
|
// We can just get our sweep fee as we would in the swap code because
|
|
|
|
// our estimate is static.
|
2022-04-24 20:59:41 +00:00
|
|
|
fee, err := inSwap.sweeper.GetSweepFee(
|
|
|
|
context.Background(), inSwap.htlc.AddTimeoutToEstimator,
|
|
|
|
inSwap.timeoutAddr, TimeoutTxConfTarget,
|
2021-03-03 09:21:24 +00:00
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
cost.Onchain += fee
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
// Confirm timeout tx.
|
|
|
|
ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
|
|
|
|
SpendingTx: timeoutTx,
|
|
|
|
SpenderInputIndex: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that timeout tx has confirmed, the client should be able to
|
|
|
|
// safely cancel the swap invoice.
|
|
|
|
<-ctx.lnd.FailInvoiceChannel
|
|
|
|
|
2021-06-07 08:56:34 +00:00
|
|
|
// Signal that the invoice was canceled.
|
2023-01-09 15:36:52 +00:00
|
|
|
ctx.updateInvoiceState(0, invpkg.ContractCanceled)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
ctx.assertState(loopdb.StateFailTimeout)
|
2024-01-18 11:26:07 +00:00
|
|
|
state := ctx.store.AssertLoopInState(loopdb.StateFailTimeout)
|
2021-03-03 09:21:24 +00:00
|
|
|
require.Equal(t, cost, state.Cost)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, <-errChan)
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TestLoopInResume tests resuming swaps in various states.
|
|
|
|
func TestLoopInResume(t *testing.T) {
|
2020-08-11 17:19:07 +00:00
|
|
|
storedVersion := []loopdb.ProtocolVersion{
|
|
|
|
loopdb.ProtocolVersionUnrecorded,
|
|
|
|
loopdb.ProtocolVersionHtlcV2,
|
2022-04-24 20:59:41 +00:00
|
|
|
loopdb.ProtocolVersionHtlcV3,
|
2023-01-05 17:16:05 +00:00
|
|
|
loopdb.ProtocolVersionMuSig2,
|
2020-08-11 17:19:07 +00:00
|
|
|
}
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
state loopdb.SwapState
|
|
|
|
expired bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "initiated",
|
|
|
|
state: loopdb.StateInitiated,
|
|
|
|
expired: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "initiated expired",
|
|
|
|
state: loopdb.StateInitiated,
|
|
|
|
expired: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "htlc published",
|
|
|
|
state: loopdb.StateHtlcPublished,
|
|
|
|
expired: false,
|
|
|
|
},
|
2020-08-11 17:19:07 +00:00
|
|
|
}
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
for _, next := range []bool{false, true} {
|
|
|
|
for _, version := range storedVersion {
|
|
|
|
version := version
|
|
|
|
for _, testCase := range testCases {
|
|
|
|
testCase := testCase
|
2020-08-11 17:19:07 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
name := fmt.Sprintf(
|
|
|
|
"%v %v", testCase, version.String(),
|
2020-08-11 17:19:07 +00:00
|
|
|
)
|
2022-04-24 20:59:41 +00:00
|
|
|
if next {
|
|
|
|
name += " next protocol"
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
testLoopInResume(
|
|
|
|
t, testCase.state,
|
|
|
|
testCase.expired,
|
|
|
|
version,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2020-08-11 17:19:07 +00:00
|
|
|
}
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
|
2020-08-11 17:19:07 +00:00
|
|
|
func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
|
2022-04-24 20:59:41 +00:00
|
|
|
storedVersion loopdb.ProtocolVersion) {
|
2020-08-11 17:19:07 +00:00
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
defer test.Guard(t)()
|
2023-05-16 15:40:52 +00:00
|
|
|
ctxb := context.Background()
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
ctx := newLoopInTestContext(t)
|
2020-06-08 10:53:07 +00:00
|
|
|
cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-04-24 20:59:41 +00:00
|
|
|
// Create sender and receiver keys.
|
|
|
|
_, senderPubKey := test.CreateKey(1)
|
|
|
|
_, receiverPubKey := test.CreateKey(2)
|
|
|
|
|
|
|
|
var senderKey, receiverKey [33]byte
|
|
|
|
copy(receiverKey[:], receiverPubKey.SerializeCompressed())
|
|
|
|
copy(senderKey[:], senderPubKey.SerializeCompressed())
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
contract := &loopdb.LoopInContract{
|
|
|
|
HtlcConfTarget: 2,
|
|
|
|
SwapContract: loopdb.SwapContract{
|
|
|
|
Preimage: testPreimage,
|
|
|
|
AmountRequested: 100000,
|
|
|
|
CltvExpiry: 744,
|
2023-01-05 17:16:05 +00:00
|
|
|
HtlcKeys: loopdb.HtlcKeys{
|
|
|
|
SenderScriptKey: senderKey,
|
|
|
|
SenderInternalPubKey: senderKey,
|
|
|
|
ReceiverScriptKey: receiverKey,
|
|
|
|
ReceiverInternalPubKey: receiverKey,
|
|
|
|
},
|
2019-03-12 15:10:37 +00:00
|
|
|
MaxSwapFee: 60000,
|
|
|
|
MaxMinerFee: 50000,
|
2020-08-11 17:19:07 +00:00
|
|
|
ProtocolVersion: storedVersion,
|
2019-03-12 15:10:37 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
pendSwap := &loopdb.LoopIn{
|
|
|
|
Contract: contract,
|
|
|
|
Loop: loopdb.Loop{
|
|
|
|
Events: []*loopdb.LoopEvent{
|
|
|
|
{
|
2019-05-15 11:55:41 +00:00
|
|
|
SwapStateData: loopdb.SwapStateData{
|
|
|
|
State: state,
|
|
|
|
},
|
2019-03-12 15:10:37 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Hash: testPreimage.Hash(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-03-03 09:21:24 +00:00
|
|
|
// If we have already published the htlc, we expect our cost to already
|
|
|
|
// be published.
|
|
|
|
var cost loopdb.SwapCost
|
|
|
|
if state == loopdb.StateHtlcPublished {
|
|
|
|
cost = loopdb.SwapCost{
|
|
|
|
Onchain: 999,
|
|
|
|
}
|
|
|
|
pendSwap.Loop.Events[0].Cost = cost
|
|
|
|
}
|
|
|
|
|
2024-01-18 11:26:14 +00:00
|
|
|
htlc, err := utils.GetHtlc(
|
2023-01-09 18:01:07 +00:00
|
|
|
testPreimage.Hash(), &contract.SwapContract,
|
|
|
|
cfg.lnd.ChainParams,
|
2022-11-16 18:01:28 +00:00
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2023-05-16 15:40:52 +00:00
|
|
|
err = ctx.store.CreateLoopIn(ctxb, testPreimage.Hash(), contract)
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, err)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2022-11-29 20:36:14 +00:00
|
|
|
inSwap, err := resumeLoopInSwap(context.Background(), cfg, pendSwap)
|
|
|
|
require.NoError(t, err)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
var height int32
|
|
|
|
if expired {
|
|
|
|
height = 740
|
|
|
|
} else {
|
|
|
|
height = 600
|
|
|
|
}
|
|
|
|
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
2022-04-24 20:59:41 +00:00
|
|
|
err := inSwap.execute(context.Background(), ctx.cfg, height)
|
2019-03-12 15:10:37 +00:00
|
|
|
if err != nil {
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Error(err)
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
defer func() {
|
2022-11-29 20:36:14 +00:00
|
|
|
require.NoError(t, <-errChan)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.lnd.SendPaymentChannel:
|
|
|
|
t.Fatal("unexpected payment sent")
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ctx.lnd.SendOutputsChannel:
|
|
|
|
t.Fatal("unexpected tx published")
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
var htlcTx wire.MsgTx
|
|
|
|
if state == loopdb.StateInitiated {
|
|
|
|
ctx.assertState(loopdb.StateInitiated)
|
|
|
|
|
|
|
|
if expired {
|
|
|
|
ctx.assertState(loopdb.StateFailTimeout)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.assertState(loopdb.StateHtlcPublished)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
// Expect htlc to be published.
|
|
|
|
htlcTx = <-ctx.lnd.SendOutputsChannel
|
2021-03-03 09:21:24 +00:00
|
|
|
cost = loopdb.SwapCost{
|
|
|
|
Onchain: getTxFee(
|
|
|
|
&htlcTx, test.DefaultMockFee.FeePerKVByte(),
|
|
|
|
),
|
|
|
|
}
|
2020-06-25 10:35:22 +00:00
|
|
|
|
|
|
|
// Expect the same state to be written again with the htlc tx
|
|
|
|
// hash.
|
2024-01-18 11:26:07 +00:00
|
|
|
state := ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2020-06-25 10:35:22 +00:00
|
|
|
require.NotNil(t, state.HtlcTxHash)
|
2019-03-12 15:10:37 +00:00
|
|
|
} else {
|
|
|
|
ctx.assertState(loopdb.StateHtlcPublished)
|
|
|
|
|
|
|
|
htlcTx.AddTxOut(&wire.TxOut{
|
2019-04-04 10:20:45 +00:00
|
|
|
PkScript: htlc.PkScript,
|
2020-06-25 11:55:27 +00:00
|
|
|
Value: int64(contract.AmountRequested),
|
2019-03-12 15:10:37 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expect register for htlc conf.
|
|
|
|
<-ctx.lnd.RegisterConfChannel
|
2022-04-24 20:59:41 +00:00
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
// Confirm htlc.
|
|
|
|
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
|
|
|
Tx: &htlcTx,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client starts listening for spend of htlc.
|
|
|
|
<-ctx.lnd.RegisterSpendChannel
|
|
|
|
|
|
|
|
// Client starts listening for swap invoice updates.
|
2021-06-07 08:56:34 +00:00
|
|
|
ctx.assertSubscribeInvoice(testPreimage.Hash())
|
2019-03-12 15:10:37 +00:00
|
|
|
|
|
|
|
// Server has already paid invoice before spending the htlc. Signal
|
|
|
|
// settled.
|
2021-06-07 08:56:34 +00:00
|
|
|
amtPaid := btcutil.Amount(49000)
|
2023-01-09 15:36:52 +00:00
|
|
|
ctx.updateInvoiceState(amtPaid, invpkg.ContractSettled)
|
2019-03-12 15:10:37 +00:00
|
|
|
|
2019-04-02 08:51:51 +00:00
|
|
|
// Swap is expected to move to the state InvoiceSettled
|
|
|
|
ctx.assertState(loopdb.StateInvoiceSettled)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateInvoiceSettled)
|
2019-04-02 08:51:51 +00:00
|
|
|
|
|
|
|
// Server spends htlc.
|
|
|
|
successTx := wire.MsgTx{}
|
2022-04-24 20:59:41 +00:00
|
|
|
witness, err := htlc.GenSuccessWitness([]byte{}, testPreimage)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2019-04-02 08:51:51 +00:00
|
|
|
successTx.AddTxIn(&wire.TxIn{
|
2022-04-24 20:59:41 +00:00
|
|
|
Witness: witness,
|
2019-04-02 08:51:51 +00:00
|
|
|
})
|
|
|
|
successTxHash := successTx.TxHash()
|
|
|
|
|
|
|
|
ctx.lnd.SpendChannel <- &chainntnfs.SpendDetail{
|
|
|
|
SpendingTx: &successTx,
|
|
|
|
SpenderTxHash: &successTxHash,
|
|
|
|
SpenderInputIndex: 0,
|
|
|
|
}
|
|
|
|
|
2019-03-12 15:10:37 +00:00
|
|
|
ctx.assertState(loopdb.StateSuccess)
|
2024-01-18 11:26:07 +00:00
|
|
|
finalState := ctx.store.AssertLoopInState(loopdb.StateSuccess)
|
2021-03-03 09:21:24 +00:00
|
|
|
|
|
|
|
// We expect our server fee to reflect as the difference between htlc
|
|
|
|
// value and invoice amount paid. We use our original on-chain cost, set
|
|
|
|
// earlier in the test, because we expect this value to be unchanged.
|
2021-06-07 08:56:34 +00:00
|
|
|
cost.Server = btcutil.Amount(htlcTx.TxOut[0].Value) - amtPaid
|
2021-03-03 09:21:24 +00:00
|
|
|
require.Equal(t, cost, finalState.Cost)
|
2019-03-12 15:10:37 +00:00
|
|
|
}
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
// TestAbandonPublishedHtlcState advances a loop-in swap to StateHtlcPublished,
|
|
|
|
// then abandons it and ensures that executing the same swap would not progress.
|
|
|
|
func TestAbandonPublishedHtlcState(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
ctx := newLoopInTestContext(t)
|
|
|
|
|
|
|
|
height := int32(600)
|
|
|
|
|
|
|
|
cfg, err, inSwap := startNewLoopIn(t, ctx, height)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
advanceToPublishedHtlc(t, ctx)
|
|
|
|
|
|
|
|
// The client requests to abandon the published htlc state.
|
|
|
|
inSwap.abandonChan <- struct{}{}
|
|
|
|
|
|
|
|
// Ensure that the swap is now in the StateFailAbandoned state.
|
|
|
|
ctx.assertState(loopdb.StateFailAbandoned)
|
|
|
|
|
|
|
|
// Ensure that the swap is also in the StateFailAbandoned state in the
|
|
|
|
// database.
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateFailAbandoned)
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
// Ensure that the swap was abandoned and the execution stopped.
|
|
|
|
err = <-ctx.errChan
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Contains(t, err.Error(), "swap hash abandoned by client")
|
|
|
|
|
|
|
|
// We re-instantiate the swap and ensure that it does not progress.
|
|
|
|
pendSwap := &loopdb.LoopIn{
|
|
|
|
Contract: &inSwap.LoopInContract,
|
|
|
|
Loop: loopdb.Loop{
|
|
|
|
Events: []*loopdb.LoopEvent{
|
|
|
|
{
|
|
|
|
SwapStateData: loopdb.SwapStateData{
|
|
|
|
State: inSwap.state,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Hash: testPreimage.Hash(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
resumedSwap, err := resumeLoopInSwap(
|
|
|
|
context.Background(), cfg, pendSwap,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Execute the abandoned swap.
|
|
|
|
go func() {
|
|
|
|
err := resumedSwap.execute(
|
|
|
|
context.Background(), ctx.cfg, height,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
ctx.errChan <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Ensure that the swap is still in the StateFailAbandoned state.
|
|
|
|
swapInfo := <-ctx.statusChan
|
|
|
|
require.Equal(t, loopdb.StateFailAbandoned, swapInfo.State)
|
|
|
|
|
|
|
|
// Ensure that the execution flagged the abandoned swap as finalized.
|
|
|
|
err = <-ctx.errChan
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, ErrSwapFinalized, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestAbandonSettledInvoiceState advances a loop-in swap to
|
|
|
|
// StateInvoiceSettled, then abandons it and ensures that executing the same
|
|
|
|
// swap would not progress.
|
|
|
|
func TestAbandonSettledInvoiceState(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
ctx := newLoopInTestContext(t)
|
|
|
|
|
|
|
|
height := int32(600)
|
|
|
|
|
|
|
|
cfg, err, inSwap := startNewLoopIn(t, ctx, height)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
advanceToPublishedHtlc(t, ctx)
|
|
|
|
|
|
|
|
// Client starts listening for swap invoice updates.
|
|
|
|
ctx.assertSubscribeInvoice(ctx.server.swapHash)
|
|
|
|
|
|
|
|
// Server has already paid invoice before spending the htlc. Signal
|
|
|
|
// settled.
|
|
|
|
ctx.updateInvoiceState(49000, invpkg.ContractSettled)
|
|
|
|
|
|
|
|
// Swap is expected to move to the state InvoiceSettled
|
|
|
|
ctx.assertState(loopdb.StateInvoiceSettled)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateInvoiceSettled)
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
// The client requests to abandon the published htlc state.
|
|
|
|
inSwap.abandonChan <- struct{}{}
|
|
|
|
|
|
|
|
// Ensure that the swap is now in the StateFailAbandoned state.
|
|
|
|
ctx.assertState(loopdb.StateFailAbandoned)
|
|
|
|
|
|
|
|
// Ensure that the swap is also in the StateFailAbandoned state in the
|
|
|
|
// database.
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateFailAbandoned)
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
// Ensure that the swap was abandoned and the execution stopped.
|
|
|
|
err = <-ctx.errChan
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Contains(t, err.Error(), "swap hash abandoned by client")
|
|
|
|
|
|
|
|
// We re-instantiate the swap and ensure that it does not progress.
|
|
|
|
pendSwap := &loopdb.LoopIn{
|
|
|
|
Contract: &inSwap.LoopInContract,
|
|
|
|
Loop: loopdb.Loop{
|
|
|
|
Events: []*loopdb.LoopEvent{
|
|
|
|
{
|
|
|
|
SwapStateData: loopdb.SwapStateData{
|
|
|
|
State: inSwap.state,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Hash: testPreimage.Hash(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
resumedSwap, err := resumeLoopInSwap(context.Background(), cfg, pendSwap)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Execute the abandoned swap.
|
|
|
|
go func() {
|
|
|
|
err := resumedSwap.execute(
|
|
|
|
context.Background(), ctx.cfg, height,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
ctx.errChan <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Ensure that the swap is still in the StateFailAbandoned state.
|
|
|
|
swapInfo := <-ctx.statusChan
|
|
|
|
require.Equal(t, loopdb.StateFailAbandoned, swapInfo.State)
|
|
|
|
|
|
|
|
// Ensure that the execution flagged the abandoned swap as finalized.
|
|
|
|
err = <-ctx.errChan
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, ErrSwapFinalized, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func advanceToPublishedHtlc(t *testing.T, ctx *loopInTestContext) SwapInfo {
|
|
|
|
swapInfo := <-ctx.statusChan
|
|
|
|
require.Equal(t, loopdb.StateInitiated, swapInfo.State)
|
|
|
|
|
|
|
|
ctx.assertState(loopdb.StateHtlcPublished)
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
// Expect htlc to be published.
|
|
|
|
htlcTx := <-ctx.lnd.SendOutputsChannel
|
|
|
|
|
|
|
|
// Expect the same state to be written again with the htlc tx hash
|
|
|
|
// and on chain fee.
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInState(loopdb.StateHtlcPublished)
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
// Expect register for htlc conf (only one, since the htlc is p2tr).
|
|
|
|
<-ctx.lnd.RegisterConfChannel
|
|
|
|
|
|
|
|
// Confirm htlc.
|
|
|
|
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
|
|
|
|
Tx: &htlcTx,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Client starts listening for spend of htlc.
|
|
|
|
<-ctx.lnd.RegisterSpendChannel
|
|
|
|
return swapInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
func startNewLoopIn(t *testing.T, ctx *loopInTestContext, height int32) (
|
|
|
|
*swapConfig, error, *loopInSwap) {
|
|
|
|
|
|
|
|
cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
|
|
|
|
|
|
|
|
req := &testLoopInRequest
|
|
|
|
|
|
|
|
initResult, err := newLoopInSwap(
|
|
|
|
context.Background(), cfg,
|
|
|
|
height, req,
|
|
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
inSwap := initResult.swap
|
|
|
|
|
2024-01-18 11:26:07 +00:00
|
|
|
ctx.store.AssertLoopInStored()
|
2023-11-14 18:05:59 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
err := inSwap.execute(context.Background(), ctx.cfg, height)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err)
|
|
|
|
}
|
|
|
|
ctx.errChan <- err
|
|
|
|
}()
|
|
|
|
return cfg, err, inSwap
|
|
|
|
}
|