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

lightningnetwork / lnd / 10426952143

16 Aug 2024 10:17PM UTC coverage: 49.856% (+0.01%) from 49.843%
10426952143

Pull #8512

github

Roasbeef
lnwallet/chancloser: add unit tests for new rbf coop close
Pull Request #8512: [3/4] - lnwallet/chancloser: add new protofsm based RBF chan closer

6 of 1064 new or added lines in 6 files covered. (0.56%)

159 existing lines in 21 files now uncovered.

96167 of 192890 relevant lines covered (49.86%)

1.55 hits per line

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

53.95
/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
        // Set defaults for a health check which ensures that the leader
173
        // election is functioning correctly. Although this check is off by
174
        // default (as etcd leader election is only used in a clustered setup),
175
        // we still set the default values so that the health check can be
176
        // easily enabled with sane defaults. Note that by default we only run
177
        // this check once, as it is critical for the node's operation.
178
        defaultLeaderCheckInterval = time.Minute
179
        defaultLeaderCheckTimeout  = time.Second * 5
180
        defaultLeaderCheckBackoff  = time.Second * 5
181
        defaultLeaderCheckAttempts = 1
182

183
        // defaultRemoteMaxHtlcs specifies the default limit for maximum
184
        // concurrent HTLCs the remote party may add to commitment transactions.
185
        // This value can be overridden with --default-remote-max-htlcs.
186
        defaultRemoteMaxHtlcs = 483
187

188
        // defaultMaxLocalCSVDelay is the maximum delay we accept on our
189
        // commitment output. The local csv delay maximum is now equal to
190
        // the remote csv delay maximum we require for the remote commitment
191
        // transaction.
192
        defaultMaxLocalCSVDelay = 2016
193

194
        // defaultChannelCommitInterval is the default maximum time between
195
        // receiving a channel state update and signing a new commitment.
196
        defaultChannelCommitInterval = 50 * time.Millisecond
197

198
        // maxChannelCommitInterval is the maximum time the commit interval can
199
        // be configured to.
200
        maxChannelCommitInterval = time.Hour
201

202
        // defaultPendingCommitInterval specifies the default timeout value
203
        // while waiting for the remote party to revoke a locally initiated
204
        // commitment state.
205
        defaultPendingCommitInterval = 1 * time.Minute
206

207
        // maxPendingCommitInterval specifies the max allowed duration when
208
        // waiting for the remote party to revoke a locally initiated
209
        // commitment state.
210
        maxPendingCommitInterval = 5 * time.Minute
211

212
        // defaultChannelCommitBatchSize is the default maximum number of
213
        // channel state updates that is accumulated before signing a new
214
        // commitment.
215
        defaultChannelCommitBatchSize = 10
216

217
        // defaultCoinSelectionStrategy is the coin selection strategy that is
218
        // used by default to fund transactions.
219
        defaultCoinSelectionStrategy = "largest"
220

221
        // defaultKeepFailedPaymentAttempts is the default setting for whether
222
        // to keep failed payments in the database.
223
        defaultKeepFailedPaymentAttempts = false
224

225
        // defaultGrpcServerPingTime is the default duration for the amount of
226
        // time of no activity after which the server pings the client to see if
227
        // the transport is still alive. If set below 1s, a minimum value of 1s
228
        // will be used instead.
229
        defaultGrpcServerPingTime = time.Minute
230

231
        // defaultGrpcServerPingTimeout is the default duration the server waits
232
        // after having pinged for keepalive check, and if no activity is seen
233
        // even after that the connection is closed.
234
        defaultGrpcServerPingTimeout = 20 * time.Second
235

236
        // defaultGrpcClientPingMinWait is the default minimum amount of time a
237
        // client should wait before sending a keepalive ping.
238
        defaultGrpcClientPingMinWait = 5 * time.Second
239

240
        // defaultHTTPHeaderTimeout is the default timeout for HTTP requests.
241
        DefaultHTTPHeaderTimeout = 5 * time.Second
242

243
        // BitcoinChainName is a string that represents the Bitcoin blockchain.
244
        BitcoinChainName = "bitcoin"
245

246
        bitcoindBackendName = "bitcoind"
247
        btcdBackendName     = "btcd"
248
        neutrinoBackendName = "neutrino"
249
)
250

251
var (
252
        // DefaultLndDir is the default directory where lnd tries to find its
253
        // configuration file and store its data. This is a directory in the
254
        // user's application data, for example:
255
        //   C:\Users\<username>\AppData\Local\Lnd on Windows
256
        //   ~/.lnd on Linux
257
        //   ~/Library/Application Support/Lnd on MacOS
258
        DefaultLndDir = btcutil.AppDataDir("lnd", false)
259

260
        // DefaultConfigFile is the default full path of lnd's configuration
261
        // file.
262
        DefaultConfigFile = filepath.Join(DefaultLndDir, lncfg.DefaultConfigFilename)
263

264
        defaultDataDir = filepath.Join(DefaultLndDir, defaultDataDirname)
265
        defaultLogDir  = filepath.Join(DefaultLndDir, defaultLogDirname)
266

267
        defaultTowerDir = filepath.Join(defaultDataDir, defaultTowerSubDirname)
268

269
        defaultTLSCertPath    = filepath.Join(DefaultLndDir, defaultTLSCertFilename)
270
        defaultTLSKeyPath     = filepath.Join(DefaultLndDir, defaultTLSKeyFilename)
271
        defaultLetsEncryptDir = filepath.Join(DefaultLndDir, defaultLetsEncryptDirname)
272

273
        defaultBtcdDir         = btcutil.AppDataDir(btcdBackendName, false)
274
        defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert")
275

276
        defaultBitcoindDir = btcutil.AppDataDir(BitcoinChainName, false)
277

278
        defaultTorSOCKS   = net.JoinHostPort("localhost", strconv.Itoa(defaultTorSOCKSPort))
279
        defaultTorDNS     = net.JoinHostPort(defaultTorDNSHost, strconv.Itoa(defaultTorDNSPort))
280
        defaultTorControl = net.JoinHostPort("localhost", strconv.Itoa(defaultTorControlPort))
281

282
        // bitcoindEsimateModes defines all the legal values for bitcoind's
283
        // estimatesmartfee RPC call.
284
        defaultBitcoindEstimateMode = "CONSERVATIVE"
285
        bitcoindEstimateModes       = [2]string{"ECONOMICAL", defaultBitcoindEstimateMode}
286

287
        defaultPrunedNodeMaxPeers = 4
288
)
289

290
// Config defines the configuration options for lnd.
291
//
292
// See LoadConfig for further details regarding the configuration
293
// loading+parsing process.
294
//
295
//nolint:lll
296
type Config struct {
297
        ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
298

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

304
        TLSCertPath        string        `long:"tlscertpath" description:"Path to write the TLS certificate for lnd's RPC and REST services"`
305
        TLSKeyPath         string        `long:"tlskeypath" description:"Path to write the TLS private key for lnd's RPC and REST services"`
306
        TLSExtraIPs        []string      `long:"tlsextraip" description:"Adds an extra ip to the generated certificate"`
307
        TLSExtraDomains    []string      `long:"tlsextradomain" description:"Adds an extra domain to the generated certificate"`
308
        TLSAutoRefresh     bool          `long:"tlsautorefresh" description:"Re-generate TLS certificate and key if the IPs or domains are changed"`
309
        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"`
310
        TLSCertDuration    time.Duration `long:"tlscertduration" description:"The duration for which the auto-generated TLS certificate will be valid for"`
311
        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"`
312

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

322
        LetsEncryptDir    string `long:"letsencryptdir" description:"The directory to store Let's Encrypt certificates within"`
323
        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."`
324
        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."`
325

326
        // We'll parse these 'raw' string arguments into real net.Addrs in the
327
        // loadConfig function. We need to expose the 'raw' strings so the
328
        // command line library can access them.
329
        // Only the parsed net.Addrs should be used!
330
        RawRPCListeners   []string `long:"rpclisten" description:"Add an interface/port/socket to listen for RPC connections"`
331
        RawRESTListeners  []string `long:"restlisten" description:"Add an interface/port/socket to listen for REST connections"`
332
        RawListeners      []string `long:"listen" description:"Add an interface/port to listen for peer connections"`
333
        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"`
334
        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."`
335
        RPCListeners      []net.Addr
336
        RESTListeners     []net.Addr
337
        RestCORS          []string `long:"restcors" description:"Add an ip:port/hostname to allow cross origin access from. To allow all origins, set as \"*\"."`
338
        Listeners         []net.Addr
339
        ExternalIPs       []net.Addr
340
        DisableListen     bool          `long:"nolisten" description:"Disable listening for incoming peer connections"`
341
        DisableRest       bool          `long:"norest" description:"Disable REST API"`
342
        DisableRestTLS    bool          `long:"no-rest-tls" description:"Disable TLS for REST connections"`
343
        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"`
344
        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"`
345
        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"`
346
        AddPeers          []string      `long:"addpeer" description:"Specify peers to connect to first"`
347
        MinBackoff        time.Duration `long:"minbackoff" description:"Shortest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
348
        MaxBackoff        time.Duration `long:"maxbackoff" description:"Longest backoff when reconnecting to persistent peers. Valid time units are {s, m, h}."`
349
        ConnectionTimeout time.Duration `long:"connectiontimeout" description:"The timeout value for network connections. Valid time units are {ms, s, m, h}."`
350

351
        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"`
352

353
        CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
354

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

357
        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."`
358
        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."`
359

360
        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"`
361
        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."`
362
        MaxPendingChannels int    `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."`
363
        BackupFilePath     string `long:"backupfilepath" description:"The target location of the channel backup file"`
364

365
        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"`
366

367
        Bitcoin      *lncfg.Chain    `group:"Bitcoin" namespace:"bitcoin"`
368
        BtcdMode     *lncfg.Btcd     `group:"btcd" namespace:"btcd"`
369
        BitcoindMode *lncfg.Bitcoind `group:"bitcoind" namespace:"bitcoind"`
370
        NeutrinoMode *lncfg.Neutrino `group:"neutrino" namespace:"neutrino"`
371

372
        BlockCacheSize uint64 `long:"blockcachesize" description:"The maximum capacity of the block cache"`
373

374
        Autopilot *lncfg.AutoPilot `group:"Autopilot" namespace:"autopilot"`
375

376
        Tor *lncfg.Tor `group:"Tor" namespace:"tor"`
377

378
        SubRPCServers *subRPCServerConfigs `group:"subrpc"`
379

380
        Hodl *hodl.Config `group:"hodl" namespace:"hodl"`
381

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

384
        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."`
385
        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."`
386
        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."`
387

388
        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."`
389

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

392
        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."`
393
        TrickleDelay                  int           `long:"trickledelay" description:"Time in milliseconds between each release of announcements to the network"`
394
        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."`
395
        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."`
396
        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."`
397
        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."`
398
        Alias                         string        `long:"alias" description:"The node alias. Used as a moniker by peers and intelligence services"`
399
        Color                         string        `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"`
400
        MinChanSize                   int64         `long:"minchansize" description:"The smallest channel size (in satoshis) that we should accept. Incoming channels smaller than this will be rejected"`
401
        MaxChanSize                   int64         `long:"maxchansize" description:"The largest channel size (in satoshis) that we should accept. Incoming channels larger than this will be rejected"`
402
        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."`
403

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

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

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

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

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

414
        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."`
415

416
        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."`
417
        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."`
418

419
        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."`
420

421
        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."`
422

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

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

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

431
        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"`
432

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

435
        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]."`
436

437
        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"`
438

439
        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."`
440

441
        net tor.Net
442

443
        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."`
444

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

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

449
        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]"`
450

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

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

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

457
        Fee *lncfg.Fee `group:"fee" namespace:"fee"`
458

459
        Invoices *lncfg.Invoices `group:"invoices" namespace:"invoices"`
460

461
        Routing *lncfg.Routing `group:"routing" namespace:"routing"`
462

463
        Gossip *lncfg.Gossip `group:"gossip" namespace:"gossip"`
464

465
        Workers *lncfg.Workers `group:"workers" namespace:"workers"`
466

467
        Caches *lncfg.Caches `group:"caches" namespace:"caches"`
468

469
        Prometheus lncfg.Prometheus `group:"prometheus" namespace:"prometheus"`
470

471
        WtClient *lncfg.WtClient `group:"wtclient" namespace:"wtclient"`
472

473
        Watchtower *lncfg.Watchtower `group:"watchtower" namespace:"watchtower"`
474

475
        ProtocolOptions *lncfg.ProtocolOptions `group:"protocol" namespace:"protocol"`
476

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

479
        HealthChecks *lncfg.HealthCheckConfig `group:"healthcheck" namespace:"healthcheck"`
480

481
        DB *lncfg.DB `group:"db" namespace:"db"`
482

483
        Cluster *lncfg.Cluster `group:"cluster" namespace:"cluster"`
484

485
        RPCMiddleware *lncfg.RPCMiddleware `group:"rpcmiddleware" namespace:"rpcmiddleware"`
486

487
        RemoteSigner *lncfg.RemoteSigner `group:"remotesigner" namespace:"remotesigner"`
488

489
        Sweeper *lncfg.Sweeper `group:"sweeper" namespace:"sweeper"`
490

491
        Htlcswitch *lncfg.Htlcswitch `group:"htlcswitch" namespace:"htlcswitch"`
492

493
        GRPC *GRPCConfig `group:"grpc" namespace:"grpc"`
494

495
        // LogWriter is the root logger that all of the daemon's subloggers are
496
        // hooked up to.
497
        LogWriter *build.RotatingLogWriter
498

499
        // networkDir is the path to the directory of the currently active
500
        // network. This path will hold the files related to each different
501
        // network.
502
        networkDir string
503

504
        // ActiveNetParams contains parameters of the target chain.
505
        ActiveNetParams chainreg.BitcoinNetParams
506

507
        // Estimator is used to estimate routing probabilities.
508
        Estimator routing.Estimator
509

510
        // Dev specifies configs used for integration tests, which is always
511
        // empty if not built with `integration` flag.
512
        Dev *lncfg.DevConfig `group:"dev" namespace:"dev"`
513

514
        // HTTPHeaderTimeout is the maximum duration that the server will wait
515
        // before timing out reading the headers of an HTTP request.
516
        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."`
517
}
518

519
// GRPCConfig holds the configuration options for the gRPC server.
520
// See https://github.com/grpc/grpc-go/blob/v1.41.0/keepalive/keepalive.go#L50
521
// for more details. Any value of 0 means we use the gRPC internal default
522
// values.
523
//
524
//nolint:lll
525
type GRPCConfig struct {
526
        // ServerPingTime is a duration for the amount of time of no activity
527
        // after which the server pings the client to see if the transport is
528
        // still alive. If set below 1s, a minimum value of 1s will be used
529
        // instead.
530
        ServerPingTime time.Duration `long:"server-ping-time" description:"How long the server waits on a gRPC stream with no activity before pinging the client."`
531

532
        // ServerPingTimeout is the duration the server waits after having
533
        // pinged for keepalive check, and if no activity is seen even after
534
        // that the connection is closed.
535
        ServerPingTimeout time.Duration `long:"server-ping-timeout" description:"How long the server waits for the response from the client for the keepalive ping response."`
536

537
        // ClientPingMinWait is the minimum amount of time a client should wait
538
        // before sending a keepalive ping.
539
        ClientPingMinWait time.Duration `long:"client-ping-min-wait" description:"The minimum amount of time the client should wait before sending a keepalive ping."`
540

541
        // ClientAllowPingWithoutStream specifies whether pings from the client
542
        // are allowed even if there are no active gRPC streams. This might be
543
        // useful to keep the underlying HTTP/2 connection open for future
544
        // requests.
545
        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."`
546
}
547

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

3✔
600
                Fee: &lncfg.Fee{
3✔
601
                        MinUpdateTimeout: lncfg.DefaultMinUpdateTimeout,
3✔
602
                        MaxUpdateTimeout: lncfg.DefaultMaxUpdateTimeout,
3✔
603
                },
3✔
604

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

741
// LoadConfig initializes and parses the config using a config file and command
742
// line options.
743
//
744
// The configuration proceeds as follows:
745
//  1. Start with a default config with sane settings
746
//  2. Pre-parse the command line to check for an alternative config file
747
//  3. Load configuration file overwriting defaults with any specified options
748
//  4. Parse CLI options and overwrite/add any specified options
749
func LoadConfig(interceptor signal.Interceptor) (*Config, error) {
3✔
750
        // Pre-parse the command line options to pick up an alternative config
3✔
751
        // file.
3✔
752
        preCfg := DefaultConfig()
3✔
753
        if _, err := flags.Parse(&preCfg); err != nil {
3✔
754
                return nil, err
×
755
        }
×
756

757
        // Show the version and exit if the version flag was specified.
758
        appName := filepath.Base(os.Args[0])
3✔
759
        appName = strings.TrimSuffix(appName, filepath.Ext(appName))
3✔
760
        usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
3✔
761
        if preCfg.ShowVersion {
3✔
762
                fmt.Println(appName, "version", build.Version(),
×
763
                        "commit="+build.Commit)
×
764
                os.Exit(0)
×
765
        }
×
766

767
        // If the config file path has not been modified by the user, then we'll
768
        // use the default config file path. However, if the user has modified
769
        // their lnddir, then we should assume they intend to use the config
770
        // file within it.
771
        configFileDir := CleanAndExpandPath(preCfg.LndDir)
3✔
772
        configFilePath := CleanAndExpandPath(preCfg.ConfigFile)
3✔
773
        switch {
3✔
774
        // User specified --lnddir but no --configfile. Update the config file
775
        // path to the lnd config directory, but don't require it to exist.
776
        case configFileDir != DefaultLndDir &&
777
                configFilePath == DefaultConfigFile:
3✔
778

3✔
779
                configFilePath = filepath.Join(
3✔
780
                        configFileDir, lncfg.DefaultConfigFilename,
3✔
781
                )
3✔
782

783
        // User did specify an explicit --configfile, so we check that it does
784
        // exist under that path to avoid surprises.
785
        case configFilePath != DefaultConfigFile:
×
786
                if !lnrpc.FileExists(configFilePath) {
×
787
                        return nil, fmt.Errorf("specified config file does "+
×
788
                                "not exist in %s", configFilePath)
×
789
                }
×
790
        }
791

792
        // Next, load any additional configuration options from the file.
793
        var configFileError error
3✔
794
        cfg := preCfg
3✔
795
        fileParser := flags.NewParser(&cfg, flags.Default)
3✔
796
        err := flags.NewIniParser(fileParser).ParseFile(configFilePath)
3✔
797
        if err != nil {
6✔
798
                // If it's a parsing related error, then we'll return
3✔
799
                // immediately, otherwise we can proceed as possibly the config
3✔
800
                // file doesn't exist which is OK.
3✔
801
                if lnutils.ErrorAs[*flags.IniError](err) ||
3✔
802
                        lnutils.ErrorAs[*flags.Error](err) {
3✔
803

×
804
                        return nil, err
×
805
                }
×
806

807
                configFileError = err
3✔
808
        }
809

810
        // Finally, parse the remaining command line options again to ensure
811
        // they take precedence.
812
        flagParser := flags.NewParser(&cfg, flags.Default)
3✔
813
        if _, err := flagParser.Parse(); err != nil {
3✔
814
                return nil, err
×
815
        }
×
816

817
        // Make sure everything we just loaded makes sense.
818
        cleanCfg, err := ValidateConfig(
3✔
819
                cfg, interceptor, fileParser, flagParser,
3✔
820
        )
3✔
821
        if usageErr, ok := err.(*usageError); ok {
3✔
822
                // The logging system might not yet be initialized, so we also
×
823
                // write to stderr to make sure the error appears somewhere.
×
824
                _, _ = fmt.Fprintln(os.Stderr, usageMessage)
×
825
                ltndLog.Warnf("Incorrect usage: %v", usageMessage)
×
826

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

×
832
                return nil, usageErr.err
×
833
        }
×
834
        if err != nil {
3✔
835
                // The log subsystem might not yet be initialized. But we still
×
836
                // try to log the error there since some packaging solutions
×
837
                // might only look at the log and not stdout/stderr.
×
838
                ltndLog.Warnf("Error validating config: %v", err)
×
839

×
840
                return nil, err
×
841
        }
×
842

843
        // Warn about missing config file only after all other configuration is
844
        // done. This prevents the warning on help messages and invalid options.
845
        // Note this should go directly before the return.
846
        if configFileError != nil {
6✔
847
                ltndLog.Warnf("%v", configFileError)
3✔
848
        }
3✔
849

850
        // Finally, log warnings for deprecated config options if they are set.
851
        logWarningsForDeprecation(*cleanCfg)
3✔
852

3✔
853
        return cleanCfg, nil
3✔
854
}
855

856
// usageError is an error type that signals a problem with the supplied flags.
857
type usageError struct {
858
        err error
859
}
860

861
// Error returns the error string.
862
//
863
// NOTE: This is part of the error interface.
864
func (u *usageError) Error() string {
×
865
        return u.err.Error()
×
866
}
×
867

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

3✔
874
        // If the provided lnd directory is not the default, we'll modify the
3✔
875
        // path to all of the files and directories that will live within it.
3✔
876
        lndDir := CleanAndExpandPath(cfg.LndDir)
3✔
877
        if lndDir != DefaultLndDir {
6✔
878
                cfg.DataDir = filepath.Join(lndDir, defaultDataDirname)
3✔
879
                cfg.LetsEncryptDir = filepath.Join(
3✔
880
                        lndDir, defaultLetsEncryptDirname,
3✔
881
                )
3✔
882
                cfg.TLSCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
3✔
883
                cfg.TLSKeyPath = filepath.Join(lndDir, defaultTLSKeyFilename)
3✔
884
                cfg.LogDir = filepath.Join(lndDir, defaultLogDirname)
3✔
885

3✔
886
                // If the watchtower's directory is set to the default, i.e. the
3✔
887
                // user has not requested a different location, we'll move the
3✔
888
                // location to be relative to the specified lnd directory.
3✔
889
                if cfg.Watchtower.TowerDir == defaultTowerDir {
6✔
890
                        cfg.Watchtower.TowerDir = filepath.Join(
3✔
891
                                cfg.DataDir, defaultTowerSubDirname,
3✔
892
                        )
3✔
893
                }
3✔
894
        }
895

896
        funcName := "ValidateConfig"
3✔
897
        mkErr := func(format string, args ...interface{}) error {
3✔
898
                return fmt.Errorf(funcName+": "+format, args...)
×
899
        }
×
900
        makeDirectory := func(dir string) error {
6✔
901
                err := os.MkdirAll(dir, 0700)
3✔
902
                if err != nil {
3✔
903
                        // Show a nicer error message if it's because a symlink
×
904
                        // is linked to a directory that does not exist
×
905
                        // (probably because it's not mounted).
×
906
                        if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
×
907
                                link, lerr := os.Readlink(e.Path)
×
908
                                if lerr == nil {
×
909
                                        str := "is symlink %s -> %s mounted?"
×
910
                                        err = fmt.Errorf(str, e.Path, link)
×
911
                                }
×
912
                        }
913

914
                        str := "Failed to create lnd directory '%s': %v"
×
915
                        return mkErr(str, dir, err)
×
916
                }
917

918
                return nil
3✔
919
        }
920

921
        // IsSet returns true if an option has been set in either the config
922
        // file or by a flag.
923
        isSet := func(field string) (bool, error) {
6✔
924
                fieldName, ok := reflect.TypeOf(Config{}).FieldByName(field)
3✔
925
                if !ok {
3✔
926
                        str := "could not find field %s"
×
927
                        return false, mkErr(str, field)
×
928
                }
×
929

930
                long, ok := fieldName.Tag.Lookup("long")
3✔
931
                if !ok {
3✔
932
                        str := "field %s does not have a long tag"
×
933
                        return false, mkErr(str, field)
×
934
                }
×
935

936
                // The user has the option to set the flag in either the config
937
                // file or as a command line flag. If any is set, we consider it
938
                // to be set, not applying any precedence rules here (since it
939
                // is a boolean the default is false anyway which would screw up
940
                // any precedence rules). Additionally, we need to also support
941
                // the use case where the config struct is embedded _within_
942
                // another struct with a prefix (as is the case with
943
                // lightning-terminal).
944
                fileOption := fileParser.FindOptionByLongName(long)
3✔
945
                fileOptionNested := fileParser.FindOptionByLongName(
3✔
946
                        "lnd." + long,
3✔
947
                )
3✔
948
                flagOption := flagParser.FindOptionByLongName(long)
3✔
949
                flagOptionNested := flagParser.FindOptionByLongName(
3✔
950
                        "lnd." + long,
3✔
951
                )
3✔
952

3✔
953
                return (fileOption != nil && fileOption.IsSet()) ||
3✔
954
                                (fileOptionNested != nil && fileOptionNested.IsSet()) ||
3✔
955
                                (flagOption != nil && flagOption.IsSet()) ||
3✔
956
                                (flagOptionNested != nil && flagOptionNested.IsSet()),
3✔
957
                        nil
3✔
958
        }
959

960
        // As soon as we're done parsing configuration options, ensure all paths
961
        // to directories and files are cleaned and expanded before attempting
962
        // to use them later on.
963
        cfg.DataDir = CleanAndExpandPath(cfg.DataDir)
3✔
964
        cfg.TLSCertPath = CleanAndExpandPath(cfg.TLSCertPath)
3✔
965
        cfg.TLSKeyPath = CleanAndExpandPath(cfg.TLSKeyPath)
3✔
966
        cfg.LetsEncryptDir = CleanAndExpandPath(cfg.LetsEncryptDir)
3✔
967
        cfg.AdminMacPath = CleanAndExpandPath(cfg.AdminMacPath)
3✔
968
        cfg.ReadMacPath = CleanAndExpandPath(cfg.ReadMacPath)
3✔
969
        cfg.InvoiceMacPath = CleanAndExpandPath(cfg.InvoiceMacPath)
3✔
970
        cfg.LogDir = CleanAndExpandPath(cfg.LogDir)
3✔
971
        cfg.BtcdMode.Dir = CleanAndExpandPath(cfg.BtcdMode.Dir)
3✔
972
        cfg.BitcoindMode.Dir = CleanAndExpandPath(cfg.BitcoindMode.Dir)
3✔
973
        cfg.BitcoindMode.ConfigPath = CleanAndExpandPath(
3✔
974
                cfg.BitcoindMode.ConfigPath,
3✔
975
        )
3✔
976
        cfg.BitcoindMode.RPCCookie = CleanAndExpandPath(cfg.BitcoindMode.RPCCookie)
3✔
977
        cfg.Tor.PrivateKeyPath = CleanAndExpandPath(cfg.Tor.PrivateKeyPath)
3✔
978
        cfg.Tor.WatchtowerKeyPath = CleanAndExpandPath(cfg.Tor.WatchtowerKeyPath)
3✔
979
        cfg.Watchtower.TowerDir = CleanAndExpandPath(cfg.Watchtower.TowerDir)
3✔
980
        cfg.BackupFilePath = CleanAndExpandPath(cfg.BackupFilePath)
3✔
981
        cfg.WalletUnlockPasswordFile = CleanAndExpandPath(
3✔
982
                cfg.WalletUnlockPasswordFile,
3✔
983
        )
3✔
984

3✔
985
        // Ensure that the user didn't attempt to specify negative values for
3✔
986
        // any of the autopilot params.
3✔
987
        if cfg.Autopilot.MaxChannels < 0 {
3✔
988
                str := "autopilot.maxchannels must be non-negative"
×
989

×
990
                return nil, mkErr(str)
×
991
        }
×
992
        if cfg.Autopilot.Allocation < 0 {
3✔
993
                str := "autopilot.allocation must be non-negative"
×
994

×
995
                return nil, mkErr(str)
×
996
        }
×
997
        if cfg.Autopilot.MinChannelSize < 0 {
3✔
998
                str := "autopilot.minchansize must be non-negative"
×
999

×
1000
                return nil, mkErr(str)
×
1001
        }
×
1002
        if cfg.Autopilot.MaxChannelSize < 0 {
3✔
1003
                str := "autopilot.maxchansize must be non-negative"
×
1004

×
1005
                return nil, mkErr(str)
×
1006
        }
×
1007
        if cfg.Autopilot.MinConfs < 0 {
3✔
1008
                str := "autopilot.minconfs must be non-negative"
×
1009

×
1010
                return nil, mkErr(str)
×
1011
        }
×
1012
        if cfg.Autopilot.ConfTarget < 1 {
3✔
1013
                str := "autopilot.conftarget must be positive"
×
1014

×
1015
                return nil, mkErr(str)
×
1016
        }
×
1017

1018
        // Ensure that the specified values for the min and max channel size
1019
        // are within the bounds of the normal chan size constraints.
1020
        if cfg.Autopilot.MinChannelSize < int64(funding.MinChanFundingSize) {
3✔
1021
                cfg.Autopilot.MinChannelSize = int64(funding.MinChanFundingSize)
×
1022
        }
×
1023
        if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
3✔
1024
                cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
×
1025
        }
×
1026

1027
        if _, err := validateAtplCfg(cfg.Autopilot); err != nil {
3✔
1028
                return nil, mkErr("error validating autopilot: %v", err)
×
1029
        }
×
1030

1031
        // Ensure that --maxchansize is properly handled when set by user.
1032
        // For non-Wumbo channels this limit remains 16777215 satoshis by default
1033
        // as specified in BOLT-02. For wumbo channels this limit is 1,000,000,000.
1034
        // satoshis (10 BTC). Always enforce --maxchansize explicitly set by user.
1035
        // If unset (marked by 0 value), then enforce proper default.
1036
        if cfg.MaxChanSize == 0 {
6✔
1037
                if cfg.ProtocolOptions.Wumbo() {
6✔
1038
                        cfg.MaxChanSize = int64(funding.MaxBtcFundingAmountWumbo)
3✔
1039
                } else {
6✔
1040
                        cfg.MaxChanSize = int64(funding.MaxBtcFundingAmount)
3✔
1041
                }
3✔
1042
        }
1043

1044
        // Ensure that the user specified values for the min and max channel
1045
        // size make sense.
1046
        if cfg.MaxChanSize < cfg.MinChanSize {
3✔
1047
                return nil, mkErr("invalid channel size parameters: "+
×
1048
                        "max channel size %v, must be no less than min chan "+
×
1049
                        "size %v", cfg.MaxChanSize, cfg.MinChanSize,
×
1050
                )
×
1051
        }
×
1052

1053
        // Don't allow superfluous --maxchansize greater than
1054
        // BOLT 02 soft-limit for non-wumbo channel
1055
        if !cfg.ProtocolOptions.Wumbo() &&
3✔
1056
                cfg.MaxChanSize > int64(MaxFundingAmount) {
3✔
1057

×
1058
                return nil, mkErr("invalid channel size parameters: "+
×
1059
                        "maximum channel size %v is greater than maximum "+
×
1060
                        "non-wumbo channel size %v", cfg.MaxChanSize,
×
1061
                        MaxFundingAmount,
×
1062
                )
×
1063
        }
×
1064

1065
        // Ensure that the amount data for revoked commitment transactions is
1066
        // stored if the watchtower client is active.
1067
        if cfg.DB.NoRevLogAmtData && cfg.WtClient.Active {
3✔
1068
                return nil, mkErr("revocation log amount data must be stored " +
×
1069
                        "if the watchtower client is active")
×
1070
        }
×
1071

1072
        // Ensure a valid max channel fee allocation was set.
1073
        if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 {
3✔
1074
                return nil, mkErr("invalid max channel fee allocation: %v, "+
×
1075
                        "must be within (0, 1]", cfg.MaxChannelFeeAllocation)
×
1076
        }
×
1077

1078
        if cfg.MaxCommitFeeRateAnchors < 1 {
3✔
1079
                return nil, mkErr("invalid max commit fee rate anchors: %v, "+
×
1080
                        "must be at least 1 sat/vByte",
×
1081
                        cfg.MaxCommitFeeRateAnchors)
×
1082
        }
×
1083

1084
        // Validate the Tor config parameters.
1085
        socks, err := lncfg.ParseAddressString(
3✔
1086
                cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),
3✔
1087
                cfg.net.ResolveTCPAddr,
3✔
1088
        )
3✔
1089
        if err != nil {
3✔
1090
                return nil, err
×
1091
        }
×
1092
        cfg.Tor.SOCKS = socks.String()
3✔
1093

3✔
1094
        // We'll only attempt to normalize and resolve the DNS host if it hasn't
3✔
1095
        // changed, as it doesn't need to be done for the default.
3✔
1096
        if cfg.Tor.DNS != defaultTorDNS {
3✔
1097
                dns, err := lncfg.ParseAddressString(
×
1098
                        cfg.Tor.DNS, strconv.Itoa(defaultTorDNSPort),
×
1099
                        cfg.net.ResolveTCPAddr,
×
1100
                )
×
1101
                if err != nil {
×
1102
                        return nil, mkErr("error parsing tor dns: %v", err)
×
1103
                }
×
1104
                cfg.Tor.DNS = dns.String()
×
1105
        }
1106

1107
        control, err := lncfg.ParseAddressString(
3✔
1108
                cfg.Tor.Control, strconv.Itoa(defaultTorControlPort),
3✔
1109
                cfg.net.ResolveTCPAddr,
3✔
1110
        )
3✔
1111
        if err != nil {
3✔
1112
                return nil, mkErr("error parsing tor control address: %v", err)
×
1113
        }
×
1114
        cfg.Tor.Control = control.String()
3✔
1115

3✔
1116
        // Ensure that tor socks host:port is not equal to tor control
3✔
1117
        // host:port. This would lead to lnd not starting up properly.
3✔
1118
        if cfg.Tor.SOCKS == cfg.Tor.Control {
3✔
1119
                str := "tor.socks and tor.control can not us the same host:port"
×
1120

×
1121
                return nil, mkErr(str)
×
1122
        }
×
1123

1124
        switch {
3✔
1125
        case cfg.Tor.V2 && cfg.Tor.V3:
×
1126
                return nil, mkErr("either tor.v2 or tor.v3 can be set, " +
×
1127
                        "but not both")
×
1128
        case cfg.DisableListen && (cfg.Tor.V2 || cfg.Tor.V3):
×
1129
                return nil, mkErr("listening must be enabled when enabling " +
×
1130
                        "inbound connections over Tor")
×
1131
        }
1132

1133
        if cfg.Tor.PrivateKeyPath == "" {
6✔
1134
                switch {
3✔
1135
                case cfg.Tor.V2:
×
1136
                        cfg.Tor.PrivateKeyPath = filepath.Join(
×
1137
                                lndDir, defaultTorV2PrivateKeyFilename,
×
1138
                        )
×
1139
                case cfg.Tor.V3:
×
1140
                        cfg.Tor.PrivateKeyPath = filepath.Join(
×
1141
                                lndDir, defaultTorV3PrivateKeyFilename,
×
1142
                        )
×
1143
                }
1144
        }
1145

1146
        if cfg.Tor.WatchtowerKeyPath == "" {
6✔
1147
                switch {
3✔
1148
                case cfg.Tor.V2:
×
1149
                        cfg.Tor.WatchtowerKeyPath = filepath.Join(
×
1150
                                cfg.Watchtower.TowerDir,
×
1151
                                defaultTorV2PrivateKeyFilename,
×
1152
                        )
×
1153
                case cfg.Tor.V3:
×
1154
                        cfg.Tor.WatchtowerKeyPath = filepath.Join(
×
1155
                                cfg.Watchtower.TowerDir,
×
1156
                                defaultTorV3PrivateKeyFilename,
×
1157
                        )
×
1158
                }
1159
        }
1160

1161
        // Set up the network-related functions that will be used throughout
1162
        // the daemon. We use the standard Go "net" package functions by
1163
        // default. If we should be proxying all traffic through Tor, then
1164
        // we'll use the Tor proxy specific functions in order to avoid leaking
1165
        // our real information.
1166
        if cfg.Tor.Active {
3✔
1167
                cfg.net = &tor.ProxyNet{
×
1168
                        SOCKS:                       cfg.Tor.SOCKS,
×
1169
                        DNS:                         cfg.Tor.DNS,
×
1170
                        StreamIsolation:             cfg.Tor.StreamIsolation,
×
1171
                        SkipProxyForClearNetTargets: cfg.Tor.SkipProxyForClearNetTargets,
×
1172
                }
×
1173
        }
×
1174

1175
        if cfg.DisableListen && cfg.NAT {
3✔
1176
                return nil, mkErr("NAT traversal cannot be used when " +
×
1177
                        "listening is disabled")
×
1178
        }
×
1179
        if cfg.NAT && len(cfg.ExternalHosts) != 0 {
3✔
1180
                return nil, mkErr("NAT support and externalhosts are " +
×
1181
                        "mutually exclusive, only one should be selected")
×
1182
        }
×
1183

1184
        // Multiple networks can't be selected simultaneously.  Count
1185
        // number of network flags passed; assign active network params
1186
        // while we're at it.
1187
        numNets := 0
3✔
1188
        if cfg.Bitcoin.MainNet {
3✔
1189
                numNets++
×
1190
                cfg.ActiveNetParams = chainreg.BitcoinMainNetParams
×
1191
        }
×
1192
        if cfg.Bitcoin.TestNet3 {
3✔
1193
                numNets++
×
1194
                cfg.ActiveNetParams = chainreg.BitcoinTestNetParams
×
1195
        }
×
1196
        if cfg.Bitcoin.RegTest {
6✔
1197
                numNets++
3✔
1198
                cfg.ActiveNetParams = chainreg.BitcoinRegTestNetParams
3✔
1199
        }
3✔
1200
        if cfg.Bitcoin.SimNet {
3✔
1201
                numNets++
×
1202
                cfg.ActiveNetParams = chainreg.BitcoinSimNetParams
×
1203

×
1204
                // For simnet, the btcsuite chain params uses a
×
1205
                // cointype of 115. However, we override this in
×
1206
                // chainreg/chainparams.go, but the raw ChainParam
×
1207
                // field is used elsewhere. To ensure everything is
×
1208
                // consistent, we'll also override the cointype within
×
1209
                // the raw params.
×
1210
                targetCoinType := chainreg.BitcoinSigNetParams.CoinType
×
1211
                cfg.ActiveNetParams.Params.HDCoinType = targetCoinType
×
1212
        }
×
1213
        if cfg.Bitcoin.SigNet {
3✔
1214
                numNets++
×
1215
                cfg.ActiveNetParams = chainreg.BitcoinSigNetParams
×
1216

×
1217
                // Let the user overwrite the default signet parameters.
×
1218
                // The challenge defines the actual signet network to
×
1219
                // join and the seed nodes are needed for network
×
1220
                // discovery.
×
1221
                sigNetChallenge := chaincfg.DefaultSignetChallenge
×
1222
                sigNetSeeds := chaincfg.DefaultSignetDNSSeeds
×
1223
                if cfg.Bitcoin.SigNetChallenge != "" {
×
1224
                        challenge, err := hex.DecodeString(
×
1225
                                cfg.Bitcoin.SigNetChallenge,
×
1226
                        )
×
1227
                        if err != nil {
×
1228
                                return nil, mkErr("Invalid "+
×
1229
                                        "signet challenge, hex decode "+
×
1230
                                        "failed: %v", err)
×
1231
                        }
×
1232
                        sigNetChallenge = challenge
×
1233
                }
1234

1235
                if len(cfg.Bitcoin.SigNetSeedNode) > 0 {
×
1236
                        sigNetSeeds = make([]chaincfg.DNSSeed, len(
×
1237
                                cfg.Bitcoin.SigNetSeedNode,
×
1238
                        ))
×
1239
                        for idx, seed := range cfg.Bitcoin.SigNetSeedNode {
×
1240
                                sigNetSeeds[idx] = chaincfg.DNSSeed{
×
1241
                                        Host:         seed,
×
1242
                                        HasFiltering: false,
×
1243
                                }
×
1244
                        }
×
1245
                }
1246

1247
                chainParams := chaincfg.CustomSignetParams(
×
1248
                        sigNetChallenge, sigNetSeeds,
×
1249
                )
×
1250
                cfg.ActiveNetParams.Params = &chainParams
×
1251
        }
1252
        if numNets > 1 {
3✔
1253
                str := "The mainnet, testnet, regtest, simnet and signet " +
×
1254
                        "params can't be used together -- choose one " +
×
1255
                        "of the five"
×
1256

×
1257
                return nil, mkErr(str)
×
1258
        }
×
1259

1260
        // The target network must be provided, otherwise, we won't
1261
        // know how to initialize the daemon.
1262
        if numNets == 0 {
3✔
1263
                str := "either --bitcoin.mainnet, or bitcoin.testnet," +
×
1264
                        "bitcoin.simnet, bitcoin.regtest or bitcoin.signet " +
×
1265
                        "must be specified"
×
1266

×
1267
                return nil, mkErr(str)
×
1268
        }
×
1269

1270
        err = cfg.Bitcoin.Validate(minTimeLockDelta, funding.MinBtcRemoteDelay)
3✔
1271
        if err != nil {
3✔
1272
                return nil, mkErr("error validating bitcoin params: %v", err)
×
1273
        }
×
1274

1275
        switch cfg.Bitcoin.Node {
3✔
1276
        case btcdBackendName:
1✔
1277
                err := parseRPCParams(
1✔
1278
                        cfg.Bitcoin, cfg.BtcdMode, cfg.ActiveNetParams,
1✔
1279
                )
1✔
1280
                if err != nil {
1✔
1281
                        return nil, mkErr("unable to load RPC "+
×
1282
                                "credentials for btcd: %v", err)
×
1283
                }
×
1284
        case bitcoindBackendName:
1✔
1285
                if cfg.Bitcoin.SimNet {
1✔
1286
                        return nil, mkErr("bitcoind does not " +
×
1287
                                "support simnet")
×
1288
                }
×
1289

1290
                err := parseRPCParams(
1✔
1291
                        cfg.Bitcoin, cfg.BitcoindMode, cfg.ActiveNetParams,
1✔
1292
                )
1✔
1293
                if err != nil {
1✔
1294
                        return nil, mkErr("unable to load RPC "+
×
1295
                                "credentials for bitcoind: %v", err)
×
1296
                }
×
1297
        case neutrinoBackendName:
1✔
1298
                // No need to get RPC parameters.
1299

1300
        case "nochainbackend":
×
1301
                // Nothing to configure, we're running without any chain
1302
                // backend whatsoever (pure signing mode).
1303

1304
        default:
×
1305
                str := "only btcd, bitcoind, and neutrino mode " +
×
1306
                        "supported for bitcoin at this time"
×
1307

×
1308
                return nil, mkErr(str)
×
1309
        }
1310

1311
        cfg.Bitcoin.ChainDir = filepath.Join(
3✔
1312
                cfg.DataDir, defaultChainSubDirname, BitcoinChainName,
3✔
1313
        )
3✔
1314

3✔
1315
        // Ensure that the user didn't attempt to specify negative values for
3✔
1316
        // any of the autopilot params.
3✔
1317
        if cfg.Autopilot.MaxChannels < 0 {
3✔
1318
                str := "autopilot.maxchannels must be non-negative"
×
1319

×
1320
                return nil, mkErr(str)
×
1321
        }
×
1322
        if cfg.Autopilot.Allocation < 0 {
3✔
1323
                str := "autopilot.allocation must be non-negative"
×
1324

×
1325
                return nil, mkErr(str)
×
1326
        }
×
1327
        if cfg.Autopilot.MinChannelSize < 0 {
3✔
1328
                str := "autopilot.minchansize must be non-negative"
×
1329

×
1330
                return nil, mkErr(str)
×
1331
        }
×
1332
        if cfg.Autopilot.MaxChannelSize < 0 {
3✔
1333
                str := "autopilot.maxchansize must be non-negative"
×
1334

×
1335
                return nil, mkErr(str)
×
1336
        }
×
1337

1338
        // Ensure that the specified values for the min and max channel size
1339
        // don't are within the bounds of the normal chan size constraints.
1340
        if cfg.Autopilot.MinChannelSize < int64(funding.MinChanFundingSize) {
3✔
1341
                cfg.Autopilot.MinChannelSize = int64(funding.MinChanFundingSize)
×
1342
        }
×
1343
        if cfg.Autopilot.MaxChannelSize > int64(MaxFundingAmount) {
3✔
1344
                cfg.Autopilot.MaxChannelSize = int64(MaxFundingAmount)
×
1345
        }
×
1346

1347
        // Validate profile port or host:port.
1348
        if cfg.Profile != "" {
6✔
1349
                str := "%s: The profile port must be between 1024 and 65535"
3✔
1350

3✔
1351
                // Try to parse Profile as a host:port.
3✔
1352
                _, hostPort, err := net.SplitHostPort(cfg.Profile)
3✔
1353
                if err == nil {
3✔
1354
                        // Determine if the port is valid.
×
1355
                        profilePort, err := strconv.Atoi(hostPort)
×
1356
                        if err != nil || profilePort < 1024 || profilePort > 65535 {
×
1357
                                return nil, &usageError{mkErr(str)}
×
1358
                        }
×
1359
                } else {
3✔
1360
                        // Try to parse Profile as a port.
3✔
1361
                        profilePort, err := strconv.Atoi(cfg.Profile)
3✔
1362
                        if err != nil || profilePort < 1024 || profilePort > 65535 {
3✔
1363
                                return nil, &usageError{mkErr(str)}
×
1364
                        }
×
1365

1366
                        // Since the user just set a port, we will serve debugging
1367
                        // information over localhost.
1368
                        cfg.Profile = net.JoinHostPort("127.0.0.1", cfg.Profile)
3✔
1369
                }
1370
        }
1371

1372
        // We'll now construct the network directory which will be where we
1373
        // store all the data specific to this chain/network.
1374
        cfg.networkDir = filepath.Join(
3✔
1375
                cfg.DataDir, defaultChainSubDirname, BitcoinChainName,
3✔
1376
                lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1377
        )
3✔
1378

3✔
1379
        // If a custom macaroon directory wasn't specified and the data
3✔
1380
        // directory has changed from the default path, then we'll also update
3✔
1381
        // the path for the macaroons to be generated.
3✔
1382
        if cfg.AdminMacPath == "" {
3✔
1383
                cfg.AdminMacPath = filepath.Join(
×
1384
                        cfg.networkDir, defaultAdminMacFilename,
×
1385
                )
×
1386
        }
×
1387
        if cfg.ReadMacPath == "" {
3✔
1388
                cfg.ReadMacPath = filepath.Join(
×
1389
                        cfg.networkDir, defaultReadMacFilename,
×
1390
                )
×
1391
        }
×
1392
        if cfg.InvoiceMacPath == "" {
3✔
1393
                cfg.InvoiceMacPath = filepath.Join(
×
1394
                        cfg.networkDir, defaultInvoiceMacFilename,
×
1395
                )
×
1396
        }
×
1397

1398
        towerDir := filepath.Join(
3✔
1399
                cfg.Watchtower.TowerDir, BitcoinChainName,
3✔
1400
                lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1401
        )
3✔
1402

3✔
1403
        // Create the lnd directory and all other sub-directories if they don't
3✔
1404
        // already exist. This makes sure that directory trees are also created
3✔
1405
        // for files that point to outside the lnddir.
3✔
1406
        dirs := []string{
3✔
1407
                lndDir, cfg.DataDir, cfg.networkDir,
3✔
1408
                cfg.LetsEncryptDir, towerDir, cfg.graphDatabaseDir(),
3✔
1409
                filepath.Dir(cfg.TLSCertPath), filepath.Dir(cfg.TLSKeyPath),
3✔
1410
                filepath.Dir(cfg.AdminMacPath), filepath.Dir(cfg.ReadMacPath),
3✔
1411
                filepath.Dir(cfg.InvoiceMacPath),
3✔
1412
                filepath.Dir(cfg.Tor.PrivateKeyPath),
3✔
1413
                filepath.Dir(cfg.Tor.WatchtowerKeyPath),
3✔
1414
        }
3✔
1415
        for _, dir := range dirs {
6✔
1416
                if err := makeDirectory(dir); err != nil {
3✔
1417
                        return nil, err
×
1418
                }
×
1419
        }
1420

1421
        // Similarly, if a custom back up file path wasn't specified, then
1422
        // we'll update the file location to match our set network directory.
1423
        if cfg.BackupFilePath == "" {
6✔
1424
                cfg.BackupFilePath = filepath.Join(
3✔
1425
                        cfg.networkDir, chanbackup.DefaultBackupFileName,
3✔
1426
                )
3✔
1427
        }
3✔
1428

1429
        // Append the network type to the log directory so it is "namespaced"
1430
        // per network in the same fashion as the data directory.
1431
        cfg.LogDir = filepath.Join(
3✔
1432
                cfg.LogDir, BitcoinChainName,
3✔
1433
                lncfg.NormalizeNetwork(cfg.ActiveNetParams.Name),
3✔
1434
        )
3✔
1435

3✔
1436
        // A log writer must be passed in, otherwise we can't function and would
3✔
1437
        // run into a panic later on.
3✔
1438
        if cfg.LogWriter == nil {
3✔
1439
                return nil, mkErr("log writer missing in config")
×
1440
        }
×
1441

1442
        // Special show command to list supported subsystems and exit.
1443
        if cfg.DebugLevel == "show" {
3✔
1444
                fmt.Println("Supported subsystems",
×
1445
                        cfg.LogWriter.SupportedSubsystems())
×
1446
                os.Exit(0)
×
1447
        }
×
1448

1449
        // Initialize logging at the default logging level.
1450
        SetupLoggers(cfg.LogWriter, interceptor)
3✔
1451
        err = cfg.LogWriter.InitLogRotator(
3✔
1452
                filepath.Join(cfg.LogDir, defaultLogFilename),
3✔
1453
                cfg.MaxLogFileSize, cfg.MaxLogFiles,
3✔
1454
        )
3✔
1455
        if err != nil {
3✔
1456
                str := "log rotation setup failed: %v"
×
1457
                return nil, mkErr(str, err)
×
1458
        }
×
1459

1460
        // Parse, validate, and set debug log level(s).
1461
        err = build.ParseAndSetDebugLevels(cfg.DebugLevel, cfg.LogWriter)
3✔
1462
        if err != nil {
3✔
1463
                str := "error parsing debug level: %v"
×
1464
                return nil, &usageError{mkErr(str, err)}
×
1465
        }
×
1466

1467
        // At least one RPCListener is required. So listen on localhost per
1468
        // default.
1469
        if len(cfg.RawRPCListeners) == 0 {
3✔
1470
                addr := fmt.Sprintf("localhost:%d", defaultRPCPort)
×
1471
                cfg.RawRPCListeners = append(cfg.RawRPCListeners, addr)
×
1472
        }
×
1473

1474
        // Listen on localhost if no REST listeners were specified.
1475
        if len(cfg.RawRESTListeners) == 0 {
3✔
1476
                addr := fmt.Sprintf("localhost:%d", defaultRESTPort)
×
1477
                cfg.RawRESTListeners = append(cfg.RawRESTListeners, addr)
×
1478
        }
×
1479

1480
        // Listen on the default interface/port if no listeners were specified.
1481
        // An empty address string means default interface/address, which on
1482
        // most unix systems is the same as 0.0.0.0. If Tor is active, we
1483
        // default to only listening on localhost for hidden service
1484
        // connections.
1485
        if len(cfg.RawListeners) == 0 {
3✔
1486
                addr := fmt.Sprintf(":%d", defaultPeerPort)
×
1487
                if cfg.Tor.Active && !cfg.Tor.SkipProxyForClearNetTargets {
×
1488
                        addr = fmt.Sprintf("localhost:%d", defaultPeerPort)
×
1489
                }
×
1490
                cfg.RawListeners = append(cfg.RawListeners, addr)
×
1491
        }
1492

1493
        // Add default port to all RPC listener addresses if needed and remove
1494
        // duplicate addresses.
1495
        cfg.RPCListeners, err = lncfg.NormalizeAddresses(
3✔
1496
                cfg.RawRPCListeners, strconv.Itoa(defaultRPCPort),
3✔
1497
                cfg.net.ResolveTCPAddr,
3✔
1498
        )
3✔
1499
        if err != nil {
3✔
1500
                return nil, mkErr("error normalizing RPC listen addrs: %v", err)
×
1501
        }
×
1502

1503
        // Add default port to all REST listener addresses if needed and remove
1504
        // duplicate addresses.
1505
        cfg.RESTListeners, err = lncfg.NormalizeAddresses(
3✔
1506
                cfg.RawRESTListeners, strconv.Itoa(defaultRESTPort),
3✔
1507
                cfg.net.ResolveTCPAddr,
3✔
1508
        )
3✔
1509
        if err != nil {
3✔
1510
                return nil, mkErr("error normalizing REST listen addrs: %v", err)
×
1511
        }
×
1512

1513
        switch {
3✔
1514
        // The no seed backup and auto unlock are mutually exclusive.
1515
        case cfg.NoSeedBackup && cfg.WalletUnlockPasswordFile != "":
×
1516
                return nil, mkErr("cannot set noseedbackup and " +
×
1517
                        "wallet-unlock-password-file at the same time")
×
1518

1519
        // The "allow-create" flag cannot be set without the auto unlock file.
1520
        case cfg.WalletUnlockAllowCreate && cfg.WalletUnlockPasswordFile == "":
×
1521
                return nil, mkErr("cannot set wallet-unlock-allow-create " +
×
1522
                        "without wallet-unlock-password-file")
×
1523

1524
        // If a password file was specified, we need it to exist.
1525
        case cfg.WalletUnlockPasswordFile != "" &&
1526
                !lnrpc.FileExists(cfg.WalletUnlockPasswordFile):
×
1527

×
1528
                return nil, mkErr("wallet unlock password file %s does "+
×
1529
                        "not exist", cfg.WalletUnlockPasswordFile)
×
1530
        }
1531

1532
        // For each of the RPC listeners (REST+gRPC), we'll ensure that users
1533
        // have specified a safe combo for authentication. If not, we'll bail
1534
        // out with an error. Since we don't allow disabling TLS for gRPC
1535
        // connections we pass in tlsActive=true.
1536
        err = lncfg.EnforceSafeAuthentication(
3✔
1537
                cfg.RPCListeners, !cfg.NoMacaroons, true,
3✔
1538
        )
3✔
1539
        if err != nil {
3✔
1540
                return nil, mkErr("error enforcing safe authentication on "+
×
1541
                        "RPC ports: %v", err)
×
1542
        }
×
1543

1544
        if cfg.DisableRest {
3✔
1545
                ltndLog.Infof("REST API is disabled!")
×
1546
                cfg.RESTListeners = nil
×
1547
        } else {
3✔
1548
                err = lncfg.EnforceSafeAuthentication(
3✔
1549
                        cfg.RESTListeners, !cfg.NoMacaroons, !cfg.DisableRestTLS,
3✔
1550
                )
3✔
1551
                if err != nil {
3✔
1552
                        return nil, mkErr("error enforcing safe "+
×
1553
                                "authentication on REST ports: %v", err)
×
1554
                }
×
1555
        }
1556

1557
        // Remove the listening addresses specified if listening is disabled.
1558
        if cfg.DisableListen {
6✔
1559
                ltndLog.Infof("Listening on the p2p interface is disabled!")
3✔
1560
                cfg.Listeners = nil
3✔
1561
                cfg.ExternalIPs = nil
3✔
1562
        } else {
6✔
1563

3✔
1564
                // Add default port to all listener addresses if needed and remove
3✔
1565
                // duplicate addresses.
3✔
1566
                cfg.Listeners, err = lncfg.NormalizeAddresses(
3✔
1567
                        cfg.RawListeners, strconv.Itoa(defaultPeerPort),
3✔
1568
                        cfg.net.ResolveTCPAddr,
3✔
1569
                )
3✔
1570
                if err != nil {
3✔
1571
                        return nil, mkErr("error normalizing p2p listen "+
×
1572
                                "addrs: %v", err)
×
1573
                }
×
1574

1575
                // Add default port to all external IP addresses if needed and remove
1576
                // duplicate addresses.
1577
                cfg.ExternalIPs, err = lncfg.NormalizeAddresses(
3✔
1578
                        cfg.RawExternalIPs, strconv.Itoa(defaultPeerPort),
3✔
1579
                        cfg.net.ResolveTCPAddr,
3✔
1580
                )
3✔
1581
                if err != nil {
3✔
1582
                        return nil, err
×
1583
                }
×
1584

1585
                // For the p2p port it makes no sense to listen to an Unix socket.
1586
                // Also, we would need to refactor the brontide listener to support
1587
                // that.
1588
                for _, p2pListener := range cfg.Listeners {
6✔
1589
                        if lncfg.IsUnix(p2pListener) {
3✔
1590
                                return nil, mkErr("unix socket addresses "+
×
1591
                                        "cannot be used for the p2p "+
×
1592
                                        "connection listener: %s", p2pListener)
×
1593
                        }
×
1594
                }
1595
        }
1596

1597
        // Ensure that the specified minimum backoff is below or equal to the
1598
        // maximum backoff.
1599
        if cfg.MinBackoff > cfg.MaxBackoff {
3✔
1600
                return nil, mkErr("maxbackoff must be greater than minbackoff")
×
1601
        }
×
1602

1603
        // Newer versions of lnd added a new sub-config for bolt-specific
1604
        // parameters. However, we want to also allow existing users to use the
1605
        // value on the top-level config. If the outer config value is set,
1606
        // then we'll use that directly.
1607
        flagSet, err := isSet("SyncFreelist")
3✔
1608
        if err != nil {
3✔
1609
                return nil, mkErr("error parsing freelist sync flag: %v", err)
×
1610
        }
×
1611
        if flagSet {
3✔
1612
                cfg.DB.Bolt.NoFreelistSync = !cfg.SyncFreelist
×
1613
        }
×
1614

1615
        // Parse any extra sqlite pragma options that may have been provided
1616
        // to determine if they override any of the defaults that we will
1617
        // otherwise add.
1618
        var (
3✔
1619
                defaultSynchronous = true
3✔
1620
                defaultAutoVacuum  = true
3✔
1621
                defaultFullfsync   = true
3✔
1622
        )
3✔
1623
        for _, option := range cfg.DB.Sqlite.PragmaOptions {
3✔
1624
                switch {
×
1625
                case strings.HasPrefix(option, "synchronous="):
×
1626
                        defaultSynchronous = false
×
1627

1628
                case strings.HasPrefix(option, "auto_vacuum="):
×
1629
                        defaultAutoVacuum = false
×
1630

1631
                case strings.HasPrefix(option, "fullfsync="):
×
1632
                        defaultFullfsync = false
×
1633

1634
                default:
×
1635
                }
1636
        }
1637

1638
        if defaultSynchronous {
6✔
1639
                cfg.DB.Sqlite.PragmaOptions = append(
3✔
1640
                        cfg.DB.Sqlite.PragmaOptions, "synchronous=full",
3✔
1641
                )
3✔
1642
        }
3✔
1643

1644
        if defaultAutoVacuum {
6✔
1645
                cfg.DB.Sqlite.PragmaOptions = append(
3✔
1646
                        cfg.DB.Sqlite.PragmaOptions, "auto_vacuum=incremental",
3✔
1647
                )
3✔
1648
        }
3✔
1649

1650
        if defaultFullfsync {
6✔
1651
                cfg.DB.Sqlite.PragmaOptions = append(
3✔
1652
                        cfg.DB.Sqlite.PragmaOptions, "fullfsync=true",
3✔
1653
                )
3✔
1654
        }
3✔
1655

1656
        // Ensure that the user hasn't chosen a remote-max-htlc value greater
1657
        // than the protocol maximum.
1658
        maxRemoteHtlcs := uint16(input.MaxHTLCNumber / 2)
3✔
1659
        if cfg.DefaultRemoteMaxHtlcs > maxRemoteHtlcs {
3✔
1660
                return nil, mkErr("default-remote-max-htlcs (%v) must be "+
×
1661
                        "less than %v", cfg.DefaultRemoteMaxHtlcs,
×
1662
                        maxRemoteHtlcs)
×
1663
        }
×
1664

1665
        // Clamp the ChannelCommitInterval so that commitment updates can still
1666
        // happen in a reasonable timeframe.
1667
        if cfg.ChannelCommitInterval > maxChannelCommitInterval {
3✔
1668
                return nil, mkErr("channel-commit-interval (%v) must be less "+
×
1669
                        "than %v", cfg.ChannelCommitInterval,
×
1670
                        maxChannelCommitInterval)
×
1671
        }
×
1672

1673
        // Limit PendingCommitInterval so we don't wait too long for the remote
1674
        // party to send back a revoke.
1675
        if cfg.PendingCommitInterval > maxPendingCommitInterval {
3✔
1676
                return nil, mkErr("pending-commit-interval (%v) must be less "+
×
1677
                        "than %v", cfg.PendingCommitInterval,
×
1678
                        maxPendingCommitInterval)
×
1679
        }
×
1680

1681
        if err := cfg.Gossip.Parse(); err != nil {
3✔
1682
                return nil, mkErr("error parsing gossip syncer: %v", err)
×
1683
        }
×
1684

1685
        // If the experimental protocol options specify any protocol messages
1686
        // that we want to handle as custom messages, set them now.
1687
        customMsg := cfg.ProtocolOptions.CustomMessageOverrides()
3✔
1688

3✔
1689
        // We can safely set our custom override values during startup because
3✔
1690
        // startup is blocked on config parsing.
3✔
1691
        if err := lnwire.SetCustomOverrides(customMsg); err != nil {
3✔
1692
                return nil, mkErr("custom-message: %v", err)
×
1693
        }
×
1694

1695
        // Validate the subconfigs for workers, caches, and the tower client.
1696
        err = lncfg.Validate(
3✔
1697
                cfg.Workers,
3✔
1698
                cfg.Caches,
3✔
1699
                cfg.WtClient,
3✔
1700
                cfg.DB,
3✔
1701
                cfg.Cluster,
3✔
1702
                cfg.HealthChecks,
3✔
1703
                cfg.RPCMiddleware,
3✔
1704
                cfg.RemoteSigner,
3✔
1705
                cfg.Sweeper,
3✔
1706
                cfg.Htlcswitch,
3✔
1707
                cfg.Invoices,
3✔
1708
                cfg.Routing,
3✔
1709
        )
3✔
1710
        if err != nil {
3✔
1711
                return nil, err
×
1712
        }
×
1713

1714
        // Finally, ensure that the user's color is correctly formatted,
1715
        // otherwise the server will not be able to start after the unlocking
1716
        // the wallet.
1717
        _, err = lncfg.ParseHexColor(cfg.Color)
3✔
1718
        if err != nil {
3✔
1719
                return nil, mkErr("unable to parse node color: %v", err)
×
1720
        }
×
1721

1722
        // All good, return the sanitized result.
1723
        return &cfg, nil
3✔
1724
}
1725

1726
// graphDatabaseDir returns the default directory where the local bolt graph db
1727
// files are stored.
1728
func (c *Config) graphDatabaseDir() string {
3✔
1729
        return filepath.Join(
3✔
1730
                c.DataDir, defaultGraphSubDirname,
3✔
1731
                lncfg.NormalizeNetwork(c.ActiveNetParams.Name),
3✔
1732
        )
3✔
1733
}
3✔
1734

1735
// ImplementationConfig returns the configuration of what actual implementations
1736
// should be used when creating the main lnd instance.
1737
func (c *Config) ImplementationConfig(
1738
        interceptor signal.Interceptor) *ImplementationCfg {
3✔
1739

3✔
1740
        // If we're using a remote signer, we still need the base wallet as a
3✔
1741
        // watch-only source of chain and address data. But we don't need any
3✔
1742
        // private key material in that btcwallet base wallet.
3✔
1743
        if c.RemoteSigner.Enable {
6✔
1744
                rpcImpl := NewRPCSignerWalletImpl(
3✔
1745
                        c, ltndLog, interceptor,
3✔
1746
                        c.RemoteSigner.MigrateWatchOnly,
3✔
1747
                )
3✔
1748
                return &ImplementationCfg{
3✔
1749
                        GrpcRegistrar:     rpcImpl,
3✔
1750
                        RestRegistrar:     rpcImpl,
3✔
1751
                        ExternalValidator: rpcImpl,
3✔
1752
                        DatabaseBuilder: NewDefaultDatabaseBuilder(
3✔
1753
                                c, ltndLog,
3✔
1754
                        ),
3✔
1755
                        WalletConfigBuilder: rpcImpl,
3✔
1756
                        ChainControlBuilder: rpcImpl,
3✔
1757
                }
3✔
1758
        }
3✔
1759

1760
        defaultImpl := NewDefaultWalletImpl(c, ltndLog, interceptor, false)
3✔
1761
        return &ImplementationCfg{
3✔
1762
                GrpcRegistrar:       defaultImpl,
3✔
1763
                RestRegistrar:       defaultImpl,
3✔
1764
                ExternalValidator:   defaultImpl,
3✔
1765
                DatabaseBuilder:     NewDefaultDatabaseBuilder(c, ltndLog),
3✔
1766
                WalletConfigBuilder: defaultImpl,
3✔
1767
                ChainControlBuilder: defaultImpl,
3✔
1768
        }
3✔
1769
}
1770

1771
// CleanAndExpandPath expands environment variables and leading ~ in the
1772
// passed path, cleans the result, and returns it.
1773
// This function is taken from https://github.com/btcsuite/btcd
1774
func CleanAndExpandPath(path string) string {
3✔
1775
        if path == "" {
6✔
1776
                return ""
3✔
1777
        }
3✔
1778

1779
        // Expand initial ~ to OS specific home directory.
1780
        if strings.HasPrefix(path, "~") {
3✔
1781
                var homeDir string
×
1782
                u, err := user.Current()
×
1783
                if err == nil {
×
1784
                        homeDir = u.HomeDir
×
1785
                } else {
×
1786
                        homeDir = os.Getenv("HOME")
×
1787
                }
×
1788

1789
                path = strings.Replace(path, "~", homeDir, 1)
×
1790
        }
1791

1792
        // NOTE: The os.ExpandEnv doesn't work with Windows-style %VARIABLE%,
1793
        // but the variables can still be expanded via POSIX-style $VARIABLE.
1794
        return filepath.Clean(os.ExpandEnv(path))
3✔
1795
}
1796

1797
func parseRPCParams(cConfig *lncfg.Chain, nodeConfig interface{},
1798
        netParams chainreg.BitcoinNetParams) error {
2✔
1799

2✔
1800
        // First, we'll check our node config to make sure the RPC parameters
2✔
1801
        // were set correctly. We'll also determine the path to the conf file
2✔
1802
        // depending on the backend node.
2✔
1803
        var daemonName, confDir, confFile, confFileBase string
2✔
1804
        switch conf := nodeConfig.(type) {
2✔
1805
        case *lncfg.Btcd:
1✔
1806
                // Resolves environment variable references in RPCUser and
1✔
1807
                // RPCPass fields.
1✔
1808
                conf.RPCUser = supplyEnvValue(conf.RPCUser)
1✔
1809
                conf.RPCPass = supplyEnvValue(conf.RPCPass)
1✔
1810

1✔
1811
                // If both RPCUser and RPCPass are set, we assume those
1✔
1812
                // credentials are good to use.
1✔
1813
                if conf.RPCUser != "" && conf.RPCPass != "" {
2✔
1814
                        return nil
1✔
1815
                }
1✔
1816

1817
                // Set the daemon name for displaying proper errors.
1818
                daemonName = btcdBackendName
×
1819
                confDir = conf.Dir
×
1820
                confFileBase = btcdBackendName
×
1821

×
1822
                // If only ONE of RPCUser or RPCPass is set, we assume the
×
1823
                // user did that unintentionally.
×
1824
                if conf.RPCUser != "" || conf.RPCPass != "" {
×
1825
                        return fmt.Errorf("please set both or neither of "+
×
1826
                                "%[1]v.rpcuser, %[1]v.rpcpass", daemonName)
×
1827
                }
×
1828

1829
        case *lncfg.Bitcoind:
1✔
1830
                // Ensure that if the ZMQ options are set, that they are not
1✔
1831
                // equal.
1✔
1832
                if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
1✔
UNCOV
1833
                        err := checkZMQOptions(
×
UNCOV
1834
                                conf.ZMQPubRawBlock, conf.ZMQPubRawTx,
×
UNCOV
1835
                        )
×
UNCOV
1836
                        if err != nil {
×
1837
                                return err
×
1838
                        }
×
1839
                }
1840

1841
                // Ensure that if the estimate mode is set, that it is a legal
1842
                // value.
1843
                if conf.EstimateMode != "" {
2✔
1844
                        err := checkEstimateMode(conf.EstimateMode)
1✔
1845
                        if err != nil {
1✔
1846
                                return err
×
1847
                        }
×
1848
                }
1849

1850
                // Set the daemon name for displaying proper errors.
1851
                daemonName = bitcoindBackendName
1✔
1852
                confDir = conf.Dir
1✔
1853
                confFile = conf.ConfigPath
1✔
1854
                confFileBase = BitcoinChainName
1✔
1855

1✔
1856
                // Resolves environment variable references in RPCUser
1✔
1857
                // and RPCPass fields.
1✔
1858
                conf.RPCUser = supplyEnvValue(conf.RPCUser)
1✔
1859
                conf.RPCPass = supplyEnvValue(conf.RPCPass)
1✔
1860

1✔
1861
                // Check that cookie and credentials don't contradict each
1✔
1862
                // other.
1✔
1863
                if (conf.RPCUser != "" || conf.RPCPass != "") &&
1✔
1864
                        conf.RPCCookie != "" {
1✔
1865

×
1866
                        return fmt.Errorf("please only provide either "+
×
1867
                                "%[1]v.rpccookie or %[1]v.rpcuser and "+
×
1868
                                "%[1]v.rpcpass", daemonName)
×
1869
                }
×
1870

1871
                // We convert the cookie into a user name and password.
1872
                if conf.RPCCookie != "" {
1✔
1873
                        cookie, err := os.ReadFile(conf.RPCCookie)
×
1874
                        if err != nil {
×
1875
                                return fmt.Errorf("cannot read cookie file: %w",
×
1876
                                        err)
×
1877
                        }
×
1878

1879
                        splitCookie := strings.Split(string(cookie), ":")
×
1880
                        if len(splitCookie) != 2 {
×
1881
                                return fmt.Errorf("cookie file has a wrong " +
×
1882
                                        "format")
×
1883
                        }
×
1884
                        conf.RPCUser = splitCookie[0]
×
1885
                        conf.RPCPass = splitCookie[1]
×
1886
                }
1887

1888
                if conf.RPCUser != "" && conf.RPCPass != "" {
2✔
1889
                        // If all of RPCUser, RPCPass, ZMQBlockHost, and
1✔
1890
                        // ZMQTxHost are set, we assume those parameters are
1✔
1891
                        // good to use.
1✔
1892
                        if conf.ZMQPubRawBlock != "" && conf.ZMQPubRawTx != "" {
1✔
UNCOV
1893
                                return nil
×
UNCOV
1894
                        }
×
1895

1896
                        // If RPCUser and RPCPass are set and RPCPolling is
1897
                        // enabled, we assume the parameters are good to use.
1898
                        if conf.RPCPolling {
2✔
1899
                                return nil
1✔
1900
                        }
1✔
1901
                }
1902

1903
                // If not all of the parameters are set, we'll assume the user
1904
                // did this unintentionally.
1905
                if conf.RPCUser != "" || conf.RPCPass != "" ||
×
1906
                        conf.ZMQPubRawBlock != "" || conf.ZMQPubRawTx != "" {
×
1907

×
1908
                        return fmt.Errorf("please set %[1]v.rpcuser and "+
×
1909
                                "%[1]v.rpcpass (or %[1]v.rpccookie) together "+
×
1910
                                "with %[1]v.zmqpubrawblock, %[1]v.zmqpubrawtx",
×
1911
                                daemonName)
×
1912
                }
×
1913
        }
1914

1915
        // If we're in simnet mode, then the running btcd instance won't read
1916
        // the RPC credentials from the configuration. So if lnd wasn't
1917
        // specified the parameters, then we won't be able to start.
1918
        if cConfig.SimNet {
×
1919
                return fmt.Errorf("rpcuser and rpcpass must be set to your " +
×
1920
                        "btcd node's RPC parameters for simnet mode")
×
1921
        }
×
1922

1923
        fmt.Println("Attempting automatic RPC configuration to " + daemonName)
×
1924

×
1925
        if confFile == "" {
×
1926
                confFile = filepath.Join(confDir, fmt.Sprintf("%v.conf",
×
1927
                        confFileBase))
×
1928
        }
×
1929
        switch cConfig.Node {
×
1930
        case btcdBackendName:
×
1931
                nConf := nodeConfig.(*lncfg.Btcd)
×
1932
                rpcUser, rpcPass, err := extractBtcdRPCParams(confFile)
×
1933
                if err != nil {
×
1934
                        return fmt.Errorf("unable to extract RPC credentials: "+
×
1935
                                "%v, cannot start w/o RPC connection", err)
×
1936
                }
×
1937
                nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
×
1938

1939
        case bitcoindBackendName:
×
1940
                nConf := nodeConfig.(*lncfg.Bitcoind)
×
1941
                rpcUser, rpcPass, zmqBlockHost, zmqTxHost, err :=
×
1942
                        extractBitcoindRPCParams(netParams.Params.Name,
×
1943
                                nConf.Dir, confFile, nConf.RPCCookie)
×
1944
                if err != nil {
×
1945
                        return fmt.Errorf("unable to extract RPC credentials: "+
×
1946
                                "%v, cannot start w/o RPC connection", err)
×
1947
                }
×
1948
                nConf.RPCUser, nConf.RPCPass = rpcUser, rpcPass
×
1949
                nConf.ZMQPubRawBlock, nConf.ZMQPubRawTx = zmqBlockHost, zmqTxHost
×
1950
        }
1951

1952
        fmt.Printf("Automatically obtained %v's RPC credentials\n", daemonName)
×
1953
        return nil
×
1954
}
1955

1956
// supplyEnvValue supplies the value of an environment variable from a string.
1957
// It supports the following formats:
1958
// 1) $ENV_VAR
1959
// 2) ${ENV_VAR}
1960
// 3) ${ENV_VAR:-DEFAULT}
1961
//
1962
// Standard environment variable naming conventions:
1963
// - ENV_VAR contains letters, digits, and underscores, and does
1964
// not start with a digit.
1965
// - DEFAULT follows the rule that it can contain any characters except
1966
// whitespace.
1967
//
1968
// Parameters:
1969
// - value: The input string containing references to environment variables
1970
// (if any).
1971
//
1972
// Returns:
1973
// - string: The value of the specified environment variable, the default
1974
// value if provided, or the original input string if no matching variable is
1975
// found or set.
1976
func supplyEnvValue(value string) string {
2✔
1977
        // Regex for $ENV_VAR format.
2✔
1978
        var reEnvVar = regexp.MustCompile(`^\$([a-zA-Z_][a-zA-Z0-9_]*)$`)
2✔
1979

2✔
1980
        // Regex for ${ENV_VAR} format.
2✔
1981
        var reEnvVarWithBrackets = regexp.MustCompile(
2✔
1982
                `^\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}$`,
2✔
1983
        )
2✔
1984

2✔
1985
        // Regex for ${ENV_VAR:-DEFAULT} format.
2✔
1986
        var reEnvVarWithDefault = regexp.MustCompile(
2✔
1987
                `^\$\{([a-zA-Z_][a-zA-Z0-9_]*):-([\S]+)\}$`,
2✔
1988
        )
2✔
1989

2✔
1990
        // Match against supported formats.
2✔
1991
        switch {
2✔
1992
        case reEnvVarWithDefault.MatchString(value):
×
1993
                matches := reEnvVarWithDefault.FindStringSubmatch(value)
×
1994
                envVariable := matches[1]
×
1995
                defaultValue := matches[2]
×
1996
                if envValue := os.Getenv(envVariable); envValue != "" {
×
1997
                        return envValue
×
1998
                }
×
1999

2000
                return defaultValue
×
2001

2002
        case reEnvVarWithBrackets.MatchString(value):
×
2003
                matches := reEnvVarWithBrackets.FindStringSubmatch(value)
×
2004
                envVariable := matches[1]
×
2005
                envValue := os.Getenv(envVariable)
×
2006

×
2007
                return envValue
×
2008

2009
        case reEnvVar.MatchString(value):
×
2010
                matches := reEnvVar.FindStringSubmatch(value)
×
2011
                envVariable := matches[1]
×
2012
                envValue := os.Getenv(envVariable)
×
2013

×
2014
                return envValue
×
2015
        }
2016

2017
        return value
2✔
2018
}
2019

2020
// extractBtcdRPCParams attempts to extract the RPC credentials for an existing
2021
// btcd instance. The passed path is expected to be the location of btcd's
2022
// application data directory on the target system.
2023
func extractBtcdRPCParams(btcdConfigPath string) (string, string, error) {
×
2024
        // First, we'll open up the btcd configuration file found at the target
×
2025
        // destination.
×
2026
        btcdConfigFile, err := os.Open(btcdConfigPath)
×
2027
        if err != nil {
×
2028
                return "", "", err
×
2029
        }
×
2030
        defer func() { _ = btcdConfigFile.Close() }()
×
2031

2032
        // With the file open extract the contents of the configuration file so
2033
        // we can attempt to locate the RPC credentials.
2034
        configContents, err := io.ReadAll(btcdConfigFile)
×
2035
        if err != nil {
×
2036
                return "", "", err
×
2037
        }
×
2038

2039
        // Attempt to locate the RPC user using a regular expression. If we
2040
        // don't have a match for our regular expression then we'll exit with
2041
        // an error.
2042
        rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
×
2043
        if err != nil {
×
2044
                return "", "", err
×
2045
        }
×
2046
        userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
×
2047
        if userSubmatches == nil {
×
2048
                return "", "", fmt.Errorf("unable to find rpcuser in config")
×
2049
        }
×
2050

2051
        // Similarly, we'll use another regular expression to find the set
2052
        // rpcpass (if any). If we can't find the pass, then we'll exit with an
2053
        // error.
2054
        rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpass\s*=\s*([^\s]+)`)
×
2055
        if err != nil {
×
2056
                return "", "", err
×
2057
        }
×
2058
        passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
×
2059
        if passSubmatches == nil {
×
2060
                return "", "", fmt.Errorf("unable to find rpcuser in config")
×
2061
        }
×
2062

2063
        return supplyEnvValue(string(userSubmatches[1])),
×
2064
                supplyEnvValue(string(passSubmatches[1])), nil
×
2065
}
2066

2067
// extractBitcoindRPCParams attempts to extract the RPC credentials for an
2068
// existing bitcoind node instance. The routine looks for a cookie first,
2069
// optionally following the datadir configuration option in the bitcoin.conf. If
2070
// it doesn't find one, it looks for rpcuser/rpcpassword.
2071
func extractBitcoindRPCParams(networkName, bitcoindDataDir, bitcoindConfigPath,
2072
        rpcCookiePath string) (string, string, string, string, error) {
×
2073

×
2074
        // First, we'll open up the bitcoind configuration file found at the
×
2075
        // target destination.
×
2076
        bitcoindConfigFile, err := os.Open(bitcoindConfigPath)
×
2077
        if err != nil {
×
2078
                return "", "", "", "", err
×
2079
        }
×
2080
        defer func() { _ = bitcoindConfigFile.Close() }()
×
2081

2082
        // With the file open extract the contents of the configuration file so
2083
        // we can attempt to locate the RPC credentials.
2084
        configContents, err := io.ReadAll(bitcoindConfigFile)
×
2085
        if err != nil {
×
2086
                return "", "", "", "", err
×
2087
        }
×
2088

2089
        // First, we'll look for the ZMQ hosts providing raw block and raw
2090
        // transaction notifications.
2091
        zmqBlockHostRE, err := regexp.Compile(
×
2092
                `(?m)^\s*zmqpubrawblock\s*=\s*([^\s]+)`,
×
2093
        )
×
2094
        if err != nil {
×
2095
                return "", "", "", "", err
×
2096
        }
×
2097
        zmqBlockHostSubmatches := zmqBlockHostRE.FindSubmatch(configContents)
×
2098
        if len(zmqBlockHostSubmatches) < 2 {
×
2099
                return "", "", "", "", fmt.Errorf("unable to find " +
×
2100
                        "zmqpubrawblock in config")
×
2101
        }
×
2102
        zmqTxHostRE, err := regexp.Compile(`(?m)^\s*zmqpubrawtx\s*=\s*([^\s]+)`)
×
2103
        if err != nil {
×
2104
                return "", "", "", "", err
×
2105
        }
×
2106
        zmqTxHostSubmatches := zmqTxHostRE.FindSubmatch(configContents)
×
2107
        if len(zmqTxHostSubmatches) < 2 {
×
2108
                return "", "", "", "", errors.New("unable to find zmqpubrawtx " +
×
2109
                        "in config")
×
2110
        }
×
2111
        zmqBlockHost := string(zmqBlockHostSubmatches[1])
×
2112
        zmqTxHost := string(zmqTxHostSubmatches[1])
×
2113
        if err := checkZMQOptions(zmqBlockHost, zmqTxHost); err != nil {
×
2114
                return "", "", "", "", err
×
2115
        }
×
2116

2117
        // Next, we'll try to find an auth cookie. We need to detect the chain
2118
        // by seeing if one is specified in the configuration file.
2119
        dataDir := filepath.Dir(bitcoindConfigPath)
×
2120
        if bitcoindDataDir != "" {
×
2121
                dataDir = bitcoindDataDir
×
2122
        }
×
2123
        dataDirRE, err := regexp.Compile(`(?m)^\s*datadir\s*=\s*([^\s]+)`)
×
2124
        if err != nil {
×
2125
                return "", "", "", "", err
×
2126
        }
×
2127
        dataDirSubmatches := dataDirRE.FindSubmatch(configContents)
×
2128
        if dataDirSubmatches != nil {
×
2129
                dataDir = string(dataDirSubmatches[1])
×
2130
        }
×
2131

2132
        var chainDir string
×
2133
        switch networkName {
×
2134
        case "mainnet":
×
2135
                chainDir = ""
×
2136
        case "regtest", "testnet3", "signet":
×
2137
                chainDir = networkName
×
2138
        default:
×
2139
                return "", "", "", "", fmt.Errorf("unexpected networkname %v", networkName)
×
2140
        }
2141

2142
        cookiePath := filepath.Join(dataDir, chainDir, ".cookie")
×
2143
        if rpcCookiePath != "" {
×
2144
                cookiePath = rpcCookiePath
×
2145
        }
×
2146
        cookie, err := os.ReadFile(cookiePath)
×
2147
        if err == nil {
×
2148
                splitCookie := strings.Split(string(cookie), ":")
×
2149
                if len(splitCookie) == 2 {
×
2150
                        return splitCookie[0], splitCookie[1], zmqBlockHost,
×
2151
                                zmqTxHost, nil
×
2152
                }
×
2153
        }
2154

2155
        // We didn't find a cookie, so we attempt to locate the RPC user using
2156
        // a regular expression. If we  don't have a match for our regular
2157
        // expression then we'll exit with an error.
2158
        rpcUserRegexp, err := regexp.Compile(`(?m)^\s*rpcuser\s*=\s*([^\s]+)`)
×
2159
        if err != nil {
×
2160
                return "", "", "", "", err
×
2161
        }
×
2162
        userSubmatches := rpcUserRegexp.FindSubmatch(configContents)
×
2163

×
2164
        // Similarly, we'll use another regular expression to find the set
×
2165
        // rpcpass (if any). If we can't find the pass, then we'll exit with an
×
2166
        // error.
×
2167
        rpcPassRegexp, err := regexp.Compile(`(?m)^\s*rpcpassword\s*=\s*([^\s]+)`)
×
2168
        if err != nil {
×
2169
                return "", "", "", "", err
×
2170
        }
×
2171
        passSubmatches := rpcPassRegexp.FindSubmatch(configContents)
×
2172

×
2173
        // Exit with an error if the cookie file, is defined in config, and
×
2174
        // can not be found, with both rpcuser and rpcpassword undefined.
×
2175
        if rpcCookiePath != "" && userSubmatches == nil && passSubmatches == nil {
×
2176
                return "", "", "", "", fmt.Errorf("unable to open cookie file (%v)",
×
2177
                        rpcCookiePath)
×
2178
        }
×
2179

2180
        if userSubmatches == nil {
×
2181
                return "", "", "", "", fmt.Errorf("unable to find rpcuser in " +
×
2182
                        "config")
×
2183
        }
×
2184
        if passSubmatches == nil {
×
2185
                return "", "", "", "", fmt.Errorf("unable to find rpcpassword " +
×
2186
                        "in config")
×
2187
        }
×
2188

2189
        return supplyEnvValue(string(userSubmatches[1])),
×
2190
                supplyEnvValue(string(passSubmatches[1])),
×
2191
                zmqBlockHost, zmqTxHost, nil
×
2192
}
2193

2194
// checkZMQOptions ensures that the provided addresses to use as the hosts for
2195
// ZMQ rawblock and rawtx notifications are different.
UNCOV
2196
func checkZMQOptions(zmqBlockHost, zmqTxHost string) error {
×
UNCOV
2197
        if zmqBlockHost == zmqTxHost {
×
2198
                return errors.New("zmqpubrawblock and zmqpubrawtx must be set " +
×
2199
                        "to different addresses")
×
2200
        }
×
2201

UNCOV
2202
        return nil
×
2203
}
2204

2205
// checkEstimateMode ensures that the provided estimate mode is legal.
2206
func checkEstimateMode(estimateMode string) error {
1✔
2207
        for _, mode := range bitcoindEstimateModes {
2✔
2208
                if estimateMode == mode {
2✔
2209
                        return nil
1✔
2210
                }
1✔
2211
        }
2212

2213
        return fmt.Errorf("estimatemode must be one of the following: %v",
×
2214
                bitcoindEstimateModes[:])
×
2215
}
2216

2217
// configToFlatMap converts the given config struct into a flat map of
2218
// key/value pairs using the dot notation we are used to from the config file
2219
// or command line flags. It also returns a map containing deprecated config
2220
// options.
2221
func configToFlatMap(cfg Config) (map[string]string,
2222
        map[string]struct{}, error) {
3✔
2223

3✔
2224
        result := make(map[string]string)
3✔
2225

3✔
2226
        // deprecated stores a map of deprecated options found in the config
3✔
2227
        // that are set by the users. A config option is considered as
3✔
2228
        // deprecated if it has a `hidden` flag.
3✔
2229
        deprecated := make(map[string]struct{})
3✔
2230

3✔
2231
        // redact is the helper function that redacts sensitive values like
3✔
2232
        // passwords.
3✔
2233
        redact := func(key, value string) string {
6✔
2234
                sensitiveKeySuffixes := []string{
3✔
2235
                        "pass",
3✔
2236
                        "password",
3✔
2237
                        "dsn",
3✔
2238
                }
3✔
2239
                for _, suffix := range sensitiveKeySuffixes {
6✔
2240
                        if strings.HasSuffix(key, suffix) {
6✔
2241
                                return "[redacted]"
3✔
2242
                        }
3✔
2243
                }
2244

2245
                return value
3✔
2246
        }
2247

2248
        // printConfig is the helper function that goes into nested structs
2249
        // recursively. Because we call it recursively, we need to declare it
2250
        // before we define it.
2251
        var printConfig func(reflect.Value, string)
3✔
2252
        printConfig = func(obj reflect.Value, prefix string) {
6✔
2253
                // Turn struct pointers into the actual struct, so we can
3✔
2254
                // iterate over the fields as we would with a struct value.
3✔
2255
                if obj.Kind() == reflect.Ptr {
6✔
2256
                        obj = obj.Elem()
3✔
2257
                }
3✔
2258

2259
                // Abort on nil values.
2260
                if !obj.IsValid() {
3✔
2261
                        return
×
2262
                }
×
2263

2264
                // Loop over all fields of the struct and inspect the type.
2265
                for i := 0; i < obj.NumField(); i++ {
6✔
2266
                        field := obj.Field(i)
3✔
2267
                        fieldType := obj.Type().Field(i)
3✔
2268

3✔
2269
                        longName := fieldType.Tag.Get("long")
3✔
2270
                        namespace := fieldType.Tag.Get("namespace")
3✔
2271
                        group := fieldType.Tag.Get("group")
3✔
2272
                        hidden := fieldType.Tag.Get("hidden")
3✔
2273

3✔
2274
                        switch {
3✔
2275
                        // We have a long name defined, this is a config value.
2276
                        case longName != "":
3✔
2277
                                key := longName
3✔
2278
                                if prefix != "" {
6✔
2279
                                        key = prefix + "." + key
3✔
2280
                                }
3✔
2281

2282
                                // Add the value directly to the flattened map.
2283
                                result[key] = redact(key, fmt.Sprintf(
3✔
2284
                                        "%v", field.Interface(),
3✔
2285
                                ))
3✔
2286

3✔
2287
                                // If there's a hidden flag, it's deprecated.
3✔
2288
                                if hidden == "true" && !field.IsZero() {
3✔
2289
                                        deprecated[key] = struct{}{}
×
2290
                                }
×
2291

2292
                        // We have no long name but a namespace, this is a
2293
                        // nested struct.
2294
                        case longName == "" && namespace != "":
3✔
2295
                                key := namespace
3✔
2296
                                if prefix != "" {
6✔
2297
                                        key = prefix + "." + key
3✔
2298
                                }
3✔
2299

2300
                                printConfig(field, key)
3✔
2301

2302
                        // Just a group means this is a dummy struct to house
2303
                        // multiple config values, the group name doesn't go
2304
                        // into the final field name.
2305
                        case longName == "" && group != "":
3✔
2306
                                printConfig(field, prefix)
3✔
2307

2308
                        // Anonymous means embedded struct. We need to recurse
2309
                        // into it but without adding anything to the prefix.
2310
                        case fieldType.Anonymous:
3✔
2311
                                printConfig(field, prefix)
3✔
2312

2313
                        default:
3✔
2314
                                continue
3✔
2315
                        }
2316
                }
2317
        }
2318

2319
        // Turn the whole config struct into a flat map.
2320
        printConfig(reflect.ValueOf(cfg), "")
3✔
2321

3✔
2322
        return result, deprecated, nil
3✔
2323
}
2324

2325
// logWarningsForDeprecation logs a warning if a deprecated config option is
2326
// set.
2327
func logWarningsForDeprecation(cfg Config) {
3✔
2328
        _, deprecated, err := configToFlatMap(cfg)
3✔
2329
        if err != nil {
3✔
2330
                ltndLog.Errorf("Convert configs to map: %v", err)
×
2331
        }
×
2332

2333
        for k := range deprecated {
3✔
2334
                ltndLog.Warnf("Config '%s' is deprecated, please remove it", k)
×
2335
        }
×
2336
}
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