Skip to content

Commit 936698c

Browse files
committed
loopd: quoting for static address loop-ins
1 parent 8eb7814 commit 936698c

File tree

7 files changed

+105
-21
lines changed

7 files changed

+105
-21
lines changed

client.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -645,9 +645,9 @@ func (s *Client) LoopIn(globalCtx context.Context,
645645
return swapInfo, nil
646646
}
647647

648-
// LoopInQuote takes an amount and returns a break down of estimated
649-
// costs for the client. Both the swap server and the on-chain fee estimator are
650-
// queried to get to build the quote response.
648+
// LoopInQuote takes an amount and returns a breakdown of estimated costs for
649+
// the client. Both the swap server and the on-chain fee estimator are queried
650+
// to get to build the quote response.
651651
func (s *Client) LoopInQuote(ctx context.Context,
652652
request *LoopInQuoteRequest) (*LoopInQuote, error) {
653653

@@ -683,7 +683,8 @@ func (s *Client) LoopInQuote(ctx context.Context,
683683
// Because the Private flag is set, we'll generate our own
684684
// set of hop hints and use that
685685
request.RouteHints, err = SelectHopHints(
686-
ctx, s.lndServices, request.Amount, DefaultMaxHopHints, includeNodes,
686+
ctx, s.lndServices, request.Amount, DefaultMaxHopHints,
687+
includeNodes,
687688
)
688689
if err != nil {
689690
return nil, err
@@ -692,7 +693,7 @@ func (s *Client) LoopInQuote(ctx context.Context,
692693

693694
quote, err := s.Server.GetLoopInQuote(
694695
ctx, request.Amount, s.lndServices.NodePubkey, request.LastHop,
695-
request.RouteHints, request.Initiator,
696+
request.RouteHints, request.Initiator, request.NumDeposits,
696697
)
697698
if err != nil {
698699
return nil, err
@@ -702,7 +703,9 @@ func (s *Client) LoopInQuote(ctx context.Context,
702703

703704
// We don't calculate the on-chain fee if the HTLC is going to be
704705
// published externally.
705-
if request.ExternalHtlc {
706+
// We also don't calculate the on-chain fee if the loop in is funded by
707+
// static address deposits because we don't publish the HTLC on-chain.
708+
if request.ExternalHtlc || request.NumDeposits > 0 {
706709
return &LoopInQuote{
707710
SwapFee: swapFee,
708711
MinerFee: 0,

interface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,12 @@ type LoopInQuoteRequest struct {
288288
// initiated the swap (loop CLI, autolooper, LiT UI and so on) and is
289289
// appended to the user agent string.
290290
Initiator string
291+
292+
// The number of static address deposits the client wants to quote for.
293+
// If the number of deposits exceeds one the server will apply a
294+
// per-input service fee. This is to cover for the increased on-chain
295+
// fee the server has to pay when the sweeping transaction is broadcast.
296+
NumDeposits uint32
291297
}
292298

293299
// LoopInQuote contains estimates for the fees making up the total swap cost

loopd/swapclient_server.go

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -748,13 +748,31 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
748748

749749
log.Infof("Loop in quote request received")
750750

751+
var (
752+
numDeposits = uint32(len(req.DepositOutpoints))
753+
err error
754+
)
755+
751756
htlcConfTarget, err := validateLoopInRequest(
752-
req.ConfTarget, req.ExternalHtlc,
757+
req.ConfTarget, req.ExternalHtlc, numDeposits, req.Amt,
753758
)
754759
if err != nil {
755760
return nil, err
756761
}
757762

763+
// Retrieve deposits to calculate their total value.
764+
var summary *clientrpc.StaticAddressSummaryResponse
765+
if len(req.DepositOutpoints) > 0 {
766+
summary, err = s.GetStaticAddressSummary(
767+
ctx, &clientrpc.StaticAddressSummaryRequest{
768+
Outpoints: req.DepositOutpoints,
769+
},
770+
)
771+
if err != nil {
772+
return nil, err
773+
}
774+
}
775+
758776
var (
759777
routeHints [][]zpay32.HopHint
760778
lastHop *route.Vertex
@@ -777,14 +795,26 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
777795
}
778796
}
779797

798+
// The requested amount is 0 here if the request contained deposit
799+
// outpoints. In case we quote for deposits we send the server both the
800+
// total value and the number of deposits. This is so the server can
801+
// probe the total amount and calculate the per input fee.
802+
amount := btcutil.Amount(req.Amt)
803+
if amount == 0 && len(summary.FilteredDeposits) > 0 {
804+
for _, deposit := range summary.FilteredDeposits {
805+
amount += btcutil.Amount(deposit.Value)
806+
}
807+
}
808+
780809
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
781-
Amount: btcutil.Amount(req.Amt),
810+
Amount: amount,
782811
HtlcConfTarget: htlcConfTarget,
783812
ExternalHtlc: req.ExternalHtlc,
784813
LastHop: lastHop,
785814
RouteHints: routeHints,
786815
Private: req.Private,
787816
Initiator: defaultLoopdInitiator,
817+
NumDeposits: numDeposits,
788818
})
789819
if err != nil {
790820
return nil, err
@@ -881,7 +911,7 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
881911
log.Infof("Loop in request received")
882912

883913
htlcConfTarget, err := validateLoopInRequest(
884-
in.HtlcConfTarget, in.ExternalHtlc,
914+
in.HtlcConfTarget, in.ExternalHtlc, 0, in.Amt,
885915
)
886916
if err != nil {
887917
return nil, err
@@ -1708,7 +1738,15 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
17081738

17091739
// validateLoopInRequest fails if the mutually exclusive conf target and
17101740
// external parameters are both set.
1711-
func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
1741+
func validateLoopInRequest(htlcConfTarget int32, external bool,
1742+
numDeposits uint32, amount int64) (int32, error) {
1743+
1744+
if amount > 0 && numDeposits > 0 {
1745+
return 0, errors.New("amount and deposits cannot both be set")
1746+
} else if amount == 0 && numDeposits == 0 {
1747+
return 0, errors.New("either amount or deposits must be set")
1748+
}
1749+
17121750
// If the htlc is going to be externally set, the htlcConfTarget should
17131751
// not be set, because it has no relevance when the htlc is external.
17141752
if external && htlcConfTarget != 0 {
@@ -1722,6 +1760,12 @@ func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
17221760
return 0, nil
17231761
}
17241762

1763+
// If the loop in uses static address deposits, we do not need to set a
1764+
// confirmation target since the HTLC won't be published by the client.
1765+
if numDeposits > 0 {
1766+
return 0, nil
1767+
}
1768+
17251769
return validateConfTarget(htlcConfTarget, loop.DefaultHtlcConfTarget)
17261770
}
17271771

loopd/swapclient_server_test.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,55 +145,83 @@ func TestValidateConfTarget(t *testing.T) {
145145
func TestValidateLoopInRequest(t *testing.T) {
146146
tests := []struct {
147147
name string
148+
amount int64
149+
numDeposits uint32
148150
external bool
149151
confTarget int32
150152
expectErr bool
151153
expectedTarget int32
152154
}{
153155
{
154156
name: "external and htlc conf set",
157+
amount: 100_000,
155158
external: true,
156159
confTarget: 1,
157160
expectErr: true,
158161
expectedTarget: 0,
159162
},
160163
{
161164
name: "external and no conf",
165+
amount: 100_000,
162166
external: true,
163167
confTarget: 0,
164168
expectErr: false,
165169
expectedTarget: 0,
166170
},
167171
{
168172
name: "not external, zero conf",
173+
amount: 100_000,
169174
external: false,
170175
confTarget: 0,
171176
expectErr: false,
172177
expectedTarget: loop.DefaultHtlcConfTarget,
173178
},
174179
{
175180
name: "not external, bad conf",
181+
amount: 100_000,
176182
external: false,
177183
confTarget: 1,
178184
expectErr: true,
179185
expectedTarget: 0,
180186
},
181187
{
182188
name: "not external, ok conf",
189+
amount: 100_000,
183190
external: false,
184191
confTarget: 5,
185192
expectErr: false,
186193
expectedTarget: 5,
187194
},
195+
{
196+
name: "not external, amount no deposit",
197+
amount: 100_000,
198+
numDeposits: 0,
199+
external: false,
200+
expectErr: false,
201+
expectedTarget: loop.DefaultHtlcConfTarget,
202+
},
203+
{
204+
name: "not external, deposit no amount",
205+
amount: 0,
206+
numDeposits: 1,
207+
external: false,
208+
expectErr: false,
209+
},
210+
{
211+
name: "not external, amount and deposits",
212+
amount: 100_000,
213+
numDeposits: 2,
214+
external: false,
215+
expectErr: true,
216+
},
188217
}
189218

190219
for _, test := range tests {
191-
test := test
192-
193220
t.Run(test.name, func(t *testing.T) {
194221
external := test.external
195222
conf, err := validateLoopInRequest(
196-
test.confTarget, external,
223+
test.confTarget, external, test.numDeposits,
224+
test.amount,
197225
)
198226

199227
if test.expectErr {

loopin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
128128
// hints.
129129
quote, err := cfg.server.GetLoopInQuote(
130130
globalCtx, request.Amount, cfg.lnd.NodePubkey, request.LastHop,
131-
request.RouteHints, request.Initiator,
131+
request.RouteHints, request.Initiator, 0,
132132
)
133133
if err != nil {
134134
return nil, wrapGrpcError("loop in terms", err)

server_mock_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ func (s *serverMock) GetLoopInTerms(ctx context.Context, initiator string) (
225225
}
226226

227227
func (s *serverMock) GetLoopInQuote(context.Context, btcutil.Amount,
228-
route.Vertex, *route.Vertex, [][]zpay32.HopHint, string) (*LoopInQuote, error) {
228+
route.Vertex, *route.Vertex, [][]zpay32.HopHint, string,
229+
uint32) (*LoopInQuote, error) {
229230

230231
return &LoopInQuote{
231232
SwapFee: testSwapFee,

swap_server_client.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ type swapServerClient interface {
8282
GetLoopInQuote(ctx context.Context, amt btcutil.Amount,
8383
pubKey route.Vertex, lastHop *route.Vertex,
8484
routeHints [][]zpay32.HopHint,
85-
initiator string) (*LoopInQuote, error)
85+
initiator string, numDeposits uint32) (*LoopInQuote, error)
8686

8787
Probe(ctx context.Context, amt btcutil.Amount, target route.Vertex,
8888
lastHop *route.Vertex, routeHints [][]zpay32.HopHint) error
@@ -268,7 +268,8 @@ func (s *grpcSwapServerClient) GetLoopInTerms(ctx context.Context,
268268

269269
func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
270270
amt btcutil.Amount, pubKey route.Vertex, lastHop *route.Vertex,
271-
routeHints [][]zpay32.HopHint, initiator string) (*LoopInQuote, error) {
271+
routeHints [][]zpay32.HopHint, initiator string,
272+
numDeposits uint32) (*LoopInQuote, error) {
272273

273274
err := s.Probe(ctx, amt, pubKey, lastHop, routeHints)
274275
if err != nil && status.Code(err) != codes.Unavailable {
@@ -279,10 +280,11 @@ func (s *grpcSwapServerClient) GetLoopInQuote(ctx context.Context,
279280
defer rpcCancel()
280281

281282
req := &looprpc.ServerLoopInQuoteRequest{
282-
Amt: uint64(amt),
283-
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
284-
Pubkey: pubKey[:],
285-
UserAgent: UserAgent(initiator),
283+
Amt: uint64(amt),
284+
ProtocolVersion: loopdb.CurrentRPCProtocolVersion(),
285+
Pubkey: pubKey[:],
286+
UserAgent: UserAgent(initiator),
287+
NumStaticAddressDeposits: numDeposits,
286288
}
287289

288290
if lastHop != nil {

0 commit comments

Comments
 (0)