decred.org/dcrdex@v1.0.5/server/cmd/dcrdex/config.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package main
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"decred.org/dcrdex/dex"
    19  	"decred.org/dcrdex/dex/wait"
    20  	"decred.org/dcrdex/server/admin"
    21  	"decred.org/dcrdex/server/auth"
    22  	"decred.org/dcrdex/server/book"
    23  	"decred.org/dcrdex/server/comms"
    24  	"decred.org/dcrdex/server/db"
    25  	dexsrv "decred.org/dcrdex/server/dex"
    26  	"decred.org/dcrdex/server/market"
    27  	"decred.org/dcrdex/server/matcher"
    28  	"decred.org/dcrdex/server/swap"
    29  	"github.com/decred/dcrd/dcrutil/v4"
    30  	flags "github.com/jessevdk/go-flags"
    31  )
    32  
    33  const (
    34  	defaultConfigFilename      = "dcrdex.conf"
    35  	defaultLogFilename         = "dcrdex.log"
    36  	defaultRPCCertFilename     = "rpc.cert"
    37  	defaultRPCKeyFilename      = "rpc.key"
    38  	defaultDataDirname         = "data"
    39  	defaultLogLevel            = "debug"
    40  	defaultLogDirname          = "logs"
    41  	defaultMarketsConfFilename = "markets.json"
    42  	defaultMaxLogZips          = 128
    43  	defaultPGHost              = "127.0.0.1:5432"
    44  	defaultPGUser              = "dcrdex"
    45  	defaultPGDBName            = "dcrdex_{netname}"
    46  	defaultDEXPrivKeyFilename  = "sigkey"
    47  	defaultRPCHost             = "127.0.0.1"
    48  	defaultRPCPort             = "7232"
    49  	defaultHSHost              = defaultRPCHost // should be a loopback address
    50  	defaultHSPort              = "7252"
    51  	defaultAdminSrvAddr        = "127.0.0.1:6542"
    52  	defaultMaxUserCancels      = 2
    53  	defaultPenaltyThresh       = 20
    54  
    55  	defaultCancelThresh     = 0.95             // 19 cancels : 1 success
    56  	defaultBroadcastTimeout = 12 * time.Minute // accommodate certain known long block download timeouts
    57  	defaultTxWaitExpiration = 2 * time.Minute
    58  )
    59  
    60  var (
    61  	defaultAppDataDir = dcrutil.AppDataDir("dcrdex", false)
    62  )
    63  
    64  type procOpts struct {
    65  	HTTPProfile bool
    66  	CPUProfile  string
    67  }
    68  
    69  // dexConf is the data that is required to setup the dex.
    70  type dexConf struct {
    71  	DataDir          string
    72  	Network          dex.Network
    73  	DBName           string
    74  	DBUser           string
    75  	DBPass           string
    76  	DBHost           string
    77  	DBPort           uint16
    78  	ShowPGConfig     bool
    79  	MarketsConfPath  string
    80  	CancelThreshold  float64
    81  	FreeCancels      bool
    82  	MaxUserCancels   uint32
    83  	PenaltyThreshold uint32
    84  	DEXPrivKeyPath   string
    85  	RPCCert          string
    86  	RPCKey           string
    87  	NoTLS            bool
    88  	RPCListen        []string
    89  	HiddenService    string
    90  	BroadcastTimeout time.Duration
    91  	TxWaitExpiration time.Duration
    92  	AltDNSNames      []string
    93  	LogMaker         *dex.LoggerMaker
    94  	SigningKeyPW     []byte
    95  	AdminSrvOn       bool
    96  	AdminSrvAddr     string
    97  	AdminSrvPW       []byte
    98  	AdminSrvNoTLS    bool
    99  	NoResumeSwaps    bool
   100  	DisableDataAPI   bool
   101  	NodeRelayAddr    string
   102  	ValidateMarkets  bool
   103  }
   104  
   105  type flagsData struct {
   106  	// General application behavior
   107  	AppDataDir  string `short:"A" long:"appdata" description:"Path to application home directory."`
   108  	ConfigFile  string `short:"C" long:"configfile" description:"Path to configuration file."`
   109  	DataDir     string `short:"b" long:"datadir" description:"Directory to store data."`
   110  	LogDir      string `long:"logdir" description:"Directory to log output."`
   111  	DebugLevel  string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}."`
   112  	LocalLogs   bool   `long:"loglocal" description:"Use local time zone time stamps in log entries."`
   113  	MaxLogZips  int    `long:"maxlogzips" description:"The number of zipped log files created by the log rotator to be retained. Setting to 0 will keep all."`
   114  	ShowVersion bool   `short:"V" long:"version" description:"Display version information and exit."`
   115  
   116  	Testnet bool `long:"testnet" description:"Use the test network (default mainnet)."`
   117  	Simnet  bool `long:"simnet" description:"Use the simulation test network (default mainnet)."`
   118  
   119  	RPCCert       string   `long:"rpccert" description:"RPC server TLS certificate file."`
   120  	RPCKey        string   `long:"rpckey" description:"RPC server TLS private key file."`
   121  	RPCListen     []string `long:"rpclisten" description:"IP addresses on which the RPC server should listen for incoming connections."`
   122  	NoTLS         bool     `long:"notls" description:"Run without TLS encryption."`
   123  	AltDNSNames   []string `long:"altdnsnames" description:"A list of hostnames to include in the RPC certificate (X509v3 Subject Alternative Name)."`
   124  	HiddenService string   `long:"hiddenservice" description:"A host:port on which the RPC server should listen for incoming hidden service connections. No TLS is used for these connections."`
   125  
   126  	MarketsConfPath  string        `long:"marketsconfpath" description:"Path to the markets configuration JSON file."`
   127  	BroadcastTimeout time.Duration `long:"bcasttimeout" description:"The broadcast timeout specifies how long clients have to broadcast an expected transaction when it is their turn to act. Matches without the expected action by this time are revoked and the actor is penalized (default: 12 minutes)."`
   128  	TxWaitExpiration time.Duration `long:"txwaitexpiration" description:"How long the server will search for a client-reported transaction before responding to the client with an error indicating that it was not found. This should ideally be less than half of swaps BroadcastTimeout to allow for more than one retry of the client's request (default: 2 minutes)."`
   129  	DEXPrivKeyPath   string        `long:"dexprivkeypath" description:"The path to a file containing the DEX private key for message signing."`
   130  
   131  	CancelThreshold  float64 `long:"cancelthresh" description:"Cancellation rate threshold (cancels/all_completed)."`
   132  	FreeCancels      bool    `long:"freecancels" description:"No cancellation rate enforcement (unlimited cancel orders)."`
   133  	MaxUserCancels   uint32  `long:"maxepochcancels" description:"The maximum number of cancel orders allowed for a user in a given epoch."`
   134  	PenaltyThreshold uint32  `long:"penaltythreshold" description:"The accumulated penalty score at which when a bond is revoked."`
   135  
   136  	HTTPProfile bool   `long:"httpprof" short:"p" description:"Start HTTP profiler."`
   137  	CPUProfile  string `long:"cpuprofile" description:"File for CPU profiling."`
   138  
   139  	PGDBName           string `long:"pgdbname" description:"PostgreSQL DB name."`
   140  	PGUser             string `long:"pguser" description:"PostgreSQL DB user."`
   141  	PGPass             string `long:"pgpass" description:"PostgreSQL DB password."`
   142  	PGHost             string `long:"pghost" description:"PostgreSQL server host:port or UNIX socket (e.g. /run/postgresql)."`
   143  	ShowPGConfig       bool   `long:"showpgconfig" description:"Logs the PostgreSQL db configuration on system start up."`
   144  	SigningKeyPassword string `long:"signingkeypass" description:"Password for encrypting/decrypting the dex privkey. INSECURE. Do not set unless absolutely necessary."`
   145  	AdminSrvOn         bool   `long:"adminsrvon" description:"Turn on the admin server."`
   146  	AdminSrvAddr       string `long:"adminsrvaddr" description:"Administration HTTPS server address (default: 127.0.0.1:6542)."`
   147  	AdminSrvPassword   string `long:"adminsrvpass" description:"Admin server password. INSECURE. Do not set unless absolutely necessary."`
   148  	AdminSrvNoTLS      bool   `long:"adminsrvnotls" description:"Run admin server without TLS. Only use this option if you are using a securely configured reverse proxy."`
   149  
   150  	NoResumeSwaps bool `long:"noresumeswaps" description:"Do not attempt to resume swaps that are active in the DB."`
   151  
   152  	DisableDataAPI bool `long:"nodata" description:"Disable the HTTP data API."`
   153  
   154  	NodeRelayAddr string `long:"noderelayaddr" description:"The public address by which node sources should connect to the node relay"`
   155  
   156  	ValidateMarkets bool `long:"validate" description:"Validate the market configuration and quit"`
   157  }
   158  
   159  // supportedSubsystems returns a sorted slice of the supported subsystems for
   160  // logging purposes.
   161  func supportedSubsystems() []string {
   162  	// Convert the subsystemLoggers map keys to a slice.
   163  	subsystems := make([]string, 0, len(subsystemLoggers))
   164  	for subsysID := range subsystemLoggers {
   165  		subsystems = append(subsystems, subsysID)
   166  	}
   167  
   168  	// Sort the subsystems for stable display.
   169  	sort.Strings(subsystems)
   170  	return subsystems
   171  }
   172  
   173  // parseAndSetDebugLevels attempts to parse the specified debug level and set
   174  // the levels accordingly. An appropriate error is returned if anything is
   175  // invalid.
   176  func parseAndSetDebugLevels(debugLevel string, UTC bool) (*dex.LoggerMaker, error) {
   177  	// Create a LoggerMaker with the level string.
   178  	lm, err := dex.NewLoggerMaker(logWriter{}, debugLevel, UTC)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	// Create subsystem loggers.
   184  	for subsysID := range subsystemLoggers {
   185  		subsystemLoggers[subsysID] = lm.Logger(subsysID)
   186  	}
   187  
   188  	// Set main's Logger.
   189  	log = subsystemLoggers["MAIN"]
   190  
   191  	// Set package-level loggers. TODO: eliminate these by replacing them with
   192  	// loggers provided to constructors.
   193  	dexsrv.UseLogger(subsystemLoggers["DEX"])
   194  	db.UseLogger(subsystemLoggers["DB"])
   195  	comms.UseLogger(subsystemLoggers["COMM"])
   196  	auth.UseLogger(subsystemLoggers["AUTH"])
   197  	swap.UseLogger(subsystemLoggers["SWAP"])
   198  	market.UseLogger(subsystemLoggers["MKT"])
   199  	book.UseLogger(subsystemLoggers["BOOK"])
   200  	matcher.UseLogger(subsystemLoggers["MTCH"])
   201  	wait.UseLogger(subsystemLoggers["WAIT"])
   202  	admin.UseLogger(subsystemLoggers["ADMN"])
   203  
   204  	return lm, nil
   205  }
   206  
   207  const missingPort = "missing port in address"
   208  
   209  // normalizeNetworkAddress checks for a valid local network address format and
   210  // adds default host and port if not present. Invalidates addresses that include
   211  // a protocol identifier.
   212  func normalizeNetworkAddress(a, defaultHost, defaultPort string) (string, error) {
   213  	if strings.Contains(a, "://") {
   214  		return a, fmt.Errorf("address %s contains a protocol identifier, which is not allowed", a)
   215  	}
   216  	if a == "" {
   217  		return net.JoinHostPort(defaultHost, defaultPort), nil
   218  	}
   219  	host, port, err := net.SplitHostPort(a)
   220  	if err != nil {
   221  		var addrErr *net.AddrError
   222  		if errors.As(err, &addrErr) && addrErr.Err == missingPort {
   223  			host = strings.Trim(addrErr.Addr, "[]") // JoinHostPort expects no brackets for ipv6 hosts
   224  			normalized := net.JoinHostPort(host, defaultPort)
   225  			host, port, err = net.SplitHostPort(normalized)
   226  			if err != nil {
   227  				return a, fmt.Errorf("unable to address %s after port resolution: %w", normalized, err)
   228  			}
   229  		} else {
   230  			return a, fmt.Errorf("unable to normalize address %s: %w", a, err)
   231  		}
   232  	}
   233  	if host == "" {
   234  		host = defaultHost
   235  	}
   236  	if port == "" {
   237  		port = defaultPort
   238  	}
   239  	return net.JoinHostPort(host, port), nil
   240  }
   241  
   242  // loadConfig initializes and parses the config using a config file and command
   243  // line options.
   244  func loadConfig() (*dexConf, *procOpts, error) {
   245  	loadConfigError := func(err error) (*dexConf, *procOpts, error) {
   246  		return nil, nil, err
   247  	}
   248  
   249  	// Default config
   250  	cfg := flagsData{
   251  		AppDataDir: defaultAppDataDir,
   252  		// Defaults for ConfigFile, LogDir, and DataDir are set relative to
   253  		// AppDataDir. They are not to be set here.
   254  		MaxLogZips:       defaultMaxLogZips,
   255  		RPCCert:          defaultRPCCertFilename,
   256  		RPCKey:           defaultRPCKeyFilename,
   257  		DebugLevel:       defaultLogLevel,
   258  		PGDBName:         defaultPGDBName,
   259  		PGUser:           defaultPGUser,
   260  		PGHost:           defaultPGHost,
   261  		MarketsConfPath:  defaultMarketsConfFilename,
   262  		DEXPrivKeyPath:   defaultDEXPrivKeyFilename,
   263  		BroadcastTimeout: defaultBroadcastTimeout,
   264  		TxWaitExpiration: defaultTxWaitExpiration,
   265  		CancelThreshold:  defaultCancelThresh,
   266  		MaxUserCancels:   defaultMaxUserCancels,
   267  		PenaltyThreshold: defaultPenaltyThresh,
   268  	}
   269  
   270  	// Pre-parse the command line options to see if an alternative config file
   271  	// or the version flag was specified. Any errors aside from the help message
   272  	// error can be ignored here since they will be caught by the final parse
   273  	// below.
   274  	var preCfg flagsData // zero values as defaults
   275  	preParser := flags.NewParser(&preCfg, flags.HelpFlag)
   276  	_, err := preParser.Parse()
   277  	if err != nil {
   278  		if e, ok := err.(*flags.Error); ok && e.Type != flags.ErrHelp {
   279  			fmt.Fprintln(os.Stderr, err)
   280  			os.Exit(1)
   281  		} else if ok && e.Type == flags.ErrHelp {
   282  			fmt.Fprintln(os.Stdout, err)
   283  			os.Exit(0)
   284  		}
   285  	}
   286  
   287  	// Show the version and exit if the version flag was specified.
   288  	if preCfg.ShowVersion {
   289  		fmt.Printf("%s version %s (Go version %s %s/%s)\n",
   290  			appName, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
   291  		os.Exit(0)
   292  	}
   293  
   294  	// Special show command to list supported subsystems and exit.
   295  	if preCfg.DebugLevel == "show" {
   296  		fmt.Println("Supported subsystems", supportedSubsystems())
   297  		os.Exit(0)
   298  	}
   299  
   300  	// If a non-default appdata folder is specified on the command line, it may
   301  	// be necessary adjust the config file location. If the the config file
   302  	// location was not specified on the command line, the default location
   303  	// should be under the non-default appdata directory. However, if the config
   304  	// file was specified on the command line, it should be used regardless of
   305  	// the appdata directory.
   306  	if preCfg.AppDataDir != "" {
   307  		// appdata was set on the command line. If it is not absolute, make it
   308  		// relative to cwd.
   309  		cfg.AppDataDir, err = filepath.Abs(preCfg.AppDataDir)
   310  		if err != nil {
   311  			fmt.Fprintf(os.Stderr, "Unable to determine working directory: %v", err)
   312  			os.Exit(1)
   313  		}
   314  	}
   315  	isDefaultConfigFile := preCfg.ConfigFile == ""
   316  	if isDefaultConfigFile {
   317  		preCfg.ConfigFile = filepath.Join(cfg.AppDataDir, defaultConfigFilename)
   318  	} else if !filepath.IsAbs(preCfg.ConfigFile) {
   319  		preCfg.ConfigFile = filepath.Join(cfg.AppDataDir, preCfg.ConfigFile)
   320  	}
   321  
   322  	// Config file name for logging.
   323  	configFile := "NONE (defaults)"
   324  
   325  	// Load additional config from file.
   326  	var configFileError error
   327  	parser := flags.NewParser(&cfg, flags.Default)
   328  	// Do not error default config file is missing.
   329  	if _, err := os.Stat(preCfg.ConfigFile); os.IsNotExist(err) {
   330  		// Non-default config file must exist.
   331  		if !isDefaultConfigFile {
   332  			fmt.Fprintln(os.Stderr, err)
   333  			return loadConfigError(err)
   334  		}
   335  		// Warn about missing default config file, but continue.
   336  		fmt.Printf("Config file (%s) does not exist. Using defaults.\n",
   337  			preCfg.ConfigFile)
   338  	} else {
   339  		// The config file exists, so attempt to parse it.
   340  		err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
   341  		if err != nil {
   342  			if _, ok := err.(*os.PathError); !ok {
   343  				fmt.Fprintln(os.Stderr, err)
   344  				parser.WriteHelp(os.Stderr)
   345  				return loadConfigError(err)
   346  			}
   347  			configFileError = err
   348  		}
   349  		configFile = preCfg.ConfigFile
   350  	}
   351  
   352  	// Parse command line options again to ensure they take precedence.
   353  	_, err = parser.Parse()
   354  	if err != nil {
   355  		if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
   356  			parser.WriteHelp(os.Stderr)
   357  		}
   358  		return loadConfigError(err)
   359  	}
   360  
   361  	// Warn about missing config file after the final command line parse
   362  	// succeeds. This prevents the warning on help messages and invalid options.
   363  	if configFileError != nil {
   364  		fmt.Printf("%v\n", configFileError)
   365  		return loadConfigError(configFileError)
   366  	}
   367  
   368  	// Select the network.
   369  	var numNets int
   370  	network := dex.Mainnet
   371  	if cfg.Testnet {
   372  		numNets++
   373  		network = dex.Testnet
   374  	}
   375  	if cfg.Simnet {
   376  		numNets++
   377  		network = dex.Simnet
   378  	}
   379  	if numNets > 1 {
   380  		err := fmt.Errorf("both testnet and simnet flags specified")
   381  		fmt.Fprintln(os.Stderr, err)
   382  		return loadConfigError(err)
   383  	}
   384  
   385  	// Create the app data directory if it doesn't already exist.
   386  	err = os.MkdirAll(cfg.AppDataDir, 0700)
   387  	if err != nil {
   388  		// Show a nicer error message if it's because a symlink is linked to a
   389  		// directory that does not exist (probably because it's not mounted).
   390  		if e, ok := err.(*os.PathError); ok && os.IsExist(err) {
   391  			if link, lerr := os.Readlink(e.Path); lerr == nil {
   392  				str := "is symlink %s -> %s mounted?"
   393  				err = fmt.Errorf(str, e.Path, link)
   394  			}
   395  		}
   396  
   397  		err := fmt.Errorf("failed to create home directory: %v", err)
   398  		fmt.Fprintln(os.Stderr, err)
   399  		return loadConfigError(err)
   400  	}
   401  
   402  	// If datadir or logdir are defaults or non-default relative paths, prepend
   403  	// the appdata directory.
   404  	if cfg.DataDir == "" {
   405  		cfg.DataDir = filepath.Join(cfg.AppDataDir, defaultDataDirname)
   406  	} else if !filepath.IsAbs(cfg.DataDir) {
   407  		cfg.DataDir = filepath.Join(cfg.AppDataDir, cfg.DataDir)
   408  	}
   409  	if cfg.LogDir == "" {
   410  		cfg.LogDir = filepath.Join(cfg.AppDataDir, defaultLogDirname)
   411  	} else if !filepath.IsAbs(cfg.LogDir) {
   412  		cfg.LogDir = filepath.Join(cfg.AppDataDir, cfg.LogDir)
   413  	}
   414  
   415  	// Append the network type to the data directory so it is "namespaced" per
   416  	// network.  In addition to the block database, there are other pieces of
   417  	// data that are saved to disk such as address manager state. All data is
   418  	// specific to a network, so namespacing the data directory means each
   419  	// individual piece of serialized data does not have to worry about changing
   420  	// names per network and such.
   421  	//
   422  	// Make list of old versions of testnet directories here since the network
   423  	// specific DataDir will be used after this.
   424  	cfg.DataDir = dex.CleanAndExpandPath(cfg.DataDir)
   425  	cfg.DataDir = filepath.Join(cfg.DataDir, network.String())
   426  	// Create the data folder if it does not exist.
   427  	err = os.MkdirAll(cfg.DataDir, 0700)
   428  	if err != nil {
   429  		return loadConfigError(err)
   430  	}
   431  
   432  	logRotator = nil
   433  	// Append the network type to the log directory so it is "namespaced"
   434  	// per network in the same fashion as the data directory.
   435  	cfg.LogDir = dex.CleanAndExpandPath(cfg.LogDir)
   436  	cfg.LogDir = filepath.Join(cfg.LogDir, network.String())
   437  
   438  	// Ensure that all specified files are absolute paths, prepending the
   439  	// appdata path if not.
   440  	if !filepath.IsAbs(cfg.RPCCert) {
   441  		cfg.RPCCert = filepath.Join(cfg.AppDataDir, cfg.RPCCert)
   442  	}
   443  	if !filepath.IsAbs(cfg.RPCKey) {
   444  		cfg.RPCKey = filepath.Join(cfg.AppDataDir, cfg.RPCKey)
   445  	}
   446  	if !filepath.IsAbs(cfg.MarketsConfPath) {
   447  		cfg.MarketsConfPath = filepath.Join(cfg.AppDataDir, cfg.MarketsConfPath)
   448  	}
   449  	if !filepath.IsAbs(cfg.DEXPrivKeyPath) {
   450  		cfg.DEXPrivKeyPath = filepath.Join(cfg.AppDataDir, cfg.DEXPrivKeyPath)
   451  	}
   452  
   453  	// Validate each RPC listen host:port.
   454  	var RPCListen []string
   455  	if len(cfg.RPCListen) == 0 {
   456  		RPCListen = []string{defaultRPCHost + ":" + defaultRPCPort}
   457  	}
   458  	for i := range cfg.RPCListen {
   459  		listen, err := normalizeNetworkAddress(cfg.RPCListen[i], defaultRPCHost, defaultRPCPort)
   460  		if err != nil {
   461  			return loadConfigError(err)
   462  		}
   463  		RPCListen = append(RPCListen, listen)
   464  	}
   465  	var HiddenService string
   466  	if cfg.HiddenService != "" {
   467  		HiddenService, err = normalizeNetworkAddress(cfg.HiddenService, defaultHSHost, defaultHSPort)
   468  		if err != nil {
   469  			return loadConfigError(err)
   470  		}
   471  	}
   472  
   473  	// Initialize log rotation. This creates the LogDir if needed.
   474  	if cfg.MaxLogZips < 0 {
   475  		cfg.MaxLogZips = 0
   476  	}
   477  	initLogRotator(filepath.Join(cfg.LogDir, defaultLogFilename), cfg.MaxLogZips)
   478  
   479  	// Create the loggers: Parse and validate the debug level string, create the
   480  	// subsystem loggers, and set package level loggers. The generated
   481  	// LoggerMaker is used by other subsystems to create new loggers with the
   482  	// same backend.
   483  	logMaker, err := parseAndSetDebugLevels(cfg.DebugLevel, !cfg.LocalLogs)
   484  	if err != nil {
   485  		fmt.Fprintln(os.Stderr, err)
   486  		parser.WriteHelp(os.Stderr)
   487  		return loadConfigError(err)
   488  	}
   489  	// Only now can any of the loggers be used.
   490  
   491  	log.Infof("App data folder: %s", cfg.AppDataDir)
   492  	log.Infof("Data folder:     %s", cfg.DataDir)
   493  	log.Infof("Log folder:      %s", cfg.LogDir)
   494  	log.Infof("Config file:     %s", configFile)
   495  
   496  	if !cfg.LocalLogs {
   497  		log.Infof("Logging with UTC time stamps. Current local time is %v", time.Now().Local().Format("15:04:05 MST"))
   498  	}
   499  
   500  	var dbPort uint16
   501  	dbHost := cfg.PGHost
   502  	// For UNIX sockets, do not attempt to parse out a port.
   503  	if !strings.HasPrefix(dbHost, "/") {
   504  		var dbPortStr string
   505  		dbHost, dbPortStr, err = net.SplitHostPort(cfg.PGHost)
   506  		if err != nil {
   507  			return loadConfigError(fmt.Errorf("invalid DB host %q: %v", cfg.PGHost, err))
   508  		}
   509  		port, err := strconv.ParseUint(dbPortStr, 10, 16)
   510  		if err != nil {
   511  			return loadConfigError(fmt.Errorf("invalid DB port %q: %v", dbPortStr, err))
   512  		}
   513  		dbPort = uint16(port)
   514  	}
   515  
   516  	adminSrvAddr := defaultAdminSrvAddr
   517  	if cfg.AdminSrvAddr != "" {
   518  		_, port, err := net.SplitHostPort(cfg.AdminSrvAddr)
   519  		if err != nil {
   520  			return loadConfigError(fmt.Errorf("invalid admin server host %q: %v", cfg.AdminSrvAddr, err))
   521  		}
   522  		_, err = strconv.ParseUint(port, 10, 16)
   523  		if err != nil {
   524  			return loadConfigError(fmt.Errorf("invalid admin server port %q: %v", port, err))
   525  		}
   526  		adminSrvAddr = cfg.AdminSrvAddr
   527  	}
   528  
   529  	// If using {netname} then replace it with the network name.
   530  	cfg.PGDBName = strings.ReplaceAll(cfg.PGDBName, "{netname}", network.String())
   531  
   532  	dexCfg := &dexConf{
   533  		DataDir:          cfg.DataDir,
   534  		Network:          network,
   535  		DBName:           cfg.PGDBName,
   536  		DBHost:           dbHost,
   537  		DBPort:           dbPort,
   538  		DBUser:           cfg.PGUser,
   539  		DBPass:           cfg.PGPass,
   540  		ShowPGConfig:     cfg.ShowPGConfig,
   541  		MarketsConfPath:  cfg.MarketsConfPath,
   542  		CancelThreshold:  cfg.CancelThreshold,
   543  		MaxUserCancels:   cfg.MaxUserCancels,
   544  		FreeCancels:      cfg.FreeCancels,
   545  		PenaltyThreshold: cfg.PenaltyThreshold,
   546  		DEXPrivKeyPath:   cfg.DEXPrivKeyPath,
   547  		RPCCert:          cfg.RPCCert,
   548  		RPCKey:           cfg.RPCKey,
   549  		NoTLS:            cfg.NoTLS,
   550  		RPCListen:        RPCListen,
   551  		HiddenService:    HiddenService,
   552  		BroadcastTimeout: cfg.BroadcastTimeout,
   553  		TxWaitExpiration: cfg.TxWaitExpiration,
   554  		AltDNSNames:      cfg.AltDNSNames,
   555  		LogMaker:         logMaker,
   556  		SigningKeyPW:     []byte(cfg.SigningKeyPassword),
   557  		AdminSrvAddr:     adminSrvAddr,
   558  		AdminSrvOn:       cfg.AdminSrvOn,
   559  		AdminSrvPW:       []byte(cfg.AdminSrvPassword),
   560  		AdminSrvNoTLS:    cfg.AdminSrvNoTLS,
   561  		NoResumeSwaps:    cfg.NoResumeSwaps,
   562  		DisableDataAPI:   cfg.DisableDataAPI,
   563  		NodeRelayAddr:    cfg.NodeRelayAddr,
   564  		ValidateMarkets:  cfg.ValidateMarkets,
   565  	}
   566  
   567  	opts := &procOpts{
   568  		CPUProfile:  cfg.CPUProfile,
   569  		HTTPProfile: cfg.HTTPProfile,
   570  	}
   571  
   572  	return dexCfg, opts, nil
   573  }