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

lightningnetwork / lnd / 13522615507

25 Feb 2025 01:40PM UTC coverage: 58.836% (+0.02%) from 58.815%
13522615507

Pull #9550

github

ellemouton
graph/db: move various cache write calls to ChannelGraph

Here, we move the graph cache writes for AddLightningNode,
DeleteLightningNode, AddChannelEdge and MarkEdgeLive to the
ChannelGraph. Since these are writes, the cache is only updated if the
DB write is successful.
Pull Request #9550: graph: extract cache from CRUD [3]

73 of 85 new or added lines in 1 file covered. (85.88%)

275 existing lines in 12 files now uncovered.

136412 of 231851 relevant lines covered (58.84%)

19316.27 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

193
        cfg *Config
194

195
        quit chan struct{}
196
}
197

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

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

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

3✔
223
                log.Infof("Making macaroons for Router RPC Server at: %v",
3✔
224
                        macFilePath)
3✔
225

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

247
        routerServer := &Server{
3✔
248
                cfg:  cfg,
3✔
249
                quit: make(chan struct{}),
3✔
250
        }
3✔
251

3✔
252
        return routerServer, macPermissions, nil
3✔
253
}
254

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

263
        return nil
3✔
264
}
265

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

274
        close(s.quit)
3✔
275
        return nil
3✔
276
}
277

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

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

3✔
296
        log.Debugf("Router RPC server successfully registered with root gRPC " +
3✔
297
                "server")
3✔
298

3✔
299
        return nil
3✔
300
}
3✔
301

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

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

319
        log.Debugf("Router REST server successfully registered with " +
3✔
320
                "root REST server")
3✔
321
        return nil
3✔
322
}
323

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

3✔
334
        subServer, macPermissions, err := createNewSubServer(configRegistry)
3✔
335
        if err != nil {
3✔
UNCOV
336
                return nil, nil, err
×
UNCOV
337
        }
×
338

339
        r.RouterServer = subServer
3✔
340
        return subServer, macPermissions, nil
3✔
341
}
342

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

3✔
351
        // Set payment request attempt timeout.
3✔
352
        if req.TimeoutSeconds == 0 {
6✔
353
                req.TimeoutSeconds = DefaultPaymentTimeout
3✔
354
        }
3✔
355

356
        payment, err := s.cfg.RouterBackend.extractIntentFromSendRequest(req)
3✔
357
        if err != nil {
3✔
UNCOV
358
                return err
×
UNCOV
359
        }
×
360

361
        // Get the payment hash.
362
        payHash := payment.Identifier()
3✔
363

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

×
UNCOV
370
                // Transform user errors to grpc code.
×
UNCOV
371
                if errors.Is(err, channeldb.ErrPaymentExists) ||
×
372
                        errors.Is(err, channeldb.ErrPaymentInFlight) ||
×
373
                        errors.Is(err, channeldb.ErrAlreadyPaid) {
×
374

×
375
                        return status.Error(
×
376
                                codes.AlreadyExists, err.Error(),
×
377
                        )
×
378
                }
×
379

380
                return err
×
381
        }
382

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

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

404
        // Send the payment asynchronously.
405
        s.cfg.Router.SendPaymentAsync(ctx, payment, paySession, shardTracker)
3✔
406

3✔
407
        // Track the payment and return.
3✔
408
        return s.trackPayment(sub, payHash, stream, req.NoInflightUpdates)
3✔
409
}
410

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

3✔
421
        isProbeDestination := len(req.Dest) > 0
3✔
422
        isProbeInvoice := len(req.PaymentRequest) > 0
3✔
423

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

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

UNCOV
434
                case req.AmtSat <= 0:
×
UNCOV
435
                        return nil, errors.New("amount must be greater than 0")
×
436

437
                default:
3✔
438
                        return s.probeDestination(req.Dest, req.AmtSat)
3✔
439
                }
440

441
        case isProbeInvoice:
3✔
442
                return s.probePaymentRequest(
3✔
443
                        ctx, req.PaymentRequest, req.Timeout,
3✔
444
                )
3✔
445
        }
446

UNCOV
447
        return &RouteFeeResponse{}, nil
×
448
}
449

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

3✔
455
        destNode, err := route.NewVertexFromBytes(dest)
3✔
456
        if err != nil {
3✔
UNCOV
457
                return nil, err
×
UNCOV
458
        }
×
459

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

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

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

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

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

499
// probePaymentRequest estimates fees along a route to a destination that is
500
// specified in an invoice. The estimation duration is limited by a timeout. In
501
// case that route hints are provided, this method applies a heuristic to
502
// identify LSPs which might block probe payments. In that case, fees are
503
// manually calculated and added to the probed fee estimation up until the LSP
504
// node. If the route hints don't indicate an LSP, they are passed as arguments
505
// to the SendPayment_V2 method, which enable it to send probe payments to the
506
// payment request destination.
507
func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
508
        timeout uint32) (*RouteFeeResponse, error) {
3✔
509

3✔
510
        payReq, err := zpay32.Decode(
3✔
511
                paymentRequest, s.cfg.RouterBackend.ActiveNetParams,
3✔
512
        )
3✔
513
        if err != nil {
3✔
UNCOV
514
                return nil, err
×
UNCOV
515
        }
×
516

517
        if *payReq.MilliSat <= 0 {
3✔
UNCOV
518
                return nil, errors.New("payment request amount must be " +
×
519
                        "greater than 0")
×
520
        }
×
521

522
        // Generate random payment hash, so we can be sure that the target of
523
        // the probe payment doesn't have the preimage to settle the htlc.
524
        var paymentHash lntypes.Hash
3✔
525
        _, err = crand.Read(paymentHash[:])
3✔
526
        if err != nil {
3✔
UNCOV
527
                return nil, fmt.Errorf("cannot generate random probe "+
×
UNCOV
528
                        "preimage: %w", err)
×
UNCOV
529
        }
×
530

531
        amtMsat := int64(*payReq.MilliSat)
3✔
532
        probeRequest := &SendPaymentRequest{
3✔
533
                TimeoutSeconds:   int32(timeout),
3✔
534
                Dest:             payReq.Destination.SerializeCompressed(),
3✔
535
                MaxParts:         1,
3✔
536
                AllowSelfPayment: false,
3✔
537
                AmtMsat:          amtMsat,
3✔
538
                PaymentHash:      paymentHash[:],
3✔
539
                FeeLimitSat:      routeFeeLimitSat,
3✔
540
                FinalCltvDelta:   int32(payReq.MinFinalCLTVExpiry()),
3✔
541
                DestFeatures:     MarshalFeatures(payReq.Features),
3✔
542
        }
3✔
543

3✔
544
        // If the payment addresses is specified, then we'll also populate that
3✔
545
        // now as well.
3✔
546
        payReq.PaymentAddr.WhenSome(func(addr [32]byte) {
6✔
547
                copy(probeRequest.PaymentAddr, addr[:])
3✔
548
        })
3✔
549

550
        hints := payReq.RouteHints
3✔
551

3✔
552
        // If the hints don't indicate an LSP then chances are that our probe
3✔
553
        // payment won't be blocked along the route to the destination. We send
3✔
554
        // a probe payment with unmodified route hints.
3✔
555
        if !isLSP(hints) {
6✔
556
                probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(hints)
3✔
557
                return s.sendProbePayment(ctx, probeRequest)
3✔
558
        }
3✔
559

560
        // If the heuristic indicates an LSP we modify the route hints to allow
561
        // probing the LSP.
562
        lspAdjustedRouteHints, lspHint, err := prepareLspRouteHints(
3✔
563
                hints, *payReq.MilliSat,
3✔
564
        )
3✔
565
        if err != nil {
3✔
UNCOV
566
                return nil, err
×
UNCOV
567
        }
×
568

569
        // The adjusted route hints serve the payment probe to find the last
570
        // public hop to the LSP on the route.
571
        probeRequest.Dest = lspHint.NodeID.SerializeCompressed()
3✔
572
        if len(lspAdjustedRouteHints) > 0 {
3✔
UNCOV
573
                probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(
×
UNCOV
574
                        lspAdjustedRouteHints,
×
UNCOV
575
                )
×
UNCOV
576
        }
×
577

578
        // The payment probe will be able to calculate the fee up until the LSP
579
        // node. The fee of the last hop has to be calculated manually. Since
580
        // the last hop's fee amount has to be sent across the payment path we
581
        // have to add it to the original payment amount. Only then will the
582
        // payment probe be able to determine the correct fee to the last hop
583
        // prior to the private destination. For example, if the user wants to
584
        // send 1000 sats to a private destination and the last hop's fee is 10
585
        // sats, then 1010 sats will have to arrive at the last hop. This means
586
        // that the probe has to be dispatched with 1010 sats to correctly
587
        // calculate the routing fee.
588
        //
589
        // Calculate the hop fee for the last hop manually.
590
        hopFee := lspHint.HopFee(*payReq.MilliSat)
3✔
591
        if err != nil {
3✔
UNCOV
592
                return nil, err
×
UNCOV
593
        }
×
594

595
        // Add the last hop's fee to the requested payment amount that we want
596
        // to get an estimate for.
597
        probeRequest.AmtMsat += int64(hopFee)
3✔
598

3✔
599
        // Use the hop hint's cltv delta as the payment request's final cltv
3✔
600
        // delta. The actual final cltv delta of the invoice will be added to
3✔
601
        // the payment probe's cltv delta.
3✔
602
        probeRequest.FinalCltvDelta = int32(lspHint.CLTVExpiryDelta)
3✔
603

3✔
604
        // Dispatch the payment probe with adjusted fee amount.
3✔
605
        resp, err := s.sendProbePayment(ctx, probeRequest)
3✔
606
        if err != nil {
3✔
UNCOV
607
                return nil, err
×
UNCOV
608
        }
×
609

610
        // If the payment probe failed we only return the failure reason and
611
        // leave the probe result params unaltered.
612
        if resp.FailureReason != lnrpc.PaymentFailureReason_FAILURE_REASON_NONE { //nolint:ll
3✔
613
                return resp, nil
×
UNCOV
614
        }
×
615

616
        // The probe succeeded, so we can add the last hop's fee to fee the
617
        // payment probe returned.
618
        resp.RoutingFeeMsat += int64(hopFee)
3✔
619

3✔
620
        // Add the final cltv delta of the invoice to the payment probe's total
3✔
621
        // cltv delta. This is the cltv delta for the hop behind the LSP.
3✔
622
        resp.TimeLockDelay += int64(payReq.MinFinalCLTVExpiry())
3✔
623

3✔
624
        return resp, nil
3✔
625
}
626

627
// isLSP checks if the route hints indicate an LSP. An LSP is indicated with
628
// true if the last node in each route hint has the same node id, false
629
// otherwise.
630
func isLSP(routeHints [][]zpay32.HopHint) bool {
3✔
631
        if len(routeHints) == 0 || len(routeHints[0]) == 0 {
6✔
632
                return false
3✔
633
        }
3✔
634

635
        refNodeID := routeHints[0][len(routeHints[0])-1].NodeID
3✔
636
        for i := 1; i < len(routeHints); i++ {
6✔
637
                // Skip empty route hints.
3✔
638
                if len(routeHints[i]) == 0 {
3✔
UNCOV
639
                        continue
×
640
                }
641

642
                lastHop := routeHints[i][len(routeHints[i])-1]
3✔
643
                idMatchesRefNode := bytes.Equal(
3✔
644
                        lastHop.NodeID.SerializeCompressed(),
3✔
645
                        refNodeID.SerializeCompressed(),
3✔
646
                )
3✔
647
                if !idMatchesRefNode {
6✔
648
                        return false
3✔
649
                }
3✔
650
        }
651

652
        return true
3✔
653
}
654

655
// prepareLspRouteHints assumes that the isLsp heuristic returned true for the
656
// route hints passed in here. It constructs a modified list of route hints that
657
// allows the caller to probe the LSP, which itself is returned as a separate
658
// hop hint.
659
func prepareLspRouteHints(routeHints [][]zpay32.HopHint,
660
        amt lnwire.MilliSatoshi) ([][]zpay32.HopHint, *zpay32.HopHint, error) {
3✔
661

3✔
662
        if len(routeHints) == 0 {
3✔
UNCOV
663
                return nil, nil, fmt.Errorf("no route hints provided")
×
UNCOV
664
        }
×
665

666
        // Create the LSP hop hint. We are probing for the worst case fee and
667
        // cltv delta. So we look for the max values amongst all LSP hop hints.
668
        refHint := routeHints[0][len(routeHints[0])-1]
3✔
669
        refHint.CLTVExpiryDelta = maxLspCltvDelta(routeHints)
3✔
670
        refHint.FeeBaseMSat, refHint.FeeProportionalMillionths = maxLspFee(
3✔
671
                routeHints, amt,
3✔
672
        )
3✔
673

3✔
674
        // We construct a modified list of route hints that allows the caller to
3✔
675
        // probe the LSP.
3✔
676
        adjustedHints := make([][]zpay32.HopHint, 0, len(routeHints))
3✔
677

3✔
678
        // Strip off the LSP hop hint from all route hints.
3✔
679
        for i := 0; i < len(routeHints); i++ {
6✔
680
                hint := routeHints[i]
3✔
681
                if len(hint) > 1 {
3✔
UNCOV
682
                        adjustedHints = append(
×
UNCOV
683
                                adjustedHints, hint[:len(hint)-1],
×
UNCOV
684
                        )
×
UNCOV
685
                }
×
686
        }
687

688
        return adjustedHints, &refHint, nil
3✔
689
}
690

691
// maxLspFee returns base fee and fee rate amongst all LSP route hints that
692
// results in the overall highest fee for the given amount.
693
func maxLspFee(routeHints [][]zpay32.HopHint, amt lnwire.MilliSatoshi) (uint32,
694
        uint32) {
3✔
695

3✔
696
        var maxFeePpm uint32
3✔
697
        var maxBaseFee uint32
3✔
698
        var maxTotalFee lnwire.MilliSatoshi
3✔
699
        for _, rh := range routeHints {
6✔
700
                lastHop := rh[len(rh)-1]
3✔
701
                lastHopFee := lastHop.HopFee(amt)
3✔
702
                if lastHopFee > maxTotalFee {
6✔
703
                        maxTotalFee = lastHopFee
3✔
704
                        maxBaseFee = lastHop.FeeBaseMSat
3✔
705
                        maxFeePpm = lastHop.FeeProportionalMillionths
3✔
706
                }
3✔
707
        }
708

709
        return maxBaseFee, maxFeePpm
3✔
710
}
711

712
// maxLspCltvDelta returns the maximum cltv delta amongst all LSP route hints.
713
func maxLspCltvDelta(routeHints [][]zpay32.HopHint) uint16 {
3✔
714
        var maxCltvDelta uint16
3✔
715
        for _, rh := range routeHints {
6✔
716
                rhLastHop := rh[len(rh)-1]
3✔
717
                if rhLastHop.CLTVExpiryDelta > maxCltvDelta {
6✔
718
                        maxCltvDelta = rhLastHop.CLTVExpiryDelta
3✔
719
                }
3✔
720
        }
721

722
        return maxCltvDelta
3✔
723
}
724

725
// probePaymentStream is a custom implementation of the grpc.ServerStream
726
// interface. It is used to send payment status updates to the caller on the
727
// stream channel.
728
type probePaymentStream struct {
729
        Router_SendPaymentV2Server
730

731
        stream chan *lnrpc.Payment
732
        ctx    context.Context //nolint:containedctx
733
}
734

735
// Send sends a payment status update to a payment stream that the caller can
736
// evaluate.
737
func (p *probePaymentStream) Send(response *lnrpc.Payment) error {
3✔
738
        select {
3✔
739
        case p.stream <- response:
3✔
740

UNCOV
741
        case <-p.ctx.Done():
×
UNCOV
742
                return p.ctx.Err()
×
743
        }
744

745
        return nil
3✔
746
}
747

748
// Context returns the context of the stream.
749
func (p *probePaymentStream) Context() context.Context {
3✔
750
        return p.ctx
3✔
751
}
3✔
752

753
// sendProbePayment sends a payment to a target node in order to obtain
754
// potential routing fees for it. The payment request has to contain a payment
755
// hash that is guaranteed to be unknown to the target node, so it cannot settle
756
// the payment. This method invokes a payment request loop in a goroutine and
757
// awaits payment status updates.
758
func (s *Server) sendProbePayment(ctx context.Context,
759
        req *SendPaymentRequest) (*RouteFeeResponse, error) {
3✔
760

3✔
761
        // We'll launch a goroutine to send the payment probes.
3✔
762
        errChan := make(chan error, 1)
3✔
763
        defer close(errChan)
3✔
764

3✔
765
        paymentStream := &probePaymentStream{
3✔
766
                stream: make(chan *lnrpc.Payment),
3✔
767
                ctx:    ctx,
3✔
768
        }
3✔
769
        go func() {
6✔
770
                err := s.SendPaymentV2(req, paymentStream)
3✔
771
                if err != nil {
3✔
772
                        select {
×
UNCOV
773
                        case errChan <- err:
×
774

UNCOV
775
                        case <-paymentStream.ctx.Done():
×
UNCOV
776
                                return
×
777
                        }
778
                }
779
        }()
780

781
        for {
6✔
782
                select {
3✔
783
                case payment := <-paymentStream.stream:
3✔
784
                        switch payment.Status {
3✔
UNCOV
785
                        case lnrpc.Payment_INITIATED:
×
786
                        case lnrpc.Payment_IN_FLIGHT:
3✔
UNCOV
787
                        case lnrpc.Payment_SUCCEEDED:
×
UNCOV
788
                                return nil, errors.New("warning, the fee " +
×
UNCOV
789
                                        "estimation payment probe " +
×
UNCOV
790
                                        "unexpectedly succeeded. Please reach" +
×
UNCOV
791
                                        "out to the probe destination to " +
×
UNCOV
792
                                        "negotiate a refund. Otherwise the " +
×
UNCOV
793
                                        "payment probe amount is lost forever")
×
794

795
                        case lnrpc.Payment_FAILED:
3✔
796
                                // Incorrect payment details point to a
3✔
797
                                // successful probe.
3✔
798
                                //nolint:ll
3✔
799
                                if payment.FailureReason == lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS {
6✔
800
                                        return paymentDetails(payment)
3✔
801
                                }
3✔
802

803
                                return &RouteFeeResponse{
3✔
804
                                        RoutingFeeMsat: 0,
3✔
805
                                        TimeLockDelay:  0,
3✔
806
                                        FailureReason:  payment.FailureReason,
3✔
807
                                }, nil
3✔
808

UNCOV
809
                        default:
×
UNCOV
810
                                return nil, errors.New("unexpected payment " +
×
UNCOV
811
                                        "status")
×
812
                        }
813

UNCOV
814
                case err := <-errChan:
×
815
                        return nil, err
×
816

817
                case <-s.quit:
×
818
                        return nil, errServerShuttingDown
×
819
                }
820
        }
821
}
822

823
func paymentDetails(payment *lnrpc.Payment) (*RouteFeeResponse, error) {
3✔
824
        fee, timeLock, err := timelockAndFee(payment)
3✔
825
        if errors.Is(err, errUnexpectedFailureSource) {
3✔
UNCOV
826
                return nil, err
×
UNCOV
827
        }
×
828

829
        return &RouteFeeResponse{
3✔
830
                RoutingFeeMsat: fee,
3✔
831
                TimeLockDelay:  timeLock,
3✔
832
                FailureReason:  lnrpc.PaymentFailureReason_FAILURE_REASON_NONE,
3✔
833
        }, nil
3✔
834
}
835

836
// timelockAndFee returns the fee and total time lock of the last payment
837
// attempt.
838
func timelockAndFee(p *lnrpc.Payment) (int64, int64, error) {
3✔
839
        if len(p.Htlcs) == 0 {
3✔
840
                return 0, 0, nil
×
841
        }
×
842

843
        lastAttempt := p.Htlcs[len(p.Htlcs)-1]
3✔
844
        if lastAttempt == nil {
3✔
845
                return 0, 0, errMissingPaymentAttempt
×
UNCOV
846
        }
×
847

848
        lastRoute := lastAttempt.Route
3✔
849
        if lastRoute == nil {
3✔
UNCOV
850
                return 0, 0, errMissingRoute
×
UNCOV
851
        }
×
852

853
        hopFailureIndex := lastAttempt.Failure.FailureSourceIndex
3✔
854
        finalHopIndex := uint32(len(lastRoute.Hops))
3✔
855
        if hopFailureIndex != finalHopIndex {
3✔
856
                return 0, 0, errUnexpectedFailureSource
×
857
        }
×
858

859
        return lastRoute.TotalFeesMsat, int64(lastRoute.TotalTimeLock), nil
3✔
860
}
861

862
// SendToRouteV2 sends a payment through a predefined route. The response of
863
// this call contains structured error information.
864
func (s *Server) SendToRouteV2(ctx context.Context,
865
        req *SendToRouteRequest) (*lnrpc.HTLCAttempt, error) {
3✔
866

3✔
867
        if req.Route == nil {
3✔
UNCOV
868
                return nil, fmt.Errorf("unable to send, no routes provided")
×
UNCOV
869
        }
×
870

871
        route, err := s.cfg.RouterBackend.UnmarshallRoute(req.Route)
3✔
872
        if err != nil {
3✔
UNCOV
873
                return nil, err
×
UNCOV
874
        }
×
875

876
        hash, err := lntypes.MakeHash(req.PaymentHash)
3✔
877
        if err != nil {
3✔
UNCOV
878
                return nil, err
×
UNCOV
879
        }
×
880

881
        firstHopRecords := lnwire.CustomRecords(req.FirstHopCustomRecords)
3✔
882
        if err := firstHopRecords.Validate(); err != nil {
3✔
UNCOV
883
                return nil, err
×
UNCOV
884
        }
×
885

886
        var attempt *channeldb.HTLCAttempt
3✔
887

3✔
888
        // Pass route to the router. This call returns the full htlc attempt
3✔
889
        // information as it is stored in the database. It is possible that both
3✔
890
        // the attempt return value and err are non-nil. This can happen when
3✔
891
        // the attempt was already initiated before the error happened. In that
3✔
892
        // case, we give precedence to the attempt information as stored in the
3✔
893
        // db.
3✔
894
        if req.SkipTempErr {
3✔
UNCOV
895
                attempt, err = s.cfg.Router.SendToRouteSkipTempErr(
×
UNCOV
896
                        hash, route, firstHopRecords,
×
UNCOV
897
                )
×
898
        } else {
3✔
899
                attempt, err = s.cfg.Router.SendToRoute(
3✔
900
                        hash, route, firstHopRecords,
3✔
901
                )
3✔
902
        }
3✔
903
        if attempt != nil {
6✔
904
                rpcAttempt, err := s.cfg.RouterBackend.MarshalHTLCAttempt(
3✔
905
                        *attempt,
3✔
906
                )
3✔
907
                if err != nil {
3✔
908
                        return nil, err
×
909
                }
×
910
                return rpcAttempt, nil
3✔
911
        }
912

913
        // Transform user errors to grpc code.
914
        switch {
×
UNCOV
915
        case errors.Is(err, channeldb.ErrPaymentExists):
×
UNCOV
916
                fallthrough
×
917

UNCOV
918
        case errors.Is(err, channeldb.ErrPaymentInFlight):
×
UNCOV
919
                fallthrough
×
920

UNCOV
921
        case errors.Is(err, channeldb.ErrAlreadyPaid):
×
UNCOV
922
                return nil, status.Error(
×
UNCOV
923
                        codes.AlreadyExists, err.Error(),
×
UNCOV
924
                )
×
925
        }
926

927
        return nil, err
×
928
}
929

930
// ResetMissionControl clears all mission control state and starts with a clean
931
// slate.
932
func (s *Server) ResetMissionControl(ctx context.Context,
933
        req *ResetMissionControlRequest) (*ResetMissionControlResponse, error) {
3✔
934

3✔
935
        err := s.cfg.RouterBackend.MissionControl.ResetHistory()
3✔
936
        if err != nil {
3✔
UNCOV
937
                return nil, err
×
938
        }
×
939

940
        return &ResetMissionControlResponse{}, nil
3✔
941
}
942

943
// GetMissionControlConfig returns our current mission control config.
944
func (s *Server) GetMissionControlConfig(ctx context.Context,
945
        req *GetMissionControlConfigRequest) (*GetMissionControlConfigResponse,
946
        error) {
3✔
947

3✔
948
        // Query the current mission control config.
3✔
949
        cfg := s.cfg.RouterBackend.MissionControl.GetConfig()
3✔
950
        resp := &GetMissionControlConfigResponse{
3✔
951
                Config: &MissionControlConfig{
3✔
952
                        MaximumPaymentResults: uint32(cfg.MaxMcHistory),
3✔
953
                        MinimumFailureRelaxInterval: uint64(
3✔
954
                                cfg.MinFailureRelaxInterval.Seconds(),
3✔
955
                        ),
3✔
956
                },
3✔
957
        }
3✔
958

3✔
959
        // We only populate fields based on the current estimator.
3✔
960
        switch v := cfg.Estimator.Config().(type) {
3✔
961
        case routing.AprioriConfig:
3✔
962
                resp.Config.Model = MissionControlConfig_APRIORI
3✔
963
                aCfg := AprioriParameters{
3✔
964
                        HalfLifeSeconds:  uint64(v.PenaltyHalfLife.Seconds()),
3✔
965
                        HopProbability:   v.AprioriHopProbability,
3✔
966
                        Weight:           v.AprioriWeight,
3✔
967
                        CapacityFraction: v.CapacityFraction,
3✔
968
                }
3✔
969

3✔
970
                // Populate deprecated fields.
3✔
971
                resp.Config.HalfLifeSeconds = uint64(
3✔
972
                        v.PenaltyHalfLife.Seconds(),
3✔
973
                )
3✔
974
                resp.Config.HopProbability = float32(v.AprioriHopProbability)
3✔
975
                resp.Config.Weight = float32(v.AprioriWeight)
3✔
976

3✔
977
                resp.Config.EstimatorConfig = &MissionControlConfig_Apriori{
3✔
978
                        Apriori: &aCfg,
3✔
979
                }
3✔
980

981
        case routing.BimodalConfig:
3✔
982
                resp.Config.Model = MissionControlConfig_BIMODAL
3✔
983
                bCfg := BimodalParameters{
3✔
984
                        NodeWeight: v.BimodalNodeWeight,
3✔
985
                        ScaleMsat:  uint64(v.BimodalScaleMsat),
3✔
986
                        DecayTime:  uint64(v.BimodalDecayTime.Seconds()),
3✔
987
                }
3✔
988

3✔
989
                resp.Config.EstimatorConfig = &MissionControlConfig_Bimodal{
3✔
990
                        Bimodal: &bCfg,
3✔
991
                }
3✔
992

UNCOV
993
        default:
×
UNCOV
994
                return nil, fmt.Errorf("unknown estimator config type %T", v)
×
995
        }
996

997
        return resp, nil
3✔
998
}
999

1000
// SetMissionControlConfig sets parameters in the mission control config.
1001
func (s *Server) SetMissionControlConfig(ctx context.Context,
1002
        req *SetMissionControlConfigRequest) (*SetMissionControlConfigResponse,
1003
        error) {
3✔
1004

3✔
1005
        mcCfg := &routing.MissionControlConfig{
3✔
1006
                MaxMcHistory: int(req.Config.MaximumPaymentResults),
3✔
1007
                MinFailureRelaxInterval: time.Duration(
3✔
1008
                        req.Config.MinimumFailureRelaxInterval,
3✔
1009
                ) * time.Second,
3✔
1010
        }
3✔
1011

3✔
1012
        switch req.Config.Model {
3✔
1013
        case MissionControlConfig_APRIORI:
3✔
1014
                var aprioriConfig routing.AprioriConfig
3✔
1015

3✔
1016
                // Determine the apriori config with backward compatibility
3✔
1017
                // should the api use deprecated fields.
3✔
1018
                switch v := req.Config.EstimatorConfig.(type) {
3✔
1019
                case *MissionControlConfig_Bimodal:
3✔
1020
                        return nil, fmt.Errorf("bimodal config " +
3✔
1021
                                "provided, but apriori model requested")
3✔
1022

1023
                case *MissionControlConfig_Apriori:
3✔
1024
                        aprioriConfig = routing.AprioriConfig{
3✔
1025
                                PenaltyHalfLife: time.Duration(
3✔
1026
                                        v.Apriori.HalfLifeSeconds,
3✔
1027
                                ) * time.Second,
3✔
1028
                                AprioriHopProbability: v.Apriori.HopProbability,
3✔
1029
                                AprioriWeight:         v.Apriori.Weight,
3✔
1030
                                CapacityFraction: v.Apriori.
3✔
1031
                                        CapacityFraction,
3✔
1032
                        }
3✔
1033

1034
                default:
3✔
1035
                        aprioriConfig = routing.AprioriConfig{
3✔
1036
                                PenaltyHalfLife: time.Duration(
3✔
1037
                                        int64(req.Config.HalfLifeSeconds),
3✔
1038
                                ) * time.Second,
3✔
1039
                                AprioriHopProbability: float64(
3✔
1040
                                        req.Config.HopProbability,
3✔
1041
                                ),
3✔
1042
                                AprioriWeight:    float64(req.Config.Weight),
3✔
1043
                                CapacityFraction: routing.DefaultCapacityFraction, //nolint:ll
3✔
1044
                        }
3✔
1045
                }
1046

1047
                estimator, err := routing.NewAprioriEstimator(aprioriConfig)
3✔
1048
                if err != nil {
3✔
UNCOV
1049
                        return nil, err
×
UNCOV
1050
                }
×
1051
                mcCfg.Estimator = estimator
3✔
1052

1053
        case MissionControlConfig_BIMODAL:
3✔
1054
                cfg, ok := req.Config.
3✔
1055
                        EstimatorConfig.(*MissionControlConfig_Bimodal)
3✔
1056
                if !ok {
3✔
UNCOV
1057
                        return nil, fmt.Errorf("bimodal estimator requested " +
×
UNCOV
1058
                                "but corresponding config not set")
×
UNCOV
1059
                }
×
1060
                bCfg := cfg.Bimodal
3✔
1061

3✔
1062
                bimodalConfig := routing.BimodalConfig{
3✔
1063
                        BimodalDecayTime: time.Duration(
3✔
1064
                                bCfg.DecayTime,
3✔
1065
                        ) * time.Second,
3✔
1066
                        BimodalScaleMsat:  lnwire.MilliSatoshi(bCfg.ScaleMsat),
3✔
1067
                        BimodalNodeWeight: bCfg.NodeWeight,
3✔
1068
                }
3✔
1069

3✔
1070
                estimator, err := routing.NewBimodalEstimator(bimodalConfig)
3✔
1071
                if err != nil {
3✔
UNCOV
1072
                        return nil, err
×
UNCOV
1073
                }
×
1074
                mcCfg.Estimator = estimator
3✔
1075

UNCOV
1076
        default:
×
UNCOV
1077
                return nil, fmt.Errorf("unknown estimator type %v",
×
UNCOV
1078
                        req.Config.Model)
×
1079
        }
1080

1081
        return &SetMissionControlConfigResponse{},
3✔
1082
                s.cfg.RouterBackend.MissionControl.SetConfig(mcCfg)
3✔
1083
}
1084

1085
// QueryMissionControl exposes the internal mission control state to callers. It
1086
// is a development feature.
1087
func (s *Server) QueryMissionControl(_ context.Context,
1088
        _ *QueryMissionControlRequest) (*QueryMissionControlResponse, error) {
×
1089

×
UNCOV
1090
        snapshot := s.cfg.RouterBackend.MissionControl.GetHistorySnapshot()
×
UNCOV
1091

×
UNCOV
1092
        rpcPairs := make([]*PairHistory, 0, len(snapshot.Pairs))
×
UNCOV
1093
        for _, p := range snapshot.Pairs {
×
UNCOV
1094
                // Prevent binding to loop variable.
×
UNCOV
1095
                pair := p
×
UNCOV
1096

×
UNCOV
1097
                rpcPair := PairHistory{
×
UNCOV
1098
                        NodeFrom: pair.Pair.From[:],
×
UNCOV
1099
                        NodeTo:   pair.Pair.To[:],
×
UNCOV
1100
                        History:  toRPCPairData(&pair.TimedPairResult),
×
UNCOV
1101
                }
×
1102

×
1103
                rpcPairs = append(rpcPairs, &rpcPair)
×
UNCOV
1104
        }
×
1105

1106
        response := QueryMissionControlResponse{
×
1107
                Pairs: rpcPairs,
×
1108
        }
×
UNCOV
1109

×
UNCOV
1110
        return &response, nil
×
1111
}
1112

1113
// toRPCPairData marshalls mission control pair data to the rpc struct.
1114
func toRPCPairData(data *routing.TimedPairResult) *PairData {
3✔
1115
        rpcData := PairData{
3✔
1116
                FailAmtSat:     int64(data.FailAmt.ToSatoshis()),
3✔
1117
                FailAmtMsat:    int64(data.FailAmt),
3✔
1118
                SuccessAmtSat:  int64(data.SuccessAmt.ToSatoshis()),
3✔
1119
                SuccessAmtMsat: int64(data.SuccessAmt),
3✔
1120
        }
3✔
1121

3✔
1122
        if !data.FailTime.IsZero() {
6✔
1123
                rpcData.FailTime = data.FailTime.Unix()
3✔
1124
        }
3✔
1125

1126
        if !data.SuccessTime.IsZero() {
3✔
1127
                rpcData.SuccessTime = data.SuccessTime.Unix()
×
1128
        }
×
1129

1130
        return &rpcData
3✔
1131
}
1132

1133
// XImportMissionControl imports the state provided to our internal mission
1134
// control. Only entries that are fresher than our existing state will be used.
1135
func (s *Server) XImportMissionControl(_ context.Context,
1136
        req *XImportMissionControlRequest) (*XImportMissionControlResponse,
1137
        error) {
3✔
1138

3✔
1139
        if len(req.Pairs) == 0 {
3✔
1140
                return nil, errors.New("at least one pair required for import")
×
UNCOV
1141
        }
×
1142

1143
        snapshot := &routing.MissionControlSnapshot{
3✔
1144
                Pairs: make(
3✔
1145
                        []routing.MissionControlPairSnapshot, len(req.Pairs),
3✔
1146
                ),
3✔
1147
        }
3✔
1148

3✔
1149
        for i, pairResult := range req.Pairs {
6✔
1150
                pairSnapshot, err := toPairSnapshot(pairResult)
3✔
1151
                if err != nil {
6✔
1152
                        return nil, err
3✔
1153
                }
3✔
1154

1155
                snapshot.Pairs[i] = *pairSnapshot
3✔
1156
        }
1157

1158
        err := s.cfg.RouterBackend.MissionControl.ImportHistory(
3✔
1159
                snapshot, req.Force,
3✔
1160
        )
3✔
1161
        if err != nil {
3✔
UNCOV
1162
                return nil, err
×
UNCOV
1163
        }
×
1164

1165
        return &XImportMissionControlResponse{}, nil
3✔
1166
}
1167

1168
func toPairSnapshot(pairResult *PairHistory) (*routing.MissionControlPairSnapshot,
1169
        error) {
3✔
1170

3✔
1171
        from, err := route.NewVertexFromBytes(pairResult.NodeFrom)
3✔
1172
        if err != nil {
3✔
UNCOV
1173
                return nil, err
×
UNCOV
1174
        }
×
1175

1176
        to, err := route.NewVertexFromBytes(pairResult.NodeTo)
3✔
1177
        if err != nil {
3✔
UNCOV
1178
                return nil, err
×
UNCOV
1179
        }
×
1180

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

3✔
1183
        if from == to {
3✔
UNCOV
1184
                return nil, fmt.Errorf("%v source and destination node must "+
×
UNCOV
1185
                        "differ", pairPrefix)
×
UNCOV
1186
        }
×
1187

1188
        failAmt, failTime, err := getPair(
3✔
1189
                lnwire.MilliSatoshi(pairResult.History.FailAmtMsat),
3✔
1190
                btcutil.Amount(pairResult.History.FailAmtSat),
3✔
1191
                pairResult.History.FailTime,
3✔
1192
                true,
3✔
1193
        )
3✔
1194
        if err != nil {
6✔
1195
                return nil, fmt.Errorf("%v invalid failure: %w", pairPrefix,
3✔
1196
                        err)
3✔
1197
        }
3✔
1198

1199
        successAmt, successTime, err := getPair(
3✔
1200
                lnwire.MilliSatoshi(pairResult.History.SuccessAmtMsat),
3✔
1201
                btcutil.Amount(pairResult.History.SuccessAmtSat),
3✔
1202
                pairResult.History.SuccessTime,
3✔
1203
                false,
3✔
1204
        )
3✔
1205
        if err != nil {
3✔
UNCOV
1206
                return nil, fmt.Errorf("%v invalid success: %w", pairPrefix,
×
UNCOV
1207
                        err)
×
1208
        }
×
1209

1210
        if successAmt == 0 && failAmt == 0 {
3✔
UNCOV
1211
                return nil, fmt.Errorf("%v: either success or failure result "+
×
UNCOV
1212
                        "required", pairPrefix)
×
UNCOV
1213
        }
×
1214

1215
        pair := routing.NewDirectedNodePair(from, to)
3✔
1216

3✔
1217
        result := &routing.TimedPairResult{
3✔
1218
                FailAmt:     failAmt,
3✔
1219
                FailTime:    failTime,
3✔
1220
                SuccessAmt:  successAmt,
3✔
1221
                SuccessTime: successTime,
3✔
1222
        }
3✔
1223

3✔
1224
        return &routing.MissionControlPairSnapshot{
3✔
1225
                Pair:            pair,
3✔
1226
                TimedPairResult: *result,
3✔
1227
        }, nil
3✔
1228
}
1229

1230
// getPair validates the values provided for a mission control result and
1231
// returns the msat amount and timestamp for it. `isFailure` can be used to
1232
// default values to 0 instead of returning an error.
1233
func getPair(amtMsat lnwire.MilliSatoshi, amtSat btcutil.Amount,
1234
        timestamp int64, isFailure bool) (lnwire.MilliSatoshi, time.Time,
1235
        error) {
3✔
1236

3✔
1237
        amt, err := getMsatPairValue(amtMsat, amtSat)
3✔
1238
        if err != nil {
6✔
1239
                return 0, time.Time{}, err
3✔
1240
        }
3✔
1241

1242
        var (
3✔
1243
                timeSet   = timestamp != 0
3✔
1244
                amountSet = amt != 0
3✔
1245
        )
3✔
1246

3✔
1247
        switch {
3✔
1248
        // If a timestamp and amount if provided, return those values.
1249
        case timeSet && amountSet:
3✔
1250
                return amt, time.Unix(timestamp, 0), nil
3✔
1251

1252
        // Return an error if it does have a timestamp without an amount, and
1253
        // it's not expected to be a failure.
UNCOV
1254
        case !isFailure && timeSet && !amountSet:
×
UNCOV
1255
                return 0, time.Time{}, errors.New("non-zero timestamp " +
×
UNCOV
1256
                        "requires non-zero amount for success pairs")
×
1257

1258
        // Return an error if it does have an amount without a timestamp, and
1259
        // it's not expected to be a failure.
UNCOV
1260
        case !isFailure && !timeSet && amountSet:
×
UNCOV
1261
                return 0, time.Time{}, errors.New("non-zero amount for " +
×
UNCOV
1262
                        "success pairs requires non-zero timestamp")
×
1263

1264
        default:
3✔
1265
                return 0, time.Time{}, nil
3✔
1266
        }
1267
}
1268

1269
// getMsatPairValue checks the msat and sat values set for a pair and ensures
1270
// that the values provided are either the same, or only a single value is set.
1271
func getMsatPairValue(msatValue lnwire.MilliSatoshi,
1272
        satValue btcutil.Amount) (lnwire.MilliSatoshi, error) {
3✔
1273

3✔
1274
        // If our msat value converted to sats equals our sat value, we just
3✔
1275
        // return the msat value, since the values are the same.
3✔
1276
        if msatValue.ToSatoshis() == satValue {
6✔
1277
                return msatValue, nil
3✔
1278
        }
3✔
1279

1280
        // If we have no msatValue, we can just return our state value even if
1281
        // it is zero, because it's impossible that we have mismatched values.
1282
        if msatValue == 0 {
3✔
UNCOV
1283
                return lnwire.MilliSatoshi(satValue * 1000), nil
×
1284
        }
×
1285

1286
        // Likewise, we can just use msat value if we have no sat value set.
1287
        if satValue == 0 {
3✔
UNCOV
1288
                return msatValue, nil
×
UNCOV
1289
        }
×
1290

1291
        // If our values are non-zero but not equal, we have invalid amounts
1292
        // set, so we fail.
1293
        return 0, fmt.Errorf("msat: %v and sat: %v values not equal", msatValue,
3✔
1294
                satValue)
3✔
1295
}
1296

1297
// TrackPaymentV2 returns a stream of payment state updates. The stream is
1298
// closed when the payment completes.
1299
func (s *Server) TrackPaymentV2(request *TrackPaymentRequest,
1300
        stream Router_TrackPaymentV2Server) error {
3✔
1301

3✔
1302
        payHash, err := lntypes.MakeHash(request.PaymentHash)
3✔
1303
        if err != nil {
3✔
UNCOV
1304
                return err
×
UNCOV
1305
        }
×
1306

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

3✔
1309
        // Make the subscription.
3✔
1310
        sub, err := s.subscribePayment(payHash)
3✔
1311
        if err != nil {
3✔
UNCOV
1312
                return err
×
1313
        }
×
1314

1315
        return s.trackPayment(sub, payHash, stream, request.NoInflightUpdates)
3✔
1316
}
1317

1318
// subscribePayment subscribes to the payment updates for the given payment
1319
// hash.
1320
func (s *Server) subscribePayment(identifier lntypes.Hash) (
1321
        routing.ControlTowerSubscriber, error) {
3✔
1322

3✔
1323
        // Make the subscription.
3✔
1324
        router := s.cfg.RouterBackend
3✔
1325
        sub, err := router.Tower.SubscribePayment(identifier)
3✔
1326

3✔
1327
        switch {
3✔
UNCOV
1328
        case errors.Is(err, channeldb.ErrPaymentNotInitiated):
×
UNCOV
1329
                return nil, status.Error(codes.NotFound, err.Error())
×
1330

UNCOV
1331
        case err != nil:
×
UNCOV
1332
                return nil, err
×
1333
        }
1334

1335
        return sub, nil
3✔
1336
}
1337

1338
// trackPayment writes payment status updates to the provided stream.
1339
func (s *Server) trackPayment(subscription routing.ControlTowerSubscriber,
1340
        identifier lntypes.Hash, stream Router_TrackPaymentV2Server,
1341
        noInflightUpdates bool) error {
3✔
1342

3✔
1343
        err := s.trackPaymentStream(
3✔
1344
                stream.Context(), subscription, noInflightUpdates, stream.Send,
3✔
1345
        )
3✔
1346
        switch {
3✔
1347
        case err == nil:
3✔
1348
                return nil
3✔
1349

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

3✔
1354
                return nil
3✔
1355

UNCOV
1356
        default:
×
1357
        }
1358

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

×
UNCOV
1363
        return err
×
1364
}
1365

1366
// TrackPayments returns a stream of payment state updates.
1367
func (s *Server) TrackPayments(request *TrackPaymentsRequest,
1368
        stream Router_TrackPaymentsServer) error {
3✔
1369

3✔
1370
        log.Debug("TrackPayments called")
3✔
1371

3✔
1372
        router := s.cfg.RouterBackend
3✔
1373

3✔
1374
        // Subscribe to payments.
3✔
1375
        subscription, err := router.Tower.SubscribeAllPayments()
3✔
1376
        if err != nil {
3✔
UNCOV
1377
                return err
×
UNCOV
1378
        }
×
1379

1380
        // Stream updates to the client.
1381
        err = s.trackPaymentStream(
3✔
1382
                stream.Context(), subscription, request.NoInflightUpdates,
3✔
1383
                stream.Send,
3✔
1384
        )
3✔
1385

3✔
1386
        if errors.Is(err, context.Canceled) {
6✔
1387
                log.Debugf("TrackPayments payment stream canceled.")
3✔
1388
        }
3✔
1389

1390
        return err
3✔
1391
}
1392

1393
// trackPaymentStream streams payment updates to the client.
1394
func (s *Server) trackPaymentStream(context context.Context,
1395
        subscription routing.ControlTowerSubscriber, noInflightUpdates bool,
1396
        send func(*lnrpc.Payment) error) error {
3✔
1397

3✔
1398
        defer subscription.Close()
3✔
1399

3✔
1400
        // Stream updates back to the client.
3✔
1401
        for {
6✔
1402
                select {
3✔
1403
                case item, ok := <-subscription.Updates():
3✔
1404
                        if !ok {
6✔
1405
                                // No more payment updates.
3✔
1406
                                return nil
3✔
1407
                        }
3✔
1408
                        result := item.(*channeldb.MPPayment)
3✔
1409

3✔
1410
                        log.Tracef("Payment %v updated to state %v",
3✔
1411
                                result.Info.PaymentIdentifier, result.Status)
3✔
1412

3✔
1413
                        // Skip in-flight updates unless requested.
3✔
1414
                        if noInflightUpdates {
3✔
UNCOV
1415
                                if result.Status == channeldb.StatusInitiated {
×
UNCOV
1416
                                        continue
×
1417
                                }
UNCOV
1418
                                if result.Status == channeldb.StatusInFlight {
×
UNCOV
1419
                                        continue
×
1420
                                }
1421
                        }
1422

1423
                        rpcPayment, err := s.cfg.RouterBackend.MarshallPayment(
3✔
1424
                                result,
3✔
1425
                        )
3✔
1426
                        if err != nil {
3✔
UNCOV
1427
                                return err
×
UNCOV
1428
                        }
×
1429

1430
                        // Send event to the client.
1431
                        err = send(rpcPayment)
3✔
1432
                        if err != nil {
3✔
UNCOV
1433
                                return err
×
UNCOV
1434
                        }
×
1435

UNCOV
1436
                case <-s.quit:
×
UNCOV
1437
                        return errServerShuttingDown
×
1438

1439
                case <-context.Done():
3✔
1440
                        return context.Err()
3✔
1441
                }
1442
        }
1443
}
1444

1445
// BuildRoute builds a route from a list of hop addresses.
1446
func (s *Server) BuildRoute(_ context.Context,
1447
        req *BuildRouteRequest) (*BuildRouteResponse, error) {
3✔
1448

3✔
1449
        if len(req.HopPubkeys) == 0 {
3✔
UNCOV
1450
                return nil, errors.New("no hops specified")
×
UNCOV
1451
        }
×
1452

1453
        // Unmarshall hop list.
1454
        hops := make([]route.Vertex, len(req.HopPubkeys))
3✔
1455
        for i, pubkeyBytes := range req.HopPubkeys {
6✔
1456
                pubkey, err := route.NewVertexFromBytes(pubkeyBytes)
3✔
1457
                if err != nil {
3✔
1458
                        return nil, err
×
UNCOV
1459
                }
×
1460
                hops[i] = pubkey
3✔
1461
        }
1462

1463
        // Prepare BuildRoute call parameters from rpc request.
1464
        var amt fn.Option[lnwire.MilliSatoshi]
3✔
1465
        if req.AmtMsat != 0 {
6✔
1466
                rpcAmt := lnwire.MilliSatoshi(req.AmtMsat)
3✔
1467
                amt = fn.Some(rpcAmt)
3✔
1468
        }
3✔
1469

1470
        var outgoingChan *uint64
3✔
1471
        if req.OutgoingChanId != 0 {
3✔
UNCOV
1472
                outgoingChan = &req.OutgoingChanId
×
UNCOV
1473
        }
×
1474

1475
        var payAddr fn.Option[[32]byte]
3✔
1476
        if len(req.PaymentAddr) != 0 {
6✔
1477
                var backingPayAddr [32]byte
3✔
1478
                copy(backingPayAddr[:], req.PaymentAddr)
3✔
1479

3✔
1480
                payAddr = fn.Some(backingPayAddr)
3✔
1481
        }
3✔
1482

1483
        if req.FinalCltvDelta == 0 {
3✔
UNCOV
1484
                req.FinalCltvDelta = int32(
×
UNCOV
1485
                        s.cfg.RouterBackend.DefaultFinalCltvDelta,
×
UNCOV
1486
                )
×
UNCOV
1487
        }
×
1488

1489
        var firstHopBlob fn.Option[[]byte]
3✔
1490
        if len(req.FirstHopCustomRecords) > 0 {
3✔
UNCOV
1491
                firstHopRecords := lnwire.CustomRecords(
×
UNCOV
1492
                        req.FirstHopCustomRecords,
×
UNCOV
1493
                )
×
UNCOV
1494
                if err := firstHopRecords.Validate(); err != nil {
×
UNCOV
1495
                        return nil, err
×
UNCOV
1496
                }
×
1497

UNCOV
1498
                firstHopData, err := firstHopRecords.Serialize()
×
UNCOV
1499
                if err != nil {
×
UNCOV
1500
                        return nil, err
×
UNCOV
1501
                }
×
1502
                firstHopBlob = fn.Some(firstHopData)
×
1503
        }
1504

1505
        // Build the route and return it to the caller.
1506
        route, err := s.cfg.Router.BuildRoute(
3✔
1507
                amt, hops, outgoingChan, req.FinalCltvDelta, payAddr,
3✔
1508
                firstHopBlob,
3✔
1509
        )
3✔
1510
        if err != nil {
3✔
UNCOV
1511
                return nil, err
×
UNCOV
1512
        }
×
1513

1514
        rpcRoute, err := s.cfg.RouterBackend.MarshallRoute(route)
3✔
1515
        if err != nil {
3✔
1516
                return nil, err
×
1517
        }
×
1518

1519
        routeResp := &BuildRouteResponse{
3✔
1520
                Route: rpcRoute,
3✔
1521
        }
3✔
1522

3✔
1523
        return routeResp, nil
3✔
1524
}
1525

1526
// SubscribeHtlcEvents creates a uni-directional stream from the server to
1527
// the client which delivers a stream of htlc events.
1528
func (s *Server) SubscribeHtlcEvents(_ *SubscribeHtlcEventsRequest,
1529
        stream Router_SubscribeHtlcEventsServer) error {
3✔
1530

3✔
1531
        htlcClient, err := s.cfg.RouterBackend.SubscribeHtlcEvents()
3✔
1532
        if err != nil {
3✔
UNCOV
1533
                return err
×
UNCOV
1534
        }
×
1535
        defer htlcClient.Cancel()
3✔
1536

3✔
1537
        // Send out an initial subscribed event so that the caller knows the
3✔
1538
        // point from which new events will be transmitted.
3✔
1539
        if err := stream.Send(&HtlcEvent{
3✔
1540
                Event: &HtlcEvent_SubscribedEvent{
3✔
1541
                        SubscribedEvent: &SubscribedEvent{},
3✔
1542
                },
3✔
1543
        }); err != nil {
3✔
UNCOV
1544
                return err
×
UNCOV
1545
        }
×
1546

1547
        for {
6✔
1548
                select {
3✔
1549
                case event := <-htlcClient.Updates():
3✔
1550
                        rpcEvent, err := rpcHtlcEvent(event)
3✔
1551
                        if err != nil {
3✔
UNCOV
1552
                                return err
×
UNCOV
1553
                        }
×
1554

1555
                        if err := stream.Send(rpcEvent); err != nil {
3✔
UNCOV
1556
                                return err
×
UNCOV
1557
                        }
×
1558

1559
                // If the stream's context is cancelled, return an error.
1560
                case <-stream.Context().Done():
3✔
1561
                        log.Debugf("htlc event stream cancelled")
3✔
1562
                        return stream.Context().Err()
3✔
1563

1564
                // If the subscribe client terminates, exit with an error.
UNCOV
1565
                case <-htlcClient.Quit():
×
UNCOV
1566
                        return errors.New("htlc event subscription terminated")
×
1567

1568
                // If the server has been signalled to shut down, exit.
UNCOV
1569
                case <-s.quit:
×
UNCOV
1570
                        return errServerShuttingDown
×
1571
                }
1572
        }
1573
}
1574

1575
// HtlcInterceptor is a bidirectional stream for streaming interception
1576
// requests to the caller.
1577
// Upon connection, it does the following:
1578
// 1. Check if there is already a live stream, if yes it rejects the request.
1579
// 2. Registered a ForwardInterceptor
1580
// 3. Delivers to the caller every √√ and detect his answer.
1581
// It uses a local implementation of holdForwardsStore to keep all the hold
1582
// forwards and find them when manual resolution is later needed.
1583
func (s *Server) HtlcInterceptor(stream Router_HtlcInterceptorServer) error {
3✔
1584
        // We ensure there is only one interceptor at a time.
3✔
1585
        if !atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 0, 1) {
3✔
1586
                return ErrInterceptorAlreadyExists
×
1587
        }
×
1588
        defer atomic.CompareAndSwapInt32(&s.forwardInterceptorActive, 1, 0)
3✔
1589

3✔
1590
        // Run the forward interceptor.
3✔
1591
        return newForwardInterceptor(
3✔
1592
                s.cfg.RouterBackend.InterceptableForwarder, stream,
3✔
1593
        ).run()
3✔
1594
}
1595

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

3✔
1605
        existingAliases := s.cfg.AliasMgr.ListAliases()
3✔
1606

3✔
1607
        // aliasExists checks if the new alias already exists in the alias map.
3✔
1608
        aliasExists := func(newAlias uint64,
3✔
1609
                baseScid lnwire.ShortChannelID) (bool, error) {
6✔
1610

3✔
1611
                // First check that we actually have a channel for the given
3✔
1612
                // base scid. This should succeed for any channel where the
3✔
1613
                // option-scid-alias feature bit was negotiated.
3✔
1614
                if _, ok := existingAliases[baseScid]; !ok {
3✔
UNCOV
1615
                        return false, fmt.Errorf("base scid %v not found",
×
1616
                                baseScid)
×
1617
                }
×
1618

1619
                for base, aliases := range existingAliases {
6✔
1620
                        for _, alias := range aliases {
6✔
1621
                                exists := alias.ToUint64() == newAlias
3✔
1622

3✔
1623
                                // Trying to add an alias that we already have
3✔
1624
                                // for another channel is wrong.
3✔
1625
                                if exists && base != baseScid {
3✔
UNCOV
1626
                                        return true, fmt.Errorf("%w: alias %v "+
×
UNCOV
1627
                                                "already exists for base scid "+
×
UNCOV
1628
                                                "%v", ErrAliasAlreadyExists,
×
UNCOV
1629
                                                alias, base)
×
UNCOV
1630
                                }
×
1631

1632
                                if exists {
6✔
1633
                                        return true, nil
3✔
1634
                                }
3✔
1635
                        }
1636
                }
1637

1638
                return false, nil
3✔
1639
        }
1640

1641
        for _, v := range in.AliasMaps {
6✔
1642
                baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid)
3✔
1643

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

3✔
1648
                        // But we only add it, if it's a valid alias, as defined
3✔
1649
                        // by the BOLT spec.
3✔
1650
                        if !aliasmgr.IsAlias(aliasScid) {
6✔
1651
                                return nil, fmt.Errorf("%w: SCID alias %v is "+
3✔
1652
                                        "not a valid alias", ErrNoValidAlias,
3✔
1653
                                        aliasScid)
3✔
1654
                        }
3✔
1655

1656
                        exists, err := aliasExists(rpcAlias, baseScid)
3✔
1657
                        if err != nil {
3✔
1658
                                return nil, err
×
1659
                        }
×
1660

1661
                        // If the alias already exists, we see that as an error.
1662
                        // This is to avoid "silent" collisions.
1663
                        if exists {
6✔
1664
                                return nil, fmt.Errorf("%w: SCID alias %v "+
3✔
1665
                                        "already exists", ErrAliasAlreadyExists,
3✔
1666
                                        rpcAlias)
3✔
1667
                        }
3✔
1668

1669
                        err = s.cfg.AliasMgr.AddLocalAlias(
3✔
1670
                                aliasScid, baseScid, false, true,
3✔
1671
                        )
3✔
1672
                        if err != nil {
3✔
UNCOV
1673
                                return nil, fmt.Errorf("error adding scid "+
×
UNCOV
1674
                                        "alias, base_scid=%v, alias_scid=%v: "+
×
UNCOV
1675
                                        "%w", baseScid, aliasScid, err)
×
UNCOV
1676
                        }
×
1677
                }
1678
        }
1679

1680
        return &AddAliasesResponse{
3✔
1681
                AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()),
3✔
1682
        }, nil
3✔
1683
}
1684

1685
// XDeleteLocalChanAliases is an experimental API that deletes a set of alias
1686
// mappings. The final total set of aliases in the manager after the delete
1687
// operation is returned. The deletion will not be communicated to the channel
1688
// peer via any message.
1689
func (s *Server) XDeleteLocalChanAliases(_ context.Context,
1690
        in *DeleteAliasesRequest) (*DeleteAliasesResponse,
1691
        error) {
3✔
1692

3✔
1693
        for _, v := range in.AliasMaps {
6✔
1694
                baseScid := lnwire.NewShortChanIDFromInt(v.BaseScid)
3✔
1695

3✔
1696
                for _, alias := range v.Aliases {
6✔
1697
                        aliasScid := lnwire.NewShortChanIDFromInt(alias)
3✔
1698

3✔
1699
                        err := s.cfg.AliasMgr.DeleteLocalAlias(
3✔
1700
                                aliasScid, baseScid,
3✔
1701
                        )
3✔
1702
                        if err != nil {
3✔
1703
                                return nil, fmt.Errorf("error deleting scid "+
×
1704
                                        "alias, base_scid=%v, alias_scid=%v: "+
×
1705
                                        "%w", baseScid, aliasScid, err)
×
1706
                        }
×
1707
                }
1708
        }
1709

1710
        return &DeleteAliasesResponse{
3✔
1711
                AliasMaps: lnrpc.MarshalAliasMap(s.cfg.AliasMgr.ListAliases()),
3✔
1712
        }, nil
3✔
1713
}
1714

1715
func extractOutPoint(req *UpdateChanStatusRequest) (*wire.OutPoint, error) {
3✔
1716
        chanPoint := req.GetChanPoint()
3✔
1717
        txid, err := lnrpc.GetChanPointFundingTxid(chanPoint)
3✔
1718
        if err != nil {
3✔
UNCOV
1719
                return nil, err
×
UNCOV
1720
        }
×
1721
        index := chanPoint.OutputIndex
3✔
1722
        return wire.NewOutPoint(txid, index), nil
3✔
1723
}
1724

1725
// UpdateChanStatus allows channel state to be set manually.
1726
func (s *Server) UpdateChanStatus(_ context.Context,
1727
        req *UpdateChanStatusRequest) (*UpdateChanStatusResponse, error) {
3✔
1728

3✔
1729
        outPoint, err := extractOutPoint(req)
3✔
1730
        if err != nil {
3✔
UNCOV
1731
                return nil, err
×
UNCOV
1732
        }
×
1733

1734
        action := req.GetAction()
3✔
1735

3✔
1736
        log.Debugf("UpdateChanStatus called for channel(%v) with "+
3✔
1737
                "action %v", outPoint, action)
3✔
1738

3✔
1739
        switch action {
3✔
1740
        case ChanStatusAction_ENABLE:
3✔
1741
                err = s.cfg.RouterBackend.SetChannelEnabled(*outPoint)
3✔
1742
        case ChanStatusAction_DISABLE:
3✔
1743
                err = s.cfg.RouterBackend.SetChannelDisabled(*outPoint)
3✔
1744
        case ChanStatusAction_AUTO:
3✔
1745
                err = s.cfg.RouterBackend.SetChannelAuto(*outPoint)
3✔
UNCOV
1746
        default:
×
UNCOV
1747
                err = fmt.Errorf("unrecognized ChannelStatusAction %v", action)
×
1748
        }
1749

1750
        if err != nil {
3✔
UNCOV
1751
                return nil, err
×
UNCOV
1752
        }
×
1753
        return &UpdateChanStatusResponse{}, nil
3✔
1754
}
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