• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 19796036140

30 Nov 2025 08:09AM UTC coverage: 65.237% (+0.02%) from 65.215%
19796036140

Pull #10396

github

web-flow
Merge 44f7fdea2 into 0a2a5b29c
Pull Request #10396: Enhance Lsp Heuristic when probing a payment

201 of 240 new or added lines in 2 files covered. (83.75%)

83 existing lines in 18 files now uncovered.

137721 of 211108 relevant lines covered (65.24%)

20776.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.78
/lnrpc/routerrpc/router_server.go
1
package routerrpc
2

3
import (
4
        "context"
5
        crand "crypto/rand"
6
        "errors"
7
        "fmt"
8
        "os"
9
        "path/filepath"
10
        "sync/atomic"
11
        "time"
12

13
        "github.com/btcsuite/btcd/btcutil"
14
        "github.com/btcsuite/btcd/wire"
15
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
16
        "github.com/lightningnetwork/lnd/aliasmgr"
17
        "github.com/lightningnetwork/lnd/fn/v2"
18
        "github.com/lightningnetwork/lnd/lnrpc"
19
        "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
20
        "github.com/lightningnetwork/lnd/lntypes"
21
        "github.com/lightningnetwork/lnd/lnwire"
22
        "github.com/lightningnetwork/lnd/macaroons"
23
        paymentsdb "github.com/lightningnetwork/lnd/payments/db"
24
        "github.com/lightningnetwork/lnd/routing"
25
        "github.com/lightningnetwork/lnd/routing/route"
26
        "github.com/lightningnetwork/lnd/zpay32"
27
        "google.golang.org/grpc"
28
        "google.golang.org/grpc/codes"
29
        "google.golang.org/grpc/status"
30
        "gopkg.in/macaroon-bakery.v2/bakery"
31
)
32

33
const (
34
        // subServerName is the name of the sub rpc server. We'll use this name
35
        // to register ourselves, and we also require that the main
36
        // SubServerConfigDispatcher instance recognize as the name of our
37
        subServerName = "RouterRPC"
38

39
        // routeFeeLimitSat is the maximum routing fee that we allow to occur
40
        // when estimating a routing fee.
41
        routeFeeLimitSat = 100_000_000
42

43
        // DefaultPaymentTimeout is the default value of time we should spend
44
        // when attempting to fulfill the payment.
45
        DefaultPaymentTimeout int32 = 60
46
)
47

48
var (
49
        errServerShuttingDown = errors.New("routerrpc server shutting down")
50

51
        // ErrInterceptorAlreadyExists is an error returned when a new stream is
52
        // opened and there is already one active interceptor. The user must
53
        // disconnect prior to open another stream.
54
        ErrInterceptorAlreadyExists = errors.New("interceptor already exists")
55

56
        errMissingPaymentAttempt = errors.New("missing payment attempt")
57

58
        errMissingRoute = errors.New("missing route")
59

60
        errUnexpectedFailureSource = errors.New("unexpected failure source")
61

62
        // ErrAliasAlreadyExists is returned if a new SCID alias is attempted
63
        // to be added that already exists.
64
        ErrAliasAlreadyExists = errors.New("alias already exists")
65

66
        // ErrNoValidAlias is returned if an alias is not in the valid range for
67
        // allowed SCID aliases.
68
        ErrNoValidAlias = errors.New("not a valid alias")
69

70
        // macaroonOps are the set of capabilities that our minted macaroon (if
71
        // it doesn't already exist) will have.
72
        macaroonOps = []bakery.Op{
73
                {
74
                        Entity: "offchain",
75
                        Action: "read",
76
                },
77
                {
78
                        Entity: "offchain",
79
                        Action: "write",
80
                },
81
        }
82

83
        // macPermissions maps RPC calls to the permissions they require.
84
        macPermissions = map[string][]bakery.Op{
85
                "/routerrpc.Router/SendPaymentV2": {{
86
                        Entity: "offchain",
87
                        Action: "write",
88
                }},
89
                "/routerrpc.Router/SendToRouteV2": {{
90
                        Entity: "offchain",
91
                        Action: "write",
92
                }},
93
                "/routerrpc.Router/SendToRoute": {{
94
                        Entity: "offchain",
95
                        Action: "write",
96
                }},
97
                "/routerrpc.Router/TrackPaymentV2": {{
98
                        Entity: "offchain",
99
                        Action: "read",
100
                }},
101
                "/routerrpc.Router/TrackPayments": {{
102
                        Entity: "offchain",
103
                        Action: "read",
104
                }},
105
                "/routerrpc.Router/EstimateRouteFee": {{
106
                        Entity: "offchain",
107
                        Action: "read",
108
                }},
109
                "/routerrpc.Router/QueryMissionControl": {{
110
                        Entity: "offchain",
111
                        Action: "read",
112
                }},
113
                "/routerrpc.Router/XImportMissionControl": {{
114
                        Entity: "offchain",
115
                        Action: "write",
116
                }},
117
                "/routerrpc.Router/GetMissionControlConfig": {{
118
                        Entity: "offchain",
119
                        Action: "read",
120
                }},
121
                "/routerrpc.Router/SetMissionControlConfig": {{
122
                        Entity: "offchain",
123
                        Action: "write",
124
                }},
125
                "/routerrpc.Router/QueryProbability": {{
126
                        Entity: "offchain",
127
                        Action: "read",
128
                }},
129
                "/routerrpc.Router/ResetMissionControl": {{
130
                        Entity: "offchain",
131
                        Action: "write",
132
                }},
133
                "/routerrpc.Router/BuildRoute": {{
134
                        Entity: "offchain",
135
                        Action: "read",
136
                }},
137
                "/routerrpc.Router/SubscribeHtlcEvents": {{
138
                        Entity: "offchain",
139
                        Action: "read",
140
                }},
141
                "/routerrpc.Router/SendPayment": {{
142
                        Entity: "offchain",
143
                        Action: "write",
144
                }},
145
                "/routerrpc.Router/TrackPayment": {{
146
                        Entity: "offchain",
147
                        Action: "read",
148
                }},
149
                "/routerrpc.Router/HtlcInterceptor": {{
150
                        Entity: "offchain",
151
                        Action: "write",
152
                }},
153
                "/routerrpc.Router/UpdateChanStatus": {{
154
                        Entity: "offchain",
155
                        Action: "write",
156
                }},
157
                "/routerrpc.Router/XAddLocalChanAliases": {{
158
                        Entity: "offchain",
159
                        Action: "write",
160
                }},
161
                "/routerrpc.Router/XDeleteLocalChanAliases": {{
162
                        Entity: "offchain",
163
                        Action: "write",
164
                }},
165
        }
166

167
        // DefaultRouterMacFilename is the default name of the router macaroon
168
        // that we expect to find via a file handle within the main
169
        // configuration file in this package.
170
        DefaultRouterMacFilename = "router.macaroon"
171
)
172

173
// HasNode returns true if the node exists in the graph (i.e., has public
174
// channels), false otherwise.
175
type HasNode func(nodePub route.Vertex) (bool, error)
176

177
// ServerShell is a shell struct holding a reference to the actual sub-server.
178
// It is used to register the gRPC sub-server with the root server before we
179
// have the necessary dependencies to populate the actual sub-server.
180
type ServerShell struct {
181
        RouterServer
182
}
183

184
// Server is a stand-alone sub RPC server which exposes functionality that
185
// allows clients to route arbitrary payment through the Lightning Network.
186
type Server struct {
187
        started                  int32 // To be used atomically.
188
        shutdown                 int32 // To be used atomically.
189
        forwardInterceptorActive int32 // To be used atomically.
190

191
        // Required by the grpc-gateway/v2 library for forward compatibility.
192
        // Must be after the atomically used variables to not break struct
193
        // alignment.
194
        UnimplementedRouterServer
195

196
        cfg *Config
197

198
        quit chan struct{}
199
}
200

201
// A compile time check to ensure that Server fully implements the RouterServer
202
// gRPC service.
203
var _ RouterServer = (*Server)(nil)
204

205
// New creates a new instance of the RouterServer given a configuration struct
206
// that contains all external dependencies. If the target macaroon exists, and
207
// we're unable to create it, then an error will be returned. We also return
208
// the set of permissions that we require as a server. At the time of writing
209
// of this documentation, this is the same macaroon as the admin macaroon.
210
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
3✔
211
        // If the path of the router macaroon wasn't generated, then we'll
3✔
212
        // assume that it's found at the default network directory.
3✔
213
        if cfg.RouterMacPath == "" {
6✔
214
                cfg.RouterMacPath = filepath.Join(
3✔
215
                        cfg.NetworkDir, DefaultRouterMacFilename,
3✔
216
                )
3✔
217
        }
3✔
218

219
        // Now that we know the full path of the router macaroon, we can check
220
        // to see if we need to create it or not. If stateless_init is set
221
        // then we don't write the macaroons.
222
        macFilePath := cfg.RouterMacPath
3✔
223
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
3✔
224
                !lnrpc.FileExists(macFilePath) {
6✔
225

3✔
226
                log.Infof("Making macaroons for Router RPC Server at: %v",
3✔
227
                        macFilePath)
3✔
228

3✔
229
                // At this point, we know that the router macaroon doesn't yet,
3✔
230
                // exist, so we need to create it with the help of the main
3✔
231
                // macaroon service.
3✔
232
                routerMac, err := cfg.MacService.NewMacaroon(
3✔
233
                        context.Background(), macaroons.DefaultRootKeyID,
3✔
234
                        macaroonOps...,
3✔
235
                )
3✔
236
                if err != nil {
3✔
237
                        return nil, nil, err
×
238
                }
×
239
                routerMacBytes, err := routerMac.M().MarshalBinary()
3✔
240
                if err != nil {
3✔
241
                        return nil, nil, err
×
242
                }
×
243
                err = os.WriteFile(macFilePath, routerMacBytes, 0644)
3✔
244
                if err != nil {
3✔
245
                        _ = os.Remove(macFilePath)
×
246
                        return nil, nil, err
×
247
                }
×
248
        }
249

250
        routerServer := &Server{
3✔
251
                cfg:  cfg,
3✔
252
                quit: make(chan struct{}),
3✔
253
        }
3✔
254

3✔
255
        return routerServer, macPermissions, nil
3✔
256
}
257

258
// Start launches any helper goroutines required for the rpcServer to function.
259
//
260
// NOTE: This is part of the lnrpc.SubServer interface.
261
func (s *Server) Start() error {
3✔
262
        if atomic.AddInt32(&s.started, 1) != 1 {
3✔
263
                return nil
×
264
        }
×
265

266
        return nil
3✔
267
}
268

269
// Stop signals any active goroutines for a graceful closure.
270
//
271
// NOTE: This is part of the lnrpc.SubServer interface.
272
func (s *Server) Stop() error {
3✔
273
        if atomic.AddInt32(&s.shutdown, 1) != 1 {
3✔
274
                return nil
×
275
        }
×
276

277
        close(s.quit)
3✔
278
        return nil
3✔
279
}
280

281
// Name returns a unique string representation of the sub-server. This can be
282
// used to identify the sub-server and also de-duplicate them.
283
//
284
// NOTE: This is part of the lnrpc.SubServer interface.
285
func (s *Server) Name() string {
3✔
286
        return subServerName
3✔
287
}
3✔
288

289
// RegisterWithRootServer will be called by the root gRPC server to direct a
290
// sub RPC server to register itself with the main gRPC root server. Until this
291
// is called, each sub-server won't be able to have requests routed towards it.
292
//
293
// NOTE: This is part of the lnrpc.GrpcHandler interface.
294
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
3✔
295
        // We make sure that we register it with the main gRPC server to ensure
3✔
296
        // all our methods are routed properly.
3✔
297
        RegisterRouterServer(grpcServer, r)
3✔
298

3✔
299
        log.Debugf("Router RPC server successfully registered with root gRPC " +
3✔
300
                "server")
3✔
301

3✔
302
        return nil
3✔
303
}
3✔
304

305
// RegisterWithRestServer will be called by the root REST mux to direct a sub
306
// RPC server to register itself with the main REST mux server. Until this is
307
// called, each sub-server won't be able to have requests routed towards it.
308
//
309
// NOTE: This is part of the lnrpc.GrpcHandler interface.
310
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
311
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
3✔
312

3✔
313
        // We make sure that we register it with the main REST server to ensure
3✔
314
        // all our methods are routed properly.
3✔
315
        err := RegisterRouterHandlerFromEndpoint(ctx, mux, dest, opts)
3✔
316
        if err != nil {
3✔
317
                log.Errorf("Could not register Router REST server "+
×
318
                        "with root REST server: %v", err)
×
319
                return err
×
320
        }
×
321

322
        log.Debugf("Router REST server successfully registered with " +
3✔
323
                "root REST server")
3✔
324
        return nil
3✔
325
}
326

327
// CreateSubServer populates the subserver's dependencies using the passed
328
// SubServerConfigDispatcher. This method should fully initialize the
329
// sub-server instance, making it ready for action. It returns the macaroon
330
// permissions that the sub-server wishes to pass on to the root server for all
331
// methods routed towards it.
332
//
333
// NOTE: This is part of the lnrpc.GrpcHandler interface.
334
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
335
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
3✔
336

3✔
337
        subServer, macPermissions, err := createNewSubServer(configRegistry)
3✔
338
        if err != nil {
3✔
339
                return nil, nil, err
×
340
        }
×
341

342
        r.RouterServer = subServer
3✔
343
        return subServer, macPermissions, nil
3✔
344
}
345

346
// SendPaymentV2 attempts to route a payment described by the passed
347
// PaymentRequest to the final destination. If we are unable to route the
348
// payment, or cannot find a route that satisfies the constraints in the
349
// PaymentRequest, then an error will be returned. Otherwise, the payment
350
// pre-image, along with the final route will be returned.
351
func (s *Server) SendPaymentV2(req *SendPaymentRequest,
352
        stream Router_SendPaymentV2Server) error {
3✔
353

3✔
354
        // Set payment request attempt timeout.
3✔
355
        if req.TimeoutSeconds == 0 {
6✔
356
                req.TimeoutSeconds = DefaultPaymentTimeout
3✔
357
        }
3✔
358

359
        payment, err := s.cfg.RouterBackend.extractIntentFromSendRequest(req)
3✔
360
        if err != nil {
6✔
361
                return err
3✔
362
        }
3✔
363

364
        // Get the payment hash.
365
        payHash := payment.Identifier()
3✔
366

3✔
367
        // Init the payment in db.
3✔
368
        paySession, shardTracker, err := s.cfg.Router.PreparePayment(payment)
3✔
369
        if err != nil {
3✔
370
                log.Errorf("SendPayment async error for payment %x: %v",
×
371
                        payment.Identifier(), err)
×
372

×
373
                // Transform user errors to grpc code.
×
374
                if errors.Is(err, paymentsdb.ErrPaymentExists) ||
×
375
                        errors.Is(err, paymentsdb.ErrPaymentInFlight) ||
×
376
                        errors.Is(err, paymentsdb.ErrAlreadyPaid) {
×
377

×
378
                        return status.Error(
×
379
                                codes.AlreadyExists, err.Error(),
×
380
                        )
×
381
                }
×
382

383
                return err
×
384
        }
385

386
        // Subscribe to the payment before sending it to make sure we won't
387
        // miss events.
388
        sub, err := s.subscribePayment(payHash)
3✔
389
        if err != nil {
3✔
390
                return err
×
391
        }
×
392

393
        // The payment context is influenced by two user-provided parameters,
394
        // the cancelable flag and the payment attempt timeout.
395
        // If the payment is cancelable, we will use the stream context as the
396
        // payment context. That way, if the user ends the stream, the payment
397
        // loop will be canceled.
398
        // The second context parameter is the timeout. If the user provides a
399
        // timeout, we will additionally wrap the context in a deadline. If the
400
        // user provided 'cancelable' and ends the stream before the timeout is
401
        // reached the payment will be canceled.
402
        ctx := context.Background()
3✔
403
        if req.Cancelable {
6✔
404
                ctx = stream.Context()
3✔
405
        }
3✔
406

407
        // Send the payment asynchronously.
408
        s.cfg.Router.SendPaymentAsync(ctx, payment, paySession, shardTracker)
3✔
409

3✔
410
        // Track the payment and return.
3✔
411
        return s.trackPayment(sub, payHash, stream, req.NoInflightUpdates)
3✔
412
}
413

414
// EstimateRouteFee allows callers to obtain an expected value w.r.t how much it
415
// may cost to send an HTLC to the target end destination. This method sends
416
// probe payments to the target node, based on target invoice parameters and a
417
// random payment hash that makes it impossible for the target to settle the
418
// htlc. The probing stops if a user-provided timeout is reached. If provided
419
// with a destination key and amount, this method will perform a local graph
420
// based fee estimation.
421
func (s *Server) EstimateRouteFee(ctx context.Context,
422
        req *RouteFeeRequest) (*RouteFeeResponse, error) {
3✔
423

3✔
424
        isProbeDestination := len(req.Dest) > 0
3✔
425
        isProbeInvoice := len(req.PaymentRequest) > 0
3✔
426

3✔
427
        switch {
3✔
428
        case isProbeDestination == isProbeInvoice:
×
429
                return nil, errors.New("specify either a destination or an " +
×
430
                        "invoice")
×
431

432
        case isProbeDestination:
3✔
433
                switch {
3✔
434
                case len(req.Dest) != 33:
×
435
                        return nil, errors.New("invalid length destination key")
×
436

437
                case req.AmtSat <= 0:
×
438
                        return nil, errors.New("amount must be greater than 0")
×
439

440
                default:
3✔
441
                        return s.probeDestination(req.Dest, req.AmtSat)
3✔
442
                }
443

444
        case isProbeInvoice:
3✔
445
                return s.probePaymentRequest(
3✔
446
                        ctx, req.PaymentRequest, req.Timeout,
3✔
447
                )
3✔
448
        }
449

450
        return &RouteFeeResponse{}, nil
×
451
}
452

453
// probeDestination estimates fees along a route to a destination based on the
454
// contents of the local graph.
455
func (s *Server) probeDestination(dest []byte, amtSat int64) (*RouteFeeResponse,
456
        error) {
3✔
457

3✔
458
        destNode, err := route.NewVertexFromBytes(dest)
3✔
459
        if err != nil {
3✔
460
                return nil, err
×
461
        }
×
462

463
        // Next, we'll convert the amount in satoshis to mSAT, which are the
464
        // native unit of LN.
465
        amtMsat := lnwire.NewMSatFromSatoshis(btcutil.Amount(amtSat))
3✔
466

3✔
467
        // Finally, we'll query for a route to the destination that can carry
3✔
468
        // that target amount, we'll only request a single route. Set a
3✔
469
        // restriction for the default CLTV limit, otherwise we can find a route
3✔
470
        // that exceeds it and is useless to us.
3✔
471
        mc := s.cfg.RouterBackend.MissionControl
3✔
472
        routeReq, err := routing.NewRouteRequest(
3✔
473
                s.cfg.RouterBackend.SelfNode, &destNode, amtMsat, 0,
3✔
474
                &routing.RestrictParams{
3✔
475
                        FeeLimit:          routeFeeLimitSat,
3✔
476
                        CltvLimit:         s.cfg.RouterBackend.MaxTotalTimelock,
3✔
477
                        ProbabilitySource: mc.GetProbability,
3✔
478
                }, nil, nil, nil, s.cfg.RouterBackend.DefaultFinalCltvDelta,
3✔
479
        )
3✔
480
        if err != nil {
3✔
481
                return nil, err
×
482
        }
×
483

484
        route, _, err := s.cfg.Router.FindRoute(routeReq)
3✔
485
        if err != nil {
6✔
486
                return nil, err
3✔
487
        }
3✔
488

489
        // We are adding a block padding to the total time lock to account for
490
        // the safety buffer that the payment session will add to the last hop's
491
        // cltv delta. This is to prevent the htlc from failing if blocks are
492
        // mined while it is in flight.
493
        timeLockDelay := route.TotalTimeLock + uint32(routing.BlockPadding)
3✔
494

3✔
495
        return &RouteFeeResponse{
3✔
496
                RoutingFeeMsat: int64(route.TotalFees()),
3✔
497
                TimeLockDelay:  int64(timeLockDelay),
3✔
498
                FailureReason:  lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
3✔
499
        }, nil
3✔
500
}
501

502
// probePaymentRequest estimates fees along a route to a destination that is
503
// specified in an invoice. The estimation duration is limited by a timeout. In
504
// case that route hints are provided, this method applies a heuristic to
505
// identify LSPs which might block probe payments. In that case, fees are
506
// manually calculated and added to the probed fee estimation up until the LSP
507
// node. If the route hints don't indicate an LSP, they are passed as arguments
508
// to the SendPayment_V2 method, which enable it to send probe payments to the
509
// payment request destination.
510
//
511
// NOTE: Be aware that because of the special heuristic that is applied to
512
// identify LSPs, the probe payment might use a different node id as the
513
// final destination (the assumed LSP node id).
514
func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
515
        timeout uint32) (*RouteFeeResponse, error) {
3✔
516

3✔
517
        payReq, err := zpay32.Decode(
3✔
518
                paymentRequest, s.cfg.RouterBackend.ActiveNetParams,
3✔
519
        )
3✔
520
        if err != nil {
3✔
521
                return nil, err
×
522
        }
×
523

524
        if payReq.MilliSat == nil || *payReq.MilliSat <= 0 {
3✔
525
                return nil, errors.New("payment request amount must be " +
×
526
                        "greater than 0")
×
527
        }
×
528

529
        // Generate random payment hash, so we can be sure that the target of
530
        // the probe payment doesn't have the preimage to settle the htlc.
531
        var paymentHash lntypes.Hash
3✔
532
        _, err = crand.Read(paymentHash[:])
3✔
533
        if err != nil {
3✔
534
                return nil, fmt.Errorf("cannot generate random probe "+
×
535
                        "preimage: %w", err)
×
536
        }
×
537

538
        amtMsat := int64(*payReq.MilliSat)
3✔
539
        probeRequest := &SendPaymentRequest{
3✔
540
                TimeoutSeconds:   int32(timeout),
3✔
541
                Dest:             payReq.Destination.SerializeCompressed(),
3✔
542
                MaxParts:         1,
3✔
543
                AllowSelfPayment: false,
3✔
544
                AmtMsat:          amtMsat,
3✔
545
                PaymentHash:      paymentHash[:],
3✔
546
                FeeLimitSat:      routeFeeLimitSat,
3✔
547
                FinalCltvDelta:   int32(payReq.MinFinalCLTVExpiry()),
3✔
548
                DestFeatures:     MarshalFeatures(payReq.Features),
3✔
549
        }
3✔
550

3✔
551
        // If the payment addresses is specified, then we'll also populate that
3✔
552
        // now as well.
3✔
553
        payReq.PaymentAddr.WhenSome(func(addr [32]byte) {
6✔
554
                copy(probeRequest.PaymentAddr, addr[:])
3✔
555
        })
3✔
556

557
        hints := payReq.RouteHints
3✔
558

3✔
559
        // If the hints don't indicate an LSP then chances are that our probe
3✔
560
        // payment won't be blocked along the route to the destination. We send
3✔
561
        // a probe payment with unmodified route hints.
3✔
562
        invoiceTargetCompressed := payReq.Destination.SerializeCompressed()
3✔
563
        if !isLSP(hints, invoiceTargetCompressed, s.cfg.RouterBackend.HasNode) {
6✔
564
                log.Infof("No LSP detected, probing destination %x",
3✔
565
                        probeRequest.Dest)
3✔
566

3✔
567
                probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(hints)
3✔
568
                return s.sendProbePayment(ctx, probeRequest)
3✔
569
        }
3✔
570

571
        // If the heuristic indicates an LSP, we filter and group route hints by
572
        // public LSP nodes, then probe each unique LSP separately and return
573
        // the cheapest route.
574
        lspGroups, err := prepareLspRouteHints(
3✔
575
                hints, *payReq.MilliSat, s.cfg.RouterBackend.HasNode,
3✔
576
        )
3✔
577
        if err != nil {
3✔
578
                return nil, err
×
579
        }
×
580

581
        log.Infof("LSP detected, found %d unique public LSP node(s) to probe",
3✔
582
                len(lspGroups))
3✔
583

3✔
584
        // Probe up to 3 LSPs and track the most expensive route for worst-case
3✔
585
        // fee estimation. This is a precautionary measure to prevent the
3✔
586
        // estimation from taking too long and also a griefing protection.
3✔
587
        const maxLspsToProbe = 3
3✔
588

3✔
589
        if len(lspGroups) > maxLspsToProbe {
3✔
NEW
590
                log.Debugf("Limiting LSP probes from %d to %d for worst-case "+
×
NEW
591
                        "fee estimation", len(lspGroups), maxLspsToProbe)
×
UNCOV
592
        }
×
593
        var (
3✔
594
                worstCaseResp    *RouteFeeResponse
3✔
595
                worstCaseLspDest route.Vertex
3✔
596
                probeCount       int
3✔
597
        )
3✔
598

3✔
599
        for lspKey, group := range lspGroups {
6✔
600
                if probeCount >= maxLspsToProbe {
3✔
NEW
601
                        break
×
602
                }
603
                probeCount++
3✔
604

3✔
605
                lspHint := group.LspHopHint
3✔
606

3✔
607
                log.Infof("Probing LSP with destination: %v", lspKey)
3✔
608

3✔
609
                // Create a new probe request for this LSP.
3✔
610
                lspProbeRequest := &SendPaymentRequest{
3✔
611
                        TimeoutSeconds:   probeRequest.TimeoutSeconds,
3✔
612
                        Dest:             lspKey[:],
3✔
613
                        MaxParts:         probeRequest.MaxParts,
3✔
614
                        AllowSelfPayment: probeRequest.AllowSelfPayment,
3✔
615
                        AmtMsat:          amtMsat,
3✔
616
                        PaymentHash:      probeRequest.PaymentHash,
3✔
617
                        FeeLimitSat:      probeRequest.FeeLimitSat,
3✔
618
                        FinalCltvDelta:   int32(lspHint.CLTVExpiryDelta),
3✔
619
                        DestFeatures:     probeRequest.DestFeatures,
3✔
620
                }
3✔
621

3✔
622
                // Copy payment address if present.
3✔
623
                if len(probeRequest.PaymentAddr) > 0 {
3✔
NEW
624
                        copy(
×
NEW
625
                                lspProbeRequest.PaymentAddr,
×
NEW
626
                                probeRequest.PaymentAddr,
×
NEW
627
                        )
×
NEW
628
                }
×
629

630
                // Set the adjusted route hints for this LSP.
631
                if len(group.AdjustedRouteHints) > 0 {
3✔
NEW
632
                        lspProbeRequest.RouteHints = invoicesrpc.
×
NEW
633
                                CreateRPCRouteHints(
×
NEW
634
                                        group.AdjustedRouteHints,
×
NEW
635
                                )
×
NEW
636
                }
×
637

638
                // Calculate the hop fee for the last hop manually.
639
                hopFee := lspHint.HopFee(*payReq.MilliSat)
3✔
640

3✔
641
                // Add the last hop's fee to the probe amount.
3✔
642
                lspProbeRequest.AmtMsat += int64(hopFee)
3✔
643

3✔
644
                // Dispatch the payment probe for this LSP.
3✔
645
                resp, err := s.sendProbePayment(ctx, lspProbeRequest)
3✔
646
                if err != nil {
3✔
NEW
647
                        log.Warnf("Failed to probe LSP %v: %v", lspKey, err)
×
NEW
648
                        continue
×
649
                }
650

651
                // If the probe failed, skip this LSP.
652
                if resp.FailureReason !=
3✔
653
                        lnrpc.PaymentFailureReason_FAILURE_REASON_NONE {
3✔
NEW
654

×
NEW
655
                        log.Debugf("Probe to LSP %v failed with reason: %v",
×
NEW
656
                                lspKey, resp.FailureReason)
×
NEW
657

×
NEW
658
                        continue
×
659
                }
660

661
                // The probe succeeded, add the last hop's fee.
662
                resp.RoutingFeeMsat += int64(hopFee)
3✔
663

3✔
664
                // Add the final cltv delta of the invoice.
3✔
665
                resp.TimeLockDelay += int64(payReq.MinFinalCLTVExpiry())
3✔
666

3✔
667
                log.Infof("Probe to LSP %v succeeded with fee: %d msat",
3✔
668
                        lspKey, resp.RoutingFeeMsat)
3✔
669

3✔
670
                // Track the most expensive route for worst-case estimation.
3✔
671
                // We solely consider the routing fee for the worst-case
3✔
672
                // estimation.
3✔
673
                if worstCaseResp == nil ||
3✔
674
                        resp.RoutingFeeMsat > worstCaseResp.RoutingFeeMsat {
6✔
675

3✔
676
                        if worstCaseResp != nil {
6✔
677
                                log.Debugf("LSP %v has higher fee "+
3✔
678
                                        "(%d msat) than current worst-case "+
3✔
679
                                        "%v (%d msat), updating worst-case "+
3✔
680
                                        "estimate", lspKey,
3✔
681
                                        resp.RoutingFeeMsat, worstCaseLspDest,
3✔
682
                                        worstCaseResp.RoutingFeeMsat)
3✔
683
                        }
3✔
684

685
                        worstCaseResp = resp
3✔
686
                        worstCaseLspDest = lspKey
3✔
687
                } else {
3✔
688
                        log.Debugf("LSP %v fee (%d msat) is lower than "+
3✔
689
                                "current worst-case %v (%d msat), keeping "+
3✔
690
                                "worst-case estimate", lspKey,
3✔
691
                                resp.RoutingFeeMsat, worstCaseLspDest,
3✔
692
                                worstCaseResp.RoutingFeeMsat)
3✔
693
                }
3✔
694
        }
695

696
        // If no LSP probe succeeded, return an error.
697
        if worstCaseResp == nil {
3✔
NEW
698
                return nil, fmt.Errorf("all LSP probe payments failed")
×
NEW
699
        }
×
700

701
        log.Infof("Returning worst-case route via LSP %v with fee: %d msat, "+
3✔
702
                "timelock: %d", worstCaseLspDest, worstCaseResp.RoutingFeeMsat,
3✔
703
                worstCaseResp.TimeLockDelay)
3✔
704

3✔
705
        return worstCaseResp, nil
3✔
706
}
707

708
// isLSP checks if the route hints indicate an LSP. This function implements
709
// three rules:
710
//  1. If the invoice target is a public node (exists in graph) => isLsp = false
711
//     (can route directly to the target).
712
//  2. If at least one destination hop hint is public => isLsp = true.
713
//  3. If all destination hop hints are private nodes => isLsp = false.
714
func isLSP(routeHints [][]zpay32.HopHint, invoiceTarget []byte,
715
        hasNode HasNode) bool {
18✔
716

18✔
717
        if len(routeHints) == 0 || len(routeHints[0]) == 0 {
23✔
718
                log.Debugf("no route hints provided, this is not an LSP setup")
5✔
719
                return false
5✔
720
        }
5✔
721

722
        // Rule 1: If the invoice target is a public node (exists in the graph),
723
        // we can route directly to it, so it's not an LSP setup.
724
        if len(invoiceTarget) > 0 {
26✔
725
                var targetVertex route.Vertex
10✔
726
                copy(targetVertex[:], invoiceTarget)
10✔
727

10✔
728
                isPublic, err := hasNode(targetVertex)
10✔
729
                if err != nil {
10✔
NEW
730
                        log.Warnf("Failed to check if invoice target %x is "+
×
NEW
731
                                "public: %v", invoiceTarget, err)
×
NEW
732

×
NEW
733
                        return false
×
NEW
734
                }
×
735
                if isPublic {
16✔
736
                        log.Infof("invoice target %x is a public node in the"+
6✔
737
                                "graph, this is NOT an LSP setup",
6✔
738
                                invoiceTarget)
6✔
739

6✔
740
                        return false
6✔
741
                }
6✔
742
        }
743

744
        destHopHint := routeHints[0][len(routeHints[0])-1]
13✔
745
        destHopNodeCompressed := destHopHint.NodeID.SerializeCompressed()
13✔
746

13✔
747
        // Check if the first destination hop hint node is public.
13✔
748
        var destVertex route.Vertex
13✔
749
        copy(destVertex[:], destHopNodeCompressed)
13✔
750

13✔
751
        isPublic, err := hasNode(destVertex)
13✔
752
        if err != nil {
13✔
NEW
753
                log.Warnf("Failed to check if destination hop hint %x is "+
×
NEW
754
                        "public: %v", destHopNodeCompressed, err)
×
UNCOV
755

×
UNCOV
756
                return false
×
UNCOV
757
        }
×
758
        if isPublic {
21✔
759
                // Rule 2: At least one destination hop hint is a public node,
8✔
760
                // signal LSP.
8✔
761
                log.Infof("destination hop hint %x is a public node, "+
8✔
762
                        "this is an LSP setup", destHopNodeCompressed)
8✔
763

8✔
764
                return true
8✔
765
        }
8✔
766

767
        for i := 1; i < len(routeHints); i++ {
9✔
768
                // Skip empty route hints.
4✔
769
                if len(routeHints[i]) == 0 {
4✔
770
                        continue
×
771
                }
772

773
                lastHop := routeHints[i][len(routeHints[i])-1]
4✔
774
                lastHopNodeCompressed := lastHop.NodeID.SerializeCompressed()
4✔
775

4✔
776
                // Check if this destination hop hint node is public.
4✔
777
                // Rule 3: If we find a public node, we can exit early.
4✔
778
                var lastHopVertex route.Vertex
4✔
779
                copy(lastHopVertex[:], lastHopNodeCompressed)
4✔
780

4✔
781
                isPublic, err := hasNode(lastHopVertex)
4✔
782
                if err != nil {
4✔
NEW
783
                        log.Warnf("Failed to check if destination hop "+
×
NEW
784
                                "hint %x is public: %v", lastHopNodeCompressed,
×
NEW
785
                                err)
×
NEW
786

×
NEW
787
                        continue
×
788
                }
789
                if isPublic {
5✔
790
                        log.Infof("destination hop hint %x is a public node, "+
1✔
791
                                "this is an LSP setup", lastHopNodeCompressed)
1✔
792

1✔
793
                        return true
1✔
794
                }
1✔
795
        }
796

797
        // Rule 3: If all destination hop hints are private nodes (not in the
798
        // graph), this is not an LSP so we try to route directly to the
799
        // destination. .
800
        log.Infof("all dest hop hints are private, this is NOT an LSP setup")
4✔
801

4✔
802
        return false
4✔
803
}
804

805
// LspRouteGroup represents a group of route hints that share the same public
806
// LSP destination node. This is needed when probing LSPs separately to find
807
// the cheapest route.
808
type LspRouteGroup struct {
809
        // LspHopHint is the hop hint for the LSP node with worst-case fees and
810
        // CLTV delta.
811
        LspHopHint *zpay32.HopHint
812

813
        // AdjustedRouteHints are the route hints with the LSP hop stripped off.
814
        AdjustedRouteHints [][]zpay32.HopHint
815
}
816

817
// prepareLspRouteHints assumes that the isLsp heuristic returned true for the
818
// route hints passed in here. It filters route hints to only include those with
819
// public destination nodes, groups them by unique LSP node, and returns a map
820
// of LSP groups keyed by the LSP node's compressed public key.
821
func prepareLspRouteHints(routeHints [][]zpay32.HopHint,
822
        amt lnwire.MilliSatoshi,
823
        hasNode HasNode) (map[route.Vertex]*LspRouteGroup, error) {
12✔
824

12✔
825
        // This should never happen, but we check for it for completeness.
12✔
826
        // Because the isLSP heuristic already checked that the route hints are
12✔
827
        // not empty.
12✔
828
        if len(routeHints) == 0 {
13✔
829
                return nil, fmt.Errorf("no route hints provided")
1✔
830
        }
1✔
831

832
        // Map to group route hints by LSP node pubkey.
833
        lspGroups := make(map[route.Vertex]*LspRouteGroup)
11✔
834

11✔
835
        for _, routeHint := range routeHints {
29✔
836
                // Skip empty route hints.
18✔
837
                if len(routeHint) == 0 {
18✔
NEW
838
                        continue
×
839
                }
840

841
                // Get the destination hop hint (last hop in the route).
842
                destHop := routeHint[len(routeHint)-1]
18✔
843
                destNodeCompressed := destHop.NodeID.SerializeCompressed()
18✔
844

18✔
845
                // Check if this destination node is public.
18✔
846
                var destVertex route.Vertex
18✔
847
                copy(destVertex[:], destNodeCompressed)
18✔
848

18✔
849
                isPublic, err := hasNode(destVertex)
18✔
850
                if err != nil {
18✔
NEW
851
                        log.Warnf("Failed to check if dest hop hint %x is "+
×
NEW
852
                                "public: %v", destNodeCompressed, err)
×
NEW
853

×
NEW
854
                        continue
×
855
                }
856

857
                // Skip private destination nodes - we only probe public LSPs.
858
                if !isPublic {
22✔
859
                        log.Debugf("Skipping route hint with private dest "+
4✔
860
                                "node %x", destNodeCompressed)
4✔
861

4✔
862
                        continue
4✔
863
                }
864

865
                // Use the compressed pubkey as the map key.
866
                var lspKey route.Vertex
14✔
867
                copy(lspKey[:], destNodeCompressed)
14✔
868

14✔
869
                // Get or create the LSP group for this node.
14✔
870
                group, exists := lspGroups[lspKey]
14✔
871
                if !exists {
25✔
872
                        //nolint:ll
11✔
873
                        lspHop := zpay32.HopHint{
11✔
874
                                NodeID:                    destHop.NodeID,
11✔
875
                                ChannelID:                 destHop.ChannelID,
11✔
876
                                FeeBaseMSat:               destHop.FeeBaseMSat,
11✔
877
                                FeeProportionalMillionths: destHop.FeeProportionalMillionths,
11✔
878
                                CLTVExpiryDelta:           destHop.CLTVExpiryDelta,
11✔
879
                        }
11✔
880
                        group = &LspRouteGroup{
11✔
881
                                LspHopHint:         &lspHop,
11✔
882
                                AdjustedRouteHints: make([][]zpay32.HopHint, 0),
11✔
883
                        }
11✔
884
                        lspGroups[lspKey] = group
11✔
885
                }
11✔
886

887
                // Update the LSP hop hint with worst-case (max) fees and CLTV.
888
                hopFee := destHop.HopFee(amt)
14✔
889
                currentMaxFee := group.LspHopHint.HopFee(amt)
14✔
890
                if hopFee > currentMaxFee {
19✔
891
                        group.LspHopHint.FeeBaseMSat = destHop.FeeBaseMSat
5✔
892
                        group.LspHopHint.FeeProportionalMillionths = destHop.
5✔
893
                                FeeProportionalMillionths
5✔
894
                }
5✔
895

896
                if destHop.CLTVExpiryDelta > group.LspHopHint.CLTVExpiryDelta {
16✔
897
                        group.LspHopHint.CLTVExpiryDelta = destHop.
2✔
898
                                CLTVExpiryDelta
2✔
899
                }
2✔
900

901
                // Add the route hint with the LSP hop stripped off (if there
902
                // are hops before the LSP).
903
                if len(routeHint) > 1 {
24✔
904
                        group.AdjustedRouteHints = append(
10✔
905
                                group.AdjustedRouteHints,
10✔
906
                                routeHint[:len(routeHint)-1],
10✔
907
                        )
10✔
908
                }
10✔
909
        }
910

911
        if len(lspGroups) == 0 {
12✔
912
                return nil, fmt.Errorf("no public LSP nodes found in " +
1✔
913
                        "route hints")
1✔
914
        }
1✔
915

916
        log.Infof("Found %d unique public LSP node(s) in route hints",
10✔
917
                len(lspGroups))
10✔
918

10✔
919
        return lspGroups, nil
10✔
920
}
921

922
// probePaymentStream is a custom implementation of the grpc.ServerStream
923
// interface. It is used to send payment status updates to the caller on the
924
// stream channel.
925
type probePaymentStream struct {
926
        Router_SendPaymentV2Server
927

928
        stream chan *lnrpc.Payment
929
        ctx    context.Context //nolint:containedctx
930
}
931

932
// Send sends a payment status update to a payment stream that the caller can
933
// evaluate.
934
func (p *probePaymentStream) Send(response *lnrpc.Payment) error {
3✔
935
        select {
3✔
936
        case p.stream <- response:
3✔
937

938
        case <-p.ctx.Done():
×
939
                return p.ctx.Err()
×
940
        }
941

942
        return nil
3✔
943
}
944

945
// Context returns the context of the stream.
946
func (p *probePaymentStream) Context() context.Context {
3✔
947
        return p.ctx
3✔
948
}
3✔
949

950
// sendProbePayment sends a payment to a target node in order to obtain
951
// potential routing fees for it. The payment request has to contain a payment
952
// hash that is guaranteed to be unknown to the target node, so it cannot settle
953
// the payment. This method invokes a payment request loop in a goroutine and
954
// awaits payment status updates.
955
func (s *Server) sendProbePayment(ctx context.Context,
956
        req *SendPaymentRequest) (*RouteFeeResponse, error) {
3✔
957

3✔
958
        // We'll launch a goroutine to send the payment probes.
3✔
959
        errChan := make(chan error, 1)
3✔
960
        defer close(errChan)
3✔
961

3✔
962
        paymentStream := &probePaymentStream{
3✔
963
                stream: make(chan *lnrpc.Payment),
3✔
964
                ctx:    ctx,
3✔
965
        }
3✔
966
        go func() {
6✔
967
                err := s.SendPaymentV2(req, paymentStream)
3✔
968
                if err != nil {
3✔
969
                        select {
×
970
                        case errChan <- err:
×
971

972
                        case <-paymentStream.ctx.Done():
×
973
                                return
×
974
                        }
975
                }
976
        }()
977

978
        for {
6✔
979
                select {
3✔
980
                case payment := <-paymentStream.stream:
3✔
981
                        switch payment.Status {
3✔
982
                        case lnrpc.Payment_INITIATED:
×
983
                        case lnrpc.Payment_IN_FLIGHT:
3✔
984
                        case lnrpc.Payment_SUCCEEDED:
×
985
                                return nil, errors.New("warning, the fee " +
×
986
                                        "estimation payment probe " +
×
987
                                        "unexpectedly succeeded. Please reach" +
×
988
                                        "out to the probe destination to " +
×
989
                                        "negotiate a refund. Otherwise the " +
×
990
                                        "payment probe amount is lost forever")
×
991

992
                        case lnrpc.Payment_FAILED:
3✔
993
                                // Incorrect payment details point to a
3✔
994
                                // successful probe.
3✔
995
                                //nolint:ll
3✔
996
                                if payment.FailureReason == lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS {
6✔
997
                                        return paymentDetails(payment)
3✔
998
                                }
3✔
999

1000
                                return &RouteFeeResponse{
3✔
1001
                                        RoutingFeeMsat: 0,
3✔
1002
                                        TimeLockDelay:  0,
3✔
1003
                                        FailureReason:  payment.FailureReason,
3✔
1004
                                }, nil
3✔
1005

1006
                        default:
×
1007
                                return nil, errors.New("unexpected payment " +
×
1008
                                        "status")
×
1009
                        }
1010

1011
                case err := <-errChan:
×
1012
                        return nil, err
×
1013

1014
                case <-s.quit:
×
1015
                        return nil, errServerShuttingDown
×
1016
                }
1017
        }
1018
}
1019

1020
func paymentDetails(payment *lnrpc.Payment) (*RouteFeeResponse, error) {
3✔
1021
        fee, timeLock, err := timelockAndFee(payment)
3✔
1022
        if errors.Is(err, errUnexpectedFailureSource) {
3✔
1023
                return nil, err
×
1024
        }
×
1025

1026
        return &RouteFeeResponse{
3✔
1027
                RoutingFeeMsat: fee,
3✔
1028
                TimeLockDelay:  timeLock,
3✔
1029
                FailureReason:  lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
3✔
1030
        }, nil
3✔
1031
}
1032

1033
// timelockAndFee returns the fee and total time lock of the last payment
1034
// attempt.
1035
func timelockAndFee(p *lnrpc.Payment) (int64, int64, error) {
3✔
1036
        if len(p.Htlcs) == 0 {
3✔
1037
                return 0, 0, nil
×
1038
        }
×
1039

1040
        lastAttempt := p.Htlcs[len(p.Htlcs)-1]
3✔
1041
        if lastAttempt == nil {
3✔
1042
                return 0, 0, errMissingPaymentAttempt
×
1043
        }
×
1044

1045
        lastRoute := lastAttempt.Route
3✔
1046
        if lastRoute == nil {
3✔
1047
                return 0, 0, errMissingRoute
×
1048
        }
×
1049

1050
        hopFailureIndex := lastAttempt.Failure.FailureSourceIndex
3✔
1051
        finalHopIndex := uint32(len(lastRoute.Hops))
3✔
1052
        if hopFailureIndex != finalHopIndex {
3✔
1053
                return 0, 0, errUnexpectedFailureSource
×
1054
        }
×
1055

1056
        return lastRoute.TotalFeesMsat, int64(lastRoute.TotalTimeLock), nil
3✔
1057
}
1058

1059
// SendToRouteV2 sends a payment through a predefined route. The response of
1060
// this call contains structured error information.
1061
func (s *Server) SendToRouteV2(ctx context.Context,
1062
        req *SendToRouteRequest) (*lnrpc.HTLCAttempt, error) {
3✔
1063

3✔
1064
        if req.Route == nil {
3✔
1065
                return nil, fmt.Errorf("unable to send, no routes provided")
×
1066
        }
×
1067

1068
        route, err := s.cfg.RouterBackend.UnmarshallRoute(req.Route)
3✔
1069
        if err != nil {
3✔
1070
                return nil, err
×
1071
        }
×
1072

1073
        hash, err := lntypes.MakeHash(req.PaymentHash)
3✔
1074
        if err != nil {
3✔
1075
                return nil, err
×
1076
        }
×
1077

1078
        firstHopRecords := lnwire.CustomRecords(req.FirstHopCustomRecords)
3✔
1079
        if err := firstHopRecords.Validate(); err != nil {
3✔
1080
                return nil, err
×
1081
        }
×
1082

1083
        var attempt *paymentsdb.HTLCAttempt
3✔
1084

3✔
1085
        // Pass route to the router. This call returns the full htlc attempt
3✔
1086
        // information as it is stored in the database. It is possible that both
3✔
1087
        // the attempt return value and err are non-nil. This can happen when
3✔
1088
        // the attempt was already initiated before the error happened. In that
3✔
1089
        // case, we give precedence to the attempt information as stored in the
3✔
1090
        // db.
3✔
1091
        if req.SkipTempErr {
3✔
1092
                attempt, err = s.cfg.Router.SendToRouteSkipTempErr(
×
1093
                        hash, route, firstHopRecords,
×
1094
                )
×
1095
        } else {
3✔
1096
                attempt, err = s.cfg.Router.SendToRoute(
3✔
1097
                        hash, route, firstHopRecords,
3✔
1098
                )
3✔
1099
        }
3✔
1100
        if attempt != nil {
6✔
1101
                rpcAttempt, err := s.cfg.RouterBackend.MarshalHTLCAttempt(
3✔
1102
                        *attempt,
3✔
1103
                )
3✔
1104
                if err != nil {
3✔
1105
                        return nil, err
×
1106
                }
×
1107
                return rpcAttempt, nil
3✔
1108
        }
1109

1110
        // Transform user errors to grpc code.
1111
        switch {
×
1112
        case errors.Is(err, paymentsdb.ErrPaymentExists):
×
1113
                fallthrough
×
1114

1115
        case errors.Is(err, paymentsdb.ErrPaymentInFlight):
×
1116
                fallthrough
×
1117

1118
        case errors.Is(err, paymentsdb.ErrAlreadyPaid):
×
1119
                return nil, status.Error(
×
1120
                        codes.AlreadyExists, err.Error(),
×
1121
                )
×
1122
        }
1123

1124
        return nil, err
×
1125
}
1126

1127
// ResetMissionControl clears all mission control state and starts with a clean
1128
// slate.
1129
func (s *Server) ResetMissionControl(ctx context.Context,
1130
        req *ResetMissionControlRequest) (*ResetMissionControlResponse, error) {
3✔
1131

3✔
1132
        err := s.cfg.RouterBackend.MissionControl.ResetHistory()
3✔
1133
        if err != nil {
3✔
1134
                return nil, err
×
1135
        }
×
1136

1137
        return &ResetMissionControlResponse{}, nil
3✔
1138
}
1139

1140
// GetMissionControlConfig returns our current mission control config.
1141
func (s *Server) GetMissionControlConfig(ctx context.Context,
1142
        req *GetMissionControlConfigRequest) (*GetMissionControlConfigResponse,
1143
        error) {
3✔
1144

3✔
1145
        // Query the current mission control config.
3✔
1146
        cfg := s.cfg.RouterBackend.MissionControl.GetConfig()
3✔
1147
        resp := &GetMissionControlConfigResponse{
3✔
1148
                Config: &MissionControlConfig{
3✔
1149
                        MaximumPaymentResults: uint32(cfg.MaxMcHistory),
3✔
1150
                        MinimumFailureRelaxInterval: uint64(
3✔
1151
                                cfg.MinFailureRelaxInterval.Seconds(),
3✔
1152
                        ),
3✔
1153
                },
3✔
1154
        }
3✔
1155

3✔
1156
        // We only populate fields based on the current estimator.
3✔
1157
        switch v := cfg.Estimator.Config().(type) {
3✔
1158
        case routing.AprioriConfig:
3✔
1159
                resp.Config.Model = MissionControlConfig_APRIORI
3✔
1160
                aCfg := AprioriParameters{
3✔
1161
                        HalfLifeSeconds:  uint64(v.PenaltyHalfLife.Seconds()),
3✔
1162
                        HopProbability:   v.AprioriHopProbability,
3✔
1163
                        Weight:           v.AprioriWeight,
3✔
1164
                        CapacityFraction: v.CapacityFraction,
3✔
1165
                }
3✔
1166

3✔
1167
                // Populate deprecated fields.
3✔
1168
                resp.Config.HalfLifeSeconds = uint64(
3✔
1169
                        v.PenaltyHalfLife.Seconds(),
3✔
1170
                )
3✔
1171
                resp.Config.HopProbability = float32(v.AprioriHopProbability)
3✔
1172
                resp.Config.Weight = float32(v.AprioriWeight)
3✔
1173

3✔
1174
                resp.Config.EstimatorConfig = &MissionControlConfig_Apriori{
3✔
1175
                        Apriori: &aCfg,
3✔
1176
                }
3✔
1177

1178
        case routing.BimodalConfig:
3✔
1179
                resp.Config.Model = MissionControlConfig_BIMODAL
3✔
1180
                bCfg := BimodalParameters{
3✔
1181
                        NodeWeight: v.BimodalNodeWeight,
3✔
1182
                        ScaleMsat:  uint64(v.BimodalScaleMsat),
3✔
1183
                        DecayTime:  uint64(v.BimodalDecayTime.Seconds()),
3✔
1184
                }
3✔
1185

3✔
1186
                resp.Config.EstimatorConfig = &MissionControlConfig_Bimodal{
3✔
1187
                        Bimodal: &bCfg,
3✔
1188
                }
3✔
1189

1190
        default:
×
1191
                return nil, fmt.Errorf("unknown estimator config type %T", v)
×
1192
        }
1193

1194
        return resp, nil
3✔
1195
}
1196

1197
// SetMissionControlConfig sets parameters in the mission control config.
1198
func (s *Server) SetMissionControlConfig(ctx context.Context,
1199
        req *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse,
1200
        error) {
3✔
1201

3✔
1202
        mcCfg := &routing.MissionControlConfig{
3✔
1203
                MaxMcHistory: int(req.Config.MaximumPaymentResults),
3✔
1204
                MinFailureRelaxInterval: time.Duration(
3✔
1205
                        req.Config.MinimumFailureRelaxInterval,
3✔
1206
                ) * time.Second,
3✔
1207
        }
3✔
1208

3✔
1209
        switch req.Config.Model {
3✔
1210
        case MissionControlConfig_APRIORI:
3✔
1211
                var aprioriConfig routing.AprioriConfig
3✔
1212

3✔
1213
                // Determine the apriori config with backward compatibility
3✔
1214
                // should the api use deprecated fields.
3✔
1215
                switch v := req.Config.EstimatorConfig.(type) {
3✔
1216
                case *MissionControlConfig_Bimodal:
3✔
1217
                        return nil, fmt.Errorf("bimodal config " +
3✔
1218
                                "provided, but apriori model requested")
3✔
1219

1220
                case *MissionControlConfig_Apriori:
3✔
1221
                        aprioriConfig = routing.AprioriConfig{
3✔
1222
                                PenaltyHalfLife: time.Duration(
3✔
1223
                                        v.Apriori.HalfLifeSeconds,
3✔
1224
                                ) * time.Second,
3✔
1225
                                AprioriHopProbability: v.Apriori.HopProbability,
3✔
1226
                                AprioriWeight:         v.Apriori.Weight,
3✔
1227
                                CapacityFraction: v.Apriori.
3✔
1228
                                        CapacityFraction,
3✔
1229
                        }
3✔
1230

1231
                default:
3✔
1232
                        aprioriConfig = routing.AprioriConfig{
3✔
1233
                                PenaltyHalfLife: time.Duration(
3✔
1234
                                        int64(req.Config.HalfLifeSeconds),
3✔
1235
                                ) * time.Second,
3✔
1236
                                AprioriHopProbability: float64(
3✔
1237
                                        req.Config.HopProbability,
3✔
1238
                                ),
3✔
1239
                                AprioriWeight:    float64(req.Config.Weight),
3✔
1240
                                CapacityFraction: routing.DefaultCapacityFraction, //nolint:ll
3✔
1241
                        }
3✔
1242
                }
1243

1244
                estimator, err := routing.NewAprioriEstimator(aprioriConfig)
3✔
1245
                if err != nil {
3✔
1246
                        return nil, err
×
1247
                }
×
1248
                mcCfg.Estimator = estimator
3✔
1249

1250
        case MissionControlConfig_BIMODAL:
3✔
1251
                cfg, ok := req.Config.
3✔
1252
                        EstimatorConfig.(*MissionControlConfig_Bimodal)
3✔
1253
                if !ok {
3✔
1254
                        return nil, fmt.Errorf("bimodal estimator requested " +
×
1255
                                "but corresponding config not set")
×
1256
                }
×
1257
                bCfg := cfg.Bimodal
3✔
1258

3✔
1259
                bimodalConfig := routing.BimodalConfig{
3✔
1260
                        BimodalDecayTime: time.Duration(
3✔
1261
                                bCfg.DecayTime,
3✔
1262
                        ) * time.Second,
3✔
1263
                        BimodalScaleMsat:  lnwire.MilliSatoshi(bCfg.ScaleMsat),
3✔
1264
                        BimodalNodeWeight: bCfg.NodeWeight,
3✔
1265
                }
3✔
1266

3✔
1267
                estimator, err := routing.NewBimodalEstimator(bimodalConfig)
3✔
1268
                if err != nil {
3✔
1269
                        return nil, err
×
1270
                }
×
1271
                mcCfg.Estimator = estimator
3✔
1272

1273
        default:
×
1274
                return nil, fmt.Errorf("unknown estimator type %v",
×
1275
                        req.Config.Model)
×
1276
        }
1277

1278
        return &SetMissionControlConfigResponse{},
3✔
1279
                s.cfg.RouterBackend.MissionControl.SetConfig(mcCfg)
3✔
1280
}
1281

1282
// QueryMissionControl exposes the internal mission control state to callers. It
1283
// is a development feature.
1284
func (s *Server) QueryMissionControl(_ context.Context,
1285
        _ *QueryMissionControlRequest) (*QueryMissionControlResponse, error) {
×
1286

×
1287
        snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot()
×
1288

×
1289
        rpcPairs := make([]*PairHistory, 0, len(snapshot.Pairs))
×
1290
        for _, p := range snapshot.Pairs {
×
1291
                // Prevent binding to loop variable.
×
1292
                pair := p
×
1293

×
1294
                rpcPair := PairHistory{
×
1295
                        NodeFrom: pair.Pair.From[:],
×
1296
                        NodeTo:   pair.Pair.To[:],
×
1297
                        History:  toRPCPairData(&pair.TimedPairResult),
×
1298
                }
×
1299

×
1300
                rpcPairs = append(rpcPairs, &rpcPair)
×
1301
        }
×
1302

1303
        response := QueryMissionControlResponse{
×
1304
                Pairs: rpcPairs,
×
1305
        }
×
1306

×
1307
        return &response, nil
×
1308
}
1309

1310
// toRPCPairData marshalls mission control pair data to the rpc struct.
1311
func toRPCPairData(data *routing.TimedPairResult) *PairData {
3✔
1312
        rpcData := PairData{
3✔
1313
                FailAmtSat:     int64(data.FailAmt.ToSatoshis()),
3✔
1314
                FailAmtMsat:    int64(data.FailAmt),
3✔
1315
                SuccessAmtSat:  int64(data.SuccessAmt.ToSatoshis()),
3✔
1316
                SuccessAmtMsat: int64(data.SuccessAmt),
3✔
1317
        }
3✔
1318

3✔
1319
        if !data.FailTime.IsZero() {
6✔
1320
                rpcData.FailTime = data.FailTime.Unix()
3✔
1321
        }
3✔
1322

1323
        if !data.SuccessTime.IsZero() {
3✔
1324
                rpcData.SuccessTime = data.SuccessTime.Unix()
×
1325
        }
×
1326

1327
        return &rpcData
3✔
1328
}
1329

1330
// XImportMissionControl imports the state provided to our internal mission
1331
// control. Only entries that are fresher than our existing state will be used.
1332
func (s *Server) XImportMissionControl(_ context.Context,
1333
        req *XImportMissionControlRequest) (*XImportMissionControlResponse,
1334
        error) {
3✔
1335

3✔
1336
        if len(req.Pairs) == 0 {
3✔
1337
                return nil, errors.New("at least one pair required for import")
×
1338
        }
×
1339

1340
        snapshot := &routing.MissionControlSnapshot{
3✔
1341
                Pairs: make(
3✔
1342
                        []routing.MissionControlPairSnapshot, len(req.Pairs),
3✔
1343
                ),
3✔
1344
        }
3✔
1345

3✔
1346
        for i, pairResult := range req.Pairs {
6✔
1347
                pairSnapshot, err := toPairSnapshot(pairResult)
3✔
1348
                if err != nil {
6✔
1349
                        return nil, err
3✔
1350
                }
3✔
1351

1352
                snapshot.Pairs[i] = *pairSnapshot
3✔
1353
        }
1354

1355
        err := s.cfg.RouterBackend.MissionControl.ImportHistory(
3✔
1356
                snapshot, req.Force,
3✔
1357
        )
3✔
1358
        if err != nil {
3✔
1359
                return nil, err
×
1360
        }
×
1361

1362
        return &XImportMissionControlResponse{}, nil
3✔
1363
}
1364

1365
func toPairSnapshot(pairResult *PairHistory) (*routing.MissionControlPairSnapshot,
1366
        error) {
3✔
1367

3✔
1368
        from, err := route.NewVertexFromBytes(pairResult.NodeFrom)
3✔
1369
        if err != nil {
3✔
1370
                return nil, err
×
1371
        }
×
1372

1373
        to, err := route.NewVertexFromBytes(pairResult.NodeTo)
3✔
1374
        if err != nil {
3✔
1375
                return nil, err
×
1376
        }
×
1377

1378
        pairPrefix := fmt.Sprintf("pair: %v -> %v:", from, to)
3✔
1379

3✔
1380
        if from == to {
3✔
1381
                return nil, fmt.Errorf("%v source and destination node must "+
×
1382
                        "differ", pairPrefix)
×
1383
        }
×
1384

1385
        failAmt, failTime, err := getPair(
3✔
1386
                lnwire.MilliSatoshi(pairResult.History.FailAmtMsat),
3✔
1387
                btcutil.Amount(pairResult.History.FailAmtSat),
3✔
1388
                pairResult.History.FailTime,
3✔
1389
                true,
3✔
1390
        )
3✔
1391
        if err != nil {
6✔
1392
                return nil, fmt.Errorf("%v invalid failure: %w", pairPrefix,
3✔
1393
                        err)
3✔
1394
        }
3✔
1395

1396
        successAmt, successTime, err := getPair(
3✔
1397
                lnwire.MilliSatoshi(pairResult.History.SuccessAmtMsat),
3✔
1398
                btcutil.Amount(pairResult.History.SuccessAmtSat),
3✔
1399
                pairResult.History.SuccessTime,
3✔
1400
                false,
3✔
1401
        )
3✔
1402
        if err != nil {
3✔
1403
                return nil, fmt.Errorf("%v invalid success: %w", pairPrefix,
×
1404
                        err)
×
1405
        }
×
1406

1407
        if successAmt == 0 && failAmt == 0 {
3✔
1408
                return nil, fmt.Errorf("%v: either success or failure result "+
×
1409
                        "required", pairPrefix)
×
1410
        }
×
1411

1412
        pair := routing.NewDirectedNodePair(from, to)
3✔
1413

3✔
1414
        result := &routing.TimedPairResult{
3✔
1415
                FailAmt:     failAmt,
3✔
1416
                FailTime:    failTime,
3✔
1417
                SuccessAmt:  successAmt,
3✔
1418
                SuccessTime: successTime,
3✔
1419
        }
3✔
1420

3✔
1421
        return &routing.MissionControlPairSnapshot{
3✔
1422
                Pair:            pair,
3✔
1423
                TimedPairResult: *result,
3✔
1424
        }, nil
3✔
1425
}
1426

1427
// getPair validates the values provided for a mission control result and
1428
// returns the msat amount and timestamp for it. `isFailure` can be used to
1429
// default values to 0 instead of returning an error.
1430
func getPair(amtMsat lnwire.MilliSatoshi, amtSat btcutil.Amount,
1431
        timestamp int64, isFailure bool) (lnwire.MilliSatoshi, time.Time,
1432
        error) {
3✔
1433

3✔
1434
        amt, err := getMsatPairValue(amtMsat, amtSat)
3✔
1435
        if err != nil {
6✔
1436
                return 0, time.Time{}, err
3✔
1437
        }
3✔
1438

1439
        var (
3✔
1440
                timeSet   = timestamp != 0
3✔
1441
                amountSet = amt != 0
3✔
1442
        )
3✔
1443

3✔
1444
        switch {
3✔
1445
        // If a timestamp and amount if provided, return those values.
1446
        case timeSet && amountSet:
3✔
1447
                return amt, time.Unix(timestamp, 0), nil
3✔
1448

1449
        // Return an error if it does have a timestamp without an amount, and
1450
        // it's not expected to be a failure.
1451
        case !isFailure && timeSet && !amountSet:
×
1452
                return 0, time.Time{}, errors.New("non-zero timestamp " +
×
1453
                        "requires non-zero amount for success pairs")
×
1454

1455
        // Return an error if it does have an amount without a timestamp, and
1456
        // it's not expected to be a failure.
1457
        case !isFailure && !timeSet && amountSet:
×
1458
                return 0, time.Time{}, errors.New("non-zero amount for " +
×
1459
                        "success pairs requires non-zero timestamp")
×
1460

1461
        default:
3✔
1462
                return 0, time.Time{}, nil
3✔
1463
        }
1464
}
1465

1466
// getMsatPairValue checks the msat and sat values set for a pair and ensures
1467
// that the values provided are either the same, or only a single value is set.
1468
func getMsatPairValue(msatValue lnwire.MilliSatoshi,
1469
        satValue btcutil.Amount) (lnwire.MilliSatoshi, error) {
3✔
1470

3✔
1471
        // If our msat value converted to sats equals our sat value, we just
3✔
1472
        // return the msat value, since the values are the same.
3✔
1473
        if msatValue.ToSatoshis() == satValue {
6✔
1474
                return msatValue, nil
3✔
1475
        }
3✔
1476

1477
        // If we have no msatValue, we can just return our state value even if
1478
        // it is zero, because it's impossible that we have mismatched values.
1479
        if msatValue == 0 {
3✔
1480
                return lnwire.MilliSatoshi(satValue * 1000), nil
×
1481
        }
×
1482

1483
        // Likewise, we can just use msat value if we have no sat value set.
1484
        if satValue == 0 {
3✔
1485
                return msatValue, nil
×
1486
        }
×
1487

1488
        // If our values are non-zero but not equal, we have invalid amounts
1489
        // set, so we fail.
1490
        return 0, fmt.Errorf("msat: %v and sat: %v values not equal", msatValue,
3✔
1491
                satValue)
3✔
1492
}
1493

1494
// TrackPaymentV2 returns a stream of payment state updates. The stream is
1495
// closed when the payment completes.
1496
func (s *Server) TrackPaymentV2(request *TrackPaymentRequest,
1497
        stream Router_TrackPaymentV2Server) error {
3✔
1498

3✔
1499
        payHash, err := lntypes.MakeHash(request.PaymentHash)
3✔
1500
        if err != nil {
3✔
1501
                return err
×
1502
        }
×
1503

1504
        log.Debugf("TrackPayment called for payment %v", payHash)
3✔
1505

3✔
1506
        // Make the subscription.
3✔
1507
        sub, err := s.subscribePayment(payHash)
3✔
1508
        if err != nil {
3✔
1509
                return err
×
1510
        }
×
1511

1512
        return s.trackPayment(sub, payHash, stream, request.NoInflightUpdates)
3✔
1513
}
1514

1515
// subscribePayment subscribes to the payment updates for the given payment
1516
// hash.
1517
func (s *Server) subscribePayment(identifier lntypes.Hash) (
1518
        routing.ControlTowerSubscriber, error) {
3✔
1519

3✔
1520
        // Make the subscription.
3✔
1521
        router := s.cfg.RouterBackend
3✔
1522
        sub, err := router.Tower.SubscribePayment(identifier)
3✔
1523

3✔
1524
        switch {
3✔
1525
        case errors.Is(err, paymentsdb.ErrPaymentNotInitiated):
×
1526
                return nil, status.Error(codes.NotFound, err.Error())
×
1527

1528
        case err != nil:
×
1529
                return nil, err
×
1530
        }
1531

1532
        return sub, nil
3✔
1533
}
1534

1535
// trackPayment writes payment status updates to the provided stream.
1536
func (s *Server) trackPayment(subscription routing.ControlTowerSubscriber,
1537
        identifier lntypes.Hash, stream Router_TrackPaymentV2Server,
1538
        noInflightUpdates bool) error {
3✔
1539

3✔
1540
        err := s.trackPaymentStream(
3✔
1541
                stream.Context(), subscription, noInflightUpdates, stream.Send,
3✔
1542
        )
3✔
1543
        switch {
3✔
1544
        case err == nil:
3✔
1545
                return nil
3✔
1546

1547
        // If the context is canceled, we don't return an error.
1548
        case errors.Is(err, context.Canceled):
3✔
1549
                log.Infof("Payment stream %v canceled", identifier)
3✔
1550

3✔
1551
                return nil
3✔
1552

1553
        default:
×
1554
        }
1555

1556
        // Otherwise, we will log and return the error as the stream has
1557
        // received an error from the payment lifecycle.
1558
        log.Errorf("TrackPayment got error for payment %v: %v", identifier, err)
×
1559

×
1560
        return err
×
1561
}
1562

1563
// TrackPayments returns a stream of payment state updates.
1564
func (s *Server) TrackPayments(request *TrackPaymentsRequest,
1565
        stream Router_TrackPaymentsServer) error {
6✔
1566

6✔
1567
        log.Debug("TrackPayments called")
6✔
1568

6✔
1569
        router := s.cfg.RouterBackend
6✔
1570

6✔
1571
        // Subscribe to payments.
6✔
1572
        subscription, err := router.Tower.SubscribeAllPayments()
6✔
1573
        if err != nil {
6✔
1574
                return err
×
1575
        }
×
1576

1577
        // Stream updates to the client.
1578
        err = s.trackPaymentStream(
6✔
1579
                stream.Context(), subscription, request.NoInflightUpdates,
6✔
1580
                stream.Send,
6✔
1581
        )
6✔
1582

6✔
1583
        if errors.Is(err, context.Canceled) {
12✔
1584
                log.Debugf("TrackPayments payment stream canceled.")
6✔
1585
        }
6✔
1586

1587
        return err
6✔
1588
}
1589

1590
// trackPaymentStream streams payment updates to the client.
1591
func (s *Server) trackPaymentStream(context context.Context,
1592
        subscription routing.ControlTowerSubscriber, noInflightUpdates bool,
1593
        send func(*lnrpc.Payment) error) error {
6✔
1594

6✔
1595
        defer subscription.Close()
6✔
1596

6✔
1597
        // Stream updates back to the client.
6✔
1598
        for {
16✔
1599
                select {
10✔
1600
                case item, ok := <-subscription.Updates():
7✔
1601
                        if !ok {
10✔
1602
                                // No more payment updates.
3✔
1603
                                return nil
3✔
1604
                        }
3✔
1605
                        result, ok := item.(*paymentsdb.MPPayment)
7✔
1606
                        if !ok {
7✔
1607
                                return fmt.Errorf("unexpected payment type: %T",
×
1608
                                        item)
×
1609
                        }
×
1610

1611
                        log.Tracef("Payment %v updated to state %v",
7✔
1612
                                result.Info.PaymentIdentifier, result.Status)
7✔
1613

7✔
1614
                        // Skip in-flight updates unless requested.
7✔
1615
                        if noInflightUpdates {
12✔
1616
                                if result.Status == paymentsdb.StatusInitiated {
8✔
1617
                                        continue
3✔
1618
                                }
1619
                                if result.Status == paymentsdb.StatusInFlight {
9✔
1620
                                        continue
4✔
1621
                                }
1622
                        }
1623

1624
                        rpcPayment, err := s.cfg.RouterBackend.MarshallPayment(
6✔
1625
                                result,
6✔
1626
                        )
6✔
1627
                        if err != nil {
6✔
1628
                                return err
×
1629
                        }
×
1630

1631
                        // Send event to the client.
1632
                        err = send(rpcPayment)
6✔
1633
                        if err != nil {
6✔
1634
                                return err
×
1635
                        }
×
1636

1637
                case <-s.quit:
×
1638
                        return errServerShuttingDown
×
1639

1640
                case <-context.Done():
6✔
1641
                        return context.Err()
6✔
1642
                }
1643
        }
1644
}
1645

1646
// BuildRoute builds a route from a list of hop addresses.
1647
func (s *Server) BuildRoute(_ context.Context,
1648
        req *BuildRouteRequest) (*BuildRouteResponse, error) {
3✔
1649

3✔
1650
        if len(req.HopPubkeys) == 0 {
3✔
1651
                return nil, errors.New("no hops specified")
×
1652
        }
×
1653

1654
        // Unmarshall hop list.
1655
        hops := make([]route.Vertex, len(req.HopPubkeys))
3✔
1656
        for i, pubkeyBytes := range req.HopPubkeys {
6✔
1657
                pubkey, err := route.NewVertexFromBytes(pubkeyBytes)
3✔
1658
                if err != nil {
3✔
1659
                        return nil, err
×
1660
                }
×
1661
                hops[i] = pubkey
3✔
1662
        }
1663

1664
        // Prepare BuildRoute call parameters from rpc request.
1665
        var amt fn.Option[lnwire.MilliSatoshi]
3✔
1666
        if req.AmtMsat != 0 {
6✔
1667
                rpcAmt := lnwire.MilliSatoshi(req.AmtMsat)
3✔
1668
                amt = fn.Some(rpcAmt)
3✔
1669
        }
3✔
1670

1671
        var outgoingChan *uint64
3✔
1672
        if req.OutgoingChanId != 0 {
3✔
1673
                outgoingChan = &req.OutgoingChanId
×
1674
        }
×
1675

1676
        var payAddr fn.Option[[32]byte]
3✔
1677
        if len(req.PaymentAddr) != 0 {
6✔
1678
                var backingPayAddr [32]byte
3✔
1679
                copy(backingPayAddr[:], req.PaymentAddr)
3✔
1680

3✔
1681
                payAddr = fn.Some(backingPayAddr)
3✔
1682
        }
3✔
1683

1684
        if req.FinalCltvDelta == 0 {
3✔
1685
                req.FinalCltvDelta = int32(
×
1686
                        s.cfg.RouterBackend.DefaultFinalCltvDelta,
×
1687
                )
×
1688
        }
×
1689

1690
        var firstHopBlob fn.Option[[]byte]
3✔
1691
        if len(req.FirstHopCustomRecords) > 0 {
3✔
1692
                firstHopRecords := lnwire.CustomRecords(
×
1693
                        req.FirstHopCustomRecords,
×
1694
                )
×
1695
                if err := firstHopRecords.Validate(); err != nil {
×
1696
                        return nil, err
×
1697
                }
×
1698

1699
                firstHopData, err := firstHopRecords.Serialize()
×
1700
                if err != nil {
×
1701
                        return nil, err
×
1702
                }
×
1703
                firstHopBlob = fn.Some(firstHopData)
×
1704
        }
1705

1706
        // Build the route and return it to the caller.
1707
        route, err := s.cfg.Router.BuildRoute(
3✔
1708
                amt, hops, outgoingChan, req.FinalCltvDelta, payAddr,
3✔
1709
                firstHopBlob,
3✔
1710
        )
3✔
1711
        if err != nil {
3✔
1712
                return nil, err
×
1713
        }
×
1714

1715
        rpcRoute, err := s.cfg.RouterBackend.MarshallRoute(route)
3✔
1716
        if err != nil {
3✔
1717
                return nil, err
×
1718
        }
×
1719

1720
        routeResp := &BuildRouteResponse{
3✔
1721
                Route: rpcRoute,
3✔
1722
        }
3✔
1723

3✔
1724
        return routeResp, nil
3✔
1725
}
1726

1727
// SubscribeHtlcEvents creates a uni-directional stream from the server to
1728
// the client which delivers a stream of htlc events.
1729
func (s *Server) SubscribeHtlcEvents(_ *SubscribeHtlcEventsRequest,
1730
        stream Router_SubscribeHtlcEventsServer) error {
3✔
1731

3✔
1732
        htlcClient, err := s.cfg.RouterBackend.SubscribeHtlcEvents()
3✔
1733
        if err != nil {
3✔
1734
                return err
×
1735
        }
×
1736
        defer htlcClient.Cancel()
3✔
1737

3✔
1738
        // Send out an initial subscribed event so that the caller knows the
3✔
1739
        // point from which new events will be transmitted.
3✔
1740
        if err := stream.Send(&HtlcEvent{
3✔
1741
                Event: &HtlcEvent_SubscribedEvent{
3✔
1742
                        SubscribedEvent: &SubscribedEvent{},
3✔
1743
                },
3✔
1744
        }); err != nil {
3✔
1745
                return err
×
1746
        }
×
1747

1748
        for {
6✔
1749
                select {
3✔
1750
                case event := <-htlcClient.Updates():
3✔
1751
                        rpcEvent, err := rpcHtlcEvent(event)
3✔
1752
                        if err != nil {
3✔
1753
                                return err
×
1754
                        }
×
1755

1756
                        if err := stream.Send(rpcEvent); err != nil {
3✔
1757
                                return err
×
1758
                        }
×
1759

1760
                // If the stream's context is cancelled, return an error.
1761
                case <-stream.Context().Done():
3✔
1762
                        log.Debugf("htlc event stream cancelled")
3✔
1763
                        return stream.Context().Err()
3✔
1764

1765
                // If the subscribe client terminates, exit with an error.
1766
                case <-htlcClient.Quit():
×
1767
                        return errors.New("htlc event subscription terminated")
×
1768

1769
                // If the server has been signalled to shut down, exit.
1770
                case <-s.quit:
×
1771
                        return errServerShuttingDown
×
1772
                }
1773
        }
1774
}
1775

1776
// HtlcInterceptor is a bidirectional stream for streaming interception
1777
// requests to the caller.
1778
// Upon connection, it does the following:
1779
// 1. Check if there is already a live stream, if yes it rejects the request.
1780
// 2. Registered a ForwardInterceptor
1781
// 3. Delivers to the caller every √√ and detect his answer.
1782
// It uses a local implementation of holdForwardsStore to keep all the hold
1783
// forwards and find them when manual resolution is later needed.
1784
func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error {
3✔
1785
        // We ensure there is only one interceptor at a time.
3✔
1786
        if !atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 0, 1) {
3✔
1787
                return ErrInterceptorAlreadyExists
×
1788
        }
×
1789
        defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0)
3✔
1790

3✔
1791
        // Run the forward interceptor.
3✔
1792
        return newForwardInterceptor(
3✔
1793
                s.cfg.RouterBackend.InterceptableForwarder, stream,
3✔
1794
        ).run()
3✔
1795
}
1796

1797
// XAddLocalChanAliases is an experimental API that creates a set of new
1798
// channel SCID alias mappings. The final total set of aliases in the manager
1799
// after the add operation is returned. This is only a locally stored alias, and
1800
// will not be communicated to the channel peer via any message. Therefore,
1801
// routing over such an alias will only work if the peer also calls this same
1802
// RPC on their end. If an alias already exists, an error is returned.
1803
func (s *Server) XAddLocalChanAliases(_ context.Context,
1804
        in *AddAliasesRequest) (*AddAliasesResponse, error) {
3✔
1805

3✔
1806
        existingAliases := s.cfg.AliasMgr.ListAliases()
3✔
1807

3✔
1808
        // aliasExists checks if the new alias already exists in the alias map.
3✔
1809
        aliasExists := func(newAlias uint64,
3✔
1810
                baseScid lnwire.ShortChannelID) (bool, error) {
6✔
1811

3✔
1812
                // First check that we actually have a channel for the given
3✔
1813
                // base scid. This should succeed for any channel where the
3✔
1814
                // option-scid-alias feature bit was negotiated.
3✔
1815
                if _, ok := existingAliases[baseScid]; !ok {
3✔
1816
                        return false, fmt.Errorf("base scid %v not found",
×
1817
                                baseScid)
×
1818
                }
×
1819

1820
                for base, aliases := range existingAliases {
6✔
1821
                        for _, alias := range aliases {
6✔
1822
                                exists := alias.ToUint64() == newAlias
3✔
1823

3✔
1824
                                // Trying to add an alias that we already have
3✔
1825
                                // for another channel is wrong.
3✔
1826
                                if exists && base != baseScid {
3✔
1827
                                        return true, fmt.Errorf("%w: alias %v "+
×
1828
                                                "already exists for base scid "+
×
1829
                                                "%v", ErrAliasAlreadyExists,
×
1830
                                                alias, base)
×
1831
                                }
×
1832

1833
                                if exists {
6✔
1834
                                        return true, nil
3✔
1835
                                }
3✔
1836
                        }
1837
                }
1838

1839
                return false, nil
3✔
1840
        }
1841

1842
        for _, v := range in.AliasMaps {
6✔
1843
                baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid)
3✔
1844

3✔
1845
                for _, rpcAlias := range v.Aliases {
6✔
1846
                        // If not, let's add it to the alias manager now.
3✔
1847
                        aliasScid := lnwire.NewShortChanIDFromInt(rpcAlias)
3✔
1848

3✔
1849
                        // But we only add it, if it's a valid alias, as defined
3✔
1850
                        // by the BOLT spec.
3✔
1851
                        if !aliasmgr.IsAlias(aliasScid) {
6✔
1852
                                return nil, fmt.Errorf("%w: SCID alias %v is "+
3✔
1853
                                        "not a valid alias", ErrNoValidAlias,
3✔
1854
                                        aliasScid)
3✔
1855
                        }
3✔
1856

1857
                        exists, err := aliasExists(rpcAlias, baseScid)
3✔
1858
                        if err != nil {
3✔
1859
                                return nil, err
×
1860
                        }
×
1861

1862
                        // If the alias already exists, we see that as an error.
1863
                        // This is to avoid "silent" collisions.
1864
                        if exists {
6✔
1865
                                return nil, fmt.Errorf("%w: SCID alias %v "+
3✔
1866
                                        "already exists", ErrAliasAlreadyExists,
3✔
1867
                                        rpcAlias)
3✔
1868
                        }
3✔
1869

1870
                        // We set the baseLookup flag as we want the alias
1871
                        // manager to keep a mapping from the alias back to its
1872
                        // base scid, in order to be able to provide it via the
1873
                        // FindBaseLocalChanAlias RPC.
1874
                        err = s.cfg.AliasMgr.AddLocalAlias(
3✔
1875
                                aliasScid, baseScid, false, true,
3✔
1876
                                aliasmgr.WithBaseLookup(),
3✔
1877
                        )
3✔
1878
                        if err != nil {
3✔
1879
                                return nil, fmt.Errorf("error adding scid "+
×
1880
                                        "alias, base_scid=%v, alias_scid=%v: "+
×
1881
                                        "%w", baseScid, aliasScid, err)
×
1882
                        }
×
1883
                }
1884
        }
1885

1886
        return &AddAliasesResponse{
3✔
1887
                AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()),
3✔
1888
        }, nil
3✔
1889
}
1890

1891
// XDeleteLocalChanAliases is an experimental API that deletes a set of alias
1892
// mappings. The final total set of aliases in the manager after the delete
1893
// operation is returned. The deletion will not be communicated to the channel
1894
// peer via any message.
1895
func (s *Server) XDeleteLocalChanAliases(_ context.Context,
1896
        in *DeleteAliasesRequest) (*DeleteAliasesResponse,
1897
        error) {
3✔
1898

3✔
1899
        for _, v := range in.AliasMaps {
6✔
1900
                baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid)
3✔
1901

3✔
1902
                for _, alias := range v.Aliases {
6✔
1903
                        aliasScid := lnwire.NewShortChanIDFromInt(alias)
3✔
1904

3✔
1905
                        err := s.cfg.AliasMgr.DeleteLocalAlias(
3✔
1906
                                aliasScid, baseScid,
3✔
1907
                        )
3✔
1908
                        if err != nil {
3✔
1909
                                return nil, fmt.Errorf("error deleting scid "+
×
1910
                                        "alias, base_scid=%v, alias_scid=%v: "+
×
1911
                                        "%w", baseScid, aliasScid, err)
×
1912
                        }
×
1913
                }
1914
        }
1915

1916
        return &DeleteAliasesResponse{
3✔
1917
                AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()),
3✔
1918
        }, nil
3✔
1919
}
1920

1921
// XFindBaseLocalChanAlias is an experimental API that looks up the base scid
1922
// for a local chan alias that was registered.
1923
func (s *Server) XFindBaseLocalChanAlias(_ context.Context,
1924
        in *FindBaseAliasRequest) (*FindBaseAliasResponse, error) {
×
1925

×
1926
        aliasScid := lnwire.NewShortChanIDFromInt(in.Alias)
×
1927
        base, err := s.cfg.AliasMgr.FindBaseSCID(aliasScid)
×
1928
        if err != nil {
×
1929
                return nil, err
×
1930
        }
×
1931

1932
        return &FindBaseAliasResponse{
×
1933
                Base: base.ToUint64(),
×
1934
        }, nil
×
1935
}
1936

1937
func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) {
3✔
1938
        chanPoint := req.GetChanPoint()
3✔
1939
        txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
3✔
1940
        if err != nil {
3✔
1941
                return nil, err
×
1942
        }
×
1943
        index := chanPoint.OutputIndex
3✔
1944
        return wire.NewOutPoint(txid, index), nil
3✔
1945
}
1946

1947
// UpdateChanStatus allows channel state to be set manually.
1948
func (s *Server) UpdateChanStatus(_ context.Context,
1949
        req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) {
3✔
1950

3✔
1951
        outPoint, err := extractOutPoint(req)
3✔
1952
        if err != nil {
3✔
1953
                return nil, err
×
1954
        }
×
1955

1956
        action := req.GetAction()
3✔
1957

3✔
1958
        log.Debugf("UpdateChanStatus called for channel(%v) with "+
3✔
1959
                "action %v", outPoint, action)
3✔
1960

3✔
1961
        switch action {
3✔
1962
        case ChanStatusAction_ENABLE:
3✔
1963
                err = s.cfg.RouterBackend.SetChannelEnabled(*outPoint)
3✔
1964
        case ChanStatusAction_DISABLE:
3✔
1965
                err = s.cfg.RouterBackend.SetChannelDisabled(*outPoint)
3✔
1966
        case ChanStatusAction_AUTO:
3✔
1967
                err = s.cfg.RouterBackend.SetChannelAuto(*outPoint)
3✔
1968
        default:
×
1969
                err = fmt.Errorf("unrecognized ChannelStatusAction %v", action)
×
1970
        }
1971

1972
        if err != nil {
3✔
1973
                return nil, err
×
1974
        }
×
1975
        return &UpdateChanStatusResponse{}, nil
3✔
1976
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc