mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-18 13:08:28 +02:00
Merge pull request #89 from wpaulino/loop-out-conf-target
loopout: compare delta from htlc expiry correctly
This commit is contained in:
commit
b61807b310
5 changed files with 205 additions and 12 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
166
loopout_test.go
166
loopout_test.go
|
|
@ -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…
Add table
Add a link
Reference in a new issue