Skip to content

Commit

Permalink
invoicesrpc: add SubscribeSingleInvoice rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
joostjager committed Feb 1, 2019
1 parent 4c4536a commit 70c874b
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 21 deletions.
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

// 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";

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)
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)

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

0 comments on commit 70c874b

Please sign in to comment.