Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vspd: Move config to internal package. #473

Merged
merged 3 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions cmd/vspd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func main() {
// initLogging uses the provided vspd config to create a logging backend, and
// returns a function which can be used to create ready-to-use subsystem
// loggers.
func initLogging(cfg *vspdConfig) (func(subsystem string) slog.Logger, error) {
backend, err := newLogBackend(cfg.logPath, "vspd", cfg.MaxLogSize, cfg.LogsToKeep)
func initLogging(cfg *vspd.Config) (func(subsystem string) slog.Logger, error) {
backend, err := newLogBackend(cfg.LogDir(), "vspd", cfg.MaxLogSize, cfg.LogsToKeep)
if err != nil {
return nil, fmt.Errorf("failed to initialize logger: %w", err)
}
Expand All @@ -60,7 +60,7 @@ func initLogging(cfg *vspdConfig) (func(subsystem string) slog.Logger, error) {
// fact that deferred functions do not run when os.Exit() is called.
func run() int {
// Load config file and parse CLI args.
cfg, err := loadConfig()
cfg, err := vspd.LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "loadConfig error: %v\n", err)
return 1
Expand All @@ -82,7 +82,9 @@ func run() int {
log.Criticalf("Version %s (Go version %s %s/%s)", version.String(),
runtime.Version(), runtime.GOOS, runtime.GOARCH)

if cfg.network == &config.MainNet && version.IsPreRelease() {
network := cfg.Network()

if network == &config.MainNet && version.IsPreRelease() {
log.Warnf("")
log.Warnf("\tWARNING: This is a pre-release version of vspd which should not be used on mainnet")
log.Warnf("")
Expand All @@ -101,7 +103,7 @@ func run() int {
}

// Open database.
db, err := database.Open(cfg.dbPath, makeLogger(" DB"), maxVoteChangeRecords)
db, err := database.Open(cfg.DatabaseFile(), makeLogger(" DB"), maxVoteChangeRecords)
if err != nil {
log.Errorf("Failed to open database: %v", err)
return 1
Expand All @@ -116,18 +118,21 @@ func run() int {

// Create RPC client for local dcrd instance (used for broadcasting and
// checking the status of fee transactions).
dcrd := rpc.SetupDcrd(cfg.DcrdUser, cfg.DcrdPass, cfg.DcrdHost, cfg.dcrdCert, cfg.network.Params, rpcLog, blockNotifChan)
dd := cfg.DcrdDetails()
dcrd := rpc.SetupDcrd(dd.User, dd.Password, dd.Host, dd.Cert, network.Params, rpcLog, blockNotifChan)

defer dcrd.Close()

// Create RPC client for remote dcrwallet instances (used for voting).
wallets := rpc.SetupWallet(cfg.walletUsers, cfg.walletPasswords, cfg.walletHosts, cfg.walletCerts, cfg.network.Params, rpcLog)
wd := cfg.WalletDetails()
wallets := rpc.SetupWallet(wd.Users, wd.Passwords, wd.Hosts, wd.Certs, network.Params, rpcLog)
defer wallets.Close()

// Create webapi server.
apiCfg := webapi.Config{
Listen: cfg.Listen,
VSPFee: cfg.VSPFee,
Network: cfg.network,
Network: network,
SupportEmail: cfg.SupportEmail,
VspClosed: cfg.VspClosed,
VspClosedMsg: cfg.VspClosedMsg,
Expand All @@ -154,7 +159,7 @@ func run() int {
}()

// Start vspd.
vspd := vspd.New(cfg.network, log, db, dcrd, wallets, blockNotifChan)
vspd := vspd.New(network, log, db, dcrd, wallets, blockNotifChan)
wg.Add(1)
go func() {
vspd.Run(ctx)
Expand Down
123 changes: 85 additions & 38 deletions cmd/vspd/config.go → internal/vspd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main
package vspd

import (
"errors"
Expand All @@ -29,8 +29,8 @@ const (
dbFilename = "vspd.db"
)

// vspdConfig defines the configuration options for the vspd process.
type vspdConfig struct {
// Config defines the configuration options for the vspd process.
type Config struct {
Listen string `long:"listen" ini-name:"listen" description:"The ip:port to listen for API requests."`
LogLevel string `long:"loglevel" ini-name:"loglevel" description:"Logging level." choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"critical"`
MaxLogSize int64 `long:"maxlogsize" ini-name:"maxlogsize" description:"File size threshold for log file rotation (MB)."`
Expand Down Expand Up @@ -59,15 +59,48 @@ type vspdConfig struct {
HomeDir string `long:"homedir" no-ini:"true" description:"Path to application home directory. Used for storing VSP database and logs."`
ConfigFile string `long:"configfile" no-ini:"true" description:"DEPRECATED: This behavior is no longer available and this option will be removed in a future version of the software."`

logPath string
dbPath string
network *config.Network
dcrdCert []byte
walletHosts, walletUsers, walletPasswords []string
walletCerts [][]byte
// The following fields are derived from the above fields by LoadConfig().
dataDir string
network *config.Network
dcrdDetails *DcrdDetails
walletDetails *WalletDetails
}

var defaultConfig = vspdConfig{
type DcrdDetails struct {
User string
Password string
Host string
Cert []byte
}

type WalletDetails struct {
Users []string
Passwords []string
Hosts []string
Certs [][]byte
}

func (cfg *Config) Network() *config.Network {
return cfg.network
}

func (cfg *Config) LogDir() string {
return filepath.Join(cfg.HomeDir, "logs", cfg.network.Name)
}

func (cfg *Config) DatabaseFile() string {
return filepath.Join(cfg.dataDir, dbFilename)
}

func (cfg *Config) DcrdDetails() *DcrdDetails {
return cfg.dcrdDetails
}

func (cfg *Config) WalletDetails() *WalletDetails {
return cfg.walletDetails
}

var DefaultConfig = Config{
Listen: ":8800",
LogLevel: "debug",
MaxLogSize: int64(10),
Expand Down Expand Up @@ -155,7 +188,7 @@ func normalizeAddress(addr, defaultPort string) string {
return addr
}

// loadConfig initializes and parses the config using a config file and command
// LoadConfig initializes and parses the config using a config file and command
// line options.
//
// The configuration proceeds as follows:
Expand All @@ -167,8 +200,8 @@ func normalizeAddress(addr, defaultPort string) string {
// The above results in vspd functioning properly without any config settings
// while still allowing the user to override settings with config files and
// command line options. Command line options always take precedence.
func loadConfig() (*vspdConfig, error) {
cfg := defaultConfig
func LoadConfig() (*Config, error) {
cfg := DefaultConfig

// If command line options are requesting help, write it to stdout and exit.
if config.WriteHelp(&cfg) {
Expand Down Expand Up @@ -299,11 +332,22 @@ func loadConfig() (*vspdConfig, error) {

// Load dcrd RPC certificate.
cfg.DcrdCert = cleanAndExpandPath(cfg.DcrdCert)
cfg.dcrdCert, err = os.ReadFile(cfg.DcrdCert)
dcrdCert, err := os.ReadFile(cfg.DcrdCert)
if err != nil {
return nil, fmt.Errorf("failed to read dcrd cert file: %w", err)
}

// Add default port for the active network if there is no port specified.
cfg.DcrdHost = normalizeAddress(cfg.DcrdHost, cfg.network.DcrdRPCServerPort)

// All dcrd connection details are validated and preprocessed.
cfg.dcrdDetails = &DcrdDetails{
User: cfg.DcrdUser,
Password: cfg.DcrdPass,
Host: cfg.DcrdHost,
Cert: dcrdCert,
}

// Ensure the dcrwallet RPC username is set.
if cfg.WalletUsers == "" {
return nil, errors.New("the walletuser option is not set")
Expand All @@ -320,20 +364,20 @@ func loadConfig() (*vspdConfig, error) {
}

// Parse list of wallet hosts.
cfg.walletHosts = strings.Split(cfg.WalletHosts, ",")
numHost := len(cfg.walletHosts)
walletHosts := strings.Split(cfg.WalletHosts, ",")
numHost := len(walletHosts)

// An RPC username must be specified for each wallet host.
cfg.walletUsers = strings.Split(cfg.WalletUsers, ",")
numUser := len(cfg.walletUsers)
walletUsers := strings.Split(cfg.WalletUsers, ",")
numUser := len(walletUsers)
if numUser != numHost {
return nil, fmt.Errorf("%d wallet hosts specified, expected %d RPC usernames, got %d",
numHost, numHost, numUser)
}

// An RPC password must be specified for each wallet host.
cfg.walletPasswords = strings.Split(cfg.WalletPasswords, ",")
numPass := len(cfg.walletPasswords)
walletPasswords := strings.Split(cfg.WalletPasswords, ",")
numPass := len(walletPasswords)
if numPass != numHost {
return nil, fmt.Errorf("%d wallet hosts specified, expected %d RPC passwords, got %d",
numHost, numHost, numPass)
Expand All @@ -348,10 +392,10 @@ func loadConfig() (*vspdConfig, error) {
}

// Load dcrwallet RPC certificate(s).
cfg.walletCerts = make([][]byte, numCert)
walletCerts := make([][]byte, numCert)
for i := 0; i < numCert; i++ {
certs[i] = cleanAndExpandPath(certs[i])
cfg.walletCerts[i], err = os.ReadFile(certs[i])
walletCerts[i], err = os.ReadFile(certs[i])
if err != nil {
return nil, fmt.Errorf("failed to read dcrwallet cert file: %w", err)
}
Expand All @@ -365,29 +409,32 @@ func loadConfig() (*vspdConfig, error) {

// Add default port for the active network if there is no port specified.
for i := 0; i < numHost; i++ {
cfg.walletHosts[i] = normalizeAddress(cfg.walletHosts[i], cfg.network.WalletRPCServerPort)
walletHosts[i] = normalizeAddress(walletHosts[i], cfg.network.WalletRPCServerPort)
}

// All dcrwallet connection details are validated and preprocessed.
cfg.walletDetails = &WalletDetails{
Users: walletUsers,
Passwords: walletPasswords,
Hosts: walletHosts,
Certs: walletCerts,
}
cfg.DcrdHost = normalizeAddress(cfg.DcrdHost, cfg.network.DcrdRPCServerPort)

// Create the data directory.
dataDir := filepath.Join(cfg.HomeDir, "data", cfg.network.Name)
err = os.MkdirAll(dataDir, 0700)
cfg.dataDir = filepath.Join(cfg.HomeDir, "data", cfg.network.Name)
err = os.MkdirAll(cfg.dataDir, 0700)
if err != nil {
return nil, fmt.Errorf("failed to create data directory: %w", err)
}

// Set the log path.
cfg.logPath = filepath.Join(cfg.HomeDir, "logs", cfg.network.Name)

// Set the database path.
cfg.dbPath = filepath.Join(dataDir, dbFilename)
dbPath := cfg.DatabaseFile()

// If xpub has been provided, create a new database and exit.
if cfg.FeeXPub != "" {
// If database already exists, return error.
if fileExists(cfg.dbPath) {
if fileExists(dbPath) {
return nil, fmt.Errorf("database already initialized at %s, "+
"--feexpub option is not needed", cfg.dbPath)
"--feexpub option is not needed", dbPath)
}

// Ensure provided value is a valid key for the selected network.
Expand All @@ -397,10 +444,10 @@ func loadConfig() (*vspdConfig, error) {
}

// Create new database.
fmt.Printf("Initializing new database at %s\n", cfg.dbPath)
err = database.CreateNew(cfg.dbPath, cfg.FeeXPub)
fmt.Printf("Initializing new database at %s\n", dbPath)
err = database.CreateNew(dbPath, cfg.FeeXPub)
if err != nil {
return nil, fmt.Errorf("error creating db file %s: %w", cfg.dbPath, err)
return nil, fmt.Errorf("error creating db file %s: %w", dbPath, err)
}

// Exit with success
Expand All @@ -409,9 +456,9 @@ func loadConfig() (*vspdConfig, error) {
}

// If database does not exist, return error.
if !fileExists(cfg.dbPath) {
if !fileExists(dbPath) {
return nil, fmt.Errorf("no database exists in %s. Run vspd with the"+
" --feexpub option to initialize one", dataDir)
" --feexpub option to initialize one", cfg.dataDir)
}

return &cfg, nil
Expand Down
Loading