mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
loopout: compare delta from htlc expiry correctly
This addresses an issue where using a sweep confirmation target greater than the default would result in most cases not revealing the preimage due to the default confirmation target yielding a higher fee than the max miner fee backed by the confirmation target provided.
This commit is contained in:
parent
09029bfdec
commit
e0d23cb180
3 changed files with 176 additions and 1 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue