vspd/vspd.go
jholdstock 7f72caafe7 Remove global vars from background.go
While removing the globals from background.go it also made sense to clean up the use of dcrd RPC clients. Previously two seperate clients were maintained, one for making RPC calls and one for receiving notifications. These clients have been unified.
2022-06-13 14:50:27 +01:00

192 lines
5.5 KiB
Go

// Copyright (c) 2020-2022 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 (
"context"
"fmt"
"os"
"runtime"
"sync"
"time"
"github.com/decred/dcrd/wire"
"github.com/decred/vspd/background"
"github.com/decred/vspd/database"
"github.com/decred/vspd/rpc"
"github.com/decred/vspd/version"
"github.com/decred/vspd/webapi"
)
// 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.
const maxVoteChangeRecords = 10
// consistencyInterval is the time period between wallet consistency checks.
const consistencyInterval = 30 * time.Minute
func main() {
// Create a context that is cancelled when a shutdown request is received
// through an interrupt signal.
shutdownCtx := withShutdownCancel(context.Background())
go shutdownListener()
// Run until an exit code is returned.
os.Exit(run(shutdownCtx))
}
// run is the main startup and teardown logic performed by the main package. It
// is responsible for parsing the config, creating dcrd and dcrwallet RPC clients,
// opening the database, starting the webserver, and stopping all started
// services when the provided context is cancelled.
func run(shutdownCtx context.Context) int {
// Load config file and parse CLI args.
cfg, err := loadConfig()
if err != nil {
// Don't use logger here because it may not be initialized.
fmt.Fprintf(os.Stderr, "Config error: %v\n", err)
return 1
}
// Show version at startup.
log.Criticalf("Version %s (Go version %s %s/%s)", version.String(), runtime.Version(),
runtime.GOOS, runtime.GOARCH)
if cfg.netParams == &mainNetParams &&
version.PreRelease != "" {
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("")
}
// WaitGroup for services to signal when they have shutdown cleanly.
var shutdownWg sync.WaitGroup
defer log.Criticalf("Shutdown complete")
// Open database.
db, err := database.Open(shutdownCtx, &shutdownWg, cfg.dbPath, cfg.BackupInterval, maxVoteChangeRecords)
if err != nil {
log.Errorf("Database error: %v", err)
requestShutdown()
shutdownWg.Wait()
return 1
}
defer db.Close()
// 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.netParams.Params)
defer dcrd.Close()
// Create RPC client for remote dcrwallet instance (used for voting).
wallets := rpc.SetupWallet(cfg.walletUsers, cfg.walletPasswords, cfg.walletHosts, cfg.walletCerts, cfg.netParams.Params)
defer wallets.Close()
// Create a channel to receive blockConnected notifications from dcrd.
notifChan := make(chan *wire.BlockHeader)
shutdownWg.Add(1)
go func() {
for {
select {
case <-shutdownCtx.Done():
shutdownWg.Done()
return
case header := <-notifChan:
log.Debugf("Block notification %d (%s)", header.Height, header.BlockHash().String())
background.BlockConnected(dcrd, wallets, db)
}
}
}()
// Attach notification listener to dcrd client.
dcrd.BlockConnectedHandler(notifChan)
// Loop forever attempting ensuring a dcrd connection is available, so
// notifications are received.
shutdownWg.Add(1)
go func() {
for {
select {
case <-shutdownCtx.Done():
shutdownWg.Done()
return
case <-time.After(time.Second * 15):
// Ensure dcrd client is still connected.
_, _, err := dcrd.Client()
if err != nil {
log.Errorf("dcrd connect error: %v", err)
}
}
}
}()
// Ensure all data in database is present and up-to-date.
err = db.CheckIntegrity(dcrd)
if err != nil {
// vspd should still start if this fails, so just log an error.
log.Errorf("Could not check database integrity: %v", err)
}
// Run the block connected handler now to catch up with any blocks mined
// while vspd was shut down.
background.BlockConnected(dcrd, wallets, db)
// Run voting wallet consistency check now to ensure all wallets are up to
// date.
background.CheckWalletConsistency(dcrd, wallets, db)
// Run voting wallet consistency check periodically.
shutdownWg.Add(1)
go func() {
for {
select {
case <-shutdownCtx.Done():
shutdownWg.Done()
return
case <-time.After(consistencyInterval):
background.CheckWalletConsistency(dcrd, wallets, db)
}
}
}()
// Create and start webapi server.
apiCfg := webapi.Config{
VSPFee: cfg.VSPFee,
NetParams: cfg.netParams.Params,
BlockExplorerURL: cfg.netParams.BlockExplorerURL,
SupportEmail: cfg.SupportEmail,
VspClosed: cfg.VspClosed,
VspClosedMsg: cfg.VspClosedMsg,
AdminPass: cfg.AdminPass,
Debug: cfg.WebServerDebug,
Designation: cfg.Designation,
MaxVoteChangeRecords: maxVoteChangeRecords,
VspdVersion: version.String(),
}
err = webapi.Start(shutdownCtx, requestShutdown, &shutdownWg, cfg.Listen, db,
dcrd, wallets, apiCfg)
if err != nil {
log.Errorf("Failed to initialize webapi: %v", err)
requestShutdown()
shutdownWg.Wait()
return 1
}
// Wait for shutdown tasks to complete before running deferred tasks and
// returning.
shutdownWg.Wait()
return 0
}