From ef472ffe5dae95fc3dd7761a0baecdf5b824207e Mon Sep 17 00:00:00 2001 From: Jamie Holdstock Date: Sat, 15 May 2021 03:09:03 +0100 Subject: [PATCH] Include best block height in /vspinfo response (#254) --- config.go | 2 +- docs/api.md | 3 ++- rpc/dcrd.go | 11 +++++++++ webapi/homepage.go | 60 +++++++++++++++++++++++++++++++++++----------- webapi/types.go | 1 + webapi/vspinfo.go | 1 + webapi/webapi.go | 9 +++---- 7 files changed, 67 insertions(+), 20 deletions(-) diff --git a/config.go b/config.go index 486644b..3a0e342 100644 --- a/config.go +++ b/config.go @@ -243,7 +243,7 @@ func loadConfig() (*config, error) { flags.IniIncludeComments|flags.IniIncludeDefaults) if err != nil { return nil, fmt.Errorf("error creating a default "+ - "config file: %v", err) + "config file: %w", err) } fmt.Printf("Config file with default values written to %s\n", defaultConfigFile) diff --git a/docs/api.md b/docs/api.md index c1bcbba..ef7bf83 100644 --- a/docs/api.md +++ b/docs/api.md @@ -53,7 +53,8 @@ when a VSP is closed will result in an error. "vspdversion":"1.0.0-pre", "voting":10, "voted":25, - "revoked":3 + "revoked":3, + "blockheight":623212 } ``` diff --git a/rpc/dcrd.go b/rpc/dcrd.go index 7e5b13f..025a569 100644 --- a/rpc/dcrd.go +++ b/rpc/dcrd.go @@ -224,6 +224,17 @@ func (c *DcrdRPC) CanTicketVote(rawTx *dcrdtypes.TxRawResult, ticketHash string, return live, nil } +// GetBestBlockHeight uses getblockcount RPC to query the height of the best +// block known by the dcrd instance. +func (c *DcrdRPC) GetBestBlockHeight() (int64, error) { + var height int64 + err := c.Call(c.ctx, "getblockcount", &height) + if err != nil { + return 0, err + } + return height, nil +} + // ParseBlockConnectedNotification extracts the block header from a // blockconnected JSON-RPC notification. func ParseBlockConnectedNotification(params json.RawMessage) (*wire.BlockHeader, error) { diff --git a/webapi/homepage.go b/webapi/homepage.go index 626f82b..08a4580 100644 --- a/webapi/homepage.go +++ b/webapi/homepage.go @@ -5,16 +5,21 @@ 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 @@ -27,6 +32,7 @@ type vspStats struct { VspClosed bool Debug bool Designation string + BlockHeight int64 } var statsMtx sync.RWMutex @@ -39,28 +45,54 @@ func getVSPStats() *vspStats { return stats } -func updateVSPStats(db *database.VspDatabase, cfg Config) error { +// 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, + Debug: cfg.Debug, + Designation: cfg.Designation, + } +} + +// 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 + } + + blockHeight, err := dcrdClient.GetBestBlockHeight() + if err != nil { + return err + } + statsMtx.Lock() defer statsMtx.Unlock() - stats = &vspStats{ - PubKey: base64.StdEncoding.EncodeToString(signPubKey), - Voting: voting, - Voted: voted, - Revoked: revoked, - VSPFee: cfg.VSPFee, - Network: cfg.NetParams.Name, - UpdateTime: dateTime(time.Now().Unix()), - SupportEmail: cfg.SupportEmail, - VspClosed: cfg.VspClosed, - Debug: cfg.Debug, - Designation: cfg.Designation, - } + stats.UpdateTime = dateTime(time.Now().Unix()) + stats.Voting = voting + stats.Voted = voted + stats.Revoked = revoked + stats.BlockHeight = blockHeight return nil } diff --git a/webapi/types.go b/webapi/types.go index 44e165c..7ee9e11 100644 --- a/webapi/types.go +++ b/webapi/types.go @@ -15,6 +15,7 @@ type vspInfoResponse struct { Voting int64 `json:"voting"` Voted int64 `json:"voted"` Revoked int64 `json:"revoked"` + BlockHeight int64 `json:"blockheight"` } type feeAddressRequest struct { diff --git a/webapi/vspinfo.go b/webapi/vspinfo.go index c0837d3..7ccc5df 100644 --- a/webapi/vspinfo.go +++ b/webapi/vspinfo.go @@ -25,5 +25,6 @@ func vspInfo(c *gin.Context) { Voting: cachedStats.Voting, Voted: cachedStats.Voted, Revoked: cachedStats.Revoked, + BlockHeight: cachedStats.BlockHeight, }, c) } diff --git a/webapi/webapi.go b/webapi/webapi.go index 9aecd3b..1d83c0f 100644 --- a/webapi/webapi.go +++ b/webapi/webapi.go @@ -69,9 +69,10 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s } // Populate cached VSP stats before starting webserver. - err = updateVSPStats(vdb, config) + initVSPStats() + err = updateVSPStats(ctx, vdb, dcrd, config.NetParams) if err != nil { - return fmt.Errorf("could not initialize VSP stats cache: %w", err) + log.Errorf("Could not initialize VSP stats cache: %v", err) } // Get the last used address index and the feeXpub from the database, and @@ -139,7 +140,7 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s } }() - // Use a ticker to update template data. + // Use a ticker to update cached VSP stats. var refresh time.Duration if cfg.Debug { refresh = 1 * time.Second @@ -156,7 +157,7 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s shutdownWg.Done() return case <-ticker.C: - err = updateVSPStats(db, cfg) + err = updateVSPStats(ctx, vdb, dcrd, config.NetParams) if err != nil { log.Errorf("Failed to update cached VSP stats: %v", err) }