Skip to content

Commit 291f790

Browse files
committed
loopd: deposit selection for GetLoopInQuote
If a quote request contains an amount and flag SelectDeposits set to true the quoting coin- selects the required deposits to meet the swap amount in order to quote for the number of deposits.
1 parent 0d9e9c6 commit 291f790

File tree

2 files changed

+130
-35
lines changed

2 files changed

+130
-35
lines changed

loopd/swapclient_server.go

Lines changed: 98 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -893,22 +893,62 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
893893
infof("Loop in quote request received")
894894

895895
var (
896-
numDeposits = uint32(len(req.DepositOutpoints))
897-
err error
896+
selectedAmount = btcutil.Amount(req.Amt)
897+
totalDepositAmount btcutil.Amount
898+
autoSelectDeposits = req.AutoSelectDeposits
899+
err error
898900
)
899901

900902
htlcConfTarget, err := validateLoopInRequest(
901-
req.ConfTarget, req.ExternalHtlc, numDeposits, req.Amt,
903+
req.ConfTarget, req.ExternalHtlc,
904+
uint32(len(req.DepositOutpoints)), selectedAmount,
905+
autoSelectDeposits,
902906
)
903907
if err != nil {
904908
return nil, err
905909
}
906910

907-
// Retrieve deposits to calculate their total value.
908-
var depositList *looprpc.ListStaticAddressDepositsResponse
909-
amount := btcutil.Amount(req.Amt)
910-
if len(req.DepositOutpoints) > 0 {
911-
depositList, err = s.ListStaticAddressDeposits(
911+
// If deposits should be automatically selected, we do so and count the
912+
// number of deposits to quote for.
913+
numDeposits := 0
914+
if autoSelectDeposits {
915+
deposits, err := s.depositManager.GetActiveDepositsInState(
916+
deposit.Deposited,
917+
)
918+
if err != nil {
919+
return nil, fmt.Errorf("unable to retrieve all "+
920+
"deposits: %w", err)
921+
}
922+
923+
// TODO(hieblmi): add params to deposit for multi-address
924+
// support.
925+
params, err := s.staticAddressManager.GetStaticAddressParameters(
926+
ctx,
927+
)
928+
if err != nil {
929+
return nil, fmt.Errorf("unable to retrieve static "+
930+
"address parameters: %w", err)
931+
}
932+
933+
info, err := s.lnd.Client.GetInfo(ctx)
934+
if err != nil {
935+
return nil, fmt.Errorf("unable to get lnd info: %w",
936+
err)
937+
}
938+
selectedDeposits, err := loopin.SelectDeposits(
939+
selectedAmount, deposits, params.Expiry,
940+
info.BlockHeight,
941+
)
942+
if err != nil {
943+
return nil, fmt.Errorf("unable to select deposits: %w",
944+
err)
945+
}
946+
947+
numDeposits = len(selectedDeposits)
948+
} else if len(req.DepositOutpoints) > 0 {
949+
// If deposits are selected, we need to retrieve them to
950+
// calculate the total value which we request a quote for.
951+
depositList, err := s.ListStaticAddressDeposits(
912952
ctx, &looprpc.ListStaticAddressDepositsRequest{
913953
Outpoints: req.DepositOutpoints,
914954
},
@@ -922,20 +962,35 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
922962
"deposit outpoints")
923963
}
924964

925-
// The requested amount should be 0 here if the request
926-
// contained deposit outpoints.
927-
if amount != 0 && len(depositList.FilteredDeposits) > 0 {
928-
return nil, fmt.Errorf("amount should be 0 for " +
929-
"deposit quotes")
965+
if len(req.DepositOutpoints) !=
966+
len(depositList.FilteredDeposits) {
967+
968+
return nil, fmt.Errorf("expected %d deposits, got %d",
969+
len(req.DepositOutpoints),
970+
len(depositList.FilteredDeposits))
971+
} else {
972+
numDeposits = len(depositList.FilteredDeposits)
930973
}
931974

932-
// In case we quote for deposits we send the server both the
933-
// total value and the number of deposits. This is so the server
934-
// can probe the total amount and calculate the per input fee.
935-
if amount == 0 && len(depositList.FilteredDeposits) > 0 {
936-
for _, deposit := range depositList.FilteredDeposits {
937-
amount += btcutil.Amount(deposit.Value)
938-
}
975+
// In case we quote for deposits, we send the server both the
976+
// selected value and the number of deposits. This is so the
977+
// server can probe the selected value and calculate the per
978+
// input fee.
979+
for _, deposit := range depositList.FilteredDeposits {
980+
totalDepositAmount += btcutil.Amount(
981+
deposit.Value,
982+
)
983+
}
984+
985+
// If a fractional amount is also selected, we check if it
986+
// leads to a dust change output.
987+
selectedAmount, err = loopin.DeduceSwapAmount(
988+
totalDepositAmount, selectedAmount,
989+
)
990+
if err != nil {
991+
return nil, fmt.Errorf("error calculating "+
992+
"swap amount from selected amount: %v",
993+
err)
939994
}
940995
}
941996

@@ -962,14 +1017,14 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
9621017
}
9631018

9641019
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
965-
Amount: amount,
1020+
Amount: selectedAmount,
9661021
HtlcConfTarget: htlcConfTarget,
9671022
ExternalHtlc: req.ExternalHtlc,
9681023
LastHop: lastHop,
9691024
RouteHints: routeHints,
9701025
Private: req.Private,
9711026
Initiator: defaultLoopdInitiator,
972-
NumDeposits: numDeposits,
1027+
NumDeposits: uint32(numDeposits),
9731028
})
9741029
if err != nil {
9751030
return nil, err
@@ -1065,8 +1120,11 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
10651120

10661121
infof("Loop in request received")
10671122

1123+
selectDeposits := false
1124+
numDeposits := uint32(0)
10681125
htlcConfTarget, err := validateLoopInRequest(
1069-
in.HtlcConfTarget, in.ExternalHtlc, 0, in.Amt,
1126+
in.HtlcConfTarget, in.ExternalHtlc, numDeposits,
1127+
btcutil.Amount(in.Amt), selectDeposits,
10701128
)
10711129
if err != nil {
10721130
return nil, err
@@ -1980,6 +2038,7 @@ func (s *swapClientServer) StaticAddressLoopIn(ctx context.Context,
19802038
}
19812039

19822040
req := &loop.StaticAddressLoopInRequest{
2041+
SelectedAmount: btcutil.Amount(in.Amount),
19832042
DepositOutpoints: in.Outpoints,
19842043
MaxSwapFee: btcutil.Amount(in.MaxSwapFeeSatoshis),
19852044
Label: in.Label,
@@ -2282,12 +2341,24 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
22822341
}
22832342

22842343
// validateLoopInRequest fails if the mutually exclusive conf target and
2285-
// external parameters are both set.
2344+
// external parameters are both set. It returns the confirmation target of the
2345+
// legacy loop-in.
22862346
func validateLoopInRequest(htlcConfTarget int32, external bool,
2287-
numDeposits uint32, amount int64) (int32, error) {
2347+
numDeposits uint32, amount btcutil.Amount,
2348+
autoSelectDeposits bool) (int32, error) {
2349+
2350+
if amount < 0 {
2351+
return 0, errors.New("amount cannot be negative")
2352+
}
22882353

22892354
if amount == 0 && numDeposits == 0 {
2290-
return 0, errors.New("either amount or deposits must be set")
2355+
return 0, errors.New("either amount, or deposits or both " +
2356+
"must be set")
2357+
}
2358+
2359+
if autoSelectDeposits && numDeposits > 0 {
2360+
return 0, errors.New("cannot auto-select deposits while " +
2361+
"providing deposits at the same time")
22912362
}
22922363

22932364
// If the htlc is going to be externally set, the htlcConfTarget should
@@ -2305,7 +2376,7 @@ func validateLoopInRequest(htlcConfTarget int32, external bool,
23052376

23062377
// If the loop in uses static address deposits, we do not need to set a
23072378
// confirmation target since the HTLC won't be published by the client.
2308-
if numDeposits > 0 {
2379+
if numDeposits > 0 || autoSelectDeposits {
23092380
return 0, nil
23102381
}
23112382

loopd/swapclient_server_test.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,14 @@ func TestValidateConfTarget(t *testing.T) {
153153
// TestValidateLoopInRequest tests validation of loop in requests.
154154
func TestValidateLoopInRequest(t *testing.T) {
155155
tests := []struct {
156-
name string
157-
amount int64
158-
numDeposits uint32
159-
external bool
160-
confTarget int32
161-
expectErr bool
162-
expectedTarget int32
156+
name string
157+
amount int64
158+
numDeposits uint32
159+
external bool
160+
confTarget int32
161+
autoSelectDeposits bool
162+
expectErr bool
163+
expectedTarget int32
163164
}{
164165
{
165166
name: "external and htlc conf set",
@@ -216,14 +217,37 @@ func TestValidateLoopInRequest(t *testing.T) {
216217
external: false,
217218
expectErr: false,
218219
},
220+
221+
{
222+
name: "not external, deposit fractional amount",
223+
amount: 100_000,
224+
numDeposits: 1,
225+
external: false,
226+
expectErr: false,
227+
},
228+
{
229+
name: "amount with deposit coin select",
230+
amount: 100_000,
231+
autoSelectDeposits: true,
232+
external: false,
233+
expectErr: false,
234+
},
235+
{
236+
name: "amount with deposit coin select",
237+
numDeposits: 1,
238+
autoSelectDeposits: true,
239+
external: false,
240+
expectErr: true,
241+
},
219242
}
220243

221244
for _, test := range tests {
222245
t.Run(test.name, func(t *testing.T) {
223246
external := test.external
224247
conf, err := validateLoopInRequest(
225248
test.confTarget, external, test.numDeposits,
226-
test.amount,
249+
btcutil.Amount(test.amount),
250+
test.autoSelectDeposits,
227251
)
228252

229253
if test.expectErr {

0 commit comments

Comments
 (0)