lnd/lntest/node/config.go
Andras Banki-Horvath 8e0534f756
multi: add leader check to the healthcheck monitor
This commit extends our healtcheck with an optional leader check. This
is to ensure that given network partition or other cluster wide failure
we act as soon as possible to avoid a split-brain situation where a new
leader is elected but we still hold onto our etcd client.
2024-08-01 19:04:10 +02:00

373 lines
9.3 KiB
Go

package node
import (
"flag"
"fmt"
"io"
"os"
"path"
"path/filepath"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/kvdb/etcd"
"github.com/lightningnetwork/lnd/lntest/port"
"github.com/lightningnetwork/lnd/lntest/wait"
)
const (
// ListenerFormat is the format string that is used to generate local
// listener addresses.
ListenerFormat = "127.0.0.1:%d"
// DefaultCSV is the CSV delay (remotedelay) we will start our test
// nodes with.
DefaultCSV = 4
)
var (
// logOutput is a flag that can be set to append the output from the
// seed nodes to log files.
logOutput = flag.Bool("logoutput", false,
"log output from node n to file output-n.log")
// logSubDir is the default directory where the logs are written to if
// logOutput is true.
logSubDir = flag.String("logdir", ".", "default dir to write logs to")
// btcdExecutable is the full path to the btcd binary.
btcdExecutable = flag.String(
"btcdexec", "", "full path to btcd binary",
)
)
type DatabaseBackend int
const (
BackendBbolt DatabaseBackend = iota
BackendEtcd
BackendPostgres
BackendSqlite
)
// Option is a function for updating a node's configuration.
type Option func(*BaseNodeConfig)
// BackendConfig is an interface that abstracts away the specific chain backend
// node implementation.
type BackendConfig interface {
// GenArgs returns the arguments needed to be passed to LND at startup
// for using this node as a chain backend.
GenArgs() []string
// ConnectMiner is called to establish a connection to the test miner.
ConnectMiner() error
// DisconnectMiner is called to disconnect the miner.
DisconnectMiner() error
// Name returns the name of the backend type.
Name() string
// Credentials returns the rpc username, password and host for the
// backend.
Credentials() (string, string, string, error)
}
// BaseNodeConfig is the base node configuration.
type BaseNodeConfig struct {
Name string
// LogFilenamePrefix is used to prefix node log files. Can be used to
// store the current test case for simpler postmortem debugging.
LogFilenamePrefix string
NetParams *chaincfg.Params
BackendCfg BackendConfig
BaseDir string
ExtraArgs []string
OriginalExtraArgs []string
DataDir string
LogDir string
TLSCertPath string
TLSKeyPath string
AdminMacPath string
ReadMacPath string
InvoiceMacPath string
SkipUnlock bool
Password []byte
P2PPort int
RPCPort int
RESTPort int
ProfilePort int
FeeURL string
DBBackend DatabaseBackend
PostgresDsn string
NativeSQL bool
// NodeID is a unique ID used to identify the node.
NodeID uint32
// LndBinary is the full path to the lnd binary that was specifically
// compiled with all required itest flags.
LndBinary string
// backupDBDir is the path where a database backup is stored, if any.
backupDBDir string
// postgresDBName is the name of the postgres database where lnd data
// is stored in.
postgresDBName string
}
func (cfg BaseNodeConfig) P2PAddr() string {
return fmt.Sprintf(ListenerFormat, cfg.P2PPort)
}
func (cfg BaseNodeConfig) RPCAddr() string {
return fmt.Sprintf(ListenerFormat, cfg.RPCPort)
}
func (cfg BaseNodeConfig) RESTAddr() string {
return fmt.Sprintf(ListenerFormat, cfg.RESTPort)
}
// DBDir returns the holding directory path of the graph database.
func (cfg BaseNodeConfig) DBDir() string {
return filepath.Join(cfg.DataDir, "graph", cfg.NetParams.Name)
}
func (cfg BaseNodeConfig) DBPath() string {
return filepath.Join(cfg.DBDir(), "channel.db")
}
func (cfg BaseNodeConfig) ChanBackupPath() string {
return filepath.Join(
cfg.DataDir, "chain", lnd.BitcoinChainName,
fmt.Sprintf(
"%v/%v", cfg.NetParams.Name,
chanbackup.DefaultBackupFileName,
),
)
}
// GenerateListeningPorts generates the ports to listen on designated for the
// current lightning network test.
func (cfg *BaseNodeConfig) GenerateListeningPorts() {
if cfg.P2PPort == 0 {
cfg.P2PPort = port.NextAvailablePort()
}
if cfg.RPCPort == 0 {
cfg.RPCPort = port.NextAvailablePort()
}
if cfg.RESTPort == 0 {
cfg.RESTPort = port.NextAvailablePort()
}
if cfg.ProfilePort == 0 {
cfg.ProfilePort = port.NextAvailablePort()
}
}
// BaseConfig returns the base node configuration struct.
func (cfg *BaseNodeConfig) BaseConfig() *BaseNodeConfig {
return cfg
}
// GenArgs generates a slice of command line arguments from the lightning node
// config struct.
func (cfg *BaseNodeConfig) GenArgs() []string {
var args []string
switch cfg.NetParams {
case &chaincfg.TestNet3Params:
args = append(args, "--bitcoin.testnet")
case &chaincfg.SimNetParams:
args = append(args, "--bitcoin.simnet")
case &chaincfg.RegressionNetParams:
args = append(args, "--bitcoin.regtest")
}
backendArgs := cfg.BackendCfg.GenArgs()
args = append(args, backendArgs...)
nodeArgs := []string{
"--nobootstrap",
"--debuglevel=debug",
"--bitcoin.defaultchanconfs=1",
"--accept-keysend",
"--keep-failed-payment-attempts",
fmt.Sprintf("--db.batch-commit-interval=%v", commitInterval),
fmt.Sprintf("--bitcoin.defaultremotedelay=%v", DefaultCSV),
fmt.Sprintf("--rpclisten=%v", cfg.RPCAddr()),
fmt.Sprintf("--restlisten=%v", cfg.RESTAddr()),
fmt.Sprintf("--restcors=https://%v", cfg.RESTAddr()),
fmt.Sprintf("--listen=%v", cfg.P2PAddr()),
fmt.Sprintf("--externalip=%v", cfg.P2PAddr()),
fmt.Sprintf("--lnddir=%v", cfg.BaseDir),
fmt.Sprintf("--adminmacaroonpath=%v", cfg.AdminMacPath),
fmt.Sprintf("--readonlymacaroonpath=%v", cfg.ReadMacPath),
fmt.Sprintf("--invoicemacaroonpath=%v", cfg.InvoiceMacPath),
fmt.Sprintf("--trickledelay=%v", trickleDelay),
fmt.Sprintf("--profile=%d", cfg.ProfilePort),
// Use a small batch delay so we can broadcast the
// announcements quickly in the tests.
"--gossip.sub-batch-delay=5ms",
// Use a small cache duration so the `DescribeGraph` can be
// updated quicker.
"--caches.rpc-graph-cache-duration=100ms",
// Speed up the tests for bitcoind backend.
"--bitcoind.blockpollinginterval=100ms",
"--bitcoind.txpollinginterval=100ms",
// Allow unsafe disconnect in itest.
"--dev.unsafedisconnect",
}
args = append(args, nodeArgs...)
if cfg.Password == nil {
args = append(args, "--noseedbackup")
}
switch cfg.DBBackend {
case BackendEtcd:
args = append(args, "--db.backend=etcd")
args = append(args, "--db.etcd.embedded")
args = append(
args, fmt.Sprintf(
"--db.etcd.embedded_client_port=%v",
port.NextAvailablePort(),
),
)
args = append(
args, fmt.Sprintf(
"--db.etcd.embedded_peer_port=%v",
port.NextAvailablePort(),
),
)
args = append(
args, fmt.Sprintf(
"--db.etcd.embedded_log_file=%v",
path.Join(cfg.LogDir, "etcd.log"),
),
)
case BackendPostgres:
args = append(args, "--db.backend=postgres")
args = append(args, "--db.postgres.dsn="+cfg.PostgresDsn)
if cfg.NativeSQL {
args = append(args, "--db.use-native-sql")
}
case BackendSqlite:
args = append(args, "--db.backend=sqlite")
args = append(args, fmt.Sprintf("--db.sqlite.busytimeout=%v",
wait.SqliteBusyTimeout))
if cfg.NativeSQL {
args = append(args, "--db.use-native-sql")
}
}
if cfg.FeeURL != "" {
args = append(args, "--fee.url="+cfg.FeeURL)
}
// Put extra args in the end so the args can be overwritten.
if cfg.ExtraArgs != nil {
args = append(args, cfg.ExtraArgs...)
}
return args
}
// ExtraArgsEtcd returns extra args for configuring LND to use an external etcd
// database (for remote channel DB and wallet DB).
func ExtraArgsEtcd(etcdCfg *etcd.Config, name string, cluster bool,
leaderSessionTTL int) []string {
extraArgs := []string{
"--db.backend=etcd",
fmt.Sprintf("--db.etcd.host=%v", etcdCfg.Host),
fmt.Sprintf("--db.etcd.user=%v", etcdCfg.User),
fmt.Sprintf("--db.etcd.pass=%v", etcdCfg.Pass),
fmt.Sprintf("--db.etcd.namespace=%v", etcdCfg.Namespace),
}
if etcdCfg.InsecureSkipVerify {
extraArgs = append(extraArgs, "--db.etcd.insecure_skip_verify")
}
if cluster {
clusterArgs := []string{
"--cluster.enable-leader-election",
fmt.Sprintf("--cluster.id=%v", name),
fmt.Sprintf("--cluster.leader-session-ttl=%v",
leaderSessionTTL),
}
extraArgs = append(extraArgs, clusterArgs...)
extraArgs = append(
extraArgs, "--healthcheck.leader.interval=10s",
)
}
return extraArgs
}
// GetLogDir returns the passed --logdir flag or the default value if it wasn't
// set.
func GetLogDir() string {
if logSubDir != nil && *logSubDir != "" {
return *logSubDir
}
return "."
}
// CopyFile copies the file src to dest.
func CopyFile(dest, src string) error {
s, err := os.Open(src)
if err != nil {
return err
}
defer s.Close()
d, err := os.Create(dest)
if err != nil {
return err
}
if _, err := io.Copy(d, s); err != nil {
d.Close()
return err
}
return d.Close()
}
// GetBtcdBinary returns the full path to the binary of the custom built btcd
// executable or an empty string if none is set.
func GetBtcdBinary() string {
if btcdExecutable != nil {
return *btcdExecutable
}
return ""
}
func init() {
// Before we start any node, we need to make sure that any btcd or
// bitcoind node that is started through the RPC harness uses a unique
// port as well to avoid any port collisions.
rpctest.ListenAddressGenerator =
port.GenerateSystemUniqueListenerAddresses
}