Skip to content

Commit b7ba193

Browse files
authored
Merge pull request #415 from lightninglabs/routehints
loopin: Add --private parameter
2 parents 0569341 + 8dba29a commit b7ba193

16 files changed

+1229
-427
lines changed

client.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/lightninglabs/loop/loopdb"
1616
"github.com/lightninglabs/loop/swap"
1717
"github.com/lightninglabs/loop/sweep"
18+
"github.com/lightningnetwork/lnd/routing/route"
1819
"google.golang.org/grpc/status"
1920
)
2021

@@ -563,6 +564,30 @@ func (s *Client) LoopInQuote(ctx context.Context,
563564
return nil, ErrSwapAmountTooHigh
564565
}
565566

567+
// Private and routehints are mutually exclusive as setting private
568+
// means we retrieve our own routehints from the connected node.
569+
if len(request.RouteHints) != 0 && request.Private {
570+
return nil, fmt.Errorf("private and route_hints both set")
571+
}
572+
573+
if request.Private {
574+
// If last_hop is set, we'll only add channels with peers
575+
// set to the last_hop parameter
576+
includeNodes := make(map[route.Vertex]struct{})
577+
if request.LastHop != nil {
578+
includeNodes[*request.LastHop] = struct{}{}
579+
}
580+
581+
// Because the Private flag is set, we'll generate our own
582+
// set of hop hints and use that
583+
request.RouteHints, err = SelectHopHints(
584+
ctx, s.lndServices, request.Amount, DefaultMaxHopHints, includeNodes,
585+
)
586+
if err != nil {
587+
return nil, err
588+
}
589+
}
590+
566591
quote, err := s.Server.GetLoopInQuote(
567592
ctx, request.Amount, s.lndServices.NodePubkey, request.LastHop,
568593
request.RouteHints,

cmd/loop/loopin.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ var (
3131
"with our reserved prefix: %v.",
3232
labels.MaxLength, labels.Reserved),
3333
}
34+
routeHintsFlag = cli.StringSliceFlag{
35+
Name: "route_hints",
36+
Usage: "Route hints that can each be individually used " +
37+
"to assist in reaching the invoice's destination.",
38+
}
39+
privateFlag = cli.BoolFlag{
40+
Name: "private",
41+
Usage: "Generates and passes routehints. Should be used " +
42+
"if the connected node is only reachable via private " +
43+
"channels",
44+
}
3445

3546
forceFlag = cli.BoolFlag{
3647
Name: "force, f",
@@ -67,6 +78,8 @@ var (
6778
labelFlag,
6879
forceFlag,
6980
verboseFlag,
81+
routeHintsFlag,
82+
privateFlag,
7083
},
7184
Action: loopIn,
7285
}
@@ -127,11 +140,20 @@ func loopIn(ctx *cli.Context) error {
127140
lastHop = lastHopVertex[:]
128141
}
129142

143+
// Private and routehints are mutually exclusive as setting private
144+
// means we retrieve our own routehints from the connected node.
145+
hints, err := validateRouteHints(ctx)
146+
if err != nil {
147+
return err
148+
}
149+
130150
quoteReq := &looprpc.QuoteRequest{
131-
Amt: int64(amt),
132-
ConfTarget: htlcConfTarget,
133-
ExternalHtlc: external,
134-
LoopInLastHop: lastHop,
151+
Amt: int64(amt),
152+
ConfTarget: htlcConfTarget,
153+
ExternalHtlc: external,
154+
LoopInLastHop: lastHop,
155+
LoopInRouteHints: hints,
156+
Private: ctx.Bool(privateFlag.Name),
135157
}
136158

137159
quote, err := client.GetLoopInQuote(context.Background(), quoteReq)
@@ -172,6 +194,8 @@ func loopIn(ctx *cli.Context) error {
172194
Label: label,
173195
Initiator: defaultInitiator,
174196
LastHop: lastHop,
197+
RouteHints: hints,
198+
Private: ctx.Bool(privateFlag.Name),
175199
}
176200

177201
resp, err := client.LoopIn(context.Background(), req)

cmd/loop/quote.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ var quoteInCommand = cli.Command{
3131
},
3232
confTargetFlag,
3333
verboseFlag,
34+
privateFlag,
35+
routeHintsFlag,
3436
},
3537
Action: quoteIn,
3638
}
@@ -53,9 +55,18 @@ func quoteIn(ctx *cli.Context) error {
5355
}
5456
defer cleanup()
5557

58+
// Private and routehints are mutually exclusive as setting private
59+
// means we retrieve our own routehints from the connected node.
60+
hints, err := validateRouteHints(ctx)
61+
if err != nil {
62+
return err
63+
}
64+
5665
quoteReq := &looprpc.QuoteRequest{
57-
Amt: int64(amt),
58-
ConfTarget: int32(ctx.Uint64("conf_target")),
66+
Amt: int64(amt),
67+
ConfTarget: int32(ctx.Uint64("conf_target")),
68+
LoopInRouteHints: hints,
69+
Private: ctx.Bool(privateFlag.Name),
5970
}
6071

6172
if ctx.IsSet(lastHopFlag.Name) {

cmd/loop/utils.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/lightninglabs/loop/looprpc"
8+
"github.com/urfave/cli"
9+
)
10+
11+
// validateRouteHints ensures that the Private flag isn't set along with
12+
// the RouteHints flag. We don't allow both options to be set as these options
13+
// are alternatives to each other. Private autogenerates hopHints while
14+
// RouteHints are manually passed.
15+
func validateRouteHints(ctx *cli.Context) ([]*looprpc.RouteHint, error) {
16+
var hints []*looprpc.RouteHint
17+
18+
if ctx.IsSet(routeHintsFlag.Name) {
19+
if ctx.IsSet(privateFlag.Name) {
20+
return nil, fmt.Errorf(
21+
"private and route_hints both set",
22+
)
23+
}
24+
25+
stringHints := []byte(ctx.String(routeHintsFlag.Name))
26+
err := json.Unmarshal(stringHints, &hints)
27+
if err != nil {
28+
return nil, fmt.Errorf("unable to parse json: %v", err)
29+
}
30+
}
31+
32+
return hints, nil
33+
}

interface.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ type LoopInRequest struct {
207207
// initiated the swap (loop CLI, autolooper, LiT UI and so on) and is
208208
// appended to the user agent string.
209209
Initiator string
210+
211+
// Private indicates whether the destination node should be considered
212+
// private. In which case, loop will generate hophints to assist with
213+
// probing and payment.
214+
Private bool
215+
216+
// RouteHints are optional route hints to reach the destination through
217+
// private channels.
218+
RouteHints [][]zpay32.HopHint
210219
}
211220

212221
// LoopInTerms are the server terms on which it executes loop in swaps.
@@ -253,6 +262,11 @@ type LoopInQuoteRequest struct {
253262
// RouteHints are optional route hints to reach the destination through
254263
// private channels.
255264
RouteHints [][]zpay32.HopHint
265+
266+
// Private indicates whether the destination node should be considered
267+
// private. In which case, loop will generate hophints to assist with
268+
// probing and payment.
269+
Private bool
256270
}
257271

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

loopd/swapclient_server.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -483,21 +483,26 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
483483
return nil, err
484484
}
485485

486-
var lastHop *route.Vertex
486+
var (
487+
routeHints [][]zpay32.HopHint
488+
lastHop *route.Vertex
489+
)
490+
487491
if req.LoopInLastHop != nil {
488492
lastHopVertex, err := route.NewVertexFromBytes(
489493
req.LoopInLastHop,
490494
)
491495
if err != nil {
492496
return nil, err
493497
}
494-
495498
lastHop = &lastHopVertex
496499
}
497500

498-
routeHints, err := unmarshallRouteHints(req.LoopInRouteHints)
499-
if err != nil {
500-
return nil, err
501+
if len(req.LoopInRouteHints) != 0 {
502+
routeHints, err = unmarshallRouteHints(req.LoopInRouteHints)
503+
if err != nil {
504+
return nil, err
505+
}
501506
}
502507

503508
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
@@ -506,10 +511,12 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
506511
ExternalHtlc: req.ExternalHtlc,
507512
LastHop: lastHop,
508513
RouteHints: routeHints,
514+
Private: req.Private,
509515
})
510516
if err != nil {
511517
return nil, err
512518
}
519+
513520
return &looprpc.InQuoteResponse{
514521
HtlcPublishFeeSat: int64(quote.MinerFee),
515522
SwapFeeSat: int64(quote.SwapFee),
@@ -613,6 +620,11 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
613620
return nil, err
614621
}
615622

623+
routeHints, err := unmarshallRouteHints(in.RouteHints)
624+
if err != nil {
625+
return nil, err
626+
}
627+
616628
req := &loop.LoopInRequest{
617629
Amount: btcutil.Amount(in.Amt),
618630
MaxMinerFee: btcutil.Amount(in.MaxMinerFee),
@@ -621,6 +633,8 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
621633
ExternalHtlc: in.ExternalHtlc,
622634
Label: in.Label,
623635
Initiator: in.Initiator,
636+
Private: in.Private,
637+
RouteHints: routeHints,
624638
}
625639
if in.LastHop != nil {
626640
lastHop, err := route.NewVertexFromBytes(in.LastHop)

loopin.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/lightningnetwork/lnd/lntypes"
2424
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2525
"github.com/lightningnetwork/lnd/lnwire"
26+
"github.com/lightningnetwork/lnd/routing/route"
2627
)
2728

2829
var (
@@ -80,6 +81,33 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
8081
currentHeight int32, request *LoopInRequest) (*loopInInitResult,
8182
error) {
8283

84+
var err error
85+
86+
// Private and routehints are mutually exclusive as setting private
87+
// means we retrieve our own routehints from the connected node.
88+
if len(request.RouteHints) != 0 && request.Private {
89+
return nil, fmt.Errorf("private and route_hints both set")
90+
}
91+
92+
// If Private is set, we generate route hints
93+
if request.Private {
94+
// If last_hop is set, we'll only add channels with peers
95+
// set to the last_hop parameter
96+
includeNodes := make(map[route.Vertex]struct{})
97+
if request.LastHop != nil {
98+
includeNodes[*request.LastHop] = struct{}{}
99+
}
100+
101+
// Because the Private flag is set, we'll generate our own
102+
// set of hop hints
103+
request.RouteHints, err = SelectHopHints(
104+
globalCtx, cfg.lnd, request.Amount, DefaultMaxHopHints, includeNodes,
105+
)
106+
if err != nil {
107+
return nil, err
108+
}
109+
}
110+
83111
// Request current server loop in terms and use these to calculate the
84112
// swap fee that we should subtract from the swap amount in the payment
85113
// request that we send to the server. We pass nil as optional route
@@ -89,7 +117,7 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
89117
// route hints.
90118
quote, err := cfg.server.GetLoopInQuote(
91119
globalCtx, request.Amount, cfg.lnd.NodePubkey, request.LastHop,
92-
nil,
120+
request.RouteHints,
93121
)
94122
if err != nil {
95123
return nil, wrapGrpcError("loop in terms", err)
@@ -129,10 +157,11 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
129157
// Create the swap invoice in lnd.
130158
_, swapInvoice, err := cfg.lnd.Client.AddInvoice(
131159
globalCtx, &invoicesrpc.AddInvoiceData{
132-
Preimage: &swapPreimage,
133-
Value: lnwire.NewMSatFromSatoshis(swapInvoiceAmt),
134-
Memo: "swap",
135-
Expiry: 3600 * 24 * 365,
160+
Preimage: &swapPreimage,
161+
Value: lnwire.NewMSatFromSatoshis(swapInvoiceAmt),
162+
Memo: "swap",
163+
Expiry: 3600 * 24 * 365,
164+
RouteHints: request.RouteHints,
136165
},
137166
)
138167
if err != nil {
@@ -148,10 +177,11 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
148177
log.Infof("Creating probe invoice %v", probeHash)
149178
probeInvoice, err := cfg.lnd.Invoices.AddHoldInvoice(
150179
globalCtx, &invoicesrpc.AddInvoiceData{
151-
Hash: &probeHash,
152-
Value: lnwire.NewMSatFromSatoshis(swapInvoiceAmt),
153-
Memo: "loop in probe",
154-
Expiry: 3600,
180+
Hash: &probeHash,
181+
Value: lnwire.NewMSatFromSatoshis(swapInvoiceAmt),
182+
Memo: "loop in probe",
183+
Expiry: 3600,
184+
RouteHints: request.RouteHints,
155185
},
156186
)
157187
if err != nil {

0 commit comments

Comments
 (0)