Merge pull request #2448 from cfromknecht/watchtower-pkg
watchtower pkg
This commit is contained in:
commit
5b2afaff8f
4
log.go
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
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
|
||||
}
|
65
watchtower/conf_experimental.go
Normal file
65
watchtower/conf_experimental.go
Normal file
@ -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
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
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
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
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
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user