Remove global cache variable. (#341)

* Remove global cache variable.

Rather than maintaining cached data in a global variable, instantiate a cache struct and keep it in the `Server` struct.

* Store net params in RPC clients.

This means net params only need to be supplied once at startup, and also removes a global instance of net params in `background.go`.
This commit is contained in:
Jamie Holdstock 2022-03-30 17:00:42 +01:00 committed by GitHub
parent 78abc59e97
commit 78bb28056c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 126 additions and 114 deletions

View File

@ -12,7 +12,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/vspd/database" "github.com/decred/vspd/database"
"github.com/decred/vspd/rpc" "github.com/decred/vspd/rpc"
"github.com/jrick/wsrpc/v2" "github.com/jrick/wsrpc/v2"
@ -22,7 +21,6 @@ var (
db *database.VspDatabase db *database.VspDatabase
dcrdRPC rpc.DcrdConnect dcrdRPC rpc.DcrdConnect
walletRPC rpc.WalletConnect walletRPC rpc.WalletConnect
netParams *chaincfg.Params
notifierClosed chan struct{} notifierClosed chan struct{}
) )
@ -76,7 +74,7 @@ func blockConnected() {
ctx := context.Background() ctx := context.Background()
dcrdClient, _, err := dcrdRPC.Client(ctx, netParams) dcrdClient, _, err := dcrdRPC.Client(ctx)
if err != nil { if err != nil {
log.Errorf("%s: %v", funcName, err) log.Errorf("%s: %v", funcName, err)
return return
@ -170,7 +168,7 @@ func blockConnected() {
log.Errorf("%s: db.GetUnconfirmedFees error: %v", funcName, err) log.Errorf("%s: db.GetUnconfirmedFees error: %v", funcName, err)
} }
walletClients, failedConnections := walletRPC.Clients(ctx, netParams) walletClients, failedConnections := walletRPC.Clients(ctx)
if len(walletClients) == 0 { if len(walletClients) == 0 {
log.Errorf("%s: Could not connect to any wallets", funcName) log.Errorf("%s: Could not connect to any wallets", funcName)
return return
@ -333,7 +331,7 @@ func blockConnected() {
func connectNotifier(shutdownCtx context.Context, dcrdWithNotifs rpc.DcrdConnect) error { func connectNotifier(shutdownCtx context.Context, dcrdWithNotifs rpc.DcrdConnect) error {
notifierClosed = make(chan struct{}) notifierClosed = make(chan struct{})
dcrdClient, _, err := dcrdWithNotifs.Client(shutdownCtx, netParams) dcrdClient, _, err := dcrdWithNotifs.Client(shutdownCtx)
if err != nil { if err != nil {
return err return err
} }
@ -361,12 +359,11 @@ func connectNotifier(shutdownCtx context.Context, dcrdWithNotifs rpc.DcrdConnect
} }
func Start(shutdownCtx context.Context, wg *sync.WaitGroup, vdb *database.VspDatabase, drpc rpc.DcrdConnect, func Start(shutdownCtx context.Context, wg *sync.WaitGroup, vdb *database.VspDatabase, drpc rpc.DcrdConnect,
dcrdWithNotif rpc.DcrdConnect, wrpc rpc.WalletConnect, p *chaincfg.Params) { dcrdWithNotif rpc.DcrdConnect, wrpc rpc.WalletConnect) {
db = vdb db = vdb
dcrdRPC = drpc dcrdRPC = drpc
walletRPC = wrpc walletRPC = wrpc
netParams = p
// Run the block connected handler now to catch up with any blocks mined // Run the block connected handler now to catch up with any blocks mined
// while vspd was shut down. // while vspd was shut down.
@ -428,13 +425,13 @@ func checkWalletConsistency() {
ctx := context.Background() ctx := context.Background()
dcrdClient, _, err := dcrdRPC.Client(ctx, netParams) dcrdClient, _, err := dcrdRPC.Client(ctx)
if err != nil { if err != nil {
log.Errorf("%s: %v", funcName, err) log.Errorf("%s: %v", funcName, err)
return return
} }
walletClients, failedConnections := walletRPC.Clients(ctx, netParams) walletClients, failedConnections := walletRPC.Clients(ctx)
if len(walletClients) == 0 { if len(walletClients) == 0 {
log.Errorf("%s: Could not connect to any wallets", funcName) log.Errorf("%s: Could not connect to any wallets", funcName)
return return

View File

@ -1,4 +1,4 @@
// Copyright (c) 2020-2021 The Decred developers // Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -16,7 +16,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/vspd/rpc" "github.com/decred/vspd/rpc"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
@ -386,7 +385,7 @@ func (vdb *VspDatabase) BackupDB(w http.ResponseWriter) error {
// CheckIntegrity will ensure that all data in the database is present and up to // CheckIntegrity will ensure that all data in the database is present and up to
// date. // date.
func (vdb *VspDatabase) CheckIntegrity(ctx context.Context, params *chaincfg.Params, dcrd rpc.DcrdConnect) error { func (vdb *VspDatabase) CheckIntegrity(ctx context.Context, dcrd rpc.DcrdConnect) error {
// Ensure all confirmed tickets have a purchase height. // Ensure all confirmed tickets have a purchase height.
// This is necessary because of an old bug which, in some circumstances, // This is necessary because of an old bug which, in some circumstances,
@ -401,7 +400,7 @@ func (vdb *VspDatabase) CheckIntegrity(ctx context.Context, params *chaincfg.Par
return nil return nil
} }
dcrdClient, _, err := dcrd.Client(ctx, params) dcrdClient, _, err := dcrd.Client(ctx)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) 2020 The Decred developers // Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -41,45 +41,53 @@ type DcrdRPC struct {
} }
type DcrdConnect struct { type DcrdConnect struct {
*client client *client
params *chaincfg.Params
} }
func SetupDcrd(user, pass, addr string, cert []byte, n wsrpc.Notifier) DcrdConnect { func SetupDcrd(user, pass, addr string, cert []byte, n wsrpc.Notifier, params *chaincfg.Params) DcrdConnect {
return DcrdConnect{setup(user, pass, addr, cert, n)} return DcrdConnect{
client: setup(user, pass, addr, cert, n),
params: params,
}
}
func (d *DcrdConnect) Close() {
d.client.Close()
} }
// Client creates a new DcrdRPC client instance. Returns an error if dialing // Client creates a new DcrdRPC client instance. Returns an error if dialing
// dcrd fails or if dcrd is misconfigured. // dcrd fails or if dcrd is misconfigured.
func (d *DcrdConnect) Client(ctx context.Context, netParams *chaincfg.Params) (*DcrdRPC, string, error) { func (d *DcrdConnect) Client(ctx context.Context) (*DcrdRPC, string, error) {
c, newConnection, err := d.dial(ctx) c, newConnection, err := d.client.dial(ctx)
if err != nil { if err != nil {
return nil, d.addr, fmt.Errorf("dcrd connection error: %w", err) return nil, d.client.addr, fmt.Errorf("dcrd connection error: %w", err)
} }
// If this is a reused connection, we don't need to validate the dcrd config // If this is a reused connection, we don't need to validate the dcrd config
// again. // again.
if !newConnection { if !newConnection {
return &DcrdRPC{c, ctx}, d.addr, nil return &DcrdRPC{c, ctx}, d.client.addr, nil
} }
// Verify dcrd is at the required api version. // Verify dcrd is at the required api version.
var verMap map[string]dcrdtypes.VersionResult var verMap map[string]dcrdtypes.VersionResult
err = c.Call(ctx, "version", &verMap) err = c.Call(ctx, "version", &verMap)
if err != nil { if err != nil {
d.Close() d.client.Close()
return nil, d.addr, fmt.Errorf("dcrd version check failed: %w", err) return nil, d.client.addr, fmt.Errorf("dcrd version check failed: %w", err)
} }
ver, exists := verMap["dcrdjsonrpcapi"] ver, exists := verMap["dcrdjsonrpcapi"]
if !exists { if !exists {
d.Close() d.client.Close()
return nil, d.addr, fmt.Errorf("dcrd version response missing 'dcrdjsonrpcapi'") return nil, d.client.addr, fmt.Errorf("dcrd version response missing 'dcrdjsonrpcapi'")
} }
sVer := semver{ver.Major, ver.Minor, ver.Patch} sVer := semver{ver.Major, ver.Minor, ver.Patch}
if !semverCompatible(requiredDcrdVersion, sVer) { if !semverCompatible(requiredDcrdVersion, sVer) {
d.Close() d.client.Close()
return nil, d.addr, fmt.Errorf("dcrd has incompatible JSON-RPC version: got %s, expected %s", return nil, d.client.addr, fmt.Errorf("dcrd has incompatible JSON-RPC version: got %s, expected %s",
sVer, requiredDcrdVersion) sVer, requiredDcrdVersion)
} }
@ -87,27 +95,27 @@ func (d *DcrdConnect) Client(ctx context.Context, netParams *chaincfg.Params) (*
var netID wire.CurrencyNet var netID wire.CurrencyNet
err = c.Call(ctx, "getcurrentnet", &netID) err = c.Call(ctx, "getcurrentnet", &netID)
if err != nil { if err != nil {
d.Close() d.client.Close()
return nil, d.addr, fmt.Errorf("dcrd getcurrentnet check failed: %w", err) return nil, d.client.addr, fmt.Errorf("dcrd getcurrentnet check failed: %w", err)
} }
if netID != netParams.Net { if netID != d.params.Net {
d.Close() d.client.Close()
return nil, d.addr, fmt.Errorf("dcrd running on %s, expected %s", netID, netParams.Net) return nil, d.client.addr, fmt.Errorf("dcrd running on %s, expected %s", netID, d.params.Net)
} }
// Verify dcrd has tx index enabled (required for getrawtransaction). // Verify dcrd has tx index enabled (required for getrawtransaction).
var info dcrdtypes.InfoChainResult var info dcrdtypes.InfoChainResult
err = c.Call(ctx, "getinfo", &info) err = c.Call(ctx, "getinfo", &info)
if err != nil { if err != nil {
d.Close() d.client.Close()
return nil, d.addr, fmt.Errorf("dcrd getinfo check failed: %w", err) return nil, d.client.addr, fmt.Errorf("dcrd getinfo check failed: %w", err)
} }
if !info.TxIndex { if !info.TxIndex {
d.Close() d.client.Close()
return nil, d.addr, errors.New("dcrd does not have transaction index enabled (--txindex)") return nil, d.client.addr, errors.New("dcrd does not have transaction index enabled (--txindex)")
} }
return &DcrdRPC{c, ctx}, d.addr, nil return &DcrdRPC{c, ctx}, d.client.addr, nil
} }
// GetRawTransaction uses getrawtransaction RPC to retrieve details about the // GetRawTransaction uses getrawtransaction RPC to retrieve details about the

View File

@ -25,32 +25,38 @@ type WalletRPC struct {
ctx context.Context ctx context.Context
} }
type WalletConnect []*client type WalletConnect struct {
clients []*client
params *chaincfg.Params
}
func SetupWallet(user, pass, addrs []string, cert [][]byte) WalletConnect { func SetupWallet(user, pass, addrs []string, cert [][]byte, params *chaincfg.Params) WalletConnect {
walletConnect := make(WalletConnect, len(addrs)) clients := make([]*client, len(addrs))
for i := 0; i < len(addrs); i++ { for i := 0; i < len(addrs); i++ {
walletConnect[i] = setup(user[i], pass[i], addrs[i], cert[i], nil) clients[i] = setup(user[i], pass[i], addrs[i], cert[i], nil)
} }
return walletConnect return WalletConnect{
clients: clients,
params: params,
}
} }
func (w *WalletConnect) Close() { func (w *WalletConnect) Close() {
for _, connect := range []*client(*w) { for _, client := range w.clients {
connect.Close() client.Close()
} }
} }
// Clients loops over each wallet and tries to establish a connection. It // Clients loops over each wallet and tries to establish a connection. It
// increments a count of failed connections if a connection cannot be // increments a count of failed connections if a connection cannot be
// established, or if the wallet is misconfigured. // established, or if the wallet is misconfigured.
func (w *WalletConnect) Clients(ctx context.Context, netParams *chaincfg.Params) ([]*WalletRPC, []string) { func (w *WalletConnect) Clients(ctx context.Context) ([]*WalletRPC, []string) {
walletClients := make([]*WalletRPC, 0) walletClients := make([]*WalletRPC, 0)
failedConnections := make([]string, 0) failedConnections := make([]string, 0)
for _, connect := range []*client(*w) { for _, connect := range w.clients {
c, newConnection, err := connect.dial(ctx) c, newConnection, err := connect.dial(ctx)
if err != nil { if err != nil {
@ -103,9 +109,9 @@ func (w *WalletConnect) Clients(ctx context.Context, netParams *chaincfg.Params)
connect.Close() connect.Close()
continue continue
} }
if netID != netParams.Net { if netID != w.params.Net {
log.Errorf("dcrwallet on wrong network (wallet=%s): running on %s, expected %s", log.Errorf("dcrwallet on wrong network (wallet=%s): running on %s, expected %s",
c.String(), netID, netParams.Net) c.String(), netID, w.params.Net)
failedConnections = append(failedConnections, connect.addr) failedConnections = append(failedConnections, connect.addr)
connect.Close() connect.Close()
continue continue

12
vspd.go
View File

@ -1,4 +1,4 @@
// Copyright (c) 2020 The Decred developers // Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@ -84,15 +84,15 @@ func run(ctx context.Context) error {
// Create RPC client for local dcrd instance (used for broadcasting and // Create RPC client for local dcrd instance (used for broadcasting and
// checking the status of fee transactions). // checking the status of fee transactions).
dcrd := rpc.SetupDcrd(cfg.DcrdUser, cfg.DcrdPass, cfg.DcrdHost, cfg.dcrdCert, nil) dcrd := rpc.SetupDcrd(cfg.DcrdUser, cfg.DcrdPass, cfg.DcrdHost, cfg.dcrdCert, nil, cfg.netParams.Params)
defer dcrd.Close() defer dcrd.Close()
// Create RPC client for remote dcrwallet instance (used for voting). // Create RPC client for remote dcrwallet instance (used for voting).
wallets := rpc.SetupWallet(cfg.walletUsers, cfg.walletPasswords, cfg.walletHosts, cfg.walletCerts) wallets := rpc.SetupWallet(cfg.walletUsers, cfg.walletPasswords, cfg.walletHosts, cfg.walletCerts, cfg.netParams.Params)
defer wallets.Close() defer wallets.Close()
// Ensure all data in database is present and up-to-date. // Ensure all data in database is present and up-to-date.
err = db.CheckIntegrity(ctx, cfg.netParams.Params, dcrd) err = db.CheckIntegrity(ctx, dcrd)
if err != nil { if err != nil {
// vspd should still start if this fails, so just log an error. // vspd should still start if this fails, so just log an error.
log.Errorf("Could not check database integrity: %v", err) log.Errorf("Could not check database integrity: %v", err)
@ -124,12 +124,12 @@ func run(ctx context.Context) error {
// Create a dcrd client with a blockconnected notification handler. // Create a dcrd client with a blockconnected notification handler.
notifHandler := background.NotificationHandler{ShutdownWg: &shutdownWg} notifHandler := background.NotificationHandler{ShutdownWg: &shutdownWg}
dcrdWithNotifs := rpc.SetupDcrd(cfg.DcrdUser, cfg.DcrdPass, dcrdWithNotifs := rpc.SetupDcrd(cfg.DcrdUser, cfg.DcrdPass,
cfg.DcrdHost, cfg.dcrdCert, &notifHandler) cfg.DcrdHost, cfg.dcrdCert, &notifHandler, cfg.netParams.Params)
defer dcrdWithNotifs.Close() defer dcrdWithNotifs.Close()
// Start background process which will continually attempt to reconnect to // Start background process which will continually attempt to reconnect to
// dcrd if the connection drops. // dcrd if the connection drops.
background.Start(ctx, &shutdownWg, db, dcrd, dcrdWithNotifs, wallets, cfg.netParams.Params) background.Start(ctx, &shutdownWg, db, dcrd, dcrdWithNotifs, wallets)
// Wait for shutdown tasks to complete before running deferred tasks and // Wait for shutdown tasks to complete before running deferred tasks and
// returning. // returning.

View File

@ -143,7 +143,7 @@ func (s *Server) statusJSON(c *gin.Context) {
// adminPage is the handler for "GET /admin". // adminPage is the handler for "GET /admin".
func (s *Server) adminPage(c *gin.Context) { func (s *Server) adminPage(c *gin.Context) {
c.HTML(http.StatusOK, "admin.html", gin.H{ c.HTML(http.StatusOK, "admin.html", gin.H{
"WebApiCache": getCache(), "WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg, "WebApiCfg": s.cfg,
"WalletStatus": walletStatus(c), "WalletStatus": walletStatus(c),
"DcrdStatus": dcrdStatus(c), "DcrdStatus": dcrdStatus(c),
@ -185,7 +185,7 @@ func (s *Server) ticketSearch(c *gin.Context) {
VoteChanges: voteChanges, VoteChanges: voteChanges,
MaxVoteChanges: s.cfg.MaxVoteChangeRecords, MaxVoteChanges: s.cfg.MaxVoteChangeRecords,
}, },
"WebApiCache": getCache(), "WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg, "WebApiCfg": s.cfg,
"WalletStatus": walletStatus(c), "WalletStatus": walletStatus(c),
"DcrdStatus": dcrdStatus(c), "DcrdStatus": dcrdStatus(c),
@ -200,7 +200,7 @@ func (s *Server) adminLogin(c *gin.Context) {
if password != s.cfg.AdminPass { if password != s.cfg.AdminPass {
log.Warnf("Failed login attempt from %s", c.ClientIP()) log.Warnf("Failed login attempt from %s", c.ClientIP())
c.HTML(http.StatusUnauthorized, "login.html", gin.H{ c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"WebApiCache": getCache(), "WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg, "WebApiCfg": s.cfg,
"IncorrectPassword": true, "IncorrectPassword": true,
}) })

View File

@ -10,15 +10,21 @@ import (
"sync" "sync"
"time" "time"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/vspd/database" "github.com/decred/vspd/database"
"github.com/decred/vspd/rpc" "github.com/decred/vspd/rpc"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
) )
// apiCache is used to cache values which are commonly used by the API, so // cache is used to store values which are commonly used by the API, so
// repeated web requests don't repeatedly trigger DB or RPC calls. // repeated web requests don't repeatedly trigger DB or RPC calls.
type apiCache struct { type cache struct {
// data is the cached data.
data cacheData
// mtx must be held to read/write cache data.
mtx sync.RWMutex
}
type cacheData struct {
UpdateTime string UpdateTime string
PubKey string PubKey string
DatabaseSize string DatabaseSize string
@ -32,31 +38,26 @@ type apiCache struct {
RevokedProportion float32 RevokedProportion float32
} }
var cacheMtx sync.RWMutex func (c *cache) getData() cacheData {
var cache apiCache c.mtx.RLock()
defer c.mtx.RUnlock()
func getCache() apiCache { return c.data
cacheMtx.RLock()
defer cacheMtx.RUnlock()
return cache
} }
// initCache creates the struct which holds the cached VSP stats, and // newCache creates a new cache and initializes it with static values.
// initializes it with static values. func newCache(signPubKey string) *cache {
func initCache(signPubKey string) { return &cache{
cacheMtx.Lock() data: cacheData{
defer cacheMtx.Unlock()
cache = apiCache{
PubKey: signPubKey, PubKey: signPubKey,
},
} }
} }
// updateCache updates the dynamic values in the cache (ticket counts and best // update will use the provided database and RPC connections to update the
// block height). // dynamic values in the cache.
func updateCache(ctx context.Context, db *database.VspDatabase, func (c *cache) update(ctx context.Context, db *database.VspDatabase,
dcrd rpc.DcrdConnect, netParams *chaincfg.Params, wallets rpc.WalletConnect) error { dcrd rpc.DcrdConnect, wallets rpc.WalletConnect) error {
dbSize, err := db.Size() dbSize, err := db.Size()
if err != nil { if err != nil {
@ -70,7 +71,7 @@ func updateCache(ctx context.Context, db *database.VspDatabase,
} }
// Get latest best block height. // Get latest best block height.
dcrdClient, _, err := dcrd.Client(ctx, netParams) dcrdClient, _, err := dcrd.Client(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -84,7 +85,7 @@ func updateCache(ctx context.Context, db *database.VspDatabase,
return errors.New("dcr node reports a network ticket pool size of zero") return errors.New("dcr node reports a network ticket pool size of zero")
} }
clients, failedConnections := wallets.Clients(ctx, netParams) clients, failedConnections := wallets.Clients(ctx)
if len(clients) == 0 { if len(clients) == 0 {
log.Error("Could not connect to any wallets") log.Error("Could not connect to any wallets")
} else if len(failedConnections) > 0 { } else if len(failedConnections) > 0 {
@ -92,29 +93,29 @@ func updateCache(ctx context.Context, db *database.VspDatabase,
len(failedConnections), len(clients)) len(failedConnections), len(clients))
} }
cacheMtx.Lock() c.mtx.Lock()
defer cacheMtx.Unlock() defer c.mtx.Unlock()
cache.UpdateTime = dateTime(time.Now().Unix()) c.data.UpdateTime = dateTime(time.Now().Unix())
cache.DatabaseSize = humanize.Bytes(dbSize) c.data.DatabaseSize = humanize.Bytes(dbSize)
cache.Voting = voting c.data.Voting = voting
cache.Voted = voted c.data.Voted = voted
cache.TotalVotingWallets = int64(len(clients) + len(failedConnections)) c.data.TotalVotingWallets = int64(len(clients) + len(failedConnections))
cache.VotingWalletsOnline = int64(len(clients)) c.data.VotingWalletsOnline = int64(len(clients))
cache.Revoked = revoked c.data.Revoked = revoked
cache.BlockHeight = bestBlock.Height c.data.BlockHeight = bestBlock.Height
cache.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize) c.data.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize)
// Prevent dividing by zero when pool has no voted tickets. // Prevent dividing by zero when pool has no voted tickets.
switch voted { switch voted {
case 0: case 0:
if revoked == 0 { if revoked == 0 {
cache.RevokedProportion = 0 c.data.RevokedProportion = 0
} else { } else {
cache.RevokedProportion = 1 c.data.RevokedProportion = 1
} }
default: default:
cache.RevokedProportion = float32(revoked) / float32(voted) c.data.RevokedProportion = float32(revoked) / float32(voted)
} }
return nil return nil

View File

@ -12,7 +12,7 @@ import (
func (s *Server) homepage(c *gin.Context) { func (s *Server) homepage(c *gin.Context) {
c.HTML(http.StatusOK, "homepage.html", gin.H{ c.HTML(http.StatusOK, "homepage.html", gin.H{
"WebApiCache": getCache(), "WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg, "WebApiCfg": s.cfg,
}) })
} }

View File

@ -59,7 +59,7 @@ func (s *Server) requireAdmin(c *gin.Context) {
if admin == nil { if admin == nil {
c.HTML(http.StatusUnauthorized, "login.html", gin.H{ c.HTML(http.StatusUnauthorized, "login.html", gin.H{
"WebApiCache": getCache(), "WebApiCache": s.cache.getData(),
"WebApiCfg": s.cfg, "WebApiCfg": s.cfg,
}) })
c.Abort() c.Abort()
@ -69,9 +69,9 @@ func (s *Server) requireAdmin(c *gin.Context) {
// withDcrdClient middleware adds a dcrd client to the request context for // withDcrdClient middleware adds a dcrd client to the request context for
// downstream handlers to make use of. // downstream handlers to make use of.
func withDcrdClient(dcrd rpc.DcrdConnect, cfg Config) gin.HandlerFunc { func withDcrdClient(dcrd rpc.DcrdConnect) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
client, hostname, err := dcrd.Client(c, cfg.NetParams) client, hostname, err := dcrd.Client(c)
// Don't handle the error here, add it to the context and let downstream // Don't handle the error here, add it to the context and let downstream
// handlers decide what to do with it. // handlers decide what to do with it.
c.Set(dcrdKey, client) c.Set(dcrdKey, client)
@ -83,9 +83,9 @@ func withDcrdClient(dcrd rpc.DcrdConnect, cfg Config) gin.HandlerFunc {
// withWalletClients middleware attempts to add voting wallet clients to the // withWalletClients middleware attempts to add voting wallet clients to the
// request context for downstream handlers to make use of. Downstream handlers // request context for downstream handlers to make use of. Downstream handlers
// must handle the case where no wallet clients are connected. // must handle the case where no wallet clients are connected.
func withWalletClients(wallets rpc.WalletConnect, cfg Config) gin.HandlerFunc { func withWalletClients(wallets rpc.WalletConnect) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
clients, failedConnections := wallets.Clients(c, cfg.NetParams) clients, failedConnections := wallets.Clients(c)
if len(clients) == 0 { if len(clients) == 0 {
log.Error("Could not connect to any wallets") log.Error("Could not connect to any wallets")
} else if len(failedConnections) > 0 { } else if len(failedConnections) > 0 {

View File

@ -13,7 +13,7 @@ import (
// vspInfo is the handler for "GET /api/v3/vspinfo". // vspInfo is the handler for "GET /api/v3/vspinfo".
func (s *Server) vspInfo(c *gin.Context) { func (s *Server) vspInfo(c *gin.Context) {
cachedStats := getCache() cachedStats := s.cache.getData()
s.sendJSONResponse(vspInfoResponse{ s.sendJSONResponse(vspInfoResponse{
APIVersions: []int64{3}, APIVersions: []int64{3},
Timestamp: time.Now().Unix(), Timestamp: time.Now().Unix(),

View File

@ -67,6 +67,7 @@ type Server struct {
cfg Config cfg Config
db *database.VspDatabase db *database.VspDatabase
addrGen *addressGenerator addrGen *addressGenerator
cache *cache
signPrivKey ed25519.PrivateKey signPrivKey ed25519.PrivateKey
signPubKey ed25519.PublicKey signPubKey ed25519.PublicKey
} }
@ -88,8 +89,8 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
} }
// Populate cached VSP stats before starting webserver. // Populate cached VSP stats before starting webserver.
initCache(base64.StdEncoding.EncodeToString(s.signPubKey)) s.cache = newCache(base64.StdEncoding.EncodeToString(s.signPubKey))
err = updateCache(ctx, vdb, dcrd, config.NetParams, wallets) err = s.cache.update(ctx, vdb, dcrd, wallets)
if err != nil { if err != nil {
log.Errorf("Could not initialize VSP stats cache: %v", err) log.Errorf("Could not initialize VSP stats cache: %v", err)
} }
@ -174,7 +175,7 @@ func Start(ctx context.Context, requestShutdown func(), shutdownWg *sync.WaitGro
shutdownWg.Done() shutdownWg.Done()
return return
case <-time.After(refresh): case <-time.After(refresh):
err := updateCache(ctx, vdb, dcrd, config.NetParams, wallets) err := s.cache.update(ctx, vdb, dcrd, wallets)
if err != nil { if err != nil {
log.Errorf("Failed to update cached VSP stats: %v", err) log.Errorf("Failed to update cached VSP stats: %v", err)
} }
@ -230,11 +231,11 @@ func (s *Server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
api := router.Group("/api/v3") api := router.Group("/api/v3")
api.GET("/vspinfo", s.vspInfo) api.GET("/vspinfo", s.vspInfo)
api.POST("/setaltsignaddr", withDcrdClient(dcrd, s.cfg), s.broadcastTicket, s.vspAuth, s.setAltSignAddr) api.POST("/setaltsignaddr", withDcrdClient(dcrd), s.broadcastTicket, s.vspAuth, s.setAltSignAddr)
api.POST("/feeaddress", withDcrdClient(dcrd, s.cfg), s.broadcastTicket, s.vspAuth, s.feeAddress) api.POST("/feeaddress", withDcrdClient(dcrd), s.broadcastTicket, s.vspAuth, s.feeAddress)
api.POST("/ticketstatus", withDcrdClient(dcrd, s.cfg), s.vspAuth, s.ticketStatus) api.POST("/ticketstatus", withDcrdClient(dcrd), s.vspAuth, s.ticketStatus)
api.POST("/payfee", withDcrdClient(dcrd, s.cfg), s.vspAuth, s.payFee) api.POST("/payfee", withDcrdClient(dcrd), s.vspAuth, s.payFee)
api.POST("/setvotechoices", withDcrdClient(dcrd, s.cfg), withWalletClients(wallets, s.cfg), s.vspAuth, s.setVoteChoices) api.POST("/setvotechoices", withDcrdClient(dcrd), withWalletClients(wallets), s.vspAuth, s.setVoteChoices)
// Website routes. // Website routes.
@ -246,16 +247,16 @@ func (s *Server) router(cookieSecret []byte, dcrd rpc.DcrdConnect, wallets rpc.W
login.POST("", s.adminLogin) login.POST("", s.adminLogin)
admin := router.Group("/admin").Use( admin := router.Group("/admin").Use(
withWalletClients(wallets, s.cfg), withSession(cookieStore), s.requireAdmin, withWalletClients(wallets), withSession(cookieStore), s.requireAdmin,
) )
admin.GET("", withDcrdClient(dcrd, s.cfg), s.adminPage) admin.GET("", withDcrdClient(dcrd), s.adminPage)
admin.POST("/ticket", withDcrdClient(dcrd, s.cfg), s.ticketSearch) admin.POST("/ticket", withDcrdClient(dcrd), s.ticketSearch)
admin.GET("/backup", s.downloadDatabaseBackup) admin.GET("/backup", s.downloadDatabaseBackup)
admin.POST("/logout", s.adminLogout) admin.POST("/logout", s.adminLogout)
// Require Basic HTTP Auth on /admin/status endpoint. // Require Basic HTTP Auth on /admin/status endpoint.
basic := router.Group("/admin").Use( basic := router.Group("/admin").Use(
withDcrdClient(dcrd, s.cfg), withWalletClients(wallets, s.cfg), gin.BasicAuth(gin.Accounts{ withDcrdClient(dcrd), withWalletClients(wallets), gin.BasicAuth(gin.Accounts{
"admin": s.cfg.AdminPass, "admin": s.cfg.AdminPass,
}), }),
) )