Returning a single struct which contains multiple named fields reduces the chance of a mistake in the calling code, as compared to returning multiple unnamed values which are all of the same type.
192 lines
5.1 KiB
Go
192 lines
5.1 KiB
Go
// Copyright (c) 2020-2024 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/wire"
|
|
"github.com/decred/slog"
|
|
"github.com/decred/vspd/database"
|
|
"github.com/decred/vspd/internal/config"
|
|
"github.com/decred/vspd/internal/signal"
|
|
"github.com/decred/vspd/internal/version"
|
|
"github.com/decred/vspd/internal/vspd"
|
|
"github.com/decred/vspd/internal/webapi"
|
|
"github.com/decred/vspd/rpc"
|
|
)
|
|
|
|
const (
|
|
// maxVoteChangeRecords defines how many vote change records will be stored
|
|
// for each ticket. The limit is in place to mitigate DoS attacks on server
|
|
// storage space. When storing a new record breaches this limit, the oldest
|
|
// record in the database is deleted.
|
|
maxVoteChangeRecords = 10
|
|
)
|
|
|
|
func main() {
|
|
os.Exit(run())
|
|
}
|
|
|
|
// 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 *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)
|
|
}
|
|
|
|
var ok bool
|
|
level, ok := slog.LevelFromString(cfg.LogLevel)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown log level: %q", cfg.LogLevel)
|
|
}
|
|
|
|
return func(subsystem string) slog.Logger {
|
|
log := backend.Logger(subsystem)
|
|
log.SetLevel(level)
|
|
return log
|
|
}, nil
|
|
}
|
|
|
|
// run is the real main function for vspd. It is necessary to work around the
|
|
// fact that deferred functions do not run when os.Exit() is called.
|
|
func run() int {
|
|
// Load config file and parse CLI args.
|
|
cfg, err := vspd.LoadConfig()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "loadConfig error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
makeLogger, err := initLogging(cfg)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "initLogging error: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
log := makeLogger("VSP")
|
|
|
|
// Create a context that is canceled when a shutdown request is received
|
|
// through an interrupt signal such as SIGINT (Ctrl+C).
|
|
ctx := signal.ShutdownListener(log)
|
|
|
|
defer log.Criticalf("Shutdown complete")
|
|
log.Criticalf("Version %s (Go version %s %s/%s)", version.String(),
|
|
runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
|
|
|
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("")
|
|
}
|
|
|
|
if cfg.VspClosed {
|
|
log.Warnf("")
|
|
log.Warnf("\tWARNING: Config --vspclosed is set. This will prevent vspd from accepting new tickets")
|
|
log.Warnf("")
|
|
}
|
|
|
|
if cfg.ConfigFile != "" {
|
|
log.Warnf("")
|
|
log.Warnf("\tWARNING: Config --configfile is set. This is a deprecated option which has no effect and will be removed in a future release")
|
|
log.Warnf("")
|
|
}
|
|
|
|
// Open database.
|
|
db, err := database.Open(cfg.DatabaseFile(), makeLogger(" DB"), maxVoteChangeRecords)
|
|
if err != nil {
|
|
log.Errorf("Failed to open database: %v", err)
|
|
return 1
|
|
}
|
|
const writeBackup = true
|
|
defer db.Close(writeBackup)
|
|
|
|
rpcLog := makeLogger("RPC")
|
|
|
|
// Create a channel to receive blockConnected notifications from dcrd.
|
|
blockNotifChan := make(chan *wire.BlockHeader)
|
|
|
|
// Create RPC client for local dcrd instance (used for broadcasting and
|
|
// checking the status of fee transactions).
|
|
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).
|
|
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: network,
|
|
SupportEmail: cfg.SupportEmail,
|
|
VspClosed: cfg.VspClosed,
|
|
VspClosedMsg: cfg.VspClosedMsg,
|
|
AdminPass: cfg.AdminPass,
|
|
Debug: cfg.WebServerDebug,
|
|
Designation: cfg.Designation,
|
|
MaxVoteChangeRecords: maxVoteChangeRecords,
|
|
VspdVersion: version.String(),
|
|
}
|
|
api, err := webapi.New(db, makeLogger("API"), dcrd, wallets, apiCfg)
|
|
if err != nil {
|
|
log.Errorf("Failed to initialize webapi: %v", err)
|
|
return 1
|
|
}
|
|
|
|
// WaitGroup for services to signal when they have shutdown cleanly.
|
|
var wg sync.WaitGroup
|
|
|
|
// Start the webapi server.
|
|
wg.Add(1)
|
|
go func() {
|
|
api.Run(ctx)
|
|
wg.Done()
|
|
}()
|
|
|
|
// Start vspd.
|
|
vspd := vspd.New(network, log, db, dcrd, wallets, blockNotifChan)
|
|
wg.Add(1)
|
|
go func() {
|
|
vspd.Run(ctx)
|
|
wg.Done()
|
|
}()
|
|
|
|
// Periodically write a database backup file.
|
|
wg.Add(1)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
wg.Done()
|
|
return
|
|
case <-time.After(cfg.BackupInterval):
|
|
err := db.WriteHotBackupFile()
|
|
if err != nil {
|
|
log.Errorf("Failed to write database backup: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Wait for shutdown tasks to complete before running deferred tasks and
|
|
// returning.
|
|
wg.Wait()
|
|
|
|
return 0
|
|
}
|