From 72e4fbff971179909faa86dee277d6941f44132b Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:53:09 -0700 Subject: [PATCH 01/12] lnrpc/watchtowerrpc: add protos for GetInfo --- lnrpc/watchtowerrpc/watchtower.pb.go | 217 +++++++++++++++++++++++++++ lnrpc/watchtowerrpc/watchtower.proto | 28 ++++ 2 files changed, 245 insertions(+) create mode 100644 lnrpc/watchtowerrpc/watchtower.pb.go create mode 100644 lnrpc/watchtowerrpc/watchtower.proto diff --git a/lnrpc/watchtowerrpc/watchtower.pb.go b/lnrpc/watchtowerrpc/watchtower.pb.go new file mode 100644 index 000000000..2a010f1ca --- /dev/null +++ b/lnrpc/watchtowerrpc/watchtower.pb.go @@ -0,0 +1,217 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: watchtowerrpc/watchtower.proto + +package watchtowerrpc + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + math "math" +) + +// 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.ProtoPackageIsVersion3 // please upgrade the proto package + +type GetInfoRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetInfoRequest) Reset() { *m = GetInfoRequest{} } +func (m *GetInfoRequest) String() string { return proto.CompactTextString(m) } +func (*GetInfoRequest) ProtoMessage() {} +func (*GetInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_9f019c0e859ad3d6, []int{0} +} + +func (m *GetInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetInfoRequest.Unmarshal(m, b) +} +func (m *GetInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetInfoRequest.Marshal(b, m, deterministic) +} +func (m *GetInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetInfoRequest.Merge(m, src) +} +func (m *GetInfoRequest) XXX_Size() int { + return xxx_messageInfo_GetInfoRequest.Size(m) +} +func (m *GetInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetInfoRequest proto.InternalMessageInfo + +type GetInfoResponse struct { + /// The public key of the watchtower. + Pubkey []byte `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + /// The listening addresses of the watchtower. + Listeners []string `protobuf:"bytes,2,rep,name=listeners,proto3" json:"listeners,omitempty"` + /// The URIs of the watchtower. + Uris []string `protobuf:"bytes,3,rep,name=uris,proto3" json:"uris,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetInfoResponse) Reset() { *m = GetInfoResponse{} } +func (m *GetInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetInfoResponse) ProtoMessage() {} +func (*GetInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9f019c0e859ad3d6, []int{1} +} + +func (m *GetInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetInfoResponse.Unmarshal(m, b) +} +func (m *GetInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetInfoResponse.Merge(m, src) +} +func (m *GetInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetInfoResponse.Size(m) +} +func (m *GetInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetInfoResponse proto.InternalMessageInfo + +func (m *GetInfoResponse) GetPubkey() []byte { + if m != nil { + return m.Pubkey + } + return nil +} + +func (m *GetInfoResponse) GetListeners() []string { + if m != nil { + return m.Listeners + } + return nil +} + +func (m *GetInfoResponse) GetUris() []string { + if m != nil { + return m.Uris + } + return nil +} + +func init() { + proto.RegisterType((*GetInfoRequest)(nil), "watchtowerrpc.GetInfoRequest") + proto.RegisterType((*GetInfoResponse)(nil), "watchtowerrpc.GetInfoResponse") +} + +func init() { proto.RegisterFile("watchtowerrpc/watchtower.proto", fileDescriptor_9f019c0e859ad3d6) } + +var fileDescriptor_9f019c0e859ad3d6 = []byte{ + // 213 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x2b, 0x4f, 0x2c, 0x49, + 0xce, 0x28, 0xc9, 0x2f, 0x4f, 0x2d, 0x2a, 0x2a, 0x48, 0xd6, 0x47, 0xf0, 0xf4, 0x0a, 0x8a, 0xf2, + 0x4b, 0xf2, 0x85, 0x78, 0x51, 0xe4, 0x95, 0x04, 0xb8, 0xf8, 0xdc, 0x53, 0x4b, 0x3c, 0xf3, 0xd2, + 0xf2, 0x83, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x94, 0xa2, 0xb9, 0xf8, 0xe1, 0x22, 0xc5, 0x05, + 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x62, 0x5c, 0x6c, 0x05, 0xa5, 0x49, 0xd9, 0xa9, 0x95, 0x12, 0x8c, + 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x50, 0x9e, 0x90, 0x0c, 0x17, 0x67, 0x4e, 0x66, 0x71, 0x49, 0x6a, + 0x5e, 0x6a, 0x51, 0xb1, 0x04, 0x93, 0x02, 0xb3, 0x06, 0x67, 0x10, 0x42, 0x40, 0x48, 0x88, 0x8b, + 0xa5, 0xb4, 0x28, 0xb3, 0x58, 0x82, 0x19, 0x2c, 0x01, 0x66, 0x1b, 0x85, 0x71, 0x71, 0x85, 0xc3, + 0xed, 0x17, 0xf2, 0xe0, 0x62, 0x87, 0x5a, 0x25, 0x24, 0xab, 0x87, 0xe2, 0x2e, 0x3d, 0x54, 0x47, + 0x49, 0xc9, 0xe1, 0x92, 0x86, 0xb8, 0xd0, 0xc9, 0x34, 0xca, 0x38, 0x3d, 0xb3, 0x24, 0xa3, 0x34, + 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x3f, 0x27, 0x33, 0x3d, 0xa3, 0x24, 0x2f, 0x33, 0x2f, 0x3d, 0x2f, + 0xb5, 0xa4, 0x3c, 0xbf, 0x28, 0x5b, 0x3f, 0x27, 0x2f, 0x45, 0x3f, 0x27, 0x0f, 0x35, 0x3c, 0x8a, + 0x0a, 0x92, 0x93, 0xd8, 0xc0, 0x61, 0x62, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xa1, 0x23, 0x0b, + 0x68, 0x35, 0x01, 0x00, 0x00, +} + +// 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 + +// WatchtowerClient is the client API for Watchtower service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type WatchtowerClient interface { + //* lncli: tower info + //GetInfo returns general information concerning the companion watchtower + //including it's public key and URIs where the server is currently + //listening for clients. + GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) +} + +type watchtowerClient struct { + cc *grpc.ClientConn +} + +func NewWatchtowerClient(cc *grpc.ClientConn) WatchtowerClient { + return &watchtowerClient{cc} +} + +func (c *watchtowerClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) { + out := new(GetInfoResponse) + err := c.cc.Invoke(ctx, "/watchtowerrpc.Watchtower/GetInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// WatchtowerServer is the server API for Watchtower service. +type WatchtowerServer interface { + //* lncli: tower info + //GetInfo returns general information concerning the companion watchtower + //including it's public key and URIs where the server is currently + //listening for clients. + GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) +} + +func RegisterWatchtowerServer(s *grpc.Server, srv WatchtowerServer) { + s.RegisterService(&_Watchtower_serviceDesc, srv) +} + +func _Watchtower_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WatchtowerServer).GetInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/watchtowerrpc.Watchtower/GetInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WatchtowerServer).GetInfo(ctx, req.(*GetInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Watchtower_serviceDesc = grpc.ServiceDesc{ + ServiceName: "watchtowerrpc.Watchtower", + HandlerType: (*WatchtowerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetInfo", + Handler: _Watchtower_GetInfo_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "watchtowerrpc/watchtower.proto", +} diff --git a/lnrpc/watchtowerrpc/watchtower.proto b/lnrpc/watchtowerrpc/watchtower.proto new file mode 100644 index 000000000..71c764863 --- /dev/null +++ b/lnrpc/watchtowerrpc/watchtower.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package watchtowerrpc; + +option go_package = "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc"; + +service Watchtower { + /** lncli: tower info + GetInfo returns general information concerning the companion watchtower + including it's public key and URIs where the server is currently + listening for clients. + */ + rpc GetInfo(GetInfoRequest) returns (GetInfoResponse); +} + +message GetInfoRequest{ +} + +message GetInfoResponse { + /// The public key of the watchtower. + bytes pubkey = 1 [json_name = "pubkey"]; + + /// The listening addresses of the watchtower. + repeated string listeners = 2 [json_name = "listeners"]; + + /// The URIs of the watchtower. + repeated string uris = 3 [json_name = "uris" ]; +} From 0db39d4fe617218f8639780a0f0c24b3511f4028 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:53:25 -0700 Subject: [PATCH 02/12] lnrpc/watchtowerrpc: add WatchtowerBackend configs --- lnrpc/watchtowerrpc/config_active.go | 13 +++++++++++++ lnrpc/watchtowerrpc/config_default.go | 6 ++++++ lnrpc/watchtowerrpc/interface.go | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 lnrpc/watchtowerrpc/config_active.go create mode 100644 lnrpc/watchtowerrpc/config_default.go create mode 100644 lnrpc/watchtowerrpc/interface.go diff --git a/lnrpc/watchtowerrpc/config_active.go b/lnrpc/watchtowerrpc/config_active.go new file mode 100644 index 000000000..985b49bff --- /dev/null +++ b/lnrpc/watchtowerrpc/config_active.go @@ -0,0 +1,13 @@ +// +build watchtowerrpc + +package watchtowerrpc + +// Config is the primary configuration struct for the watchtower RPC server. It +// contains all items required for the RPC server to carry out its duties. The +// fields with struct tags are meant to parsed as normal configuration options, +// while if able to be populated, the latter fields MUST also be specified. +type Config struct { + // Tower is the active watchtower which serves as the primary source for + // information presented via RPC. + Tower WatchtowerBackend +} diff --git a/lnrpc/watchtowerrpc/config_default.go b/lnrpc/watchtowerrpc/config_default.go new file mode 100644 index 000000000..ac40e0bf1 --- /dev/null +++ b/lnrpc/watchtowerrpc/config_default.go @@ -0,0 +1,6 @@ +// +build !watchtowerrpc + +package watchtowerrpc + +// Config is empty for non-watchtowerrpc builds. +type Config struct{} diff --git a/lnrpc/watchtowerrpc/interface.go b/lnrpc/watchtowerrpc/interface.go new file mode 100644 index 000000000..aafe3e06f --- /dev/null +++ b/lnrpc/watchtowerrpc/interface.go @@ -0,0 +1,23 @@ +package watchtowerrpc + +import ( + "net" + + "github.com/btcsuite/btcd/btcec" +) + +// WatchtowerBackend abstracts access to the watchtower information that is +// served via RPC connections. +type WatchtowerBackend interface { + // PubKey returns the public key for the watchtower used to + // authentication and encrypt traffic with clients. + PubKey() *btcec.PublicKey + + // ListeningAddrs returns the listening addresses where the watchtower + // server can accept client connections. + ListeningAddrs() []net.Addr + + // ExternalIPs returns the addresses where the watchtower can be reached + // by clients externally. + ExternalIPs() []net.Addr +} From b98bc768780b2ceba9f8de9a2f57888c599a16cb Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:53:40 -0700 Subject: [PATCH 03/12] lnrpc/watchtowerrpc/handler: implement Watchtower gRPC server --- lnrpc/watchtowerrpc/handler.go | 131 +++++++++++++++++++++++++++++++++ lnrpc/watchtowerrpc/log.go | 47 ++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 lnrpc/watchtowerrpc/handler.go create mode 100644 lnrpc/watchtowerrpc/log.go diff --git a/lnrpc/watchtowerrpc/handler.go b/lnrpc/watchtowerrpc/handler.go new file mode 100644 index 000000000..4aeaabee8 --- /dev/null +++ b/lnrpc/watchtowerrpc/handler.go @@ -0,0 +1,131 @@ +// +build watchtowerrpc + +package watchtowerrpc + +import ( + "context" + "errors" + fmt "fmt" + + "github.com/lightningnetwork/lnd/lnrpc" + "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 recognizes it as the name of our + // RPC service. + subServerName = "WatchtowerRPC" +) + +var ( + // macPermissions maps RPC calls to the permissions they require. + macPermissions = map[string][]bakery.Op{ + "/watchtowerrpc.Watchtower/GetInfo": {{ + Entity: "info", + Action: "read", + }}, + } + + // ErrTowerNotActive signals that RPC calls cannot be processed because + // the watchtower is not active. + ErrTowerNotActive = errors.New("watchtower not active") +) + +// Handler is the RPC server we'll use to interact with the backing active +// watchtower. +type Handler struct { + cfg Config +} + +// A compile time check to ensure that Handler fully implements the Handler gRPC +// service. +var _ WatchtowerServer = (*Handler)(nil) + +// New returns a new instance of the Watchtower 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) (*Handler, lnrpc.MacaroonPerms, error) { + return &Handler{*cfg}, macPermissions, nil +} + +// Start launches any helper goroutines required for the Handler to function. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (c *Handler) Start() error { + return nil +} + +// Stop signals any active goroutines for a graceful closure. +// +// NOTE: This is part of the lnrpc.SubServer interface. +func (c *Handler) 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 (c *Handler) 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 (c *Handler) 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. + RegisterWatchtowerServer(grpcServer, c) + + log.Debugf("Watchtower RPC server successfully register with root " + + "gRPC server") + + return nil +} + +// AddTower adds a new watchtower reachable at the given address and considers +// it for new sessions. If the watchtower already exists, then any new addresses +// included will be considered when dialing it for session negotiations and +// backups. +func (c *Handler) GetInfo(ctx context.Context, + req *GetInfoRequest) (*GetInfoResponse, error) { + + if err := c.isActive(); err != nil { + return nil, err + } + + pubkey := c.cfg.Tower.PubKey().SerializeCompressed() + + var listeners []string + for _, addr := range c.cfg.Tower.ListeningAddrs() { + listeners = append(listeners, addr.String()) + } + + var uris []string + for _, addr := range c.cfg.Tower.ExternalIPs() { + uris = append(uris, fmt.Sprintf("%x@%v", pubkey, addr)) + } + + return &GetInfoResponse{ + Pubkey: pubkey, + Listeners: listeners, + Uris: uris, + }, nil +} + +// isActive returns nil if the tower backend is initialized, and the Handler can +// proccess RPC requests. +func (c *Handler) isActive() error { + if c.cfg.Tower != nil { + return nil + } + return ErrTowerNotActive +} diff --git a/lnrpc/watchtowerrpc/log.go b/lnrpc/watchtowerrpc/log.go new file mode 100644 index 000000000..af2bc6401 --- /dev/null +++ b/lnrpc/watchtowerrpc/log.go @@ -0,0 +1,47 @@ +package watchtowerrpc + +import ( + "github.com/btcsuite/btclog" + "github.com/lightningnetwork/lnd/build" +) + +// Subsystem defines the logging code for this subsystem. +const Subsystem = "WRPC" + +// 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(Subsystem, 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 + +// 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) +} From 7e81a5144e19e7f2f6133e041632655ab0bd6f58 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:53:56 -0700 Subject: [PATCH 04/12] watchtower: rename DefaultPeerPortStr -> DefaultListenAddr --- watchtower/conf.go | 5 +++-- watchtower/config.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/watchtower/conf.go b/watchtower/conf.go index 212109956..bf071ae87 100644 --- a/watchtower/conf.go +++ b/watchtower/conf.go @@ -1,6 +1,7 @@ package watchtower import ( + "strconv" "time" ) @@ -36,7 +37,7 @@ func (c *Conf) Apply(cfg *Config, // If no addresses are specified by the Config, we will resort // to the default peer port. if len(c.RawListeners) == 0 { - addr := DefaultPeerPortStr + addr := DefaultListenAddr c.RawListeners = append(c.RawListeners, addr) } @@ -44,7 +45,7 @@ func (c *Conf) Apply(cfg *Config, // used by the brontide listener. var err error cfg.ListenAddrs, err = normalizer( - c.RawListeners, DefaultPeerPortStr, + c.RawListeners, strconv.Itoa(DefaultPeerPort), cfg.Net.ResolveTCPAddr, ) if err != nil { diff --git a/watchtower/config.go b/watchtower/config.go index b8136682c..f5b5721e9 100644 --- a/watchtower/config.go +++ b/watchtower/config.go @@ -28,8 +28,9 @@ const ( ) var ( - // DefaultPeerPortStr is the default server port as a string. - DefaultPeerPortStr = fmt.Sprintf(":%d", DefaultPeerPort) + // DefaultListenAddr is the default watchtower address listening on all + // interfaces. + DefaultListenAddr = fmt.Sprintf(":%d", DefaultPeerPort) ) // Config defines the resources and parameters used to configure a Watchtower. From fa966a490143eeea38175a3d8aa7761d417f2941 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:54:14 -0700 Subject: [PATCH 05/12] watchtower: add externalip CLI configuration --- watchtower/conf.go | 21 +++++++++++++++++++++ watchtower/config.go | 6 +++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/watchtower/conf.go b/watchtower/conf.go index bf071ae87..653ed1e85 100644 --- a/watchtower/conf.go +++ b/watchtower/conf.go @@ -11,6 +11,9 @@ type Conf struct { // RawListeners configures the watchtower's listening ports/interfaces. RawListeners []string `long:"listen" description:"Add interfaces/ports to listen for peer connections"` + // RawExternalIPs configures the watchtower's external ports/interfaces. + RawExternalIPs []string `long:"externalip" description:"Add interfaces/ports where the watchtower can accept peer connections"` + // ReadTimeout specifies the duration the tower will wait when trying to // read a message from a client before hanging up. ReadTimeout time.Duration `long:"readtimeout" description:"Duration the watchtower server will wait for messages to be received before hanging up on clients"` @@ -53,6 +56,24 @@ func (c *Conf) Apply(cfg *Config, } } + // Set the Config's external ips if they are empty. + if cfg.ExternalIPs == nil { + // Without a network, we will be unable to resolve the external + // IP addresses. + if cfg.Net == nil { + return nil, ErrNoNetwork + } + + var err error + cfg.ExternalIPs, err = normalizer( + c.RawExternalIPs, strconv.Itoa(DefaultPeerPort), + cfg.Net.ResolveTCPAddr, + ) + if err != nil { + return nil, err + } + } + // If the Config has no read timeout, we will use the parsed Conf // value. if cfg.ReadTimeout == 0 && c.ReadTimeout != 0 { diff --git a/watchtower/config.go b/watchtower/config.go index f5b5721e9..26dfac75a 100644 --- a/watchtower/config.go +++ b/watchtower/config.go @@ -74,9 +74,13 @@ type Config struct { // have stronger guarantees wrt. returned error types. PublishTx func(*wire.MsgTx) error - // ListenAddrs specifies which address to which clients may connect. + // ListenAddrs specifies the listening addresses of the tower. ListenAddrs []net.Addr + // ExternalIPs specifies the addresses to which clients may connect to + // the tower. + ExternalIPs []net.Addr + // ReadTimeout specifies how long a client may go without sending a // message. ReadTimeout time.Duration From c06d71530a2340442c8550682f652d12b9fc753d Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:54:31 -0700 Subject: [PATCH 06/12] watchtowerrpc/standalone: implement watchtowerrpc.WatchtowerBackend iface --- watchtower/standalone.go | 45 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/watchtower/standalone.go b/watchtower/standalone.go index 83bf49dc6..8f50dc65a 100644 --- a/watchtower/standalone.go +++ b/watchtower/standalone.go @@ -4,6 +4,7 @@ import ( "net" "sync/atomic" + "github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/brontide" "github.com/lightningnetwork/lnd/watchtower/lookout" "github.com/lightningnetwork/lnd/watchtower/wtserver" @@ -20,6 +21,9 @@ type Standalone struct { cfg *Config + // listeners is a reference to the wtserver's listeners. + listeners []net.Listener + // server is the client endpoint, used for negotiating sessions and // uploading state updates. server wtserver.Interface @@ -92,9 +96,10 @@ func New(cfg *Config) (*Standalone, error) { } return &Standalone{ - cfg: cfg, - server: server, - lookout: lookout, + cfg: cfg, + listeners: listeners, + server: server, + lookout: lookout, }, nil } @@ -136,3 +141,37 @@ func (w *Standalone) Stop() error { return nil } + +// PubKey returns the public key for the watchtower used to authentication and +// encrypt traffic with clients. +// +// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface. +func (w *Standalone) PubKey() *btcec.PublicKey { + return w.cfg.NodePrivKey.PubKey() +} + +// ListeningAddrs returns the listening addresses where the watchtower server +// can accept client connections. +// +// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface. +func (w *Standalone) ListeningAddrs() []net.Addr { + addrs := make([]net.Addr, 0, len(w.listeners)) + for _, listener := range w.listeners { + addrs = append(addrs, listener.Addr()) + } + + return addrs +} + +// ExternalIPs returns the addresses where the watchtower can be reached by +// clients externally. +// +// NOTE: Part of the watchtowerrpc.WatchtowerBackend interface. +func (w *Standalone) ExternalIPs() []net.Addr { + addrs := make([]net.Addr, 0, len(w.cfg.ExternalIPs)) + for _, addr := range w.cfg.ExternalIPs { + addrs = append(addrs, addr) + } + + return addrs +} From 59c9418eca335b8fb2059bdb89adcee6c6ed6a3f Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:54:45 -0700 Subject: [PATCH 07/12] lnd+rpcserver: thread watchtower into subserver configs --- lnd.go | 2 +- rpcserver.go | 4 +++- subrpcserver_config.go | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lnd.go b/lnd.go index 28b3d511e..eaf8f877f 100644 --- a/lnd.go +++ b/lnd.go @@ -406,7 +406,7 @@ func Main() error { rpcServer, err := newRPCServer( server, macaroonService, cfg.SubRPCServers, serverOpts, restDialOpts, restProxyDest, atplManager, server.invoices, - tlsCfg, + tower, tlsCfg, ) if err != nil { srvrLog.Errorf("unable to start RPC server: %v", err) diff --git a/rpcserver.go b/rpcserver.go index 604dbc55e..88c30aebc 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -17,6 +17,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/watchtower" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec" @@ -440,7 +441,7 @@ func newRPCServer(s *server, macService *macaroons.Service, subServerCgs *subRPCServerConfigs, serverOpts []grpc.ServerOption, restDialOpts []grpc.DialOption, restProxyDest string, atpl *autopilot.Manager, invoiceRegistry *invoices.InvoiceRegistry, - tlsCfg *tls.Config) (*rpcServer, error) { + tower *watchtower.Standalone, tlsCfg *tls.Config) (*rpcServer, error) { // Set up router rpc backend. channelGraph := s.chanDB.ChannelGraph() @@ -494,6 +495,7 @@ func newRPCServer(s *server, macService *macaroons.Service, s.cc, networkDir, macService, atpl, invoiceRegistry, s.htlcSwitch, activeNetParams.Params, s.chanRouter, routerBackend, s.nodeSigner, s.chanDB, s.sweeper, + tower, ) if err != nil { return nil, err diff --git a/subrpcserver_config.go b/subrpcserver_config.go index cb9b20768..600f2d022 100644 --- a/subrpcserver_config.go +++ b/subrpcserver_config.go @@ -15,10 +15,12 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/netann" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/sweep" + "github.com/lightningnetwork/lnd/watchtower" ) // subRPCServerConfigs is special sub-config in the main configuration that @@ -56,6 +58,10 @@ type subRPCServerConfigs struct { // payment related queries such as requests for estimates of off-chain // fees. RouterRPC *routerrpc.Config `group:"routerrpc" namespace:"routerrpc"` + + // WatchtowerRPC is a sub-RPC server that exposes functionality allowing + // clients to monitor and control their embedded watchtower. + WatchtowerRPC *watchtowerrpc.Config `group:"watchtowerrpc" namespace:"watchtowerrpc"` } // PopulateDependencies attempts to iterate through all the sub-server configs @@ -74,7 +80,8 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl, routerBackend *routerrpc.RouterBackend, nodeSigner *netann.NodeSigner, chanDB *channeldb.DB, - sweeper *sweep.UtxoSweeper) error { + sweeper *sweep.UtxoSweeper, + tower *watchtower.Standalone) error { // First, we'll use reflect to obtain a version of the config struct // that allows us to programmatically inspect its fields. @@ -206,6 +213,13 @@ func (s *subRPCServerConfigs) PopulateDependencies(cc *chainControl, reflect.ValueOf(routerBackend), ) + case *watchtowerrpc.Config: + subCfgValue := extractReflectValue(subCfg) + + subCfgValue.FieldByName("Tower").Set( + reflect.ValueOf(tower), + ) + default: return fmt.Errorf("unknown field: %v, %T", fieldName, cfg) From ccb2b29b934d36f463ebbfaac26e41dfcf1a5c81 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:54:59 -0700 Subject: [PATCH 08/12] lnrpc/watchtowerrpc/driver: register watchtower rpc server on init --- lnrpc/watchtowerrpc/driver.go | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 lnrpc/watchtowerrpc/driver.go diff --git a/lnrpc/watchtowerrpc/driver.go b/lnrpc/watchtowerrpc/driver.go new file mode 100644 index 000000000..dc9cecce8 --- /dev/null +++ b/lnrpc/watchtowerrpc/driver.go @@ -0,0 +1,55 @@ +// +build watchtowerrpc + +package watchtowerrpc + +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) ( + 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. + 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) + } + + 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)) + } +} From f7da87acc40bd57aee647f0cb2fa811e58c306af Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:55:13 -0700 Subject: [PATCH 09/12] lntest+make: build w/ watchtowerrpc tag, add grpc client to harness --- lntest/node.go | 12 ++++++++---- make/testing_flags.mk | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lntest/node.go b/lntest/node.go index 9205658a8..b5f55e876 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -25,6 +25,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/macaroons" "golang.org/x/net/context" "google.golang.org/grpc" @@ -250,10 +251,11 @@ type HarnessNode struct { invoicesrpc.InvoicesClient - // RouterClient and WalletKitClient cannot be embedded, because a name - // collision would occur with LightningClient. - RouterClient routerrpc.RouterClient - WalletKitClient walletrpc.WalletKitClient + // RouterClient, WalletKitClient, WatchtowerClient cannot be embedded, + // because a name collision would occur with LightningClient. + RouterClient routerrpc.RouterClient + WalletKitClient walletrpc.WalletKitClient + WatchtowerClient watchtowerrpc.WatchtowerClient } // Assert *HarnessNode implements the lnrpc.LightningClient interface. @@ -515,6 +517,7 @@ func (hn *HarnessNode) initLightningClient(conn *grpc.ClientConn) error { hn.InvoicesClient = invoicesrpc.NewInvoicesClient(conn) hn.RouterClient = routerrpc.NewRouterClient(conn) hn.WalletKitClient = walletrpc.NewWalletKitClient(conn) + hn.WatchtowerClient = watchtowerrpc.NewWatchtowerClient(conn) // Set the harness node's pubkey to what the node claims in GetInfo. err := hn.FetchNodeInfo() @@ -717,6 +720,7 @@ func (hn *HarnessNode) stop() error { hn.processExit = nil hn.LightningClient = nil hn.WalletUnlockerClient = nil + hn.WatchtowerClient = nil return nil } diff --git a/make/testing_flags.mk b/make/testing_flags.mk index d947803a4..feea946e2 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -56,7 +56,7 @@ endif # Construct the integration test command with the added build flags. -ITEST_TAGS := $(DEV_TAGS) rpctest chainrpc walletrpc signrpc invoicesrpc autopilotrpc routerrpc +ITEST_TAGS := $(DEV_TAGS) rpctest chainrpc walletrpc signrpc invoicesrpc autopilotrpc routerrpc watchtowerrpc # Default to btcd backend if not set. ifneq ($(backend),) From a7415336bc901f1661730f4f5a896e7b504c6d57 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:55:32 -0700 Subject: [PATCH 10/12] keychain/derivation: add KeyFamilyTowerKey distinct from NodeID --- keychain/derivation.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/keychain/derivation.go b/keychain/derivation.go index 54144efca..f3a2f3fb9 100644 --- a/keychain/derivation.go +++ b/keychain/derivation.go @@ -96,6 +96,13 @@ const ( // session keys are limited to the lifetime of the session and are used // to increase privacy in the watchtower protocol. KeyFamilyTowerSession KeyFamily = 8 + + // KeyFamilyTowerID is the family of keys used to derive the public key + // of a watchtower. This made distinct from the node key to offer a form + // of rudimentary whitelisting, i.e. via knowledge of the pubkey, + // preventing others from having full access to the tower just as a + // result of knowing the node key. + KeyFamilyTowerID KeyFamily = 9 ) // KeyLocator is a two-tuple that can be used to derive *any* key that has ever From ea311649b43b10aca45c5e50107eb3256859a837 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:55:52 -0700 Subject: [PATCH 11/12] lnd: use distinct pubkey for watchtowers and server --- lnd.go | 14 ++++++++++++- lntest/itest/lnd_test.go | 43 ++++++++++++++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lnd.go b/lnd.go index eaf8f877f..650e47b9b 100644 --- a/lnd.go +++ b/lnd.go @@ -344,6 +344,18 @@ func Main() error { } defer towerDB.Close() + towerPrivKey, err := activeChainControl.wallet.DerivePrivKey( + keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamilyTowerID, + Index: 0, + }, + }, + ) + if err != nil { + return err + } + wtConfig, err := cfg.Watchtower.Apply(&watchtower.Config{ BlockFetcher: activeChainControl.chainIO, DB: towerDB, @@ -354,7 +366,7 @@ func Main() error { lnwallet.WitnessPubKey, false, ) }, - NodePrivKey: idPrivKey, + NodePrivKey: towerPrivKey, PublishTx: activeChainControl.wallet.PublishTransaction, ChainHash: *activeNetParams.GenesisHash, }, lncfg.NormalizeAddresses) diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index b34fe433e..220fb53f2 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -42,6 +42,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" "github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -7620,6 +7621,7 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, chanAmt = lnd.MaxBtcFundingAmount paymentAmt = 10000 numInvoices = 6 + externalIP = "1.2.3.4" ) // Since we'd like to test some multi-hop failure scenarios, we'll @@ -7635,28 +7637,57 @@ func testRevokedCloseRetributionAltruistWatchtower(net *lntest.NetworkHarness, // Willy the watchtower will protect Dave from Carol's breach. He will // remain online in order to punish Carol on Dave's behalf, since the // breach will happen while Dave is offline. - willy, err := net.NewNode("Willy", []string{"--watchtower.active"}) + willy, err := net.NewNode("Willy", []string{ + "--watchtower.active", + "--watchtower.externalip=" + externalIP, + }) if err != nil { t.Fatalf("unable to create new nodes: %v", err) } defer shutdownAndAssert(net, t, willy) ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - willyInfo, err := willy.GetInfo(ctxt, &lnrpc.GetInfoRequest{}) + willyInfo, err := willy.WatchtowerClient.GetInfo( + ctxt, &watchtowerrpc.GetInfoRequest{}, + ) if err != nil { t.Fatalf("unable to getinfo from willy: %v", err) } - willyAddr := willyInfo.Uris[0] - parts := strings.Split(willyAddr, ":") - willyTowerAddr := parts[0] + // Assert that Willy has one listener and it is 0.0.0.0:9911 or + // [::]:9911. Since no listener is explicitly specified, one of these + // should be the default depending on whether the host supports IPv6 or + // not. + if len(willyInfo.Listeners) != 1 { + t.Fatalf("Willy should have 1 listener, has %d", + len(willyInfo.Listeners)) + } + listener := willyInfo.Listeners[0] + if listener != "0.0.0.0:9911" && listener != "[::]:9911" { + t.Fatalf("expected listener on 0.0.0.0:9911 or [::]:9911, "+ + "got %v", listener) + } + + // Assert the Willy's URIs properly display the chosen external IP. + if len(willyInfo.Uris) != 1 { + t.Fatalf("Willy should have 1 uri, has %d", + len(willyInfo.Uris)) + } + if !strings.Contains(willyInfo.Uris[0], externalIP) { + t.Fatalf("expected uri with %v, got %v", + externalIP, willyInfo.Uris[0]) + } + + // Construct a URI from listening port and public key, since aren't + // actually connecting remotely. + willyTowerURI := fmt.Sprintf("%x@%s", willyInfo.Pubkey, listener) // Dave will be the breached party. We set --nolisten to ensure Carol // won't be able to connect to him and trigger the channel data // protection logic automatically. dave, err := net.NewNode("Dave", []string{ "--nolisten", - "--wtclient.private-tower-uris=" + willyTowerAddr, + "--wtclient.private-tower-uris=" + willyTowerURI, }) if err != nil { t.Fatalf("unable to create new node: %v", err) From 24b160b1c40d9b1e9fe50c8e7ca8ebce3e2d0a19 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 20 Jun 2019 16:56:11 -0700 Subject: [PATCH 12/12] cmd/lncli/watchtower: add tower info command --- cmd/lncli/main.go | 3 +- cmd/lncli/watchtower_active.go | 65 +++++++++++++++++++++++++++++++++ cmd/lncli/watchtower_default.go | 10 +++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 cmd/lncli/watchtower_active.go create mode 100644 cmd/lncli/watchtower_default.go diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 51917e4e5..5832ce209 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -300,11 +300,12 @@ func main() { restoreChanBackupCommand, } - // Add any extra autopilot commands determined by build flags. + // Add any extra commands determined by build flags. app.Commands = append(app.Commands, autopilotCommands()...) app.Commands = append(app.Commands, invoicesCommands()...) app.Commands = append(app.Commands, routerCommands()...) app.Commands = append(app.Commands, walletCommands()...) + app.Commands = append(app.Commands, watchtowerCommands()...) if err := app.Run(os.Args); err != nil { fatal(err) diff --git a/cmd/lncli/watchtower_active.go b/cmd/lncli/watchtower_active.go new file mode 100644 index 000000000..bfa9af666 --- /dev/null +++ b/cmd/lncli/watchtower_active.go @@ -0,0 +1,65 @@ +// +build watchtowerrpc + +package main + +import ( + "context" + "encoding/hex" + + "github.com/lightningnetwork/lnd/lnrpc/watchtowerrpc" + "github.com/urfave/cli" +) + +func watchtowerCommands() []cli.Command { + return []cli.Command{ + { + Name: "tower", + Usage: "Interact with the watchtower.", + Category: "Watchtower", + Subcommands: []cli.Command{ + towerInfoCommand, + }, + }, + } +} + +func getWatchtowerClient(ctx *cli.Context) (watchtowerrpc.WatchtowerClient, func()) { + conn := getClientConn(ctx, false) + cleanup := func() { + conn.Close() + } + return watchtowerrpc.NewWatchtowerClient(conn), cleanup +} + +var towerInfoCommand = cli.Command{ + Name: "info", + Usage: "Returns basic information related to the active watchtower.", + Action: actionDecorator(towerInfo), +} + +func towerInfo(ctx *cli.Context) error { + if ctx.NArg() != 0 || ctx.NumFlags() > 0 { + return cli.ShowCommandHelp(ctx, "info") + } + + client, cleanup := getWatchtowerClient(ctx) + defer cleanup() + + req := &watchtowerrpc.GetInfoRequest{} + resp, err := client.GetInfo(context.Background(), req) + if err != nil { + return err + } + + printJSON(struct { + Pubkey string `json:"pubkey"` + Listeners []string `json:"listeners"` + URIs []string `json:"uris"` + }{ + Pubkey: hex.EncodeToString(resp.Pubkey), + Listeners: resp.Listeners, + URIs: resp.Uris, + }) + + return nil +} diff --git a/cmd/lncli/watchtower_default.go b/cmd/lncli/watchtower_default.go new file mode 100644 index 000000000..41d887a9c --- /dev/null +++ b/cmd/lncli/watchtower_default.go @@ -0,0 +1,10 @@ +// +build !watchtowerrpc + +package main + +import "github.com/urfave/cli" + +// watchtowerCommands will return nil for non-watchtowerrpc builds. +func watchtowerCommands() []cli.Command { + return nil +}