Merge pull request #3224 from cfromknecht/wtserver-key-family

watchtower: use separate key family for wtserver public key, add watchtower subserver
This commit is contained in:
Olaoluwa Osuntokun 2019-06-20 18:01:21 -07:00 committed by GitHub
commit 0506b1e587
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 756 additions and 24 deletions

@ -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)

@ -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
}

@ -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
}

@ -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

16
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)
@ -406,7 +418,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)

@ -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
}

@ -0,0 +1,6 @@
// +build !watchtowerrpc
package watchtowerrpc
// Config is empty for non-watchtowerrpc builds.
type Config struct{}

@ -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))
}
}

@ -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
}

@ -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
}

@ -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)
}

@ -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",
}

@ -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" ];
}

@ -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)

@ -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, 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
}

@ -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),)

@ -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

@ -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)

@ -1,6 +1,7 @@
package watchtower
import (
"strconv"
"time"
)
@ -10,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"`
@ -36,7 +40,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 +48,25 @@ 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 {
return nil, err
}
}
// 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 {

@ -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.
@ -73,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

@ -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
@ -93,6 +97,7 @@ func New(cfg *Config) (*Standalone, error) {
return &Standalone{
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
}