loopout: extend htlc expiry based on conf target

pull/248/head
Joost Jager 4 years ago
parent e72d998e78
commit 43323ffbe2
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7

@ -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
}

@ -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…
Cancel
Save