Merge pull request #2081 from Roasbeef/signer-service
lnrpc+lnd+rpc: add new Signer RPC service, and sub RPC server infrastructure
This commit is contained in:
commit
fc4fe07010
@ -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,
|
||||
|
68
lnd.go
68
lnd.go
@ -312,67 +312,21 @@ 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)
|
||||
rpcServer, err := newRPCServer(
|
||||
server, macaroonService, cfg.SubRPCServers, serverOpts,
|
||||
proxyOpts, tlsConf,
|
||||
)
|
||||
if err != nil {
|
||||
srvrLog.Errorf("unable to start RPC server: %v", err)
|
||||
return err
|
||||
}
|
||||
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,
|
||||
)
|
||||
if err != nil {
|
||||
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 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
|
||||
// that we don't accept any possibly invalid state transitions, or
|
||||
@ -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,
|
||||
admFile, roFile, invoiceFile string) error {
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Generating root gRPC server protos"
|
||||
|
||||
# Generate the protos.
|
||||
protoc -I/usr/local/include -I. \
|
||||
-I$GOPATH/src \
|
||||
@ -7,8 +9,6 @@ protoc -I/usr/local/include -I. \
|
||||
--go_out=plugins=grpc:. \
|
||||
rpc.proto
|
||||
|
||||
|
||||
|
||||
# Generate the REST reverse proxy.
|
||||
protoc -I/usr/local/include -I. \
|
||||
-I$GOPATH/src \
|
||||
@ -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
|
||||
|
@ -522,7 +522,9 @@ func (m *FeeLimit) String() string { return proto.CompactTextString(m
|
||||
func (*FeeLimit) ProtoMessage() {}
|
||||
func (*FeeLimit) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
|
||||
|
||||
type isFeeLimit_Limit interface{ isFeeLimit_Limit() }
|
||||
type isFeeLimit_Limit interface {
|
||||
isFeeLimit_Limit()
|
||||
}
|
||||
|
||||
type FeeLimit_Fixed struct {
|
||||
Fixed int64 `protobuf:"varint,1,opt,name=fixed,oneof"`
|
||||
@ -789,7 +791,9 @@ func (m *ChannelPoint) String() string { return proto.CompactTextStri
|
||||
func (*ChannelPoint) ProtoMessage() {}
|
||||
func (*ChannelPoint) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
|
||||
|
||||
type isChannelPoint_FundingTxid interface{ isChannelPoint_FundingTxid() }
|
||||
type isChannelPoint_FundingTxid interface {
|
||||
isChannelPoint_FundingTxid()
|
||||
}
|
||||
|
||||
type ChannelPoint_FundingTxidBytes struct {
|
||||
FundingTxidBytes []byte `protobuf:"bytes,1,opt,name=funding_txid_bytes,proto3,oneof"`
|
||||
@ -2046,7 +2050,9 @@ func (m *CloseStatusUpdate) String() string { return proto.CompactTex
|
||||
func (*CloseStatusUpdate) ProtoMessage() {}
|
||||
func (*CloseStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} }
|
||||
|
||||
type isCloseStatusUpdate_Update interface{ isCloseStatusUpdate_Update() }
|
||||
type isCloseStatusUpdate_Update interface {
|
||||
isCloseStatusUpdate_Update()
|
||||
}
|
||||
|
||||
type CloseStatusUpdate_ClosePending struct {
|
||||
ClosePending *PendingUpdate `protobuf:"bytes,1,opt,name=close_pending,oneof"`
|
||||
@ -2327,7 +2333,9 @@ func (m *OpenStatusUpdate) String() string { return proto.CompactText
|
||||
func (*OpenStatusUpdate) ProtoMessage() {}
|
||||
func (*OpenStatusUpdate) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} }
|
||||
|
||||
type isOpenStatusUpdate_Update interface{ isOpenStatusUpdate_Update() }
|
||||
type isOpenStatusUpdate_Update interface {
|
||||
isOpenStatusUpdate_Update()
|
||||
}
|
||||
|
||||
type OpenStatusUpdate_ChanPending struct {
|
||||
ChanPending *PendingUpdate `protobuf:"bytes,1,opt,name=chan_pending,oneof"`
|
||||
@ -4668,7 +4676,9 @@ func (m *PolicyUpdateRequest) String() string { return proto.CompactT
|
||||
func (*PolicyUpdateRequest) ProtoMessage() {}
|
||||
func (*PolicyUpdateRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{102} }
|
||||
|
||||
type isPolicyUpdateRequest_Scope interface{ isPolicyUpdateRequest_Scope() }
|
||||
type isPolicyUpdateRequest_Scope interface {
|
||||
isPolicyUpdateRequest_Scope()
|
||||
}
|
||||
|
||||
type PolicyUpdateRequest_Global struct {
|
||||
Global bool `protobuf:"varint,1,opt,name=global,oneof"`
|
||||
|
33
lnrpc/signrpc/config_active.go
Normal file
33
lnrpc/signrpc/config_active.go
Normal file
@ -0,0 +1,33 @@
|
||||
// +build signrpc
|
||||
|
||||
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
lnrpc/signrpc/config_default.go
Normal file
6
lnrpc/signrpc/config_default.go
Normal file
@ -0,0 +1,6 @@
|
||||
// +build !signrpc
|
||||
|
||||
package signrpc
|
||||
|
||||
// Config is empty for non-signrpc builds.
|
||||
type Config struct{}
|
71
lnrpc/signrpc/driver.go
Normal file
71
lnrpc/signrpc/driver.go
Normal file
@ -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
|
||||
// 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
lnrpc/signrpc/log.go
Normal file
45
lnrpc/signrpc/log.go
Normal file
@ -0,0 +1,45 @@
|
||||
package signrpc
|
||||
|
||||
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)
|
||||
}
|
588
lnrpc/signrpc/signer.pb.go
Normal file
588
lnrpc/signrpc/signer.pb.go
Normal file
@ -0,0 +1,588 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: signrpc/signer.proto
|
||||
|
||||
/*
|
||||
Package signrpc is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
signrpc/signer.proto
|
||||
|
||||
It has these top-level messages:
|
||||
KeyLocator
|
||||
KeyDescriptor
|
||||
TxOut
|
||||
SignDescriptor
|
||||
SignReq
|
||||
SignResp
|
||||
InputScript
|
||||
InputScriptResp
|
||||
*/
|
||||
package signrpc
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type KeyLocator struct {
|
||||
// / The family of key being identified.
|
||||
KeyFamily int32 `protobuf:"varint,1,opt,name=key_family,json=keyFamily" json:"key_family,omitempty"`
|
||||
// / The precise index of the key being identified.
|
||||
KeyIndex int32 `protobuf:"varint,2,opt,name=key_index,json=keyIndex" json:"key_index,omitempty"`
|
||||
}
|
||||
|
||||
func (m *KeyLocator) Reset() { *m = KeyLocator{} }
|
||||
func (m *KeyLocator) String() string { return proto.CompactTextString(m) }
|
||||
func (*KeyLocator) ProtoMessage() {}
|
||||
func (*KeyLocator) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *KeyLocator) GetKeyFamily() int32 {
|
||||
if m != nil {
|
||||
return m.KeyFamily
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *KeyLocator) GetKeyIndex() int32 {
|
||||
if m != nil {
|
||||
return m.KeyIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type KeyDescriptor struct {
|
||||
// Types that are valid to be assigned to Key:
|
||||
// *KeyDescriptor_RawKeyBytes
|
||||
// *KeyDescriptor_KeyLoc
|
||||
Key isKeyDescriptor_Key `protobuf_oneof:"key"`
|
||||
}
|
||||
|
||||
func (m *KeyDescriptor) Reset() { *m = KeyDescriptor{} }
|
||||
func (m *KeyDescriptor) String() string { return proto.CompactTextString(m) }
|
||||
func (*KeyDescriptor) ProtoMessage() {}
|
||||
func (*KeyDescriptor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
type isKeyDescriptor_Key interface {
|
||||
isKeyDescriptor_Key()
|
||||
}
|
||||
|
||||
type KeyDescriptor_RawKeyBytes struct {
|
||||
RawKeyBytes []byte `protobuf:"bytes,1,opt,name=raw_key_bytes,json=rawKeyBytes,proto3,oneof"`
|
||||
}
|
||||
type KeyDescriptor_KeyLoc struct {
|
||||
KeyLoc *KeyLocator `protobuf:"bytes,2,opt,name=key_loc,json=keyLoc,oneof"`
|
||||
}
|
||||
|
||||
func (*KeyDescriptor_RawKeyBytes) isKeyDescriptor_Key() {}
|
||||
func (*KeyDescriptor_KeyLoc) isKeyDescriptor_Key() {}
|
||||
|
||||
func (m *KeyDescriptor) GetKey() isKeyDescriptor_Key {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KeyDescriptor) GetRawKeyBytes() []byte {
|
||||
if x, ok := m.GetKey().(*KeyDescriptor_RawKeyBytes); ok {
|
||||
return x.RawKeyBytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KeyDescriptor) GetKeyLoc() *KeyLocator {
|
||||
if x, ok := m.GetKey().(*KeyDescriptor_KeyLoc); ok {
|
||||
return x.KeyLoc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XXX_OneofFuncs is for the internal use of the proto package.
|
||||
func (*KeyDescriptor) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
|
||||
return _KeyDescriptor_OneofMarshaler, _KeyDescriptor_OneofUnmarshaler, _KeyDescriptor_OneofSizer, []interface{}{
|
||||
(*KeyDescriptor_RawKeyBytes)(nil),
|
||||
(*KeyDescriptor_KeyLoc)(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func _KeyDescriptor_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
|
||||
m := msg.(*KeyDescriptor)
|
||||
// key
|
||||
switch x := m.Key.(type) {
|
||||
case *KeyDescriptor_RawKeyBytes:
|
||||
b.EncodeVarint(1<<3 | proto.WireBytes)
|
||||
b.EncodeRawBytes(x.RawKeyBytes)
|
||||
case *KeyDescriptor_KeyLoc:
|
||||
b.EncodeVarint(2<<3 | proto.WireBytes)
|
||||
if err := b.EncodeMessage(x.KeyLoc); err != nil {
|
||||
return err
|
||||
}
|
||||
case nil:
|
||||
default:
|
||||
return fmt.Errorf("KeyDescriptor.Key has unexpected type %T", x)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _KeyDescriptor_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
|
||||
m := msg.(*KeyDescriptor)
|
||||
switch tag {
|
||||
case 1: // key.raw_key_bytes
|
||||
if wire != proto.WireBytes {
|
||||
return true, proto.ErrInternalBadWireType
|
||||
}
|
||||
x, err := b.DecodeRawBytes(true)
|
||||
m.Key = &KeyDescriptor_RawKeyBytes{x}
|
||||
return true, err
|
||||
case 2: // key.key_loc
|
||||
if wire != proto.WireBytes {
|
||||
return true, proto.ErrInternalBadWireType
|
||||
}
|
||||
msg := new(KeyLocator)
|
||||
err := b.DecodeMessage(msg)
|
||||
m.Key = &KeyDescriptor_KeyLoc{msg}
|
||||
return true, err
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func _KeyDescriptor_OneofSizer(msg proto.Message) (n int) {
|
||||
m := msg.(*KeyDescriptor)
|
||||
// key
|
||||
switch x := m.Key.(type) {
|
||||
case *KeyDescriptor_RawKeyBytes:
|
||||
n += proto.SizeVarint(1<<3 | proto.WireBytes)
|
||||
n += proto.SizeVarint(uint64(len(x.RawKeyBytes)))
|
||||
n += len(x.RawKeyBytes)
|
||||
case *KeyDescriptor_KeyLoc:
|
||||
s := proto.Size(x.KeyLoc)
|
||||
n += proto.SizeVarint(2<<3 | proto.WireBytes)
|
||||
n += proto.SizeVarint(uint64(s))
|
||||
n += s
|
||||
case nil:
|
||||
default:
|
||||
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type TxOut struct {
|
||||
// / The value of the output being spent.
|
||||
Value int64 `protobuf:"varint,1,opt,name=value" json:"value,omitempty"`
|
||||
// / The script of the output being spent.
|
||||
PkScript []byte `protobuf:"bytes,2,opt,name=pk_script,json=pkScript,proto3" json:"pk_script,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TxOut) Reset() { *m = TxOut{} }
|
||||
func (m *TxOut) String() string { return proto.CompactTextString(m) }
|
||||
func (*TxOut) ProtoMessage() {}
|
||||
func (*TxOut) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *TxOut) GetValue() int64 {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *TxOut) GetPkScript() []byte {
|
||||
if m != nil {
|
||||
return m.PkScript
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SignDescriptor struct {
|
||||
// *
|
||||
// A descriptor that precisely describes *which* key to use for signing. This
|
||||
// may provide the raw public key directly, or require the Signer to re-derive
|
||||
// the key according to the populated derivation path.
|
||||
KeyDesc *KeyDescriptor `protobuf:"bytes,1,opt,name=key_desc,json=keyDesc" json:"key_desc,omitempty"`
|
||||
// *
|
||||
// A scalar value that will be added to the private key corresponding to the
|
||||
// above public key to obtain the private key to be used to sign this input.
|
||||
// This value is typically derived via the following computation:
|
||||
//
|
||||
// derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N
|
||||
SingleTweak []byte `protobuf:"bytes,2,opt,name=single_tweak,json=singleTweak,proto3" json:"single_tweak,omitempty"`
|
||||
// *
|
||||
// A private key that will be used in combination with its corresponding
|
||||
// private key to derive the private key that is to be used to sign the target
|
||||
// input. Within the Lightning protocol, this value is typically the
|
||||
// commitment secret from a previously revoked commitment transaction. This
|
||||
// value is in combination with two hash values, and the original private key
|
||||
// to derive the private key to be used when signing.
|
||||
//
|
||||
// k = (privKey*sha256(pubKey || tweakPub) +
|
||||
// tweakPriv*sha256(tweakPub || pubKey)) mod N
|
||||
DoubleTweak []byte `protobuf:"bytes,3,opt,name=double_tweak,json=doubleTweak,proto3" json:"double_tweak,omitempty"`
|
||||
// *
|
||||
// The full script required to properly redeem the output. This field will
|
||||
// only be populated if a p2wsh or a p2sh output is being signed.
|
||||
WitnessScript []byte `protobuf:"bytes,4,opt,name=witness_script,json=witnessScript,proto3" json:"witness_script,omitempty"`
|
||||
// *
|
||||
// A description of the output being spent. The value and script MUST be provided.
|
||||
Output *TxOut `protobuf:"bytes,5,opt,name=output" json:"output,omitempty"`
|
||||
// *
|
||||
// The target sighash type that should be used when generating the final
|
||||
// sighash, and signature.
|
||||
Sighash uint32 `protobuf:"varint,7,opt,name=sighash" json:"sighash,omitempty"`
|
||||
// *
|
||||
// The target input within the transaction that should be signed.
|
||||
InputIndex int32 `protobuf:"varint,8,opt,name=input_index,json=inputIndex" json:"input_index,omitempty"`
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) Reset() { *m = SignDescriptor{} }
|
||||
func (m *SignDescriptor) String() string { return proto.CompactTextString(m) }
|
||||
func (*SignDescriptor) ProtoMessage() {}
|
||||
func (*SignDescriptor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func (m *SignDescriptor) GetKeyDesc() *KeyDescriptor {
|
||||
if m != nil {
|
||||
return m.KeyDesc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) GetSingleTweak() []byte {
|
||||
if m != nil {
|
||||
return m.SingleTweak
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) GetDoubleTweak() []byte {
|
||||
if m != nil {
|
||||
return m.DoubleTweak
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) GetWitnessScript() []byte {
|
||||
if m != nil {
|
||||
return m.WitnessScript
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) GetOutput() *TxOut {
|
||||
if m != nil {
|
||||
return m.Output
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) GetSighash() uint32 {
|
||||
if m != nil {
|
||||
return m.Sighash
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *SignDescriptor) GetInputIndex() int32 {
|
||||
if m != nil {
|
||||
return m.InputIndex
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type SignReq struct {
|
||||
// / The raw bytes of the transaction to be signed.
|
||||
RawTxBytes []byte `protobuf:"bytes,1,opt,name=raw_tx_bytes,json=rawTxBytes,proto3" json:"raw_tx_bytes,omitempty"`
|
||||
// / A set of sign descriptors, for each input to be signed.
|
||||
SignDescs []*SignDescriptor `protobuf:"bytes,2,rep,name=sign_descs,json=signDescs" json:"sign_descs,omitempty"`
|
||||
}
|
||||
|
||||
func (m *SignReq) Reset() { *m = SignReq{} }
|
||||
func (m *SignReq) String() string { return proto.CompactTextString(m) }
|
||||
func (*SignReq) ProtoMessage() {}
|
||||
func (*SignReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
||||
|
||||
func (m *SignReq) GetRawTxBytes() []byte {
|
||||
if m != nil {
|
||||
return m.RawTxBytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SignReq) GetSignDescs() []*SignDescriptor {
|
||||
if m != nil {
|
||||
return m.SignDescs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SignResp struct {
|
||||
// *
|
||||
// A set of signatures realized in a fixed 64-byte format ordered in ascending
|
||||
// input order.
|
||||
RawSigs [][]byte `protobuf:"bytes,1,rep,name=raw_sigs,json=rawSigs,proto3" json:"raw_sigs,omitempty"`
|
||||
}
|
||||
|
||||
func (m *SignResp) Reset() { *m = SignResp{} }
|
||||
func (m *SignResp) String() string { return proto.CompactTextString(m) }
|
||||
func (*SignResp) ProtoMessage() {}
|
||||
func (*SignResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
||||
|
||||
func (m *SignResp) GetRawSigs() [][]byte {
|
||||
if m != nil {
|
||||
return m.RawSigs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InputScript struct {
|
||||
// / The serializes witness stack for the specified input.
|
||||
Witness [][]byte `protobuf:"bytes,1,rep,name=witness,proto3" json:"witness,omitempty"`
|
||||
// **
|
||||
// The optional sig script for the specified witness that will only be set if
|
||||
// the input specified is a nested p2sh witness program.
|
||||
SigScript []byte `protobuf:"bytes,2,opt,name=sig_script,json=sigScript,proto3" json:"sig_script,omitempty"`
|
||||
}
|
||||
|
||||
func (m *InputScript) Reset() { *m = InputScript{} }
|
||||
func (m *InputScript) String() string { return proto.CompactTextString(m) }
|
||||
func (*InputScript) ProtoMessage() {}
|
||||
func (*InputScript) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
|
||||
|
||||
func (m *InputScript) GetWitness() [][]byte {
|
||||
if m != nil {
|
||||
return m.Witness
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *InputScript) GetSigScript() []byte {
|
||||
if m != nil {
|
||||
return m.SigScript
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type InputScriptResp struct {
|
||||
InputScripts []*InputScript `protobuf:"bytes,1,rep,name=input_scripts,json=inputScripts" json:"input_scripts,omitempty"`
|
||||
}
|
||||
|
||||
func (m *InputScriptResp) Reset() { *m = InputScriptResp{} }
|
||||
func (m *InputScriptResp) String() string { return proto.CompactTextString(m) }
|
||||
func (*InputScriptResp) ProtoMessage() {}
|
||||
func (*InputScriptResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
|
||||
|
||||
func (m *InputScriptResp) GetInputScripts() []*InputScript {
|
||||
if m != nil {
|
||||
return m.InputScripts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*KeyLocator)(nil), "signrpc.KeyLocator")
|
||||
proto.RegisterType((*KeyDescriptor)(nil), "signrpc.KeyDescriptor")
|
||||
proto.RegisterType((*TxOut)(nil), "signrpc.TxOut")
|
||||
proto.RegisterType((*SignDescriptor)(nil), "signrpc.SignDescriptor")
|
||||
proto.RegisterType((*SignReq)(nil), "signrpc.SignReq")
|
||||
proto.RegisterType((*SignResp)(nil), "signrpc.SignResp")
|
||||
proto.RegisterType((*InputScript)(nil), "signrpc.InputScript")
|
||||
proto.RegisterType((*InputScriptResp)(nil), "signrpc.InputScriptResp")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Signer service
|
||||
|
||||
type SignerClient interface {
|
||||
// *
|
||||
// SignOutputRaw is a method that can be used to generated a signature for a
|
||||
// set of inputs/outputs to a transaction. Each request specifies details
|
||||
// concerning how the outputs should be signed, which keys they should be
|
||||
// signed with, and also any optional tweaks. The return value is a fixed
|
||||
// 64-byte signature (the same format as we use on the wire in Lightning).
|
||||
//
|
||||
// If we are unable to sign using the specified keys, then an error will be
|
||||
// returned.
|
||||
SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, error)
|
||||
// *
|
||||
// ComputeInputScript generates a complete InputIndex for the passed
|
||||
// transaction with the signature as defined within the passed SignDescriptor.
|
||||
// This method should be capable of generating the proper input script for
|
||||
// both regular p2wkh output and p2wkh outputs nested within a regular p2sh
|
||||
// output.
|
||||
//
|
||||
// Note that when using this method to sign inputs belonging to the wallet,
|
||||
// the only items of the SignDescriptor that need to be populated are pkScript
|
||||
// in the TxOut field, the value in that same field, and finally the input
|
||||
// index.
|
||||
ComputeInputScript(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*InputScriptResp, error)
|
||||
}
|
||||
|
||||
type signerClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewSignerClient(cc *grpc.ClientConn) SignerClient {
|
||||
return &signerClient{cc}
|
||||
}
|
||||
|
||||
func (c *signerClient) SignOutputRaw(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*SignResp, error) {
|
||||
out := new(SignResp)
|
||||
err := grpc.Invoke(ctx, "/signrpc.Signer/SignOutputRaw", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *signerClient) ComputeInputScript(ctx context.Context, in *SignReq, opts ...grpc.CallOption) (*InputScriptResp, error) {
|
||||
out := new(InputScriptResp)
|
||||
err := grpc.Invoke(ctx, "/signrpc.Signer/ComputeInputScript", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Signer service
|
||||
|
||||
type SignerServer interface {
|
||||
// *
|
||||
// SignOutputRaw is a method that can be used to generated a signature for a
|
||||
// set of inputs/outputs to a transaction. Each request specifies details
|
||||
// concerning how the outputs should be signed, which keys they should be
|
||||
// signed with, and also any optional tweaks. The return value is a fixed
|
||||
// 64-byte signature (the same format as we use on the wire in Lightning).
|
||||
//
|
||||
// If we are unable to sign using the specified keys, then an error will be
|
||||
// returned.
|
||||
SignOutputRaw(context.Context, *SignReq) (*SignResp, error)
|
||||
// *
|
||||
// ComputeInputScript generates a complete InputIndex for the passed
|
||||
// transaction with the signature as defined within the passed SignDescriptor.
|
||||
// This method should be capable of generating the proper input script for
|
||||
// both regular p2wkh output and p2wkh outputs nested within a regular p2sh
|
||||
// output.
|
||||
//
|
||||
// Note that when using this method to sign inputs belonging to the wallet,
|
||||
// the only items of the SignDescriptor that need to be populated are pkScript
|
||||
// in the TxOut field, the value in that same field, and finally the input
|
||||
// index.
|
||||
ComputeInputScript(context.Context, *SignReq) (*InputScriptResp, error)
|
||||
}
|
||||
|
||||
func RegisterSignerServer(s *grpc.Server, srv SignerServer) {
|
||||
s.RegisterService(&_Signer_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Signer_SignOutputRaw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SignReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).SignOutputRaw(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/SignOutputRaw",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).SignOutputRaw(ctx, req.(*SignReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Signer_ComputeInputScript_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SignReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SignerServer).ComputeInputScript(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/signrpc.Signer/ComputeInputScript",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SignerServer).ComputeInputScript(ctx, req.(*SignReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Signer_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "signrpc.Signer",
|
||||
HandlerType: (*SignerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SignOutputRaw",
|
||||
Handler: _Signer_SignOutputRaw_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ComputeInputScript",
|
||||
Handler: _Signer_ComputeInputScript_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "signrpc/signer.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("signrpc/signer.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 536 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x93, 0xd1, 0x8f, 0xd2, 0x40,
|
||||
0x10, 0xc6, 0x0f, 0x10, 0xca, 0x4d, 0x5b, 0xd4, 0x95, 0x68, 0xd5, 0x18, 0xb1, 0xf1, 0x0c, 0x4f,
|
||||
0x18, 0xd1, 0x98, 0xe8, 0x93, 0x39, 0xcd, 0x85, 0x0b, 0x97, 0x5c, 0xb2, 0xf0, 0xde, 0x94, 0xb2,
|
||||
0xf6, 0x36, 0xe5, 0xda, 0x5e, 0x77, 0x6b, 0xe9, 0x9b, 0xff, 0x83, 0xff, 0xb0, 0x99, 0xdd, 0x05,
|
||||
0x8a, 0xde, 0x13, 0x7c, 0x5f, 0x67, 0x67, 0x7e, 0x3b, 0x5f, 0x0b, 0x43, 0xc1, 0xe3, 0xb4, 0xc8,
|
||||
0xa3, 0xf7, 0xf8, 0xcb, 0x8a, 0x49, 0x5e, 0x64, 0x32, 0x23, 0x96, 0x71, 0xfd, 0x19, 0xc0, 0x9c,
|
||||
0xd5, 0x57, 0x59, 0x14, 0xca, 0xac, 0x20, 0xaf, 0x00, 0x12, 0x56, 0x07, 0x3f, 0xc3, 0x5b, 0xbe,
|
||||
0xa9, 0xbd, 0xd6, 0xa8, 0x35, 0xee, 0xd2, 0xd3, 0x84, 0xd5, 0x17, 0xca, 0x20, 0x2f, 0x01, 0x45,
|
||||
0xc0, 0xd3, 0x35, 0xdb, 0x7a, 0x6d, 0xf5, 0xb4, 0x9f, 0xb0, 0xfa, 0x12, 0xb5, 0xbf, 0x01, 0x77,
|
||||
0xce, 0xea, 0x1f, 0x4c, 0x44, 0x05, 0xcf, 0xb1, 0xd9, 0x5b, 0x70, 0x8b, 0xb0, 0x0a, 0xf0, 0xc4,
|
||||
0xaa, 0x96, 0x4c, 0xa8, 0x7e, 0xce, 0xec, 0x84, 0xda, 0x45, 0x58, 0xcd, 0x59, 0x7d, 0x8e, 0x26,
|
||||
0x99, 0x80, 0x85, 0x15, 0x9b, 0x2c, 0x52, 0x1d, 0xed, 0xe9, 0x93, 0x89, 0x61, 0x9b, 0x1c, 0xc0,
|
||||
0x66, 0x27, 0xb4, 0x97, 0x28, 0x75, 0xde, 0x85, 0x4e, 0xc2, 0x6a, 0xff, 0x2b, 0x74, 0x97, 0xdb,
|
||||
0xeb, 0x52, 0x92, 0x21, 0x74, 0x7f, 0x85, 0x9b, 0x92, 0xa9, 0xee, 0x1d, 0xaa, 0x05, 0x92, 0xe6,
|
||||
0x49, 0xa0, 0x51, 0x54, 0x5f, 0x87, 0xf6, 0xf3, 0x64, 0xa1, 0xb4, 0xff, 0xa7, 0x0d, 0x83, 0x05,
|
||||
0x8f, 0xd3, 0x06, 0xeb, 0x07, 0xc0, 0x8b, 0x04, 0x6b, 0x26, 0x22, 0xd5, 0xc8, 0x9e, 0x3e, 0x6d,
|
||||
0x62, 0x1c, 0x2a, 0x29, 0xd2, 0xa2, 0x24, 0x6f, 0xc0, 0x11, 0x3c, 0x8d, 0x37, 0x2c, 0x90, 0x15,
|
||||
0x0b, 0x13, 0x33, 0xc5, 0xd6, 0xde, 0x12, 0x2d, 0x2c, 0x59, 0x67, 0xe5, 0x6a, 0x5f, 0xd2, 0xd1,
|
||||
0x25, 0xda, 0xd3, 0x25, 0x67, 0x30, 0xa8, 0xb8, 0x4c, 0x99, 0x10, 0x3b, 0xda, 0x07, 0xaa, 0xc8,
|
||||
0x35, 0xae, 0x46, 0x26, 0xef, 0xa0, 0x97, 0x95, 0x32, 0x2f, 0xa5, 0xd7, 0x55, 0x74, 0x83, 0x3d,
|
||||
0x9d, 0xda, 0x02, 0x35, 0x4f, 0x89, 0x07, 0x98, 0xec, 0x4d, 0x28, 0x6e, 0x3c, 0x6b, 0xd4, 0x1a,
|
||||
0xbb, 0x74, 0x27, 0xc9, 0x6b, 0xb0, 0x79, 0x9a, 0x97, 0xd2, 0xa4, 0xd7, 0x57, 0xe9, 0x81, 0xb2,
|
||||
0x74, 0x7e, 0x11, 0x58, 0xb8, 0x14, 0xca, 0xee, 0xc8, 0x08, 0x1c, 0x4c, 0x4e, 0x6e, 0x9b, 0xc1,
|
||||
0x51, 0x28, 0xc2, 0x6a, 0xb9, 0xd5, 0xa9, 0x7d, 0x06, 0x40, 0x00, 0xb5, 0x30, 0xe1, 0xb5, 0x47,
|
||||
0x9d, 0xb1, 0x3d, 0x7d, 0xb6, 0x67, 0x3a, 0x5e, 0x2e, 0x3d, 0x15, 0x46, 0x0b, 0xff, 0x0c, 0xfa,
|
||||
0x7a, 0x88, 0xc8, 0xc9, 0x73, 0xe8, 0xe3, 0x14, 0xc1, 0x63, 0x9c, 0xd0, 0x19, 0x3b, 0xd4, 0x2a,
|
||||
0xc2, 0x6a, 0xc1, 0x63, 0xe1, 0x5f, 0x80, 0x7d, 0x89, 0x64, 0xe6, 0xf6, 0x1e, 0x58, 0x66, 0x1d,
|
||||
0xbb, 0x42, 0x23, 0xf1, 0x85, 0x15, 0x3c, 0x3e, 0x0e, 0x1a, 0xc7, 0x99, 0xa4, 0xaf, 0xe0, 0x61,
|
||||
0xa3, 0x8f, 0x9a, 0xfa, 0x05, 0x5c, 0xbd, 0x07, 0x7d, 0x46, 0x77, 0xb4, 0xa7, 0xc3, 0x3d, 0x7c,
|
||||
0xf3, 0x80, 0xc3, 0x0f, 0x42, 0x4c, 0x7f, 0xb7, 0xa0, 0xb7, 0x50, 0x5f, 0x11, 0xf9, 0x04, 0x2e,
|
||||
0xfe, 0xbb, 0x56, 0x5b, 0xa7, 0x61, 0x45, 0x1e, 0x1d, 0x5d, 0x9e, 0xb2, 0xbb, 0x17, 0x8f, 0xff,
|
||||
0x71, 0x44, 0x4e, 0xbe, 0x01, 0xf9, 0x9e, 0xdd, 0xe6, 0xa5, 0x64, 0xcd, 0xdb, 0xfd, 0x7f, 0xd4,
|
||||
0xbb, 0x17, 0x86, 0x89, 0x7c, 0xd5, 0x53, 0x9f, 0xef, 0xc7, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff,
|
||||
0x7b, 0x48, 0x93, 0x2a, 0xd6, 0x03, 0x00, 0x00,
|
||||
}
|
148
lnrpc/signrpc/signer.proto
Normal file
148
lnrpc/signrpc/signer.proto
Normal file
@ -0,0 +1,148 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package signrpc;
|
||||
|
||||
message KeyLocator {
|
||||
/// The family of key being identified.
|
||||
int32 key_family = 1;
|
||||
|
||||
/// The precise index of the key being identified.
|
||||
int32 key_index = 2;
|
||||
}
|
||||
|
||||
message KeyDescriptor {
|
||||
oneof key {
|
||||
/**
|
||||
The raw bytes of the key being identified. Either this or the KeyLocator
|
||||
must be specified.
|
||||
*/
|
||||
bytes raw_key_bytes = 1;
|
||||
|
||||
/**
|
||||
The key locator that identifies which key to use for signing. Either this
|
||||
or the raw bytes of the target key must be specified.
|
||||
*/
|
||||
KeyLocator key_loc = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message TxOut {
|
||||
/// The value of the output being spent.
|
||||
int64 value = 1;
|
||||
|
||||
/// The script of the output being spent.
|
||||
bytes pk_script = 2;
|
||||
}
|
||||
|
||||
message SignDescriptor {
|
||||
/**
|
||||
A descriptor that precisely describes *which* key to use for signing. This
|
||||
may provide the raw public key directly, or require the Signer to re-derive
|
||||
the key according to the populated derivation path.
|
||||
*/
|
||||
KeyDescriptor key_desc = 1;
|
||||
|
||||
/**
|
||||
A scalar value that will be added to the private key corresponding to the
|
||||
above public key to obtain the private key to be used to sign this input.
|
||||
This value is typically derived via the following computation:
|
||||
|
||||
* derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N
|
||||
*/
|
||||
bytes single_tweak = 2;
|
||||
|
||||
/**
|
||||
A private key that will be used in combination with its corresponding
|
||||
private key to derive the private key that is to be used to sign the target
|
||||
input. Within the Lightning protocol, this value is typically the
|
||||
commitment secret from a previously revoked commitment transaction. This
|
||||
value is in combination with two hash values, and the original private key
|
||||
to derive the private key to be used when signing.
|
||||
|
||||
* k = (privKey*sha256(pubKey || tweakPub) +
|
||||
tweakPriv*sha256(tweakPub || pubKey)) mod N
|
||||
*/
|
||||
bytes double_tweak = 3;
|
||||
|
||||
/**
|
||||
The full script required to properly redeem the output. This field will
|
||||
only be populated if a p2wsh or a p2sh output is being signed.
|
||||
*/
|
||||
bytes witness_script = 4;
|
||||
|
||||
/**
|
||||
A description of the output being spent. The value and script MUST be provided.
|
||||
*/
|
||||
TxOut output = 5;
|
||||
|
||||
/**
|
||||
The target sighash type that should be used when generating the final
|
||||
sighash, and signature.
|
||||
*/
|
||||
uint32 sighash = 7;
|
||||
|
||||
/**
|
||||
The target input within the transaction that should be signed.
|
||||
*/
|
||||
int32 input_index = 8;
|
||||
}
|
||||
|
||||
message SignReq {
|
||||
/// The raw bytes of the transaction to be signed.
|
||||
bytes raw_tx_bytes = 1;
|
||||
|
||||
/// A set of sign descriptors, for each input to be signed.
|
||||
repeated SignDescriptor sign_descs = 2;
|
||||
}
|
||||
|
||||
message SignResp {
|
||||
/**
|
||||
A set of signatures realized in a fixed 64-byte format ordered in ascending
|
||||
input order.
|
||||
*/
|
||||
repeated bytes raw_sigs = 1;
|
||||
}
|
||||
|
||||
message InputScript {
|
||||
/// The serializes witness stack for the specified input.
|
||||
repeated bytes witness = 1;
|
||||
|
||||
/***
|
||||
The optional sig script for the specified witness that will only be set if
|
||||
the input specified is a nested p2sh witness program.
|
||||
*/
|
||||
bytes sig_script = 2;
|
||||
}
|
||||
|
||||
message InputScriptResp {
|
||||
/// The set of fully valid input scripts requested.
|
||||
repeated InputScript input_scripts = 1;
|
||||
}
|
||||
|
||||
service Signer {
|
||||
/**
|
||||
SignOutputRaw is a method that can be used to generated a signature for a
|
||||
set of inputs/outputs to a transaction. Each request specifies details
|
||||
concerning how the outputs should be signed, which keys they should be
|
||||
signed with, and also any optional tweaks. The return value is a fixed
|
||||
64-byte signature (the same format as we use on the wire in Lightning).
|
||||
|
||||
If we are unable to sign using the specified keys, then an error will be
|
||||
returned.
|
||||
*/
|
||||
rpc SignOutputRaw(SignReq) returns (SignResp);
|
||||
|
||||
/**
|
||||
ComputeInputScript generates a complete InputIndex for the passed
|
||||
transaction with the signature as defined within the passed SignDescriptor.
|
||||
This method should be capable of generating the proper input script for
|
||||
both regular p2wkh output and p2wkh outputs nested within a regular p2sh
|
||||
output.
|
||||
|
||||
Note that when using this method to sign inputs belonging to the wallet,
|
||||
the only items of the SignDescriptor that need to be populated are pkScript
|
||||
in the TxOut field, the value in that same field, and finally the input
|
||||
index.
|
||||
*/
|
||||
rpc ComputeInputScript(SignReq) returns (InputScriptResp);
|
||||
}
|
390
lnrpc/signrpc/signer_server.go
Normal file
390
lnrpc/signrpc/signer_server.go
Normal file
@ -0,0 +1,390 @@
|
||||
// +build signrpc
|
||||
|
||||
package signrpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"google.golang.org/grpc"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
const (
|
||||
// subServerName is the name of the sub rpc server. We'll use this name
|
||||
// to register ourselves, and we also require that the main
|
||||
// SubServerConfigDispatcher instance recognize this as the name of the
|
||||
// config file that we need.
|
||||
subServerName = "SignRPC"
|
||||
)
|
||||
|
||||
var (
|
||||
// macaroonOps are the set of capabilities that our minted macaroon (if
|
||||
// it doesn't already exist) will have.
|
||||
macaroonOps = []bakery.Op{
|
||||
{
|
||||
Entity: "signer",
|
||||
Action: "generate",
|
||||
},
|
||||
}
|
||||
|
||||
// macPermissions maps RPC calls to the permissions they require.
|
||||
macPermissions = map[string][]bakery.Op{
|
||||
"/signrpc.Signer/SignOutputRaw": {{
|
||||
Entity: "signer",
|
||||
Action: "generate",
|
||||
}},
|
||||
}
|
||||
|
||||
// DefaultSignerMacFilename is the default name of the signer macaroon
|
||||
// that we expect to find via a file handle within the main
|
||||
// configuration file in this package.
|
||||
DefaultSignerMacFilename = "signer.macaroon"
|
||||
)
|
||||
|
||||
// Server is a sub-server of the main RPC server: the signer RPC. This sub RPC
|
||||
// server allows external callers to access the full signing capabilities of
|
||||
// lnd. This allows callers to create custom protocols, external to lnd, even
|
||||
// backed by multiple distinct lnd across independent failure domains.
|
||||
type Server struct {
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
// A compile time check to ensure that Server fully implements the SignerServer
|
||||
// gRPC service.
|
||||
var _ SignerServer = (*Server)(nil)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// New returns a new instance of the signrpc Signer sub-server. We also return
|
||||
// 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.
|
||||
func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) {
|
||||
// If the path of the signer macaroon wasn't generated, then we'll
|
||||
// assume that it's found at the default network directory.
|
||||
if cfg.SignerMacPath == "" {
|
||||
cfg.SignerMacPath = filepath.Join(
|
||||
cfg.NetworkDir, DefaultSignerMacFilename,
|
||||
)
|
||||
}
|
||||
|
||||
// Now that we know the full path of the signer macaroon, we can check
|
||||
// to see if we need to create it or not.
|
||||
macFilePath := cfg.SignerMacPath
|
||||
if cfg.MacService != nil && !fileExists(macFilePath) {
|
||||
log.Infof("Making macaroons for Signer RPC Server at: %v",
|
||||
macFilePath)
|
||||
|
||||
// At this point, we know that the signer macaroon doesn't yet,
|
||||
// exist, so we need to create it with the help of the main
|
||||
// macaroon service.
|
||||
signerMac, err := cfg.MacService.Oven.NewMacaroon(
|
||||
context.Background(), bakery.LatestVersion, nil,
|
||||
macaroonOps...,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
signerMacBytes, err := signerMac.M().MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = ioutil.WriteFile(macFilePath, signerMacBytes, 0644)
|
||||
if err != nil {
|
||||
os.Remove(macFilePath)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
signerServer := &Server{
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
return signerServer, macPermissions, nil
|
||||
}
|
||||
|
||||
// Start launches any helper goroutines required for the rpcServer to function.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop signals any active goroutines for a graceful closure.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns a unique string representation of the sub-server. This can be
|
||||
// used to identify the sub-server and also de-duplicate them.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) Name() string {
|
||||
return subServerName
|
||||
}
|
||||
|
||||
// RegisterWithRootServer will be called by the root gRPC server to direct a
|
||||
// sub RPC server to register itself with the main gRPC root server. Until this
|
||||
// is called, each sub-server won't be able to have
|
||||
// requests routed towards it.
|
||||
//
|
||||
// NOTE: This is part of the lnrpc.SubServer interface.
|
||||
func (s *Server) RegisterWithRootServer(grpcServer *grpc.Server) error {
|
||||
// We make sure that we register it with the main gRPC server to ensure
|
||||
// all our methods are routed properly.
|
||||
RegisterSignerServer(grpcServer, s)
|
||||
|
||||
log.Debugf("Signer RPC server successfully register with root gRPC " +
|
||||
"server")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignOutputRaw generates a signature for the passed transaction according to
|
||||
// the data within the passed SignReq. If we're unable to find the keys that
|
||||
// correspond to the KeyLocators in the SignReq then we'll return an error.
|
||||
// Additionally, if the user doesn't provide the set of required parameters, or
|
||||
// provides an invalid transaction, then we'll return with an error.
|
||||
//
|
||||
// NOTE: The resulting signature should be void of a sighash byte.
|
||||
func (s *Server) SignOutputRaw(ctx context.Context, in *SignReq) (*SignResp, error) {
|
||||
|
||||
switch {
|
||||
// If the client doesn't specify a transaction, then there's nothing to
|
||||
// sign, so we'll exit early.
|
||||
case len(in.RawTxBytes) == 0:
|
||||
return nil, fmt.Errorf("a transaction to sign MUST be " +
|
||||
"passed in")
|
||||
|
||||
// If the client doesn't tell us *how* to sign the transaction, then we
|
||||
// can't sign anything, so we'll exit early.
|
||||
case len(in.SignDescs) == 0:
|
||||
return nil, fmt.Errorf("at least one SignDescs MUST be " +
|
||||
"passed in")
|
||||
}
|
||||
|
||||
// Now that we know we have an actual transaction to decode, we'll
|
||||
// deserialize it into something that we can properly utilize.
|
||||
var (
|
||||
txToSign wire.MsgTx
|
||||
err error
|
||||
)
|
||||
txReader := bytes.NewReader(in.RawTxBytes)
|
||||
if err := txToSign.Deserialize(txReader); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode tx: %v", err)
|
||||
}
|
||||
|
||||
sigHashCache := txscript.NewTxSigHashes(&txToSign)
|
||||
|
||||
log.Debugf("Generating sigs for %v inputs: ", len(in.SignDescs))
|
||||
|
||||
// With the transaction deserialized, we'll now convert sign descs so
|
||||
// we can feed it into the actual signer.
|
||||
signDescs := make([]*lnwallet.SignDescriptor, 0, len(in.SignDescs))
|
||||
for _, signDesc := range in.SignDescs {
|
||||
keyDesc := signDesc.KeyDesc
|
||||
|
||||
// The caller can either specify the key using the raw pubkey,
|
||||
// or the description of the key. Below we'll feel out the
|
||||
// oneof field to decide which one we will attempt to parse.
|
||||
var (
|
||||
targetPubKey *btcec.PublicKey
|
||||
keyLoc keychain.KeyLocator
|
||||
)
|
||||
switch {
|
||||
|
||||
// If this method doesn't return nil, then we know that user is
|
||||
// attempting to include a raw serialized pub key.
|
||||
case keyDesc.GetRawKeyBytes() != nil:
|
||||
rawKeyBytes := keyDesc.GetRawKeyBytes()
|
||||
|
||||
switch {
|
||||
// If the user provided a raw key, but it's of the
|
||||
// wrong length, then we'll return with an error.
|
||||
case len(rawKeyBytes) != 0 && len(rawKeyBytes) != 33:
|
||||
|
||||
return nil, fmt.Errorf("pubkey must be " +
|
||||
"serialized in compressed format if " +
|
||||
"specified")
|
||||
|
||||
// If a proper raw key was provided, then we'll attempt
|
||||
// to decode and parse it.
|
||||
case len(rawKeyBytes) != 0 && len(rawKeyBytes) == 33:
|
||||
targetPubKey, err = btcec.ParsePubKey(
|
||||
rawKeyBytes, btcec.S256(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to "+
|
||||
"parse pubkey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, if they specified a key locator, then we'll use
|
||||
// that instead.
|
||||
case keyDesc.GetKeyLoc() != nil:
|
||||
protoLoc := keyDesc.GetKeyLoc()
|
||||
keyLoc = keychain.KeyLocator{
|
||||
Family: keychain.KeyFamily(
|
||||
protoLoc.KeyFamily,
|
||||
),
|
||||
Index: uint32(protoLoc.KeyIndex),
|
||||
}
|
||||
}
|
||||
|
||||
// If a witness script isn't passed, then we can't proceed, as
|
||||
// in the p2wsh case, we can't properly generate the sighash.
|
||||
if len(signDesc.WitnessScript) == 0 {
|
||||
// TODO(roasbeef): if regualr p2wkh, then at times
|
||||
// internally we allow script to go by
|
||||
return nil, fmt.Errorf("witness script MUST be " +
|
||||
"specified")
|
||||
}
|
||||
|
||||
// If the users provided a double tweak, then we'll need to
|
||||
// parse that out now to ensure their input is properly signed.
|
||||
var tweakPrivKey *btcec.PrivateKey
|
||||
if len(signDesc.DoubleTweak) != 0 {
|
||||
tweakPrivKey, _ = btcec.PrivKeyFromBytes(
|
||||
btcec.S256(), signDesc.DoubleTweak,
|
||||
)
|
||||
}
|
||||
|
||||
// Finally, with verification and parsing complete, we can
|
||||
// construct the final sign descriptor to generate the proper
|
||||
// signature for this input.
|
||||
signDescs = append(signDescs, &lnwallet.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: keyLoc,
|
||||
PubKey: targetPubKey,
|
||||
},
|
||||
SingleTweak: signDesc.SingleTweak,
|
||||
DoubleTweak: tweakPrivKey,
|
||||
WitnessScript: signDesc.WitnessScript,
|
||||
Output: &wire.TxOut{
|
||||
Value: signDesc.Output.Value,
|
||||
PkScript: signDesc.Output.PkScript,
|
||||
},
|
||||
HashType: txscript.SigHashType(signDesc.Sighash),
|
||||
SigHashes: sigHashCache,
|
||||
InputIndex: int(signDesc.InputIndex),
|
||||
})
|
||||
}
|
||||
|
||||
// Now that we've mapped all the proper sign descriptors, we can
|
||||
// request signatures for each of them, passing in the transaction to
|
||||
// be signed.
|
||||
numSigs := len(in.SignDescs)
|
||||
resp := &SignResp{
|
||||
RawSigs: make([][]byte, numSigs),
|
||||
}
|
||||
for i, signDesc := range signDescs {
|
||||
sig, err := s.cfg.Signer.SignOutputRaw(&txToSign, signDesc)
|
||||
if err != nil {
|
||||
log.Errorf("unable to generate sig for input "+
|
||||
"#%v: %v", i, err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.RawSigs[i] = sig
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ComputeInputScript generates a complete InputIndex for the passed
|
||||
// transaction with the signature as defined within the passed SignDescriptor.
|
||||
// This method should be capable of generating the proper input script for both
|
||||
// regular p2wkh output and p2wkh outputs nested within a regular p2sh output.
|
||||
//
|
||||
// Note that when using this method to sign inputs belonging to the wallet, the
|
||||
// only items of the SignDescriptor that need to be populated are pkScript in
|
||||
// the TxOut field, the value in that same field, and finally the input index.
|
||||
func (s *Server) ComputeInputScript(ctx context.Context,
|
||||
in *SignReq) (*InputScriptResp, error) {
|
||||
|
||||
switch {
|
||||
// If the client doesn't specify a transaction, then there's nothing to
|
||||
// sign, so we'll exit early.
|
||||
case len(in.RawTxBytes) == 0:
|
||||
return nil, fmt.Errorf("a transaction to sign MUST be " +
|
||||
"passed in")
|
||||
|
||||
// If the client doesn't tell us *how* to sign the transaction, then we
|
||||
// can't sign anything, so we'll exit early.
|
||||
case len(in.SignDescs) == 0:
|
||||
return nil, fmt.Errorf("at least one SignDescs MUST be " +
|
||||
"passed in")
|
||||
}
|
||||
|
||||
// Now that we know we have an actual transaction to decode, we'll
|
||||
// deserialize it into something that we can properly utilize.
|
||||
var txToSign wire.MsgTx
|
||||
txReader := bytes.NewReader(in.RawTxBytes)
|
||||
if err := txToSign.Deserialize(txReader); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode tx: %v", err)
|
||||
}
|
||||
|
||||
sigHashCache := txscript.NewTxSigHashes(&txToSign)
|
||||
|
||||
signDescs := make([]*lnwallet.SignDescriptor, 0, len(in.SignDescs))
|
||||
for _, signDesc := range in.SignDescs {
|
||||
// For this method, the only fields that we care about are the
|
||||
// hash type, and the information concerning the output as we
|
||||
// only know how to provide full witnesses for outputs that we
|
||||
// solely control.
|
||||
signDescs = append(signDescs, &lnwallet.SignDescriptor{
|
||||
Output: &wire.TxOut{
|
||||
Value: signDesc.Output.Value,
|
||||
PkScript: signDesc.Output.PkScript,
|
||||
},
|
||||
HashType: txscript.SigHashType(signDesc.Sighash),
|
||||
SigHashes: sigHashCache,
|
||||
})
|
||||
}
|
||||
|
||||
// With all of our signDescs assembled, we can now generate a valid
|
||||
// input script for each of them, and collate the responses to return
|
||||
// back to the caller.
|
||||
numWitnesses := len(in.SignDescs)
|
||||
resp := &InputScriptResp{
|
||||
InputScripts: make([]*InputScript, numWitnesses),
|
||||
}
|
||||
for i, signDesc := range signDescs {
|
||||
inputScript, err := s.cfg.Signer.ComputeInputScript(
|
||||
&txToSign, signDesc,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.InputScripts[i] = &InputScript{
|
||||
Witness: inputScript.Witness,
|
||||
SigScript: inputScript.ScriptSig,
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
131
lnrpc/sub_server.go
Normal file
131
lnrpc/sub_server.go
Normal file
@ -0,0 +1,131 @@
|
||||
package lnrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
// MacaroonPerms is a map from the FullMethod of an invoked gRPC command. It
|
||||
// maps the set of operations that the macaroon presented with the command MUST
|
||||
// satisfy. With this map, all sub-servers are able to communicate to the
|
||||
// primary macaroon service what type of macaroon must be passed with each
|
||||
// method present on the service of the sub-server.
|
||||
type MacaroonPerms map[string][]bakery.Op
|
||||
|
||||
// SubServer is a child server of the main lnrpc gRPC server. Sub-servers allow
|
||||
// lnd to expose discrete services that can be used with or independent of the
|
||||
// main RPC server. The main rpcserver will create, start, stop, and manage
|
||||
// each sub-server in a generalized manner.
|
||||
type SubServer interface {
|
||||
// Start starts the sub-server and all goroutines it needs to operate.
|
||||
Start() error
|
||||
|
||||
// Stop signals that the sub-server should wrap up any lingering
|
||||
// requests, and being a graceful shutdown.
|
||||
Stop() error
|
||||
|
||||
// Name returns a unique string representation of the sub-server. This
|
||||
// can be used to identify the sub-server and also de-duplicate them.
|
||||
Name() string
|
||||
|
||||
// RegisterWithRootServer will be called by the root gRPC server to
|
||||
// direct a sub RPC server to register itself with the main gRPC root
|
||||
// server. Until this is called, each sub-server won't be able to have
|
||||
// requests routed towards it.
|
||||
RegisterWithRootServer(*grpc.Server) error
|
||||
}
|
||||
|
||||
// SubServerConfigDispatcher is an interface that all sub-servers will use to
|
||||
// dynamically locate their configuration files. This abstraction will allow
|
||||
// the primary RPC sever to initialize all sub-servers in a generic manner
|
||||
// without knowing of each individual sub server.
|
||||
type SubServerConfigDispatcher interface {
|
||||
// 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.
|
||||
FetchConfig(subServerName string) (interface{}, bool)
|
||||
}
|
||||
|
||||
// SubServerDriver is a template struct that allows the root server to create a
|
||||
// sub-server with minimal knowledge. The root server only need a fully
|
||||
// populated SubServerConfigDispatcher and with the aide of the
|
||||
// RegisterSubServers method, it's able to create and initialize all
|
||||
// sub-servers.
|
||||
type SubServerDriver struct {
|
||||
// SubServerName is the full name of a sub-sever.
|
||||
//
|
||||
// NOTE: This MUST be unique.
|
||||
SubServerName string
|
||||
|
||||
// New creates, and fully initializes a new sub-server instance with
|
||||
// the aide of the SubServerConfigDispatcher. This closure should
|
||||
// return the SubServer, ready for action, along with the set of
|
||||
// macaroon permissions that the sub-server wishes to pass on to the
|
||||
// root server for all methods routed towards it.
|
||||
New func(subCfgs SubServerConfigDispatcher) (SubServer, MacaroonPerms, error)
|
||||
}
|
||||
|
||||
var (
|
||||
// subServers is a package level global variable that houses all the
|
||||
// registered sub-servers.
|
||||
subServers = make(map[string]*SubServerDriver)
|
||||
|
||||
// registerMtx is a mutex that protects access to the above subServer
|
||||
// map.
|
||||
registerMtx sync.Mutex
|
||||
)
|
||||
|
||||
// RegisteredSubServers returns all registered sub-servers.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func RegisteredSubServers() []*SubServerDriver {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
drivers := make([]*SubServerDriver, 0, len(subServers))
|
||||
for _, driver := range subServers {
|
||||
drivers = append(drivers, driver)
|
||||
}
|
||||
|
||||
return drivers
|
||||
}
|
||||
|
||||
// RegisterSubServer should be called by a sub-server within its package's
|
||||
// init() method to register its existence with the main sub-server map. Each
|
||||
// sub-server, if active, is meant to register via this method in their init()
|
||||
// method. This allows callers to easily initialize and register all
|
||||
// sub-servers without knowing any details beyond that the fact that they
|
||||
// satisfy the necessary interfaces.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func RegisterSubServer(driver *SubServerDriver) error {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
if _, ok := subServers[driver.SubServerName]; ok {
|
||||
return fmt.Errorf("subserver already registered")
|
||||
}
|
||||
|
||||
subServers[driver.SubServerName] = driver
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportedServers returns slice of the names of all registered sub-servers.
|
||||
//
|
||||
// NOTE: This function is safe for concurrent access.
|
||||
func SupportedServers() []string {
|
||||
registerMtx.Lock()
|
||||
defer registerMtx.Unlock()
|
||||
|
||||
supportedSubServers := make([]string, 0, len(subServers))
|
||||
for driverName := range subServers {
|
||||
supportedSubServers = append(supportedSubServers, driverName)
|
||||
}
|
||||
|
||||
return supportedSubServers
|
||||
}
|
4
log.go
4
log.go
@ -19,6 +19,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/contractcourt"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/signal"
|
||||
@ -65,6 +66,7 @@ var (
|
||||
cnctLog = build.NewSubLogger("CNCT", backendLog.Logger)
|
||||
sphxLog = build.NewSubLogger("SPHX", backendLog.Logger)
|
||||
swprLog = build.NewSubLogger("SWPR", backendLog.Logger)
|
||||
sgnrLog = build.NewSubLogger("SGNR", backendLog.Logger)
|
||||
)
|
||||
|
||||
// Initialize package-global logger variables.
|
||||
@ -82,6 +84,7 @@ func init() {
|
||||
sphinx.UseLogger(sphxLog)
|
||||
signal.UseLogger(ltndLog)
|
||||
sweep.UseLogger(swprLog)
|
||||
signrpc.UseLogger(sgnrLog)
|
||||
}
|
||||
|
||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||
@ -105,6 +108,7 @@ var subsystemLoggers = map[string]btclog.Logger{
|
||||
"CNCT": cnctLog,
|
||||
"SPHX": sphxLog,
|
||||
"SWPR": swprLog,
|
||||
"SGNR": sgnrLog,
|
||||
}
|
||||
|
||||
// initLogRotator initializes the logging rotator to write logs to logFile and
|
||||
|
@ -140,8 +140,9 @@ func (svc *Service) StreamServerInterceptor(
|
||||
"for method", info.FullMethod)
|
||||
}
|
||||
|
||||
err := svc.ValidateMacaroon(ss.Context(),
|
||||
permissionMap[info.FullMethod])
|
||||
err := svc.ValidateMacaroon(
|
||||
ss.Context(), permissionMap[info.FullMethod],
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
223
rpcserver.go
223
rpcserver.go
@ -4,11 +4,13 @@ import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -24,17 +26,21 @@ import (
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/signal"
|
||||
"github.com/lightningnetwork/lnd/zpay32"
|
||||
"github.com/tv42/zbase32"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
@ -339,6 +345,31 @@ type rpcServer struct {
|
||||
|
||||
wg sync.WaitGroup
|
||||
|
||||
// subServers are a set of sub-RPC servers that use the same gRPC and
|
||||
// listening sockets as the main RPC server, but which maintain their
|
||||
// own independent service. This allows us to expose a set of
|
||||
// micro-service like abstractions to the outside world for users to
|
||||
// consume.
|
||||
subServers []lnrpc.SubServer
|
||||
|
||||
// grpcServer is the main gRPC server that this RPC server, and all the
|
||||
// sub-servers will use to register themselves and accept client
|
||||
// requests from.
|
||||
grpcServer *grpc.Server
|
||||
|
||||
// listenerCleanUp are a set of closures functions that will allow this
|
||||
// main RPC server to clean up all the listening socket created for the
|
||||
// server.
|
||||
listenerCleanUp []func()
|
||||
|
||||
// restServerOpts are a set of gRPC dial options that the REST server
|
||||
// proxy will use to connect to the main gRPC server.
|
||||
restServerOpts []grpc.DialOption
|
||||
|
||||
// tlsCfg is the TLS config that allows the REST server proxy to
|
||||
// connect to the main gRPC server to proxy all incoming requests.
|
||||
tlsCfg *tls.Config
|
||||
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
@ -346,12 +377,106 @@ type rpcServer struct {
|
||||
// LightningServer gRPC service.
|
||||
var _ lnrpc.LightningServer = (*rpcServer)(nil)
|
||||
|
||||
// newRPCServer creates and returns a new instance of the rpcServer.
|
||||
func newRPCServer(s *server) *rpcServer {
|
||||
return &rpcServer{
|
||||
server: s,
|
||||
quit: make(chan struct{}, 1),
|
||||
// newRPCServer creates and returns a new instance of the rpcServer. The
|
||||
// rpcServer will handle creating all listening sockets needed by it, and any
|
||||
// of the sub-servers that it maintains. The set of serverOpts should be the
|
||||
// base level options passed to the grPC server. This typically includes things
|
||||
// like requiring TLS, etc.
|
||||
func newRPCServer(s *server, macService *macaroons.Service,
|
||||
subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption,
|
||||
restServerOpts []grpc.DialOption,
|
||||
tlsCfg *tls.Config) (*rpcServer, error) {
|
||||
|
||||
var (
|
||||
subServers []lnrpc.SubServer
|
||||
subServerPerms []lnrpc.MacaroonPerms
|
||||
)
|
||||
|
||||
// Before we create any of the sub-servers, we need to ensure that all
|
||||
// the dependencies they need are properly populated within each sub
|
||||
// server configuration struct.
|
||||
err := subServerCgs.PopulateDependencies(
|
||||
s.cc, networkDir, macService,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that the sub-servers have all their dependencies in place, we
|
||||
// can create each sub-server!
|
||||
registeredSubServers := lnrpc.RegisteredSubServers()
|
||||
for _, subServer := range registeredSubServers {
|
||||
subServerInstance, macPerms, err := subServer.New(subServerCgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll collect the sub-server, and also the set of
|
||||
// permissions it needs for macaroons so we can apply the
|
||||
// interceptors below.
|
||||
subServers = append(subServers, subServerInstance)
|
||||
subServerPerms = append(subServerPerms, macPerms)
|
||||
}
|
||||
|
||||
// Next, we need to merge the set of sub server macaroon permissions
|
||||
// with the main RPC server permissions so we can unite them under a
|
||||
// single set of interceptors.
|
||||
for _, subServerPerm := range subServerPerms {
|
||||
for method, ops := range subServerPerm {
|
||||
// For each new method:ops combo, we also ensure that
|
||||
// non of the sub-servers try to override each other.
|
||||
if _, ok := permissions[method]; ok {
|
||||
return nil, fmt.Errorf("detected duplicate "+
|
||||
"macaroon constraints for path: %v",
|
||||
method)
|
||||
}
|
||||
|
||||
permissions[method] = ops
|
||||
}
|
||||
}
|
||||
|
||||
// If macaroons aren't disabled (a non-nil service), then we'll set up
|
||||
// our set of interceptors which will allow us handle the macaroon
|
||||
// authentication in a single location .
|
||||
if macService != nil {
|
||||
unaryInterceptor := grpc.UnaryInterceptor(
|
||||
macService.UnaryServerInterceptor(permissions),
|
||||
)
|
||||
streamInterceptor := grpc.StreamInterceptor(
|
||||
macService.StreamServerInterceptor(permissions),
|
||||
)
|
||||
|
||||
serverOpts = append(serverOpts,
|
||||
unaryInterceptor, streamInterceptor,
|
||||
)
|
||||
}
|
||||
|
||||
// Finally, with all the pre-set up complete, we can create the main
|
||||
// gRPC server, and register the main lnrpc server along side.
|
||||
grpcServer := grpc.NewServer(serverOpts...)
|
||||
rootRPCServer := &rpcServer{
|
||||
restServerOpts: restServerOpts,
|
||||
subServers: subServers,
|
||||
tlsCfg: tlsCfg,
|
||||
grpcServer: grpcServer,
|
||||
server: s,
|
||||
quit: make(chan struct{}, 1),
|
||||
}
|
||||
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
|
||||
|
||||
// Now the main RPC server has been registered, we'll iterate through
|
||||
// all the sub-RPC servers and register them to ensure that requests
|
||||
// are properly routed towards them.
|
||||
for _, subServer := range subServers {
|
||||
err := subServer.RegisterWithRootServer(grpcServer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to register "+
|
||||
"sub-server %v with root: %v",
|
||||
subServer.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
return rootRPCServer, nil
|
||||
}
|
||||
|
||||
// Start launches any helper goroutines required for the rpcServer to function.
|
||||
@ -360,6 +485,72 @@ func (r *rpcServer) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// First, we'll start all the sub-servers to ensure that they're ready
|
||||
// to take new requests in.
|
||||
//
|
||||
// TODO(roasbeef): some may require that the entire daemon be started
|
||||
// at that point
|
||||
for _, subServer := range r.subServers {
|
||||
rpcsLog.Debugf("Starting sub RPC server: %v", subServer.Name())
|
||||
|
||||
if err := subServer.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// With all the sub-servers started, we'll spin up the listeners for
|
||||
// the main RPC server itself.
|
||||
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
|
||||
}
|
||||
|
||||
r.listenerCleanUp = append(r.listenerCleanUp, func() {
|
||||
lis.Close()
|
||||
})
|
||||
|
||||
go func() {
|
||||
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
|
||||
r.grpcServer.Serve(lis)
|
||||
}()
|
||||
}
|
||||
|
||||
// Finally, start the REST proxy for our gRPC server above.
|
||||
//
|
||||
// TODO(roasbeef): eventually also allow the sub-servers to themselves
|
||||
// have a REST proxy.
|
||||
mux := proxy.NewServeMux()
|
||||
err := lnrpc.RegisterLightningHandlerFromEndpoint(
|
||||
context.Background(), mux, cfg.RPCListeners[0].String(),
|
||||
r.restServerOpts,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, restEndpoint := range cfg.RESTListeners {
|
||||
lis, err := lncfg.TLSListenOnAddress(restEndpoint, r.tlsCfg)
|
||||
if err != nil {
|
||||
ltndLog.Errorf(
|
||||
"gRPC proxy unable to listen on %s",
|
||||
restEndpoint,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
r.listenerCleanUp = append(r.listenerCleanUp, func() {
|
||||
lis.Close()
|
||||
})
|
||||
|
||||
go func() {
|
||||
rpcsLog.Infof("gRPC proxy started at %s", lis.Addr())
|
||||
http.Serve(lis, mux)
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -369,8 +560,30 @@ func (r *rpcServer) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
rpcsLog.Infof("Stopping RPC Server")
|
||||
|
||||
close(r.quit)
|
||||
|
||||
// After we've signalled all of our active goroutines to exit, we'll
|
||||
// then do the same to signal a graceful shutdown of all the sub
|
||||
// servers.
|
||||
for _, subServer := range r.subServers {
|
||||
rpcsLog.Infof("Stopping %v Sub-RPC Server",
|
||||
subServer.Name())
|
||||
|
||||
if err := subServer.Stop(); err != nil {
|
||||
rpcsLog.Errorf("unable to stop sub-server %v: %v",
|
||||
subServer.Name(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we can clean up all the listening sockets to ensure that we
|
||||
// give the file descriptors back to the OS.
|
||||
for _, cleanUp := range r.listenerCleanUp {
|
||||
cleanUp()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
128
subrpcserver_config.go
Normal file
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"`
|
||||
}
|
||||
|
||||
// PopulateDependencies 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) PopulateDependencies(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
|
||||
}
|
Loading…
Reference in New Issue
Block a user