2019-03-07 02:22:46 +00:00
|
|
|
package loop
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2019-10-01 15:21:19 +00:00
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
2019-03-06 23:29:44 +00:00
|
|
|
"github.com/lightninglabs/loop/lndclient"
|
2019-03-07 04:32:24 +00:00
|
|
|
"github.com/lightninglabs/loop/loopdb"
|
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
|
|
|
)
|
|
|
|
|
|
|
|
// TestLateHtlcPublish tests that the client is not revealing the preimage if
|
|
|
|
// there are not enough blocks left.
|
|
|
|
func TestLateHtlcPublish(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
lnd := test.NewMockLnd()
|
|
|
|
|
|
|
|
ctx := test.NewContext(t, lnd)
|
|
|
|
|
|
|
|
server := newServerMock()
|
|
|
|
|
|
|
|
store := newStoreMock(t)
|
|
|
|
|
|
|
|
expiryChan := make(chan time.Time)
|
|
|
|
timerFactory := func(expiry time.Duration) <-chan time.Time {
|
|
|
|
return expiryChan
|
|
|
|
}
|
|
|
|
|
|
|
|
height := int32(600)
|
|
|
|
|
|
|
|
cfg := &swapConfig{
|
|
|
|
lnd: &lnd.LndServices,
|
|
|
|
store: store,
|
|
|
|
server: server,
|
|
|
|
}
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
swap, err := newLoopOutSwap(
|
2019-03-06 20:13:50 +00:00
|
|
|
context.Background(), cfg, height, testRequest,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices}
|
|
|
|
|
|
|
|
blockEpochChan := make(chan interface{})
|
|
|
|
statusChan := make(chan SwapInfo)
|
|
|
|
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
|
|
|
err := swap.execute(context.Background(), &executeConfig{
|
|
|
|
statusChan: statusChan,
|
|
|
|
sweeper: sweeper,
|
|
|
|
blockEpochChan: blockEpochChan,
|
|
|
|
timerFactory: timerFactory,
|
|
|
|
}, height)
|
|
|
|
if err != nil {
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Error(err)
|
2019-03-06 20:13:50 +00:00
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
store.assertLoopOutStored()
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
state := <-statusChan
|
2019-03-07 04:32:24 +00:00
|
|
|
if state.State != loopdb.StateInitiated {
|
2019-03-06 20:13:50 +00:00
|
|
|
t.Fatal("unexpected state")
|
|
|
|
}
|
|
|
|
|
|
|
|
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
|
|
|
|
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
|
|
|
|
|
|
|
|
// Expect client to register for conf
|
|
|
|
ctx.AssertRegisterConf()
|
|
|
|
|
|
|
|
// // Wait too long before publishing htlc.
|
|
|
|
blockEpochChan <- int32(swap.CltvExpiry - 10)
|
|
|
|
|
|
|
|
signalSwapPaymentResult(
|
|
|
|
errors.New(lndclient.PaymentResultUnknownPaymentHash),
|
|
|
|
)
|
|
|
|
signalPrepaymentResult(
|
|
|
|
errors.New(lndclient.PaymentResultUnknownPaymentHash),
|
|
|
|
)
|
|
|
|
|
2019-03-07 04:32:24 +00:00
|
|
|
store.assertStoreFinished(loopdb.StateFailTimeout)
|
2019-03-06 20:13:50 +00:00
|
|
|
|
|
|
|
status := <-statusChan
|
2019-03-07 04:32:24 +00:00
|
|
|
if status.State != loopdb.StateFailTimeout {
|
2019-03-06 20:13:50 +00:00
|
|
|
t.Fatal("unexpected state")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = <-errChan
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2019-10-01 15:21:19 +00:00
|
|
|
|
|
|
|
// TestCustomSweepConfTarget ensures we are able to sweep a Loop Out HTLC with a
|
|
|
|
// custom confirmation target.
|
|
|
|
func TestCustomSweepConfTarget(t *testing.T) {
|
|
|
|
defer test.Guard(t)()
|
|
|
|
|
|
|
|
lnd := test.NewMockLnd()
|
|
|
|
ctx := test.NewContext(t, lnd)
|
|
|
|
|
|
|
|
// Use the highest sweep confirmation target before we attempt to use
|
|
|
|
// the default.
|
|
|
|
testRequest.SweepConfTarget = testLoopOutOnChainCltvDelta -
|
|
|
|
DefaultSweepConfTargetDelta - 1
|
|
|
|
|
|
|
|
// Set up custom fee estimates such that the lower confirmation target
|
|
|
|
// yields a much higher fee rate.
|
|
|
|
ctx.Lnd.SetFeeEstimate(testRequest.SweepConfTarget, 250)
|
|
|
|
ctx.Lnd.SetFeeEstimate(DefaultSweepConfTarget, 10000)
|
|
|
|
|
|
|
|
cfg := &swapConfig{
|
|
|
|
lnd: &lnd.LndServices,
|
|
|
|
store: newStoreMock(t),
|
|
|
|
server: newServerMock(),
|
|
|
|
}
|
|
|
|
swap, err := newLoopOutSwap(
|
|
|
|
context.Background(), cfg, ctx.Lnd.Height, testRequest,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up the required dependencies to execute the swap.
|
|
|
|
//
|
|
|
|
// TODO: create test context similar to loopInTestContext.
|
|
|
|
sweeper := &sweep.Sweeper{Lnd: &lnd.LndServices}
|
|
|
|
blockEpochChan := make(chan interface{})
|
|
|
|
statusChan := make(chan SwapInfo)
|
|
|
|
expiryChan := make(chan time.Time)
|
|
|
|
timerFactory := func(expiry time.Duration) <-chan time.Time {
|
|
|
|
return expiryChan
|
|
|
|
}
|
|
|
|
|
|
|
|
errChan := make(chan error)
|
|
|
|
go func() {
|
|
|
|
err := swap.execute(context.Background(), &executeConfig{
|
|
|
|
statusChan: statusChan,
|
|
|
|
blockEpochChan: blockEpochChan,
|
|
|
|
timerFactory: timerFactory,
|
|
|
|
sweeper: sweeper,
|
|
|
|
}, ctx.Lnd.Height)
|
|
|
|
if err != nil {
|
2019-10-28 16:06:07 +00:00
|
|
|
log.Error(err)
|
2019-10-01 15:21:19 +00:00
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
// The swap should be found in its initial state.
|
|
|
|
cfg.store.(*storeMock).assertLoopOutStored()
|
|
|
|
state := <-statusChan
|
|
|
|
if state.State != loopdb.StateInitiated {
|
|
|
|
t.Fatal("unexpected state")
|
|
|
|
}
|
|
|
|
|
|
|
|
// We'll then pay both the swap and prepay invoice, which should trigger
|
|
|
|
// the server to publish the on-chain HTLC.
|
|
|
|
signalSwapPaymentResult := ctx.AssertPaid(swapInvoiceDesc)
|
|
|
|
signalPrepaymentResult := ctx.AssertPaid(prepayInvoiceDesc)
|
|
|
|
|
|
|
|
signalSwapPaymentResult(nil)
|
|
|
|
signalPrepaymentResult(nil)
|
|
|
|
|
|
|
|
// Notify the confirmation notification for the HTLC.
|
|
|
|
ctx.AssertRegisterConf()
|
|
|
|
|
2019-10-07 15:29:24 +00:00
|
|
|
blockEpochChan <- ctx.Lnd.Height + 1
|
2019-10-01 15:21:19 +00:00
|
|
|
|
|
|
|
htlcTx := wire.NewMsgTx(2)
|
|
|
|
htlcTx.AddTxOut(&wire.TxOut{
|
|
|
|
Value: int64(swap.AmountRequested),
|
|
|
|
PkScript: swap.htlc.PkScript,
|
|
|
|
})
|
|
|
|
|
|
|
|
ctx.NotifyConf(htlcTx)
|
|
|
|
|
|
|
|
// The client should then register for a spend of the HTLC and attempt
|
|
|
|
// to sweep it using the custom confirmation target.
|
|
|
|
ctx.AssertRegisterSpendNtfn(swap.htlc.PkScript)
|
|
|
|
|
|
|
|
expiryChan <- time.Now()
|
|
|
|
|
2020-01-15 08:55:22 +00:00
|
|
|
// Expect a signing request for the HTLC success transaction.
|
|
|
|
<-ctx.Lnd.SignOutputRawChannel
|
|
|
|
|
2019-10-01 15:21:19 +00:00
|
|
|
cfg.store.(*storeMock).assertLoopOutState(loopdb.StatePreimageRevealed)
|
|
|
|
status := <-statusChan
|
|
|
|
if status.State != loopdb.StatePreimageRevealed {
|
|
|
|
t.Fatalf("expected state %v, got %v",
|
|
|
|
loopdb.StatePreimageRevealed, status.State)
|
|
|
|
}
|
|
|
|
|
|
|
|
// assertSweepTx performs some sanity checks on a sweep transaction to
|
|
|
|
// ensure it was constructed correctly.
|
|
|
|
assertSweepTx := func(expConfTarget int32) *wire.MsgTx {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
sweepTx := ctx.ReceiveTx()
|
|
|
|
if sweepTx.TxIn[0].PreviousOutPoint.Hash != htlcTx.TxHash() {
|
|
|
|
t.Fatalf("expected sweep tx to spend %v, got %v",
|
|
|
|
htlcTx.TxHash(), sweepTx.TxIn[0].PreviousOutPoint)
|
|
|
|
}
|
|
|
|
|
|
|
|
// The fee used for the sweep transaction is an estimate based
|
|
|
|
// on the maximum witness size, so we should expect to see a
|
|
|
|
// lower fee when using the actual witness size of the
|
|
|
|
// transaction.
|
|
|
|
fee := btcutil.Amount(
|
|
|
|
htlcTx.TxOut[0].Value - sweepTx.TxOut[0].Value,
|
|
|
|
)
|
|
|
|
|
|
|
|
weight := blockchain.GetTransactionWeight(btcutil.NewTx(sweepTx))
|
|
|
|
feeRate, err := ctx.Lnd.WalletKit.EstimateFee(
|
|
|
|
context.Background(), expConfTarget,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to retrieve fee estimate: %v", err)
|
|
|
|
}
|
|
|
|
minFee := feeRate.FeeForWeight(weight)
|
|
|
|
maxFee := btcutil.Amount(float64(minFee) * 1.1)
|
|
|
|
|
|
|
|
if fee < minFee && fee > maxFee {
|
|
|
|
t.Fatalf("expected sweep tx to have fee between %v-%v, "+
|
|
|
|
"got %v", minFee, maxFee, fee)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sweepTx
|
|
|
|
}
|
|
|
|
|
|
|
|
// The sweep should have a fee that corresponds to the custom
|
|
|
|
// confirmation target.
|
2019-10-07 15:29:24 +00:00
|
|
|
_ = assertSweepTx(testRequest.SweepConfTarget)
|
2019-10-01 15:21:19 +00:00
|
|
|
|
|
|
|
// We'll then notify the height at which we begin using the default
|
|
|
|
// confirmation target.
|
|
|
|
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
|
|
|
|
DefaultSweepConfTargetDelta
|
|
|
|
blockEpochChan <- int32(defaultConfTargetHeight)
|
|
|
|
expiryChan <- time.Now()
|
|
|
|
|
2020-01-15 08:55:22 +00:00
|
|
|
// Expect another signing request.
|
|
|
|
<-ctx.Lnd.SignOutputRawChannel
|
|
|
|
|
2019-10-01 15:21:19 +00:00
|
|
|
// We should expect to see another sweep using the higher fee since the
|
|
|
|
// spend hasn't been confirmed yet.
|
2019-10-07 15:29:24 +00:00
|
|
|
sweepTx := assertSweepTx(DefaultSweepConfTarget)
|
2019-10-01 15:21:19 +00:00
|
|
|
|
|
|
|
// Notify the spend so that the swap reaches its final state.
|
|
|
|
ctx.NotifySpend(sweepTx, 0)
|
|
|
|
|
|
|
|
cfg.store.(*storeMock).assertLoopOutState(loopdb.StateSuccess)
|
|
|
|
status = <-statusChan
|
|
|
|
if status.State != loopdb.StateSuccess {
|
|
|
|
t.Fatalf("expected state %v, got %v", loopdb.StateSuccess,
|
|
|
|
status.State)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := <-errChan; err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|