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

lightningnetwork / lnd / 14919919715

09 May 2025 02:10AM UTC coverage: 58.581% (-0.009%) from 58.59%
14919919715

Pull #9797

github

web-flow
Merge 49fa69b5e into ee25c228e
Pull Request #9797: peer: introduce super priority send queue for pings+pongs

33 of 119 new or added lines in 1 file covered. (27.73%)

35 existing lines in 11 files now uncovered.

97442 of 166336 relevant lines covered (58.58%)

1.82 hits per line

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

74.55
/lnrpc/invoicesrpc/invoices_server.go
1
//go:build invoicesrpc
2
// +build invoicesrpc
3

4
package invoicesrpc
5

6
import (
7
        "context"
8
        "errors"
9
        "fmt"
10
        "os"
11
        "path/filepath"
12

13
        "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
14
        "github.com/lightningnetwork/lnd/invoices"
15
        "github.com/lightningnetwork/lnd/lnrpc"
16
        "github.com/lightningnetwork/lnd/lntypes"
17
        "github.com/lightningnetwork/lnd/macaroons"
18
        "google.golang.org/grpc"
19
        "google.golang.org/grpc/codes"
20
        "google.golang.org/grpc/status"
21
        "gopkg.in/macaroon-bakery.v2/bakery"
22
)
23

24
const (
25
        // subServerName is the name of the sub rpc server. We'll use this name
26
        // to register ourselves, and we also require that the main
27
        // SubServerConfigDispatcher instance recognize it as the name of our
28
        // RPC service.
29
        subServerName = "InvoicesRPC"
30
)
31

32
var (
33
        // ErrServerShuttingDown is returned when the server is shutting down.
34
        ErrServerShuttingDown = errors.New("server shutting down")
35

36
        // macaroonOps are the set of capabilities that our minted macaroon (if
37
        // it doesn't already exist) will have.
38
        macaroonOps = []bakery.Op{
39
                {
40
                        Entity: "invoices",
41
                        Action: "write",
42
                },
43
                {
44
                        Entity: "invoices",
45
                        Action: "read",
46
                },
47
        }
48

49
        // macPermissions maps RPC calls to the permissions they require.
50
        macPermissions = map[string][]bakery.Op{
51
                "/invoicesrpc.Invoices/SubscribeSingleInvoice": {{
52
                        Entity: "invoices",
53
                        Action: "read",
54
                }},
55
                "/invoicesrpc.Invoices/SettleInvoice": {{
56
                        Entity: "invoices",
57
                        Action: "write",
58
                }},
59
                "/invoicesrpc.Invoices/CancelInvoice": {{
60
                        Entity: "invoices",
61
                        Action: "write",
62
                }},
63
                "/invoicesrpc.Invoices/AddHoldInvoice": {{
64
                        Entity: "invoices",
65
                        Action: "write",
66
                }},
67
                "/invoicesrpc.Invoices/LookupInvoiceV2": {{
68
                        Entity: "invoices",
69
                        Action: "write",
70
                }},
71
                "/invoicesrpc.Invoices/HtlcModifier": {{
72
                        Entity: "invoices",
73
                        Action: "write",
74
                }},
75
        }
76

77
        // DefaultInvoicesMacFilename is the default name of the invoices
78
        // macaroon that we expect to find via a file handle within the main
79
        // configuration file in this package.
80
        DefaultInvoicesMacFilename = "invoices.macaroon"
81
)
82

83
// ServerShell is a shell struct holding a reference to the actual sub-server.
84
// It is used to register the gRPC sub-server with the root server before we
85
// have the necessary dependencies to populate the actual sub-server.
86
type ServerShell struct {
87
        InvoicesServer
88
}
89

90
// Server is a sub-server of the main RPC server: the invoices RPC. This sub
91
// RPC server allows external callers to access the status of the invoices
92
// currently active within lnd, as well as configuring it at runtime.
93
type Server struct {
94
        // Required by the grpc-gateway/v2 library for forward compatibility.
95
        UnimplementedInvoicesServer
96

97
        quit chan struct{}
98

99
        cfg *Config
100
}
101

102
// A compile time check to ensure that Server fully implements the
103
// InvoicesServer gRPC service.
104
var _ InvoicesServer = (*Server)(nil)
105

106
// New returns a new instance of the invoicesrpc Invoices sub-server. We also
107
// return the set of permissions for the macaroons that we may create within
108
// this method. If the macaroons we need aren't found in the filepath, then
109
// we'll create them on start up. If we're unable to locate, or create the
110
// macaroons we need, then we'll return with an error.
111
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
3✔
112
        // If the path of the invoices macaroon wasn't specified, then we'll
3✔
113
        // assume that it's found at the default network directory.
3✔
114
        macFilePath := filepath.Join(
3✔
115
                cfg.NetworkDir, DefaultInvoicesMacFilename,
3✔
116
        )
3✔
117

3✔
118
        // Now that we know the full path of the invoices macaroon, we can
3✔
119
        // check to see if we need to create it or not. If stateless_init is set
3✔
120
        // then we don't write the macaroons.
3✔
121
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
3✔
122
                !lnrpc.FileExists(macFilePath) {
6✔
123

3✔
124
                log.Infof("Baking macaroons for invoices RPC Server at: %v",
3✔
125
                        macFilePath)
3✔
126

3✔
127
                // At this point, we know that the invoices macaroon doesn't
3✔
128
                // yet, exist, so we need to create it with the help of the
3✔
129
                // main macaroon service.
3✔
130
                invoicesMac, err := cfg.MacService.NewMacaroon(
3✔
131
                        context.Background(), macaroons.DefaultRootKeyID,
3✔
132
                        macaroonOps...,
3✔
133
                )
3✔
134
                if err != nil {
3✔
135
                        return nil, nil, err
×
136
                }
×
137
                invoicesMacBytes, err := invoicesMac.M().MarshalBinary()
3✔
138
                if err != nil {
3✔
139
                        return nil, nil, err
×
140
                }
×
141
                err = os.WriteFile(macFilePath, invoicesMacBytes, 0644)
3✔
142
                if err != nil {
3✔
143
                        _ = os.Remove(macFilePath)
×
144
                        return nil, nil, err
×
145
                }
×
146
        }
147

148
        server := &Server{
3✔
149
                cfg:  cfg,
3✔
150
                quit: make(chan struct{}, 1),
3✔
151
        }
3✔
152

3✔
153
        return server, macPermissions, nil
3✔
154
}
155

156
// Start launches any helper goroutines required for the Server to function.
157
//
158
// NOTE: This is part of the lnrpc.SubServer interface.
159
func (s *Server) Start() error {
3✔
160
        return nil
3✔
161
}
3✔
162

163
// Stop signals any active goroutines for a graceful closure.
164
//
165
// NOTE: This is part of the lnrpc.SubServer interface.
166
func (s *Server) Stop() error {
3✔
167
        close(s.quit)
3✔
168

3✔
169
        return nil
3✔
170
}
3✔
171

172
// Name returns a unique string representation of the sub-server. This can be
173
// used to identify the sub-server and also de-duplicate them.
174
//
175
// NOTE: This is part of the lnrpc.SubServer interface.
176
func (s *Server) Name() string {
3✔
177
        return subServerName
3✔
178
}
3✔
179

180
// RegisterWithRootServer will be called by the root gRPC server to direct a sub
181
// RPC server to register itself with the main gRPC root server. Until this is
182
// called, each sub-server won't be able to have requests routed towards it.
183
//
184
// NOTE: This is part of the lnrpc.GrpcHandler interface.
185
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
3✔
186
        // We make sure that we register it with the main gRPC server to ensure
3✔
187
        // all our methods are routed properly.
3✔
188
        RegisterInvoicesServer(grpcServer, r)
3✔
189

3✔
190
        log.Debugf("Invoices RPC server successfully registered with root " +
3✔
191
                "gRPC server")
3✔
192

3✔
193
        return nil
3✔
194
}
3✔
195

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

3✔
204
        // We make sure that we register it with the main REST server to ensure
3✔
205
        // all our methods are routed properly.
3✔
206
        err := RegisterInvoicesHandlerFromEndpoint(ctx, mux, dest, opts)
3✔
207
        if err != nil {
3✔
208
                log.Errorf("Could not register Invoices REST server "+
×
209
                        "with root REST server: %v", err)
×
210
                return err
×
211
        }
×
212

213
        log.Debugf("Invoices REST server successfully registered with " +
3✔
214
                "root REST server")
3✔
215
        return nil
3✔
216
}
217

218
// CreateSubServer populates the subserver's dependencies using the passed
219
// SubServerConfigDispatcher. This method should fully initialize the
220
// sub-server instance, making it ready for action. It returns the macaroon
221
// permissions that the sub-server wishes to pass on to the root server for all
222
// methods routed towards it.
223
//
224
// NOTE: This is part of the lnrpc.GrpcHandler interface.
225
func (r *ServerShell) CreateSubServer(
226
        configRegistry lnrpc.SubServerConfigDispatcher) (lnrpc.SubServer,
227
        lnrpc.MacaroonPerms, error) {
3✔
228

3✔
229
        subServer, macPermissions, err := createNewSubServer(configRegistry)
3✔
230
        if err != nil {
3✔
231
                return nil, nil, err
×
232
        }
×
233

234
        r.InvoicesServer = subServer
3✔
235
        return subServer, macPermissions, nil
3✔
236
}
237

238
// SubscribeSingleInvoice returns a uni-directional stream (server -> client)
239
// for notifying the client of state changes for a specified invoice.
240
func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest,
241
        updateStream Invoices_SubscribeSingleInvoiceServer) error {
3✔
242

3✔
243
        hash, err := lntypes.MakeHash(req.RHash)
3✔
244
        if err != nil {
3✔
245
                return err
×
246
        }
×
247

248
        invoiceClient, err := s.cfg.InvoiceRegistry.SubscribeSingleInvoice(
3✔
249
                updateStream.Context(), hash,
3✔
250
        )
3✔
251
        if err != nil {
3✔
252
                return err
×
253
        }
×
254
        defer invoiceClient.Cancel()
3✔
255

3✔
256
        log.Debugf("Created new single invoice(pay_hash=%v) subscription", hash)
3✔
257

3✔
258
        for {
6✔
259
                select {
3✔
260
                case newInvoice := <-invoiceClient.Updates:
3✔
261
                        rpcInvoice, err := CreateRPCInvoice(
3✔
262
                                newInvoice, s.cfg.ChainParams,
3✔
263
                        )
3✔
264
                        if err != nil {
3✔
265
                                return err
×
266
                        }
×
267

268
                        // Give the aux data parser a chance to format the
269
                        // custom data in the invoice HTLCs.
270
                        err = s.cfg.ParseAuxData(rpcInvoice)
3✔
271
                        if err != nil {
3✔
272
                                return fmt.Errorf("error parsing custom data: "+
×
273
                                        "%w", err)
×
274
                        }
×
275

276
                        if err := updateStream.Send(rpcInvoice); err != nil {
3✔
UNCOV
277
                                return err
×
UNCOV
278
                        }
×
279

280
                        // If we have reached a terminal state, close the
281
                        // stream with no error.
282
                        if newInvoice.State.IsFinal() {
6✔
283
                                return nil
3✔
284
                        }
3✔
285

286
                case <-updateStream.Context().Done():
3✔
287
                        return fmt.Errorf("subscription for "+
3✔
288
                                "invoice(pay_hash=%v): %w", hash,
3✔
289
                                updateStream.Context().Err())
3✔
290

291
                case <-s.quit:
×
292
                        return nil
×
293
                }
294
        }
295
}
296

297
// SettleInvoice settles an accepted invoice. If the invoice is already settled,
298
// this call will succeed.
299
func (s *Server) SettleInvoice(ctx context.Context,
300
        in *SettleInvoiceMsg) (*SettleInvoiceResp, error) {
3✔
301

3✔
302
        preimage, err := lntypes.MakePreimage(in.Preimage)
3✔
303
        if err != nil {
3✔
304
                return nil, err
×
305
        }
×
306

307
        err = s.cfg.InvoiceRegistry.SettleHodlInvoice(ctx, preimage)
3✔
308
        if err != nil && !errors.Is(err, invoices.ErrInvoiceAlreadySettled) {
3✔
309
                return nil, err
×
310
        }
×
311

312
        return &SettleInvoiceResp{}, nil
3✔
313
}
314

315
// CancelInvoice cancels a currently open invoice. If the invoice is already
316
// canceled, this call will succeed. If the invoice is already settled, it will
317
// fail.
318
func (s *Server) CancelInvoice(ctx context.Context,
319
        in *CancelInvoiceMsg) (*CancelInvoiceResp, error) {
3✔
320

3✔
321
        paymentHash, err := lntypes.MakeHash(in.PaymentHash)
3✔
322
        if err != nil {
3✔
323
                return nil, err
×
324
        }
×
325

326
        err = s.cfg.InvoiceRegistry.CancelInvoice(ctx, paymentHash)
3✔
327
        if err != nil {
3✔
328
                return nil, err
×
329
        }
×
330

331
        log.Infof("Canceled invoice %v", paymentHash)
3✔
332

3✔
333
        return &CancelInvoiceResp{}, nil
3✔
334
}
335

336
// AddHoldInvoice attempts to add a new hold invoice to the invoice database.
337
// Any duplicated invoices are rejected, therefore all invoices *must* have a
338
// unique payment hash.
339
func (s *Server) AddHoldInvoice(ctx context.Context,
340
        invoice *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error) {
3✔
341

3✔
342
        addInvoiceCfg := &AddInvoiceConfig{
3✔
343
                AddInvoice:            s.cfg.InvoiceRegistry.AddInvoice,
3✔
344
                IsChannelActive:       s.cfg.IsChannelActive,
3✔
345
                ChainParams:           s.cfg.ChainParams,
3✔
346
                NodeSigner:            s.cfg.NodeSigner,
3✔
347
                DefaultCLTVExpiry:     s.cfg.DefaultCLTVExpiry,
3✔
348
                ChanDB:                s.cfg.ChanStateDB,
3✔
349
                Graph:                 s.cfg.Graph,
3✔
350
                GenInvoiceFeatures:    s.cfg.GenInvoiceFeatures,
3✔
351
                GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures,
3✔
352
                GetAlias:              s.cfg.GetAlias,
3✔
353
        }
3✔
354

3✔
355
        hash, err := lntypes.MakeHash(invoice.Hash)
3✔
356
        if err != nil {
3✔
357
                return nil, err
×
358
        }
×
359

360
        value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
3✔
361
        if err != nil {
3✔
362
                return nil, err
×
363
        }
×
364

365
        // Convert the passed routing hints to the required format.
366
        routeHints, err := CreateZpay32HopHints(invoice.RouteHints)
3✔
367
        if err != nil {
3✔
368
                return nil, err
×
369
        }
×
370
        addInvoiceData := &AddInvoiceData{
3✔
371
                Memo:            invoice.Memo,
3✔
372
                Hash:            &hash,
3✔
373
                Value:           value,
3✔
374
                DescriptionHash: invoice.DescriptionHash,
3✔
375
                Expiry:          invoice.Expiry,
3✔
376
                FallbackAddr:    invoice.FallbackAddr,
3✔
377
                CltvExpiry:      invoice.CltvExpiry,
3✔
378
                Private:         invoice.Private,
3✔
379
                HodlInvoice:     true,
3✔
380
                Preimage:        nil,
3✔
381
                RouteHints:      routeHints,
3✔
382
        }
3✔
383

3✔
384
        _, dbInvoice, err := AddInvoice(ctx, addInvoiceCfg, addInvoiceData)
3✔
385
        if err != nil {
3✔
386
                return nil, err
×
387
        }
×
388

389
        return &AddHoldInvoiceResp{
3✔
390
                AddIndex:       dbInvoice.AddIndex,
3✔
391
                PaymentRequest: string(dbInvoice.PaymentRequest),
3✔
392
                PaymentAddr:    dbInvoice.Terms.PaymentAddr[:],
3✔
393
        }, nil
3✔
394
}
395

396
// LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
397
// using either its payment hash, payment address, or set ID.
398
func (s *Server) LookupInvoiceV2(ctx context.Context,
399
        req *LookupInvoiceMsg) (*lnrpc.Invoice, error) {
3✔
400

3✔
401
        var invoiceRef invoices.InvoiceRef
3✔
402

3✔
403
        // First, we'll attempt to parse out the invoice ref from the proto
3✔
404
        // oneof.  If none of the three currently supported types was
3✔
405
        // specified, then we'll exit with an error.
3✔
406
        switch {
3✔
407
        case req.GetPaymentHash() != nil:
×
408
                payHash, err := lntypes.MakeHash(req.GetPaymentHash())
×
409
                if err != nil {
×
410
                        return nil, status.Error(
×
411
                                codes.InvalidArgument,
×
412
                                fmt.Sprintf("unable to parse pay hash: %v", err),
×
413
                        )
×
414
                }
×
415

416
                invoiceRef = invoices.InvoiceRefByHash(payHash)
×
417

418
        case req.GetPaymentAddr() != nil &&
419
                req.LookupModifier == LookupModifier_HTLC_SET_BLANK:
3✔
420

3✔
421
                var payAddr [32]byte
3✔
422
                copy(payAddr[:], req.GetPaymentAddr())
3✔
423

3✔
424
                invoiceRef = invoices.InvoiceRefByAddrBlankHtlc(payAddr)
3✔
425

426
        case req.GetPaymentAddr() != nil:
3✔
427
                var payAddr [32]byte
3✔
428
                copy(payAddr[:], req.GetPaymentAddr())
3✔
429

3✔
430
                invoiceRef = invoices.InvoiceRefByAddr(payAddr)
3✔
431

432
        case req.GetSetId() != nil &&
433
                req.LookupModifier == LookupModifier_HTLC_SET_ONLY:
3✔
434

3✔
435
                var setID [32]byte
3✔
436
                copy(setID[:], req.GetSetId())
3✔
437

3✔
438
                invoiceRef = invoices.InvoiceRefBySetIDFiltered(setID)
3✔
439

440
        case req.GetSetId() != nil:
×
441
                var setID [32]byte
×
442
                copy(setID[:], req.GetSetId())
×
443

×
444
                invoiceRef = invoices.InvoiceRefBySetID(setID)
×
445

446
        default:
×
447
                return nil, status.Error(codes.InvalidArgument,
×
448
                        "invoice ref must be set")
×
449
        }
450

451
        // Attempt to locate the invoice, returning a nice "not found" error if
452
        // we can't find it in the database.
453
        invoice, err := s.cfg.InvoiceRegistry.LookupInvoiceByRef(
3✔
454
                ctx, invoiceRef,
3✔
455
        )
3✔
456
        switch {
3✔
457
        case errors.Is(err, invoices.ErrInvoiceNotFound):
×
458
                return nil, status.Error(codes.NotFound, err.Error())
×
459
        case err != nil:
×
460
                return nil, err
×
461
        }
462

463
        rpcInvoice, err := CreateRPCInvoice(&invoice, s.cfg.ChainParams)
3✔
464
        if err != nil {
3✔
465
                return nil, err
×
466
        }
×
467

468
        // Give the aux data parser a chance to format the custom data in the
469
        // invoice HTLCs.
470
        err = s.cfg.ParseAuxData(rpcInvoice)
3✔
471
        if err != nil {
3✔
472
                return nil, fmt.Errorf("error parsing custom data: %w", err)
×
473
        }
×
474

475
        return rpcInvoice, nil
3✔
476
}
477

478
// HtlcModifier is a bidirectional streaming RPC that allows a client to
479
// intercept and modify the HTLCs that attempt to settle the given invoice. The
480
// server will send HTLCs of invoices to the client and the client can modify
481
// some aspects of the HTLC in order to pass the invoice acceptance tests.
482
func (s *Server) HtlcModifier(
483
        modifierServer Invoices_HtlcModifierServer) error {
3✔
484

3✔
485
        modifier := newHtlcModifier(s.cfg.ChainParams, modifierServer)
3✔
486
        reset, modifierQuit, err := s.cfg.HtlcModifier.RegisterInterceptor(
3✔
487
                modifier.onIntercept,
3✔
488
        )
3✔
489
        if err != nil {
6✔
490
                return fmt.Errorf("cannot register interceptor: %w", err)
3✔
491
        }
3✔
492

493
        defer reset()
3✔
494

3✔
495
        log.Debugf("Invoice HTLC modifier client connected")
3✔
496

3✔
497
        for {
6✔
498
                select {
3✔
499
                case <-modifierServer.Context().Done():
3✔
500
                        return modifierServer.Context().Err()
3✔
501

502
                case <-modifierQuit:
×
503
                        return ErrServerShuttingDown
×
504

505
                case <-s.quit:
×
506
                        return ErrServerShuttingDown
×
507
                }
508
        }
509
}
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