mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
loopd: quoting for static address loop-ins
This commit is contained in:
parent
549c33e08e
commit
82b95798ab
7 changed files with 105 additions and 20 deletions
12
client.go
12
client.go
|
|
@ -694,9 +694,9 @@ func (s *Client) LoopIn(globalCtx context.Context,
|
|||
return swapInfo, nil
|
||||
}
|
||||
|
||||
// LoopInQuote takes an 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.
|
||||
// LoopInQuote takes an amount and returns a breakdown 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.
|
||||
func (s *Client) LoopInQuote(ctx context.Context,
|
||||
request *LoopInQuoteRequest) (*LoopInQuote, error) {
|
||||
|
||||
|
|
@ -742,7 +742,7 @@ func (s *Client) LoopInQuote(ctx context.Context,
|
|||
|
||||
quote, err := s.Server.GetLoopInQuote(
|
||||
ctx, request.Amount, s.lndServices.NodePubkey, request.LastHop,
|
||||
request.RouteHints, request.Initiator,
|
||||
request.RouteHints, request.Initiator, request.NumDeposits,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -752,7 +752,9 @@ func (s *Client) LoopInQuote(ctx context.Context,
|
|||
|
||||
// We don't calculate the on-chain fee if the HTLC is going to be
|
||||
// published externally.
|
||||
if request.ExternalHtlc {
|
||||
// We also don't calculate the on-chain fee if the loop in is funded by
|
||||
// static address deposits because we don't publish the HTLC on-chain.
|
||||
if request.ExternalHtlc || request.NumDeposits > 0 {
|
||||
return &LoopInQuote{
|
||||
SwapFee: swapFee,
|
||||
MinerFee: 0,
|
||||
|
|
|
|||
|
|
@ -288,6 +288,12 @@ type LoopInQuoteRequest struct {
|
|||
// initiated the swap (loop CLI, autolooper, LiT UI and so on) and is
|
||||
// appended to the user agent string.
|
||||
Initiator string
|
||||
|
||||
// The number of static address deposits the client wants to quote for.
|
||||
// If the number of deposits exceeds one the server will apply a
|
||||
// per-input service fee. This is to cover for the increased on-chain
|
||||
// fee the server has to pay when the sweeping transaction is broadcast.
|
||||
NumDeposits uint32
|
||||
}
|
||||
|
||||
// LoopInQuote contains estimates for the fees making up the total swap cost
|
||||
|
|
|
|||
|
|
@ -748,13 +748,53 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
|
|||
|
||||
log.Infof("Loop in quote request received")
|
||||
|
||||
var (
|
||||
numDeposits = uint32(len(req.DepositOutpoints))
|
||||
err error
|
||||
)
|
||||
|
||||
htlcConfTarget, err := validateLoopInRequest(
|
||||
req.ConfTarget, req.ExternalHtlc,
|
||||
req.ConfTarget, req.ExternalHtlc, numDeposits, req.Amt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Retrieve deposits to calculate their total value.
|
||||
var summary *looprpc.StaticAddressSummaryResponse
|
||||
amount := btcutil.Amount(req.Amt)
|
||||
if len(req.DepositOutpoints) > 0 {
|
||||
summary, err = s.GetStaticAddressSummary(
|
||||
ctx, &looprpc.StaticAddressSummaryRequest{
|
||||
Outpoints: req.DepositOutpoints,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if summary == nil {
|
||||
return nil, fmt.Errorf("no summary returned for " +
|
||||
"deposit outpoints")
|
||||
}
|
||||
|
||||
// The requested amount should be 0 here if the request
|
||||
// contained deposit outpoints.
|
||||
if amount != 0 && len(summary.FilteredDeposits) > 0 {
|
||||
return nil, fmt.Errorf("amount should be 0 for " +
|
||||
"deposit quotes")
|
||||
}
|
||||
|
||||
// In case we quote for deposits we send the server both the
|
||||
// total value and the number of deposits. This is so the server
|
||||
// can probe the total amount and calculate the per input fee.
|
||||
if amount == 0 && len(summary.FilteredDeposits) > 0 {
|
||||
for _, deposit := range summary.FilteredDeposits {
|
||||
amount += btcutil.Amount(deposit.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
routeHints [][]zpay32.HopHint
|
||||
lastHop *route.Vertex
|
||||
|
|
@ -778,13 +818,14 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
|
|||
}
|
||||
|
||||
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
|
||||
Amount: btcutil.Amount(req.Amt),
|
||||
Amount: amount,
|
||||
HtlcConfTarget: htlcConfTarget,
|
||||
ExternalHtlc: req.ExternalHtlc,
|
||||
LastHop: lastHop,
|
||||
RouteHints: routeHints,
|
||||
Private: req.Private,
|
||||
Initiator: defaultLoopdInitiator,
|
||||
NumDeposits: numDeposits,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -881,7 +922,7 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
|
|||
log.Infof("Loop in request received")
|
||||
|
||||
htlcConfTarget, err := validateLoopInRequest(
|
||||
in.HtlcConfTarget, in.ExternalHtlc,
|
||||
in.HtlcConfTarget, in.ExternalHtlc, 0, in.Amt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -1730,7 +1771,13 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
|
|||
|
||||
// validateLoopInRequest fails if the mutually exclusive conf target and
|
||||
// external parameters are both set.
|
||||
func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
|
||||
func validateLoopInRequest(htlcConfTarget int32, external bool,
|
||||
numDeposits uint32, amount int64) (int32, error) {
|
||||
|
||||
if amount == 0 && numDeposits == 0 {
|
||||
return 0, errors.New("either amount or deposits must be set")
|
||||
}
|
||||
|
||||
// If the htlc is going to be externally set, the htlcConfTarget should
|
||||
// not be set, because it has no relevance when the htlc is external.
|
||||
if external && htlcConfTarget != 0 {
|
||||
|
|
@ -1744,6 +1791,12 @@ func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
// If the loop in uses static address deposits, we do not need to set a
|
||||
// confirmation target since the HTLC won't be published by the client.
|
||||
if numDeposits > 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return validateConfTarget(htlcConfTarget, loop.DefaultHtlcConfTarget)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ func TestValidateConfTarget(t *testing.T) {
|
|||
func TestValidateLoopInRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
amount int64
|
||||
numDeposits uint32
|
||||
external bool
|
||||
confTarget int32
|
||||
expectErr bool
|
||||
|
|
@ -153,6 +155,7 @@ func TestValidateLoopInRequest(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "external and htlc conf set",
|
||||
amount: 100_000,
|
||||
external: true,
|
||||
confTarget: 1,
|
||||
expectErr: true,
|
||||
|
|
@ -160,6 +163,7 @@ func TestValidateLoopInRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "external and no conf",
|
||||
amount: 100_000,
|
||||
external: true,
|
||||
confTarget: 0,
|
||||
expectErr: false,
|
||||
|
|
@ -167,6 +171,7 @@ func TestValidateLoopInRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "not external, zero conf",
|
||||
amount: 100_000,
|
||||
external: false,
|
||||
confTarget: 0,
|
||||
expectErr: false,
|
||||
|
|
@ -174,6 +179,7 @@ func TestValidateLoopInRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "not external, bad conf",
|
||||
amount: 100_000,
|
||||
external: false,
|
||||
confTarget: 1,
|
||||
expectErr: true,
|
||||
|
|
@ -181,20 +187,35 @@ func TestValidateLoopInRequest(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "not external, ok conf",
|
||||
amount: 100_000,
|
||||
external: false,
|
||||
confTarget: 5,
|
||||
expectErr: false,
|
||||
expectedTarget: 5,
|
||||
},
|
||||
{
|
||||
name: "not external, amount no deposit",
|
||||
amount: 100_000,
|
||||
numDeposits: 0,
|
||||
external: false,
|
||||
expectErr: false,
|
||||
expectedTarget: loop.DefaultHtlcConfTarget,
|
||||
},
|
||||
{
|
||||
name: "not external, deposit no amount",
|
||||
amount: 100_000,
|
||||
numDeposits: 1,
|
||||
external: false,
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
external := test.external
|
||||
conf, err := validateLoopInRequest(
|
||||
test.confTarget, external,
|
||||
test.confTarget, external, test.numDeposits,
|
||||
test.amount,
|
||||
)
|
||||
|
||||
if test.expectErr {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
|
|||
// hints.
|
||||
quote, err := cfg.server.GetLoopInQuote(
|
||||
globalCtx, request.Amount, cfg.lnd.NodePubkey, request.LastHop,
|
||||
request.RouteHints, request.Initiator,
|
||||
request.RouteHints, request.Initiator, 0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, wrapGrpcError("loop in terms", err)
|
||||
|
|
|
|||
|
|
@ -225,7 +225,8 @@ func (s *serverMock) GetLoopInTerms(ctx context.Context, initiator string) (
|
|||
}
|
||||
|
||||
func (s *serverMock) GetLoopInQuote(context.Context, btcutil.Amount,
|
||||
route.Vertex, *route.Vertex, [][]zpay32.HopHint, string) (*LoopInQuote, error) {
|
||||
route.Vertex, *route.Vertex, [][]zpay32.HopHint, string,
|
||||
uint32) (*LoopInQuote, error) {
|
||||
|
||||
return &LoopInQuote{
|
||||
SwapFee: testSwapFee,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ type swapServerClient interface {
|
|||
GetLoopInQuote(ctx context.Context, amt btcutil.Amount,
|
||||
pubKey route.Vertex, lastHop *route.Vertex,
|
||||
routeHints [][]zpay32.HopHint,
|
||||
initiator string) (*LoopInQuote, error)
|
||||
initiator string, numDeposits uint32) (*LoopInQuote, error)
|
||||
|
||||
Probe(ctx context.Context, amt btcutil.Amount, target route.Vertex,
|
||||
lastHop *route.Vertex, routeHints [][]zpay32.HopHint) error
|
||||
|
|
@ -268,7 +268,8 @@ func (s *grpcSwapServerClient) GetLoopInTerms(ctx context.Context,
|
|||
|
||||
func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
|
||||
amt btcutil.Amount, pubKey route.Vertex, lastHop *route.Vertex,
|
||||
routeHints [][]zpay32.HopHint, initiator string) (*LoopInQuote, error) {
|
||||
routeHints [][]zpay32.HopHint, initiator string,
|
||||
numDeposits uint32) (*LoopInQuote, error) {
|
||||
|
||||
err := s.Probe(ctx, amt, pubKey, lastHop, routeHints)
|
||||
if err != nil && status.Code(err) != codes.Unavailable {
|
||||
|
|
@ -279,10 +280,11 @@ func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
|
|||
defer rpcCancel()
|
||||
|
||||
req := &swapserverrpc.ServerLoopInQuoteRequest{
|
||||
Amt: uint64(amt),
|
||||
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
|
||||
Pubkey: pubKey[:],
|
||||
UserAgent: UserAgent(initiator),
|
||||
Amt: uint64(amt),
|
||||
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
|
||||
Pubkey: pubKey[:],
|
||||
UserAgent: UserAgent(initiator),
|
||||
NumStaticAddressDeposits: numDeposits,
|
||||
}
|
||||
|
||||
if lastHop != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue