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:
parent
78abc59e97
commit
78bb28056c
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
56
rpc/dcrd.go
56
rpc/dcrd.go
@ -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
|
||||||
|
|||||||
@ -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
12
vspd.go
@ -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, ¬ifHandler)
|
cfg.DcrdHost, cfg.dcrdCert, ¬ifHandler, 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.
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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()
|
PubKey: signPubKey,
|
||||||
|
},
|
||||||
cache = apiCache{
|
|
||||||
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
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
@ -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,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user