decred.org/dcrwallet/v3@v3.1.0/config.go (about)

     1  // Copyright (c) 2013-2016 The btcsuite developers
     2  // Copyright (c) 2015-2020 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package main
     7  
     8  import (
     9  	"context"
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"fmt"
    13  	"net"
    14  	"os"
    15  	"os/user"
    16  	"path/filepath"
    17  	"runtime"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"decred.org/dcrwallet/v3/errors"
    23  	"decred.org/dcrwallet/v3/internal/cfgutil"
    24  	"decred.org/dcrwallet/v3/internal/loggers"
    25  	"decred.org/dcrwallet/v3/internal/netparams"
    26  	"decred.org/dcrwallet/v3/version"
    27  	"decred.org/dcrwallet/v3/wallet"
    28  	"decred.org/dcrwallet/v3/wallet/txrules"
    29  	"github.com/decred/dcrd/connmgr/v3"
    30  	"github.com/decred/dcrd/dcrutil/v4"
    31  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    32  	"github.com/decred/go-socks/socks"
    33  	"github.com/decred/slog"
    34  	flags "github.com/jessevdk/go-flags"
    35  )
    36  
    37  const (
    38  	defaultCAFilename              = "dcrd.cert"
    39  	defaultConfigFilename          = "dcrwallet.conf"
    40  	defaultLogLevel                = "info"
    41  	defaultLogDirname              = "logs"
    42  	defaultLogFilename             = "dcrwallet.log"
    43  	defaultLogSize                 = "10M"
    44  	defaultRPCMaxClients           = 10
    45  	defaultRPCMaxWebsockets        = 25
    46  	defaultAuthType                = "basic"
    47  	defaultEnableTicketBuyer       = false
    48  	defaultEnableVoting            = false
    49  	defaultPurchaseAccount         = "default"
    50  	defaultPromptPass              = false
    51  	defaultPass                    = ""
    52  	defaultPromptPublicPass        = false
    53  	defaultGapLimit                = wallet.DefaultGapLimit
    54  	defaultStakePoolColdExtKey     = ""
    55  	defaultAllowHighFees           = false
    56  	defaultAccountGapLimit         = wallet.DefaultAccountGapLimit
    57  	defaultDisableCoinTypeUpgrades = false
    58  	defaultCircuitLimit            = 32
    59  	defaultMixSplitLimit           = 10
    60  	defaultVSPMaxFee               = dcrutil.Amount(0.2e8)
    61  
    62  	// ticket buyer options
    63  	defaultBalanceToMaintainAbsolute = 0
    64  	defaultTicketbuyerLimit          = 1
    65  
    66  	walletDbName = "wallet.db"
    67  )
    68  
    69  var (
    70  	dcrdDefaultCAFile      = filepath.Join(dcrutil.AppDataDir("dcrd", false), "rpc.cert")
    71  	defaultAppDataDir      = dcrutil.AppDataDir("dcrwallet", false)
    72  	defaultConfigFile      = filepath.Join(defaultAppDataDir, defaultConfigFilename)
    73  	defaultRPCKeyFile      = filepath.Join(defaultAppDataDir, "rpc.key")
    74  	defaultRPCCertFile     = filepath.Join(defaultAppDataDir, "rpc.cert")
    75  	defaultRPCClientCAFile = filepath.Join(defaultAppDataDir, "clients.pem")
    76  	defaultLogDir          = filepath.Join(defaultAppDataDir, defaultLogDirname)
    77  )
    78  
    79  type config struct {
    80  	// General application behavior
    81  	ConfigFile         *cfgutil.ExplicitString `short:"C" long:"configfile" description:"Path to configuration file"`
    82  	ShowVersion        bool                    `short:"V" long:"version" description:"Display version information and exit"`
    83  	Create             bool                    `long:"create" description:"Create new wallet"`
    84  	CreateTemp         bool                    `long:"createtemp" description:"Create simulation wallet in nonstandard --appdata; private passphrase is 'password'"`
    85  	CreateWatchingOnly bool                    `long:"createwatchingonly" description:"Create watching wallet from account extended pubkey"`
    86  	AppDataDir         *cfgutil.ExplicitString `short:"A" long:"appdata" description:"Application data directory for wallet config, databases and logs"`
    87  	TestNet            bool                    `long:"testnet" description:"Use the test network"`
    88  	SimNet             bool                    `long:"simnet" description:"Use the simulation test network"`
    89  	NoInitialLoad      bool                    `long:"noinitialload" description:"Defer wallet creation/opening on startup and enable loading wallets over RPC"`
    90  	DebugLevel         string                  `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
    91  	LogDir             *cfgutil.ExplicitString `long:"logdir" description:"Directory to log output."`
    92  	LogSize            string                  `long:"logsize" description:"Maximum size of log file before it is rotated"`
    93  	NoFileLogging      bool                    `long:"nofilelogging" description:"Disable file logging"`
    94  	Profile            []string                `long:"profile" description:"Enable HTTP profiling this interface/port"`
    95  	MemProfile         string                  `long:"memprofile" description:"Write mem profile to the specified file"`
    96  	CPUProfile         string                  `long:"cpuprofile" description:"Write cpu profile to the specified file"`
    97  
    98  	// Wallet options
    99  	WalletPass              string               `long:"walletpass" default-mask:"-" description:"Public wallet password; required when created with one"`
   100  	PromptPass              bool                 `long:"promptpass" description:"Prompt for private passphase from terminal and unlock without timeout"`
   101  	Pass                    string               `long:"pass" description:"Unlock with private passphrase"`
   102  	PromptPublicPass        bool                 `long:"promptpublicpass" description:"Prompt for public passphrase from terminal"`
   103  	EnableTicketBuyer       bool                 `long:"enableticketbuyer" description:"Enable the automatic ticket buyer"`
   104  	EnableVoting            bool                 `long:"enablevoting" description:"Automatically create votes and revocations"`
   105  	PurchaseAccount         string               `long:"purchaseaccount" description:"Account to autobuy tickets from"`
   106  	PoolAddress             *cfgutil.AddressFlag `long:"pooladdress" description:"VSP fee address"`
   107  	poolAddress             stdaddr.StakeAddress
   108  	PoolFees                float64             `long:"poolfees" description:"VSP fee percentage (1.00 equals 1.00% fee)"`
   109  	GapLimit                uint32              `long:"gaplimit" description:"Allowed unused address gap between used addresses of accounts"`
   110  	WatchLast               uint32              `long:"watchlast" description:"Limit watched previous addresses of each HD account branch"`
   111  	StakePoolColdExtKey     string              `long:"stakepoolcoldextkey" description:"xpub:maxindex for fee addresses (VSP-only option)"`
   112  	ManualTickets           bool                `long:"manualtickets" description:"Do not discover new tickets through network synchronization"`
   113  	AllowHighFees           bool                `long:"allowhighfees" description:"Do not perform high fee checks"`
   114  	RelayFee                *cfgutil.AmountFlag `long:"txfee" description:"Transaction fee per kilobyte"`
   115  	AccountGapLimit         int                 `long:"accountgaplimit" description:"Allowed gap of unused accounts"`
   116  	DisableCoinTypeUpgrades bool                `long:"disablecointypeupgrades" description:"Never upgrade from legacy to SLIP0044 coin type keys"`
   117  
   118  	// RPC client options
   119  	RPCConnect       string                  `short:"c" long:"rpcconnect" description:"Network address of dcrd RPC server"`
   120  	CAFile           *cfgutil.ExplicitString `long:"cafile" description:"dcrd RPC Certificate Authority"`
   121  	ClientCAFile     *cfgutil.ExplicitString `long:"clientcafile" description:"Certficate Authority to verify TLS client certificates"`
   122  	DisableClientTLS bool                    `long:"noclienttls" description:"Disable TLS for dcrd RPC; only allowed when connecting to localhost"`
   123  	DcrdUsername     string                  `long:"dcrdusername" description:"dcrd RPC username; overrides --username"`
   124  	DcrdPassword     string                  `long:"dcrdpassword" default-mask:"-" description:"dcrd RPC password; overrides --password"`
   125  
   126  	// Proxy and Tor settings
   127  	Proxy        string `long:"proxy" description:"Establish network connections and DNS lookups through a SOCKS5 proxy (e.g. 127.0.0.1:9050)"`
   128  	ProxyUser    string `long:"proxyuser" description:"Proxy server username"`
   129  	ProxyPass    string `long:"proxypass" default-mask:"-" description:"Proxy server password"`
   130  	CircuitLimit int    `long:"circuitlimit" description:"Set maximum number of open Tor circuits; used only when --torisolation is enabled"`
   131  	TorIsolation bool   `long:"torisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection"`
   132  	NoDcrdProxy  bool   `long:"nodcrdproxy" description:"Never use configured proxy to dial dcrd websocket connectons"`
   133  	dial         func(ctx context.Context, network, address string) (net.Conn, error)
   134  	lookup       func(name string) ([]net.IP, error)
   135  
   136  	// SPV options
   137  	SPV        bool     `long:"spv" description:"Sync using simplified payment verification"`
   138  	SPVConnect []string `long:"spvconnect" description:"SPV sync only with specified peers; disables DNS seeding"`
   139  
   140  	// RPC server options
   141  	RPCCert                *cfgutil.ExplicitString `long:"rpccert" description:"RPC server TLS certificate"`
   142  	RPCKey                 *cfgutil.ExplicitString `long:"rpckey" description:"RPC server TLS key"`
   143  	TLSCurve               *cfgutil.CurveFlag      `long:"tlscurve" description:"Curve to use when generating TLS keypairs"`
   144  	OneTimeTLSKey          bool                    `long:"onetimetlskey" description:"Generate self-signed TLS keypairs each startup; only write certificate file"`
   145  	DisableServerTLS       bool                    `long:"noservertls" description:"Disable TLS for the RPC servers; only allowed when binding to localhost"`
   146  	GRPCListeners          []string                `long:"grpclisten" description:"Listen for gRPC connections on this interface"`
   147  	LegacyRPCListeners     []string                `long:"rpclisten" description:"Listen for JSON-RPC connections on this interface"`
   148  	NoGRPC                 bool                    `long:"nogrpc" description:"Disable gRPC server"`
   149  	NoLegacyRPC            bool                    `long:"nolegacyrpc" description:"Disable JSON-RPC server"`
   150  	LegacyRPCMaxClients    int64                   `long:"rpcmaxclients" description:"Max JSON-RPC HTTP POST clients"`
   151  	LegacyRPCMaxWebsockets int64                   `long:"rpcmaxwebsockets" description:"Max JSON-RPC websocket clients"`
   152  	Username               string                  `short:"u" long:"username" description:"JSON-RPC username and default dcrd RPC username"`
   153  	Password               string                  `short:"P" long:"password" default-mask:"-" description:"JSON-RPC password and default dcrd RPC password"`
   154  	JSONRPCAuthType        string                  `long:"jsonrpcauthtype" description:"Method for JSON-RPC client authentication (basic or clientcert)"`
   155  
   156  	// IPC options
   157  	PipeTx            *uint `long:"pipetx" description:"File descriptor or handle of write end pipe to enable child -> parent process communication"`
   158  	PipeRx            *uint `long:"piperx" description:"File descriptor or handle of read end pipe to enable parent -> child process communication"`
   159  	RPCListenerEvents bool  `long:"rpclistenerevents" description:"Notify JSON-RPC and gRPC listener addresses over the TX pipe"`
   160  	IssueClientCert   bool  `long:"issueclientcert" description:"Notify a client cert and key over the TX pipe for RPC authentication"`
   161  
   162  	// CSPP
   163  	CSPPServer         string `long:"csppserver" description:"Network address of CoinShuffle++ server"`
   164  	CSPPServerCA       string `long:"csppserver.ca" description:"CoinShuffle++ Certificate Authority"`
   165  	dialCSPPServer     func(ctx context.Context, network, addr string) (net.Conn, error)
   166  	MixedAccount       string `long:"mixedaccount" description:"Account/branch used to derive CoinShuffle++ mixed outputs and voting rewards"`
   167  	mixedAccount       string
   168  	mixedBranch        uint32
   169  	TicketSplitAccount string `long:"ticketsplitaccount" description:"Account to derive fresh addresses from for mixed ticket splits; uses mixedaccount if unset"`
   170  	ChangeAccount      string `long:"changeaccount" description:"Account used to derive unmixed CoinJoin outputs in CoinShuffle++ protocol"`
   171  	MixChange          bool   `long:"mixchange" description:"Use CoinShuffle++ to mix change account outputs into mix account"`
   172  	MixSplitLimit      int    `long:"mixsplitlimit" description:"Connection limit to CoinShuffle++ server per change amount"`
   173  
   174  	TBOpts ticketBuyerOptions `group:"Ticket Buyer Options" namespace:"ticketbuyer"`
   175  
   176  	VSPOpts vspOptions `group:"VSP Options" namespace:"vsp"`
   177  }
   178  
   179  type ticketBuyerOptions struct {
   180  	BalanceToMaintainAbsolute *cfgutil.AmountFlag  `long:"balancetomaintainabsolute" description:"Amount of funds to keep in wallet when purchasing tickets"`
   181  	VotingAddress             *cfgutil.AddressFlag `long:"votingaddress" description:"Purchase tickets with voting rights assigned to this address"`
   182  	votingAddress             stdaddr.StakeAddress
   183  	Limit                     uint   `long:"limit" description:"Buy no more than specified number of tickets per block"`
   184  	VotingAccount             string `long:"votingaccount" description:"Account used to derive addresses specifying voting rights"`
   185  }
   186  
   187  type vspOptions struct {
   188  	// VSP - TODO: VSPServer to a []string to support multiple VSPs
   189  	URL    string              `long:"url" description:"Base URL of the VSP server"`
   190  	PubKey string              `long:"pubkey" description:"VSP server pubkey"`
   191  	Sync   bool                `long:"sync" description:"sync tickets to vsp"`
   192  	MaxFee *cfgutil.AmountFlag `long:"maxfee" description:"Maximum VSP fee"`
   193  }
   194  
   195  // cleanAndExpandPath expands environement variables and leading ~ in the
   196  // passed path, cleans the result, and returns it.
   197  func cleanAndExpandPath(path string) string {
   198  	// Do not try to clean the empty string
   199  	if path == "" {
   200  		return ""
   201  	}
   202  
   203  	// NOTE: The os.ExpandEnv doesn't work with Windows cmd.exe-style
   204  	// %VARIABLE%, but they variables can still be expanded via POSIX-style
   205  	// $VARIABLE.
   206  	path = os.ExpandEnv(path)
   207  
   208  	if !strings.HasPrefix(path, "~") {
   209  		return filepath.Clean(path)
   210  	}
   211  
   212  	// Expand initial ~ to the current user's home directory, or ~otheruser
   213  	// to otheruser's home directory.  On Windows, both forward and backward
   214  	// slashes can be used.
   215  	path = path[1:]
   216  
   217  	var pathSeparators string
   218  	if runtime.GOOS == "windows" {
   219  		pathSeparators = string(os.PathSeparator) + "/"
   220  	} else {
   221  		pathSeparators = string(os.PathSeparator)
   222  	}
   223  
   224  	userName := ""
   225  	if i := strings.IndexAny(path, pathSeparators); i != -1 {
   226  		userName = path[:i]
   227  		path = path[i:]
   228  	}
   229  
   230  	homeDir := ""
   231  	var u *user.User
   232  	var err error
   233  	if userName == "" {
   234  		u, err = user.Current()
   235  	} else {
   236  		u, err = user.Lookup(userName)
   237  	}
   238  	if err == nil {
   239  		homeDir = u.HomeDir
   240  	}
   241  	// Fallback to CWD if user lookup fails or user has no home directory.
   242  	if homeDir == "" {
   243  		homeDir = "."
   244  	}
   245  
   246  	return filepath.Join(homeDir, path)
   247  }
   248  
   249  // validLogLevel returns whether or not logLevel is a valid debug log level.
   250  func validLogLevel(logLevel string) bool {
   251  	_, ok := slog.LevelFromString(logLevel)
   252  	return ok
   253  }
   254  
   255  // supportedSubsystems returns a sorted slice of the supported subsystems for
   256  // logging purposes.
   257  func supportedSubsystems() []string {
   258  	// Convert the subsystemLoggers map keys to a slice.
   259  	subsystems := make([]string, 0, len(subsystemLoggers))
   260  	for subsysID := range subsystemLoggers {
   261  		subsystems = append(subsystems, subsysID)
   262  	}
   263  
   264  	// Sort the subsytems for stable display.
   265  	sort.Strings(subsystems)
   266  	return subsystems
   267  }
   268  
   269  // parseAndSetDebugLevels attempts to parse the specified debug level and set
   270  // the levels accordingly.  An appropriate error is returned if anything is
   271  // invalid.
   272  func parseAndSetDebugLevels(debugLevel string) error {
   273  	// When the specified string doesn't have any delimters, treat it as
   274  	// the log level for all subsystems.
   275  	if !strings.Contains(debugLevel, ",") && !strings.Contains(debugLevel, "=") {
   276  		// Validate debug log level.
   277  		if !validLogLevel(debugLevel) {
   278  			str := "The specified debug level [%v] is invalid"
   279  			return errors.Errorf(str, debugLevel)
   280  		}
   281  
   282  		// Change the logging level for all subsystems.
   283  		setLogLevels(debugLevel)
   284  
   285  		return nil
   286  	}
   287  
   288  	// Split the specified string into subsystem/level pairs while detecting
   289  	// issues and update the log levels accordingly.
   290  	for _, logLevelPair := range strings.Split(debugLevel, ",") {
   291  		if !strings.Contains(logLevelPair, "=") {
   292  			str := "The specified debug level contains an invalid " +
   293  				"subsystem/level pair [%v]"
   294  			return errors.Errorf(str, logLevelPair)
   295  		}
   296  
   297  		// Extract the specified subsystem and log level.
   298  		fields := strings.Split(logLevelPair, "=")
   299  		subsysID, logLevel := fields[0], fields[1]
   300  
   301  		// Validate subsystem.
   302  		if _, exists := subsystemLoggers[subsysID]; !exists {
   303  			str := "The specified subsystem [%v] is invalid -- " +
   304  				"supported subsytems %v"
   305  			return errors.Errorf(str, subsysID, supportedSubsystems())
   306  		}
   307  
   308  		// Validate log level.
   309  		if !validLogLevel(logLevel) {
   310  			str := "The specified debug level [%v] is invalid"
   311  			return errors.Errorf(str, logLevel)
   312  		}
   313  
   314  		setLogLevel(subsysID, logLevel)
   315  	}
   316  
   317  	return nil
   318  }
   319  
   320  // loadConfig initializes and parses the config using a config file and command
   321  // line options.
   322  //
   323  // The configuration proceeds as follows:
   324  //  1. Start with a default config with sane settings
   325  //  2. Pre-parse the command line to check for an alternative config file
   326  //  3. Load configuration file overwriting defaults with any specified options
   327  //  4. Parse CLI options and overwrite/add any specified options
   328  //
   329  // The above results in dcrwallet functioning properly without any config
   330  // settings while still allowing the user to override settings with config files
   331  // and command line options.  Command line options always take precedence.
   332  // The bool returned indicates whether or not the wallet was recreated from a
   333  // seed and needs to perform the initial resync. The []byte is the private
   334  // passphrase required to do the sync for this special case.
   335  func loadConfig(ctx context.Context) (*config, []string, error) {
   336  	loadConfigError := func(err error) (*config, []string, error) {
   337  		return nil, nil, err
   338  	}
   339  
   340  	// Default config.
   341  	cfg := config{
   342  		DebugLevel:              defaultLogLevel,
   343  		ConfigFile:              cfgutil.NewExplicitString(defaultConfigFile),
   344  		AppDataDir:              cfgutil.NewExplicitString(defaultAppDataDir),
   345  		LogDir:                  cfgutil.NewExplicitString(defaultLogDir),
   346  		LogSize:                 defaultLogSize,
   347  		WalletPass:              wallet.InsecurePubPassphrase,
   348  		CAFile:                  cfgutil.NewExplicitString(""),
   349  		ClientCAFile:            cfgutil.NewExplicitString(defaultRPCClientCAFile),
   350  		dial:                    new(net.Dialer).DialContext,
   351  		lookup:                  net.LookupIP,
   352  		PromptPass:              defaultPromptPass,
   353  		Pass:                    defaultPass,
   354  		PromptPublicPass:        defaultPromptPublicPass,
   355  		RPCKey:                  cfgutil.NewExplicitString(defaultRPCKeyFile),
   356  		RPCCert:                 cfgutil.NewExplicitString(defaultRPCCertFile),
   357  		TLSCurve:                cfgutil.NewCurveFlag(cfgutil.PreferredCurve),
   358  		LegacyRPCMaxClients:     defaultRPCMaxClients,
   359  		LegacyRPCMaxWebsockets:  defaultRPCMaxWebsockets,
   360  		JSONRPCAuthType:         defaultAuthType,
   361  		EnableTicketBuyer:       defaultEnableTicketBuyer,
   362  		EnableVoting:            defaultEnableVoting,
   363  		PurchaseAccount:         defaultPurchaseAccount,
   364  		GapLimit:                defaultGapLimit,
   365  		StakePoolColdExtKey:     defaultStakePoolColdExtKey,
   366  		AllowHighFees:           defaultAllowHighFees,
   367  		RelayFee:                cfgutil.NewAmountFlag(txrules.DefaultRelayFeePerKb),
   368  		PoolAddress:             cfgutil.NewAddressFlag(),
   369  		AccountGapLimit:         defaultAccountGapLimit,
   370  		DisableCoinTypeUpgrades: defaultDisableCoinTypeUpgrades,
   371  		CircuitLimit:            defaultCircuitLimit,
   372  		MixSplitLimit:           defaultMixSplitLimit,
   373  
   374  		// Ticket Buyer Options
   375  		TBOpts: ticketBuyerOptions{
   376  			BalanceToMaintainAbsolute: cfgutil.NewAmountFlag(defaultBalanceToMaintainAbsolute),
   377  			VotingAddress:             cfgutil.NewAddressFlag(),
   378  			Limit:                     defaultTicketbuyerLimit,
   379  		},
   380  
   381  		VSPOpts: vspOptions{
   382  			MaxFee: cfgutil.NewAmountFlag(defaultVSPMaxFee),
   383  		},
   384  	}
   385  
   386  	// Pre-parse the command line options to see if an alternative config
   387  	// file or the version flag was specified.
   388  	preCfg := cfg
   389  	preParser := flags.NewParser(&preCfg, flags.Default)
   390  	_, err := preParser.Parse()
   391  	if err != nil {
   392  		var e *flags.Error
   393  		if errors.As(err, &e) && e.Type == flags.ErrHelp {
   394  			os.Exit(0)
   395  		}
   396  		preParser.WriteHelp(os.Stderr)
   397  		return loadConfigError(err)
   398  	}
   399  
   400  	// Show the version and exit if the version flag was specified.
   401  	funcName := "loadConfig"
   402  	appName := filepath.Base(os.Args[0])
   403  	appName = strings.TrimSuffix(appName, filepath.Ext(appName))
   404  	usageMessage := fmt.Sprintf("Use %s -h to show usage", appName)
   405  	if preCfg.ShowVersion {
   406  		fmt.Printf("%s version %s (Go version %s %s/%s)\n", appName,
   407  			version.String(), runtime.Version(), runtime.GOOS, runtime.GOARCH)
   408  		os.Exit(0)
   409  	}
   410  
   411  	// Load additional config from file.
   412  	var configFileError error
   413  	parser := flags.NewParser(&cfg, flags.Default)
   414  	configFilePath := preCfg.ConfigFile.Value
   415  	if preCfg.ConfigFile.ExplicitlySet() {
   416  		configFilePath = cleanAndExpandPath(configFilePath)
   417  	} else {
   418  		appDataDir := preCfg.AppDataDir.Value
   419  		if appDataDir != defaultAppDataDir {
   420  			configFilePath = filepath.Join(appDataDir, defaultConfigFilename)
   421  		}
   422  	}
   423  	err = flags.NewIniParser(parser).ParseFile(configFilePath)
   424  	if err != nil {
   425  		var e *os.PathError
   426  		if !errors.As(err, &e) {
   427  			fmt.Fprintln(os.Stderr, err)
   428  			parser.WriteHelp(os.Stderr)
   429  			return loadConfigError(err)
   430  		}
   431  		configFileError = err
   432  	}
   433  
   434  	// Parse command line options again to ensure they take precedence.
   435  	remainingArgs, err := parser.Parse()
   436  	if err != nil {
   437  		var e *flags.Error
   438  		if !errors.As(err, &e) || e.Type != flags.ErrHelp {
   439  			parser.WriteHelp(os.Stderr)
   440  		}
   441  		return loadConfigError(err)
   442  	}
   443  
   444  	// If an alternate data directory was specified, and paths with defaults
   445  	// relative to the data dir are unchanged, modify each path to be
   446  	// relative to the new data dir.
   447  	if cfg.AppDataDir.ExplicitlySet() {
   448  		cfg.AppDataDir.Value = cleanAndExpandPath(cfg.AppDataDir.Value)
   449  		if !cfg.RPCKey.ExplicitlySet() {
   450  			cfg.RPCKey.Value = filepath.Join(cfg.AppDataDir.Value, "rpc.key")
   451  		}
   452  		if !cfg.RPCCert.ExplicitlySet() {
   453  			cfg.RPCCert.Value = filepath.Join(cfg.AppDataDir.Value, "rpc.cert")
   454  		}
   455  		if !cfg.ClientCAFile.ExplicitlySet() {
   456  			cfg.ClientCAFile.Value = filepath.Join(cfg.AppDataDir.Value, "clients.pem")
   457  		}
   458  		if !cfg.LogDir.ExplicitlySet() {
   459  			cfg.LogDir.Value = filepath.Join(cfg.AppDataDir.Value, defaultLogDirname)
   460  		}
   461  	}
   462  
   463  	// Choose the active network params based on the selected network.
   464  	// Multiple networks can't be selected simultaneously.
   465  	numNets := 0
   466  	if cfg.TestNet {
   467  		activeNet = &netparams.TestNet3Params
   468  		numNets++
   469  	}
   470  	if cfg.SimNet {
   471  		activeNet = &netparams.SimNetParams
   472  		numNets++
   473  	}
   474  	if numNets > 1 {
   475  		str := "%s: The testnet and simnet params can't be used " +
   476  			"together -- choose one"
   477  		err := errors.Errorf(str, "loadConfig")
   478  		fmt.Fprintln(os.Stderr, err)
   479  		return loadConfigError(err)
   480  	}
   481  
   482  	if !cfg.NoFileLogging {
   483  		// Append the network type to the log directory so it is
   484  		// "namespaced" per network.
   485  		cfg.LogDir.Value = cleanAndExpandPath(cfg.LogDir.Value)
   486  		cfg.LogDir.Value = filepath.Join(cfg.LogDir.Value,
   487  			activeNet.Params.Name)
   488  
   489  		var units int
   490  		for i, r := range cfg.LogSize {
   491  			if r < '0' || r > '9' {
   492  				units = i
   493  				break
   494  			}
   495  		}
   496  		invalidSize := func() error {
   497  			str := "%s: Invalid logsize: %v "
   498  			err := errors.Errorf(str, funcName, cfg.LogSize)
   499  			fmt.Fprintln(os.Stderr, err)
   500  			return err
   501  		}
   502  		if units == 0 {
   503  			return loadConfigError(invalidSize())
   504  		}
   505  		// Parsing a 32-bit number prevents 64-bit overflow after unit
   506  		// multiplication.
   507  		logsize, err := strconv.ParseInt(cfg.LogSize[:units], 10, 32)
   508  		if err != nil {
   509  			return loadConfigError(invalidSize())
   510  		}
   511  		switch cfg.LogSize[units:] {
   512  		case "k", "K", "KiB":
   513  		case "m", "M", "MiB":
   514  			logsize <<= 10
   515  		case "g", "G", "GiB":
   516  			logsize <<= 20
   517  		default:
   518  			return loadConfigError(invalidSize())
   519  		}
   520  
   521  		// Initialize log rotation.  After log rotation has been initialized, the
   522  		// logger variables may be used.
   523  		loggers.InitLogRotator(filepath.Join(cfg.LogDir.Value, defaultLogFilename), logsize)
   524  	}
   525  
   526  	// Special show command to list supported subsystems and exit.
   527  	if cfg.DebugLevel == "show" {
   528  		fmt.Println("Supported subsystems", supportedSubsystems())
   529  		os.Exit(0)
   530  	}
   531  
   532  	// Check that no addresses were created for the wrong network
   533  	for _, a := range []struct {
   534  		flag *cfgutil.AddressFlag
   535  		addr *stdaddr.StakeAddress
   536  	}{
   537  		{cfg.PoolAddress, &cfg.poolAddress},
   538  		{cfg.TBOpts.VotingAddress, &cfg.TBOpts.votingAddress},
   539  	} {
   540  		addr, err := a.flag.StakeAddress(activeNet.Params)
   541  		if err != nil {
   542  			log.Error(err)
   543  			return loadConfigError(err)
   544  		}
   545  		*a.addr = addr
   546  	}
   547  
   548  	// Parse, validate, and set debug log level(s).
   549  	if err := parseAndSetDebugLevels(cfg.DebugLevel); err != nil {
   550  		err := errors.Errorf("%s: %v", "loadConfig", err.Error())
   551  		fmt.Fprintln(os.Stderr, err)
   552  		parser.WriteHelp(os.Stderr)
   553  		return loadConfigError(err)
   554  	}
   555  
   556  	// Error and shutdown if config file is specified on the command line
   557  	// but cannot be found.
   558  	if configFileError != nil && cfg.ConfigFile.ExplicitlySet() {
   559  		if preCfg.ConfigFile.ExplicitlySet() || cfg.ConfigFile.ExplicitlySet() {
   560  			log.Errorf("%v", configFileError)
   561  			return loadConfigError(configFileError)
   562  		}
   563  	}
   564  
   565  	// Warn about missing config file after the final command line parse
   566  	// succeeds.  This prevents the warning on help messages and invalid
   567  	// options.
   568  	if configFileError != nil {
   569  		log.Warnf("%v", configFileError)
   570  	}
   571  
   572  	// Sanity check BalanceToMaintainAbsolute
   573  	if cfg.TBOpts.BalanceToMaintainAbsolute.ToCoin() < 0 {
   574  		str := "%s: balancetomaintainabsolute cannot be negative: %v"
   575  		err := errors.Errorf(str, funcName, cfg.TBOpts.BalanceToMaintainAbsolute)
   576  		fmt.Fprintln(os.Stderr, err)
   577  		return loadConfigError(err)
   578  	}
   579  
   580  	// Exit if you try to use a simulation wallet with a standard
   581  	// data directory.
   582  	if !cfg.AppDataDir.ExplicitlySet() && cfg.CreateTemp {
   583  		fmt.Fprintln(os.Stderr, "Tried to create a temporary simulation "+
   584  			"wallet, but failed to specify data directory!")
   585  		os.Exit(0)
   586  	}
   587  
   588  	// Exit if you try to use a simulation wallet on anything other than
   589  	// simnet or testnet.
   590  	if !cfg.SimNet && cfg.CreateTemp {
   591  		fmt.Fprintln(os.Stderr, "Tried to create a temporary simulation "+
   592  			"wallet for network other than simnet!")
   593  		os.Exit(0)
   594  	}
   595  
   596  	// Ensure the wallet exists or create it when the create flag is set.
   597  	netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
   598  	dbPath := filepath.Join(netDir, walletDbName)
   599  
   600  	if cfg.CreateTemp && cfg.Create {
   601  		err := errors.Errorf("The flags --create and --createtemp can not " +
   602  			"be specified together. Use --help for more information.")
   603  		fmt.Fprintln(os.Stderr, err)
   604  		return loadConfigError(err)
   605  	}
   606  
   607  	dbFileExists, err := cfgutil.FileExists(dbPath)
   608  	if err != nil {
   609  		fmt.Fprintln(os.Stderr, err)
   610  		return loadConfigError(err)
   611  	}
   612  
   613  	if cfg.CreateTemp {
   614  		tempWalletExists := false
   615  
   616  		if dbFileExists {
   617  			str := fmt.Sprintf("The wallet already exists. Loading this " +
   618  				"wallet instead.")
   619  			fmt.Fprintln(os.Stdout, str)
   620  			tempWalletExists = true
   621  		}
   622  
   623  		// Ensure the data directory for the network exists.
   624  		if err := checkCreateDir(netDir); err != nil {
   625  			fmt.Fprintln(os.Stderr, err)
   626  			return loadConfigError(err)
   627  		}
   628  
   629  		if !tempWalletExists {
   630  			// Perform the initial wallet creation wizard.
   631  			if err := createSimulationWallet(ctx, &cfg); err != nil {
   632  				fmt.Fprintln(os.Stderr, "Unable to create wallet:", err)
   633  				return loadConfigError(err)
   634  			}
   635  		}
   636  	} else if cfg.Create || cfg.CreateWatchingOnly {
   637  		// Error if the create flag is set and the wallet already
   638  		// exists.
   639  		if dbFileExists {
   640  			err := errors.Errorf("The wallet database file `%v` "+
   641  				"already exists.", dbPath)
   642  			fmt.Fprintln(os.Stderr, err)
   643  			return loadConfigError(err)
   644  		}
   645  
   646  		// Ensure the data directory for the network exists.
   647  		if err := checkCreateDir(netDir); err != nil {
   648  			fmt.Fprintln(os.Stderr, err)
   649  			return loadConfigError(err)
   650  		}
   651  
   652  		// Perform the initial wallet creation wizard.
   653  		os.Stdout.Sync()
   654  		if cfg.CreateWatchingOnly {
   655  			err = createWatchingOnlyWallet(ctx, &cfg)
   656  		} else {
   657  			err = createWallet(ctx, &cfg)
   658  		}
   659  		if err != nil {
   660  			fmt.Fprintln(os.Stderr, "Unable to create wallet:", err)
   661  			return loadConfigError(err)
   662  		}
   663  
   664  		// Created successfully, so exit now with success.
   665  		os.Exit(0)
   666  	} else if !dbFileExists && !cfg.NoInitialLoad {
   667  		err := errors.Errorf("The wallet does not exist.  Run with the " +
   668  			"--create option to initialize and create it.")
   669  		fmt.Fprintln(os.Stderr, err)
   670  		return loadConfigError(err)
   671  	}
   672  
   673  	if cfg.PoolFees != 0.0 {
   674  		if !txrules.ValidPoolFeeRate(cfg.PoolFees) {
   675  			err := errors.E(errors.Invalid, errors.Errorf("pool fee rate %v", cfg.PoolFees))
   676  			fmt.Fprintln(os.Stderr, err.Error())
   677  			fmt.Fprintln(os.Stderr, usageMessage)
   678  			return loadConfigError(err)
   679  		}
   680  	}
   681  
   682  	ipNet := func(cidr string) net.IPNet {
   683  		_, ipNet, err := net.ParseCIDR(cidr)
   684  		if err != nil {
   685  			panic(err)
   686  		}
   687  		return *ipNet
   688  	}
   689  	privNets := []net.IPNet{
   690  		// IPv4 loopback
   691  		ipNet("127.0.0.0/8"),
   692  
   693  		// IPv6 loopback
   694  		ipNet("::1/128"),
   695  
   696  		// RFC 1918
   697  		ipNet("10.0.0.0/8"),
   698  		ipNet("172.16.0.0/12"),
   699  		ipNet("192.168.0.0/16"),
   700  
   701  		// RFC 4193
   702  		ipNet("fc00::/7"),
   703  	}
   704  
   705  	// Set dialer and DNS lookup functions if proxy settings are provided.
   706  	if cfg.Proxy != "" {
   707  		proxy := socks.Proxy{
   708  			Addr:         cfg.Proxy,
   709  			Username:     cfg.ProxyUser,
   710  			Password:     cfg.ProxyPass,
   711  			TorIsolation: cfg.TorIsolation,
   712  		}
   713  
   714  		var proxyDialer func(context.Context, string, string) (net.Conn, error)
   715  		var noproxyDialer net.Dialer
   716  		if cfg.TorIsolation {
   717  			proxyDialer = socks.NewPool(proxy, uint32(cfg.CircuitLimit)).DialContext
   718  		} else {
   719  			proxyDialer = proxy.DialContext
   720  		}
   721  
   722  		cfg.dial = func(ctx context.Context, network, address string) (net.Conn, error) {
   723  			host, _, err := net.SplitHostPort(address)
   724  			if err != nil {
   725  				host = address
   726  			}
   727  			if host == "localhost" {
   728  				return noproxyDialer.DialContext(ctx, network, address)
   729  			}
   730  			ip := net.ParseIP(host)
   731  			if len(ip) == 4 || len(ip) == 16 {
   732  				for i := range privNets {
   733  					if privNets[i].Contains(ip) {
   734  						return noproxyDialer.DialContext(ctx, network, address)
   735  					}
   736  				}
   737  			}
   738  			conn, err := proxyDialer(ctx, network, address)
   739  			if err != nil {
   740  				return nil, errors.Errorf("proxy dial %v %v: %w", network, address, err)
   741  			}
   742  			return conn, nil
   743  		}
   744  		cfg.lookup = func(host string) ([]net.IP, error) {
   745  			ip, err := connmgr.TorLookupIP(context.Background(), host, cfg.Proxy)
   746  			if err != nil {
   747  				return nil, errors.Errorf("proxy lookup for %v: %w", host, err)
   748  			}
   749  			return ip, nil
   750  		}
   751  	}
   752  
   753  	// Create CoinShuffle++ TLS dialer based on server name and certificate
   754  	// authority settings.
   755  	csppTLSConfig := new(tls.Config)
   756  	if cfg.CSPPServer != "" {
   757  		csppTLSConfig.ServerName, _, err = net.SplitHostPort(cfg.CSPPServer)
   758  		if err != nil {
   759  			err := errors.Errorf("Cannot parse CoinShuffle++ "+
   760  				"server name %q: %v", cfg.CSPPServer, err)
   761  			fmt.Fprintln(os.Stderr, err.Error())
   762  			return loadConfigError(err)
   763  		}
   764  	}
   765  	if cfg.CSPPServerCA != "" {
   766  		cfg.CSPPServerCA = cleanAndExpandPath(cfg.CSPPServerCA)
   767  		ca, err := os.ReadFile(cfg.CSPPServerCA)
   768  		if err != nil {
   769  			err := errors.Errorf("Cannot read CoinShuffle++ "+
   770  				"Certificate Authority file: %v", err)
   771  			fmt.Fprintln(os.Stderr, err.Error())
   772  			return loadConfigError(err)
   773  		}
   774  		pool := x509.NewCertPool()
   775  		pool.AppendCertsFromPEM(ca)
   776  		csppTLSConfig.RootCAs = pool
   777  	}
   778  	cfg.dialCSPPServer = func(ctx context.Context, network, addr string) (net.Conn, error) {
   779  		conn, err := cfg.dial(ctx, network, addr)
   780  		if err != nil {
   781  			return nil, err
   782  		}
   783  		conn = tls.Client(conn, csppTLSConfig)
   784  		return conn, nil
   785  	}
   786  
   787  	// Parse mixedaccount account/branch
   788  	if cfg.MixedAccount != "" {
   789  		indexSlash := strings.LastIndex(cfg.MixedAccount, "/")
   790  		if indexSlash == -1 {
   791  			err := errors.Errorf("--mixedaccount must have form 'accountname/branch'")
   792  			fmt.Fprintln(os.Stderr, err)
   793  			return loadConfigError(err)
   794  		}
   795  		cfg.mixedAccount = cfg.MixedAccount[:indexSlash]
   796  		switch cfg.MixedAccount[indexSlash+1:] {
   797  		case "0":
   798  			cfg.mixedBranch = 0
   799  		case "1":
   800  			cfg.mixedBranch = 1
   801  		default:
   802  			err := errors.Errorf("--mixedaccount branch must be 0 or 1")
   803  			fmt.Fprintln(os.Stderr, err)
   804  			return loadConfigError(err)
   805  		}
   806  	}
   807  	// Use mixedaccount as default ticketsplitaccount if unset.
   808  	if cfg.TicketSplitAccount == "" {
   809  		cfg.TicketSplitAccount = cfg.mixedAccount
   810  	}
   811  
   812  	if cfg.RPCConnect == "" {
   813  		cfg.RPCConnect = net.JoinHostPort("localhost", activeNet.JSONRPCClientPort)
   814  	}
   815  
   816  	// Add default port to connect flag if missing.
   817  	cfg.RPCConnect, err = cfgutil.NormalizeAddress(cfg.RPCConnect,
   818  		activeNet.JSONRPCClientPort)
   819  	if err != nil {
   820  		fmt.Fprintf(os.Stderr,
   821  			"Invalid rpcconnect network address: %v\n", err)
   822  		return loadConfigError(err)
   823  	}
   824  
   825  	localhostListeners := map[string]struct{}{
   826  		"localhost": {},
   827  		"127.0.0.1": {},
   828  		"::1":       {},
   829  	}
   830  	RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect)
   831  	if err != nil {
   832  		return loadConfigError(err)
   833  	}
   834  	if cfg.DisableClientTLS {
   835  		if _, ok := localhostListeners[RPCHost]; !ok {
   836  			str := "%s: the --noclienttls option may not be used " +
   837  				"when connecting RPC to non localhost " +
   838  				"addresses: %s"
   839  			err := errors.Errorf(str, funcName, cfg.RPCConnect)
   840  			fmt.Fprintln(os.Stderr, err)
   841  			fmt.Fprintln(os.Stderr, usageMessage)
   842  			return loadConfigError(err)
   843  		}
   844  	} else {
   845  		// If CAFile is unset, choose either the copy or local dcrd cert.
   846  		if !cfg.CAFile.ExplicitlySet() {
   847  			cfg.CAFile.Value = filepath.Join(cfg.AppDataDir.Value, defaultCAFilename)
   848  
   849  			// If the CA copy does not exist, check if we're connecting to
   850  			// a local dcrd and switch to its RPC cert if it exists.
   851  			certExists, err := cfgutil.FileExists(cfg.CAFile.Value)
   852  			if err != nil {
   853  				fmt.Fprintln(os.Stderr, err)
   854  				return loadConfigError(err)
   855  			}
   856  			if !certExists {
   857  				if _, ok := localhostListeners[RPCHost]; ok {
   858  					dcrdCertExists, err := cfgutil.FileExists(
   859  						dcrdDefaultCAFile)
   860  					if err != nil {
   861  						fmt.Fprintln(os.Stderr, err)
   862  						return loadConfigError(err)
   863  					}
   864  					if dcrdCertExists {
   865  						cfg.CAFile.Value = dcrdDefaultCAFile
   866  					}
   867  				}
   868  			}
   869  		}
   870  	}
   871  
   872  	if cfg.SPV && cfg.EnableVoting {
   873  		err := errors.E("SPV voting is not possible: disable --spv or --enablevoting")
   874  		fmt.Fprintln(os.Stderr, err)
   875  		return loadConfigError(err)
   876  	}
   877  	if !cfg.SPV && len(cfg.SPVConnect) > 0 {
   878  		err := errors.E("--spvconnect requires --spv")
   879  		fmt.Fprintln(os.Stderr, err)
   880  		return loadConfigError(err)
   881  	}
   882  	for i, p := range cfg.SPVConnect {
   883  		cfg.SPVConnect[i], err = cfgutil.NormalizeAddress(p, activeNet.Params.DefaultPort)
   884  		if err != nil {
   885  			return loadConfigError(err)
   886  		}
   887  	}
   888  
   889  	// Default to localhost listen addresses if no listeners were manually
   890  	// specified.  When the RPC server is configured to be disabled, remove all
   891  	// listeners so it is not started.
   892  	localhostAddrs, err := net.LookupHost("localhost")
   893  	if err != nil {
   894  		return loadConfigError(err)
   895  	}
   896  	if len(cfg.GRPCListeners) == 0 && !cfg.NoGRPC {
   897  		cfg.GRPCListeners = make([]string, 0, len(localhostAddrs))
   898  		for _, addr := range localhostAddrs {
   899  			cfg.GRPCListeners = append(cfg.GRPCListeners,
   900  				net.JoinHostPort(addr, activeNet.GRPCServerPort))
   901  		}
   902  	} else if cfg.NoGRPC {
   903  		cfg.GRPCListeners = nil
   904  	}
   905  	if len(cfg.LegacyRPCListeners) == 0 && !cfg.NoLegacyRPC {
   906  		cfg.LegacyRPCListeners = make([]string, 0, len(localhostAddrs))
   907  		for _, addr := range localhostAddrs {
   908  			cfg.LegacyRPCListeners = append(cfg.LegacyRPCListeners,
   909  				net.JoinHostPort(addr, activeNet.JSONRPCServerPort))
   910  		}
   911  	} else if cfg.NoLegacyRPC {
   912  		cfg.LegacyRPCListeners = nil
   913  	}
   914  
   915  	// Add default port to all rpc listener addresses if needed and remove
   916  	// duplicate addresses.
   917  	cfg.LegacyRPCListeners, err = cfgutil.NormalizeAddresses(
   918  		cfg.LegacyRPCListeners, activeNet.JSONRPCServerPort)
   919  	if err != nil {
   920  		fmt.Fprintf(os.Stderr,
   921  			"Invalid network address in legacy RPC listeners: %v\n", err)
   922  		return loadConfigError(err)
   923  	}
   924  	cfg.GRPCListeners, err = cfgutil.NormalizeAddresses(
   925  		cfg.GRPCListeners, activeNet.GRPCServerPort)
   926  	if err != nil {
   927  		fmt.Fprintf(os.Stderr,
   928  			"Invalid network address in RPC listeners: %v\n", err)
   929  		return loadConfigError(err)
   930  	}
   931  
   932  	// Both RPC servers may not listen on the same interface/port, with the
   933  	// exception of listeners using port 0.
   934  	if len(cfg.LegacyRPCListeners) > 0 && len(cfg.GRPCListeners) > 0 {
   935  		seenAddresses := make(map[string]struct{}, len(cfg.LegacyRPCListeners))
   936  		for _, addr := range cfg.LegacyRPCListeners {
   937  			seenAddresses[addr] = struct{}{}
   938  		}
   939  		for _, addr := range cfg.GRPCListeners {
   940  			_, seen := seenAddresses[addr]
   941  			if seen && !strings.HasSuffix(addr, ":0") {
   942  				err := errors.Errorf("Address `%s` may not be "+
   943  					"used as a listener address for both "+
   944  					"RPC servers", addr)
   945  				fmt.Fprintln(os.Stderr, err)
   946  				return loadConfigError(err)
   947  			}
   948  		}
   949  	}
   950  
   951  	// Only allow server TLS to be disabled if the RPC server is bound to
   952  	// localhost addresses.
   953  	if cfg.DisableServerTLS {
   954  		allListeners := append(cfg.LegacyRPCListeners, cfg.GRPCListeners...)
   955  		for _, addr := range allListeners {
   956  			host, _, err := net.SplitHostPort(addr)
   957  			if err != nil {
   958  				str := "%s: RPC listen interface '%s' is " +
   959  					"invalid: %v"
   960  				err := errors.Errorf(str, funcName, addr, err)
   961  				fmt.Fprintln(os.Stderr, err)
   962  				fmt.Fprintln(os.Stderr, usageMessage)
   963  				return loadConfigError(err)
   964  			}
   965  			if _, ok := localhostListeners[host]; !ok {
   966  				str := "%s: the --noservertls option may not be used " +
   967  					"when binding RPC to non localhost " +
   968  					"addresses: %s"
   969  				err := errors.Errorf(str, funcName, addr)
   970  				fmt.Fprintln(os.Stderr, err)
   971  				fmt.Fprintln(os.Stderr, usageMessage)
   972  				return loadConfigError(err)
   973  			}
   974  		}
   975  	}
   976  
   977  	// If either VSP pubkey or URL are specified, validate VSP options.
   978  	if cfg.VSPOpts.PubKey != "" || cfg.VSPOpts.URL != "" {
   979  		if cfg.VSPOpts.PubKey == "" {
   980  			err := errors.New("vsp pubkey can not be null")
   981  			fmt.Fprintln(os.Stderr, err)
   982  			return loadConfigError(err)
   983  		}
   984  		if cfg.VSPOpts.URL == "" {
   985  			err := errors.New("vsp URL can not be null")
   986  			fmt.Fprintln(os.Stderr, err)
   987  			return loadConfigError(err)
   988  		}
   989  		if cfg.VSPOpts.MaxFee.Amount == 0 {
   990  			err := errors.New("vsp max fee must be greater than zero")
   991  			fmt.Fprintln(os.Stderr, err)
   992  			return loadConfigError(err)
   993  		}
   994  	}
   995  
   996  	// Expand environment variable and leading ~ for filepaths.
   997  	cfg.CAFile.Value = cleanAndExpandPath(cfg.CAFile.Value)
   998  	cfg.RPCCert.Value = cleanAndExpandPath(cfg.RPCCert.Value)
   999  	cfg.RPCKey.Value = cleanAndExpandPath(cfg.RPCKey.Value)
  1000  	cfg.ClientCAFile.Value = cleanAndExpandPath(cfg.ClientCAFile.Value)
  1001  
  1002  	// If the dcrd username or password are unset, use the same auth as for
  1003  	// the client.  The two settings were previously shared for dcrd and
  1004  	// client auth, so this avoids breaking backwards compatibility while
  1005  	// allowing users to use different auth settings for dcrd and wallet.
  1006  	if cfg.DcrdUsername == "" {
  1007  		cfg.DcrdUsername = cfg.Username
  1008  	}
  1009  	if cfg.DcrdPassword == "" {
  1010  		cfg.DcrdPassword = cfg.Password
  1011  	}
  1012  
  1013  	switch cfg.JSONRPCAuthType {
  1014  	case "basic", "clientcert":
  1015  	default:
  1016  		err := fmt.Errorf("unknown authtype %q", cfg.JSONRPCAuthType)
  1017  		fmt.Fprintln(os.Stderr, err)
  1018  		fmt.Fprintln(os.Stderr, usageMessage)
  1019  		return loadConfigError(err)
  1020  	}
  1021  
  1022  	// Warn if user still has an old ticket buyer configuration file.
  1023  	oldTBConfigFile := filepath.Join(cfg.AppDataDir.Value, "ticketbuyer.conf")
  1024  	if _, err := os.Stat(oldTBConfigFile); err == nil {
  1025  		log.Warnf("%s is no longer used and should be removed. "+
  1026  			"Please prepend 'ticketbuyer.' to each option and "+
  1027  			"move it under the [Ticket Buyer Options] section "+
  1028  			"of %s\n",
  1029  			oldTBConfigFile, configFilePath)
  1030  	}
  1031  
  1032  	// Make list of old versions of testnet directories.
  1033  	var oldTestNets []string
  1034  	oldTestNets = append(oldTestNets, filepath.Join(cfg.AppDataDir.Value, "testnet"))
  1035  	// Warn if old testnet directory is present.
  1036  	for _, oldDir := range oldTestNets {
  1037  		oldDirExists, _ := cfgutil.FileExists(oldDir)
  1038  		if oldDirExists {
  1039  			log.Warnf("Wallet data from previous testnet"+
  1040  				" found (%v) and can probably be removed.",
  1041  				oldDir)
  1042  		}
  1043  	}
  1044  
  1045  	return &cfg, remainingArgs, nil
  1046  }