Merge pull request #6149 from bhandras/graph_import_rpc

lnd: add `devrpc` sub server and `devrpc.ImportGraph` to import graph dumps
This commit is contained in:
Oliver Gugger 2022-01-28 10:24:59 +01:00 committed by GitHub
commit b77c1fb200
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1403 additions and 70 deletions

View file

@ -15,6 +15,7 @@ run:
build-tags:
- autopilotrpc
- chainrpc
- dev
- invoicesrpc
- signrpc
- walletrpc

View file

@ -26,6 +26,7 @@ import (
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/stretchr/testify/require"
)
func testSingleConfirmationNotification(miner *rpctest.Harness,
@ -1178,7 +1179,8 @@ func testReorgConf(miner *rpctest.Harness,
// Reorganize transaction out of the chain by generating a longer fork
// from the other miner. The transaction is not included in this fork.
miner2.Client.Generate(2)
_, err = miner2.Client.Generate(2)
require.NoError(t, err)
// Reconnect nodes to reach consensus on the longest chain. miner2's chain
// should win and become active on miner1.

View file

@ -0,0 +1,64 @@
//go:build dev
// +build dev
package main
import (
"bytes"
"fmt"
"io/ioutil"
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/urfave/cli"
)
// devCommands will return the set of commands to enable for devrpc builds.
func devCommands() []cli.Command {
return []cli.Command{
{
Name: "importgraph",
Category: "Development",
Description: "Imports graph from describegraph JSON",
Usage: "Import the network graph.",
ArgsUsage: "graph-json-file",
Action: actionDecorator(importGraph),
},
}
}
func getDevClient(ctx *cli.Context) (devrpc.DevClient, func()) {
conn := getClientConn(ctx, false)
cleanUp := func() {
conn.Close()
}
return devrpc.NewDevClient(conn), cleanUp
}
func importGraph(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getDevClient(ctx)
defer cleanUp()
jsonFile := lncfg.CleanAndExpandPath(ctx.Args().First())
jsonBytes, err := ioutil.ReadFile(jsonFile)
if err != nil {
return fmt.Errorf("error reading JSON from file %v: %v",
jsonFile, err)
}
jsonGraph := &lnrpc.ChannelGraph{}
err = jsonpb.Unmarshal(bytes.NewReader(jsonBytes), jsonGraph)
if err != nil {
return fmt.Errorf("error parsing JSON: %v", err)
}
res, err := client.ImportGraph(ctxc, jsonGraph)
if err != nil {
return err
}
printRespJSON(res)
return nil
}

View file

@ -0,0 +1,11 @@
//go:build !dev
// +build !dev
package main
import "github.com/urfave/cli"
// devCommands will return nil for non-devrpc builds.
func devCommands() []cli.Command {
return nil
}

View file

@ -39,7 +39,7 @@ var (
// maxMsgRecvSize is the largest message our client will receive. We
// set this to 200MiB atm.
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200)
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)
)
func fatal(err error) {
@ -400,6 +400,7 @@ func main() {
app.Commands = append(app.Commands, walletCommands()...)
app.Commands = append(app.Commands, watchtowerCommands()...)
app.Commands = append(app.Commands, wtclientCommands()...)
app.Commands = append(app.Commands, devCommands()...)
if err := app.Run(os.Args); err != nil {
fatal(err)

View file

@ -1596,7 +1596,7 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
// Finally, ensure that the user's color is correctly formatted,
// otherwise the server will not be able to start after the unlocking
// the wallet.
_, err = parseHexColor(cfg.Color)
_, err = lncfg.ParseHexColor(cfg.Color)
if err != nil {
return nil, mkErr("unable to parse node color: %v", err)
}

View file

@ -62,6 +62,9 @@
`pending_force_closing_channels` under `pendingchannels` whereas before was
empty(zero).
* [Add dev only RPC subserver and the devrpc.ImportGraph
call](https://github.com/lightningnetwork/lnd/pull/6149)
## Documentation
* Improved instructions on [how to build lnd for mobile](https://github.com/lightningnetwork/lnd/pull/6085).

34
lncfg/color.go Normal file
View file

@ -0,0 +1,34 @@
package lncfg
import (
"encoding/hex"
"errors"
"image/color"
"regexp"
)
var (
// validColorRegexp is a regexp that lets you check if a particular
// color string matches the standard hex color format #RRGGBB.
validColorRegexp = regexp.MustCompile("^#[A-Fa-f0-9]{6}$")
)
// ParseHexColor takes a hex string representation of a color in the
// form "#RRGGBB", parses the hex color values, and returns a color.RGBA
// struct of the same color.
func ParseHexColor(colorStr string) (color.RGBA, error) {
// Check if the hex color string is a valid color representation.
if !validColorRegexp.MatchString(colorStr) {
return color.RGBA{}, errors.New("color must be specified " +
"using a hexadecimal value in the form #RRGGBB")
}
// Decode the hex color string to bytes.
// The resulting byte array is in the form [R, G, B].
colorBytes, err := hex.DecodeString(colorStr[1:])
if err != nil {
return color.RGBA{}, err
}
return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2]}, nil
}

43
lncfg/color_test.go Normal file
View file

@ -0,0 +1,43 @@
package lncfg
import (
"testing"
)
func TestParseHexColor(t *testing.T) {
var colorTestCases = []struct {
test string
valid bool // If valid format
R byte
G byte
B byte
}{
{"#123", false, 0, 0, 0},
{"#1234567", false, 0, 0, 0},
{"$123456", false, 0, 0, 0},
{"#12345+", false, 0, 0, 0},
{"#fFGG00", false, 0, 0, 0},
{"", false, 0, 0, 0},
{"#123456", true, 0x12, 0x34, 0x56},
{"#C0FfeE", true, 0xc0, 0xff, 0xee},
}
// Perform the table driven tests.
for _, ct := range colorTestCases {
color, err := ParseHexColor(ct.test)
if !ct.valid && err == nil {
t.Fatalf("Invalid color string: %s, should return "+
"error, but did not", ct.test)
}
if ct.valid && err != nil {
t.Fatalf("Color %s valid to parse: %s", ct.test, err)
}
// Ensure that the string to hex decoding is working properly.
if color.R != ct.R || color.G != ct.G || color.B != ct.B {
t.Fatalf("Color %s incorrectly parsed as %v", ct.test, color)
}
}
}

5
lnd.go
View file

@ -268,6 +268,9 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
rpcServerOpts := interceptorChain.CreateServerOpts()
serverOpts = append(serverOpts, rpcServerOpts...)
serverOpts = append(
serverOpts, grpc.MaxRecvMsgSize(lnrpc.MaxGrpcMsgSize),
)
grpcServer := grpc.NewServer(serverOpts...)
defer grpcServer.Stop()
@ -771,7 +774,7 @@ func getTLSConfig(cfg *Config) ([]grpc.ServerOption, []grpc.DialOption,
restDialOpts := []grpc.DialOption{
grpc.WithTransportCredentials(restCreds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200),
grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize),
),
}

View file

@ -0,0 +1,19 @@
//go:build dev
// +build dev
package devrpc
import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightningnetwork/lnd/channeldb"
)
// Config is the primary configuration struct for the DEV RPC server. It
// contains all the items required for the rpc server to carry out its
// duties. Any 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 {
ActiveNetParams *chaincfg.Params
GraphDB *channeldb.ChannelGraph
}

View file

@ -0,0 +1,7 @@
//go:build !dev
// +build !dev
package devrpc
// Config is empty for non-devrpc builds.
type Config struct{}

144
lnrpc/devrpc/dev.pb.go Normal file
View file

@ -0,0 +1,144 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.6.1
// source: devrpc/dev.proto
package devrpc
import (
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ImportGraphResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *ImportGraphResponse) Reset() {
*x = ImportGraphResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_devrpc_dev_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ImportGraphResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ImportGraphResponse) ProtoMessage() {}
func (x *ImportGraphResponse) ProtoReflect() protoreflect.Message {
mi := &file_devrpc_dev_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ImportGraphResponse.ProtoReflect.Descriptor instead.
func (*ImportGraphResponse) Descriptor() ([]byte, []int) {
return file_devrpc_dev_proto_rawDescGZIP(), []int{0}
}
var File_devrpc_dev_proto protoreflect.FileDescriptor
var file_devrpc_dev_proto_rawDesc = []byte{
0x0a, 0x10, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x06, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x1a, 0x0f, 0x6c, 0x69, 0x67, 0x68,
0x74, 0x6e, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x15, 0x0a, 0x13, 0x49,
0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x32, 0x46, 0x0a, 0x03, 0x44, 0x65, 0x76, 0x12, 0x3f, 0x0a, 0x0b, 0x49, 0x6d, 0x70,
0x6f, 0x72, 0x74, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x13, 0x2e, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x47, 0x72, 0x61, 0x70, 0x68, 0x1a, 0x1b, 0x2e,
0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x72, 0x61,
0x70, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69,
0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2f, 0x64, 0x65, 0x76, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_devrpc_dev_proto_rawDescOnce sync.Once
file_devrpc_dev_proto_rawDescData = file_devrpc_dev_proto_rawDesc
)
func file_devrpc_dev_proto_rawDescGZIP() []byte {
file_devrpc_dev_proto_rawDescOnce.Do(func() {
file_devrpc_dev_proto_rawDescData = protoimpl.X.CompressGZIP(file_devrpc_dev_proto_rawDescData)
})
return file_devrpc_dev_proto_rawDescData
}
var file_devrpc_dev_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_devrpc_dev_proto_goTypes = []interface{}{
(*ImportGraphResponse)(nil), // 0: devrpc.ImportGraphResponse
(*lnrpc.ChannelGraph)(nil), // 1: lnrpc.ChannelGraph
}
var file_devrpc_dev_proto_depIdxs = []int32{
1, // 0: devrpc.Dev.ImportGraph:input_type -> lnrpc.ChannelGraph
0, // 1: devrpc.Dev.ImportGraph:output_type -> devrpc.ImportGraphResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_devrpc_dev_proto_init() }
func file_devrpc_dev_proto_init() {
if File_devrpc_dev_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_devrpc_dev_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ImportGraphResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_devrpc_dev_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_devrpc_dev_proto_goTypes,
DependencyIndexes: file_devrpc_dev_proto_depIdxs,
MessageInfos: file_devrpc_dev_proto_msgTypes,
}.Build()
File_devrpc_dev_proto = out.File
file_devrpc_dev_proto_rawDesc = nil
file_devrpc_dev_proto_goTypes = nil
file_devrpc_dev_proto_depIdxs = nil
}

168
lnrpc/devrpc/dev.pb.gw.go Normal file
View file

@ -0,0 +1,168 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: devrpc/dev.proto
/*
Package devrpc is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package devrpc
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
func request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Marshaler, client DevClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq lnrpc.ChannelGraph
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ImportGraph(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Dev_ImportGraph_0(ctx context.Context, marshaler runtime.Marshaler, server DevServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq lnrpc.ChannelGraph
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ImportGraph(ctx, &protoReq)
return msg, metadata, err
}
// RegisterDevHandlerServer registers the http handlers for service Dev to "mux".
// UnaryRPC :call DevServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterDevHandlerFromEndpoint instead.
func RegisterDevHandlerServer(ctx context.Context, mux *runtime.ServeMux, server DevServer) error {
mux.Handle("POST", pattern_Dev_ImportGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/devrpc.Dev/ImportGraph", runtime.WithHTTPPathPattern("/v2/dev/importgraph"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Dev_ImportGraph_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Dev_ImportGraph_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterDevHandlerFromEndpoint is same as RegisterDevHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterDevHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterDevHandler(ctx, mux, conn)
}
// RegisterDevHandler registers the http handlers for service Dev to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterDevHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterDevHandlerClient(ctx, mux, NewDevClient(conn))
}
// RegisterDevHandlerClient registers the http handlers for service Dev
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "DevClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "DevClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "DevClient" to call the correct interceptors.
func RegisterDevHandlerClient(ctx context.Context, mux *runtime.ServeMux, client DevClient) error {
mux.Handle("POST", pattern_Dev_ImportGraph_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req, "/devrpc.Dev/ImportGraph", runtime.WithHTTPPathPattern("/v2/dev/importgraph"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Dev_ImportGraph_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Dev_ImportGraph_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Dev_ImportGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "dev", "importgraph"}, ""))
)
var (
forward_Dev_ImportGraph_0 = runtime.ForwardResponseMessage
)

View file

@ -0,0 +1,51 @@
// Code generated by falafel 0.9.1. DO NOT EDIT.
// source: dev.proto
// +build js
package devrpc
import (
"context"
gateway "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc"
"google.golang.org/protobuf/encoding/protojson"
)
func RegisterDevJSONCallbacks(registry map[string]func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error))) {
marshaler := &gateway.JSONPb{
MarshalOptions: protojson.MarshalOptions{
UseProtoNames: true,
EmitUnpopulated: true,
},
}
registry["devrpc.Dev.ImportGraph"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &lnrpc.ChannelGraph{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewDevClient(conn)
resp, err := client.ImportGraph(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}

18
lnrpc/devrpc/dev.proto Normal file
View file

@ -0,0 +1,18 @@
syntax = "proto3";
import "lightning.proto";
package devrpc;
option go_package = "github.com/lightningnetwork/lnd/lnrpc/devrpc";
service Dev {
/* lncli: `importgraph`
ImportGraph imports a ChannelGraph into the graph database. Should only be
used for development.
*/
rpc ImportGraph (lnrpc.ChannelGraph) returns (ImportGraphResponse);
}
message ImportGraphResponse {
}

View file

@ -0,0 +1,230 @@
{
"swagger": "2.0",
"info": {
"title": "devrpc/dev.proto",
"version": "version not set"
},
"tags": [
{
"name": "Dev"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/v2/dev/importgraph": {
"post": {
"summary": "lncli: `importgraph`\nImportGraph imports a ChannelGraph into the graph database. Should only be\nused for development.",
"operationId": "Dev_ImportGraph",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/devrpcImportGraphResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/lnrpcChannelGraph"
}
}
],
"tags": [
"Dev"
]
}
}
},
"definitions": {
"devrpcImportGraphResponse": {
"type": "object"
},
"lnrpcChannelEdge": {
"type": "object",
"properties": {
"channel_id": {
"type": "string",
"format": "uint64",
"description": "The unique channel ID for the channel. The first 3 bytes are the block\nheight, the next 3 the index within the block, and the last 2 bytes are the\noutput index for the channel."
},
"chan_point": {
"type": "string"
},
"last_update": {
"type": "integer",
"format": "int64"
},
"node1_pub": {
"type": "string"
},
"node2_pub": {
"type": "string"
},
"capacity": {
"type": "string",
"format": "int64"
},
"node1_policy": {
"$ref": "#/definitions/lnrpcRoutingPolicy"
},
"node2_policy": {
"$ref": "#/definitions/lnrpcRoutingPolicy"
}
},
"description": "A fully authenticated channel along with all its unique attributes.\nOnce an authenticated channel announcement has been processed on the network,\nthen an instance of ChannelEdgeInfo encapsulating the channels attributes is\nstored. The other portions relevant to routing policy of a channel are stored\nwithin a ChannelEdgePolicy for each direction of the channel."
},
"lnrpcChannelGraph": {
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcLightningNode"
},
"title": "The list of `LightningNode`s in this channel graph"
},
"edges": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcChannelEdge"
},
"title": "The list of `ChannelEdge`s in this channel graph"
}
},
"description": "Returns a new instance of the directed channel graph."
},
"lnrpcFeature": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"is_required": {
"type": "boolean"
},
"is_known": {
"type": "boolean"
}
}
},
"lnrpcLightningNode": {
"type": "object",
"properties": {
"last_update": {
"type": "integer",
"format": "int64"
},
"pub_key": {
"type": "string"
},
"alias": {
"type": "string"
},
"addresses": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcNodeAddress"
}
},
"color": {
"type": "string"
},
"features": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/lnrpcFeature"
}
}
},
"description": "An individual vertex/node within the channel graph. A node is\nconnected to other nodes by one or more channel edges emanating from it. As the\ngraph is directed, a node will also have an incoming edge attached to it for\neach outgoing edge."
},
"lnrpcNodeAddress": {
"type": "object",
"properties": {
"network": {
"type": "string"
},
"addr": {
"type": "string"
}
}
},
"lnrpcRoutingPolicy": {
"type": "object",
"properties": {
"time_lock_delta": {
"type": "integer",
"format": "int64"
},
"min_htlc": {
"type": "string",
"format": "int64"
},
"fee_base_msat": {
"type": "string",
"format": "int64"
},
"fee_rate_milli_msat": {
"type": "string",
"format": "int64"
},
"disabled": {
"type": "boolean"
},
"max_htlc_msat": {
"type": "string",
"format": "uint64"
},
"last_update": {
"type": "integer",
"format": "int64"
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"type_url": {
"type": "string"
},
"value": {
"type": "string",
"format": "byte"
}
}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
}
}
}

8
lnrpc/devrpc/dev.yaml Normal file
View file

@ -0,0 +1,8 @@
type: google.api.Service
config_version: 3
http:
rules:
- selector: devrpc.Dev.ImportGraph
post: "/v2/dev/importgraph"
body: "*"

108
lnrpc/devrpc/dev_grpc.pb.go Normal file
View file

@ -0,0 +1,108 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package devrpc
import (
context "context"
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// DevClient is the client API for Dev service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type DevClient interface {
// lncli: `importgraph`
//ImportGraph imports a ChannelGraph into the graph database. Should only be
//used for development.
ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error)
}
type devClient struct {
cc grpc.ClientConnInterface
}
func NewDevClient(cc grpc.ClientConnInterface) DevClient {
return &devClient{cc}
}
func (c *devClient) ImportGraph(ctx context.Context, in *lnrpc.ChannelGraph, opts ...grpc.CallOption) (*ImportGraphResponse, error) {
out := new(ImportGraphResponse)
err := c.cc.Invoke(ctx, "/devrpc.Dev/ImportGraph", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DevServer is the server API for Dev service.
// All implementations must embed UnimplementedDevServer
// for forward compatibility
type DevServer interface {
// lncli: `importgraph`
//ImportGraph imports a ChannelGraph into the graph database. Should only be
//used for development.
ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error)
mustEmbedUnimplementedDevServer()
}
// UnimplementedDevServer must be embedded to have forward compatible implementations.
type UnimplementedDevServer struct {
}
func (UnimplementedDevServer) ImportGraph(context.Context, *lnrpc.ChannelGraph) (*ImportGraphResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportGraph not implemented")
}
func (UnimplementedDevServer) mustEmbedUnimplementedDevServer() {}
// UnsafeDevServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to DevServer will
// result in compilation errors.
type UnsafeDevServer interface {
mustEmbedUnimplementedDevServer()
}
func RegisterDevServer(s grpc.ServiceRegistrar, srv DevServer) {
s.RegisterService(&Dev_ServiceDesc, srv)
}
func _Dev_ImportGraph_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(lnrpc.ChannelGraph)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DevServer).ImportGraph(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/devrpc.Dev/ImportGraph",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DevServer).ImportGraph(ctx, req.(*lnrpc.ChannelGraph))
}
return interceptor(ctx, in, info, handler)
}
// Dev_ServiceDesc is the grpc.ServiceDesc for Dev service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Dev_ServiceDesc = grpc.ServiceDesc{
ServiceName: "devrpc.Dev",
HandlerType: (*DevServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ImportGraph",
Handler: _Dev_ImportGraph_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "devrpc/dev.proto",
}

342
lnrpc/devrpc/dev_server.go Normal file
View file

@ -0,0 +1,342 @@
//go:build dev
// +build dev
package devrpc
import (
"context"
"encoding/hex"
"fmt"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"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 tt as the name of our
// RPC service.
subServerName = "DevRPC"
)
var (
// macPermissions maps RPC calls to the permissions they require.
macPermissions = map[string][]bakery.Op{
"/devrpc.Dev/ImportGraph": {{
Entity: "offchain",
Action: "write",
}},
}
)
// ServerShell is a shell struct holding a reference to the actual sub-server.
// It is used to register the gRPC sub-server with the root server before we
// have the necessary dependencies to populate the actual sub-server.
type ServerShell struct {
DevServer
}
// Server is a sub-server of the main RPC server: the dev RPC. This sub
// RPC server allows developers to set and query LND state that is not possible
// during normal operation.
type Server struct {
started int32 // To be used atomically.
shutdown int32 // To be used atomically.
// Required by the grpc-gateway/v2 library for forward compatibility.
// Must be after the atomically used variables to not break struct
// alignment.
UnimplementedDevServer
cfg *Config
}
// A compile time check to ensure that Server fully implements the
// DevServer gRPC service.
var _ DevServer = (*Server)(nil)
// New returns a new instance of the devrpc Dev 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) {
// We don't create any new macaroons for this subserver, instead reuse
// existing onchain/offchain permissions.
server := &Server{
cfg: cfg,
}
return server, macPermissions, nil
}
// Start launches any helper goroutines required for the Server to function.
//
// NOTE: This is part of the lnrpc.SubServer interface.
func (s *Server) Start() error {
if atomic.AddInt32(&s.started, 1) != 1 {
return nil
}
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 {
if atomic.AddInt32(&s.shutdown, 1) != 1 {
return nil
}
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.GrpcHandler interface.
func (r *ServerShell) 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.
RegisterDevServer(grpcServer, r)
log.Debugf("DEV RPC server successfully register with root the " +
"gRPC server")
return nil
}
// RegisterWithRestServer will be called by the root REST mux to direct a sub
// RPC server to register itself with the main REST mux 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.GrpcHandler interface.
func (r *ServerShell) RegisterWithRestServer(ctx context.Context,
mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error {
// We make sure that we register it with the main REST server to ensure
// all our methods are routed properly.
err := RegisterDevHandlerFromEndpoint(ctx, mux, dest, opts)
if err != nil {
log.Errorf("Could not register DEV REST server with the root "+
"REST server: %v", err)
return err
}
log.Debugf("DEV REST server successfully registered with the root " +
"REST server")
return nil
}
// CreateSubServer populates the subserver's dependencies using the passed
// SubServerConfigDispatcher. This method should fully initialize the
// sub-server instance, making it ready for action. It returns the macaroon
// permissions that the sub-server wishes to pass on to the root server for all
// methods routed towards it.
//
// NOTE: This is part of the lnrpc.GrpcHandler interface.
func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) (
lnrpc.SubServer, lnrpc.MacaroonPerms, error) {
subServer, macPermissions, err := createNewSubServer(configRegistry)
if err != nil {
return nil, nil, err
}
r.DevServer = subServer
return subServer, macPermissions, nil
}
func parseOutPoint(s string) (*wire.OutPoint, error) {
split := strings.Split(s, ":")
if len(split) != 2 {
return nil, fmt.Errorf("expecting outpoint to be in format of: " +
"txid:index")
}
index, err := strconv.ParseInt(split[1], 10, 32)
if err != nil {
return nil, fmt.Errorf("unable to decode output index: %v", err)
}
txid, err := chainhash.NewHashFromStr(split[0])
if err != nil {
return nil, fmt.Errorf("unable to parse hex string: %v", err)
}
return &wire.OutPoint{
Hash: *txid,
Index: uint32(index),
}, nil
}
func parsePubKey(pubKeyStr string) ([33]byte, error) {
var pubKey [33]byte
pubKeyBytes, err := hex.DecodeString(pubKeyStr)
if err != nil || len(pubKeyBytes) != 33 {
return pubKey, fmt.Errorf("invalid pubkey: %v", pubKeyStr)
}
copy(pubKey[:], pubKeyBytes)
return pubKey, nil
}
// ImportGraph imports a graph dump (without auth proofs).
//
// NOTE: Part of the DevServer interface.
func (s *Server) ImportGraph(ctx context.Context,
graph *lnrpc.ChannelGraph) (*ImportGraphResponse, error) {
// Obtain the pointer to the global singleton channel graph.
graphDB := s.cfg.GraphDB
var err error
for _, rpcNode := range graph.Nodes {
node := &channeldb.LightningNode{
HaveNodeAnnouncement: true,
LastUpdate: time.Unix(
int64(rpcNode.LastUpdate), 0,
),
Alias: rpcNode.Alias,
}
node.PubKeyBytes, err = parsePubKey(rpcNode.PubKey)
if err != nil {
return nil, err
}
featureBits := make([]lnwire.FeatureBit, 0, len(rpcNode.Features))
featureNames := make(map[lnwire.FeatureBit]string)
for featureBit, feature := range rpcNode.Features {
featureBits = append(
featureBits, lnwire.FeatureBit(featureBit),
)
featureNames[lnwire.FeatureBit(featureBit)] = feature.Name
}
featureVector := lnwire.NewRawFeatureVector(featureBits...)
node.Features = lnwire.NewFeatureVector(
featureVector, featureNames,
)
node.Color, err = lncfg.ParseHexColor(rpcNode.Color)
if err != nil {
return nil, err
}
if err := graphDB.AddLightningNode(node); err != nil {
return nil, fmt.Errorf("unable to add node %v: %v",
rpcNode.PubKey, err)
}
log.Debugf("Imported node: %v", rpcNode.PubKey)
}
for _, rpcEdge := range graph.Edges {
rpcEdge := rpcEdge
edge := &channeldb.ChannelEdgeInfo{
ChannelID: rpcEdge.ChannelId,
ChainHash: *s.cfg.ActiveNetParams.GenesisHash,
Capacity: btcutil.Amount(rpcEdge.Capacity),
}
edge.NodeKey1Bytes, err = parsePubKey(rpcEdge.Node1Pub)
if err != nil {
return nil, err
}
edge.NodeKey2Bytes, err = parsePubKey(rpcEdge.Node2Pub)
if err != nil {
return nil, err
}
channelPoint, err := parseOutPoint(rpcEdge.ChanPoint)
if err != nil {
return nil, err
}
edge.ChannelPoint = *channelPoint
if err := graphDB.AddChannelEdge(edge); err != nil {
return nil, fmt.Errorf("unable to add edge %v: %v",
rpcEdge.ChanPoint, err)
}
makePolicy := func(rpcPolicy *lnrpc.RoutingPolicy) *channeldb.ChannelEdgePolicy {
policy := &channeldb.ChannelEdgePolicy{
ChannelID: rpcEdge.ChannelId,
LastUpdate: time.Unix(
int64(rpcPolicy.LastUpdate), 0,
),
TimeLockDelta: uint16(
rpcPolicy.TimeLockDelta,
),
MinHTLC: lnwire.MilliSatoshi(
rpcPolicy.MinHtlc,
),
FeeBaseMSat: lnwire.MilliSatoshi(
rpcPolicy.FeeBaseMsat,
),
FeeProportionalMillionths: lnwire.MilliSatoshi(
rpcPolicy.FeeRateMilliMsat,
),
}
if rpcPolicy.MaxHtlcMsat > 0 {
policy.MaxHTLC = lnwire.MilliSatoshi(
rpcPolicy.MaxHtlcMsat,
)
policy.MessageFlags |= lnwire.ChanUpdateOptionMaxHtlc
}
return policy
}
if rpcEdge.Node1Policy != nil {
policy := makePolicy(rpcEdge.Node1Policy)
policy.ChannelFlags = 0
if err := graphDB.UpdateEdgePolicy(policy); err != nil {
return nil, fmt.Errorf(
"unable to update policy: %v", err)
}
}
if rpcEdge.Node2Policy != nil {
policy := makePolicy(rpcEdge.Node2Policy)
policy.ChannelFlags = 1
if err := graphDB.UpdateEdgePolicy(policy); err != nil {
return nil, fmt.Errorf(
"unable to update policy: %v", err)
}
}
log.Debugf("Added edge: %v", rpcEdge.ChannelId)
}
return &ImportGraphResponse{}, nil
}

67
lnrpc/devrpc/driver.go Normal file
View file

@ -0,0 +1,67 @@
//go:build dev
// +build dev
package devrpc
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
// createNewSubServer is a helper method that will create the new 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) (
*Server, 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.
subServerConf, 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 := subServerConf.(*Config)
if !ok {
return nil, nil, fmt.Errorf("wrong type of config for "+
"subserver %s, expected %T got %T", subServerName,
&Config{}, subServerConf)
}
// Before we try to make the new service instance, we'll perform
// some sanity checks on the arguments to ensure that they're useable.
if config.ActiveNetParams == nil {
return nil, nil, fmt.Errorf("ActiveNetParams must be set to " +
"create DevRPC")
}
if config.GraphDB == nil {
return nil, nil, fmt.Errorf("GraphDB must be set to create " +
"DevRPC")
}
return New(config)
}
func init() {
subServer := &lnrpc.SubServerDriver{
SubServerName: subServerName,
NewGrpcHandler: func() lnrpc.GrpcHandler {
return &ServerShell{}
},
}
// 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/devrpc/log.go Normal file
View file

@ -0,0 +1,45 @@
package devrpc
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("DRPC", nil))
}
// DisableLog disables all library log output. Logging output is disabled by
// 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 // nolint:unused
// 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 { // nolint:unused
return logClosure(c)
}

View file

@ -48,7 +48,7 @@ function generate() {
--custom_opt="$opts" \
lightning.proto stateservice.proto walletunlocker.proto
PACKAGES="autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc"
PACKAGES="autopilotrpc chainrpc invoicesrpc routerrpc signrpc verrpc walletrpc watchtowerrpc wtclientrpc devrpc"
for package in $PACKAGES; do
# Special import for the wallet kit.
manual_import=""
@ -56,6 +56,11 @@ function generate() {
manual_import="github.com/lightningnetwork/lnd/lnrpc/signrpc"
fi
# Special import for devrpc.
if [[ "$package" == "devrpc" ]]; then
manual_import="github.com/lightningnetwork/lnd/lnrpc"
fi
opts="package_name=$package,manual_import=$manual_import,js_stubs=1,build_tags=// +build js"
pushd $package
protoc -I/usr/local/include -I. -I.. \

View file

@ -14,4 +14,8 @@ var (
regexp.MustCompile("^/v1/channels/transaction-stream$"),
regexp.MustCompile("^/v2/router/htlcinterceptor$"),
}
// MaxGrpcMsgSize is used when we configure both server and clients to
// allow sending/receiving at most 200 MiB GRPC messages.
MaxGrpcMsgSize = 200 * 1024 * 1024
)

2
log.go
View file

@ -23,6 +23,7 @@ import (
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
@ -140,6 +141,7 @@ func SetupLoggers(root *build.RotatingLogWriter, interceptor signal.Interceptor)
AddSubLogger(root, "SGNR", interceptor, signrpc.UseLogger)
AddSubLogger(root, "WLKT", interceptor, walletrpc.UseLogger)
AddSubLogger(root, "ARPC", interceptor, autopilotrpc.UseLogger)
AddSubLogger(root, "DRPC", interceptor, devrpc.UseLogger)
AddSubLogger(root, "INVC", interceptor, invoices.UseLogger)
AddSubLogger(root, "NANN", interceptor, netann.UseLogger)
AddSubLogger(root, "WTWR", interceptor, watchtower.UseLogger)

View file

@ -6,11 +6,9 @@ import (
"crypto/rand"
"encoding/hex"
"fmt"
"image/color"
"math/big"
prand "math/rand"
"net"
"regexp"
"strconv"
"strings"
"sync"
@ -116,10 +114,6 @@ var (
// gracefully exiting.
ErrServerShuttingDown = errors.New("server is shutting down")
// validColorRegexp is a regexp that lets you check if a particular
// color string matches the standard hex color format #RRGGBB.
validColorRegexp = regexp.MustCompile("^#[A-Fa-f0-9]{6}$")
// MaxFundingAmount is a soft-limit of the maximum channel size
// currently accepted within the Lightning Protocol. This is
// defined in BOLT-0002, and serves as an initial precautionary limit
@ -772,7 +766,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
// the network.
//
// We'll start by parsing the node color from configuration.
color, err := parseHexColor(cfg.Color)
color, err := lncfg.ParseHexColor(cfg.Color)
if err != nil {
srvrLog.Errorf("unable to parse color: %v\n", err)
return nil, err
@ -4204,26 +4198,6 @@ func (s *server) Peers() []*peer.Brontide {
return peers
}
// parseHexColor takes a hex string representation of a color in the
// form "#RRGGBB", parses the hex color values, and returns a color.RGBA
// struct of the same color.
func parseHexColor(colorStr string) (color.RGBA, error) {
// Check if the hex color string is a valid color representation.
if !validColorRegexp.MatchString(colorStr) {
return color.RGBA{}, errors.New("Color must be specified " +
"using a hexadecimal value in the form #RRGGBB")
}
// Decode the hex color string to bytes.
// The resulting byte array is in the form [R, G, B].
colorBytes, err := hex.DecodeString(colorStr[1:])
if err != nil {
return color.RGBA{}, err
}
return color.RGBA{R: colorBytes[0], G: colorBytes[1], B: colorBytes[2]}, nil
}
// computeNextBackoff uses a truncated exponential backoff to compute the next
// backoff using the value of the exiting backoff. The returned duration is
// randomized in either direction by 1/20 to prevent tight loops from

View file

@ -22,44 +22,6 @@ import (
"github.com/lightningnetwork/lnd/lncfg"
)
func TestParseHexColor(t *testing.T) {
var colorTestCases = []struct {
test string
valid bool // If valid format
R byte
G byte
B byte
}{
{"#123", false, 0, 0, 0},
{"#1234567", false, 0, 0, 0},
{"$123456", false, 0, 0, 0},
{"#12345+", false, 0, 0, 0},
{"#fFGG00", false, 0, 0, 0},
{"", false, 0, 0, 0},
{"#123456", true, 0x12, 0x34, 0x56},
{"#C0FfeE", true, 0xc0, 0xff, 0xee},
}
// Perform the table driven tests.
for _, ct := range colorTestCases {
color, err := parseHexColor(ct.test)
if !ct.valid && err == nil {
t.Fatalf("Invalid color string: %s, should return "+
"error, but did not", ct.test)
}
if ct.valid && err != nil {
t.Fatalf("Color %s valid to parse: %s", ct.test, err)
}
// Ensure that the string to hex decoding is working properly.
if color.R != ct.R || color.G != ct.G || color.B != ct.B {
t.Fatalf("Color %s incorrectly parsed as %v", ct.test, color)
}
}
}
// TestTLSAutoRegeneration creates an expired TLS certificate, to test that a
// new TLS certificate pair is regenerated when the old pair expires. This is
// necessary because the pair expires after a little over a year.

View file

@ -14,6 +14,7 @@ import (
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/autopilotrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/devrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
@ -74,6 +75,11 @@ type subRPCServerConfigs struct {
// instance within lnd in order to add, remove, list registered client
// towers, etc.
WatchtowerClientRPC *wtclientrpc.Config `group:"wtclientrpc" namespace:"wtclientrpc"`
// DevRPC is a sub-RPC server that exposes functionality that allows
// developers manipulate LND state that is normally not possible.
// Should only be used for development purposes.
DevRPC *devrpc.Config `group:"devrpc" namespace:"devrpc"`
}
// PopulateDependencies attempts to iterate through all the sub-server configs
@ -270,6 +276,17 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
reflect.ValueOf(rpcLogger),
)
case *devrpc.Config:
subCfgValue := extractReflectValue(subCfg)
subCfgValue.FieldByName("ActiveNetParams").Set(
reflect.ValueOf(activeNetParams),
)
subCfgValue.FieldByName("GraphDB").Set(
reflect.ValueOf(graphDB),
)
default:
return fmt.Errorf("unknown field: %v, %T", fieldName,
cfg)