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

lightningnetwork / lnd / 11954082915

21 Nov 2024 01:20PM UTC coverage: 59.327% (+0.6%) from 58.776%
11954082915

Pull #8754

github

ViktorTigerstrom
itest: wrap deriveCustomScopeAccounts at 80 chars

This commit fixes that word wrapping for the deriveCustomScopeAccounts
function docs, and ensures that it wraps at 80 characters or less.
Pull Request #8754: Add `Outbound` Remote Signer implementation

1940 of 2984 new or added lines in 44 files covered. (65.01%)

226 existing lines in 37 files now uncovered.

135234 of 227947 relevant lines covered (59.33%)

19316.75 hits per line

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

65.41
/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
        "sync/atomic"
13

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

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

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

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

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

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

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

91
// Server is a sub-server of the main RPC server: the invoices RPC. This sub
92
// RPC server allows external callers to access the status of the invoices
93
// currently active within lnd, as well as configuring it at runtime.
94
type Server struct {
95
        injected int32 // To be used atomically.
96

97
        // Required by the grpc-gateway/v2 library for forward compatibility.
98
        UnimplementedInvoicesServer
99

100
        quit chan struct{}
101

102
        cfg *Config
103
}
104

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

109
// New returns a new instance of the invoicesrpc Invoices sub-server. We also
110
// return the set of permissions for the macaroons that we may create within
111
// this method. If the macaroons we need aren't found in the filepath, then
112
// we'll create them on start up. If we're unable to locate, or create the
113
// macaroons we need, then we'll return with an error.
114
func New() (*Server, lnrpc.MacaroonPerms, error) {
4✔
115
        server := &Server{
4✔
116
                cfg:  &Config{},
4✔
117
                quit: make(chan struct{}, 1),
4✔
118
        }
4✔
119

4✔
120
        return server, macPermissions, nil
4✔
121
}
4✔
122

123
// Stop signals any active goroutines for a graceful closure.
124
//
125
// NOTE: This is part of the lnrpc.SubServer interface.
126
func (s *Server) Stop() error {
4✔
127
        close(s.quit)
4✔
128

4✔
129
        return nil
4✔
130
}
4✔
131

132
// InjectDependencies populates the sub-server's dependencies. If the
133
// finalizeDependencies boolean is true, then the sub-server will finalize its
134
// dependencies and return an error if any required dependencies are missing.
135
//
136
// NOTE: This is part of the lnrpc.SubServer interface.
137
func (s *Server) InjectDependencies(
138
        configRegistry lnrpc.SubServerConfigDispatcher,
139
        finalizeDependencies bool) error {
4✔
140

4✔
141
        if finalizeDependencies && atomic.AddInt32(&s.injected, 1) != 1 {
4✔
NEW
142
                return lnrpc.ErrDependenciesFinalized
×
NEW
143
        }
×
144

145
        cfg, err := getConfig(configRegistry, finalizeDependencies)
4✔
146
        if err != nil {
4✔
NEW
147
                return err
×
NEW
148
        }
×
149

150
        if finalizeDependencies {
8✔
151
                s.cfg = cfg
4✔
152

4✔
153
                return nil
4✔
154
        }
4✔
155

156
        // If the path of the invoices macaroon wasn't specified, then we'll
157
        // assume that it's found at the default network directory.
UNCOV
158
        macFilePath := filepath.Join(
×
UNCOV
159
                cfg.NetworkDir, DefaultInvoicesMacFilename,
×
UNCOV
160
        )
×
UNCOV
161

×
UNCOV
162
        // Now that we know the full path of the invoices macaroon, we can
×
UNCOV
163
        // check to see if we need to create it or not. If stateless_init is set
×
UNCOV
164
        // then we don't write the macaroons.
×
UNCOV
165
        if cfg.MacService != nil && !cfg.MacService.StatelessInit &&
×
UNCOV
166
                !lnrpc.FileExists(macFilePath) {
×
UNCOV
167

×
UNCOV
168
                log.Infof("Baking macaroons for invoices RPC Server at: %v",
×
UNCOV
169
                        macFilePath)
×
UNCOV
170

×
UNCOV
171
                // At this point, we know that the invoices macaroon doesn't
×
UNCOV
172
                // yet, exist, so we need to create it with the help of the
×
UNCOV
173
                // main macaroon service.
×
UNCOV
174
                invoicesMac, err := cfg.MacService.NewMacaroon(
×
UNCOV
175
                        context.Background(), macaroons.DefaultRootKeyID,
×
UNCOV
176
                        macaroonOps...,
×
UNCOV
177
                )
×
UNCOV
178
                if err != nil {
×
NEW
179
                        return err
×
180
                }
×
UNCOV
181
                invoicesMacBytes, err := invoicesMac.M().MarshalBinary()
×
UNCOV
182
                if err != nil {
×
NEW
183
                        return err
×
184
                }
×
UNCOV
185
                err = os.WriteFile(macFilePath, invoicesMacBytes, 0644)
×
UNCOV
186
                if err != nil {
×
187
                        _ = os.Remove(macFilePath)
×
NEW
188
                        return err
×
189
                }
×
190
        }
191

NEW
192
        s.cfg = cfg
×
UNCOV
193

×
UNCOV
194
        return nil
×
195
}
196

197
// Name returns a unique string representation of the sub-server. This can be
198
// used to identify the sub-server and also de-duplicate them.
199
//
200
// NOTE: This is part of the lnrpc.SubServer interface.
201
func (s *Server) Name() string {
4✔
202
        return subServerName
4✔
203
}
4✔
204

205
// RegisterWithRootServer will be called by the root gRPC server to direct a sub
206
// RPC server to register itself with the main gRPC root server. Until this is
207
// called, each sub-server won't be able to have requests routed towards it.
208
//
209
// NOTE: This is part of the lnrpc.GrpcHandler interface.
210
func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error {
4✔
211
        // We make sure that we register it with the main gRPC server to ensure
4✔
212
        // all our methods are routed properly.
4✔
213
        RegisterInvoicesServer(grpcServer, r)
4✔
214

4✔
215
        log.Debugf("Invoices RPC server successfully registered with root " +
4✔
216
                "gRPC server")
4✔
217

4✔
218
        return nil
4✔
219
}
4✔
220

221
// RegisterWithRestServer will be called by the root REST mux to direct a sub
222
// RPC server to register itself with the main REST mux server. Until this is
223
// called, each sub-server won't be able to have requests routed towards it.
224
//
225
// NOTE: This is part of the lnrpc.GrpcHandler interface.
226
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
227
        mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
4✔
228

4✔
229
        // We make sure that we register it with the main REST server to ensure
4✔
230
        // all our methods are routed properly.
4✔
231
        err := RegisterInvoicesHandlerFromEndpoint(ctx, mux, dest, opts)
4✔
232
        if err != nil {
4✔
233
                log.Errorf("Could not register Invoices REST server "+
×
234
                        "with root REST server: %v", err)
×
235
                return err
×
236
        }
×
237

238
        log.Debugf("Invoices REST server successfully registered with " +
4✔
239
                "root REST server")
4✔
240
        return nil
4✔
241
}
242

243
// CreateSubServer creates an instance of the sub-server, and returns the
244
// macaroon permissions that the sub-server wishes to pass on to the root server
245
// for all methods routed towards it.
246
//
247
// NOTE: This is part of the lnrpc.GrpcHandler interface.
248
func (r *ServerShell) CreateSubServer() (
249
        lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
4✔
250

4✔
251
        subServer, macPermissions, err := New()
4✔
252
        if err != nil {
4✔
253
                return nil, nil, err
×
254
        }
×
255

256
        r.InvoicesServer = subServer
4✔
257
        return subServer, macPermissions, nil
4✔
258
}
259

260
// SubscribeSingleInvoice returns a uni-directional stream (server -> client)
261
// for notifying the client of state changes for a specified invoice.
262
func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest,
263
        updateStream Invoices_SubscribeSingleInvoiceServer) error {
4✔
264

4✔
265
        hash, err := lntypes.MakeHash(req.RHash)
4✔
266
        if err != nil {
4✔
267
                return err
×
268
        }
×
269

270
        invoiceClient, err := s.cfg.InvoiceRegistry.SubscribeSingleInvoice(
4✔
271
                updateStream.Context(), hash,
4✔
272
        )
4✔
273
        if err != nil {
4✔
274
                return err
×
275
        }
×
276
        defer invoiceClient.Cancel()
4✔
277

4✔
278
        log.Debugf("Created new single invoice(pay_hash=%v) subscription", hash)
4✔
279

4✔
280
        for {
8✔
281
                select {
4✔
282
                case newInvoice := <-invoiceClient.Updates:
4✔
283
                        rpcInvoice, err := CreateRPCInvoice(
4✔
284
                                newInvoice, s.cfg.ChainParams,
4✔
285
                        )
4✔
286
                        if err != nil {
4✔
287
                                return err
×
288
                        }
×
289

290
                        // Give the aux data parser a chance to format the
291
                        // custom data in the invoice HTLCs.
292
                        err = s.cfg.ParseAuxData(rpcInvoice)
4✔
293
                        if err != nil {
4✔
294
                                return fmt.Errorf("error parsing custom data: "+
×
295
                                        "%w", err)
×
296
                        }
×
297

298
                        if err := updateStream.Send(rpcInvoice); err != nil {
8✔
299
                                return err
4✔
300
                        }
4✔
301

302
                        // If we have reached a terminal state, close the
303
                        // stream with no error.
304
                        if newInvoice.State.IsFinal() {
8✔
305
                                return nil
4✔
306
                        }
4✔
307

308
                case <-updateStream.Context().Done():
4✔
309
                        return fmt.Errorf("subscription for "+
4✔
310
                                "invoice(pay_hash=%v): %w", hash,
4✔
311
                                updateStream.Context().Err())
4✔
312

313
                case <-s.quit:
×
314
                        return nil
×
315
                }
316
        }
317
}
318

319
// SettleInvoice settles an accepted invoice. If the invoice is already settled,
320
// this call will succeed.
321
func (s *Server) SettleInvoice(ctx context.Context,
322
        in *SettleInvoiceMsg) (*SettleInvoiceResp, error) {
4✔
323

4✔
324
        preimage, err := lntypes.MakePreimage(in.Preimage)
4✔
325
        if err != nil {
4✔
326
                return nil, err
×
327
        }
×
328

329
        err = s.cfg.InvoiceRegistry.SettleHodlInvoice(ctx, preimage)
4✔
330
        if err != nil && !errors.Is(err, invoices.ErrInvoiceAlreadySettled) {
4✔
331
                return nil, err
×
332
        }
×
333

334
        return &SettleInvoiceResp{}, nil
4✔
335
}
336

337
// CancelInvoice cancels a currently open invoice. If the invoice is already
338
// canceled, this call will succeed. If the invoice is already settled, it will
339
// fail.
340
func (s *Server) CancelInvoice(ctx context.Context,
341
        in *CancelInvoiceMsg) (*CancelInvoiceResp, error) {
4✔
342

4✔
343
        paymentHash, err := lntypes.MakeHash(in.PaymentHash)
4✔
344
        if err != nil {
4✔
345
                return nil, err
×
346
        }
×
347

348
        err = s.cfg.InvoiceRegistry.CancelInvoice(ctx, paymentHash)
4✔
349
        if err != nil {
4✔
350
                return nil, err
×
351
        }
×
352

353
        log.Infof("Canceled invoice %v", paymentHash)
4✔
354

4✔
355
        return &CancelInvoiceResp{}, nil
4✔
356
}
357

358
// AddHoldInvoice attempts to add a new hold invoice to the invoice database.
359
// Any duplicated invoices are rejected, therefore all invoices *must* have a
360
// unique payment hash.
361
func (s *Server) AddHoldInvoice(ctx context.Context,
362
        invoice *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error) {
4✔
363

4✔
364
        addInvoiceCfg := &AddInvoiceConfig{
4✔
365
                AddInvoice:            s.cfg.InvoiceRegistry.AddInvoice,
4✔
366
                IsChannelActive:       s.cfg.IsChannelActive,
4✔
367
                ChainParams:           s.cfg.ChainParams,
4✔
368
                NodeSigner:            s.cfg.NodeSigner,
4✔
369
                DefaultCLTVExpiry:     s.cfg.DefaultCLTVExpiry,
4✔
370
                ChanDB:                s.cfg.ChanStateDB,
4✔
371
                Graph:                 s.cfg.GraphDB,
4✔
372
                GenInvoiceFeatures:    s.cfg.GenInvoiceFeatures,
4✔
373
                GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures,
4✔
374
                GetAlias:              s.cfg.GetAlias,
4✔
375
        }
4✔
376

4✔
377
        hash, err := lntypes.MakeHash(invoice.Hash)
4✔
378
        if err != nil {
4✔
379
                return nil, err
×
380
        }
×
381

382
        value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMsat)
4✔
383
        if err != nil {
4✔
384
                return nil, err
×
385
        }
×
386

387
        // Convert the passed routing hints to the required format.
388
        routeHints, err := CreateZpay32HopHints(invoice.RouteHints)
4✔
389
        if err != nil {
4✔
390
                return nil, err
×
391
        }
×
392
        addInvoiceData := &AddInvoiceData{
4✔
393
                Memo:            invoice.Memo,
4✔
394
                Hash:            &hash,
4✔
395
                Value:           value,
4✔
396
                DescriptionHash: invoice.DescriptionHash,
4✔
397
                Expiry:          invoice.Expiry,
4✔
398
                FallbackAddr:    invoice.FallbackAddr,
4✔
399
                CltvExpiry:      invoice.CltvExpiry,
4✔
400
                Private:         invoice.Private,
4✔
401
                HodlInvoice:     true,
4✔
402
                Preimage:        nil,
4✔
403
                RouteHints:      routeHints,
4✔
404
        }
4✔
405

4✔
406
        _, dbInvoice, err := AddInvoice(ctx, addInvoiceCfg, addInvoiceData)
4✔
407
        if err != nil {
4✔
408
                return nil, err
×
409
        }
×
410

411
        return &AddHoldInvoiceResp{
4✔
412
                AddIndex:       dbInvoice.AddIndex,
4✔
413
                PaymentRequest: string(dbInvoice.PaymentRequest),
4✔
414
                PaymentAddr:    dbInvoice.Terms.PaymentAddr[:],
4✔
415
        }, nil
4✔
416
}
417

418
// LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced
419
// using either its payment hash, payment address, or set ID.
420
func (s *Server) LookupInvoiceV2(ctx context.Context,
421
        req *LookupInvoiceMsg) (*lnrpc.Invoice, error) {
4✔
422

4✔
423
        var invoiceRef invoices.InvoiceRef
4✔
424

4✔
425
        // First, we'll attempt to parse out the invoice ref from the proto
4✔
426
        // oneof.  If none of the three currently supported types was
4✔
427
        // specified, then we'll exit with an error.
4✔
428
        switch {
4✔
429
        case req.GetPaymentHash() != nil:
×
430
                payHash, err := lntypes.MakeHash(req.GetPaymentHash())
×
431
                if err != nil {
×
432
                        return nil, status.Error(
×
433
                                codes.InvalidArgument,
×
434
                                fmt.Sprintf("unable to parse pay hash: %v", err),
×
435
                        )
×
436
                }
×
437

438
                invoiceRef = invoices.InvoiceRefByHash(payHash)
×
439

440
        case req.GetPaymentAddr() != nil &&
441
                req.LookupModifier == LookupModifier_HTLC_SET_BLANK:
4✔
442

4✔
443
                var payAddr [32]byte
4✔
444
                copy(payAddr[:], req.GetPaymentAddr())
4✔
445

4✔
446
                invoiceRef = invoices.InvoiceRefByAddrBlankHtlc(payAddr)
4✔
447

448
        case req.GetPaymentAddr() != nil:
4✔
449
                var payAddr [32]byte
4✔
450
                copy(payAddr[:], req.GetPaymentAddr())
4✔
451

4✔
452
                invoiceRef = invoices.InvoiceRefByAddr(payAddr)
4✔
453

454
        case req.GetSetId() != nil &&
455
                req.LookupModifier == LookupModifier_HTLC_SET_ONLY:
4✔
456

4✔
457
                var setID [32]byte
4✔
458
                copy(setID[:], req.GetSetId())
4✔
459

4✔
460
                invoiceRef = invoices.InvoiceRefBySetIDFiltered(setID)
4✔
461

462
        case req.GetSetId() != nil:
×
463
                var setID [32]byte
×
464
                copy(setID[:], req.GetSetId())
×
465

×
466
                invoiceRef = invoices.InvoiceRefBySetID(setID)
×
467

468
        default:
×
469
                return nil, status.Error(codes.InvalidArgument,
×
470
                        "invoice ref must be set")
×
471
        }
472

473
        // Attempt to locate the invoice, returning a nice "not found" error if
474
        // we can't find it in the database.
475
        invoice, err := s.cfg.InvoiceRegistry.LookupInvoiceByRef(
4✔
476
                ctx, invoiceRef,
4✔
477
        )
4✔
478
        switch {
4✔
479
        case errors.Is(err, invoices.ErrInvoiceNotFound):
×
480
                return nil, status.Error(codes.NotFound, err.Error())
×
481
        case err != nil:
×
482
                return nil, err
×
483
        }
484

485
        rpcInvoice, err := CreateRPCInvoice(&invoice, s.cfg.ChainParams)
4✔
486
        if err != nil {
4✔
487
                return nil, err
×
488
        }
×
489

490
        // Give the aux data parser a chance to format the custom data in the
491
        // invoice HTLCs.
492
        err = s.cfg.ParseAuxData(rpcInvoice)
4✔
493
        if err != nil {
4✔
494
                return nil, fmt.Errorf("error parsing custom data: %w", err)
×
495
        }
×
496

497
        return rpcInvoice, nil
4✔
498
}
499

500
// HtlcModifier is a bidirectional streaming RPC that allows a client to
501
// intercept and modify the HTLCs that attempt to settle the given invoice. The
502
// server will send HTLCs of invoices to the client and the client can modify
503
// some aspects of the HTLC in order to pass the invoice acceptance tests.
504
func (s *Server) HtlcModifier(
505
        modifierServer Invoices_HtlcModifierServer) error {
4✔
506

4✔
507
        modifier := newHtlcModifier(s.cfg.ChainParams, modifierServer)
4✔
508
        reset, modifierQuit, err := s.cfg.HtlcModifier.RegisterInterceptor(
4✔
509
                modifier.onIntercept,
4✔
510
        )
4✔
511
        if err != nil {
8✔
512
                return fmt.Errorf("cannot register interceptor: %w", err)
4✔
513
        }
4✔
514

515
        defer reset()
4✔
516

4✔
517
        log.Debugf("Invoice HTLC modifier client connected")
4✔
518

4✔
519
        for {
8✔
520
                select {
4✔
521
                case <-modifierServer.Context().Done():
4✔
522
                        return modifierServer.Context().Err()
4✔
523

524
                case <-modifierQuit:
×
525
                        return ErrServerShuttingDown
×
526

527
                case <-s.quit:
×
528
                        return ErrServerShuttingDown
×
529
                }
530
        }
531
}
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