• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 10190851021

01 Aug 2024 02:21AM UTC coverage: 58.627% (-0.01%) from 58.641%
10190851021

push

github

web-flow
Merge pull request #8764 from ellemouton/rb-send-via-multi-path

[3/4] Route Blinding: send MPP over multiple blinded paths

197 of 248 new or added lines in 7 files covered. (79.44%)

249 existing lines in 19 files now uncovered.

125259 of 213655 relevant lines covered (58.63%)

28116.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.57
/config.go
1
// Copyright (c) 2013-2017 The btcsuite developers
2
// Copyright (c) 2015-2016 The Decred developers
3
// Copyright (C) 2015-2022 The Lightning Network Developers
4

5
package lnd
6

7
import (
8
        "encoding/hex"
9
        "errors"
10
        "fmt"
11
        "io"
12
        "net"
13
        "os"
14
        "os/user"
15
        "path/filepath"
16
        "reflect"
17
        "regexp"
18
        "strconv"
19
        "strings"
20
        "time"
21

22
        "github.com/btcsuite/btcd/btcutil"
23
        "github.com/btcsuite/btcd/chaincfg"
24
        flags "github.com/jessevdk/go-flags"
25
        "github.com/lightninglabs/neutrino"
26
        "github.com/lightningnetwork/lnd/autopilot"
27
        "github.com/lightningnetwork/lnd/build"
28
        "github.com/lightningnetwork/lnd/chainreg"
29
        "github.com/lightningnetwork/lnd/chanbackup"
30
        "github.com/lightningnetwork/lnd/channeldb"
31
        "github.com/lightningnetwork/lnd/discovery"
32
        "github.com/lightningnetwork/lnd/funding"
33
        "github.com/lightningnetwork/lnd/htlcswitch"
34
        "github.com/lightningnetwork/lnd/htlcswitch/hodl"
35
        "github.com/lightningnetwork/lnd/input"
36
        "github.com/lightningnetwork/lnd/lncfg"
37
        "github.com/lightningnetwork/lnd/lnrpc"
38
        "github.com/lightningnetwork/lnd/lnrpc/peersrpc"
39
        "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
40
        "github.com/lightningnetwork/lnd/lnrpc/signrpc"
41
        "github.com/lightningnetwork/lnd/lnutils"
42
        "github.com/lightningnetwork/lnd/lnwallet"
43
        "github.com/lightningnetwork/lnd/lnwire"
44
        "github.com/lightningnetwork/lnd/routing"
45
        "github.com/lightningnetwork/lnd/signal"
46
        "github.com/lightningnetwork/lnd/tor"
47
)
48

49
const (
50
        defaultDataDirname        = "data"
51
        defaultChainSubDirname    = "chain"
52
        defaultGraphSubDirname    = "graph"
53
        defaultTowerSubDirname    = "watchtower"
54
        defaultTLSCertFilename    = "tls.cert"
55
        defaultTLSKeyFilename     = "tls.key"
56
        defaultAdminMacFilename   = "admin.macaroon"
57
        defaultReadMacFilename    = "readonly.macaroon"
58
        defaultInvoiceMacFilename = "invoice.macaroon"
59
        defaultLogLevel           = "info"
60
        defaultLogDirname         = "logs"
61
        defaultLogFilename        = "lnd.log"
62
        defaultRPCPort            = 10009
63
        defaultRESTPort           = 8080
64
        defaultPeerPort           = 9735
65
        defaultRPCHost            = "localhost"
66

67
        defaultNoSeedBackup                  = false
68
        defaultPaymentsExpirationGracePeriod = time.Duration(0)
69
        defaultTrickleDelay                  = 90 * 1000
70
        defaultChanStatusSampleInterval      = time.Minute
71
        defaultChanEnableTimeout             = 19 * time.Minute
72
        defaultChanDisableTimeout            = 20 * time.Minute
73
        defaultHeightHintCacheQueryDisable   = false
74
        defaultMaxLogFiles                   = 3
75
        defaultMaxLogFileSize                = 10
76
        defaultMinBackoff                    = time.Second
77
        defaultMaxBackoff                    = time.Hour
78
        defaultLetsEncryptDirname            = "letsencrypt"
79
        defaultLetsEncryptListen             = ":80"
80

81
        defaultTorSOCKSPort            = 9050
82
        defaultTorDNSHost              = "soa.nodes.lightning.directory"
83
        defaultTorDNSPort              = 53
84
        defaultTorControlPort          = 9051
85
        defaultTorV2PrivateKeyFilename = "v2_onion_private_key"
86
        defaultTorV3PrivateKeyFilename = "v3_onion_private_key"
87

88
        // defaultZMQReadDeadline is the default read deadline to be used for
89
        // both the block and tx ZMQ subscriptions.
90
        defaultZMQReadDeadline = 5 * time.Second
91

92
        // DefaultAutogenValidity is the default validity of a self-signed
93
        // certificate. The value corresponds to 14 months
94
        // (14 months * 30 days * 24 hours).
95
        defaultTLSCertDuration = 14 * 30 * 24 * time.Hour
96

97
        // minTimeLockDelta is the minimum timelock we require for incoming
98
        // HTLCs on our channels.
99
        minTimeLockDelta = routing.MinCLTVDelta
100

101
        // MaxTimeLockDelta is the maximum CLTV delta that can be applied to
102
        // forwarded HTLCs.
103
        MaxTimeLockDelta = routing.MaxCLTVDelta
104

105
        // defaultAcceptorTimeout is the time after which an RPCAcceptor will time
106
        // out and return false if it hasn't yet received a response.
107
        defaultAcceptorTimeout = 15 * time.Second
108

109
        defaultAlias = ""
110
        defaultColor = "#3399FF"
111

112
        // defaultCoopCloseTargetConfs is the default confirmation target
113
        // that will be used to estimate a fee rate to use during a
114
        // cooperative channel closure initiated by a remote peer. By default
115
        // we'll set this to a lax value since we weren't the ones that
116
        // initiated the channel closure.
117
        defaultCoopCloseTargetConfs = 6
118

119
        // defaultBlockCacheSize is the size (in bytes) of blocks that will be
120
        // keep in memory if no size is specified.
121
        defaultBlockCacheSize uint64 = 20 * 1024 * 1024 // 20 MB
122

123
        // defaultHostSampleInterval is the default amount of time that the
124
        // HostAnnouncer will wait between DNS resolutions to check if the
125
        // backing IP of a host has changed.
126
        defaultHostSampleInterval = time.Minute * 5
127

128
        defaultChainInterval = time.Minute
129
        defaultChainTimeout  = time.Second * 30
130
        defaultChainBackoff  = time.Minute * 2
131
        defaultChainAttempts = 3
132

133
        // Set defaults for a health check which ensures that we have space
134
        // available on disk. Although this check is off by default so that we
135
        // avoid breaking any existing setups (particularly on mobile), we still
136
        // set the other default values so that the health check can be easily
137
        // enabled with sane defaults.
138
        defaultRequiredDisk = 0.1
139
        defaultDiskInterval = time.Hour * 12
140
        defaultDiskTimeout  = time.Second * 5
141
        defaultDiskBackoff  = time.Minute
142
        defaultDiskAttempts = 0
143

144
        // Set defaults for a health check which ensures that the TLS certificate
145
        // is not expired. Although this check is off by default (not all setups
146
        // require it), we still set the other default values so that the health
147
        // check can be easily enabled with sane defaults.
148
        defaultTLSInterval = time.Minute
149
        defaultTLSTimeout  = time.Second * 5
150
        defaultTLSBackoff  = time.Minute
151
        defaultTLSAttempts = 0
152

153
        // Set defaults for a health check which ensures that the tor
154
        // connection is alive. Although this check is off by default (not all
155
        // setups require it), we still set the other default values so that
156
        // the health check can be easily enabled with sane defaults.
157
        defaultTCInterval = time.Minute
158
        defaultTCTimeout  = time.Second * 5
159
        defaultTCBackoff  = time.Minute
160
        defaultTCAttempts = 0
161

162
        // Set defaults for a health check which ensures that the remote signer
163
        // RPC connection is alive. Although this check is off by default (only
164
        // active when remote signing is turned on), we still set the other
165
        // default values so that the health check can be easily enabled with
166
        // sane defaults.
167
        defaultRSInterval = time.Minute
168
        defaultRSTimeout  = time.Second * 1
169
        defaultRSBackoff  = time.Second * 30
170
        defaultRSAttempts = 1
171

172
        // defaultRemoteMaxHtlcs specifies the default limit for maximum
173
        // concurrent HTLCs the remote party may add to commitment transactions.
174
        // This value can be overridden with --default-remote-max-htlcs.
175
        defaultRemoteMaxHtlcs = 483
176

177
        // defaultMaxLocalCSVDelay is the maximum delay we accept on our
178
        // commitment output. The local csv delay maximum is now equal to
179
        // the remote csv delay maximum we require for the remote commitment
180
        // transaction.
181
        defaultMaxLocalCSVDelay = 2016
182

183
        // defaultChannelCommitInterval is the default maximum time between
184
        // receiving a channel state update and signing a new commitment.
185
        defaultChannelCommitInterval = 50 * time.Millisecond
186

187
        // maxChannelCommitInterval is the maximum time the commit interval can
188
        // be configured to.
189
        maxChannelCommitInterval = time.Hour
190

191
        // defaultPendingCommitInterval specifies the default timeout value
192
        // while waiting for the remote party to revoke a locally initiated
193
        // commitment state.
194
        defaultPendingCommitInterval = 1 * time.Minute
195

196
        // maxPendingCommitInterval specifies the max allowed duration when
197
        // waiting for the remote party to revoke a locally initiated
198
        // commitment state.
199
        maxPendingCommitInterval = 5 * time.Minute
200

201
        // defaultChannelCommitBatchSize is the default maximum number of
202
        // channel state updates that is accumulated before signing a new
203
        // commitment.
204
        defaultChannelCommitBatchSize = 10
205

206
        // defaultCoinSelectionStrategy is the coin selection strategy that is
207
        // used by default to fund transactions.
208
        defaultCoinSelectionStrategy = "largest"
209

210
        // defaultKeepFailedPaymentAttempts is the default setting for whether
211
        // to keep failed payments in the database.
212
        defaultKeepFailedPaymentAttempts = false
213

214
        // defaultGrpcServerPingTime is the default duration for the amount of
215
        // time of no activity after which the server pings the client to see if
216
        // the transport is still alive. If set below 1s, a minimum value of 1s
217
        // will be used instead.
218
        defaultGrpcServerPingTime = time.Minute
219

220
        // defaultGrpcServerPingTimeout is the default duration the server waits
221
        // after having pinged for keepalive check, and if no activity is seen
222
        // even after that the connection is closed.
223
        defaultGrpcServerPingTimeout = 20 * time.Second
224

225
        // defaultGrpcClientPingMinWait is the default minimum amount of time a
226
        // client should wait before sending a keepalive ping.
227
        defaultGrpcClientPingMinWait = 5 * time.Second
228

229
        // defaultHTTPHeaderTimeout is the default timeout for HTTP requests.
230
        DefaultHTTPHeaderTimeout = 5 * time.Second
231

232
        // BitcoinChainName is a string that represents the Bitcoin blockchain.
233
        BitcoinChainName = "bitcoin"
234

235
        bitcoindBackendName = "bitcoind"
236
        btcdBackendName     = "btcd"
237
        neutrinoBackendName = "neutrino"
238
)
239

240
var (
241
        // DefaultLndDir is the default directory where lnd tries to find its
242
        // configuration file and store its data. This is a directory in the
243
        // user's application data, for example:
244
        //   C:\Users\<username>\AppData\Local\Lnd on Windows
245
        //   ~/.lnd on Linux
246
        //   ~/Library/Application Support/Lnd on MacOS
247
        DefaultLndDir = btcutil.AppDataDir("lnd", false)
248

249
        // DefaultConfigFile is the default full path of lnd's configuration
250
        // file.
251
        DefaultConfigFile = filepath.Join(DefaultLndDir, lncfg.DefaultConfigFilename)
252

253
        defaultDataDir = filepath.Join(DefaultLndDir, defaultDataDirname)
254
        defaultLogDir  = filepath.Join(DefaultLndDir, defaultLogDirname)
255

256
        defaultTowerDir = filepath.Join(defaultDataDir, defaultTowerSubDirname)
257

258
        defaultTLSCertPath    = filepath.Join(DefaultLndDir, defaultTLSCertFilename)
259
        defaultTLSKeyPath     = filepath.Join(DefaultLndDir, defaultTLSKeyFilename)
260
        defaultLetsEncryptDir = filepath.Join(DefaultLndDir, defaultLetsEncryptDirname)
261

262
        defaultBtcdDir         = btcutil.AppDataDir(btcdBackendName, false)
263
        defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert")
264

265
        defaultBitcoindDir = btcutil.AppDataDir(BitcoinChainName, false)
266

267
        defaultTorSOCKS   = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
268
        defaultTorDNS     = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
269
        defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
270

271
        // bitcoindEsimateModes defines all the legal values for bitcoind's
272
        // estimatesmartfee RPC call.
273
        defaultBitcoindEstimateMode = "CONSERVATIVE"
274
        bitcoindEstimateModes       = [2]string{"ECONOMICAL", defaultBitcoindEstimateMode}
275

276
        defaultPrunedNodeMaxPeers = 4
277
)
278

279
// Config defines the configuration options for lnd.
280
//
281
// See LoadConfig for further details regarding the configuration
282
// loading+parsing process.
283
//
284
//nolint:lll
285
type Config struct {
286
        ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
287

288
        LndDir       string `long:"lnddir" description:"The base directory that contains lnd's data, logs, configuration file, etc. This option overwrites all other directory options."`
289
        ConfigFile   string `short:"C" long:"configfile" description:"Path to configuration file"`
290
        DataDir      string `short:"b" long:"datadir" description:"The directory to store lnd's data within"`
291
        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."`
292

293
        TLSCertPath        string        `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"`
294
        TLSKeyPath         string        `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"`
295
        TLSExtraIPs        []string      `long:"tlsextraip" description:"Adds an extra ip to the generated certificate"`
296
        TLSExtraDomains    []string      `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate"`
297
        TLSAutoRefresh     bool          `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed"`
298
        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"`
299
        TLSCertDuration    time.Duration `long:"tlscertduration" description:"The duration for which the auto-generated TLS certificate will be valid for"`
300
        TLSEncryptKey      bool          `long:"tlsencryptkey" description:"Automatically encrypts the TLS private key and generates ephemeral TLS key pairs when the wallet is locked or not initialized"`
301

302
        NoMacaroons     bool          `long:"no-macaroons" description:"Disable macaroon authentication, can only be used if server is not listening on a public interface."`
303
        AdminMacPath    string        `long:"adminmacaroonpath" description:"Path to write the admin macaroon for lnd's RPC and REST services if it doesn't exist"`
304
        ReadMacPath     string        `long:"readonlymacaroonpath" description:"Path to write the read-only macaroon for lnd's RPC and REST services if it doesn't exist"`
305
        InvoiceMacPath  string        `long:"invoicemacaroonpath" description:"Path to the invoice-only macaroon for lnd's RPC and REST services if it doesn't exist"`
306
        LogDir          string        `long:"logdir" description:"Directory to log output."`
307
        MaxLogFiles     int           `long:"maxlogfiles" description:"Maximum logfiles to keep (0 for no rotation)"`
308
        MaxLogFileSize  int           `long:"maxlogfilesize" description:"Maximum logfile size in MB"`
309
        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"`
310

311
        LetsEncryptDir    string `long:"letsencryptdir" description:"The directory to store Let's Encrypt certificates within"`
312
        LetsEncryptListen string `long:"letsencryptlisten" description:"The IP:port on which lnd will listen for Let's Encrypt challenges. Let's Encrypt will always try to contact on port 80. Often non-root processes are not allowed to bind to ports lower than 1024. This configuration option allows a different port to be used, but must be used in combination with port forwarding from port 80. This configuration can also be used to specify another IP address to listen on, for example an IPv6 address."`
313
        LetsEncryptDomain string `long:"letsencryptdomain" description:"Request a Let's Encrypt certificate for this domain. Note that the certificate is only requested and stored when the first rpc connection comes in."`
314

315
        // We'll parse these 'raw' string arguments into real net.Addrs in the
316
        // loadConfig function. We need to expose the 'raw' strings so the
317
        // command line library can access them.
318
        // Only the parsed net.Addrs should be used!
319
        RawRPCListeners   []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
320
        RawRESTListeners  []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
321
        RawListeners      []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
322
        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"`
323
        ExternalHosts     []string `long:"externalhosts" description:"Add a hostname:port that should be periodically resolved to announce IPs for. If a port is not specified, the default (9735) will be used."`
324
        RPCListeners      []net.Addr
325
        RESTListeners     []net.Addr
326
        RestCORS          []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`
327
        Listeners         []net.Addr
328
        ExternalIPs       []net.Addr
329
        DisableListen     bool          `long:"nolisten" description:"Disable listening for incoming peer connections"`
330
        DisableRest       bool          `long:"norest" description:"Disable REST API"`
331
        DisableRestTLS    bool          `long:"no-rest-tls" description:"Disable TLS for REST connections"`
332
        WSPingInterval    time.Duration `long:"ws-ping-interval" description:"The ping interval for REST based WebSocket connections, set to 0 to disable sending ping messages from the server side"`
333
        WSPongWait        time.Duration `long:"ws-pong-wait" description:"The time we wait for a pong response message on REST based WebSocket connections before the connection is closed as inactive"`
334
        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"`
335
        AddPeers          []string      `long:"addpeer" description:"Specify peers to connect to first"`
336
        MinBackoff        time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
337
        MaxBackoff        time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
338
        ConnectionTimeout time.Duration `long:"connectiontimeout" description:"The timeout value for network connections. Valid time units are {ms, s, m, h}."`
339

340
        DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify <global-level>,<subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems -- Use show to list available subsystems"`
341

342
        CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
343

344
        Profile string `long:"profile" description:"Enable HTTP profiling on either a port or host:port"`
345

346
        BlockingProfile int `long:"blockingprofile" description:"Used to enable a blocking profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every blocking event, and 0 including no events."`
347
        MutexProfile    int `long:"mutexprofile" description:"Used to Enable a mutex profile to be served on the profiling port. This takes a value from 0 to 1, with 1 including every mutex event, and 0 including no events."`
348

349
        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" hidden:"true"`
350
        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."`
351
        MaxPendingChannels int    `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
352
        BackupFilePath     string `long:"backupfilepath" description:"The target location of the channel backup file"`
353

354
        FeeURL string `long:"feeurl" description:"DEPRECATED: Use 'fee.url' option. Optional URL for external fee estimation. If no URL is specified, the method for fee estimation will depend on the chosen backend and network. Must be set for neutrino on mainnet." hidden:"true"`
355

356
        Bitcoin      *lncfg.Chain    `group:"Bitcoin" namespace:"bitcoin"`
357
        BtcdMode     *lncfg.Btcd     `group:"btcd" namespace:"btcd"`
358
        BitcoindMode *lncfg.Bitcoind `group:"bitcoind" namespace:"bitcoind"`
359
        NeutrinoMode *lncfg.Neutrino `group:"neutrino" namespace:"neutrino"`
360

361
        BlockCacheSize uint64 `long:"blockcachesize" description:"The maximum capacity of the block cache"`
362

363
        Autopilot *lncfg.AutoPilot `group:"Autopilot" namespace:"autopilot"`
364

365
        Tor *lncfg.Tor `group:"Tor" namespace:"tor"`
366

367
        SubRPCServers *subRPCServerConfigs `group:"subrpc"`
368

369
        Hodl *hodl.Config `group:"hodl" namespace:"hodl"`
370

371
        NoNetBootstrap bool `long:"nobootstrap" description:"If true, then automatic network bootstrapping will not be attempted."`
372

373
        NoSeedBackup             bool   `long:"noseedbackup" description:"If true, NO SEED WILL BE EXPOSED -- EVER, AND THE WALLET WILL BE ENCRYPTED USING THE DEFAULT PASSPHRASE. THIS FLAG IS ONLY FOR TESTING AND SHOULD NEVER BE USED ON MAINNET."`
374
        WalletUnlockPasswordFile string `long:"wallet-unlock-password-file" description:"The full path to a file (or pipe/device) that contains the password for unlocking the wallet; if set, no unlocking through RPC is possible and lnd will exit if no wallet exists or the password is incorrect; if wallet-unlock-allow-create is also set then lnd will ignore this flag if no wallet exists and allow a wallet to be created through RPC."`
375
        WalletUnlockAllowCreate  bool   `long:"wallet-unlock-allow-create" description:"Don't fail with an error if wallet-unlock-password-file is set but no wallet exists yet."`
376

377
        ResetWalletTransactions bool `long:"reset-wallet-transactions" description:"Removes all transaction history from the on-chain wallet on startup, forcing a full chain rescan starting at the wallet's birthday. Implements the same functionality as btcwallet's dropwtxmgr command. Should be set to false after successful execution to avoid rescanning on every restart of lnd."`
378

379
        CoinSelectionStrategy string `long:"coin-selection-strategy" description:"The strategy to use for selecting coins for wallet transactions." choice:"largest" choice:"random"`
380

381
        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."`
382
        TrickleDelay                  int           `long:"trickledelay" description:"Time in milliseconds between each release of announcements to the network"`
383
        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 re-enable or cancel a pending disables of the peer's channels on the network."`
384
        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."`
385
        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."`
386
        HeightHintCacheQueryDisable   bool          `long:"height-hint-cache-query-disable" description:"Disable queries from the height-hint cache to try to recover channels stuck in the pending close state. Disabling height hint queries may cause longer chain rescans, resulting in a performance hit. Unset this after channels are unstuck so you can get better performance again."`
387
        Alias                         string        `long:"alias" description:"The node alias. Used as a moniker by peers and intelligence services"`
388
        Color                         string        `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"`
389
        MinChanSize                   int64         `long:"minchansize" description:"The smallest channel size (in satoshis) that we should accept. Incoming channels smaller than this will be rejected"`
390
        MaxChanSize                   int64         `long:"maxchansize" description:"The largest channel size (in satoshis) that we should accept. Incoming channels larger than this will be rejected"`
391
        CoopCloseTargetConfs          uint32        `long:"coop-close-target-confs" description:"The target number of blocks that a cooperative channel close transaction should confirm in. This is used to estimate the fee to use as the lower bound during fee negotiation for the channel closure."`
392

393
        ChannelCommitInterval time.Duration `long:"channel-commit-interval" description:"The maximum time that is allowed to pass between receiving a channel state update and signing the next commitment. Setting this to a longer duration allows for more efficient channel operations at the cost of latency."`
394

395
        PendingCommitInterval time.Duration `long:"pending-commit-interval" description:"The maximum time that is allowed to pass while waiting for the remote party to revoke a locally initiated commitment state. Setting this to a longer duration if a slow response is expected from the remote party or large number of payments are attempted at the same time."`
396

397
        ChannelCommitBatchSize uint32 `long:"channel-commit-batch-size" description:"The maximum number of channel state updates that is accumulated before signing a new commitment."`
398

399
        KeepFailedPaymentAttempts bool `long:"keep-failed-payment-attempts" description:"Keeps persistent record of all failed payment attempts for successfully settled payments."`
400

401
        StoreFinalHtlcResolutions bool `long:"store-final-htlc-resolutions" description:"Persistently store the final resolution of incoming htlcs."`
402

403
        DefaultRemoteMaxHtlcs uint16 `long:"default-remote-max-htlcs" description:"The default max_htlc applied when opening or accepting channels. This value limits the number of concurrent HTLCs that the remote party can add to the commitment. The maximum possible value is 483."`
404

405
        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."`
406
        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."`
407

408
        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."`
409

410
        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."`
411

412
        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."`
413

414
        AcceptPositiveInboundFees bool `long:"accept-positive-inbound-fees" description:"If true, lnd will also allow setting positive inbound fees. By default, lnd only allows to set negative inbound fees (an inbound \"discount\") to remain backwards compatible with senders whose implementations do not yet support inbound fees."`
415

416
        // RequireInterceptor determines whether the HTLC interceptor is
417
        // registered regardless of whether the RPC is called or not.
418
        RequireInterceptor bool `long:"requireinterceptor" description:"Whether to always intercept HTLCs, even if no stream is attached"`
419

420
        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"`
421

422
        MaxOutgoingCltvExpiry uint32 `long:"max-cltv-expiry" description:"The maximum number of blocks funds could be locked up for when forwarding payments."`
423

424
        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]."`
425

426
        MaxCommitFeeRateAnchors uint64 `long:"max-commit-fee-rate-anchors" description:"The maximum fee rate in sat/vbyte that will be used for commitments of channels of the anchors type. Must be large enough to ensure transaction propagation"`
427

428
        DryRunMigration bool `long:"dry-run-migration" description:"If true, lnd will abort committing a migration if it would otherwise have been successful. This leaves the database unmodified, and still compatible with the previously active version of lnd."`
429

430
        net tor.Net
431

432
        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."`
433

434
        AcceptKeySend bool `long:"accept-keysend" description:"If true, spontaneous payments through keysend will be accepted. [experimental]"`
435

436
        AcceptAMP bool `long:"accept-amp" description:"If true, spontaneous payments via AMP will be accepted."`
437

438
        KeysendHoldTime time.Duration `long:"keysend-hold-time" description:"If non-zero, keysend payments are accepted but not immediately settled. If the payment isn't settled manually after the specified time, it is canceled automatically. [experimental]"`
439

440
        GcCanceledInvoicesOnStartup bool `long:"gc-canceled-invoices-on-startup" description:"If true, we'll attempt to garbage collect canceled invoices upon start."`
441

442
        GcCanceledInvoicesOnTheFly bool `long:"gc-canceled-invoices-on-the-fly" description:"If true, we'll delete newly canceled invoices on the fly."`
443

444
        MaxFeeExposure uint64 `long:"dust-threshold" description:"Sets the max fee exposure in satoshis for a channel after which HTLC's will be failed."`
445

446
        Fee *lncfg.Fee `group:"fee" namespace:"fee"`
447

448
        Invoices *lncfg.Invoices `group:"invoices" namespace:"invoices"`
449

450
        Routing *lncfg.Routing `group:"routing" namespace:"routing"`
451

452
        Gossip *lncfg.Gossip `group:"gossip" namespace:"gossip"`
453

454
        Workers *lncfg.Workers `group:"workers" namespace:"workers"`
455

456
        Caches *lncfg.Caches `group:"caches" namespace:"caches"`
457

458
        Prometheus lncfg.Prometheus `group:"prometheus" namespace:"prometheus"`
459

460
        WtClient *lncfg.WtClient `group:"wtclient" namespace:"wtclient"`
461

462
        Watchtower *lncfg.Watchtower `group:"watchtower" namespace:"watchtower"`
463

464
        ProtocolOptions *lncfg.ProtocolOptions `group:"protocol" namespace:"protocol"`
465

466
        AllowCircularRoute bool `long:"allow-circular-route" description:"If true, our node will allow htlc forwards that arrive and depart on the same channel."`
467

468
        HealthChecks *lncfg.HealthCheckConfig `group:"healthcheck" namespace:"healthcheck"`
469

470
        DB *lncfg.DB `group:"db" namespace:"db"`
471

472
        Cluster *lncfg.Cluster `group:"cluster" namespace:"cluster"`
473

474
        RPCMiddleware *lncfg.RPCMiddleware `group:"rpcmiddleware" namespace:"rpcmiddleware"`
475

476
        RemoteSigner *lncfg.RemoteSigner `group:"remotesigner" namespace:"remotesigner"`
477

478
        Sweeper *lncfg.Sweeper `group:"sweeper" namespace:"sweeper"`
479

480
        Htlcswitch *lncfg.Htlcswitch `group:"htlcswitch" namespace:"htlcswitch"`
481

482
        GRPC *GRPCConfig `group:"grpc" namespace:"grpc"`
483

484
        // LogWriter is the root logger that all of the daemon's subloggers are
485
        // hooked up to.
486
        LogWriter *build.RotatingLogWriter
487

488
        // networkDir is the path to the directory of the currently active
489
        // network. This path will hold the files related to each different
490
        // network.
491
        networkDir string
492

493
        // ActiveNetParams contains parameters of the target chain.
494
        ActiveNetParams chainreg.BitcoinNetParams
495

496
        // Estimator is used to estimate routing probabilities.
497
        Estimator routing.Estimator
498

499
        // Dev specifies configs used for integration tests, which is always
500
        // empty if not built with `integration` flag.
501
        Dev *lncfg.DevConfig `group:"dev" namespace:"dev"`
502

503
        // HTTPHeaderTimeout is the maximum duration that the server will wait
504
        // before timing out reading the headers of an HTTP request.
505
        HTTPHeaderTimeout time.Duration `long:"http-header-timeout" description:"The maximum duration that the server will wait before timing out reading the headers of an HTTP request."`
506
}
507

508
// GRPCConfig holds the configuration options for the gRPC server.
509
// See https://github.com/grpc/grpc-go/blob/v1.41.0/keepalive/keepalive.go#L50
510
// for more details. Any value of 0 means we use the gRPC internal default
511
// values.
512
//
513
//nolint:lll
514
type GRPCConfig struct {
515
        // ServerPingTime is a duration for the amount of time of no activity
516
        // after which the server pings the client to see if the transport is
517
        // still alive. If set below 1s, a minimum value of 1s will be used
518
        // instead.
519
        ServerPingTime time.Duration `long:"server-ping-time" description:"How long the server waits on a gRPC stream with no activity before pinging the client."`
520

521
        // ServerPingTimeout is the duration the server waits after having
522
        // pinged for keepalive check, and if no activity is seen even after
523
        // that the connection is closed.
524
        ServerPingTimeout time.Duration `long:"server-ping-timeout" description:"How long the server waits for the response from the client for the keepalive ping response."`
525

526
        // ClientPingMinWait is the minimum amount of time a client should wait
527
        // before sending a keepalive ping.
528
        ClientPingMinWait time.Duration `long:"client-ping-min-wait" description:"The minimum amount of time the client should wait before sending a keepalive ping."`
529

530
        // ClientAllowPingWithoutStream specifies whether pings from the client
531
        // are allowed even if there are no active gRPC streams. This might be
532
        // useful to keep the underlying HTTP/2 connection open for future
533
        // requests.
534
        ClientAllowPingWithoutStream bool `long:"client-allow-ping-without-stream" description:"If true, the server allows keepalive pings from the client even when there are no active gRPC streams. This might be useful to keep the underlying HTTP/2 connection open for future requests."`
535
}
536

537
// DefaultConfig returns all default values for the Config struct.
538
//
539
//nolint:lll
540
func DefaultConfig() Config {
4✔
541
        return Config{
4✔
542
                LndDir:            DefaultLndDir,
4✔
543
                ConfigFile:        DefaultConfigFile,
4✔
544
                DataDir:           defaultDataDir,
4✔
545
                DebugLevel:        defaultLogLevel,
4✔
546
                TLSCertPath:       defaultTLSCertPath,
4✔
547
                TLSKeyPath:        defaultTLSKeyPath,
4✔
548
                TLSCertDuration:   defaultTLSCertDuration,
4✔
549
                LetsEncryptDir:    defaultLetsEncryptDir,
4✔
550
                LetsEncryptListen: defaultLetsEncryptListen,
4✔
551
                LogDir:            defaultLogDir,
4✔
552
                MaxLogFiles:       defaultMaxLogFiles,
4✔
553
                MaxLogFileSize:    defaultMaxLogFileSize,
4✔
554
                AcceptorTimeout:   defaultAcceptorTimeout,
4✔
555
                WSPingInterval:    lnrpc.DefaultPingInterval,
4✔
556
                WSPongWait:        lnrpc.DefaultPongWait,
4✔
557
                Bitcoin: &lncfg.Chain{
4✔
558
                        MinHTLCIn:     chainreg.DefaultBitcoinMinHTLCInMSat,
4✔
559
                        MinHTLCOut:    chainreg.DefaultBitcoinMinHTLCOutMSat,
4✔
560
                        BaseFee:       chainreg.DefaultBitcoinBaseFeeMSat,
4✔
561
                        FeeRate:       chainreg.DefaultBitcoinFeeRate,
4✔
562
                        TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta,
4✔
563
                        MaxLocalDelay: defaultMaxLocalCSVDelay,
4✔
564
                        Node:          btcdBackendName,
4✔
565
                },
4✔
566
                BtcdMode: &lncfg.Btcd{
4✔
567
                        Dir:     defaultBtcdDir,
4✔
568
                        RPCHost: defaultRPCHost,
4✔
569
                        RPCCert: defaultBtcdRPCCertFile,
4✔
570
                },
4✔
571
                BitcoindMode: &lncfg.Bitcoind{
4✔
572
                        Dir:                defaultBitcoindDir,
4✔
573
                        RPCHost:            defaultRPCHost,
4✔
574
                        EstimateMode:       defaultBitcoindEstimateMode,
4✔
575
                        PrunedNodeMaxPeers: defaultPrunedNodeMaxPeers,
4✔
576
                        ZMQReadDeadline:    defaultZMQReadDeadline,
4✔
577
                },
4✔
578
                NeutrinoMode: &lncfg.Neutrino{
4✔
579
                        UserAgentName:    neutrino.UserAgentName,
4✔
580
                        UserAgentVersion: neutrino.UserAgentVersion,
4✔
581
                },
4✔
582
                BlockCacheSize:     defaultBlockCacheSize,
4✔
583
                MaxPendingChannels: lncfg.DefaultMaxPendingChannels,
4✔
584
                NoSeedBackup:       defaultNoSeedBackup,
4✔
585
                MinBackoff:         defaultMinBackoff,
4✔
586
                MaxBackoff:         defaultMaxBackoff,
4✔
587
                ConnectionTimeout:  tor.DefaultConnTimeout,
4✔
588

4✔
589
                Fee: &lncfg.Fee{
4✔
590
                        MinUpdateTimeout: lncfg.DefaultMinUpdateTimeout,
4✔
591
                        MaxUpdateTimeout: lncfg.DefaultMaxUpdateTimeout,
4✔
592
                },
4✔
593

4✔
594
                SubRPCServers: &subRPCServerConfigs{
4✔
595
                        SignRPC:   &signrpc.Config{},
4✔
596
                        RouterRPC: routerrpc.DefaultConfig(),
4✔
597
                        PeersRPC:  &peersrpc.Config{},
4✔
598
                },
4✔
599
                Autopilot: &lncfg.AutoPilot{
4✔
600
                        MaxChannels:    5,
4✔
601
                        Allocation:     0.6,
4✔
602
                        MinChannelSize: int64(funding.MinChanFundingSize),
4✔
603
                        MaxChannelSize: int64(MaxFundingAmount),
4✔
604
                        MinConfs:       1,
4✔
605
                        ConfTarget:     autopilot.DefaultConfTarget,
4✔
606
                        Heuristic: map[string]float64{
4✔
607
                                "top_centrality": 1.0,
4✔
608
                        },
4✔
609
                },
4✔
610
                PaymentsExpirationGracePeriod: defaultPaymentsExpirationGracePeriod,
4✔
611
                TrickleDelay:                  defaultTrickleDelay,
4✔
612
                ChanStatusSampleInterval:      defaultChanStatusSampleInterval,
4✔
613
                ChanEnableTimeout:             defaultChanEnableTimeout,
4✔
614
                ChanDisableTimeout:            defaultChanDisableTimeout,
4✔
615
                HeightHintCacheQueryDisable:   defaultHeightHintCacheQueryDisable,
4✔
616
                Alias:                         defaultAlias,
4✔
617
                Color:                         defaultColor,
4✔
618
                MinChanSize:                   int64(funding.MinChanFundingSize),
4✔
619
                MaxChanSize:                   int64(0),
4✔
620
                CoopCloseTargetConfs:          defaultCoopCloseTargetConfs,
4✔
621
                DefaultRemoteMaxHtlcs:         defaultRemoteMaxHtlcs,
4✔
622
                NumGraphSyncPeers:             defaultMinPeers,
4✔
623
                HistoricalSyncInterval:        discovery.DefaultHistoricalSyncInterval,
4✔
624
                Tor: &lncfg.Tor{
4✔
625
                        SOCKS:   defaultTorSOCKS,
4✔
626
                        DNS:     defaultTorDNS,
4✔
627
                        Control: defaultTorControl,
4✔
628
                },
4✔
629
                net: &tor.ClearNet{},
4✔
630
                Workers: &lncfg.Workers{
4✔
631
                        Read:  lncfg.DefaultReadWorkers,
4✔
632
                        Write: lncfg.DefaultWriteWorkers,
4✔
633
                        Sig:   lncfg.DefaultSigWorkers,
4✔
634
                },
4✔
635
                Caches: &lncfg.Caches{
4✔
636
                        RejectCacheSize:  channeldb.DefaultRejectCacheSize,
4✔
637
                        ChannelCacheSize: channeldb.DefaultChannelCacheSize,
4✔
638
                },
4✔
639
                Prometheus: lncfg.DefaultPrometheus(),
4✔
640
                Watchtower: lncfg.DefaultWatchtowerCfg(defaultTowerDir),
4✔
641
                HealthChecks: &lncfg.HealthCheckConfig{
4✔
642
                        ChainCheck: &lncfg.CheckConfig{
4✔
643
                                Interval: defaultChainInterval,
4✔
644
                                Timeout:  defaultChainTimeout,
4✔
645
                                Attempts: defaultChainAttempts,
4✔
646
                                Backoff:  defaultChainBackoff,
4✔
647
                        },
4✔
648
                        DiskCheck: &lncfg.DiskCheckConfig{
4✔
649
                                RequiredRemaining: defaultRequiredDisk,
4✔
650
                                CheckConfig: &lncfg.CheckConfig{
4✔
651
                                        Interval: defaultDiskInterval,
4✔
652
                                        Attempts: defaultDiskAttempts,
4✔
653
                                        Timeout:  defaultDiskTimeout,
4✔
654
                                        Backoff:  defaultDiskBackoff,
4✔
655
                                },
4✔
656
                        },
4✔
657
                        TLSCheck: &lncfg.CheckConfig{
4✔
658
                                Interval: defaultTLSInterval,
4✔
659
                                Timeout:  defaultTLSTimeout,
4✔
660
                                Attempts: defaultTLSAttempts,
4✔
661
                                Backoff:  defaultTLSBackoff,
4✔
662
                        },
4✔
663
                        TorConnection: &lncfg.CheckConfig{
4✔
664
                                Interval: defaultTCInterval,
4✔
665
                                Timeout:  defaultTCTimeout,
4✔
666
                                Attempts: defaultTCAttempts,
4✔
667
                                Backoff:  defaultTCBackoff,
4✔
668
                        },
4✔
669
                        RemoteSigner: &lncfg.CheckConfig{
4✔
670
                                Interval: defaultRSInterval,
4✔
671
                                Timeout:  defaultRSTimeout,
4✔
672
                                Attempts: defaultRSAttempts,
4✔
673
                                Backoff:  defaultRSBackoff,
4✔
674
                        },
4✔
675
                },
4✔
676
                Gossip: &lncfg.Gossip{
4✔
677
                        MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst,
4✔
678
                        ChannelUpdateInterval: discovery.DefaultChannelUpdateInterval,
4✔
679
                        SubBatchDelay:         discovery.DefaultSubBatchDelay,
4✔
680
                },
4✔
681
                Invoices: &lncfg.Invoices{
4✔
682
                        HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
4✔
683
                },
4✔
684
                Routing: &lncfg.Routing{
4✔
685
                        BlindedPaths: lncfg.BlindedPaths{
4✔
686
                                MinNumRealHops:           lncfg.DefaultMinNumRealBlindedPathHops,
4✔
687
                                NumHops:                  lncfg.DefaultNumBlindedPathHops,
4✔
688
                                MaxNumPaths:              lncfg.DefaultMaxNumBlindedPaths,
4✔
689
                                PolicyIncreaseMultiplier: lncfg.DefaultBlindedPathPolicyIncreaseMultiplier,
4✔
690
                                PolicyDecreaseMultiplier: lncfg.DefaultBlindedPathPolicyDecreaseMultiplier,
4✔
691
                        },
4✔
692
                },
4✔
693
                MaxOutgoingCltvExpiry:     htlcswitch.DefaultMaxOutgoingCltvExpiry,
4✔
694
                MaxChannelFeeAllocation:   htlcswitch.DefaultMaxLinkFeeAllocation,
4✔
695
                MaxCommitFeeRateAnchors:   lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
4✔
696
                MaxFeeExposure:            uint64(htlcswitch.DefaultMaxFeeExposure.ToSatoshis()),
4✔
697
                LogWriter:                 build.NewRotatingLogWriter(),
4✔
698
                DB:                        lncfg.DefaultDB(),
4✔
699
                Cluster:                   lncfg.DefaultCluster(),
4✔
700
                RPCMiddleware:             lncfg.DefaultRPCMiddleware(),
4✔
701
                ActiveNetParams:           chainreg.BitcoinTestNetParams,
4✔
702
                ChannelCommitInterval:     defaultChannelCommitInterval,
4✔
703
                PendingCommitInterval:     defaultPendingCommitInterval,
4✔
704
                ChannelCommitBatchSize:    defaultChannelCommitBatchSize,
4✔
705
                CoinSelectionStrategy:     defaultCoinSelectionStrategy,
4✔
706
                KeepFailedPaymentAttempts: defaultKeepFailedPaymentAttempts,
4✔
707
                RemoteSigner: &lncfg.RemoteSigner{
4✔
708
                        Timeout: lncfg.DefaultRemoteSignerRPCTimeout,
4✔
709
                },
4✔
710
                Sweeper: lncfg.DefaultSweeperConfig(),
4✔
711
                Htlcswitch: &lncfg.Htlcswitch{
4✔
712
                        MailboxDeliveryTimeout: htlcswitch.DefaultMailboxDeliveryTimeout,
4✔
713
                },
4✔
714
                GRPC: &GRPCConfig{
4✔
715
                        ServerPingTime:    defaultGrpcServerPingTime,
4✔
716
                        ServerPingTimeout: defaultGrpcServerPingTimeout,
4✔
717
                        ClientPingMinWait: defaultGrpcClientPingMinWait,
4✔
718
                },
4✔
719
                WtClient:          lncfg.DefaultWtClientCfg(),
4✔
720
                HTTPHeaderTimeout: DefaultHTTPHeaderTimeout,
4✔
721
        }
4✔
722
}
4✔
723

724
// LoadConfig initializes and parses the config using a config file and command
725
// line options.
726
//
727
// The configuration proceeds as follows:
728
//  1. Start with a default config with sane settings
729
//  2. Pre-parse the command line to check for an alternative config file
730
//  3. Load configuration file overwriting defaults with any specified options
731
//  4. Parse CLI options and overwrite/add any specified options
732
func LoadConfig(interceptor signal.Interceptor) (*Config, error) {
3✔
733
        // Pre-parse the command line options to pick up an alternative config
3✔
734
        // file.
3✔
735
        preCfg := DefaultConfig()
3✔
736
        if _, err := flags.Parse(&preCfg); err != nil {
3✔
737
                return nil, err
×
738
        }
×
739

740
        // Show the version and exit if the version flag was specified.
741
        appName := filepath.Base(os.Args[0])
3✔
742
        appName = strings.TrimSuffix(appName, filepath.Ext(appName))
3✔
743
        usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
3✔
744
        if preCfg.ShowVersion {
3✔
745
                fmt.Println(appName, "version", build.Version(),
×
746
                        "commit="+build.Commit)
×
747
                os.Exit(0)
×
748
        }
×
749

750
        // If the config file path has not been modified by the user, then we'll
751
        // use the default config file path. However, if the user has modified
752
        // their lnddir, then we should assume they intend to use the config
753
        // file within it.
754
        configFileDir := CleanAndExpandPath(preCfg.LndDir)
3✔
755
        configFilePath := CleanAndExpandPath(preCfg.ConfigFile)
3✔
756
        switch {
3✔
757
        // User specified --lnddir but no --configfile. Update the config file
758
        // path to the lnd config directory, but don't require it to exist.
759
        case configFileDir != DefaultLndDir &&
760
                configFilePath == DefaultConfigFile:
3✔
761

3✔
762
                configFilePath = filepath.Join(
3✔
763
                        configFileDir, lncfg.DefaultConfigFilename,
3✔
764
                )
3✔
765

766
        // User did specify an explicit --configfile, so we check that it does
767
        // exist under that path to avoid surprises.
768
        case configFilePath != DefaultConfigFile:
×
769
                if !lnrpc.FileExists(configFilePath) {
×
770
                        return nil, fmt.Errorf("specified config file does "+
×
771
                                "not exist in %s", configFilePath)
×
772
                }
×
773
        }
774

775
        // Next, load any additional configuration options from the file.
776
        var configFileError error
3✔
777
        cfg := preCfg
3✔
778
        fileParser := flags.NewParser(&cfg, flags.Default)
3✔
779
        err := flags.NewIniParser(fileParser).ParseFile(configFilePath)
3✔
780
        if err != nil {
6✔
781
                // If it's a parsing related error, then we'll return
3✔
782
                // immediately, otherwise we can proceed as possibly the config
3✔
783
                // file doesn't exist which is OK.
3✔
784
                if lnutils.ErrorAs[*flags.IniError](err) ||
3✔
785
                        lnutils.ErrorAs[*flags.Error](err) {
3✔
786

×
787
                        return nil, err
×
788
                }
×
789

790
                configFileError = err
3✔
791
        }
792

793
        // Finally, parse the remaining command line options again to ensure
794
        // they take precedence.
795
        flagParser := flags.NewParser(&cfg, flags.Default)
3✔
796
        if _, err := flagParser.Parse(); err != nil {
3✔
797
                return nil, err
×
798
        }
×
799

800
        // Make sure everything we just loaded makes sense.
801
        cleanCfg, err := ValidateConfig(
3✔
802
                cfg, interceptor, fileParser, flagParser,
3✔
803
        )
3✔
804
        if usageErr, ok := err.(*usageError); ok {
3✔
805
                // The logging system might not yet be initialized, so we also
×
806
                // write to stderr to make sure the error appears somewhere.
×
807
                _, _ = fmt.Fprintln(os.Stderr, usageMessage)
×
808
                ltndLog.Warnf("Incorrect usage: %v", usageMessage)
×
809

×
810
                // The log subsystem might not yet be initialized. But we still
×
811
                // try to log the error there since some packaging solutions
×
812
                // might only look at the log and not stdout/stderr.
×
813
                ltndLog.Warnf("Error validating config: %v", usageErr.err)
×
814

×
815
                return nil, usageErr.err
×
816
        }
×
817
        if err != nil {
3✔
818
                // The log subsystem might not yet be initialized. But we still
×
819
                // try to log the error there since some packaging solutions
×
820
                // might only look at the log and not stdout/stderr.
×
821
                ltndLog.Warnf("Error validating config: %v", err)
×
822

×
823
                return nil, err
×
824
        }
×
825

826
        // Warn about missing config file only after all other configuration is
827
        // done. This prevents the warning on help messages and invalid options.
828
        // Note this should go directly before the return.
829
        if configFileError != nil {
6✔
830
                ltndLog.Warnf("%v", configFileError)
3✔
831
        }
3✔
832

833
        // Finally, log warnings for deprecated config options if they are set.
834
        logWarningsForDeprecation(*cleanCfg)
3✔
835

3✔
836
        return cleanCfg, nil
3✔
837
}
838

839
// usageError is an error type that signals a problem with the supplied flags.
840
type usageError struct {
841
        err error
842
}
843

844
// Error returns the error string.
845
//
846
// NOTE: This is part of the error interface.
847
func (u *usageError) Error() string {
×
848
        return u.err.Error()
×
849
}
×
850

851
// ValidateConfig check the given configuration to be sane. This makes sure no
852
// illegal values or combination of values are set. All file system paths are
853
// normalized. The cleaned up config is returned on success.
854
func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
855
        flagParser *flags.Parser) (*Config, error) {
3✔
856

3✔
857
        // If the provided lnd directory is not the default, we'll modify the
3✔
858
        // path to all of the files and directories that will live within it.
3✔
859
        lndDir := CleanAndExpandPath(cfg.LndDir)
3✔
860
        if lndDir != DefaultLndDir {
6✔
861
                cfg.DataDir = filepath.Join(lndDir, defaultDataDirname)
3✔
862
                cfg.LetsEncryptDir = filepath.Join(
3✔
863
                        lndDir, defaultLetsEncryptDirname,
3✔
864
                )
3✔
865
                cfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
3✔
866
                cfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename)
3✔
867
                cfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
3✔
868

3✔
869
                // If the watchtower's directory is set to the default, i.e. the
3✔
870
                // user has not requested a different location, we'll move the
3✔
871
                // location to be relative to the specified lnd directory.
3✔
872
                if cfg.Watchtower.TowerDir == defaultTowerDir {
6✔
873
                        cfg.Watchtower.TowerDir = filepath.Join(
3✔
874
                                cfg.DataDir, defaultTowerSubDirname,
3✔
875
                        )
3✔
876
                }
3✔
877
        }
878

879
        funcName := "ValidateConfig"
3✔
880
        mkErr := func(format string, args ...interface{}) error {
3✔
881
                return fmt.Errorf(funcName+": "+format, args...)
×
882
        }
×
883
        makeDirectory := func(dir string) error {
6✔
884
                err := os.MkdirAll(dir, 0700)
3✔
885
                if err != nil {
3✔
886
                        // Show a nicer error message if it's because a symlink
×
887
                        // is linked to a directory that does not exist
×
888
                        // (probably because it's not mounted).
×
889
                        if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
×
890
                                link, lerr := os.Readlink(e.Path)
×
891
                                if lerr == nil {
×
892
                                        str := "is symlink %s -> %s mounted?"
×
893
                                        err = fmt.Errorf(str, e.Path, link)
×
894
                                }
×
895
                        }
896

897
                        str := "Failed to create lnd directory '%s': %v"
×
898
                        return mkErr(str, dir, err)
×
899
                }
900

901
                return nil
3✔
902
        }
903

904
        // IsSet returns true if an option has been set in either the config
905
        // file or by a flag.
906
        isSet := func(field string) (bool, error) {
6✔
907
                fieldName, ok := reflect.TypeOf(Config{}).FieldByName(field)
3✔
908
                if !ok {
3✔
909
                        str := "could not find field %s"
×
910
                        return false, mkErr(str, field)
×
911
                }
×
912

913
                long, ok := fieldName.Tag.Lookup("long")
3✔
914
                if !ok {
3✔
915
                        str := "field %s does not have a long tag"
×
916
                        return false, mkErr(str, field)
×
917
                }
×
918

919
                // The user has the option to set the flag in either the config
920
                // file or as a command line flag. If any is set, we consider it
921
                // to be set, not applying any precedence rules here (since it
922
                // is a boolean the default is false anyway which would screw up
923
                // any precedence rules). Additionally, we need to also support
924
                // the use case where the config struct is embedded _within_
925
                // another struct with a prefix (as is the case with
926
                // lightning-terminal).
927
                fileOption := fileParser.FindOptionByLongName(long)
3✔
928
                fileOptionNested := fileParser.FindOptionByLongName(
3✔
929
                        "lnd." + long,
3✔
930
                )
3✔
931
                flagOption := flagParser.FindOptionByLongName(long)
3✔
932
                flagOptionNested := flagParser.FindOptionByLongName(
3✔
933
                        "lnd." + long,
3✔
934
                )
3✔
935

3✔
936
                return (fileOption != nil && fileOption.IsSet()) ||
3✔
937
                                (fileOptionNested != nil && fileOptionNested.IsSet()) ||
3✔
938
                                (flagOption != nil && flagOption.IsSet()) ||
3✔
939
                                (flagOptionNested != nil && flagOptionNested.IsSet()),
3✔
940
                        nil
3✔
941
        }
942

943
        // As soon as we're done parsing configuration options, ensure all paths
944
        // to directories and files are cleaned and expanded before attempting
945
        // to use them later on.
946
        cfg.DataDir = CleanAndExpandPath(cfg.DataDir)
3✔
947
        cfg.TLSCertPath = CleanAndExpandPath(cfg.TLSCertPath)
3✔
948
        cfg.TLSKeyPath = CleanAndExpandPath(cfg.TLSKeyPath)
3✔
949
        cfg.LetsEncryptDir = CleanAndExpandPath(cfg.LetsEncryptDir)
3✔
950
        cfg.AdminMacPath = CleanAndExpandPath(cfg.AdminMacPath)
3✔
951
        cfg.ReadMacPath = CleanAndExpandPath(cfg.ReadMacPath)
3✔
952
        cfg.InvoiceMacPath = CleanAndExpandPath(cfg.InvoiceMacPath)
3✔
953
        cfg.LogDir = CleanAndExpandPath(cfg.LogDir)
3✔
954
        cfg.BtcdMode.Dir = CleanAndExpandPath(cfg.BtcdMode.Dir)
3✔
955
        cfg.BitcoindMode.Dir = CleanAndExpandPath(cfg.BitcoindMode.Dir)
3✔
956
        cfg.BitcoindMode.ConfigPath = CleanAndExpandPath(
3✔
957
                cfg.BitcoindMode.ConfigPath,
3✔
958
        )
3✔
959
        cfg.BitcoindMode.RPCCookie = CleanAndExpandPath(cfg.BitcoindMode.RPCCookie)
3✔
960
        cfg.Tor.PrivateKeyPath = CleanAndExpandPath(cfg.Tor.PrivateKeyPath)
3✔
961
        cfg.Tor.WatchtowerKeyPath = CleanAndExpandPath(cfg.Tor.WatchtowerKeyPath)
3✔
962
        cfg.Watchtower.TowerDir = CleanAndExpandPath(cfg.Watchtower.TowerDir)
3✔
963
        cfg.BackupFilePath = CleanAndExpandPath(cfg.BackupFilePath)
3✔
964
        cfg.WalletUnlockPasswordFile = CleanAndExpandPath(
3✔
965
                cfg.WalletUnlockPasswordFile,
3✔
966
        )
3✔
967

3✔
968
        // Ensure that the user didn't attempt to specify negative values for
3✔
969
        // any of the autopilot params.
3✔
970
        if cfg.Autopilot.MaxChannels < 0 {
3✔
971
                str := "autopilot.maxchannels must be non-negative"
×
972

×
973
                return nil, mkErr(str)
×
974
        }
×
975
        if cfg.Autopilot.Allocation < 0 {
3✔
976
                str := "autopilot.allocation must be non-negative"
×
977

×
978
                return nil, mkErr(str)
×
979
        }
×
980
        if cfg.Autopilot.MinChannelSize < 0 {
3✔
981
                str := "autopilot.minchansize must be non-negative"
×
982

×
983
                return nil, mkErr(str)
×
984
        }
×
985
        if cfg.Autopilot.MaxChannelSize < 0 {
3✔
986
                str := "autopilot.maxchansize must be non-negative"
×
987

×
988
                return nil, mkErr(str)
×
989
        }
×
990
        if cfg.Autopilot.MinConfs < 0 {
3✔
991
                str := "autopilot.minconfs must be non-negative"
×
992

×
993
                return nil, mkErr(str)
×
994
        }
×
995
        if cfg.Autopilot.ConfTarget < 1 {
3✔
996
                str := "autopilot.conftarget must be positive"
×
997

×
998
                return nil, mkErr(str)
×
999
        }
×
1000

1001
        // Ensure that the specified values for the min and max channel size
1002
        // are within the bounds of the normal chan size constraints.
1003
        if cfg.Autopilot.MinChannelSize < int64(funding.MinChanFundingSize) {
3✔
1004
                cfg.Autopilot.MinChannelSize = int64(funding.MinChanFundingSize)
×
1005
        }
×
1006
        if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
3✔
1007
                cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
×
1008
        }
×
1009

1010
        if _, err := validateAtplCfg(cfg.Autopilot); err != nil {
3✔
1011
                return nil, mkErr("error validating autopilot: %v", err)
×
1012
        }
×
1013

1014
        // Ensure that --maxchansize is properly handled when set by user.
1015
        // For non-Wumbo channels this limit remains 16777215 satoshis by default
1016
        // as specified in BOLT-02. For wumbo channels this limit is 1,000,000,000.
1017
        // satoshis (10 BTC). Always enforce --maxchansize explicitly set by user.
1018
        // If unset (marked by 0 value), then enforce proper default.
1019
        if cfg.MaxChanSize == 0 {
6✔
1020
                if cfg.ProtocolOptions.Wumbo() {
6✔
1021
                        cfg.MaxChanSize = int64(funding.MaxBtcFundingAmountWumbo)
3✔
1022
                } else {
6✔
1023
                        cfg.MaxChanSize = int64(funding.MaxBtcFundingAmount)
3✔
1024
                }
3✔
1025
        }
1026

1027
        // Ensure that the user specified values for the min and max channel
1028
        // size make sense.
1029
        if cfg.MaxChanSize < cfg.MinChanSize {
3✔
1030
                return nil, mkErr("invalid channel size parameters: "+
×
1031
                        "max channel size %v, must be no less than min chan "+
×
1032
                        "size %v", cfg.MaxChanSize, cfg.MinChanSize,
×
1033
                )
×
1034
        }
×
1035

1036
        // Don't allow superfluous --maxchansize greater than
1037
        // BOLT 02 soft-limit for non-wumbo channel
1038
        if !cfg.ProtocolOptions.Wumbo() &&
3✔
1039
                cfg.MaxChanSize > int64(MaxFundingAmount) {
3✔
1040

×
1041
                return nil, mkErr("invalid channel size parameters: "+
×
1042
                        "maximum channel size %v is greater than maximum "+
×
1043
                        "non-wumbo channel size %v", cfg.MaxChanSize,
×
1044
                        MaxFundingAmount,
×
1045
                )
×
1046
        }
×
1047

1048
        // Ensure that the amount data for revoked commitment transactions is
1049
        // stored if the watchtower client is active.
1050
        if cfg.DB.NoRevLogAmtData && cfg.WtClient.Active {
3✔
1051
                return nil, mkErr("revocation log amount data must be stored " +
×
1052
                        "if the watchtower client is active")
×
1053
        }
×
1054

1055
        // Ensure a valid max channel fee allocation was set.
1056
        if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 {
3✔
1057
                return nil, mkErr("invalid max channel fee allocation: %v, "+
×
1058
                        "must be within (0, 1]", cfg.MaxChannelFeeAllocation)
×
1059
        }
×
1060

1061
        if cfg.MaxCommitFeeRateAnchors < 1 {
3✔
1062
                return nil, mkErr("invalid max commit fee rate anchors: %v, "+
×
1063
                        "must be at least 1 sat/vByte",
×
1064
                        cfg.MaxCommitFeeRateAnchors)
×
1065
        }
×
1066

1067
        // Validate the Tor config parameters.
1068
        socks, err := lncfg.ParseAddressString(
3✔
1069
                cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),
3✔
1070
                cfg.net.ResolveTCPAddr,
3✔
1071
        )
3✔
1072
        if err != nil {
3✔
1073
                return nil, err
×
1074
        }
×
1075
        cfg.Tor.SOCKS = socks.String()
3✔
1076

3✔
1077
        // We'll only attempt to normalize and resolve the DNS host if it hasn't
3✔
1078
        // changed, as it doesn't need to be done for the default.
3✔
1079
        if cfg.Tor.DNS != defaultTorDNS {
3✔
1080
                dns, err := lncfg.ParseAddressString(
×
1081
                        cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort),
×
1082
                        cfg.net.ResolveTCPAddr,
×
1083
                )
×
1084
                if err != nil {
×
1085
                        return nil, mkErr("error parsing tor dns: %v", err)
×
1086
                }
×
1087
                cfg.Tor.DNS = dns.String()
×
1088
        }
1089

1090
        control, err := lncfg.ParseAddressString(
3✔
1091
                cfg.Tor.Control, strconv.Itoa(defaultTorControlPort),
3✔
1092
                cfg.net.ResolveTCPAddr,
3✔
1093
        )
3✔
1094
        if err != nil {
3✔
1095
                return nil, mkErr("error parsing tor control address: %v", err)
×
1096
        }
×
1097
        cfg.Tor.Control = control.String()
3✔
1098

3✔
1099
        // Ensure that tor socks host:port is not equal to tor control
3✔
1100
        // host:port. This would lead to lnd not starting up properly.
3✔
1101
        if cfg.Tor.SOCKS == cfg.Tor.Control {
3✔
1102
                str := "tor.socks and tor.control can not us the same host:port"
×
1103

×
1104
                return nil, mkErr(str)
×
1105
        }
×
1106

1107
        switch {
3✔
1108
        case cfg.Tor.V2 && cfg.Tor.V3:
×
1109
                return nil, mkErr("either tor.v2 or tor.v3 can be set, " +
×
1110
                        "but not both")
×
1111
        case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3):
×
1112
                return nil, mkErr("listening must be enabled when enabling " +
×
1113
                        "inbound connections over Tor")
×
1114
        }
1115

1116
        if cfg.Tor.PrivateKeyPath == "" {
6✔
1117
                switch {
3✔
1118
                case cfg.Tor.V2:
×
1119
                        cfg.Tor.PrivateKeyPath = filepath.Join(
×
1120
                                lndDir, defaultTorV2PrivateKeyFilename,
×
1121
                        )
×
1122
                case cfg.Tor.V3:
×
1123
                        cfg.Tor.PrivateKeyPath = filepath.Join(
×
1124
                                lndDir, defaultTorV3PrivateKeyFilename,
×
1125
                        )
×
1126
                }
1127
        }
1128

1129
        if cfg.Tor.WatchtowerKeyPath == "" {
6✔
1130
                switch {
3✔
1131
                case cfg.Tor.V2:
×
1132
                        cfg.Tor.WatchtowerKeyPath = filepath.Join(
×
1133
                                cfg.Watchtower.TowerDir,
×
1134
                                defaultTorV2PrivateKeyFilename,
×
1135
                        )
×
1136
                case cfg.Tor.V3:
×
1137
                        cfg.Tor.WatchtowerKeyPath = filepath.Join(
×
1138
                                cfg.Watchtower.TowerDir,
×
1139
                                defaultTorV3PrivateKeyFilename,
×
1140
                        )
×
1141
                }
1142
        }
1143

1144
        // Set up the network-related functions that will be used throughout
1145
        // the daemon. We use the standard Go "net" package functions by
1146
        // default. If we should be proxying all traffic through Tor, then
1147
        // we'll use the Tor proxy specific functions in order to avoid leaking
1148
        // our real information.
1149
        if cfg.Tor.Active {
3✔
1150
                cfg.net = &tor.ProxyNet{
×
1151
                        SOCKS:                       cfg.Tor.SOCKS,
×
1152
                        DNS:                         cfg.Tor.DNS,
×
1153
                        StreamIsolation:             cfg.Tor.StreamIsolation,
×
1154
                        SkipProxyForClearNetTargets: cfg.Tor.SkipProxyForClearNetTargets,
×
1155
                }
×
1156
        }
×
1157

1158
        if cfg.DisableListen && cfg.NAT {
3✔
1159
                return nil, mkErr("NAT traversal cannot be used when " +
×
1160
                        "listening is disabled")
×
1161
        }
×
1162
        if cfg.NAT && len(cfg.ExternalHosts) != 0 {
3✔
1163
                return nil, mkErr("NAT support and externalhosts are " +
×
1164
                        "mutually exclusive, only one should be selected")
×
1165
        }
×
1166

1167
        // Multiple networks can't be selected simultaneously.  Count
1168
        // number of network flags passed; assign active network params
1169
        // while we're at it.
1170
        numNets := 0
3✔
1171
        if cfg.Bitcoin.MainNet {
3✔
1172
                numNets++
×
1173
                cfg.ActiveNetParams = chainreg.BitcoinMainNetParams
×
1174
        }
×
1175
        if cfg.Bitcoin.TestNet3 {
3✔
1176
                numNets++
×
1177
                cfg.ActiveNetParams = chainreg.BitcoinTestNetParams
×
1178
        }
×
1179
        if cfg.Bitcoin.RegTest {
6✔
1180
                numNets++
3✔
1181
                cfg.ActiveNetParams = chainreg.BitcoinRegTestNetParams
3✔
1182
        }
3✔
1183
        if cfg.Bitcoin.SimNet {
3✔
1184
                numNets++
×
1185
                cfg.ActiveNetParams = chainreg.BitcoinSimNetParams
×
1186

×
1187
                // For simnet, the btcsuite chain params uses a
×
1188
                // cointype of 115. However, we override this in
×
1189
                // chainreg/chainparams.go, but the raw ChainParam
×
1190
                // field is used elsewhere. To ensure everything is
×
1191
                // consistent, we'll also override the cointype within
×
1192
                // the raw params.
×
1193
                targetCoinType := chainreg.BitcoinSigNetParams.CoinType
×
1194
                cfg.ActiveNetParams.Params.HDCoinType = targetCoinType
×
1195
        }
×
1196
        if cfg.Bitcoin.SigNet {
3✔
1197
                numNets++
×
1198
                cfg.ActiveNetParams = chainreg.BitcoinSigNetParams
×
1199

×
1200
                // Let the user overwrite the default signet parameters.
×
1201
                // The challenge defines the actual signet network to
×
1202
                // join and the seed nodes are needed for network
×
1203
                // discovery.
×
1204
                sigNetChallenge := chaincfg.DefaultSignetChallenge
×
1205
                sigNetSeeds := chaincfg.DefaultSignetDNSSeeds
×
1206
                if cfg.Bitcoin.SigNetChallenge != "" {
×
1207
                        challenge, err := hex.DecodeString(
×
1208
                                cfg.Bitcoin.SigNetChallenge,
×
1209
                        )
×
1210
                        if err != nil {
×
1211
                                return nil, mkErr("Invalid "+
×
1212
                                        "signet challenge, hex decode "+
×
1213
                                        "failed: %v", err)
×
1214
                        }
×
1215
                        sigNetChallenge = challenge
×
1216
                }
1217

1218
                if len(cfg.Bitcoin.SigNetSeedNode) > 0 {
×
1219
                        sigNetSeeds = make([]chaincfg.DNSSeed, len(
×
1220
                                cfg.Bitcoin.SigNetSeedNode,
×
1221
                        ))
×
1222
                        for idx, seed := range cfg.Bitcoin.SigNetSeedNode {
×
1223
                                sigNetSeeds[idx] = chaincfg.DNSSeed{
×
1224
                                        Host:         seed,
×
1225
                                        HasFiltering: false,
×
1226
                                }
×
1227
                        }
×
1228
                }
1229

1230
                chainParams := chaincfg.CustomSignetParams(
×
1231
                        sigNetChallenge, sigNetSeeds,
×
1232
                )
×
1233
                cfg.ActiveNetParams.Params = &chainParams
×
1234
        }
1235
        if numNets > 1 {
3✔
1236
                str := "The mainnet, testnet, regtest, simnet and signet " +
×
1237
                        "params can't be used together -- choose one " +
×
1238
                        "of the five"
×
1239

×
1240
                return nil, mkErr(str)
×
1241
        }
×
1242

1243
        // The target network must be provided, otherwise, we won't
1244
        // know how to initialize the daemon.
1245
        if numNets == 0 {
3✔
1246
                str := "either --bitcoin.mainnet, or bitcoin.testnet," +
×
1247
                        "bitcoin.simnet, bitcoin.regtest or bitcoin.signet " +
×
1248
                        "must be specified"
×
1249

×
1250
                return nil, mkErr(str)
×
1251
        }
×
1252

1253
        err = cfg.Bitcoin.Validate(minTimeLockDelta, funding.MinBtcRemoteDelay)
3✔
1254
        if err != nil {
3✔
1255
                return nil, mkErr("error validating bitcoin params: %v", err)
×
1256
        }
×
1257

1258
        switch cfg.Bitcoin.Node {
3✔
UNCOV
1259
        case btcdBackendName:
×
UNCOV
1260
                err := parseRPCParams(
×
UNCOV
1261
                        cfg.Bitcoin, cfg.BtcdMode, cfg.ActiveNetParams,
×
UNCOV
1262
                )
×
UNCOV
1263
                if err != nil {
×
1264
                        return nil, mkErr("unable to load RPC "+
×
1265
                                "credentials for btcd: %v", err)
×
1266
                }
×
1267
        case bitcoindBackendName:
2✔
1268
                if cfg.Bitcoin.SimNet {
2✔
1269
                        return nil, mkErr("bitcoind does not " +
×
1270
                                "support simnet")
×
1271
                }
×
1272

1273
                err := parseRPCParams(
2✔
1274
                        cfg.Bitcoin, cfg.BitcoindMode, cfg.ActiveNetParams,
2✔
1275
                )
2✔
1276
                if err != nil {
2✔
1277
                        return nil, mkErr("unable to load RPC "+
×
1278
                                "credentials for bitcoind: %v", err)
×
1279
                }
×
1280
        case neutrinoBackendName:
1✔
1281
                // No need to get RPC parameters.
1282

1283
        case "nochainbackend":
×
1284
                // Nothing to configure, we're running without any chain
1285
                // backend whatsoever (pure signing mode).
1286

1287
        default:
×
1288
                str := "only btcd, bitcoind, and neutrino mode " +
×
1289
                        "supported for bitcoin at this time"
×
1290

×
1291
                return nil, mkErr(str)
×
1292
        }
1293

1294
        cfg.Bitcoin.ChainDir = filepath.Join(
3✔
1295
                cfg.DataDir, defaultChainSubDirname, BitcoinChainName,
3✔
1296
        )
3✔
1297

3✔
1298
        // Ensure that the user didn't attempt to specify negative values for
3✔
1299
        // any of the autopilot params.
3✔
1300
        if cfg.Autopilot.MaxChannels < 0 {
3✔
1301
                str := "autopilot.maxchannels must be non-negative"
×
1302

×
1303
                return nil, mkErr(str)
×
1304
        }
×
1305
        if cfg.Autopilot.Allocation < 0 {
3✔
1306
                str := "autopilot.allocation must be non-negative"
×
1307

×
1308
                return nil, mkErr(str)
×
1309
        }
×
1310
        if cfg.Autopilot.MinChannelSize < 0 {
3✔
1311
                str := "autopilot.minchansize must be non-negative"
×
1312

×
1313
                return nil, mkErr(str)
×
1314
        }
×
1315
        if cfg.Autopilot.MaxChannelSize < 0 {
3✔
1316
                str := "autopilot.maxchansize must be non-negative"
×
1317

×
1318
                return nil, mkErr(str)
×
1319
        }
×
1320

1321
        // Ensure that the specified values for the min and max channel size
1322
        // don't are within the bounds of the normal chan size constraints.
1323
        if cfg.Autopilot.MinChannelSize < int64(funding.MinChanFundingSize) {
3✔
1324
                cfg.Autopilot.MinChannelSize = int64(funding.MinChanFundingSize)
×
1325
        }
×
1326
        if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
3✔
1327
                cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
×
1328
        }
×
1329

1330
        // Validate profile port or host:port.
1331
        if cfg.Profile != "" {
6✔
1332
                str := "%s: The profile port must be between 1024 and 65535"
3✔
1333

3✔
1334
                // Try to parse Profile as a host:port.
3✔
1335
                _, hostPort, err := net.SplitHostPort(cfg.Profile)
3✔
1336
                if err == nil {
3✔
1337
                        // Determine if the port is valid.
×
1338
                        profilePort, err := strconv.Atoi(hostPort)
×
1339
                        if err != nil || profilePort < 1024 || profilePort > 65535 {
×
1340
                                return nil, &usageError{mkErr(str)}
×
1341
                        }
×
1342
                } else {
3✔
1343
                        // Try to parse Profile as a port.
3✔
1344
                        profilePort, err := strconv.Atoi(cfg.Profile)
3✔
1345
                        if err != nil || profilePort < 1024 || profilePort > 65535 {
3✔
1346
                                return nil, &usageError{mkErr(str)}
×
1347
                        }
×
1348

1349
                        // Since the user just set a port, we will serve debugging
1350
                        // information over localhost.
1351
                        cfg.Profile = net.JoinHostPort("127.0.0.1", cfg.Profile)
3✔
1352
                }
1353
        }
1354

1355
        // We'll now construct the network directory which will be where we
1356
        // store all the data specific to this chain/network.
1357
        cfg.networkDir = filepath.Join(
3✔
1358
                cfg.DataDir, defaultChainSubDirname, BitcoinChainName,
3✔
1359
                lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1360
        )
3✔
1361

3✔
1362
        // If a custom macaroon directory wasn't specified and the data
3✔
1363
        // directory has changed from the default path, then we'll also update
3✔
1364
        // the path for the macaroons to be generated.
3✔
1365
        if cfg.AdminMacPath == "" {
3✔
1366
                cfg.AdminMacPath = filepath.Join(
×
1367
                        cfg.networkDir, defaultAdminMacFilename,
×
1368
                )
×
1369
        }
×
1370
        if cfg.ReadMacPath == "" {
3✔
1371
                cfg.ReadMacPath = filepath.Join(
×
1372
                        cfg.networkDir, defaultReadMacFilename,
×
1373
                )
×
1374
        }
×
1375
        if cfg.InvoiceMacPath == "" {
3✔
1376
                cfg.InvoiceMacPath = filepath.Join(
×
1377
                        cfg.networkDir, defaultInvoiceMacFilename,
×
1378
                )
×
1379
        }
×
1380

1381
        towerDir := filepath.Join(
3✔
1382
                cfg.Watchtower.TowerDir, BitcoinChainName,
3✔
1383
                lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1384
        )
3✔
1385

3✔
1386
        // Create the lnd directory and all other sub-directories if they don't
3✔
1387
        // already exist. This makes sure that directory trees are also created
3✔
1388
        // for files that point to outside the lnddir.
3✔
1389
        dirs := []string{
3✔
1390
                lndDir, cfg.DataDir, cfg.networkDir,
3✔
1391
                cfg.LetsEncryptDir, towerDir, cfg.graphDatabaseDir(),
3✔
1392
                filepath.Dir(cfg.TLSCertPath), filepath.Dir(cfg.TLSKeyPath),
3✔
1393
                filepath.Dir(cfg.AdminMacPath), filepath.Dir(cfg.ReadMacPath),
3✔
1394
                filepath.Dir(cfg.InvoiceMacPath),
3✔
1395
                filepath.Dir(cfg.Tor.PrivateKeyPath),
3✔
1396
                filepath.Dir(cfg.Tor.WatchtowerKeyPath),
3✔
1397
        }
3✔
1398
        for _, dir := range dirs {
6✔
1399
                if err := makeDirectory(dir); err != nil {
3✔
1400
                        return nil, err
×
1401
                }
×
1402
        }
1403

1404
        // Similarly, if a custom back up file path wasn't specified, then
1405
        // we'll update the file location to match our set network directory.
1406
        if cfg.BackupFilePath == "" {
6✔
1407
                cfg.BackupFilePath = filepath.Join(
3✔
1408
                        cfg.networkDir, chanbackup.DefaultBackupFileName,
3✔
1409
                )
3✔
1410
        }
3✔
1411

1412
        // Append the network type to the log directory so it is "namespaced"
1413
        // per network in the same fashion as the data directory.
1414
        cfg.LogDir = filepath.Join(
3✔
1415
                cfg.LogDir, BitcoinChainName,
3✔
1416
                lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1417
        )
3✔
1418

3✔
1419
        // A log writer must be passed in, otherwise we can't function and would
3✔
1420
        // run into a panic later on.
3✔
1421
        if cfg.LogWriter == nil {
3✔
1422
                return nil, mkErr("log writer missing in config")
×
1423
        }
×
1424

1425
        // Special show command to list supported subsystems and exit.
1426
        if cfg.DebugLevel == "show" {
3✔
1427
                fmt.Println("Supported subsystems",
×
1428
                        cfg.LogWriter.SupportedSubsystems())
×
1429
                os.Exit(0)
×
1430
        }
×
1431

1432
        // Initialize logging at the default logging level.
1433
        SetupLoggers(cfg.LogWriter, interceptor)
3✔
1434
        err = cfg.LogWriter.InitLogRotator(
3✔
1435
                filepath.Join(cfg.LogDir, defaultLogFilename),
3✔
1436
                cfg.MaxLogFileSize, cfg.MaxLogFiles,
3✔
1437
        )
3✔
1438
        if err != nil {
3✔
1439
                str := "log rotation setup failed: %v"
×
1440
                return nil, mkErr(str, err)
×
1441
        }
×
1442

1443
        // Parse, validate, and set debug log level(s).
1444
        err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter)
3✔
1445
        if err != nil {
3✔
1446
                str := "error parsing debug level: %v"
×
1447
                return nil, &usageError{mkErr(str, err)}
×
1448
        }
×
1449

1450
        // At least one RPCListener is required. So listen on localhost per
1451
        // default.
1452
        if len(cfg.RawRPCListeners) == 0 {
3✔
1453
                addr := fmt.Sprintf("localhost:%d", defaultRPCPort)
×
1454
                cfg.RawRPCListeners = append(cfg.RawRPCListeners, addr)
×
1455
        }
×
1456

1457
        // Listen on localhost if no REST listeners were specified.
1458
        if len(cfg.RawRESTListeners) == 0 {
3✔
1459
                addr := fmt.Sprintf("localhost:%d", defaultRESTPort)
×
1460
                cfg.RawRESTListeners = append(cfg.RawRESTListeners, addr)
×
1461
        }
×
1462

1463
        // Listen on the default interface/port if no listeners were specified.
1464
        // An empty address string means default interface/address, which on
1465
        // most unix systems is the same as 0.0.0.0. If Tor is active, we
1466
        // default to only listening on localhost for hidden service
1467
        // connections.
1468
        if len(cfg.RawListeners) == 0 {
3✔
1469
                addr := fmt.Sprintf(":%d", defaultPeerPort)
×
1470
                if cfg.Tor.Active && !cfg.Tor.SkipProxyForClearNetTargets {
×
1471
                        addr = fmt.Sprintf("localhost:%d", defaultPeerPort)
×
1472
                }
×
1473
                cfg.RawListeners = append(cfg.RawListeners, addr)
×
1474
        }
1475

1476
        // Add default port to all RPC listener addresses if needed and remove
1477
        // duplicate addresses.
1478
        cfg.RPCListeners, err = lncfg.NormalizeAddresses(
3✔
1479
                cfg.RawRPCListeners, strconv.Itoa(defaultRPCPort),
3✔
1480
                cfg.net.ResolveTCPAddr,
3✔
1481
        )
3✔
1482
        if err != nil {
3✔
1483
                return nil, mkErr("error normalizing RPC listen addrs: %v", err)
×
1484
        }
×
1485

1486
        // Add default port to all REST listener addresses if needed and remove
1487
        // duplicate addresses.
1488
        cfg.RESTListeners, err = lncfg.NormalizeAddresses(
3✔
1489
                cfg.RawRESTListeners, strconv.Itoa(defaultRESTPort),
3✔
1490
                cfg.net.ResolveTCPAddr,
3✔
1491
        )
3✔
1492
        if err != nil {
3✔
1493
                return nil, mkErr("error normalizing REST listen addrs: %v", err)
×
1494
        }
×
1495

1496
        switch {
3✔
1497
        // The no seed backup and auto unlock are mutually exclusive.
1498
        case cfg.NoSeedBackup && cfg.WalletUnlockPasswordFile != "":
×
1499
                return nil, mkErr("cannot set noseedbackup and " +
×
1500
                        "wallet-unlock-password-file at the same time")
×
1501

1502
        // The "allow-create" flag cannot be set without the auto unlock file.
1503
        case cfg.WalletUnlockAllowCreate && cfg.WalletUnlockPasswordFile == "":
×
1504
                return nil, mkErr("cannot set wallet-unlock-allow-create " +
×
1505
                        "without wallet-unlock-password-file")
×
1506

1507
        // If a password file was specified, we need it to exist.
1508
        case cfg.WalletUnlockPasswordFile != "" &&
1509
                !lnrpc.FileExists(cfg.WalletUnlockPasswordFile):
×
1510

×
1511
                return nil, mkErr("wallet unlock password file %s does "+
×
1512
                        "not exist", cfg.WalletUnlockPasswordFile)
×
1513
        }
1514

1515
        // For each of the RPC listeners (REST+gRPC), we'll ensure that users
1516
        // have specified a safe combo for authentication. If not, we'll bail
1517
        // out with an error. Since we don't allow disabling TLS for gRPC
1518
        // connections we pass in tlsActive=true.
1519
        err = lncfg.EnforceSafeAuthentication(
3✔
1520
                cfg.RPCListeners, !cfg.NoMacaroons, true,
3✔
1521
        )
3✔
1522
        if err != nil {
3✔
1523
                return nil, mkErr("error enforcing safe authentication on "+
×
1524
                        "RPC ports: %v", err)
×
1525
        }
×
1526

1527
        if cfg.DisableRest {
3✔
1528
                ltndLog.Infof("REST API is disabled!")
×
1529
                cfg.RESTListeners = nil
×
1530
        } else {
3✔
1531
                err = lncfg.EnforceSafeAuthentication(
3✔
1532
                        cfg.RESTListeners, !cfg.NoMacaroons, !cfg.DisableRestTLS,
3✔
1533
                )
3✔
1534
                if err != nil {
3✔
1535
                        return nil, mkErr("error enforcing safe "+
×
1536
                                "authentication on REST ports: %v", err)
×
1537
                }
×
1538
        }
1539

1540
        // Remove the listening addresses specified if listening is disabled.
1541
        if cfg.DisableListen {
6✔
1542
                ltndLog.Infof("Listening on the p2p interface is disabled!")
3✔
1543
                cfg.Listeners = nil
3✔
1544
                cfg.ExternalIPs = nil
3✔
1545
        } else {
6✔
1546

3✔
1547
                // Add default port to all listener addresses if needed and remove
3✔
1548
                // duplicate addresses.
3✔
1549
                cfg.Listeners, err = lncfg.NormalizeAddresses(
3✔
1550
                        cfg.RawListeners, strconv.Itoa(defaultPeerPort),
3✔
1551
                        cfg.net.ResolveTCPAddr,
3✔
1552
                )
3✔
1553
                if err != nil {
3✔
1554
                        return nil, mkErr("error normalizing p2p listen "+
×
1555
                                "addrs: %v", err)
×
1556
                }
×
1557

1558
                // Add default port to all external IP addresses if needed and remove
1559
                // duplicate addresses.
1560
                cfg.ExternalIPs, err = lncfg.NormalizeAddresses(
3✔
1561
                        cfg.RawExternalIPs, strconv.Itoa(defaultPeerPort),
3✔
1562
                        cfg.net.ResolveTCPAddr,
3✔
1563
                )
3✔
1564
                if err != nil {
3✔
1565
                        return nil, err
×
1566
                }
×
1567

1568
                // For the p2p port it makes no sense to listen to an Unix socket.
1569
                // Also, we would need to refactor the brontide listener to support
1570
                // that.
1571
                for _, p2pListener := range cfg.Listeners {
6✔
1572
                        if lncfg.IsUnix(p2pListener) {
3✔
1573
                                return nil, mkErr("unix socket addresses "+
×
1574
                                        "cannot be used for the p2p "+
×
1575
                                        "connection listener: %s", p2pListener)
×
1576
                        }
×
1577
                }
1578
        }
1579

1580
        // Ensure that the specified minimum backoff is below or equal to the
1581
        // maximum backoff.
1582
        if cfg.MinBackoff > cfg.MaxBackoff {
3✔
1583
                return nil, mkErr("maxbackoff must be greater than minbackoff")
×
1584
        }
×
1585

1586
        // Newer versions of lnd added a new sub-config for bolt-specific
1587
        // parameters. However, we want to also allow existing users to use the
1588
        // value on the top-level config. If the outer config value is set,
1589
        // then we'll use that directly.
1590
        flagSet, err := isSet("SyncFreelist")
3✔
1591
        if err != nil {
3✔
1592
                return nil, mkErr("error parsing freelist sync flag: %v", err)
×
1593
        }
×
1594
        if flagSet {
3✔
1595
                cfg.DB.Bolt.NoFreelistSync = !cfg.SyncFreelist
×
1596
        }
×
1597

1598
        // Parse any extra sqlite pragma options that may have been provided
1599
        // to determine if they override any of the defaults that we will
1600
        // otherwise add.
1601
        var (
3✔
1602
                defaultSynchronous = true
3✔
1603
                defaultAutoVacuum  = true
3✔
1604
                defaultFullfsync   = true
3✔
1605
        )
3✔
1606
        for _, option := range cfg.DB.Sqlite.PragmaOptions {
3✔
1607
                switch {
×
1608
                case strings.HasPrefix(option, "synchronous="):
×
1609
                        defaultSynchronous = false
×
1610

1611
                case strings.HasPrefix(option, "auto_vacuum="):
×
1612
                        defaultAutoVacuum = false
×
1613

1614
                case strings.HasPrefix(option, "fullfsync="):
×
1615
                        defaultFullfsync = false
×
1616

1617
                default:
×
1618
                }
1619
        }
1620

1621
        if defaultSynchronous {
6✔
1622
                cfg.DB.Sqlite.PragmaOptions = append(
3✔
1623
                        cfg.DB.Sqlite.PragmaOptions, "synchronous=full",
3✔
1624
                )
3✔
1625
        }
3✔
1626

1627
        if defaultAutoVacuum {
6✔
1628
                cfg.DB.Sqlite.PragmaOptions = append(
3✔
1629
                        cfg.DB.Sqlite.PragmaOptions, "auto_vacuum=incremental",
3✔
1630
                )
3✔
1631
        }
3✔
1632

1633
        if defaultFullfsync {
6✔
1634
                cfg.DB.Sqlite.PragmaOptions = append(
3✔
1635
                        cfg.DB.Sqlite.PragmaOptions, "fullfsync=true",
3✔
1636
                )
3✔
1637
        }
3✔
1638

1639
        // Ensure that the user hasn't chosen a remote-max-htlc value greater
1640
        // than the protocol maximum.
1641
        maxRemoteHtlcs := uint16(input.MaxHTLCNumber / 2)
3✔
1642
        if cfg.DefaultRemoteMaxHtlcs > maxRemoteHtlcs {
3✔
1643
                return nil, mkErr("default-remote-max-htlcs (%v) must be "+
×
1644
                        "less than %v", cfg.DefaultRemoteMaxHtlcs,
×
1645
                        maxRemoteHtlcs)
×
1646
        }
×
1647

1648
        // Clamp the ChannelCommitInterval so that commitment updates can still
1649
        // happen in a reasonable timeframe.
1650
        if cfg.ChannelCommitInterval > maxChannelCommitInterval {
3✔
1651
                return nil, mkErr("channel-commit-interval (%v) must be less "+
×
1652
                        "than %v", cfg.ChannelCommitInterval,
×
1653
                        maxChannelCommitInterval)
×
1654
        }
×
1655

1656
        // Limit PendingCommitInterval so we don't wait too long for the remote
1657
        // party to send back a revoke.
1658
        if cfg.PendingCommitInterval > maxPendingCommitInterval {
3✔
1659
                return nil, mkErr("pending-commit-interval (%v) must be less "+
×
1660
                        "than %v", cfg.PendingCommitInterval,
×
1661
                        maxPendingCommitInterval)
×
1662
        }
×
1663

1664
        if err := cfg.Gossip.Parse(); err != nil {
3✔
1665
                return nil, mkErr("error parsing gossip syncer: %v", err)
×
1666
        }
×
1667

1668
        // If the experimental protocol options specify any protocol messages
1669
        // that we want to handle as custom messages, set them now.
1670
        customMsg := cfg.ProtocolOptions.CustomMessageOverrides()
3✔
1671

3✔
1672
        // We can safely set our custom override values during startup because
3✔
1673
        // startup is blocked on config parsing.
3✔
1674
        if err := lnwire.SetCustomOverrides(customMsg); err != nil {
3✔
1675
                return nil, mkErr("custom-message: %v", err)
×
1676
        }
×
1677

1678
        // Validate the subconfigs for workers, caches, and the tower client.
1679
        err = lncfg.Validate(
3✔
1680
                cfg.Workers,
3✔
1681
                cfg.Caches,
3✔
1682
                cfg.WtClient,
3✔
1683
                cfg.DB,
3✔
1684
                cfg.Cluster,
3✔
1685
                cfg.HealthChecks,
3✔
1686
                cfg.RPCMiddleware,
3✔
1687
                cfg.RemoteSigner,
3✔
1688
                cfg.Sweeper,
3✔
1689
                cfg.Htlcswitch,
3✔
1690
                cfg.Invoices,
3✔
1691
                cfg.Routing,
3✔
1692
        )
3✔
1693
        if err != nil {
3✔
1694
                return nil, err
×
1695
        }
×
1696

1697
        // Finally, ensure that the user's color is correctly formatted,
1698
        // otherwise the server will not be able to start after the unlocking
1699
        // the wallet.
1700
        _, err = lncfg.ParseHexColor(cfg.Color)
3✔
1701
        if err != nil {
3✔
1702
                return nil, mkErr("unable to parse node color: %v", err)
×
1703
        }
×
1704

1705
        // All good, return the sanitized result.
1706
        return &cfg, nil
3✔
1707
}
1708

1709
// graphDatabaseDir returns the default directory where the local bolt graph db
1710
// files are stored.
1711
func (c *Config) graphDatabaseDir() string {
3✔
1712
        return filepath.Join(
3✔
1713
                c.DataDir, defaultGraphSubDirname,
3✔
1714
                lncfg.NormalizeNetwork(c.ActiveNetParams.Name),
3✔
1715
        )
3✔
1716
}
3✔
1717

1718
// ImplementationConfig returns the configuration of what actual implementations
1719
// should be used when creating the main lnd instance.
1720
func (c *Config) ImplementationConfig(
1721
        interceptor signal.Interceptor) *ImplementationCfg {
3✔
1722

3✔
1723
        // If we're using a remote signer, we still need the base wallet as a
3✔
1724
        // watch-only source of chain and address data. But we don't need any
3✔
1725
        // private key material in that btcwallet base wallet.
3✔
1726
        if c.RemoteSigner.Enable {
6✔
1727
                rpcImpl := NewRPCSignerWalletImpl(
3✔
1728
                        c, ltndLog, interceptor,
3✔
1729
                        c.RemoteSigner.MigrateWatchOnly,
3✔
1730
                )
3✔
1731
                return &ImplementationCfg{
3✔
1732
                        GrpcRegistrar:     rpcImpl,
3✔
1733
                        RestRegistrar:     rpcImpl,
3✔
1734
                        ExternalValidator: rpcImpl,
3✔
1735
                        DatabaseBuilder: NewDefaultDatabaseBuilder(
3✔
1736
                                c, ltndLog,
3✔
1737
                        ),
3✔
1738
                        WalletConfigBuilder: rpcImpl,
3✔
1739
                        ChainControlBuilder: rpcImpl,
3✔
1740
                }
3✔
1741
        }
3✔
1742

1743
        defaultImpl := NewDefaultWalletImpl(c, ltndLog, interceptor, false)
3✔
1744
        return &ImplementationCfg{
3✔
1745
                GrpcRegistrar:       defaultImpl,
3✔
1746
                RestRegistrar:       defaultImpl,
3✔
1747
                ExternalValidator:   defaultImpl,
3✔
1748
                DatabaseBuilder:     NewDefaultDatabaseBuilder(c, ltndLog),
3✔
1749
                WalletConfigBuilder: defaultImpl,
3✔
1750
                ChainControlBuilder: defaultImpl,
3✔
1751
        }
3✔
1752
}
1753

1754
// CleanAndExpandPath expands environment variables and leading ~ in the
1755
// passed path, cleans the result, and returns it.
1756
// This function is taken from https://github.com/btcsuite/btcd
1757
func CleanAndExpandPath(path string) string {
3✔
1758
        if path == "" {
6✔
1759
                return ""
3✔
1760
        }
3✔
1761

1762
        // Expand initial ~ to OS specific home directory.
1763
        if strings.HasPrefix(path, "~") {
3✔
1764
                var homeDir string
×
1765
                u, err := user.Current()
×
1766
                if err == nil {
×
1767
                        homeDir = u.HomeDir
×
1768
                } else {
×
1769
                        homeDir = os.Getenv("HOME")
×
1770
                }
×
1771

1772
                path = strings.Replace(path, "~", homeDir, 1)
×
1773
        }
1774

1775
        // NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
1776
        // but the variables can still be expanded via POSIX-style $VARIABLE.
1777
        return filepath.Clean(os.ExpandEnv(path))
3✔
1778
}
1779

1780
func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{},
1781
        netParams chainreg.BitcoinNetParams) error {
2✔
1782

2✔
1783
        // First, we'll check our node config to make sure the RPC parameters
2✔
1784
        // were set correctly. We'll also determine the path to the conf file
2✔
1785
        // depending on the backend node.
2✔
1786
        var daemonName, confDir, confFile, confFileBase string
2✔
1787
        switch conf := nodeConfig.(type) {
2✔
UNCOV
1788
        case *lncfg.Btcd:
×
UNCOV
1789
                // Resolves environment variable references in RPCUser and
×
UNCOV
1790
                // RPCPass fields.
×
UNCOV
1791
                conf.RPCUser = supplyEnvValue(conf.RPCUser)
×
UNCOV
1792
                conf.RPCPass = supplyEnvValue(conf.RPCPass)
×
UNCOV
1793

×
UNCOV
1794
                // If both RPCUser and RPCPass are set, we assume those
×
UNCOV
1795
                // credentials are good to use.
×
UNCOV
1796
                if conf.RPCUser != "" && conf.RPCPass != "" {
×
UNCOV
1797
                        return nil
×
UNCOV
1798
                }
×
1799

1800
                // Set the daemon name for displaying proper errors.
1801
                daemonName = btcdBackendName
×
1802
                confDir = conf.Dir
×
1803
                confFileBase = btcdBackendName
×
1804

×
1805
                // If only ONE of RPCUser or RPCPass is set, we assume the
×
1806
                // user did that unintentionally.
×
1807
                if conf.RPCUser != "" || conf.RPCPass != "" {
×
1808
                        return fmt.Errorf("please set both or neither of "+
×
1809
                                "%[1]v.rpcuser, %[1]v.rpcpass", daemonName)
×
1810
                }
×
1811

1812
        case *lncfg.Bitcoind:
2✔
1813
                // Ensure that if the ZMQ options are set, that they are not
2✔
1814
                // equal.
2✔
1815
                if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
3✔
1816
                        err := checkZMQOptions(
1✔
1817
                                conf.ZMQPubRawBlock, conf.ZMQPubRawTx,
1✔
1818
                        )
1✔
1819
                        if err != nil {
1✔
1820
                                return err
×
1821
                        }
×
1822
                }
1823

1824
                // Ensure that if the estimate mode is set, that it is a legal
1825
                // value.
1826
                if conf.EstimateMode != "" {
4✔
1827
                        err := checkEstimateMode(conf.EstimateMode)
2✔
1828
                        if err != nil {
2✔
1829
                                return err
×
1830
                        }
×
1831
                }
1832

1833
                // Set the daemon name for displaying proper errors.
1834
                daemonName = bitcoindBackendName
2✔
1835
                confDir = conf.Dir
2✔
1836
                confFile = conf.ConfigPath
2✔
1837
                confFileBase = BitcoinChainName
2✔
1838

2✔
1839
                // Resolves environment variable references in RPCUser
2✔
1840
                // and RPCPass fields.
2✔
1841
                conf.RPCUser = supplyEnvValue(conf.RPCUser)
2✔
1842
                conf.RPCPass = supplyEnvValue(conf.RPCPass)
2✔
1843

2✔
1844
                // Check that cookie and credentials don't contradict each
2✔
1845
                // other.
2✔
1846
                if (conf.RPCUser != "" || conf.RPCPass != "") &&
2✔
1847
                        conf.RPCCookie != "" {
2✔
1848

×
1849
                        return fmt.Errorf("please only provide either "+
×
1850
                                "%[1]v.rpccookie or %[1]v.rpcuser and "+
×
1851
                                "%[1]v.rpcpass", daemonName)
×
1852
                }
×
1853

1854
                // We convert the cookie into a user name and password.
1855
                if conf.RPCCookie != "" {
2✔
1856
                        cookie, err := os.ReadFile(conf.RPCCookie)
×
1857
                        if err != nil {
×
1858
                                return fmt.Errorf("cannot read cookie file: %w",
×
1859
                                        err)
×
1860
                        }
×
1861

1862
                        splitCookie := strings.Split(string(cookie), ":")
×
1863
                        if len(splitCookie) != 2 {
×
1864
                                return fmt.Errorf("cookie file has a wrong " +
×
1865
                                        "format")
×
1866
                        }
×
1867
                        conf.RPCUser = splitCookie[0]
×
1868
                        conf.RPCPass = splitCookie[1]
×
1869
                }
1870

1871
                if conf.RPCUser != "" && conf.RPCPass != "" {
4✔
1872
                        // If all of RPCUser, RPCPass, ZMQBlockHost, and
2✔
1873
                        // ZMQTxHost are set, we assume those parameters are
2✔
1874
                        // good to use.
2✔
1875
                        if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
3✔
1876
                                return nil
1✔
1877
                        }
1✔
1878

1879
                        // If RPCUser and RPCPass are set and RPCPolling is
1880
                        // enabled, we assume the parameters are good to use.
1881
                        if conf.RPCPolling {
2✔
1882
                                return nil
1✔
1883
                        }
1✔
1884
                }
1885

1886
                // If not all of the parameters are set, we'll assume the user
1887
                // did this unintentionally.
1888
                if conf.RPCUser != "" || conf.RPCPass != "" ||
×
1889
                        conf.ZMQPubRawBlock != "" || conf.ZMQPubRawTx != "" {
×
1890

×
1891
                        return fmt.Errorf("please set %[1]v.rpcuser and "+
×
1892
                                "%[1]v.rpcpass (or %[1]v.rpccookie) together "+
×
1893
                                "with %[1]v.zmqpubrawblock, %[1]v.zmqpubrawtx",
×
1894
                                daemonName)
×
1895
                }
×
1896
        }
1897

1898
        // If we're in simnet mode, then the running btcd instance won't read
1899
        // the RPC credentials from the configuration. So if lnd wasn't
1900
        // specified the parameters, then we won't be able to start.
1901
        if cConfig.SimNet {
×
1902
                return fmt.Errorf("rpcuser and rpcpass must be set to your " +
×
1903
                        "btcd node's RPC parameters for simnet mode")
×
1904
        }
×
1905

1906
        fmt.Println("Attempting automatic RPC configuration to " + daemonName)
×
1907

×
1908
        if confFile == "" {
×
1909
                confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf",
×
1910
                        confFileBase))
×
1911
        }
×
1912
        switch cConfig.Node {
×
1913
        case btcdBackendName:
×
1914
                nConf := nodeConfig.(*lncfg.Btcd)
×
1915
                rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
×
1916
                if err != nil {
×
1917
                        return fmt.Errorf("unable to extract RPC credentials: "+
×
1918
                                "%v, cannot start w/o RPC connection", err)
×
1919
                }
×
1920
                nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
×
1921

1922
        case bitcoindBackendName:
×
1923
                nConf := nodeConfig.(*lncfg.Bitcoind)
×
1924
                rpcUser, rpcPass, zmqBlockHost, zmqTxHost, err :=
×
1925
                        extractBitcoindRPCParams(netParams.Params.Name,
×
1926
                                nConf.Dir, confFile, nConf.RPCCookie)
×
1927
                if err != nil {
×
1928
                        return fmt.Errorf("unable to extract RPC credentials: "+
×
1929
                                "%v, cannot start w/o RPC connection", err)
×
1930
                }
×
1931
                nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
×
1932
                nConf.ZMQPubRawBlock, nConf.ZMQPubRawTx = zmqBlockHost, zmqTxHost
×
1933
        }
1934

1935
        fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName)
×
1936
        return nil
×
1937
}
1938

1939
// supplyEnvValue supplies the value of an environment variable from a string.
1940
// It supports the following formats:
1941
// 1) $ENV_VAR
1942
// 2) ${ENV_VAR}
1943
// 3) ${ENV_VAR:-DEFAULT}
1944
//
1945
// Standard environment variable naming conventions:
1946
// - ENV_VAR contains letters, digits, and underscores, and does
1947
// not start with a digit.
1948
// - DEFAULT follows the rule that it can contain any characters except
1949
// whitespace.
1950
//
1951
// Parameters:
1952
// - value: The input string containing references to environment variables
1953
// (if any).
1954
//
1955
// Returns:
1956
// - string: The value of the specified environment variable, the default
1957
// value if provided, or the original input string if no matching variable is
1958
// found or set.
1959
func supplyEnvValue(value string) string {
9✔
1960
        // Regex for $ENV_VAR format.
9✔
1961
        var reEnvVar = regexp.MustCompile(`^\$([a-zA-Z_][a-zA-Z0-9_]*)$`)
9✔
1962

9✔
1963
        // Regex for ${ENV_VAR} format.
9✔
1964
        var reEnvVarWithBrackets = regexp.MustCompile(
9✔
1965
                `^\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}$`,
9✔
1966
        )
9✔
1967

9✔
1968
        // Regex for ${ENV_VAR:-DEFAULT} format.
9✔
1969
        var reEnvVarWithDefault = regexp.MustCompile(
9✔
1970
                `^\$\{([a-zA-Z_][a-zA-Z0-9_]*):-([\S]+)\}$`,
9✔
1971
        )
9✔
1972

9✔
1973
        // Match against supported formats.
9✔
1974
        switch {
9✔
1975
        case reEnvVarWithDefault.MatchString(value):
3✔
1976
                matches := reEnvVarWithDefault.FindStringSubmatch(value)
3✔
1977
                envVariable := matches[1]
3✔
1978
                defaultValue := matches[2]
3✔
1979
                if envValue := os.Getenv(envVariable); envValue != "" {
4✔
1980
                        return envValue
1✔
1981
                }
1✔
1982

1983
                return defaultValue
2✔
1984

1985
        case reEnvVarWithBrackets.MatchString(value):
×
1986
                matches := reEnvVarWithBrackets.FindStringSubmatch(value)
×
1987
                envVariable := matches[1]
×
1988
                envValue := os.Getenv(envVariable)
×
1989

×
1990
                return envValue
×
1991

1992
        case reEnvVar.MatchString(value):
3✔
1993
                matches := reEnvVar.FindStringSubmatch(value)
3✔
1994
                envVariable := matches[1]
3✔
1995
                envValue := os.Getenv(envVariable)
3✔
1996

3✔
1997
                return envValue
3✔
1998
        }
1999

2000
        return value
3✔
2001
}
2002

2003
// extractBtcdRPCParams attempts to extract the RPC credentials for an existing
2004
// btcd instance. The passed path is expected to be the location of btcd's
2005
// application data directory on the target system.
2006
func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) {
×
2007
        // First, we'll open up the btcd configuration file found at the target
×
2008
        // destination.
×
2009
        btcdConfigFile, err := os.Open(btcdConfigPath)
×
2010
        if err != nil {
×
2011
                return "", "", err
×
2012
        }
×
2013
        defer func() { _ = btcdConfigFile.Close() }()
×
2014

2015
        // With the file open extract the contents of the configuration file so
2016
        // we can attempt to locate the RPC credentials.
2017
        configContents, err := io.ReadAll(btcdConfigFile)
×
2018
        if err != nil {
×
2019
                return "", "", err
×
2020
        }
×
2021

2022
        // Attempt to locate the RPC user using a regular expression. If we
2023
        // don't have a match for our regular expression then we'll exit with
2024
        // an error.
2025
        rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
×
2026
        if err != nil {
×
2027
                return "", "", err
×
2028
        }
×
2029
        userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
×
2030
        if userSubmatches == nil {
×
2031
                return "", "", fmt.Errorf("unable to find rpcuser in config")
×
2032
        }
×
2033

2034
        // Similarly, we'll use another regular expression to find the set
2035
        // rpcpass (if any). If we can't find the pass, then we'll exit with an
2036
        // error.
2037
        rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass\s*=\s*([^\s]+)`)
×
2038
        if err != nil {
×
2039
                return "", "", err
×
2040
        }
×
2041
        passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
×
2042
        if passSubmatches == nil {
×
2043
                return "", "", fmt.Errorf("unable to find rpcuser in config")
×
2044
        }
×
2045

2046
        return supplyEnvValue(string(userSubmatches[1])),
×
2047
                supplyEnvValue(string(passSubmatches[1])), nil
×
2048
}
2049

2050
// extractBitcoindRPCParams attempts to extract the RPC credentials for an
2051
// existing bitcoind node instance. The routine looks for a cookie first,
2052
// optionally following the datadir configuration option in the bitcoin.conf. If
2053
// it doesn't find one, it looks for rpcuser/rpcpassword.
2054
func extractBitcoindRPCParams(networkName, bitcoindDataDir, bitcoindConfigPath,
2055
        rpcCookiePath string) (string, string, string, string, error) {
×
2056

×
2057
        // First, we'll open up the bitcoind configuration file found at the
×
2058
        // target destination.
×
2059
        bitcoindConfigFile, err := os.Open(bitcoindConfigPath)
×
2060
        if err != nil {
×
2061
                return "", "", "", "", err
×
2062
        }
×
2063
        defer func() { _ = bitcoindConfigFile.Close() }()
×
2064

2065
        // With the file open extract the contents of the configuration file so
2066
        // we can attempt to locate the RPC credentials.
2067
        configContents, err := io.ReadAll(bitcoindConfigFile)
×
2068
        if err != nil {
×
2069
                return "", "", "", "", err
×
2070
        }
×
2071

2072
        // First, we'll look for the ZMQ hosts providing raw block and raw
2073
        // transaction notifications.
2074
        zmqBlockHostRE, err := regexp.Compile(
×
2075
                `(?m)^\s*zmqpubrawblock\s*=\s*([^\s]+)`,
×
2076
        )
×
2077
        if err != nil {
×
2078
                return "", "", "", "", err
×
2079
        }
×
2080
        zmqBlockHostSubmatches := zmqBlockHostRE.FindSubmatch(configContents)
×
2081
        if len(zmqBlockHostSubmatches) < 2 {
×
2082
                return "", "", "", "", fmt.Errorf("unable to find " +
×
2083
                        "zmqpubrawblock in config")
×
2084
        }
×
2085
        zmqTxHostRE, err := regexp.Compile(`(?m)^\s*zmqpubrawtx\s*=\s*([^\s]+)`)
×
2086
        if err != nil {
×
2087
                return "", "", "", "", err
×
2088
        }
×
2089
        zmqTxHostSubmatches := zmqTxHostRE.FindSubmatch(configContents)
×
2090
        if len(zmqTxHostSubmatches) < 2 {
×
2091
                return "", "", "", "", errors.New("unable to find zmqpubrawtx " +
×
2092
                        "in config")
×
2093
        }
×
2094
        zmqBlockHost := string(zmqBlockHostSubmatches[1])
×
2095
        zmqTxHost := string(zmqTxHostSubmatches[1])
×
2096
        if err := checkZMQOptions(zmqBlockHost, zmqTxHost); err != nil {
×
2097
                return "", "", "", "", err
×
2098
        }
×
2099

2100
        // Next, we'll try to find an auth cookie. We need to detect the chain
2101
        // by seeing if one is specified in the configuration file.
2102
        dataDir := filepath.Dir(bitcoindConfigPath)
×
2103
        if bitcoindDataDir != "" {
×
2104
                dataDir = bitcoindDataDir
×
2105
        }
×
2106
        dataDirRE, err := regexp.Compile(`(?m)^\s*datadir\s*=\s*([^\s]+)`)
×
2107
        if err != nil {
×
2108
                return "", "", "", "", err
×
2109
        }
×
2110
        dataDirSubmatches := dataDirRE.FindSubmatch(configContents)
×
2111
        if dataDirSubmatches != nil {
×
2112
                dataDir = string(dataDirSubmatches[1])
×
2113
        }
×
2114

2115
        var chainDir string
×
2116
        switch networkName {
×
2117
        case "mainnet":
×
2118
                chainDir = ""
×
2119
        case "regtest", "testnet3", "signet":
×
2120
                chainDir = networkName
×
2121
        default:
×
2122
                return "", "", "", "", fmt.Errorf("unexpected networkname %v", networkName)
×
2123
        }
2124

2125
        cookiePath := filepath.Join(dataDir, chainDir, ".cookie")
×
2126
        if rpcCookiePath != "" {
×
2127
                cookiePath = rpcCookiePath
×
2128
        }
×
2129
        cookie, err := os.ReadFile(cookiePath)
×
2130
        if err == nil {
×
2131
                splitCookie := strings.Split(string(cookie), ":")
×
2132
                if len(splitCookie) == 2 {
×
2133
                        return splitCookie[0], splitCookie[1], zmqBlockHost,
×
2134
                                zmqTxHost, nil
×
2135
                }
×
2136
        }
2137

2138
        // We didn't find a cookie, so we attempt to locate the RPC user using
2139
        // a regular expression. If we  don't have a match for our regular
2140
        // expression then we'll exit with an error.
2141
        rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
×
2142
        if err != nil {
×
2143
                return "", "", "", "", err
×
2144
        }
×
2145
        userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
×
2146

×
2147
        // Similarly, we'll use another regular expression to find the set
×
2148
        // rpcpass (if any). If we can't find the pass, then we'll exit with an
×
2149
        // error.
×
2150
        rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpassword\s*=\s*([^\s]+)`)
×
2151
        if err != nil {
×
2152
                return "", "", "", "", err
×
2153
        }
×
2154
        passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
×
2155

×
2156
        // Exit with an error if the cookie file, is defined in config, and
×
2157
        // can not be found, with both rpcuser and rpcpassword undefined.
×
2158
        if rpcCookiePath != "" && userSubmatches == nil && passSubmatches == nil {
×
2159
                return "", "", "", "", fmt.Errorf("unable to open cookie file (%v)",
×
2160
                        rpcCookiePath)
×
2161
        }
×
2162

2163
        if userSubmatches == nil {
×
2164
                return "", "", "", "", fmt.Errorf("unable to find rpcuser in " +
×
2165
                        "config")
×
2166
        }
×
2167
        if passSubmatches == nil {
×
2168
                return "", "", "", "", fmt.Errorf("unable to find rpcpassword " +
×
2169
                        "in config")
×
2170
        }
×
2171

2172
        return supplyEnvValue(string(userSubmatches[1])),
×
2173
                supplyEnvValue(string(passSubmatches[1])),
×
2174
                zmqBlockHost, zmqTxHost, nil
×
2175
}
2176

2177
// checkZMQOptions ensures that the provided addresses to use as the hosts for
2178
// ZMQ rawblock and rawtx notifications are different.
2179
func checkZMQOptions(zmqBlockHost, zmqTxHost string) error {
1✔
2180
        if zmqBlockHost == zmqTxHost {
1✔
2181
                return errors.New("zmqpubrawblock and zmqpubrawtx must be set " +
×
2182
                        "to different addresses")
×
2183
        }
×
2184

2185
        return nil
1✔
2186
}
2187

2188
// checkEstimateMode ensures that the provided estimate mode is legal.
2189
func checkEstimateMode(estimateMode string) error {
2✔
2190
        for _, mode := range bitcoindEstimateModes {
4✔
2191
                if estimateMode == mode {
4✔
2192
                        return nil
2✔
2193
                }
2✔
2194
        }
2195

2196
        return fmt.Errorf("estimatemode must be one of the following: %v",
×
2197
                bitcoindEstimateModes[:])
×
2198
}
2199

2200
// configToFlatMap converts the given config struct into a flat map of
2201
// key/value pairs using the dot notation we are used to from the config file
2202
// or command line flags. It also returns a map containing deprecated config
2203
// options.
2204
func configToFlatMap(cfg Config) (map[string]string,
2205
        map[string]struct{}, error) {
4✔
2206

4✔
2207
        result := make(map[string]string)
4✔
2208

4✔
2209
        // deprecated stores a map of deprecated options found in the config
4✔
2210
        // that are set by the users. A config option is considered as
4✔
2211
        // deprecated if it has a `hidden` flag.
4✔
2212
        deprecated := make(map[string]struct{})
4✔
2213

4✔
2214
        // redact is the helper function that redacts sensitive values like
4✔
2215
        // passwords.
4✔
2216
        redact := func(key, value string) string {
299✔
2217
                sensitiveKeySuffixes := []string{
295✔
2218
                        "pass",
295✔
2219
                        "password",
295✔
2220
                        "dsn",
295✔
2221
                }
295✔
2222
                for _, suffix := range sensitiveKeySuffixes {
1,167✔
2223
                        if strings.HasSuffix(key, suffix) {
880✔
2224
                                return "[redacted]"
8✔
2225
                        }
8✔
2226
                }
2227

2228
                return value
290✔
2229
        }
2230

2231
        // printConfig is the helper function that goes into nested structs
2232
        // recursively. Because we call it recursively, we need to declare it
2233
        // before we define it.
2234
        var printConfig func(reflect.Value, string)
4✔
2235
        printConfig = func(obj reflect.Value, prefix string) {
61✔
2236
                // Turn struct pointers into the actual struct, so we can
57✔
2237
                // iterate over the fields as we would with a struct value.
57✔
2238
                if obj.Kind() == reflect.Ptr {
109✔
2239
                        obj = obj.Elem()
52✔
2240
                }
52✔
2241

2242
                // Abort on nil values.
2243
                if !obj.IsValid() {
68✔
2244
                        return
11✔
2245
                }
11✔
2246

2247
                // Loop over all fields of the struct and inspect the type.
2248
                for i := 0; i < obj.NumField(); i++ {
409✔
2249
                        field := obj.Field(i)
363✔
2250
                        fieldType := obj.Type().Field(i)
363✔
2251

363✔
2252
                        longName := fieldType.Tag.Get("long")
363✔
2253
                        namespace := fieldType.Tag.Get("namespace")
363✔
2254
                        group := fieldType.Tag.Get("group")
363✔
2255
                        hidden := fieldType.Tag.Get("hidden")
363✔
2256

363✔
2257
                        switch {
363✔
2258
                        // We have a long name defined, this is a config value.
2259
                        case longName != "":
295✔
2260
                                key := longName
295✔
2261
                                if prefix != "" {
495✔
2262
                                        key = prefix + "." + key
200✔
2263
                                }
200✔
2264

2265
                                // Add the value directly to the flattened map.
2266
                                result[key] = redact(key, fmt.Sprintf(
295✔
2267
                                        "%v", field.Interface(),
295✔
2268
                                ))
295✔
2269

295✔
2270
                                // If there's a hidden flag, it's deprecated.
295✔
2271
                                if hidden == "true" && !field.IsZero() {
296✔
2272
                                        deprecated[key] = struct{}{}
1✔
2273
                                }
1✔
2274

2275
                        // We have no long name but a namespace, this is a
2276
                        // nested struct.
2277
                        case longName == "" && namespace != "":
52✔
2278
                                key := namespace
52✔
2279
                                if prefix != "" {
67✔
2280
                                        key = prefix + "." + key
15✔
2281
                                }
15✔
2282

2283
                                printConfig(field, key)
52✔
2284

2285
                        // Just a group means this is a dummy struct to house
2286
                        // multiple config values, the group name doesn't go
2287
                        // into the final field name.
2288
                        case longName == "" && group != "":
4✔
2289
                                printConfig(field, prefix)
4✔
2290

2291
                        // Anonymous means embedded struct. We need to recurse
2292
                        // into it but without adding anything to the prefix.
2293
                        case fieldType.Anonymous:
6✔
2294
                                printConfig(field, prefix)
6✔
2295

2296
                        default:
18✔
2297
                                continue
18✔
2298
                        }
2299
                }
2300
        }
2301

2302
        // Turn the whole config struct into a flat map.
2303
        printConfig(reflect.ValueOf(cfg), "")
4✔
2304

4✔
2305
        return result, deprecated, nil
4✔
2306
}
2307

2308
// logWarningsForDeprecation logs a warning if a deprecated config option is
2309
// set.
2310
func logWarningsForDeprecation(cfg Config) {
3✔
2311
        _, deprecated, err := configToFlatMap(cfg)
3✔
2312
        if err != nil {
3✔
2313
                ltndLog.Errorf("Convert configs to map: %v", err)
×
2314
        }
×
2315

2316
        for k := range deprecated {
3✔
2317
                ltndLog.Warnf("Config '%s' is deprecated, please remove it", k)
×
2318
        }
×
2319
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc