mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
loopout: extend htlc expiry based on conf target
This commit is contained in:
parent
e72d998e78
commit
43323ffbe2
7 changed files with 74 additions and 58 deletions
53
client.go
53
client.go
|
|
@ -3,6 +3,7 @@ package loop
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -33,20 +34,10 @@ var (
|
|||
// more than the server maximum.
|
||||
ErrSwapAmountTooHigh = errors.New("swap amount too high")
|
||||
|
||||
// ErrExpiryTooSoon is returned when the server proposes an expiry that
|
||||
// is too soon for us.
|
||||
ErrExpiryTooSoon = errors.New("swap expiry too soon")
|
||||
|
||||
// ErrExpiryTooFar is returned when the server proposes an expiry that
|
||||
// is too soon for us.
|
||||
ErrExpiryTooFar = errors.New("swap expiry too far")
|
||||
|
||||
// ErrSweepConfTargetTooFar is returned when the client proposes a
|
||||
// confirmation target to sweep the on-chain HTLC of a Loop Out that is
|
||||
// beyond the expiration height proposed by the server.
|
||||
ErrSweepConfTargetTooFar = errors.New("sweep confirmation target is " +
|
||||
"beyond swap expiration height")
|
||||
|
||||
// serverRPCTimeout is the maximum time a gRPC request to the server
|
||||
// should be allowed to take.
|
||||
serverRPCTimeout = 30 * time.Second
|
||||
|
|
@ -363,8 +354,21 @@ func (s *Client) LoopOut(globalCtx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new swap object for this swap.
|
||||
// Calculate htlc expiry height.
|
||||
terms, err := s.Server.GetLoopOutTerms(globalCtx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
initiationHeight := s.executor.height()
|
||||
request.Expiry, err = s.getExpiry(
|
||||
initiationHeight, terms, request.SweepConfTarget,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new swap object for this swap.
|
||||
swapCfg := newSwapConfig(s.lndServices, s.Store, s.Server)
|
||||
initResult, err := newLoopOutSwap(
|
||||
globalCtx, swapCfg, initiationHeight, request,
|
||||
|
|
@ -386,6 +390,24 @@ func (s *Client) LoopOut(globalCtx context.Context,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// getExpiry returns an absolute expiry height based on the sweep confirmation
|
||||
// target, constrained by the server terms.
|
||||
func (s *Client) getExpiry(height int32, terms *LoopOutTerms,
|
||||
confTarget int32) (int32, error) {
|
||||
|
||||
switch {
|
||||
case confTarget < terms.MinCltvDelta:
|
||||
return height + terms.MinCltvDelta, nil
|
||||
|
||||
case confTarget > terms.MaxCltvDelta:
|
||||
return 0, fmt.Errorf("confirmation target %v exceeds maximum "+
|
||||
"server cltv delta of %v", confTarget,
|
||||
terms.MaxCltvDelta)
|
||||
}
|
||||
|
||||
return height + confTarget, nil
|
||||
}
|
||||
|
||||
// LoopOutQuote takes a LoopOut amount and returns a break down of estimated
|
||||
// costs for the client. Both the swap server and the on-chain fee estimator
|
||||
// are queried to get to build the quote response.
|
||||
|
|
@ -405,8 +427,14 @@ func (s *Client) LoopOutQuote(ctx context.Context,
|
|||
return nil, ErrSwapAmountTooHigh
|
||||
}
|
||||
|
||||
height := s.executor.height()
|
||||
expiry, err := s.getExpiry(height, terms, request.SweepConfTarget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
quote, err := s.Server.GetLoopOutQuote(
|
||||
ctx, request.Amount, request.SwapPublicationDeadline,
|
||||
ctx, request.Amount, expiry, request.SwapPublicationDeadline,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -440,7 +468,6 @@ func (s *Client) LoopOutQuote(ctx context.Context,
|
|||
MinerFee: minerFee,
|
||||
PrepayAmount: quote.PrepayAmount,
|
||||
SwapPaymentDest: quote.SwapPaymentDest,
|
||||
CltvDelta: quote.CltvDelta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ type OutRequest struct {
|
|||
// SwapPublicationDeadline can be set by the client to allow the server
|
||||
// delaying publication of the swap HTLC to save on chain fees.
|
||||
SwapPublicationDeadline time.Time
|
||||
|
||||
// Expiry is the absolute expiry height of the on-chain htlc.
|
||||
Expiry int32
|
||||
}
|
||||
|
||||
// Out contains the full details of a loop out request. This includes things
|
||||
|
|
@ -146,10 +149,6 @@ type LoopOutQuote struct {
|
|||
// sweep the htlc.
|
||||
MinerFee btcutil.Amount
|
||||
|
||||
// Time lock delta relative to current block height that swap server
|
||||
// will accept on the swap initiation call.
|
||||
CltvDelta int32
|
||||
|
||||
// SwapPaymentDest is the node pubkey where to swap payment needs to be
|
||||
// sent to.
|
||||
SwapPaymentDest [33]byte
|
||||
|
|
|
|||
|
|
@ -373,7 +373,6 @@ func (s *swapClientServer) LoopOutQuote(ctx context.Context,
|
|||
PrepayAmtSat: int64(quote.PrepayAmount),
|
||||
SwapFeeSat: int64(quote.SwapFee),
|
||||
SwapPaymentDest: quote.SwapPaymentDest[:],
|
||||
CltvDelta: quote.CltvDelta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
22
loopout.go
22
loopout.go
|
|
@ -106,13 +106,14 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
|
||||
// Post the swap parameters to the swap server. The response contains
|
||||
// the server revocation key and the swap and prepay invoices.
|
||||
log.Infof("Initiating swap request at height %v", currentHeight)
|
||||
log.Infof("Initiating swap request at height %v: amt=%v, expiry=%v",
|
||||
currentHeight, request.Amount, request.Expiry)
|
||||
|
||||
// The swap deadline will be given to the server for it to use as the
|
||||
// latest swap publication time.
|
||||
swapResp, err := cfg.server.NewLoopOutSwap(
|
||||
globalCtx, swapHash, request.Amount, receiverKey,
|
||||
request.SwapPublicationDeadline,
|
||||
globalCtx, swapHash, request.Amount, request.Expiry,
|
||||
receiverKey, request.SwapPublicationDeadline,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot initiate swap: %v", err)
|
||||
|
|
@ -150,7 +151,7 @@ func newLoopOutSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
SenderKey: swapResp.senderKey,
|
||||
Preimage: swapPreimage,
|
||||
AmountRequested: request.Amount,
|
||||
CltvExpiry: swapResp.expiry,
|
||||
CltvExpiry: request.Expiry,
|
||||
MaxMinerFee: request.MaxMinerFee,
|
||||
MaxSwapFee: request.MaxSwapFee,
|
||||
},
|
||||
|
|
@ -994,18 +995,5 @@ func validateLoopOutContract(lnd *lndclient.LndServices,
|
|||
return ErrPrepayAmountTooHigh
|
||||
}
|
||||
|
||||
if response.expiry-height < MinLoopOutPreimageRevealDelta {
|
||||
log.Warnf("Proposed expiry %v (delta %v) too soon",
|
||||
response.expiry, response.expiry-height)
|
||||
|
||||
return ErrExpiryTooSoon
|
||||
}
|
||||
|
||||
// Ensure the client has provided a sweep confirmation target that does
|
||||
// not exceed the height at which we revert back to using the default.
|
||||
if height+request.SweepConfTarget >= response.expiry-DefaultSweepConfTargetDelta {
|
||||
return ErrSweepConfTargetTooFar
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,6 +151,8 @@ func TestLateHtlcPublish(t *testing.T) {
|
|||
|
||||
cfg := newSwapConfig(&lnd.LndServices, store, server)
|
||||
|
||||
testRequest.Expiry = height + testLoopOutMinOnChainCltvDelta
|
||||
|
||||
initResult, err := newLoopOutSwap(
|
||||
context.Background(), cfg, height, testRequest,
|
||||
)
|
||||
|
|
@ -227,7 +229,7 @@ func TestCustomSweepConfTarget(t *testing.T) {
|
|||
// the default.
|
||||
testReq := *testRequest
|
||||
|
||||
testReq.SweepConfTarget = testLoopOutOnChainCltvDelta -
|
||||
testReq.SweepConfTarget = testLoopOutMinOnChainCltvDelta -
|
||||
DefaultSweepConfTargetDelta - 1
|
||||
|
||||
// Set up custom fee estimates such that the lower confirmation target
|
||||
|
|
@ -376,8 +378,8 @@ func TestCustomSweepConfTarget(t *testing.T) {
|
|||
|
||||
// We'll then notify the height at which we begin using the default
|
||||
// confirmation target.
|
||||
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
|
||||
DefaultSweepConfTargetDelta
|
||||
defaultConfTargetHeight := ctx.Lnd.Height +
|
||||
testLoopOutMinOnChainCltvDelta - DefaultSweepConfTargetDelta
|
||||
blockEpochChan <- int32(defaultConfTargetHeight)
|
||||
expiryChan <- time.Now()
|
||||
|
||||
|
|
@ -427,8 +429,9 @@ func TestPreimagePush(t *testing.T) {
|
|||
// Start with a high confirmation delta which will have a very high fee
|
||||
// attached to it.
|
||||
testReq := *testRequest
|
||||
testReq.SweepConfTarget = testLoopOutOnChainCltvDelta -
|
||||
testReq.SweepConfTarget = testLoopOutMinOnChainCltvDelta -
|
||||
DefaultSweepConfTargetDelta - 1
|
||||
testReq.Expiry = ctx.Lnd.Height + testLoopOutMinOnChainCltvDelta
|
||||
|
||||
// We set our mock fee estimate for our target sweep confs to be our
|
||||
// max miner fee *2, so that our fee will definitely be above what we
|
||||
|
|
@ -520,7 +523,7 @@ func TestPreimagePush(t *testing.T) {
|
|||
// Now, we notify the height at which the client will start using the
|
||||
// default confirmation target. This has the effect of lowering our fees
|
||||
// so that the client still start sweeping.
|
||||
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutOnChainCltvDelta -
|
||||
defaultConfTargetHeight := ctx.Lnd.Height + testLoopOutMinOnChainCltvDelta -
|
||||
DefaultSweepConfTargetDelta
|
||||
blockEpochChan <- defaultConfTargetHeight
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,13 @@ import (
|
|||
var (
|
||||
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
|
||||
|
||||
testLoopOutOnChainCltvDelta = int32(30)
|
||||
testChargeOnChainCltvDelta = int32(100)
|
||||
testSwapFee = btcutil.Amount(210)
|
||||
testFixedPrepayAmount = btcutil.Amount(100)
|
||||
testMinSwapAmount = btcutil.Amount(10000)
|
||||
testMaxSwapAmount = btcutil.Amount(1000000)
|
||||
testLoopOutMinOnChainCltvDelta = int32(30)
|
||||
testLoopOutMaxOnChainCltvDelta = int32(40)
|
||||
testChargeOnChainCltvDelta = int32(100)
|
||||
testSwapFee = btcutil.Amount(210)
|
||||
testFixedPrepayAmount = btcutil.Amount(100)
|
||||
testMinSwapAmount = btcutil.Amount(10000)
|
||||
testMaxSwapAmount = btcutil.Amount(1000000)
|
||||
)
|
||||
|
||||
// serverMock is used in client unit tests to simulate swap server behaviour.
|
||||
|
|
@ -58,7 +59,7 @@ func newServerMock() *serverMock {
|
|||
}
|
||||
|
||||
func (s *serverMock) NewLoopOutSwap(ctx context.Context,
|
||||
swapHash lntypes.Hash, amount btcutil.Amount,
|
||||
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
|
||||
receiverKey [33]byte, _ time.Time) (
|
||||
*newLoopOutResponse, error) {
|
||||
|
||||
|
|
@ -87,7 +88,6 @@ func (s *serverMock) NewLoopOutSwap(ctx context.Context,
|
|||
senderKey: senderKeyArray,
|
||||
swapInvoice: swapPayReqString,
|
||||
prepayInvoice: prePayReqString,
|
||||
expiry: s.height + testLoopOutOnChainCltvDelta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -97,18 +97,19 @@ func (s *serverMock) GetLoopOutTerms(ctx context.Context) (
|
|||
return &LoopOutTerms{
|
||||
MinSwapAmount: testMinSwapAmount,
|
||||
MaxSwapAmount: testMaxSwapAmount,
|
||||
MinCltvDelta: testLoopOutMinOnChainCltvDelta,
|
||||
MaxCltvDelta: testLoopOutMaxOnChainCltvDelta,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *serverMock) GetLoopOutQuote(ctx context.Context, amt btcutil.Amount,
|
||||
_ time.Time) (*LoopOutQuote, error) {
|
||||
expiry int32, _ time.Time) (*LoopOutQuote, error) {
|
||||
|
||||
dest := [33]byte{1, 2, 3}
|
||||
|
||||
return &LoopOutQuote{
|
||||
SwapFee: testSwapFee,
|
||||
SwapPaymentDest: dest,
|
||||
CltvDelta: testLoopOutOnChainCltvDelta,
|
||||
PrepayAmount: testFixedPrepayAmount,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
|
||||
// protocolVersion defines the version of the protocol that is currently
|
||||
// supported by the loop client.
|
||||
const protocolVersion = looprpc.ProtocolVersion_PREIMAGE_PUSH_LOOP_OUT
|
||||
const protocolVersion = looprpc.ProtocolVersion_USER_EXPIRY_LOOP_OUT
|
||||
|
||||
var (
|
||||
// errServerSubscriptionComplete is returned when our subscription to
|
||||
|
|
@ -46,7 +46,7 @@ type swapServerClient interface {
|
|||
GetLoopOutTerms(ctx context.Context) (
|
||||
*LoopOutTerms, error)
|
||||
|
||||
GetLoopOutQuote(ctx context.Context, amt btcutil.Amount,
|
||||
GetLoopOutQuote(ctx context.Context, amt btcutil.Amount, expiry int32,
|
||||
swapPublicationDeadline time.Time) (
|
||||
*LoopOutQuote, error)
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ type swapServerClient interface {
|
|||
*LoopInQuote, error)
|
||||
|
||||
NewLoopOutSwap(ctx context.Context,
|
||||
swapHash lntypes.Hash, amount btcutil.Amount,
|
||||
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
|
||||
receiverKey [33]byte,
|
||||
swapPublicationDeadline time.Time) (
|
||||
*newLoopOutResponse, error)
|
||||
|
|
@ -146,7 +146,7 @@ func (s *grpcSwapServerClient) GetLoopOutTerms(ctx context.Context) (
|
|||
}
|
||||
|
||||
func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
|
||||
amt btcutil.Amount, swapPublicationDeadline time.Time) (
|
||||
amt btcutil.Amount, expiry int32, swapPublicationDeadline time.Time) (
|
||||
*LoopOutQuote, error) {
|
||||
|
||||
rpcCtx, rpcCancel := context.WithTimeout(ctx, globalCallTimeout)
|
||||
|
|
@ -156,6 +156,7 @@ func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
|
|||
Amt: uint64(amt),
|
||||
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
|
||||
ProtocolVersion: protocolVersion,
|
||||
Expiry: expiry,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -175,7 +176,6 @@ func (s *grpcSwapServerClient) GetLoopOutQuote(ctx context.Context,
|
|||
return &LoopOutQuote{
|
||||
PrepayAmount: btcutil.Amount(quoteResp.PrepayAmt),
|
||||
SwapFee: btcutil.Amount(quoteResp.SwapFee),
|
||||
CltvDelta: quoteResp.CltvDelta,
|
||||
SwapPaymentDest: destArray,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
|
|||
}
|
||||
|
||||
func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
|
||||
swapHash lntypes.Hash, amount btcutil.Amount,
|
||||
swapHash lntypes.Hash, amount btcutil.Amount, expiry int32,
|
||||
receiverKey [33]byte, swapPublicationDeadline time.Time) (
|
||||
*newLoopOutResponse, error) {
|
||||
|
||||
|
|
@ -235,6 +235,7 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
|
|||
ReceiverKey: receiverKey[:],
|
||||
SwapPublicationDeadline: swapPublicationDeadline.Unix(),
|
||||
ProtocolVersion: protocolVersion,
|
||||
Expiry: expiry,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -254,7 +255,6 @@ func (s *grpcSwapServerClient) NewLoopOutSwap(ctx context.Context,
|
|||
swapInvoice: swapResp.SwapInvoice,
|
||||
prepayInvoice: swapResp.PrepayInvoice,
|
||||
senderKey: senderKey,
|
||||
expiry: swapResp.Expiry,
|
||||
serverMessage: swapResp.ServerMessage,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -528,7 +528,6 @@ type newLoopOutResponse struct {
|
|||
swapInvoice string
|
||||
prepayInvoice string
|
||||
senderKey [33]byte
|
||||
expiry int32
|
||||
serverMessage string
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue