Merge pull request #89 from wpaulino/loop-out-conf-target

loopout: compare delta from htlc expiry correctly
pull/92/head
Alex Bosworth 5 years ago committed by GitHub
commit b61807b310
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -597,7 +597,7 @@ func (s *loopOutSwap) sweep(ctx context.Context,
// close to the expiration height, in which case we'll use the default
// if it is better than what the client provided.
confTarget := s.SweepConfTarget
if s.CltvExpiry-s.height >= DefaultSweepConfTargetDelta &&
if s.CltvExpiry-s.height <= DefaultSweepConfTargetDelta &&
confTarget > DefaultSweepConfTarget {
confTarget = DefaultSweepConfTarget
}

@ -6,6 +6,9 @@ import (
"testing"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep"
@ -99,3 +102,166 @@ func TestLateHtlcPublish(t *testing.T) {
t.Fatal(err)
}
}
// 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 {
logger.Error(err)
}
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()
blockEpochChan <- int32(ctx.Lnd.Height + 1)
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()
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.
sweepTx := assertSweepTx(testRequest.SweepConfTarget)
// 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()
// We should expect to see another sweep using the higher fee since the
// spend hasn't been confirmed yet.
sweepTx = assertSweepTx(DefaultSweepConfTarget)
// 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)
}
}

@ -170,8 +170,8 @@ func (s *storeMock) UpdateLoopIn(hash lntypes.Hash, time time.Time,
}
updates = append(updates, state)
s.loopOutUpdates[hash] = updates
s.loopOutUpdateChan <- state
s.loopInUpdates[hash] = updates
s.loopInUpdateChan <- state
return nil
}
@ -205,6 +205,15 @@ func (s *storeMock) assertLoopOutStored() {
}
}
func (s *storeMock) assertLoopOutState(expectedState loopdb.SwapState) {
s.t.Helper()
state := <-s.loopOutUpdateChan
if state.State != expectedState {
s.t.Fatalf("expected state %v, got %v", expectedState, state)
}
}
func (s *storeMock) assertLoopInStored() {
s.t.Helper()
@ -214,9 +223,9 @@ func (s *storeMock) assertLoopInStored() {
func (s *storeMock) assertLoopInState(expectedState loopdb.SwapState) {
s.t.Helper()
state := <-s.loopOutUpdateChan
state := <-s.loopInUpdateChan
if state.State != expectedState {
s.t.Fatalf("unexpected state")
s.t.Fatalf("expected state %v, got %v", expectedState, state)
}
}

@ -6,12 +6,12 @@ import (
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/zpay32"
)
var testStartingHeight = int32(600)
@ -20,7 +20,9 @@ var testStartingHeight = int32(600)
// tests.
func NewMockLnd() *LndMockServices {
lightningClient := &mockLightningClient{}
walletKit := &mockWalletKit{}
walletKit := &mockWalletKit{
feeEstimates: make(map[int32]lnwallet.SatPerKWeight),
}
chainNotifier := &mockChainNotifier{}
signer := &mockSigner{}
invoices := &mockInvoices{}
@ -197,3 +199,9 @@ func (s *LndMockServices) DecodeInvoice(request string) (*zpay32.Invoice,
return zpay32.Decode(request, s.ChainParams)
}
func (s *LndMockServices) SetFeeEstimate(confTarget int32,
feeEstimate lnwallet.SatPerKWeight) {
s.WalletKit.(*mockWalletKit).feeEstimates[confTarget] = feeEstimate
}

@ -8,15 +8,19 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
)
type mockWalletKit struct {
lnd *LndMockServices
keyIndex int32
lnd *LndMockServices
keyIndex int32
feeEstimates map[int32]lnwallet.SatPerKWeight
}
var _ lndclient.WalletKitClient = (*mockWalletKit)(nil)
func (m *mockWalletKit) DeriveNextKey(ctx context.Context, family int32) (
*keychain.KeyDescriptor, error) {
@ -87,9 +91,15 @@ func (m *mockWalletKit) SendOutputs(ctx context.Context, outputs []*wire.TxOut,
func (m *mockWalletKit) EstimateFee(ctx context.Context, confTarget int32) (
lnwallet.SatPerKWeight, error) {
if confTarget <= 1 {
return 0, errors.New("conf target must be greater than 1")
}
return 10000, nil
feeEstimate, ok := m.feeEstimates[confTarget]
if !ok {
return 10000, nil
}
return feeEstimate, nil
}

Loading…
Cancel
Save