From 4419ae3a6e630fe6d4e79c7348f45a95d7cb9615 Mon Sep 17 00:00:00 2001 From: jholdstock Date: Wed, 23 Aug 2023 09:53:02 +0100 Subject: [PATCH] vspd: Introduce vspd struct. Storing all of the essential resources such as db, rpcs and logger in a vspd struct enables vspd functions to be called without passing a bunch of parameters unnecessarily. This will be increasingly useful later when further changes are introduced. --- cmd/vspd/background.go | 141 ++++++++++++++++----------------- cmd/vspd/main.go | 176 +++++++++++++++++++++++------------------ 2 files changed, 168 insertions(+), 149 deletions(-) diff --git a/cmd/vspd/background.go b/cmd/vspd/background.go index 7ed3c11..511ca18 100644 --- a/cmd/vspd/background.go +++ b/cmd/vspd/background.go @@ -8,7 +8,6 @@ import ( "errors" "strings" - "github.com/decred/slog" "github.com/decred/vspd/database" "github.com/decred/vspd/rpc" "github.com/jrick/wsrpc/v2" @@ -22,22 +21,21 @@ const ( // blockConnected is called once when vspd starts up, and once each time a // blockconnected notification is received from dcrd. -func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *database.VspDatabase, log slog.Logger) { - +func (v *vspd) blockConnected() { const funcName = "blockConnected" - dcrdClient, _, err := dcrdRPC.Client() + dcrdClient, _, err := v.dcrd.Client() if err != nil { - log.Errorf("%s: %v", funcName, err) + v.log.Errorf("%s: %v", funcName, err) return } // Step 1/4: Update the database with any tickets which now have 6+ // confirmations. - unconfirmed, err := db.GetUnconfirmedTickets() + unconfirmed, err := v.db.GetUnconfirmedTickets() if err != nil { - log.Errorf("%s: db.GetUnconfirmedTickets error: %v", funcName, err) + v.log.Errorf("%s: db.GetUnconfirmedTickets error: %v", funcName, err) } for _, ticket := range unconfirmed { @@ -49,24 +47,24 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da // which expired. Remove it from the db. var e *wsrpc.Error if errors.As(err, &e) && e.Code == rpc.ErrNoTxInfo { - log.Infof("%s: Removing unconfirmed ticket from db - no information available "+ + v.log.Infof("%s: Removing unconfirmed ticket from db - no information available "+ "about transaction (ticketHash=%s)", funcName, ticket.Hash) - err = db.DeleteTicket(ticket) + err = v.db.DeleteTicket(ticket) if err != nil { - log.Errorf("%s: db.DeleteTicket error (ticketHash=%s): %v", + v.log.Errorf("%s: db.DeleteTicket error (ticketHash=%s): %v", funcName, ticket.Hash, err) } // This will not error if an alternate signing address does not // exist for ticket. - err = db.DeleteAltSignAddr(ticket.Hash) + err = v.db.DeleteAltSignAddr(ticket.Hash) if err != nil { - log.Errorf("%s: db.DeleteAltSignAddr error (ticketHash=%s): %v", + v.log.Errorf("%s: db.DeleteAltSignAddr error (ticketHash=%s): %v", funcName, ticket.Hash, err) } } else { - log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", + v.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err) } @@ -76,70 +74,70 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da if tktTx.Confirmations >= requiredConfs { ticket.PurchaseHeight = tktTx.BlockHeight ticket.Confirmed = true - err = db.UpdateTicket(ticket) + err = v.db.UpdateTicket(ticket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to set ticket as confirmed (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to set ticket as confirmed (ticketHash=%s): %v", funcName, ticket.Hash, err) continue } - log.Infof("%s: Ticket confirmed (ticketHash=%s)", funcName, ticket.Hash) + v.log.Infof("%s: Ticket confirmed (ticketHash=%s)", funcName, ticket.Hash) } } // Step 2/4: Broadcast fee tx for tickets which are confirmed. - pending, err := db.GetPendingFees() + pending, err := v.db.GetPendingFees() if err != nil { - log.Errorf("%s: db.GetPendingFees error: %v", funcName, err) + v.log.Errorf("%s: db.GetPendingFees error: %v", funcName, err) } for _, ticket := range pending { err = dcrdClient.SendRawTransaction(ticket.FeeTxHex) if err != nil { - log.Errorf("%s: dcrd.SendRawTransaction for fee tx failed (ticketHash=%s): %v", + v.log.Errorf("%s: dcrd.SendRawTransaction for fee tx failed (ticketHash=%s): %v", funcName, ticket.Hash, err) ticket.FeeTxStatus = database.FeeError } else { - log.Infof("%s: Fee tx broadcast for ticket (ticketHash=%s, feeHash=%s)", + v.log.Infof("%s: Fee tx broadcast for ticket (ticketHash=%s, feeHash=%s)", funcName, ticket.Hash, ticket.FeeTxHash) ticket.FeeTxStatus = database.FeeBroadcast } - err = db.UpdateTicket(ticket) + err = v.db.UpdateTicket(ticket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v", funcName, ticket.Hash, err) } } // Step 3/4: Add tickets with confirmed fees to voting wallets. - unconfirmedFees, err := db.GetUnconfirmedFees() + unconfirmedFees, err := v.db.GetUnconfirmedFees() if err != nil { - log.Errorf("%s: db.GetUnconfirmedFees error: %v", funcName, err) + v.log.Errorf("%s: db.GetUnconfirmedFees error: %v", funcName, err) } - walletClients, failedConnections := walletRPC.Clients() + walletClients, failedConnections := v.wallets.Clients() if len(walletClients) == 0 { - log.Errorf("%s: Could not connect to any wallets", funcName) + v.log.Errorf("%s: Could not connect to any wallets", funcName) return } if len(failedConnections) > 0 { - log.Errorf("%s: Failed to connect to %d wallet(s), proceeding with only %d", + v.log.Errorf("%s: Failed to connect to %d wallet(s), proceeding with only %d", funcName, len(failedConnections), len(walletClients)) } for _, ticket := range unconfirmedFees { feeTx, err := dcrdClient.GetRawTransaction(ticket.FeeTxHash) if err != nil { - log.Errorf("%s: dcrd.GetRawTransaction for fee tx failed (feeTxHash=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrd.GetRawTransaction for fee tx failed (feeTxHash=%s, ticketHash=%s): %v", funcName, ticket.FeeTxHash, ticket.Hash, err) ticket.FeeTxStatus = database.FeeError - err = db.UpdateTicket(ticket) + err = v.db.UpdateTicket(ticket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to set fee tx status to error (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx status to error (ticketHash=%s): %v", funcName, ticket.Hash, err) } continue @@ -151,26 +149,26 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da // We no longer need the hex once the tx is confirmed on-chain. ticket.FeeTxHex = "" ticket.FeeTxStatus = database.FeeConfirmed - err = db.UpdateTicket(ticket) + err = v.db.UpdateTicket(ticket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as confirmed (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as confirmed (ticketHash=%s): %v", funcName, ticket.Hash, err) continue } - log.Infof("%s: Fee tx confirmed (ticketHash=%s)", funcName, ticket.Hash) + v.log.Infof("%s: Fee tx confirmed (ticketHash=%s)", funcName, ticket.Hash) // Add ticket to the voting wallet. rawTicket, err := dcrdClient.GetRawTransaction(ticket.Hash) if err != nil { - log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", + v.log.Errorf("%s: dcrd.GetRawTransaction for ticket failed (ticketHash=%s): %v", funcName, ticket.Hash, err) continue } for _, walletClient := range walletClients { err = walletClient.AddTicketForVoting(ticket.VotingWIF, rawTicket.BlockHash, rawTicket.Hex) if err != nil { - log.Errorf("%s: dcrwallet.AddTicketForVoting error (wallet=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrwallet.AddTicketForVoting error (wallet=%s, ticketHash=%s): %v", funcName, walletClient.String(), ticket.Hash, err) continue } @@ -180,16 +178,16 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash) if err != nil { if strings.Contains(err.Error(), "no agenda with ID") { - log.Warnf("%s: Removing invalid agenda from ticket vote choices (ticketHash=%s, agenda=%s)", + v.log.Warnf("%s: Removing invalid agenda from ticket vote choices (ticketHash=%s, agenda=%s)", funcName, ticket.Hash, agenda) delete(ticket.VoteChoices, agenda) - err = db.UpdateTicket(ticket) + err = v.db.UpdateTicket(ticket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to remove invalid agenda (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to remove invalid agenda (ticketHash=%s): %v", funcName, ticket.Hash, err) } } else { - log.Errorf("%s: dcrwallet.SetVoteChoice error (wallet=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrwallet.SetVoteChoice error (wallet=%s, ticketHash=%s): %v", funcName, walletClient.String(), ticket.Hash, err) } } @@ -199,7 +197,7 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da for tspend, policy := range ticket.TSpendPolicy { err = walletClient.SetTSpendPolicy(tspend, policy, ticket.Hash) if err != nil { - log.Errorf("%s: dcrwallet.SetTSpendPolicy failed (wallet=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrwallet.SetTSpendPolicy failed (wallet=%s, ticketHash=%s): %v", funcName, walletClient.String(), ticket.Hash, err) } } @@ -208,12 +206,12 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da for key, policy := range ticket.TreasuryPolicy { err = walletClient.SetTreasuryPolicy(key, policy, ticket.Hash) if err != nil { - log.Errorf("%s: dcrwallet.SetTreasuryPolicy failed (wallet=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrwallet.SetTreasuryPolicy failed (wallet=%s, ticketHash=%s): %v", funcName, walletClient.String(), ticket.Hash, err) } } - log.Infof("%s: Ticket added to voting wallet (wallet=%s, ticketHash=%s)", + v.log.Infof("%s: Ticket added to voting wallet (wallet=%s, ticketHash=%s)", funcName, walletClient.String(), ticket.Hash) } } @@ -227,9 +225,9 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da // successful wallet will have the most up-to-date ticket status, the others // will be outdated. for _, walletClient := range walletClients { - votableTickets, err := db.GetVotableTickets() + votableTickets, err := v.db.GetVotableTickets() if err != nil { - log.Errorf("%s: db.GetVotableTickets failed: %v", funcName, err) + v.log.Errorf("%s: db.GetVotableTickets failed: %v", funcName, err) continue } @@ -243,7 +241,7 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da ticketInfo, err := walletClient.TicketInfo(oldestHeight) if err != nil { - log.Errorf("%s: dcrwallet.TicketInfo failed (startHeight=%d, wallet=%s): %v", + v.log.Errorf("%s: dcrwallet.TicketInfo failed (startHeight=%d, wallet=%s): %v", funcName, oldestHeight, walletClient.String(), err) continue } @@ -251,7 +249,7 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da for _, dbTicket := range votableTickets { tInfo, ok := ticketInfo[dbTicket.Hash] if !ok { - log.Warnf("%s: TicketInfo response did not include expected ticket (wallet=%s, ticketHash=%s)", + v.log.Warnf("%s: TicketInfo response did not include expected ticket (wallet=%s, ticketHash=%s)", funcName, walletClient.String(), dbTicket.Hash) continue } @@ -266,14 +264,14 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da continue } - err = db.UpdateTicket(dbTicket) + err = v.db.UpdateTicket(dbTicket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to set ticket outcome (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to set ticket outcome (ticketHash=%s): %v", funcName, dbTicket.Hash, err) continue } - log.Infof("%s: Ticket no longer votable: outcome=%s, ticketHash=%s", funcName, + v.log.Infof("%s: Ticket no longer votable: outcome=%s, ticketHash=%s", funcName, dbTicket.Outcome, dbTicket.Hash) } } @@ -283,33 +281,32 @@ func blockConnected(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *da // checkWalletConsistency will retrieve all votable tickets from the database // and ensure they are all added to voting wallets with the correct vote // choices. -func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect, db *database.VspDatabase, log slog.Logger) { - +func (v *vspd) checkWalletConsistency() { const funcName = "checkWalletConsistency" - log.Info("Checking voting wallet consistency") + v.log.Info("Checking voting wallet consistency") - dcrdClient, _, err := dcrdRPC.Client() + dcrdClient, _, err := v.dcrd.Client() if err != nil { - log.Errorf("%s: %v", funcName, err) + v.log.Errorf("%s: %v", funcName, err) return } - walletClients, failedConnections := walletRPC.Clients() + walletClients, failedConnections := v.wallets.Clients() if len(walletClients) == 0 { - log.Errorf("%s: Could not connect to any wallets", funcName) + v.log.Errorf("%s: Could not connect to any wallets", funcName) return } if len(failedConnections) > 0 { - log.Errorf("%s: Failed to connect to %d wallet(s), proceeding with only %d", + v.log.Errorf("%s: Failed to connect to %d wallet(s), proceeding with only %d", funcName, len(failedConnections), len(walletClients)) } // Step 1/2: Check all tickets are added to all voting wallets. - votableTickets, err := db.GetVotableTickets() + votableTickets, err := v.db.GetVotableTickets() if err != nil { - log.Errorf("%s: db.GetVotableTickets failed: %v", funcName, err) + v.log.Errorf("%s: db.GetVotableTickets failed: %v", funcName, err) return } @@ -326,7 +323,7 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect // Get all tickets the wallet is aware of. walletTickets, err := walletClient.TicketInfo(oldestHeight) if err != nil { - log.Errorf("%s: dcrwallet.TicketInfo failed (startHeight=%d, wallet=%s): %v", + v.log.Errorf("%s: dcrwallet.TicketInfo failed (startHeight=%d, wallet=%s): %v", funcName, oldestHeight, walletClient.String(), err) continue } @@ -342,18 +339,18 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect continue } - log.Debugf("%s: Adding missing ticket (wallet=%s, ticketHash=%s)", + v.log.Debugf("%s: Adding missing ticket (wallet=%s, ticketHash=%s)", funcName, walletClient.String(), dbTicket.Hash) rawTicket, err := dcrdClient.GetRawTransaction(dbTicket.Hash) if err != nil { - log.Errorf("%s: dcrd.GetRawTransaction error: %v", funcName, err) + v.log.Errorf("%s: dcrd.GetRawTransaction error: %v", funcName, err) continue } err = walletClient.AddTicketForVoting(dbTicket.VotingWIF, rawTicket.BlockHash, rawTicket.Hex) if err != nil { - log.Errorf("%s: dcrwallet.AddTicketForVoting error (wallet=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrwallet.AddTicketForVoting error (wallet=%s, ticketHash=%s): %v", funcName, walletClient.String(), dbTicket.Hash, err) continue } @@ -366,11 +363,11 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect // Perform a rescan if any missing tickets were added to this wallet. if added { - log.Infof("%s: Performing a rescan on wallet %s (fromHeight=%d)", + v.log.Infof("%s: Performing a rescan on wallet %s (fromHeight=%d)", funcName, walletClient.String(), minHeight) err = walletClient.RescanFrom(minHeight) if err != nil { - log.Errorf("%s: dcrwallet.RescanFrom failed (wallet=%s): %v", + v.log.Errorf("%s: dcrwallet.RescanFrom failed (wallet=%s): %v", funcName, walletClient.String(), err) continue } @@ -384,7 +381,7 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect // Get all tickets the wallet is aware of. walletTickets, err := walletClient.TicketInfo(oldestHeight) if err != nil { - log.Errorf("%s: dcrwallet.TicketInfo failed (startHeight=%d, wallet=%s): %v", + v.log.Errorf("%s: dcrwallet.TicketInfo failed (startHeight=%d, wallet=%s): %v", funcName, oldestHeight, walletClient.String(), err) continue } @@ -394,7 +391,7 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect // a warning if any are still missing. walletTicket, exists := walletTickets[dbTicket.Hash] if !exists { - log.Warnf("%s: Ticket missing from voting wallet (wallet=%s, ticketHash=%s)", + v.log.Warnf("%s: Ticket missing from voting wallet (wallet=%s, ticketHash=%s)", funcName, walletClient.String(), dbTicket.Hash) continue } @@ -413,7 +410,7 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect continue } - log.Debugf("%s: Updating incorrect consensus vote choices (wallet=%s, agenda=%s, ticketHash=%s)", + v.log.Debugf("%s: Updating incorrect consensus vote choices (wallet=%s, agenda=%s, ticketHash=%s)", funcName, walletClient.String(), dbAgenda, dbTicket.Hash) // If db and wallet are not matching, update wallet with correct @@ -421,16 +418,16 @@ func checkWalletConsistency(dcrdRPC rpc.DcrdConnect, walletRPC rpc.WalletConnect err = walletClient.SetVoteChoice(dbAgenda, dbChoice, dbTicket.Hash) if err != nil { if strings.Contains(err.Error(), "no agenda with ID") { - log.Warnf("%s: Removing invalid agenda from ticket vote choices (ticketHash=%s, agenda=%s)", + v.log.Warnf("%s: Removing invalid agenda from ticket vote choices (ticketHash=%s, agenda=%s)", funcName, dbTicket.Hash, dbAgenda) delete(dbTicket.VoteChoices, dbAgenda) - err = db.UpdateTicket(dbTicket) + err = v.db.UpdateTicket(dbTicket) if err != nil { - log.Errorf("%s: db.UpdateTicket error, failed to remove invalid agenda (ticketHash=%s): %v", + v.log.Errorf("%s: db.UpdateTicket error, failed to remove invalid agenda (ticketHash=%s): %v", funcName, dbTicket.Hash, err) } } else { - log.Errorf("%s: dcrwallet.SetVoteChoice error (wallet=%s, ticketHash=%s): %v", + v.log.Errorf("%s: dcrwallet.SetVoteChoice error (wallet=%s, ticketHash=%s): %v", funcName, walletClient.String(), dbTicket.Hash, err) } } diff --git a/cmd/vspd/main.go b/cmd/vspd/main.go index e059a65..5766a2f 100644 --- a/cmd/vspd/main.go +++ b/cmd/vspd/main.go @@ -12,6 +12,7 @@ import ( "time" "github.com/decred/dcrd/wire" + "github.com/decred/slog" "github.com/decred/vspd/database" "github.com/decred/vspd/rpc" "github.com/decred/vspd/version" @@ -28,89 +29,110 @@ const maxVoteChangeRecords = 10 const consistencyInterval = 30 * time.Minute func main() { - // Run until an exit code is returned. - os.Exit(run()) -} - -// 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 a shutdown is requested. -func run() int { - // Load config file and parse CLI args. cfg, err := loadConfig() if err != nil { - fmt.Fprintf(os.Stderr, "Config error: %v\n", err) - return 1 + fmt.Fprintf(os.Stderr, "loadConfig error: %v\n", err) + os.Exit(1) + } + + vspd, err := newVspd(cfg) + if err != nil { + fmt.Fprintf(os.Stderr, "newVspd error: %v\n", err) + os.Exit(1) + } + + // Run until an exit code is returned. + os.Exit(vspd.run()) +} + +type vspd struct { + cfg *config + log slog.Logger + db *database.VspDatabase + dcrd rpc.DcrdConnect + wallets rpc.WalletConnect +} + +// newVspd creates the essential resources required by vspd - a database, logger +// and RPC clients - then returns an instance of vspd which is ready to be run. +func newVspd(cfg *config) (*vspd, error) { + // Open database. + db, err := database.Open(cfg.dbPath, cfg.logger(" DB"), maxVoteChangeRecords) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) } log := cfg.logger("VSP") - dbLog := cfg.logger(" DB") - apiLog := cfg.logger("API") rpcLog := cfg.logger("RPC") - // 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("") - } - - defer log.Criticalf("Shutdown complete") - - // Open database. - db, err := database.Open(cfg.dbPath, dbLog, maxVoteChangeRecords) - if err != nil { - log.Errorf("Database error: %v", err) - return 1 - } - - const writeBackup = true - defer db.Close(writeBackup) - - // Create a context that is cancelled when a shutdown request is received - // through an interrupt signal. - shutdownCtx := shutdownListener(log) - - // WaitGroup for services to signal when they have shutdown cleanly. - var shutdownWg sync.WaitGroup - - db.WritePeriodicBackups(shutdownCtx, &shutdownWg, cfg.BackupInterval) - // 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, rpcLog) - defer dcrd.Close() - // Create RPC client for remote dcrwallet instance (used for voting). + // Create RPC client for remote dcrwallet instances (used for voting). wallets := rpc.SetupWallet(cfg.walletUsers, cfg.walletPasswords, cfg.walletHosts, cfg.walletCerts, cfg.netParams.Params, rpcLog) - defer wallets.Close() + + v := &vspd{ + cfg: cfg, + log: log, + db: db, + dcrd: dcrd, + wallets: wallets, + } + + return v, nil +} + +// run starts all of vspds background services including the web server, and +// stops all started services when a shutdown is requested. +func (v *vspd) run() int { + v.log.Criticalf("Version %s (Go version %s %s/%s)", version.String(), runtime.Version(), + runtime.GOOS, runtime.GOARCH) + + if v.cfg.netParams == &mainNetParams && + version.PreRelease != "" { + v.log.Warnf("") + v.log.Warnf("\tWARNING: This is a pre-release version of vspd which should not be used on mainnet.") + v.log.Warnf("") + } + + if v.cfg.VspClosed { + v.log.Warnf("") + v.log.Warnf("\tWARNING: Config --vspclosed is set. This will prevent vspd from accepting new tickets.") + v.log.Warnf("") + } + + // Defer shutdown tasks. + defer v.log.Criticalf("Shutdown complete") + const writeBackup = true + defer v.db.Close(writeBackup) + defer v.dcrd.Close() + defer v.wallets.Close() + + // Create a context that is cancelled when a shutdown request is received + // through an interrupt signal. + shutdownCtx := shutdownListener(v.log) + + // WaitGroup for services to signal when they have shutdown cleanly. + var shutdownWg sync.WaitGroup + + v.db.WritePeriodicBackups(shutdownCtx, &shutdownWg, v.cfg.BackupInterval) // Ensure all data in database is present and up-to-date. - err = db.CheckIntegrity(dcrd) + err := v.db.CheckIntegrity(v.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) + v.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. - blockConnected(dcrd, wallets, db, log) + v.blockConnected() // Run voting wallet consistency check now to ensure all wallets are up to // date. - checkWalletConsistency(dcrd, wallets, db, log) + v.checkWalletConsistency() // Run voting wallet consistency check periodically. shutdownWg.Add(1) @@ -121,29 +143,29 @@ func run() int { shutdownWg.Done() return case <-time.After(consistencyInterval): - checkWalletConsistency(dcrd, wallets, db, log) + v.checkWalletConsistency() } } }() // 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, + VSPFee: v.cfg.VSPFee, + NetParams: v.cfg.netParams.Params, + BlockExplorerURL: v.cfg.netParams.blockExplorerURL, + SupportEmail: v.cfg.SupportEmail, + VspClosed: v.cfg.VspClosed, + VspClosedMsg: v.cfg.VspClosedMsg, + AdminPass: v.cfg.AdminPass, + Debug: v.cfg.WebServerDebug, + Designation: v.cfg.Designation, MaxVoteChangeRecords: maxVoteChangeRecords, VspdVersion: version.String(), } - err = webapi.Start(shutdownCtx, requestShutdown, &shutdownWg, cfg.Listen, db, apiLog, - dcrd, wallets, apiCfg) + err = webapi.Start(shutdownCtx, requestShutdown, &shutdownWg, v.cfg.Listen, v.db, v.cfg.logger("API"), + v.dcrd, v.wallets, apiCfg) if err != nil { - log.Errorf("Failed to initialize webapi: %v", err) + v.log.Errorf("Failed to initialize webapi: %v", err) requestShutdown() shutdownWg.Wait() return 1 @@ -159,14 +181,14 @@ func run() int { shutdownWg.Done() return case header := <-notifChan: - log.Debugf("Block notification %d (%s)", header.Height, header.BlockHash().String()) - blockConnected(dcrd, wallets, db, log) + v.log.Debugf("Block notification %d (%s)", header.Height, header.BlockHash().String()) + v.blockConnected() } } }() // Attach notification listener to dcrd client. - dcrd.BlockConnectedHandler(notifChan) + v.dcrd.BlockConnectedHandler(notifChan) // Loop forever attempting ensuring a dcrd connection is available, so // notifications are received. @@ -179,9 +201,9 @@ func run() int { return case <-time.After(time.Second * 15): // Ensure dcrd client is still connected. - _, _, err := dcrd.Client() + _, _, err := v.dcrd.Client() if err != nil { - log.Errorf("dcrd connect error: %v", err) + v.log.Errorf("dcrd connect error: %v", err) } } }