Merge pull request #2448 from cfromknecht/watchtower-pkg

watchtower pkg
This commit is contained in:
Olaoluwa Osuntokun 2019-01-15 20:01:50 -08:00 committed by GitHub
commit 5b2afaff8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 381 additions and 0 deletions

4
log.go

@ -28,6 +28,7 @@ import (
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/sweep"
"github.com/lightningnetwork/lnd/watchtower"
)
// Loggers per subsystem. A single backend logger is created and all subsystem
@ -75,6 +76,7 @@ var (
arpcLog = build.NewSubLogger("ARPC", backendLog.Logger)
invcLog = build.NewSubLogger("INVC", backendLog.Logger)
nannLog = build.NewSubLogger("NANN", backendLog.Logger)
wtwrLog = build.NewSubLogger("WTWR", backendLog.Logger)
)
// Initialize package-global logger variables.
@ -97,6 +99,7 @@ func init() {
autopilotrpc.UseLogger(arpcLog)
invoices.UseLogger(invcLog)
netann.UseLogger(nannLog)
watchtower.UseLogger(wtwrLog)
}
// subsystemLoggers maps each subsystem identifier to its associated logger.
@ -125,6 +128,7 @@ var subsystemLoggers = map[string]btclog.Logger{
"ARPC": arpcLog,
"INVC": invcLog,
"NANN": nannLog,
"WTWR": wtwrLog,
}
// initLogRotator initializes the logging rotator to write logs to logFile and

14
watchtower/conf.go Normal file

@ -0,0 +1,14 @@
// +build !experimental
package watchtower
// Conf specifies the watchtower options that be configured from the command
// line or configuration file. In non-experimental builds, we disallow such
// configuration.
type Conf struct{}
// Apply returns an error signaling that the Conf could not be applied in
// non-experimental builds.
func (c *Conf) Apply(cfg *Config) (*Config, error) {
return nil, ErrNonExperimentalConf
}

@ -0,0 +1,65 @@
// +build experimental
package watchtower
import (
"time"
"github.com/lightningnetwork/lnd/lncfg"
)
// Conf specifies the watchtower options that can be configured from the command
// line or configuration file.
type Conf struct {
RawListeners []string `long:"listen" description:"Add interfaces/ports to listen for peer connections"`
ReadTimeout time.Duration `long:"readtimeout" description:"Duration the watchtower server will wait for messages to be received before hanging up on clients"`
WriteTimeout time.Duration `long:"writetimeout" description:"Duration the watchtower server will wait for messages to be written before hanging up on client connections"`
}
// Apply completes the passed Config struct by applying any parsed Conf options.
// If the corresponding values parsed by Conf are already set in the Config,
// those fields will be not be modified.
func (c *Conf) Apply(cfg *Config) (*Config, error) {
// Set the Config's listening addresses if they are empty.
if cfg.ListenAddrs == nil {
// Without a network, we will be unable to resolve the listening
// addresses.
if cfg.Net == nil {
return nil, ErrNoNetwork
}
// If no addresses are specified by the Config, we will resort
// to the default peer port.
if len(c.RawListeners) == 0 {
addr := DefaultPeerPortStr
c.RawListeners = append(c.RawListeners, addr)
}
// Normalize the raw listening addresses so that they can be
// used by the brontide listener.
var err error
cfg.ListenAddrs, err = lncfg.NormalizeAddresses(
c.RawListeners, DefaultPeerPortStr,
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 {
cfg.ReadTimeout = c.ReadTimeout
}
// If the Config has no write timeout, we will use the parsed Conf
// value.
if cfg.WriteTimeout == 0 && c.WriteTimeout != 0 {
cfg.WriteTimeout = c.WriteTimeout
}
return cfg, nil
}

81
watchtower/config.go Normal file

@ -0,0 +1,81 @@
package watchtower
import (
"fmt"
"net"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/tor"
"github.com/lightningnetwork/lnd/watchtower/lookout"
)
const (
// DefaultPeerPort is the default server port to which clients can
// connect.
DefaultPeerPort = 9911
// DefaultReadTimeout is the default timeout after which the tower will
// hang up on a client if nothing is received.
DefaultReadTimeout = 15 * time.Second
// DefaultWriteTimeout is the default timeout after which the tower will
// hang up on a client if it is unable to send a message.
DefaultWriteTimeout = 15 * time.Second
)
var (
// DefaultPeerPortStr is the default server port as a string.
DefaultPeerPortStr = fmt.Sprintf(":%d", DefaultPeerPort)
)
// Config defines the resources and parameters used to configure a Watchtower.
// All nil-able elements with the Config must be set in order for the Watchtower
// to function properly.
type Config struct {
// BlockFetcher supports the ability to fetch blocks from the network by
// hash.
BlockFetcher lookout.BlockFetcher
// DB provides access to persistent storage of sessions and state
// updates uploaded by watchtower clients, and the ability to query for
// breach hints when receiving new blocks.
DB DB
// EpochRegistrar supports the ability to register for events
// corresponding to newly created blocks.
EpochRegistrar lookout.EpochRegistrar
// Net specifies the network type that the watchtower will use to listen
// for client connections. Either a clear net or Tor are supported.
Net tor.Net
// NewAddress is used to generate reward addresses, where a cut of
// successfully sent funds can be received.
NewAddress func() (btcutil.Address, error)
// NodePrivKey is private key to be used in accepting new brontide
// connections.
NodePrivKey *btcec.PrivateKey
// PublishTx provides the ability to send a signed transaction to the
// network.
//
// TODO(conner): replace with lnwallet.WalletController interface to
// have stronger guarantees wrt. returned error types.
PublishTx func(*wire.MsgTx) error
// ListenAddrs specifies which address to which clients may connect.
ListenAddrs []net.Addr
// ReadTimeout specifies how long a client may go without sending a
// message.
ReadTimeout time.Duration
// WriteTimeout specifies how long a client may go without reading a
// message from the other end, if the connection has stopped buffering
// the server's replies.
WriteTimeout time.Duration
}

18
watchtower/errors.go Normal file

@ -0,0 +1,18 @@
package watchtower
import "errors"
var (
// ErrNoListeners signals that no listening ports were provided,
// rendering the tower unable to receive client requests.
ErrNoListeners = errors.New("no listening ports were specified")
// ErrNonExperimentalConf signals that an attempt to apply a
// non-experimental Conf to a Config was detected.
ErrNonExperimentalConf = errors.New("cannot use watchtower in non-" +
"experimental builds")
// ErrNoNetwork signals that no tor.Net is provided in the Config, which
// prevents resolution of listening addresses.
ErrNoNetwork = errors.New("no network specified, must be tor or clearnet")
)

14
watchtower/interface.go Normal file

@ -0,0 +1,14 @@
package watchtower
import (
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// DB abstracts the persistent functionality required to run the watchtower
// daemon. It composes the database interfaces required by the lookout and
// wtserver subsystems.
type DB interface {
lookout.DB
wtserver.DB
}

49
watchtower/log.go Normal file

@ -0,0 +1,49 @@
package watchtower
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// 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("WTWR", nil))
}
// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
lookout.UseLogger(logger)
wtserver.UseLogger(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)
}

136
watchtower/standalone.go Normal file

@ -0,0 +1,136 @@
package watchtower
import (
"net"
"sync/atomic"
"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtserver"
)
// Standalone encapsulates the server-side functionality required by watchtower
// clients. A Standalone couples the two primary subsystems such that, as a
// unit, this instance can negotiate sessions with clients, accept state updates
// for active sessions, monitor the chain for breaches matching known breach
// hints, publish reconstructed justice transactions on behalf of tower clients.
type Standalone struct {
started uint32 // to be used atomically
stopped uint32 // to be used atomically
cfg *Config
// server is the client endpoint, used for negotiating sessions and
// uploading state updates.
server wtserver.Interface
// lookout is a service that monitors the chain and inspects the
// transactions found in new blocks against the state updates received
// by the server.
lookout lookout.Service
}
// New validates the passed Config and returns a fresh Standalone instance if
// the tower's subsystems could be properly initialized.
func New(cfg *Config) (*Standalone, error) {
// The tower must have listening address in order to accept new updates
// from clients.
if len(cfg.ListenAddrs) == 0 {
return nil, ErrNoListeners
}
// Assign the default read timeout if none is provided.
if cfg.ReadTimeout == 0 {
cfg.ReadTimeout = DefaultReadTimeout
}
// Assign the default write timeout if none is provided.
if cfg.WriteTimeout == 0 {
cfg.WriteTimeout = DefaultWriteTimeout
}
punisher := lookout.NewBreachPunisher(&lookout.PunisherConfig{
PublishTx: cfg.PublishTx,
})
// Initialize the lookout service with its required resources.
lookout := lookout.New(&lookout.Config{
BlockFetcher: cfg.BlockFetcher,
DB: cfg.DB,
EpochRegistrar: cfg.EpochRegistrar,
Punisher: punisher,
})
// Create a brontide listener on each of the provided listening
// addresses. Client should be able to connect to any of open ports to
// communicate with this Standalone instance.
listeners := make([]net.Listener, 0, len(cfg.ListenAddrs))
for _, listenAddr := range cfg.ListenAddrs {
listener, err := brontide.NewListener(
cfg.NodePrivKey, listenAddr.String(),
)
if err != nil {
return nil, err
}
listeners = append(listeners, listener)
}
// Initialize the server with its required resources.
server, err := wtserver.New(&wtserver.Config{
DB: cfg.DB,
NodePrivKey: cfg.NodePrivKey,
Listeners: listeners,
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
NewAddress: cfg.NewAddress,
})
if err != nil {
return nil, err
}
return &Standalone{
cfg: cfg,
server: server,
lookout: lookout,
}, nil
}
// Start idempotently starts the Standalone, an error is returned if the
// subsystems could not be initialized.
func (w *Standalone) Start() error {
if !atomic.CompareAndSwapUint32(&w.started, 0, 1) {
return nil
}
log.Infof("Starting watchtower")
if err := w.lookout.Start(); err != nil {
return err
}
if err := w.server.Start(); err != nil {
w.lookout.Stop()
return err
}
log.Infof("Watchtower started successfully")
return nil
}
// Stop idempotently stops the Standalone and blocks until the subsystems have
// completed their shutdown.
func (w *Standalone) Stop() error {
if !atomic.CompareAndSwapUint32(&w.stopped, 0, 1) {
return nil
}
log.Infof("Stopping watchtower")
w.server.Stop()
w.lookout.Stop()
log.Infof("Watchtower stopped successfully")
return nil
}