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

lnrpc+lnd+rpc: add new Signer RPC service, and sub RPC server infrastructure #2081

Merged
merged 14 commits into from
Nov 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/tor"
Expand Down Expand Up @@ -220,6 +221,8 @@ type config struct {

Tor *torConfig `group:"Tor" namespace:"tor"`

SubRPCServers *subRPCServerConfigs `group:"subrpc"`
Copy link
Contributor

Choose a reason for hiding this comment

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

squash changes with relevant commits.


Hodl *hodl.Config `group:"hodl" namespace:"hodl"`

NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
Expand Down Expand Up @@ -295,6 +298,9 @@ func loadConfig() (*config, error) {
},
MaxPendingChannels: defaultMaxPendingChannels,
NoSeedBackup: defaultNoSeedBackup,
SubRPCServers: &subRPCServerConfigs{
SignRPC: &signrpc.Config{},
},
Autopilot: &autoPilotConfig{
MaxChannels: 5,
Allocation: 0.6,
Expand Down
66 changes: 10 additions & 56 deletions lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,66 +312,20 @@ func lndMain() error {
return err
}

// Check macaroon authentication if macaroons aren't disabled.
if macaroonService != nil {
serverOpts = append(serverOpts,
grpc.UnaryInterceptor(macaroonService.
UnaryServerInterceptor(permissions)),
grpc.StreamInterceptor(macaroonService.
StreamServerInterceptor(permissions)),
)
}

// Initialize, and register our implementation of the gRPC interface
// exported by the rpcServer.
rpcServer := newRPCServer(server)
if err := rpcServer.Start(); err != nil {
return err
}
defer rpcServer.Stop()

grpcServer := grpc.NewServer(serverOpts...)
lnrpc.RegisterLightningServer(grpcServer, rpcServer)

// Next, Start the gRPC server listening for HTTP/2 connections.
for _, listener := range cfg.RPCListeners {
lis, err := lncfg.ListenOnAddress(listener)
if err != nil {
ltndLog.Errorf(
"RPC server unable to listen on %s", listener,
)
return err
}
defer lis.Close()
go func() {
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
grpcServer.Serve(lis)
}()
}

// Finally, start the REST proxy for our gRPC server above.
mux := proxy.NewServeMux()
err = lnrpc.RegisterLightningHandlerFromEndpoint(
ctx, mux, cfg.RPCListeners[0].String(), proxyOpts,
rpcServer, err := newRPCServer(
server, macaroonService, cfg.SubRPCServers, serverOpts,
proxyOpts, tlsConf,
)
if err != nil {
srvrLog.Errorf("unable to start RPC server: %v", err)
return err
}
for _, restEndpoint := range cfg.RESTListeners {
lis, err := lncfg.TLSListenOnAddress(restEndpoint, tlsConf)
if err != nil {
ltndLog.Errorf(
"gRPC proxy unable to listen on %s",
restEndpoint,
)
return err
}
defer lis.Close()
go func() {
rpcsLog.Infof("gRPC proxy started at %s", lis.Addr())
http.Serve(lis, mux)
}()
if err := rpcServer.Start(); err != nil {
return err
}
defer rpcServer.Stop()

// If we're not in simnet mode, We'll wait until we're fully synced to
// continue the start up of the remainder of the daemon. This ensures
Expand Down Expand Up @@ -602,9 +556,9 @@ func genCertPair(certFile, keyFile string) error {
return nil
}

// genMacaroons generates three macaroon files; one admin-level, one
// for invoice access and one read-only. These can also be used
// to generate more granular macaroons.
// genMacaroons generates three macaroon files; one admin-level, one for
// invoice access and one read-only. These can also be used to generate more
// granular macaroons.
func genMacaroons(ctx context.Context, svc *macaroons.Service,
Copy link
Contributor

@joostjager joostjager Oct 29, 2018

Choose a reason for hiding this comment

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

How to generate an admin macaroon that can access all sub services? In signer, a new entity and action is introduced. I think admin should be able to have permissions to access signer?

Hack: joostjager@61b6453

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah the only one that that makes a new namespace is the signer. We can include this in the admin macaroon, but then that would invalidate all existing created admin macaroons. We can do this eventually IMO, but as this is behind an experimental build flag, I'd prefer now to.

Copy link
Contributor

@joostjager joostjager Oct 31, 2018

Choose a reason for hiding this comment

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

So the solution then now is to use two macaroons from the client (admin and signer)? I think this also means that two tcp connections need to be created?

Copy link
Member Author

Choose a reason for hiding this comment

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

It should be possible to do it with a single TCP connection. I'd need to double check the docs, but it should be possible to switch to a per-call credential model, rather than a connection wide credential mode as we use widely atm.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this something that can be controlled client side, or is a change on the server needed?

Copy link
Contributor

Choose a reason for hiding this comment

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

I did a quick try passing multiple macaroon credentials to the dial call, but that is not going to work. PerRPCCredentials, didn't try that, but it also means we need to pass around those macaroons. Is it worth the effort at this point? Otherwise I'd say, just make signing possible with the admin macaroon for now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Why do you need to pass multiple macaroon credentials? You should only need to pass the signer.macaroon if you're interacting with the signer sub-server. The systems that interact w/ the sub-servers will need to get used to managing their various macaroons, as we can easily implemented domain isolation using them. For example, merchants are already used to using the invoice.macaroon everywhere.

It's possible to create custom macarons that can access only what's needed (so signer and new address for example), as there's an open PR for this, but it isn't yet merged.

Copy link
Contributor

Choose a reason for hiding this comment

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

So I need to open multiple tcp connections then? Swaplet needs signer, walletkit and the main rpc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Possibly, will look into if we can modify the way we manually set the credentials and get back to you!

admFile, roFile, invoiceFile string) error {

Expand Down
17 changes: 15 additions & 2 deletions lnrpc/gen_protos.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/bin/sh

echo "Generating root gRPC server protos"

# Generate the protos.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
rpc.proto



# Generate the REST reverse proxy.
protoc -I/usr/local/include -I. \
-I$GOPATH/src \
Expand All @@ -22,3 +22,16 @@ protoc -I/usr/local/include -I. \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
rpc.proto

# For each of the sub-servers, we then generate their protos, but a restricted
# set as they don't yet require REST proxies, or swagger docs.
for file in **/*.proto
do
DIRECTORY=$(dirname ${file})
echo "Generating protos from ${file}, into ${DIRECTORY}"

protoc -I/usr/local/include -I. \
-I$GOPATH/src \
--go_out=plugins=grpc:. \
${file}
done
20 changes: 15 additions & 5 deletions lnrpc/rpc.pb.go

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

33 changes: 33 additions & 0 deletions lnrpc/signrpc/config_active.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// +build signrpc
Copy link
Contributor

Choose a reason for hiding this comment

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

This commit should be squashed with the first.


package signrpc

import (
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/macaroons"
)

// Config is the primary configuration struct for the signer RPC server. It
// contains all the items required for the signer rpc server to carry out its
// duties. The fields with struct tags are meant to be parsed as normal
// configuration options, while if able to be populated, the latter fields MUST
// also be specified.
type Config struct {
// SignerMacPath is the path for the signer macaroon. If unspecified
// then we assume that the macaroon will be found under the network
// directory, named DefaultSignerMacFilename.
SignerMacPath string `long:"signermacaroonpath" description:"Path to the signer macaroon"`

// NetworkDir is the main network directory wherein the signer rpc
// server will find the macaroon named DefaultSignerMacFilename.
NetworkDir string

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

// Signer is the signer instance that backs the signer RPC server. The
// job of the signer RPC server is simply to proxy valid requests to
// the active signer instance.
Signer lnwallet.Signer
}
6 changes: 6 additions & 0 deletions lnrpc/signrpc/config_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// +build !signrpc

package signrpc

// Config is empty for non-signrpc builds.
type Config struct{}
71 changes: 71 additions & 0 deletions lnrpc/signrpc/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// +build signrpc

package signrpc

import (
"fmt"

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

// createNewSubServer is a helper method that will create the new signer sub
// server given the main config dispatcher method. If we're unable to find the
// config that is meant for us in the config dispatcher, then we'll exit with
// an error.
func createNewSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {

// We'll attempt to look up the config that we expect, according to our
// subServerName name. If we can't find this, then we'll exit with an
// error, as we're unable to properly initialize ourselves without this
// config.
signServerConf, ok := configRegistry.FetchConfig(subServerName)
if !ok {
return nil, nil, fmt.Errorf("unable to find config for "+
"subserver type %s", subServerName)
}

// Now that we've found an object mapping to our service name, we'll
// ensure that it's the type we need.
config, ok := signServerConf.(*Config)
if !ok {
return nil, nil, fmt.Errorf("wrong type of config for "+
"subserver %s, expected %T got %T", subServerName,
&Config{}, signServerConf)
}

// Before we try to make the new signer service instance, we'll perform
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be squashed with the commit where driver was introduced.

// some sanity checks on the arguments to ensure that they're useable.

switch {
// If the macaroon service is set (we should use macaroons), then
// ensure that we know where to look for them, or create them if not
// found.
case config.MacService != nil && config.NetworkDir == "":
return nil, nil, fmt.Errorf("NetworkDir must be set to create " +
"Signrpc")
case config.Signer == nil:
return nil, nil, fmt.Errorf("Signer must be set to create " +
"Signrpc")
}

return New(config)
}

func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
New: func(c lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {

return createNewSubServer(c)
},
}

// If the build tag is active, then we'll register ourselves as a
// sub-RPC server within the global lnrpc package namespace.
if err := lnrpc.RegisterSubServer(subServer); err != nil {
panic(fmt.Sprintf("failed to register sub server driver '%s': %v",
subServerName, err))
}
}
45 changes: 45 additions & 0 deletions lnrpc/signrpc/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package signrpc
halseth marked this conversation as resolved.
Show resolved Hide resolved

import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)

// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger

// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("SGNR", nil))
}

// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}

// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}

// logClosure is used to provide a closure over expensive logging operations so
// don't have to be performed when the logging level doesn't warrant it.
type logClosure func() string

// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}

// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}
Loading