package lnd
import (
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"os/user"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcutil"
flags "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/autopilot"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/tor"
)
const (
defaultConfigFilename = "lnd.conf"
defaultDataDirname = "data"
defaultChainSubDirname = "chain"
defaultGraphSubDirname = "graph"
defaultTowerSubDirname = "watchtower"
defaultTLSCertFilename = "tls.cert"
defaultTLSKeyFilename = "tls.key"
defaultAdminMacFilename = "admin.macaroon"
defaultReadMacFilename = "readonly.macaroon"
defaultInvoiceMacFilename = "invoice.macaroon"
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "lnd.log"
defaultRPCPort = 10009
defaultRESTPort = 8080
defaultPeerPort = 9735
defaultRPCHost = "localhost"
DefaultMaxPendingChannels = 1
defaultNoSeedBackup = false
defaultPaymentsExpirationGracePeriod = time.Duration(0)
defaultTrickleDelay = 90 * 1000
defaultChanStatusSampleInterval = time.Minute
defaultChanEnableTimeout = 19 * time.Minute
defaultChanDisableTimeout = 20 * time.Minute
defaultMaxLogFiles = 3
defaultMaxLogFileSize = 10
defaultMinBackoff = time.Second
defaultMaxBackoff = time.Hour
defaultTorSOCKSPort = 9050
defaultTorDNSHost = "soa.nodes.lightning.directory"
defaultTorDNSPort = 53
defaultTorControlPort = 9051
defaultTorV2PrivateKeyFilename = "v2_onion_private_key"
defaultTorV3PrivateKeyFilename = "v3_onion_private_key"
DefaultIncomingBroadcastDelta = 10
defaultFinalCltvRejectDelta = DefaultIncomingBroadcastDelta + 3
DefaultOutgoingBroadcastDelta = 0
defaultOutgoingCltvRejectDelta = DefaultOutgoingBroadcastDelta + 3
minTimeLockDelta = 4
defaultAcceptorTimeout = 15 * time.Second
defaultAlias = ""
defaultColor = "#3399FF"
)
var (
defaultLndDir = btcutil.AppDataDir("lnd", false)
defaultConfigFile = filepath.Join(defaultLndDir, defaultConfigFilename)
defaultDataDir = filepath.Join(defaultLndDir, defaultDataDirname)
defaultLogDir = filepath.Join(defaultLndDir, defaultLogDirname)
defaultTowerDir = filepath.Join(defaultDataDir, defaultTowerSubDirname)
defaultTLSCertPath = filepath.Join(defaultLndDir, defaultTLSCertFilename)
defaultTLSKeyPath = filepath.Join(defaultLndDir, defaultTLSKeyFilename)
defaultBtcdDir = btcutil.AppDataDir("btcd", false)
defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert")
defaultLtcdDir = btcutil.AppDataDir("ltcd", false)
defaultLtcdRPCCertFile = filepath.Join(defaultLtcdDir, "rpc.cert")
defaultBitcoindDir = btcutil.AppDataDir("bitcoin", false)
defaultLitecoindDir = btcutil.AppDataDir("litecoin", false)
defaultTorSOCKS = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
defaultTorDNS = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
defaultBitcoindEstimateMode = "CONSERVATIVE"
bitcoindEstimateModes = [2]string{"ECONOMICAL", defaultBitcoindEstimateMode}
)
type chainConfig struct {
Active bool `long:"active" description:"If the chain should be active or not."`
ChainDir string `long:"chaindir" description:"The directory to store the chain's data within."`
Node string `long:"node" description:"The blockchain interface to use." choice:"btcd" choice:"bitcoind" choice:"neutrino" choice:"ltcd" choice:"litecoind"`
MainNet bool `long:"mainnet" description:"Use the main network"`
TestNet3 bool `long:"testnet" description:"Use the test network"`
SimNet bool `long:"simnet" description:"Use the simulation test network"`
RegTest bool `long:"regtest" description:"Use the regression test network"`
DefaultNumChanConfs int `long:"defaultchanconfs" description:"The default number of confirmations a channel must have before it's considered open. If this is not set, we will scale the value according to the channel size."`
DefaultRemoteDelay int `long:"defaultremotedelay" description:"The default number of blocks we will require our channel counterparty to wait before accessing its funds in case of unilateral close. If this is not set, we will scale the value according to the channel size."`
MinHTLCIn lnwire.MilliSatoshi `long:"minhtlc" description:"The smallest HTLC we are willing to accept on our channels, in millisatoshi"`
MinHTLCOut lnwire.MilliSatoshi `long:"minhtlcout" description:"The smallest HTLC we are willing to send out on our channels, in millisatoshi"`
BaseFee lnwire.MilliSatoshi `long:"basefee" description:"The base fee in millisatoshi we will charge for forwarding payments on our channels"`
FeeRate lnwire.MilliSatoshi `long:"feerate" description:"The fee rate used when forwarding payments on our channels. The total fee charged is basefee + (amount * feerate / 1000000), where amount is the forwarded amount."`
TimeLockDelta uint32 `long:"timelockdelta" description:"The CLTV delta we will subtract from a forwarded HTLC's timelock value"`
}
type neutrinoConfig struct {
AddPeers []string `short:"a" long:"addpeer" description:"Add a peer to connect with at startup"`
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
MaxPeers int `long:"maxpeers" description:"Max number of inbound and outbound peers"`
BanDuration time.Duration `long:"banduration" description:"How long to ban misbehaving peers. Valid time units are {s, m, h}. Minimum 1 second"`
BanThreshold uint32 `long:"banthreshold" description:"Maximum allowed ban score before disconnecting and banning misbehaving peers."`
FeeURL string `long:"feeurl" description:"Optional URL for fee estimation. If a URL is not specified, static fees will be used for estimation."`
AssertFilterHeader string `long:"assertfilterheader" description:"Optional filter header in height:hash format to assert the state of neutrino's filter header chain on startup. If the assertion does not hold, then the filter header chain will be re-synced from the genesis block."`
}
type btcdConfig struct {
Dir string `long:"dir" description:"The base directory that contains the node's data, logs, configuration file, etc."`
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
RPCCert string `long:"rpccert" description:"File containing the daemon's certificate file"`
RawRPCCert string `long:"rawrpccert" description:"The raw bytes of the daemon's PEM-encoded certificate chain which will be used to authenticate the RPC connection."`
}
type bitcoindConfig struct {
Dir string `long:"dir" description:"The base directory that contains the node's data, logs, configuration file, etc."`
RPCHost string `long:"rpchost" description:"The daemon's rpc listening address. If a port is omitted, then the default port for the selected chain parameters will be used."`
RPCUser string `long:"rpcuser" description:"Username for RPC connections"`
RPCPass string `long:"rpcpass" default-mask:"-" description:"Password for RPC connections"`
ZMQPubRawBlock string `long:"zmqpubrawblock" description:"The address listening for ZMQ connections to deliver raw block notifications"`
ZMQPubRawTx string `long:"zmqpubrawtx" description:"The address listening for ZMQ connections to deliver raw transaction notifications"`
EstimateMode string `long:"estimatemode" description:"The fee estimate mode. Must be either ECONOMICAL or CONSERVATIVE."`
}
type autoPilotConfig struct {
Active bool `long:"active" description:"If the autopilot agent should be active or not."`
Heuristic map[string]float64 `long:"heuristic" description:"Heuristic to activate, and the weight to give it during scoring."`
MaxChannels int `long:"maxchannels" description:"The maximum number of channels that should be created"`
Allocation float64 `long:"allocation" description:"The percentage of total funds that should be committed to automatic channel establishment"`
MinChannelSize int64 `long:"minchansize" description:"The smallest channel that the autopilot agent should create"`
MaxChannelSize int64 `long:"maxchansize" description:"The largest channel that the autopilot agent should create"`
Private bool `long:"private" description:"Whether the channels created by the autopilot agent should be private or not. Private channels won't be announced to the network."`
MinConfs int32 `long:"minconfs" description:"The minimum number of confirmations each of your inputs in funding transactions created by the autopilot agent must have."`
ConfTarget uint32 `long:"conftarget" description:"The confirmation target (in blocks) for channels opened by autopilot."`
}
type torConfig struct {
Active bool `long:"active" description:"Allow outbound and inbound connections to be routed through Tor"`
SOCKS string `long:"socks" description:"The host:port that Tor's exposed SOCKS5 proxy is listening on"`
DNS string `long:"dns" description:"The DNS server as host:port that Tor will use for SRV queries - NOTE must have TCP resolution enabled"`
StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."`
Control string `long:"control" description:"The host:port that Tor is listening on for Tor control connections"`
TargetIPAddress string `long:"targetipaddress" description:"IP address that Tor should use as the target of the hidden service"`
Password string `long:"password" description:"The password used to arrive at the HashedControlPassword for the control port. If provided, the HASHEDPASSWORD authentication method will be used instead of the SAFECOOKIE one."`
V2 bool `long:"v2" description:"Automatically set up a v2 onion service to listen for inbound connections"`
V3 bool `long:"v3" description:"Automatically set up a v3 onion service to listen for inbound connections"`
PrivateKeyPath string `long:"privatekeypath" description:"The path to the private key of the onion service being created"`
WatchtowerKeyPath string `long:"watchtowerkeypath" description:"The path to the private key of the watchtower onion service being created"`
}
type config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
LndDir string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc."`
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
DataDir string `short:"b" long:"datadir" description:"The directory to store lnd's data within"`
SyncFreelist bool `long:"sync-freelist" description:"Whether the databases used within lnd should sync their freelist to disk. This is disabled by default resulting in improved memory performance during operation, but with an increase in startup time."`
TLSCertPath string `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"`
TLSKeyPath string `long:"tlskeypath" description:"Path to write the TLS private key for lnd'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"`
NoMacaroons bool `long:"no-macaroons" description:"Disable macaroon authentication"`
AdminMacPath string `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"`
ReadMacPath string `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"`
InvoiceMacPath string `long:"invoicemacaroonpath" description:"Path to the invoice-only macaroon for lnd's RPC and REST services if it doesn't exist"`
LogDir string `long:"logdir" description:"Directory to log output."`
MaxLogFiles int `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
MaxLogFileSize int `long:"maxlogfilesize" description:"Maximum logfile size in MB"`
AcceptorTimeout time.Duration `long:"acceptortimeout" description:"Time after which an RPCAcceptor will time out and return false if it hasn't yet received a response"`
RawRPCListeners []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
RawRESTListeners []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
RawListeners []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
RawExternalIPs []string `long:"externalip" description:"Add an ip:port to the list of local addresses we claim to listen on to peers. If a port is not specified, the default (9735) will be used regardless of other parameters"`
RPCListeners []net.Addr
RESTListeners []net.Addr
Listeners []net.Addr
ExternalIPs []net.Addr
DisableListen bool `long:"nolisten" description:"Disable listening for incoming peer connections"`
DisableRest bool `long:"norest" description:"Disable REST API"`
NAT bool `long:"nat" description:"Toggle NAT traversal support (using either UPnP or NAT-PMP) to automatically advertise your external IP address to the network -- NOTE this does not support devices behind multiple NATs"`
MinBackoff time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
MaxBackoff time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65535"`
UnsafeDisconnect bool `long:"unsafe-disconnect" description:"DEPRECATED: Allows the rpcserver to intentionally disconnect from peers with open channels. THIS FLAG WILL BE REMOVED IN 0.10.0"`
UnsafeReplay bool `long:"unsafe-replay" description:"Causes a link to replay the adds on its commitment txn after starting up, this enables testing of the sphinx replay logic."`
MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
BackupFilePath string `long:"backupfilepath" description:"The target location of the channel backup file"`
Bitcoin *chainConfig `group:"Bitcoin" namespace:"bitcoin"`
BtcdMode *btcdConfig `group:"btcd" namespace:"btcd"`
BitcoindMode *bitcoindConfig `group:"bitcoind" namespace:"bitcoind"`
NeutrinoMode *neutrinoConfig `group:"neutrino" namespace:"neutrino"`
Litecoin *chainConfig `group:"Litecoin" namespace:"litecoin"`
LtcdMode *btcdConfig `group:"ltcd" namespace:"ltcd"`
LitecoindMode *bitcoindConfig `group:"litecoind" namespace:"litecoind"`
Autopilot *autoPilotConfig `group:"Autopilot" namespace:"autopilot"`
Tor *torConfig `group:"Tor" namespace:"tor"`
SubRPCServers *subRPCServerConfigs `group:"subrpc"`
Hodl *hodl.Config `group:"hodl" namespace:"hodl"`
NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
NoSeedBackup bool `long:"noseedbackup" description:"If true, NO SEED WILL BE EXPOSED AND THE WALLET WILL BE ENCRYPTED USING THE DEFAULT PASSPHRASE -- EVER. THIS FLAG IS ONLY FOR TESTING AND IS BEING DEPRECATED."`
PaymentsExpirationGracePeriod time.Duration `long:"payments-expiration-grace-period" description:"A period to wait before force closing channels with outgoing htlcs that have timed-out and are a result of this node initiated payments."`
TrickleDelay int `long:"trickledelay" description:"Time in milliseconds between each release of announcements to the network"`
ChanEnableTimeout time.Duration `long:"chan-enable-timeout" description:"The duration that a peer connection must be stable before attempting to send a channel update to reenable or cancel a pending disables of the peer's channels on the network."`
ChanDisableTimeout time.Duration `long:"chan-disable-timeout" description:"The duration that must elapse after first detecting that an already active channel is actually inactive and sending channel update disabling it to the network. The pending disable can be canceled if the peer reconnects and becomes stable for chan-enable-timeout before the disable update is sent."`
ChanStatusSampleInterval time.Duration `long:"chan-status-sample-interval" description:"The polling interval between attempts to detect if an active channel has become inactive due to its peer going offline."`
Alias string `long:"alias" description:"The node alias. Used as a moniker by peers and intelligence services"`
Color string `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"`
MinChanSize int64 `long:"minchansize" description:"The smallest channel size (in satoshis) that we should accept. Incoming channels smaller than this will be rejected"`
NumGraphSyncPeers int `long:"numgraphsyncpeers" description:"The number of peers that we should receive new graph updates from. This option can be tuned to save bandwidth for light clients or routing nodes."`
HistoricalSyncInterval time.Duration `long:"historicalsyncinterval" description:"The polling interval between historical graph sync attempts. Each historical graph sync attempt ensures we reconcile with the remote peer's graph from the genesis block."`
IgnoreHistoricalGossipFilters bool `long:"ignore-historical-gossip-filters" description:"If true, will not reply with historical data that matches the range specified by a remote peer's gossip_timestamp_filter. Doing so will result in lower memory and bandwidth requirements."`
RejectPush bool `long:"rejectpush" description:"If true, lnd will not accept channel opening requests with non-zero push amounts. This should prevent accidental pushes to merchant nodes."`
RejectHTLC bool `long:"rejecthtlc" description:"If true, lnd will not forward any HTLCs that are meant as onward payments. This option will still allow lnd to send HTLCs and receive HTLCs but lnd won't be used as a hop."`
StaggerInitialReconnect bool `long:"stagger-initial-reconnect" description:"If true, will apply a randomized staggering between 0s and 30s when reconnecting to persistent peers on startup. The first 10 reconnections will be attempted instantly, regardless of the flag's value"`
MaxOutgoingCltvExpiry uint32 `long:"max-cltv-expiry" description:"The maximum number of blocks funds could be locked up for when forwarding payments."`
MaxChannelFeeAllocation float64 `long:"max-channel-fee-allocation" description:"The maximum percentage of total funds that can be allocated to a channel's commitment fee. This only applies for the initiator of the channel. Valid values are within [0.1, 1]."`
net tor.Net
EnableUpfrontShutdown bool `long:"enable-upfront-shutdown" description:"If true, option upfront shutdown script will be enabled. If peers that we open channels with support this feature, we will automatically set the script to which cooperative closes should be paid out to on channel open. This offers the partial protection of a channel peer disconnecting from us if cooperative close is attempted with a different script."`
AcceptKeySend bool `long:"accept-keysend" description:"If true, spontaneous payments through keysend will be accepted. [experimental]"`
Routing *routing.Conf `group:"routing" namespace:"routing"`
Workers *lncfg.Workers `group:"workers" namespace:"workers"`
Caches *lncfg.Caches `group:"caches" namespace:"caches"`
Prometheus lncfg.Prometheus `group:"prometheus" namespace:"prometheus"`
WtClient *lncfg.WtClient `group:"wtclient" namespace:"wtclient"`
Watchtower *lncfg.Watchtower `group:"watchtower" namespace:"watchtower"`
ProtocolOptions *lncfg.ProtocolOptions `group:"protocol" namespace:"protocol"`
AllowCircularRoute bool `long:"allow-circular-route" description:"If true, our node will allow htlc forwards that arrive and depart on the same channel."`
}
func loadConfig() (*config, error) {
defaultCfg := config{
LndDir: defaultLndDir,
ConfigFile: defaultConfigFile,
DataDir: defaultDataDir,
DebugLevel: defaultLogLevel,
TLSCertPath: defaultTLSCertPath,
TLSKeyPath: defaultTLSKeyPath,
LogDir: defaultLogDir,
MaxLogFiles: defaultMaxLogFiles,
MaxLogFileSize: defaultMaxLogFileSize,
AcceptorTimeout: defaultAcceptorTimeout,
Bitcoin: &chainConfig{
MinHTLCIn: defaultBitcoinMinHTLCInMSat,
MinHTLCOut: defaultBitcoinMinHTLCOutMSat,
BaseFee: DefaultBitcoinBaseFeeMSat,
FeeRate: DefaultBitcoinFeeRate,
TimeLockDelta: DefaultBitcoinTimeLockDelta,
Node: "btcd",
},
BtcdMode: &btcdConfig{
Dir: defaultBtcdDir,
RPCHost: defaultRPCHost,
RPCCert: defaultBtcdRPCCertFile,
},
BitcoindMode: &bitcoindConfig{
Dir: defaultBitcoindDir,
RPCHost: defaultRPCHost,
EstimateMode: defaultBitcoindEstimateMode,
},
Litecoin: &chainConfig{
MinHTLCIn: defaultLitecoinMinHTLCInMSat,
MinHTLCOut: defaultLitecoinMinHTLCOutMSat,
BaseFee: defaultLitecoinBaseFeeMSat,
FeeRate: defaultLitecoinFeeRate,
TimeLockDelta: defaultLitecoinTimeLockDelta,
Node: "ltcd",
},
LtcdMode: &btcdConfig{
Dir: defaultLtcdDir,
RPCHost: defaultRPCHost,
RPCCert: defaultLtcdRPCCertFile,
},
LitecoindMode: &bitcoindConfig{
Dir: defaultLitecoindDir,
RPCHost: defaultRPCHost,
EstimateMode: defaultBitcoindEstimateMode,
},
UnsafeDisconnect: true,
MaxPendingChannels: DefaultMaxPendingChannels,
NoSeedBackup: defaultNoSeedBackup,
MinBackoff: defaultMinBackoff,
MaxBackoff: defaultMaxBackoff,
SubRPCServers: &subRPCServerConfigs{
SignRPC: &signrpc.Config{},
RouterRPC: routerrpc.DefaultConfig(),
},
Autopilot: &autoPilotConfig{
MaxChannels: 5,
Allocation: 0.6,
MinChannelSize: int64(minChanFundingSize),
MaxChannelSize: int64(MaxFundingAmount),
MinConfs: 1,
ConfTarget: autopilot.DefaultConfTarget,
Heuristic: map[string]float64{
"preferential": 1.0,
},
},
PaymentsExpirationGracePeriod: defaultPaymentsExpirationGracePeriod,
TrickleDelay: defaultTrickleDelay,
ChanStatusSampleInterval: defaultChanStatusSampleInterval,
ChanEnableTimeout: defaultChanEnableTimeout,
ChanDisableTimeout: defaultChanDisableTimeout,
Alias: defaultAlias,
Color: defaultColor,
MinChanSize: int64(minChanFundingSize),
NumGraphSyncPeers: defaultMinPeers,
HistoricalSyncInterval: discovery.DefaultHistoricalSyncInterval,
Tor: &torConfig{
SOCKS: defaultTorSOCKS,
DNS: defaultTorDNS,
Control: defaultTorControl,
},
net: &tor.ClearNet{},
Workers: &lncfg.Workers{
Read: lncfg.DefaultReadWorkers,
Write: lncfg.DefaultWriteWorkers,
Sig: lncfg.DefaultSigWorkers,
},
Caches: &lncfg.Caches{
RejectCacheSize: channeldb.DefaultRejectCacheSize,
ChannelCacheSize: channeldb.DefaultChannelCacheSize,
},
Prometheus: lncfg.DefaultPrometheus(),
Watchtower: &lncfg.Watchtower{
TowerDir: defaultTowerDir,
},
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
}
preCfg := defaultCfg
if _, err := flags.Parse(&preCfg); err != nil {
return nil, err
}
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
if preCfg.ShowVersion {
fmt.Println(appName, "version", build.Version(),
"commit="+build.Commit)
os.Exit(0)
}
configFileDir := cleanAndExpandPath(preCfg.LndDir)
configFilePath := cleanAndExpandPath(preCfg.ConfigFile)
if configFileDir != defaultLndDir {
if configFilePath == defaultConfigFile {
configFilePath = filepath.Join(
configFileDir, defaultConfigFilename,
)
}
}
var configFileError error
cfg := preCfg
if err := flags.IniParse(configFilePath, &cfg); err != nil {
if _, ok := err.(*flags.IniError); ok {
return nil, err
}
configFileError = err
}
if _, err := flags.Parse(&cfg); err != nil {
return nil, err
}
lndDir := cleanAndExpandPath(cfg.LndDir)
if lndDir != defaultLndDir {
cfg.DataDir = filepath.Join(lndDir, defaultDataDirname)
cfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
cfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename)
cfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
if cfg.Watchtower.TowerDir == defaultTowerDir {
cfg.Watchtower.TowerDir =
filepath.Join(cfg.DataDir, defaultTowerSubDirname)
}
}
funcName := "loadConfig"
if err := os.MkdirAll(lndDir, 0700); err != nil {
if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
if link, lerr := os.Readlink(e.Path); lerr == nil {
str := "is symlink %s -> %s mounted?"
err = fmt.Errorf(str, e.Path, link)
}
}
str := "%s: Failed to create lnd directory: %v"
err := fmt.Errorf(str, funcName, err)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
cfg.DataDir = cleanAndExpandPath(cfg.DataDir)
cfg.TLSCertPath = cleanAndExpandPath(cfg.TLSCertPath)
cfg.TLSKeyPath = cleanAndExpandPath(cfg.TLSKeyPath)
cfg.AdminMacPath = cleanAndExpandPath(cfg.AdminMacPath)
cfg.ReadMacPath = cleanAndExpandPath(cfg.ReadMacPath)
cfg.InvoiceMacPath = cleanAndExpandPath(cfg.InvoiceMacPath)
cfg.LogDir = cleanAndExpandPath(cfg.LogDir)
cfg.BtcdMode.Dir = cleanAndExpandPath(cfg.BtcdMode.Dir)
cfg.LtcdMode.Dir = cleanAndExpandPath(cfg.LtcdMode.Dir)
cfg.BitcoindMode.Dir = cleanAndExpandPath(cfg.BitcoindMode.Dir)
cfg.LitecoindMode.Dir = cleanAndExpandPath(cfg.LitecoindMode.Dir)
cfg.Tor.PrivateKeyPath = cleanAndExpandPath(cfg.Tor.PrivateKeyPath)
cfg.Tor.WatchtowerKeyPath = cleanAndExpandPath(cfg.Tor.WatchtowerKeyPath)
cfg.Watchtower.TowerDir = cleanAndExpandPath(cfg.Watchtower.TowerDir)
if cfg.Autopilot.MaxChannels < 0 {
str := "%s: autopilot.maxchannels must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.Allocation < 0 {
str := "%s: autopilot.allocation must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MinChannelSize < 0 {
str := "%s: autopilot.minchansize must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MaxChannelSize < 0 {
str := "%s: autopilot.maxchansize must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MinConfs < 0 {
str := "%s: autopilot.minconfs must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.ConfTarget < 1 {
str := "%s: autopilot.conftarget must be positive"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MinChannelSize < int64(minChanFundingSize) {
cfg.Autopilot.MinChannelSize = int64(minChanFundingSize)
}
if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
}
if _, err := validateAtplCfg(cfg.Autopilot); err != nil {
return nil, err
}
if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 {
return nil, fmt.Errorf("invalid max channel fee allocation: "+
"%v, must be within (0, 1]",
cfg.MaxChannelFeeAllocation)
}
socks, err := lncfg.ParseAddressString(
cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
cfg.Tor.SOCKS = socks.String()
if cfg.Tor.DNS != defaultTorDNS {
dns, err := lncfg.ParseAddressString(
cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
cfg.Tor.DNS = dns.String()
}
control, err := lncfg.ParseAddressString(
cfg.Tor.Control, strconv.Itoa(defaultTorControlPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
cfg.Tor.Control = control.String()
if cfg.Tor.SOCKS == cfg.Tor.Control {
str := "%s: tor.socks and tor.control can not use " +
"the same host:port"
return nil, fmt.Errorf(str, funcName)
}
switch {
case cfg.Tor.V2 && cfg.Tor.V3:
return nil, errors.New("either tor.v2 or tor.v3 can be set, " +
"but not both")
case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3):
return nil, errors.New("listening must be enabled when " +
"enabling inbound connections over Tor")
}
if cfg.Tor.PrivateKeyPath == "" {
switch {
case cfg.Tor.V2:
cfg.Tor.PrivateKeyPath = filepath.Join(
lndDir, defaultTorV2PrivateKeyFilename,
)
case cfg.Tor.V3:
cfg.Tor.PrivateKeyPath = filepath.Join(
lndDir, defaultTorV3PrivateKeyFilename,
)
}
}
if cfg.Tor.WatchtowerKeyPath == "" {
switch {
case cfg.Tor.V2:
cfg.Tor.WatchtowerKeyPath = filepath.Join(
cfg.Watchtower.TowerDir, defaultTorV2PrivateKeyFilename,
)
case cfg.Tor.V3:
cfg.Tor.WatchtowerKeyPath = filepath.Join(
cfg.Watchtower.TowerDir, defaultTorV3PrivateKeyFilename,
)
}
}
if cfg.Tor.Active {
cfg.net = &tor.ProxyNet{
SOCKS: cfg.Tor.SOCKS,
DNS: cfg.Tor.DNS,
StreamIsolation: cfg.Tor.StreamIsolation,
}
}
if cfg.DisableListen && cfg.NAT {
return nil, errors.New("NAT traversal cannot be used when " +
"listening is disabled")
}
switch {
case cfg.Litecoin.Active && cfg.Bitcoin.Active:
str := "%s: Currently both Bitcoin and Litecoin cannot be " +
"active together"
return nil, fmt.Errorf(str, funcName)
case !cfg.Bitcoin.Active && !cfg.Litecoin.Active:
return nil, fmt.Errorf("%s: either bitcoin.active or "+
"litecoin.active must be set to 1 (true)", funcName)
case cfg.Litecoin.Active:
if cfg.Litecoin.TimeLockDelta < minTimeLockDelta {
return nil, fmt.Errorf("timelockdelta must be at least %v",
minTimeLockDelta)
}
numNets := 0
var ltcParams litecoinNetParams
if cfg.Litecoin.MainNet {
numNets++
ltcParams = litecoinMainNetParams
}
if cfg.Litecoin.TestNet3 {
numNets++
ltcParams = litecoinTestNetParams
}
if cfg.Litecoin.RegTest {
numNets++
ltcParams = litecoinRegTestNetParams
}
if cfg.Litecoin.SimNet {
numNets++
ltcParams = litecoinSimNetParams
}
if numNets > 1 {
str := "%s: The mainnet, testnet, and simnet params " +
"can't be used together -- choose one of the " +
"three"
err := fmt.Errorf(str, funcName)
return nil, err
}
if numNets == 0 {
str := "%s: either --litecoin.mainnet, or " +
"litecoin.testnet must be specified"
err := fmt.Errorf(str, funcName)
return nil, err
}
applyLitecoinParams(&activeNetParams, <cParams)
switch cfg.Litecoin.Node {
case "ltcd":
err := parseRPCParams(cfg.Litecoin, cfg.LtcdMode,
litecoinChain, funcName)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for ltcd: %v", err)
return nil, err
}
case "litecoind":
if cfg.Litecoin.SimNet {
return nil, fmt.Errorf("%s: litecoind does not "+
"support simnet", funcName)
}
err := parseRPCParams(cfg.Litecoin, cfg.LitecoindMode,
litecoinChain, funcName)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for litecoind: %v", err)
return nil, err
}
default:
str := "%s: only ltcd and litecoind mode supported for " +
"litecoin at this time"
return nil, fmt.Errorf(str, funcName)
}
cfg.Litecoin.ChainDir = filepath.Join(cfg.DataDir,
defaultChainSubDirname,
litecoinChain.String())
registeredChains.RegisterPrimaryChain(litecoinChain)
MaxFundingAmount = maxLtcFundingAmount
MaxPaymentMSat = maxLtcPaymentMSat
case cfg.Bitcoin.Active:
numNets := 0
if cfg.Bitcoin.MainNet {
numNets++
activeNetParams = bitcoinMainNetParams
}
if cfg.Bitcoin.TestNet3 {
numNets++
activeNetParams = bitcoinTestNetParams
}
if cfg.Bitcoin.RegTest {
numNets++
activeNetParams = bitcoinRegTestNetParams
}
if cfg.Bitcoin.SimNet {
numNets++
activeNetParams = bitcoinSimNetParams
}
if numNets > 1 {
str := "%s: The mainnet, testnet, regtest, and " +
"simnet params can't be used together -- " +
"choose one of the four"
err := fmt.Errorf(str, funcName)
return nil, err
}
if numNets == 0 {
str := "%s: either --bitcoin.mainnet, or " +
"bitcoin.testnet, bitcoin.simnet, or bitcoin.regtest " +
"must be specified"
err := fmt.Errorf(str, funcName)
return nil, err
}
if cfg.Bitcoin.TimeLockDelta < minTimeLockDelta {
return nil, fmt.Errorf("timelockdelta must be at least %v",
minTimeLockDelta)
}
switch cfg.Bitcoin.Node {
case "btcd":
err := parseRPCParams(
cfg.Bitcoin, cfg.BtcdMode, bitcoinChain, funcName,
)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for btcd: %v", err)
return nil, err
}
case "bitcoind":
if cfg.Bitcoin.SimNet {
return nil, fmt.Errorf("%s: bitcoind does not "+
"support simnet", funcName)
}
err := parseRPCParams(
cfg.Bitcoin, cfg.BitcoindMode, bitcoinChain, funcName,
)
if err != nil {
err := fmt.Errorf("unable to load RPC "+
"credentials for bitcoind: %v", err)
return nil, err
}
case "neutrino":
default:
str := "%s: only btcd, bitcoind, and neutrino mode " +
"supported for bitcoin at this time"
return nil, fmt.Errorf(str, funcName)
}
cfg.Bitcoin.ChainDir = filepath.Join(cfg.DataDir,
defaultChainSubDirname,
bitcoinChain.String())
registeredChains.RegisterPrimaryChain(bitcoinChain)
}
if cfg.Autopilot.MaxChannels < 0 {
str := "%s: autopilot.maxchannels must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.Allocation < 0 {
str := "%s: autopilot.allocation must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MinChannelSize < 0 {
str := "%s: autopilot.minchansize must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MaxChannelSize < 0 {
str := "%s: autopilot.maxchansize must be non-negative"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
if cfg.Autopilot.MinChannelSize < int64(minChanFundingSize) {
cfg.Autopilot.MinChannelSize = int64(minChanFundingSize)
}
if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
}
if cfg.Profile != "" {
profilePort, err := strconv.Atoi(cfg.Profile)
if err != nil || profilePort < 1024 || profilePort > 65535 {
str := "%s: The profile port must be between 1024 and 65535"
err := fmt.Errorf(str, funcName)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
}
networkDir = filepath.Join(
cfg.DataDir, defaultChainSubDirname,
registeredChains.PrimaryChain().String(),
normalizeNetwork(activeNetParams.Name),
)
if cfg.AdminMacPath == "" {
cfg.AdminMacPath = filepath.Join(
networkDir, defaultAdminMacFilename,
)
}
if cfg.ReadMacPath == "" {
cfg.ReadMacPath = filepath.Join(
networkDir, defaultReadMacFilename,
)
}
if cfg.InvoiceMacPath == "" {
cfg.InvoiceMacPath = filepath.Join(
networkDir, defaultInvoiceMacFilename,
)
}
if cfg.BackupFilePath == "" {
cfg.BackupFilePath = filepath.Join(
networkDir, chanbackup.DefaultBackupFileName,
)
}
cfg.LogDir = filepath.Join(cfg.LogDir,
registeredChains.PrimaryChain().String(),
normalizeNetwork(activeNetParams.Name))
if cfg.DebugLevel == "show" {
fmt.Println("Supported subsystems",
logWriter.SupportedSubsystems())
os.Exit(0)
}
err = logWriter.InitLogRotator(
filepath.Join(cfg.LogDir, defaultLogFilename),
cfg.MaxLogFileSize, cfg.MaxLogFiles,
)
if err != nil {
str := "%s: log rotation setup failed: %v"
err = fmt.Errorf(str, funcName, err.Error())
fmt.Fprintln(os.Stderr, err)
return nil, err
}
err = build.ParseAndSetDebugLevels(cfg.DebugLevel, logWriter)
if err != nil {
err = fmt.Errorf("%s: %v", funcName, err.Error())
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, err
}
if len(cfg.RawRPCListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRPCPort)
cfg.RawRPCListeners = append(cfg.RawRPCListeners, addr)
}
if len(cfg.RawRESTListeners) == 0 {
addr := fmt.Sprintf("localhost:%d", defaultRESTPort)
cfg.RawRESTListeners = append(cfg.RawRESTListeners, addr)
}
if len(cfg.RawListeners) == 0 {
addr := fmt.Sprintf(":%d", defaultPeerPort)
if cfg.Tor.Active {
addr = fmt.Sprintf("localhost:%d", defaultPeerPort)
}
cfg.RawListeners = append(cfg.RawListeners, addr)
}
cfg.RPCListeners, err = lncfg.NormalizeAddresses(
cfg.RawRPCListeners, strconv.Itoa(defaultRPCPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
cfg.RESTListeners, err = lncfg.NormalizeAddresses(
cfg.RawRESTListeners, strconv.Itoa(defaultRESTPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
err = lncfg.EnforceSafeAuthentication(
cfg.RPCListeners, !cfg.NoMacaroons,
)
if err != nil {
return nil, err
}
if cfg.DisableRest {
ltndLog.Infof("REST API is disabled!")
cfg.RESTListeners = nil
} else {
err = lncfg.EnforceSafeAuthentication(
cfg.RESTListeners, !cfg.NoMacaroons,
)
if err != nil {
return nil, err
}
}
if cfg.DisableListen {
ltndLog.Infof("Listening on the p2p interface is disabled!")
cfg.Listeners = nil
cfg.ExternalIPs = nil
} else {
cfg.Listeners, err = lncfg.NormalizeAddresses(
cfg.RawListeners, strconv.Itoa(defaultPeerPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
cfg.ExternalIPs, err = lncfg.NormalizeAddresses(
cfg.RawExternalIPs, strconv.Itoa(defaultPeerPort),
cfg.net.ResolveTCPAddr,
)
if err != nil {
return nil, err
}
for _, p2pListener := range cfg.Listeners {
if lncfg.IsUnix(p2pListener) {
err := fmt.Errorf("unix socket addresses cannot be "+
"used for the p2p connection listener: %s",
p2pListener)
return nil, err
}
}
}
if cfg.MinBackoff > cfg.MaxBackoff {
return nil, fmt.Errorf("maxbackoff must be greater than " +
"minbackoff")
}
err = lncfg.Validate(
cfg.Workers,
cfg.Caches,
cfg.WtClient,
)
if err != nil {
return nil, err
}
_, err = parseHexColor(cfg.Color)
if err != nil {
return nil, fmt.Errorf("unable to parse node color: %v", err)
}
if configFileError != nil {
ltndLog.Warnf("%v", configFileError)
}
return &cfg, nil
}
func cleanAndExpandPath(path string) string {
if path == "" {
return ""
}
if strings.HasPrefix(path, "~") {
var homeDir string
user, err := user.Current()
if err == nil {
homeDir = user.HomeDir
} else {
homeDir = os.Getenv("HOME")
}
path = strings.Replace(path, "~", homeDir, 1)
}
return filepath.Clean(os.ExpandEnv(path))
}
func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
funcName string) error {
var daemonName, confDir, confFile string
switch conf := nodeConfig.(type) {
case *btcdConfig:
if conf.RPCUser != "" && conf.RPCPass != "" {
return nil
}
switch net {
case bitcoinChain:
daemonName = "btcd"
confDir = conf.Dir
confFile = "btcd"
case litecoinChain:
daemonName = "ltcd"
confDir = conf.Dir
confFile = "ltcd"
}
if conf.RPCUser != "" || conf.RPCPass != "" {
return fmt.Errorf("please set both or neither of "+
"%[1]v.rpcuser, %[1]v.rpcpass", daemonName)
}
case *bitcoindConfig:
if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
err := checkZMQOptions(
conf.ZMQPubRawBlock, conf.ZMQPubRawTx,
)
if err != nil {
return err
}
}
if conf.EstimateMode != "" {
err := checkEstimateMode(conf.EstimateMode)
if err != nil {
return err
}
}
if conf.RPCUser != "" && conf.RPCPass != "" &&
conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
return nil
}
switch net {
case bitcoinChain:
daemonName = "bitcoind"
confDir = conf.Dir
confFile = "bitcoin"
case litecoinChain:
daemonName = "litecoind"
confDir = conf.Dir
confFile = "litecoin"
}
if conf.RPCUser != "" || conf.RPCPass != "" ||
conf.ZMQPubRawBlock != "" || conf.ZMQPubRawTx != "" {
return fmt.Errorf("please set all or none of "+
"%[1]v.rpcuser, %[1]v.rpcpass, "+
"%[1]v.zmqpubrawblock, %[1]v.zmqpubrawtx",
daemonName)
}
}
if cConfig.SimNet {
str := "%v: rpcuser and rpcpass must be set to your btcd " +
"node's RPC parameters for simnet mode"
return fmt.Errorf(str, funcName)
}
fmt.Println("Attempting automatic RPC configuration to " + daemonName)
confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf", confFile))
switch cConfig.Node {
case "btcd", "ltcd":
nConf := nodeConfig.(*btcdConfig)
rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
if err != nil {
return fmt.Errorf("unable to extract RPC credentials:"+
" %v, cannot start w/o RPC connection",
err)
}
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
case "bitcoind", "litecoind":
nConf := nodeConfig.(*bitcoindConfig)
rpcUser, rpcPass, zmqBlockHost, zmqTxHost, err :=
extractBitcoindRPCParams(confFile)
if err != nil {
return fmt.Errorf("unable to extract RPC credentials:"+
" %v, cannot start w/o RPC connection",
err)
}
nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
nConf.ZMQPubRawBlock, nConf.ZMQPubRawTx = zmqBlockHost, zmqTxHost
}
fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName)
return nil
}
func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) {
btcdConfigFile, err := os.Open(btcdConfigPath)
if err != nil {
return "", "", err
}
defer btcdConfigFile.Close()
configContents, err := ioutil.ReadAll(btcdConfigFile)
if err != nil {
return "", "", err
}
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
if err != nil {
return "", "", err
}
userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
if userSubmatches == nil {
return "", "", fmt.Errorf("unable to find rpcuser in config")
}
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass\s*=\s*([^\s]+)`)
if err != nil {
return "", "", err
}
passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
if passSubmatches == nil {
return "", "", fmt.Errorf("unable to find rpcuser in config")
}
return string(userSubmatches[1]), string(passSubmatches[1]), nil
}
func extractBitcoindRPCParams(bitcoindConfigPath string) (string, string, string, string, error) {
bitcoindConfigFile, err := os.Open(bitcoindConfigPath)
if err != nil {
return "", "", "", "", err
}
defer bitcoindConfigFile.Close()
configContents, err := ioutil.ReadAll(bitcoindConfigFile)
if err != nil {
return "", "", "", "", err
}
zmqBlockHostRE, err := regexp.Compile(
`(?m)^\s*zmqpubrawblock\s*=\s*([^\s]+)`,
)
if err != nil {
return "", "", "", "", err
}
zmqBlockHostSubmatches := zmqBlockHostRE.FindSubmatch(configContents)
if len(zmqBlockHostSubmatches) < 2 {
return "", "", "", "", fmt.Errorf("unable to find " +
"zmqpubrawblock in config")
}
zmqTxHostRE, err := regexp.Compile(`(?m)^\s*zmqpubrawtx\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
zmqTxHostSubmatches := zmqTxHostRE.FindSubmatch(configContents)
if len(zmqTxHostSubmatches) < 2 {
return "", "", "", "", errors.New("unable to find zmqpubrawtx " +
"in config")
}
zmqBlockHost := string(zmqBlockHostSubmatches[1])
zmqTxHost := string(zmqTxHostSubmatches[1])
if err := checkZMQOptions(zmqBlockHost, zmqTxHost); err != nil {
return "", "", "", "", err
}
dataDir := path.Dir(bitcoindConfigPath)
dataDirRE, err := regexp.Compile(`(?m)^\s*datadir\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
dataDirSubmatches := dataDirRE.FindSubmatch(configContents)
if dataDirSubmatches != nil {
dataDir = string(dataDirSubmatches[1])
}
chainDir := "/"
switch activeNetParams.Params.Name {
case "testnet3":
chainDir = "/testnet3/"
case "testnet4":
chainDir = "/testnet4/"
case "regtest":
chainDir = "/regtest/"
}
cookie, err := ioutil.ReadFile(dataDir + chainDir + ".cookie")
if err == nil {
splitCookie := strings.Split(string(cookie), ":")
if len(splitCookie) == 2 {
return splitCookie[0], splitCookie[1], zmqBlockHost,
zmqTxHost, nil
}
}
rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
if userSubmatches == nil {
return "", "", "", "", fmt.Errorf("unable to find rpcuser in " +
"config")
}
rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpassword\s*=\s*([^\s]+)`)
if err != nil {
return "", "", "", "", err
}
passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
if passSubmatches == nil {
return "", "", "", "", fmt.Errorf("unable to find rpcpassword " +
"in config")
}
return string(userSubmatches[1]), string(passSubmatches[1]),
zmqBlockHost, zmqTxHost, nil
}
func checkZMQOptions(zmqBlockHost, zmqTxHost string) error {
if zmqBlockHost == zmqTxHost {
return errors.New("zmqpubrawblock and zmqpubrawtx must be set " +
"to different addresses")
}
return nil
}
func checkEstimateMode(estimateMode string) error {
for _, mode := range bitcoindEstimateModes {
if estimateMode == mode {
return nil
}
}
return fmt.Errorf("estimatemode must be one of the following: %v",
bitcoindEstimateModes[:])
}
func normalizeNetwork(network string) string {
if strings.HasPrefix(network, "testnet") {
return "testnet"
}
return network
}