config+rpc: create new generalized dynamic sub rpc server config framework

In this commit, we add the glue infrastructure to make the sub RPC
server system work properly. Our high level goal is the following: using
only the lnrpc package (with no visibility into the sub RPC servers),
the RPC server is able to find, create, run, and manage the entire set
of present and future sub RPC servers. In order to achieve this, we use
the reflect package and build tags heavily to permit a loosely coupled
configuration parsing system for the sub RPC servers.

We start with a new `subRpcServerConfigs` struct which is _always_
present.  This struct has its own group, and will house a series of
sub-configs, one for each sub RPC server. Each sub-config is actually
gated behind a build flag, and can be used to allow users on the command
line or in the config to specify arguments related to the sub-server. If
the config isn't present, then we don't attempt to parse it at all, if
it is, then that means the RPC server has been registered, and we should
parse the contents of its config.

The `subRpcServerConfigs` struct has two main methods:
`PopulateDependancies` and `FetchConfig`. The `PopulateDependancies` is
used to dynamically locate and set the config fields for each new
sub-server. As the config may not actually have any fields (if the build
flag is off), we use the reflect pacakge to determine if things are
compiled in or not, then if so, we dynamically set each of the config
parameters. The `PopulateDependancies` method implements the
`lnrpc.SubServerConfigDispatcher` interface. Our goal is to allow sub
servers to look up their actual config in this main config struct. We
achieve this by using reflect to look up the target field _as if it were
a key in a map_. If the field is found, then we check if it has any
actual attributes (it won't if the build flag is off), if it is, then we
return it as we expect it to be populated already.
This commit is contained in:
Olaoluwa Osuntokun 2018-10-22 17:58:30 -07:00
parent 16d16cf1b6
commit 4002caec69
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
3 changed files with 137 additions and 3 deletions

@ -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"
@ -220,6 +221,8 @@ type config struct {
Tor *torConfig `group:"Tor" namespace:"tor"`
SubRpcServers *subRpcServerConfigs `group:"subrpc"`
Hodl *hodl.Config `group:"hodl" namespace:"hodl"`
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
@ -295,6 +298,9 @@ func loadConfig() (*config, error) {
},
MaxPendingChannels: defaultMaxPendingChannels,
NoSeedBackup: defaultNoSeedBackup,
SubRpcServers: &subRpcServerConfigs{
SignRPC: &signrpc.Config{},
},
Autopilot: &autoPilotConfig{
MaxChannels: 5,
Allocation: 0.6,

@ -1,4 +1,4 @@
// +build signerrpc
// +build signrpc
package signrpc
@ -16,7 +16,7 @@ import (
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
grpc "google.golang.org/grpc"
"google.golang.org/grpc"
"gopkg.in/macaroon-bakery.v2/bakery"
)
@ -75,7 +75,7 @@ func fileExists(name string) bool {
}
// New returns a new instance of the signrpc Signer sub-server. We also return
// the set of permission s for the macaroons that we may create within this
// the set of permissions for the macaroons that we may create within this
// method. If the macaroons we need aren't found in the filepath, then 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.

128
subrpcserver_config.go Normal file

@ -0,0 +1,128 @@
package main
import (
"fmt"
"reflect"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/macaroons"
)
// subRpcServerConfigs is special sub-config in the main configuration that
// houses the configuration for the optional sub-servers. These sub-RPC servers
// are meant to house experimental new features that may eventually make it
// into the main RPC server that lnd exposes. Special methods are present on
// this struct to allow the main RPC server to create and manipulate the
// sub-RPC servers in a generalized manner.
type subRpcServerConfigs struct {
// SignRPC is a sub-RPC server that exposes signing of arbitrary inputs
// as a gRPC service.
SignRPC *signrpc.Config `group:"signrpc" namespace:"signrpc"`
}
// PopulateDependancies attempts to iterate through all the sub-server configs
// within this struct, and populate the items it requires based on the main
// configuration file, and the chain control.
//
// NOTE: This MUST be called before any callers are permitted to execute the
// FetchConfig method.
func (s *subRpcServerConfigs) PopulateDependancies(cc *chainControl,
networkDir string, macService *macaroons.Service) error {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
selfVal := extractReflectValue(s)
selfType := selfVal.Type()
numFields := selfVal.NumField()
for i := 0; i < numFields; i++ {
field := selfVal.Field(i)
fieldElem := field.Elem()
fieldName := selfType.Field(i).Name
ltndLog.Debugf("Populating dependencies for sub RPC "+
"server: %v", fieldName)
// If this sub-config doesn't actually have any fields, then we
// can skip it, as the build tag for it is likely off.
if fieldElem.NumField() == 0 {
continue
}
if !fieldElem.CanSet() {
continue
}
switch cfg := field.Interface().(type) {
case *signrpc.Config:
subCfgValue := extractReflectValue(cfg)
subCfgValue.FieldByName("MacService").Set(
reflect.ValueOf(macService),
)
subCfgValue.FieldByName("NetworkDir").Set(
reflect.ValueOf(networkDir),
)
subCfgValue.FieldByName("Signer").Set(
reflect.ValueOf(cc.signer),
)
default:
return fmt.Errorf("unknown field: %v, %T", fieldName,
cfg)
}
}
return nil
}
// FetchConfig attempts to locate an existing configuration file mapped to the
// target sub-server. If we're unable to find a config file matching the
// subServerName name, then false will be returned for the second parameter.
//
// NOTE: Part of the lnrpc.SubServerConfigDispatcher interface.
func (s *subRpcServerConfigs) FetchConfig(subServerName string) (interface{}, bool) {
// First, we'll use reflect to obtain a version of the config struct
// that allows us to programmatically inspect its fields.
selfVal := extractReflectValue(s)
// Now that we have the value of the struct, we can check to see if it
// has an attribute with the same name as the subServerName.
configVal := selfVal.FieldByName(subServerName)
configValElem := configVal.Elem()
// We'll now ensure that this field actually exists in this value. If
// not, then we'll return false for the ok value to indicate to the
// caller that this field doesn't actually exist.
if !configVal.IsValid() {
return nil, false
}
// If a config of this type is found, it doesn't have any fields, then
// it's the same as if it wasn't present. This can happen if the build
// tag for the sub-server is inactive.
if configValElem.NumField() == 0 {
return nil, false
}
// At this pint, we know that the field is actually present in the
// config struct, so we can return it directly.
return configVal.Interface(), true
}
// extractReflectValue attempts to extract the value from an interface using
// the reflect package. The resulting reflect.Value allows the caller to
// programmatically examine and manipulate the underlying value.
func extractReflectValue(instance interface{}) reflect.Value {
var val reflect.Value
// If the type of the instance is a pointer, then we need to deference
// the pointer one level to get its value. Otherwise, we can access the
// value directly.
if reflect.TypeOf(instance).Kind() == reflect.Ptr {
val = reflect.ValueOf(instance).Elem()
} else {
val = reflect.ValueOf(instance)
}
return val
}