invoicesrpc: add SubscribeSingleInvoice rpc

This commit is contained in:
Joost Jager 2019-01-03 19:15:14 +01:00
parent 4c4536a488
commit 70c874be88
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
7 changed files with 241 additions and 19 deletions

15
lnrpc/file_utils.go Normal file

@ -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
}

@ -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
@ -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
}

@ -6,6 +6,8 @@ package invoicesrpc // import "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import lnrpc "github.com/lightningnetwork/lnd/lnrpc"
import _ "google.golang.org/genproto/googleapis/api/annotations"
import (
context "golang.org/x/net/context"
@ -35,6 +37,11 @@ const _ = grpc.SupportPackageIsVersion4
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type InvoicesClient interface {
// *
// 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.
SubscribeSingleInvoice(ctx context.Context, in *lnrpc.PaymentHash, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error)
}
type invoicesClient struct {
@ -45,33 +52,102 @@ func NewInvoicesClient(cc *grpc.ClientConn) InvoicesClient {
return &invoicesClient{cc}
}
func (c *invoicesClient) SubscribeSingleInvoice(ctx context.Context, in *lnrpc.PaymentHash, opts ...grpc.CallOption) (Invoices_SubscribeSingleInvoiceClient, error) {
stream, err := c.cc.NewStream(ctx, &_Invoices_serviceDesc.Streams[0], "/invoicesrpc.Invoices/SubscribeSingleInvoice", opts...)
if err != nil {
return nil, err
}
x := &invoicesSubscribeSingleInvoiceClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Invoices_SubscribeSingleInvoiceClient interface {
Recv() (*lnrpc.Invoice, error)
grpc.ClientStream
}
type invoicesSubscribeSingleInvoiceClient struct {
grpc.ClientStream
}
func (x *invoicesSubscribeSingleInvoiceClient) Recv() (*lnrpc.Invoice, error) {
m := new(lnrpc.Invoice)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// InvoicesServer is the server API for Invoices service.
type InvoicesServer interface {
// *
// 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.
SubscribeSingleInvoice(*lnrpc.PaymentHash, Invoices_SubscribeSingleInvoiceServer) error
}
func RegisterInvoicesServer(s *grpc.Server, srv InvoicesServer) {
s.RegisterService(&_Invoices_serviceDesc, srv)
}
func _Invoices_SubscribeSingleInvoice_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(lnrpc.PaymentHash)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(InvoicesServer).SubscribeSingleInvoice(m, &invoicesSubscribeSingleInvoiceServer{stream})
}
type Invoices_SubscribeSingleInvoiceServer interface {
Send(*lnrpc.Invoice) error
grpc.ServerStream
}
type invoicesSubscribeSingleInvoiceServer struct {
grpc.ServerStream
}
func (x *invoicesSubscribeSingleInvoiceServer) Send(m *lnrpc.Invoice) error {
return x.ServerStream.SendMsg(m)
}
var _Invoices_serviceDesc = grpc.ServiceDesc{
ServiceName: "invoicesrpc.Invoices",
HandlerType: (*InvoicesServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "SubscribeSingleInvoice",
Handler: _Invoices_SubscribeSingleInvoice_Handler,
ServerStreams: true,
},
},
Metadata: "invoicesrpc/invoices.proto",
}
func init() {
proto.RegisterFile("invoicesrpc/invoices.proto", fileDescriptor_invoices_560fa62749d29606)
proto.RegisterFile("invoicesrpc/invoices.proto", fileDescriptor_invoices_c6414974947f2940)
}
var fileDescriptor_invoices_560fa62749d29606 = []byte{
// 105 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0xcc, 0x2b, 0xcb,
0xcf, 0x4c, 0x4e, 0x2d, 0x2e, 0x2a, 0x48, 0xd6, 0x87, 0xb1, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2,
0x85, 0xb8, 0x91, 0xe4, 0x8c, 0xb8, 0xb8, 0x38, 0x3c, 0xa1, 0x5c, 0x27, 0xe3, 0x28, 0xc3, 0xf4,
0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0xbc,
0xcc, 0xbc, 0xf4, 0xbc, 0xd4, 0x92, 0xf2, 0xfc, 0xa2, 0x6c, 0xfd, 0x9c, 0xbc, 0x14, 0xfd, 0x9c,
0x3c, 0x64, 0x03, 0x8b, 0x0a, 0x92, 0x93, 0xd8, 0xc0, 0x86, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff,
0xff, 0xaa, 0xe0, 0xa9, 0x52, 0x72, 0x00, 0x00, 0x00,
var fileDescriptor_invoices_c6414974947f2940 = []byte{
// 177 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0xb1, 0x8e, 0xc2, 0x30,
0x10, 0x44, 0x75, 0xcd, 0xe9, 0x2e, 0x27, 0x5d, 0xe1, 0x82, 0xc2, 0xe2, 0x1b, 0xb2, 0x40, 0x7a,
0x0a, 0x2a, 0xa0, 0x42, 0x4a, 0x47, 0x67, 0x1b, 0xcb, 0x59, 0xe1, 0xec, 0x5a, 0xce, 0x06, 0xc4,
0xdf, 0x23, 0x82, 0x91, 0xd2, 0x8d, 0x66, 0xe6, 0x49, 0xaf, 0xd2, 0x48, 0x37, 0x46, 0xe7, 0x87,
0x9c, 0x1c, 0x7c, 0x72, 0x9d, 0x32, 0x0b, 0xab, 0xbf, 0xd9, 0xa6, 0x97, 0x81, 0x39, 0x44, 0x0f,
0x26, 0x21, 0x18, 0x22, 0x16, 0x23, 0xc8, 0x54, 0xae, 0xfa, 0x37, 0x27, 0xf7, 0x8e, 0x9b, 0x63,
0xf5, 0x73, 0x28, 0x9c, 0xda, 0x56, 0x8b, 0x76, 0xb4, 0x83, 0xcb, 0x68, 0x7d, 0x8b, 0x14, 0xa2,
0x2f, 0x93, 0x52, 0x75, 0xa4, 0x17, 0x73, 0x32, 0x8f, 0xde, 0x93, 0xec, 0xcd, 0xd0, 0xe9, 0xff,
0xd2, 0x95, 0xcf, 0xea, 0x6b, 0xd7, 0x9c, 0xd7, 0x01, 0xa5, 0x1b, 0x6d, 0xed, 0xb8, 0x87, 0x88,
0xa1, 0x13, 0x42, 0x0a, 0xe4, 0xe5, 0xce, 0xf9, 0x0a, 0x91, 0x2e, 0x30, 0x21, 0x30, 0x33, 0xb5,
0xdf, 0x93, 0x47, 0xf3, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xcf, 0x4e, 0x97, 0xf2, 0xdb, 0x00, 0x00,
0x00,
}

@ -1,5 +1,8 @@
syntax = "proto3";
import "google/api/annotations.proto";
import "rpc.proto";
package invoicesrpc;
option go_package = "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc";
@ -7,5 +10,11 @@ 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);
}

@ -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 (
@ -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
@ -28,6 +53,8 @@ type Server struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
quit chan struct{}
cfg *Config
}
@ -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,
quit: make(chan struct{}, 1),
}
return server, macPermissions, nil
@ -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
}
@ -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
}
}
}

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

@ -4,6 +4,7 @@ import (
"fmt"
"reflect"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
@ -54,7 +55,8 @@ type subRPCServerConfigs struct {
func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
networkDir string, macService *macaroons.Service,
atpl *autopilot.Manager,
invoiceRegistry *invoices.InvoiceRegistry) error {
invoiceRegistry *invoices.InvoiceRegistry,
activeNetParams *chaincfg.Params) error {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
@ -135,9 +137,18 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl,
case *invoicesrpc.Config:
subCfgValue := extractReflectValue(cfg)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("InvoiceRegistry").Set(
reflect.ValueOf(invoiceRegistry),
)
subCfgValue.FieldByName("ChainParams").Set(
reflect.ValueOf(activeNetParams),
)
default:
return fmt.Errorf("unknown field: %v, %T", fieldName,