Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invoices: add subscribesingleinvoice #2356

Merged
merged 11 commits into from
Feb 2, 2019
Prev Previous commit
Next Next commit
invoicesrpc: add SubscribeSingleInvoice rpc
  • Loading branch information
joostjager committed Feb 1, 2019
commit 70c874be8810f83ddb26379d359fe198547f0f1f
15 changes: 15 additions & 0 deletions lnrpc/file_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lnrpc

import (
"os"
)

// FileExists reports whether the named file or directory exists.
func FileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
16 changes: 16 additions & 0 deletions lnrpc/invoicesrpc/config_active.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
package invoicesrpc

import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/macaroons"
)

// Config is the primary configuration struct for the invoices RPC server. It
Expand All @@ -12,5 +14,19 @@ import (
// configuration options, while if able to be populated, the latter fields MUST
// also be specified.
type Config struct {
// NetworkDir is the main network directory wherein the invoices rpc
// server will find the macaroon named DefaultInvoicesMacFilename.
NetworkDir string

// MacService is the main macaroon service that we'll use to handle
// authentication for the invoices rpc server.
MacService *macaroons.Service

joostjager marked this conversation as resolved.
Show resolved Hide resolved
// InvoiceRegistry is a central registry of all the outstanding invoices
// created by the daemon.
InvoiceRegistry *invoices.InvoiceRegistry

// ChainParams are required to properly decode invoice payment requests
// that are marshalled over rpc.
ChainParams *chaincfg.Params
}
104 changes: 90 additions & 14 deletions lnrpc/invoicesrpc/invoices.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions lnrpc/invoicesrpc/invoices.proto
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
syntax = "proto3";

import "google/api/annotations.proto";
import "rpc.proto";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right... this creates a dependency on the parent package. Would it make sense to re-define the Invoice type in this new package, if we plan to deprecate the type from lnrpc?

Copy link
Contributor Author

@joostjager joostjager Jan 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is code duplication, how does it make it better? I don't mind this as a temporary structure. Would you like it more if the dependency was reversed? So make the main rpc depend on invoices rpc?


package invoicesrpc;

option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc";

// Invoices is a service that can be used to create, accept, settle and cancel
// invoices.
service Invoices {
/**
SubscribeSingleInvoice returns a uni-directional stream (server -> client)
joostjager marked this conversation as resolved.
Show resolved Hide resolved
to notify the client of state transitions of the specified invoice.
Initially the current invoice state is always sent out.
*/
rpc SubscribeSingleInvoice (lnrpc.PaymentHash) returns (stream lnrpc.Invoice);
}

106 changes: 100 additions & 6 deletions lnrpc/invoicesrpc/invoices_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
package invoicesrpc

import (
"github.com/lightningnetwork/lnd/lnrpc"
"context"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
"io/ioutil"
"os"
"path/filepath"

"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
)

const (
Expand All @@ -17,8 +23,27 @@ const (
)

var (
// macaroonOps are the set of capabilities that our minted macaroon (if
// it doesn't already exist) will have.
macaroonOps = []bakery.Op{
{
Entity: "invoices",
Action: "read",
},
}

// macPermissions maps RPC calls to the permissions they require.
macPermissions = map[string][]bakery.Op{}
macPermissions = map[string][]bakery.Op{
"/invoicesrpc.Invoices/SubscribeSingleInvoice": {{
Entity: "invoices",
Action: "read",
}},
}

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

// Server is a sub-server of the main RPC server: the invoices RPC. This sub
Expand All @@ -28,6 +53,8 @@ type Server struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.

quit chan struct{}

cfg *Config
}

Expand All @@ -41,10 +68,42 @@ var _ InvoicesServer = (*Server)(nil)
// we'll create them on start up. If we're unable to locate, or create the
// macaroons we need, then we'll return with an error.
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
// We don't create any new macaroons for this subserver, instead reuse
// existing onchain/offchain permissions.
// If the path of the invoices macaroon wasn't specified, then we'll
// assume that it's found at the default network directory.
macFilePath := filepath.Join(
cfg.NetworkDir, DefaultInvoicesMacFilename,
)

// Now that we know the full path of the invoices macaroon, we can
// check to see if we need to create it or not.
if !lnrpc.FileExists(macFilePath) && cfg.MacService != nil {
log.Infof("Baking macaroons for invoices RPC Server at: %v",
macFilePath)

// At this point, we know that the invoices macaroon doesn't
// yet, exist, so we need to create it with the help of the
// main macaroon service.
invoicesMac, err := cfg.MacService.Oven.NewMacaroon(
context.Background(), bakery.LatestVersion, nil,
macaroonOps...,
)
if err != nil {
return nil, nil, err
}
invoicesMacBytes, err := invoicesMac.M().MarshalBinary()
if err != nil {
return nil, nil, err
}
err = ioutil.WriteFile(macFilePath, invoicesMacBytes, 0644)
if err != nil {
os.Remove(macFilePath)
return nil, nil, err
}
}

server := &Server{
cfg: cfg,
cfg: cfg,
quit: make(chan struct{}, 1),
}

return server, macPermissions, nil
Expand All @@ -61,6 +120,8 @@ func (s *Server) Start() error {
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Stop() error {
close(s.quit)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it'd be worth protecting this with a compare and swap to prevent us from accidentally closing the same channel twice, as it would lead us to panic during shutdown. Start should be fine though since it's a NOP

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling Stop() twice is invalid use of the subserver and adding the compare/swap would camouflage this. We would loose the opportunity to discover the bug of the caller. In general I'd like to avoid defensive code, unless we are dealing with legacy that has become too complicate to reason about and we want to take no risk.


return nil
}

Expand All @@ -82,8 +143,41 @@ func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error {
// all our methods are routed properly.
RegisterInvoicesServer(grpcServer, s)

log.Debugf("Invoices RPC server successfully register with root " +
log.Debugf("Invoices RPC server successfully registered with root " +
"gRPC server")

return nil
}

// SubscribeInvoices returns a uni-directional stream (server -> client) for
// notifying the client of invoice state changes.
func (s *Server) SubscribeSingleInvoice(req *lnrpc.PaymentHash,
updateStream Invoices_SubscribeSingleInvoiceServer) error {

hash, err := lntypes.NewHash(req.RHash)
if err != nil {
return err
}

invoiceClient := s.cfg.InvoiceRegistry.SubscribeSingleInvoice(*hash)
defer invoiceClient.Cancel()

for {
select {
case newInvoice := <-invoiceClient.Updates:
rpcInvoice, err := CreateRPCInvoice(
newInvoice, s.cfg.ChainParams,
)
if err != nil {
return err
}

if err := updateStream.Send(rpcInvoice); err != nil {
return err
}

case <-s.quit:
return nil
}
}
}
1 change: 1 addition & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
// server configuration struct.
err := subServerCgs.PopulateDependencies(
s.cc, networkDir, macService, atpl, invoiceRegistry,
activeNetParams.Params,
)
if err != nil {
return nil, err
Expand Down
Loading