Skip to content

Commit 3e289a7

Browse files
authored
Merge pull request #750 from hieblmi/static-addr-6
[6/?] StaticAddr: Quoting
2 parents b3b9176 + 850a75b commit 3e289a7

File tree

13 files changed

+1165
-967
lines changed

13 files changed

+1165
-967
lines changed

client.go

Lines changed: 7 additions & 5 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

@@ -693,7 +693,7 @@ func (s *Client) LoopInQuote(ctx context.Context,
693693

694694
quote, err := s.Server.GetLoopInQuote(
695695
ctx, request.Amount, s.lndServices.NodePubkey, request.LastHop,
696-
request.RouteHints, request.Initiator,
696+
request.RouteHints, request.Initiator, request.NumDeposits,
697697
)
698698
if err != nil {
699699
return nil, err
@@ -703,7 +703,9 @@ func (s *Client) LoopInQuote(ctx context.Context,
703703

704704
// We don't calculate the on-chain fee if the HTLC is going to be
705705
// published externally.
706-
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 {
707709
return &LoopInQuote{
708710
SwapFee: swapFee,
709711
MinerFee: 0,

cmd/loop/quote.go

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"time"
88

9+
"github.com/btcsuite/btcd/btcutil"
910
"github.com/lightninglabs/loop"
1011
"github.com/lightninglabs/loop/looprpc"
1112
"github.com/lightningnetwork/lnd/routing/route"
@@ -19,10 +20,11 @@ var quoteCommand = cli.Command{
1920
}
2021

2122
var quoteInCommand = cli.Command{
22-
Name: "in",
23-
Usage: "get a quote for the cost of a loop in swap",
24-
ArgsUsage: "amt",
25-
Description: "Allows to determine the cost of a swap up front",
23+
Name: "in",
24+
Usage: "get a quote for the cost of a loop in swap",
25+
ArgsUsage: "amt",
26+
Description: "Allows to determine the cost of a swap up front." +
27+
"Either specify an amount or deposit outpoints.",
2628
Flags: []cli.Flag{
2729
cli.StringFlag{
2830
Name: lastHopFlag.Name,
@@ -33,20 +35,38 @@ var quoteInCommand = cli.Command{
3335
verboseFlag,
3436
privateFlag,
3537
routeHintsFlag,
38+
cli.StringSliceFlag{
39+
Name: "deposit_outpoint",
40+
Usage: "one or more static address deposit outpoints " +
41+
"to quote for. Deposit outpoints are not to " +
42+
"be used in combination with an amount. Each" +
43+
"additional outpoint can be added by " +
44+
"specifying --deposit_outpoint tx_id:idx",
45+
},
3646
},
3747
Action: quoteIn,
3848
}
3949

4050
func quoteIn(ctx *cli.Context) error {
4151
// Show command help if the incorrect number arguments was provided.
42-
if ctx.NArg() != 1 {
52+
if ctx.NArg() != 1 && !ctx.IsSet("deposit_outpoint") {
4353
return cli.ShowCommandHelp(ctx, "in")
4454
}
4555

46-
args := ctx.Args()
47-
amt, err := parseAmt(args[0])
48-
if err != nil {
49-
return err
56+
var (
57+
manualAmt btcutil.Amount
58+
depositAmt btcutil.Amount
59+
depositOutpoints []string
60+
err error
61+
ctxb = context.Background()
62+
)
63+
64+
if ctx.NArg() == 1 {
65+
args := ctx.Args()
66+
manualAmt, err = parseAmt(args[0])
67+
if err != nil {
68+
return err
69+
}
5070
}
5171

5272
client, cleanup, err := getClient(ctx)
@@ -62,11 +82,20 @@ func quoteIn(ctx *cli.Context) error {
6282
return err
6383
}
6484

85+
if ctx.IsSet("deposit_outpoint") {
86+
depositOutpoints = ctx.StringSlice("deposit_outpoint")
87+
depositAmt, err = depositAmount(ctxb, client, depositOutpoints)
88+
if err != nil {
89+
return err
90+
}
91+
}
92+
6593
quoteReq := &looprpc.QuoteRequest{
66-
Amt: int64(amt),
94+
Amt: int64(manualAmt),
6795
ConfTarget: int32(ctx.Uint64("conf_target")),
6896
LoopInRouteHints: hints,
6997
Private: ctx.Bool(privateFlag.Name),
98+
DepositOutpoints: depositOutpoints,
7099
}
71100

72101
if ctx.IsSet(lastHopFlag.Name) {
@@ -80,7 +109,6 @@ func quoteIn(ctx *cli.Context) error {
80109
quoteReq.LoopInLastHop = lastHopVertex[:]
81110
}
82111

83-
ctxb := context.Background()
84112
quoteResp, err := client.GetLoopInQuote(ctxb, quoteReq)
85113
if err != nil {
86114
return err
@@ -98,10 +126,36 @@ func quoteIn(ctx *cli.Context) error {
98126
"amount.\n")
99127
}
100128

129+
// If the user specified static address deposits, we quoted for their
130+
// total value and need to display that value instead of the manually
131+
// selected one.
132+
if manualAmt == 0 {
133+
quoteReq.Amt = int64(depositAmt)
134+
}
101135
printQuoteInResp(quoteReq, quoteResp, ctx.Bool("verbose"))
102136
return nil
103137
}
104138

139+
func depositAmount(ctx context.Context, client looprpc.SwapClientClient,
140+
depositOutpoints []string) (btcutil.Amount, error) {
141+
142+
addressSummary, err := client.GetStaticAddressSummary(
143+
ctx, &looprpc.StaticAddressSummaryRequest{
144+
Outpoints: depositOutpoints,
145+
},
146+
)
147+
if err != nil {
148+
return 0, err
149+
}
150+
151+
var depositAmt btcutil.Amount
152+
for _, deposit := range addressSummary.FilteredDeposits {
153+
depositAmt += btcutil.Amount(deposit.Value)
154+
}
155+
156+
return depositAmt, nil
157+
}
158+
105159
var quoteOutCommand = cli.Command{
106160
Name: "out",
107161
Usage: "get a quote for the cost of a loop out swap",

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: 52 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,32 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
777795
}
778796
}
779797

798+
// The requested amount should be 0 here if the request contained
799+
// deposit outpoints.
800+
amount := btcutil.Amount(req.Amt)
801+
if amount != 0 && len(summary.FilteredDeposits) > 0 {
802+
return nil, fmt.Errorf("amount should be 0 for deposit " +
803+
"quotes")
804+
}
805+
806+
// In case we quote for deposits we send the server both the total value
807+
// and the number of deposits. This is so the server can probe the total
808+
// amount and calculate the per input fee.
809+
if amount == 0 && len(summary.FilteredDeposits) > 0 {
810+
for _, deposit := range summary.FilteredDeposits {
811+
amount += btcutil.Amount(deposit.Value)
812+
}
813+
}
814+
780815
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
781-
Amount: btcutil.Amount(req.Amt),
816+
Amount: amount,
782817
HtlcConfTarget: htlcConfTarget,
783818
ExternalHtlc: req.ExternalHtlc,
784819
LastHop: lastHop,
785820
RouteHints: routeHints,
786821
Private: req.Private,
787822
Initiator: defaultLoopdInitiator,
823+
NumDeposits: numDeposits,
788824
})
789825
if err != nil {
790826
return nil, err
@@ -881,7 +917,7 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
881917
log.Infof("Loop in request received")
882918

883919
htlcConfTarget, err := validateLoopInRequest(
884-
in.HtlcConfTarget, in.ExternalHtlc,
920+
in.HtlcConfTarget, in.ExternalHtlc, 0, in.Amt,
885921
)
886922
if err != nil {
887923
return nil, err
@@ -1710,7 +1746,13 @@ func validateConfTarget(target, defaultTarget int32) (int32, error) {
17101746

17111747
// validateLoopInRequest fails if the mutually exclusive conf target and
17121748
// external parameters are both set.
1713-
func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
1749+
func validateLoopInRequest(htlcConfTarget int32, external bool,
1750+
numDeposits uint32, amount int64) (int32, error) {
1751+
1752+
if amount == 0 && numDeposits == 0 {
1753+
return 0, errors.New("either amount or deposits must be set")
1754+
}
1755+
17141756
// If the htlc is going to be externally set, the htlcConfTarget should
17151757
// not be set, because it has no relevance when the htlc is external.
17161758
if external && htlcConfTarget != 0 {
@@ -1724,6 +1766,12 @@ func validateLoopInRequest(htlcConfTarget int32, external bool) (int32, error) {
17241766
return 0, nil
17251767
}
17261768

1769+
// If the loop in uses static address deposits, we do not need to set a
1770+
// confirmation target since the HTLC won't be published by the client.
1771+
if numDeposits > 0 {
1772+
return 0, nil
1773+
}
1774+
17271775
return validateConfTarget(htlcConfTarget, loop.DefaultHtlcConfTarget)
17281776
}
17291777

loopd/swapclient_server_test.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,55 +145,76 @@ 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: 100_000,
206+
numDeposits: 1,
207+
external: false,
208+
expectErr: false,
209+
},
188210
}
189211

190212
for _, test := range tests {
191-
test := test
192-
193213
t.Run(test.name, func(t *testing.T) {
194214
external := test.external
195215
conf, err := validateLoopInRequest(
196-
test.confTarget, external,
216+
test.confTarget, external, test.numDeposits,
217+
test.amount,
197218
)
198219

199220
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)

0 commit comments

Comments
 (0)