faraday/config.go

565 lines
20 KiB
Go
Raw Permalink Normal View History

2020-03-28 14:38:14 +02:00
package faraday
import (
"crypto/tls"
"crypto/x509"
"fmt"
"math"
2020-08-10 15:08:59 +02:00
"os"
"path"
2020-08-10 15:08:59 +02:00
"path/filepath"
"time"
"github.com/btcsuite/btcd/btcutil"
2022-11-21 17:12:17 +01:00
"github.com/lightninglabs/faraday/chain"
2025-09-24 13:20:46 +02:00
"github.com/lightninglabs/faraday/chanevents"
"github.com/lightninglabs/faraday/db"
"github.com/lightninglabs/faraday/db/sqlc"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert"
2025-09-24 13:20:46 +02:00
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
2025-09-24 13:20:46 +02:00
"github.com/lightningnetwork/lnd/sqldb/v2"
"google.golang.org/grpc/credentials"
)
const (
defaultRPCPort = "10009"
defaultRPCHostPort = "localhost:" + defaultRPCPort
DefaultNetwork = "mainnet"
defaultMinimumMonitor = time.Hour * 24 * 7 * 4 // four weeks in hours
defaultDebugLevel = "info"
defaultRPCListen = "localhost:8465"
// By default we do not require connecting to a bitcoin node so that
// we can serve basic functionality by default.
defaultChainConn = false
// defaultTLSCertDuration is the default validity of a self-signed
// certificate. The value corresponds to 14 months
// (14 months * 30 days * 24 hours).
defaultTLSCertDuration = 14 * 30 * 24 * time.Hour
2025-09-24 13:20:46 +02:00
// DatabaseBackendSqlite is the name of the SQLite database backend.
DatabaseBackendSqlite = "sqlite"
// DatabaseBackendPostgres is the name of the Postgres database backend.
DatabaseBackendPostgres = "postgres"
// defaultSqliteDatabaseFileName is the default name of the SQLite
// database file.
defaultSqliteDatabaseFileName = "faraday.db"
// defaultChanEventsMaxEvents is the default maximum number of channel
// events to retain. At roughly 140 bytes per event this acts as a hard
// ceiling of approximately 1 GB. A value of 0 disables the size-based
// limit.
defaultChanEventsMaxEvents = 7000000
// defaultChanEventsRetention is the default retention window for channel
// events. Age-based pruning is disabled by default (0): out of the box
// only the max-events size ceiling bounds the table, and operators opt
// into a retention window explicitly.
defaultChanEventsRetention = 0
)
var (
// FaradayDirBase is the default main directory where faraday stores its
// data.
FaradayDirBase = btcutil.AppDataDir("faraday", false)
2020-09-08 10:17:26 +02:00
// DefaultTLSCertFilename is the default file name for the autogenerated
// TLS certificate.
DefaultTLSCertFilename = "tls.cert"
// DefaultTLSKeyFilename is the default file name for the autogenerated
// TLS key.
DefaultTLSKeyFilename = "tls.key"
defaultSelfSignedOrganization = "faraday autogenerated cert"
2020-09-08 10:17:26 +02:00
// DefaultTLSCertPath is the default full path of the autogenerated TLS
// certificate.
DefaultTLSCertPath = filepath.Join(
FaradayDirBase, DefaultNetwork, DefaultTLSCertFilename,
)
// DefaultTLSKeyPath is the default full path of the autogenerated TLS
// key.
DefaultTLSKeyPath = filepath.Join(
FaradayDirBase, DefaultNetwork, DefaultTLSKeyFilename,
)
2020-09-09 10:34:25 +02:00
// DefaultMacaroonFilename is the default file name for the
// autogenerated faraday macaroon.
DefaultMacaroonFilename = "faraday.macaroon"
// DefaultMacaroonPath is the default full path of the base faraday
// macaroon.
DefaultMacaroonPath = filepath.Join(
FaradayDirBase, DefaultNetwork, DefaultMacaroonFilename,
)
// defaultLndMacaroon is the default macaroon file we use to connect to
// lnd.
defaultLndMacaroon = "admin.macaroon"
// DefaultLndDir is the default location where we look for lnd's tls and
// macaroon files.
DefaultLndDir = btcutil.AppDataDir("lnd", false)
// DefaultLndMacaroonPath is the default location where we look for a
// macaroon to use when connecting to lnd.
DefaultLndMacaroonPath = filepath.Join(
DefaultLndDir, "data", "chain", "bitcoin", DefaultNetwork,
defaultLndMacaroon,
)
)
type LndConfig struct {
// RPCServer is host:port that lnd's RPC server is listening on.
RPCServer string `long:"rpcserver" description:"host:port that LND is listening for RPC connections on"`
// MacaroonDir is the directory that contains all the macaroon files
// required for the remote connection.
MacaroonDir string `long:"macaroondir" description:"DEPRECATED: Use macaroonpath."`
// MacaroonPath is the path to the single macaroon that should be used
// instead of needing to specify the macaroon directory that contains
// all of lnd's macaroons. The specified macaroon MUST have all
// permissions that all the subservers use, otherwise permission errors
// will occur.
MacaroonPath string `long:"macaroonpath" description:"The full path to the single macaroon to use, either the admin.macaroon or a custom baked one. Cannot be specified at the same time as macaroondir. A custom macaroon must contain ALL permissions required for all subservers to work, otherwise permission errors will occur."`
2020-03-28 14:38:14 +02:00
// TLSCertPath is the path to the tls cert that faraday should use.
TLSCertPath string `long:"tlscertpath" description:"Path to TLS cert"`
// RequestTimeout is the maximum time to wait for a response from lnd.
RequestTimeout time.Duration `long:"requesttimeout" description:"The maximum time to wait for a response from lnd, if not set the default of 30 seconds will be used."`
}
type Config struct { //nolint:maligned
// Lnd holds the configuration options for the connection to lnd.
Lnd *LndConfig `group:"lnd" namespace:"lnd"`
// FaradayDir is the main directory where faraday stores all its data.
2020-09-09 10:34:25 +02:00
FaradayDir string `long:"faradaydir" description:"The directory for all of faraday's data. If set, this option overwrites --macaroonpath, --tlscertpath and --tlskeypath."`
// ChainConn specifies whether to attempt connecting to a bitcoin backend.
ChainConn bool `long:"connect_bitcoin" description:"Whether to attempt to connect to a backing bitcoin node. Some endpoints will not be available if this option is not enabled."`
2020-08-10 15:08:59 +02:00
ShowVersion bool `long:"version" description:"Display version information and exit"`
// MinimumMonitored is the minimum amount of time that a channel must be monitored for before we consider it for termination.
MinimumMonitored time.Duration `long:"min_monitored" description:"The minimum amount of time that a channel must be monitored for before recommending termination. Valid time units are {s, m, h}."`
// Network is a string containing the network we're running on.
2024-08-15 16:46:25 +02:00
Network string `long:"network" description:"The network to run on." choice:"regtest" choice:"testnet" choice:"mainnet" choice:"simnet" choice:"signet" `
// DebugLevel is a string defining the log level for the service either
// for all subsystems the same or individual level by subsystem.
2020-03-28 14:38:14 +02:00
DebugLevel string `long:"debuglevel" description:"Debug level for faraday and its subsystems."`
2020-01-16 09:13:14 +02:00
TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for faraday's RPC and REST services."`
TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for faraday's RPC and REST services."`
TLSExtraIPs []string `long:"tlsextraip" description:"Adds an extra IP to the generated certificate."`
TLSExtraDomains []string `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate."`
TLSAutoRefresh bool `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed."`
TLSDisableAutofill bool `long:"tlsdisableautofill" description:"Do not include the interface IPs or the system hostname in TLS certificate, use first --tlsextradomain as Common Name instead, if set."`
TLSCertDuration time.Duration `long:"tlscertduration" description:"The duration for which the auto-generated TLS certificate will be valid for."`
2020-09-08 10:17:26 +02:00
2020-09-09 10:34:25 +02:00
MacaroonPath string `long:"macaroonpath" description:"Path to write the macaroon for faraday's RPC and REST services if it doesn't exist."`
2020-03-28 14:38:14 +02:00
// RPCListen is the listen address for the faraday rpc server.
RPCListen string `long:"rpclisten" description:"Address to listen on for gRPC clients."`
// RESTListen is the listen address for the faraday REST server.
RESTListen string `long:"restlisten" description:"Address to listen on for REST clients. If not specified, no REST listener will be started."`
// CORSOrigin specifies the CORS header that should be set on REST responses. No header is added if the value is empty.
CORSOrigin string `long:"corsorigin" description:"The value to send in the Access-Control-Allow-Origin header. Header will be omitted if empty."`
// Bitcoin is the configuration required to connect to a bitcoin node.
Bitcoin *chain.BitcoinConfig `group:"bitcoin" namespace:"bitcoin"`
// Logging controls various aspects of pool logging.
Logging *build.LogConfig `group:"logging" namespace:"logging"`
2025-09-24 13:20:46 +02:00
// DatabaseBackend is the database backend we will use for storing all
// liveness data.
DatabaseBackend string `long:"databasebackend" description:"The database backend to use for storing all liveness data." choice:"sqlite" choice:"postgres"`
// Sqlite holds the configuration options for a SQLite database
// backend.
Sqlite *db.SqliteConfig `group:"sqlite" namespace:"sqlite"`
// Postgres holds the configuration options for a Postgres database
Postgres *sqldb.PostgresConfig `group:"postgres" namespace:"postgres"`
// ChanEvents holds the configuration options for channel event safety
// pruning.
ChanEvents *chanevents.Config `group:"chanevents" namespace:"chanevents"`
}
2020-04-30 10:31:46 +02:00
// DefaultConfig returns all default values for the Config struct.
func DefaultConfig() Config {
return Config{
Lnd: &LndConfig{
RPCServer: defaultRPCHostPort,
MacaroonPath: DefaultLndMacaroonPath,
},
FaradayDir: FaradayDirBase,
Network: DefaultNetwork,
MinimumMonitored: defaultMinimumMonitor,
DebugLevel: defaultDebugLevel,
2020-09-08 10:17:26 +02:00
TLSCertPath: DefaultTLSCertPath,
TLSKeyPath: DefaultTLSKeyPath,
TLSCertDuration: defaultTLSCertDuration,
2020-09-09 10:34:25 +02:00
MacaroonPath: DefaultMacaroonPath,
2020-01-16 09:13:14 +02:00
RPCListen: defaultRPCListen,
ChainConn: defaultChainConn,
Bitcoin: chain.DefaultConfig,
Logging: build.DefaultLogConfig(),
2025-09-24 13:20:46 +02:00
DatabaseBackend: DatabaseBackendSqlite,
Sqlite: &db.SqliteConfig{
DatabaseFileName: defaultSqliteDatabaseFileName,
},
ChanEvents: &chanevents.Config{
MaxEvents: defaultChanEventsMaxEvents,
Retention: defaultChanEventsRetention,
},
}
}
// ValidateConfig sanitizes all file system paths and makes sure no incompatible
// configuration combinations are used.
func ValidateConfig(config *Config) error {
// Validate the network.
_, err := lndclient.Network(config.Network).ChainParams()
if err != nil {
return fmt.Errorf("error validating network: %v", err)
}
// Clean up and validate paths, then make sure the directories exist.
config.FaradayDir = lncfg.CleanAndExpandPath(config.FaradayDir)
2020-09-08 10:17:26 +02:00
config.TLSCertPath = lncfg.CleanAndExpandPath(config.TLSCertPath)
config.TLSKeyPath = lncfg.CleanAndExpandPath(config.TLSKeyPath)
2020-09-09 10:34:25 +02:00
config.MacaroonPath = lncfg.CleanAndExpandPath(config.MacaroonPath)
// Before adding the network namespace below, check if the user has
// overwritten the default faraday directory.
faradayDirSet := config.FaradayDir != FaradayDirBase
// Append the network type to faraday directory so they are "namespaced"
// per network.
config.FaradayDir = filepath.Join(config.FaradayDir, config.Network)
// Create the full path of directories now, including the network path.
if err := os.MkdirAll(config.FaradayDir, os.ModePerm); err != nil {
return err
}
2020-09-09 10:34:25 +02:00
// Since our faraday directory overrides our TLS dir and macaroon path
// values, make sure that they are not set when faraday dir is set. We
// fail hard here rather than overwriting and potentially confusing the
// user.
2020-09-08 10:17:26 +02:00
if faradayDirSet {
tlsCertPathSet := config.TLSCertPath != DefaultTLSCertPath
tlsKeyPathSet := config.TLSKeyPath != DefaultTLSKeyPath
2020-09-09 10:34:25 +02:00
macaroonPathSet := config.MacaroonPath != DefaultMacaroonPath
2020-09-08 10:17:26 +02:00
if tlsCertPathSet {
return fmt.Errorf("faradaydir overwrites " +
2020-09-08 10:17:26 +02:00
"tlscertpath, please only set one value")
}
if tlsKeyPathSet {
return fmt.Errorf("faradaydir overwrites " +
2020-09-08 10:17:26 +02:00
"tlskeypath, please only set one value")
}
2020-09-09 10:34:25 +02:00
if macaroonPathSet {
return fmt.Errorf("faradaydir overwrites " +
"macaroonpath, please only set one value")
}
2020-09-08 10:17:26 +02:00
}
// We want the TLS files to also be in the "namespaced" sub directory.
// Replace the default values with actual values in case the user
// specified faradaydir.
if config.TLSCertPath == DefaultTLSCertPath {
config.TLSCertPath = filepath.Join(
config.FaradayDir, DefaultTLSCertFilename,
)
}
if config.TLSKeyPath == DefaultTLSKeyPath {
config.TLSKeyPath = filepath.Join(
config.FaradayDir, DefaultTLSKeyFilename,
)
}
2020-09-09 10:34:25 +02:00
if config.MacaroonPath == DefaultMacaroonPath {
config.MacaroonPath = filepath.Join(
config.FaradayDir, DefaultMacaroonFilename,
)
}
2020-09-08 10:17:26 +02:00
// If the user has opted into connecting to a bitcoin backend, check
// that we have a rpc user and password, and that tls path is set if
// required.
if config.ChainConn {
if config.Bitcoin.User == "" || config.Bitcoin.Password == "" {
return fmt.Errorf("rpc user and password " +
"required when chainconn is set")
}
if config.Bitcoin.UseTLS && config.Bitcoin.TLSPath == "" {
return fmt.Errorf("bitcoin.tlspath required " +
"when chainconn is set")
}
}
// Make sure only one of the macaroon options is used.
switch {
case config.Lnd.MacaroonPath != DefaultLndMacaroonPath &&
config.Lnd.MacaroonDir != "":
return fmt.Errorf("use --lnd.macaroonpath only")
case config.Lnd.MacaroonDir != "":
// With the new version of lndclient we can only specify a
// single macaroon instead of all of them. If the old
// macaroondir is used, we use the readonly macaroon located in
// that directory.
config.Lnd.MacaroonPath = path.Join(
lncfg.CleanAndExpandPath(config.Lnd.MacaroonDir),
defaultLndMacaroon,
)
case config.Lnd.MacaroonPath != "":
config.Lnd.MacaroonPath = lncfg.CleanAndExpandPath(
config.Lnd.MacaroonPath,
)
default:
return fmt.Errorf("must specify --lnd.macaroonpath")
}
// Adjust the default lnd macaroon path if only the network is
// specified.
if config.Network != DefaultNetwork &&
config.Lnd.MacaroonPath == DefaultLndMacaroonPath {
config.Lnd.MacaroonPath = path.Join(
DefaultLndDir, "data", "chain", "bitcoin",
config.Network, defaultLndMacaroon,
)
}
// Expand the lnd cert path, in case the user is specifying the home
// directory with ~ (which only the shell understands, not the low-level
// file system).
config.Lnd.TLSCertPath = lncfg.CleanAndExpandPath(
config.Lnd.TLSCertPath,
)
if config.ChanEvents != nil {
// The channel event size limit becomes an int32 SQL OFFSET
// during pruning, so reject values that would overflow it and
// silently corrupt the prune bound.
if config.ChanEvents.MaxEvents > math.MaxInt32 {
return fmt.Errorf("chanevents.max-events must not "+
"exceed %d", math.MaxInt32)
}
// A negative retention is silently ignored by the prune checks,
// which only treat a strictly positive duration as enabling
// age-based pruning. Reject it so a misconfigured window fails
// loudly instead of disabling pruning unexpectedly.
if config.ChanEvents.Retention < 0 {
return fmt.Errorf("chanevents.retention must not be " +
"negative")
}
}
return nil
}
// getTLSConfig generates a new self signed certificate or refreshes an existing
// one if necessary, then returns the full TLS configuration for initializing
// a secure server interface.
func getTLSConfig(cfg *Config) (*tls.Config, *credentials.TransportCredentials,
error) {
// Let's load our certificate first or create then load if it doesn't
// yet exist.
certData, parsedCert, err := loadCertWithCreate(cfg)
if err != nil {
return nil, nil, err
}
// If the certificate expired or it was outdated, delete it and the TLS
// key and generate a new pair.
if time.Now().After(parsedCert.NotAfter) {
log.Info("TLS certificate is expired or outdated, " +
"removing old file then generating a new one")
err := os.Remove(cfg.TLSCertPath)
if err != nil {
return nil, nil, err
}
err = os.Remove(cfg.TLSKeyPath)
if err != nil {
return nil, nil, err
}
certData, _, err = loadCertWithCreate(cfg)
if err != nil {
return nil, nil, err
}
}
tlsCfg := cert.TLSConfFromCert(certData)
restCreds, err := credentials.NewClientTLSFromFile(
cfg.TLSCertPath, "",
)
if err != nil {
return nil, nil, err
}
return tlsCfg, &restCreds, nil
}
// loadCertWithCreate tries to load the TLS certificate from disk. If the
// specified cert and key files don't exist, the certificate/key pair is created
// first.
func loadCertWithCreate(cfg *Config) (tls.Certificate, *x509.Certificate,
error) {
// Ensure we create TLS key and certificate if they don't exist.
if !lnrpc.FileExists(cfg.TLSCertPath) &&
!lnrpc.FileExists(cfg.TLSKeyPath) {
log.Infof("Generating TLS certificates...")
certBytes, keyBytes, err := cert.GenCertPair(
defaultSelfSignedOrganization, cfg.TLSExtraIPs,
cfg.TLSExtraDomains, cfg.TLSDisableAutofill,
cfg.TLSCertDuration,
)
if err != nil {
return tls.Certificate{}, nil, err
}
// Now that we have the certificate and key, we'll store them
// to the file system.
err = cert.WriteCertPair(
cfg.TLSCertPath, cfg.TLSKeyPath, certBytes, keyBytes,
)
if err != nil {
return tls.Certificate{}, nil, err
}
log.Infof("Done generating TLS certificates")
}
return cert.LoadCert(cfg.TLSCertPath, cfg.TLSKeyPath)
}
2025-09-24 13:20:46 +02:00
// stores holds a collection of the DB stores that are used by faraday.
type stores struct {
// ChanEventsStore is used to watch for channel events.
ChanEventsStore *chanevents.Store
// closeFns holds various callbacks that can be used to close any open
// stores in the stores struct.
closeFns map[string]func() error
}
// NewStores creates a new stores instance based on the chosen database backend.
func NewStores(cfg Config, clock clock.Clock) (*stores, error) {
var (
stores = &stores{
closeFns: make(map[string]func() error),
}
)
switch cfg.DatabaseBackend {
case DatabaseBackendSqlite:
dbPath := filepath.Join(
cfg.FaradayDir, cfg.Sqlite.DatabaseFileName,
)
sqlStore, err := sqldb.NewSqliteStore(&sqldb.SqliteConfig{
SkipMigrations: cfg.Sqlite.SkipMigrations,
SkipMigrationDbBackup: cfg.Sqlite.SkipMigrationDbBackup,
}, dbPath)
if err != nil {
return stores, err
}
if !cfg.Sqlite.SkipMigrations {
err = sqldb.ApplyAllMigrations(
sqlStore, db.FaradayMigrationSets,
)
if err != nil {
return stores, fmt.Errorf("error applying "+
"migrations to SQLite store: %w", err,
)
}
}
queries := sqlc.NewForType(sqlStore, sqlStore.BackendType)
stores.ChanEventsStore = chanevents.NewStore(
sqlStore.BaseDB, queries, clock,
)
stores.closeFns["sqlite"] = sqlStore.Close
case DatabaseBackendPostgres:
sqlStore, err := sqldb.NewPostgresStore(cfg.Postgres)
if err != nil {
return stores, err
}
if !cfg.Postgres.SkipMigrations {
err = sqldb.ApplyAllMigrations(
sqlStore, db.FaradayMigrationSets,
)
if err != nil {
return stores, fmt.Errorf("error applying "+
"migrations to Postgres store: %w", err,
)
}
}
queries := sqlc.NewForType(sqlStore, sqlStore.BackendType)
stores.ChanEventsStore = chanevents.NewStore(
sqlStore.BaseDB, queries, clock,
)
stores.closeFns["postgres"] = sqlStore.Close
default:
return nil, fmt.Errorf("unsupported database backend: "+
"%s", cfg.DatabaseBackend)
}
return stores, nil
}
// Close closes all the stores.
func (s *stores) Close() error {
for name, closeFn := range s.closeFns {
err := closeFn()
if err != nil {
return fmt.Errorf("error closing %s store: %v", name, err)
}
}
return nil
}