Refactor gui cache
* Remove static cfg values from GUI cache. Theres no need for these values to be copied into the cache when templates could simply access the config struct directly. This also changes the cache accessor so it returns a copy of the cache rather than a pointer, which removes a potential race. * Rename and move cache. Cache code was previous in `homepage.go`, but its used in multiple places and not just on the homepage. Its enough code to go into its own dedicated `cache.go`.
This commit is contained in:
parent
d1a838bf7f
commit
fc131e926d
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 The Decred developers
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -97,7 +97,8 @@ func statusJSON(c *gin.Context) {
|
||||
// adminPage is the handler for "GET /admin".
|
||||
func adminPage(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "admin.html", gin.H{
|
||||
"VspStats": getVSPStats(),
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"WalletStatus": walletStatus(c),
|
||||
})
|
||||
}
|
||||
@ -129,7 +130,8 @@ func ticketSearch(c *gin.Context) {
|
||||
VoteChanges: voteChanges,
|
||||
MaxVoteChanges: cfg.MaxVoteChangeRecords,
|
||||
},
|
||||
"VspStats": getVSPStats(),
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"WalletStatus": walletStatus(c),
|
||||
})
|
||||
}
|
||||
@ -142,7 +144,8 @@ func adminLogin(c *gin.Context) {
|
||||
if password != cfg.AdminPass {
|
||||
log.Warnf("Failed login attempt from %s", c.ClientIP())
|
||||
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
|
||||
"VspStats": getVSPStats(),
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
"IncorrectPassword": true,
|
||||
})
|
||||
return
|
||||
|
||||
86
webapi/cache.go
Normal file
86
webapi/cache.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/v3"
|
||||
"github.com/decred/vspd/database"
|
||||
"github.com/decred/vspd/rpc"
|
||||
)
|
||||
|
||||
// apiCache is used to cache values which are commonly used by the API, so
|
||||
// repeated web requests don't repeatedly trigger DB or RPC calls.
|
||||
type apiCache struct {
|
||||
UpdateTime string
|
||||
PubKey string
|
||||
Voting int64
|
||||
Voted int64
|
||||
Revoked int64
|
||||
BlockHeight uint32
|
||||
NetworkProportion float32
|
||||
RevokedProportion float32
|
||||
}
|
||||
|
||||
var cacheMtx sync.RWMutex
|
||||
var cache apiCache
|
||||
|
||||
func getCache() apiCache {
|
||||
cacheMtx.RLock()
|
||||
defer cacheMtx.RUnlock()
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// initCache creates the struct which holds the cached VSP stats, and
|
||||
// initializes it with static values.
|
||||
func initCache() {
|
||||
cacheMtx.Lock()
|
||||
defer cacheMtx.Unlock()
|
||||
|
||||
cache = apiCache{
|
||||
PubKey: base64.StdEncoding.EncodeToString(signPubKey),
|
||||
}
|
||||
}
|
||||
|
||||
// updateCache updates the dynamic values in the cache (ticket counts and best
|
||||
// block height).
|
||||
func updateCache(ctx context.Context, db *database.VspDatabase,
|
||||
dcrd rpc.DcrdConnect, netParams *chaincfg.Params) error {
|
||||
|
||||
// Get latest counts of voting, voted and revoked tickets.
|
||||
voting, voted, revoked, err := db.CountTickets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get latest best block height.
|
||||
dcrdClient, err := dcrd.Client(ctx, netParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bestBlock, err := dcrdClient.GetBestBlockHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cacheMtx.Lock()
|
||||
defer cacheMtx.Unlock()
|
||||
|
||||
cache.UpdateTime = dateTime(time.Now().Unix())
|
||||
cache.Voting = voting
|
||||
cache.Voted = voted
|
||||
cache.Revoked = revoked
|
||||
cache.BlockHeight = bestBlock.Height
|
||||
cache.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize)
|
||||
cache.RevokedProportion = float32(revoked) / float32(voted)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,112 +1,18 @@
|
||||
// Copyright (c) 2020 The Decred developers
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package webapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/decred/dcrd/chaincfg/v3"
|
||||
"github.com/decred/vspd/database"
|
||||
"github.com/decred/vspd/rpc"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// vspStats is used to cache values which are commonly used by the API, so
|
||||
// repeated web requests don't repeatedly trigger DB or RPC calls.
|
||||
type vspStats struct {
|
||||
PubKey string
|
||||
Voting int64
|
||||
Voted int64
|
||||
Revoked int64
|
||||
VSPFee float64
|
||||
Network string
|
||||
UpdateTime string
|
||||
SupportEmail string
|
||||
VspClosed bool
|
||||
VspClosedMsg string
|
||||
Debug bool
|
||||
Designation string
|
||||
BlockHeight uint32
|
||||
NetworkProportion float32
|
||||
RevokedProportion float32
|
||||
VspdVersion string
|
||||
}
|
||||
|
||||
var statsMtx sync.RWMutex
|
||||
var stats *vspStats
|
||||
|
||||
func getVSPStats() *vspStats {
|
||||
statsMtx.RLock()
|
||||
defer statsMtx.RUnlock()
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// initVSPStats creates the struct which holds the cached VSP stats, and
|
||||
// initializes it with static values.
|
||||
func initVSPStats() {
|
||||
|
||||
statsMtx.Lock()
|
||||
defer statsMtx.Unlock()
|
||||
|
||||
stats = &vspStats{
|
||||
PubKey: base64.StdEncoding.EncodeToString(signPubKey),
|
||||
VSPFee: cfg.VSPFee,
|
||||
Network: cfg.NetParams.Name,
|
||||
SupportEmail: cfg.SupportEmail,
|
||||
VspClosed: cfg.VspClosed,
|
||||
VspClosedMsg: cfg.VspClosedMsg,
|
||||
Debug: cfg.Debug,
|
||||
Designation: cfg.Designation,
|
||||
VspdVersion: cfg.VspdVersion,
|
||||
}
|
||||
}
|
||||
|
||||
// updateVSPStats updates the dynamic values in the cached VSP stats (ticket
|
||||
// counts and best block height).
|
||||
func updateVSPStats(ctx context.Context, db *database.VspDatabase,
|
||||
dcrd rpc.DcrdConnect, netParams *chaincfg.Params) error {
|
||||
|
||||
// Update counts of voting, voted and revoked tickets.
|
||||
voting, voted, revoked, err := db.CountTickets()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update best block height.
|
||||
dcrdClient, err := dcrd.Client(ctx, netParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bestBlock, err := dcrdClient.GetBestBlockHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
statsMtx.Lock()
|
||||
defer statsMtx.Unlock()
|
||||
|
||||
stats.UpdateTime = dateTime(time.Now().Unix())
|
||||
stats.Voting = voting
|
||||
stats.Voted = voted
|
||||
stats.Revoked = revoked
|
||||
stats.BlockHeight = bestBlock.Height
|
||||
stats.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize)
|
||||
stats.RevokedProportion = float32(revoked) / float32(voted)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func homepage(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "homepage.html", gin.H{
|
||||
"VspStats": getVSPStats(),
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 The Decred developers
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -62,7 +62,8 @@ func requireAdmin() gin.HandlerFunc {
|
||||
|
||||
if admin == nil {
|
||||
c.HTML(http.StatusUnauthorized, "login.html", gin.H{
|
||||
"VspStats": getVSPStats(),
|
||||
"WebApiCache": getCache(),
|
||||
"WebApiCfg": cfg,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
{{ template "vsp-stats" .VspStats }}
|
||||
{{ template "vsp-stats" . }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,11 +5,11 @@
|
||||
<footer class="row m-0">
|
||||
<div class="col-md-8 col-12 d-flex justify-content-center align-items-center">
|
||||
<p class="py-4 m-0">
|
||||
<strong>Stats updated:</strong> {{ .VspStats.UpdateTime }}
|
||||
<strong>Stats updated:</strong> {{ .WebApiCache.UpdateTime }}
|
||||
<br />
|
||||
<strong>Support:</strong> <a href="mailto:{{ .VspStats.SupportEmail }}" rel="noopener noreferrer">{{ .VspStats.SupportEmail }}</a>
|
||||
<strong>Support:</strong> <a href="mailto:{{ .WebApiCfg.SupportEmail }}" rel="noopener noreferrer">{{ .WebApiCfg.SupportEmail }}</a>
|
||||
<br />
|
||||
<strong>VSP public key:</strong> <span class="code">{{ .VspStats.PubKey }}</span>
|
||||
<strong>VSP public key:</strong> <span class="code">{{ .WebApiCache.PubKey }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>Decred VSP - {{ .VspStats.Designation }}</title>
|
||||
<title>Decred VSP - {{ .WebApiCfg.Designation }}</title>
|
||||
|
||||
<link rel="stylesheet" href="/public/css/vendor/bootstrap-4.5.0.min.css" />
|
||||
<link rel="stylesheet" href="/public/css/vspd.css?v={{ .VspStats.VspdVersion }}" />
|
||||
<link rel="stylesheet" href="/public/css/vspd.css?v={{ .WebApiCfg.VspdVersion }}" />
|
||||
<!-- fonts.css should be last to ensure dcr fonts take precedence. -->
|
||||
<link rel="stylesheet" href="/public/css/fonts.css?v={{ .VspStats.VspdVersion }}" />
|
||||
<link rel="stylesheet" href="/public/css/fonts.css?v={{ .WebApiCfg.VspdVersion }}" />
|
||||
|
||||
<!-- Custom favicon -->
|
||||
<!-- Apple PWA -->
|
||||
@ -59,13 +59,13 @@
|
||||
</div>
|
||||
<div class="logo--text">
|
||||
VSP<br>
|
||||
<b>{{ .VspStats.Designation }}</b>
|
||||
<b>{{ .WebApiCfg.Designation }}</b>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{{ if .VspStats.Debug }}
|
||||
{{ if .WebApiCfg.Debug }}
|
||||
<div class="container">
|
||||
<div class="alert alert-warning my-2">
|
||||
Web server is running in debug mode - don't do this in production!
|
||||
|
||||
@ -3,13 +3,13 @@
|
||||
<div class="vsp-overview pt-4 pb-3 mb-3">
|
||||
<div class="container">
|
||||
|
||||
{{ if .VspStats.VspClosed }}
|
||||
{{ if .WebApiCfg.VspClosed }}
|
||||
<div class="alert alert-danger">
|
||||
<h4 class="alert-heading mb-3">
|
||||
This Voting Service Provider is closed
|
||||
</h4>
|
||||
<p>
|
||||
{{ .VspStats.VspClosedMsg }}
|
||||
{{ .WebApiCfg.VspClosedMsg }}
|
||||
</p>
|
||||
<p>
|
||||
A closed VSP will still vote on tickets with already paid fees, but will not accept new any tickets.
|
||||
@ -27,7 +27,7 @@
|
||||
to find out more about VSPs, tickets, and voting.
|
||||
</p>
|
||||
|
||||
{{ template "vsp-stats" .VspStats }}
|
||||
{{ template "vsp-stats" . }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,35 +4,35 @@
|
||||
|
||||
<div class="col-6 col-sm-4 col-lg-2 py-3">
|
||||
<div class="stat-title">Live tickets</div>
|
||||
<div class="stat-value">{{ .Voting }}</div>
|
||||
<div class="stat-value">{{ .WebApiCache.Voting }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-sm-4 col-lg-2 py-3">
|
||||
<div class="stat-title">Voted tickets</div>
|
||||
<div class="stat-value">{{ .Voted }}</div>
|
||||
<div class="stat-value">{{ .WebApiCache.Voted }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-sm-4 col-lg-2 py-3">
|
||||
<div class="stat-title">Revoked tickets</div>
|
||||
<div class="stat-value">
|
||||
{{ .Revoked }}
|
||||
<span class="text-muted">({{ float32ToPercent .RevokedProportion }})</span>
|
||||
{{ .WebApiCache.Revoked }}
|
||||
<span class="text-muted">({{ float32ToPercent .WebApiCache.RevokedProportion }})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-sm-4 col-lg-2 py-3">
|
||||
<div class="stat-title">VSP Fee</div>
|
||||
<div class="stat-value">{{ .VSPFee }}%</div>
|
||||
<div class="stat-value">{{ .WebApiCfg.VSPFee }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-sm-4 col-lg-2 py-3">
|
||||
<div class="stat-title">Network</div>
|
||||
<div class="stat-value">{{ .Network }}</div>
|
||||
<div class="stat-value">{{ .WebApiCfg.NetParams.Name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-6 col-sm-4 col-lg-2 py-3">
|
||||
<div class="stat-title">Network Proportion</div>
|
||||
<div class="stat-value">{{ float32ToPercent .NetworkProportion }}</div>
|
||||
<div class="stat-value">{{ float32ToPercent .WebApiCache.NetworkProportion }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 The Decred developers
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
|
||||
// vspInfo is the handler for "GET /api/v3/vspinfo".
|
||||
func vspInfo(c *gin.Context) {
|
||||
cachedStats := getVSPStats()
|
||||
cachedStats := getCache()
|
||||
sendJSONResponse(vspInfoResponse{
|
||||
APIVersions: []int64{3},
|
||||
Timestamp: time.Now().Unix(),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2020 The Decred developers
|
||||
// Copyright (c) 2020-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -69,8 +69,8 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s
|
||||
}
|
||||
|
||||
// Populate cached VSP stats before starting webserver.
|
||||
initVSPStats()
|
||||
err = updateVSPStats(ctx, vdb, dcrd, config.NetParams)
|
||||
initCache()
|
||||
err = updateCache(ctx, vdb, dcrd, config.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("Could not initialize VSP stats cache: %v", err)
|
||||
}
|
||||
@ -130,7 +130,7 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s
|
||||
|
||||
// Start webserver.
|
||||
go func() {
|
||||
err = srv.Serve(listener)
|
||||
err := srv.Serve(listener)
|
||||
// If the server dies for any reason other than ErrServerClosed (from
|
||||
// graceful server.Shutdown), log the error and request vspd be
|
||||
// shutdown.
|
||||
@ -157,7 +157,7 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s
|
||||
shutdownWg.Done()
|
||||
return
|
||||
case <-ticker.C:
|
||||
err = updateVSPStats(ctx, vdb, dcrd, config.NetParams)
|
||||
err := updateCache(ctx, vdb, dcrd, config.NetParams)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to update cached VSP stats: %v", err)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user