Add network proportion to homepage and /vspinfo (and revoked proportion) (#264)

* Add network proportion to homepage and /vspinfo.

Proportion is calculated using the number of tickets  currently registered with the VSP, divided by the total size of the network ticket pool as reported by `getblockheader`.

The value will only ever be an estimate because:

- it's possible for a single ticket to be added to multiple VSPs.
- vspd does not distinguish between immature and live tickets, whereas `getblockheader` only reports live tickets.
- `getblockheader` is reporting the size of the ticket pool as of the previous block, not the current block.

* xaur suggestions

* Show missed ticket %, not just the raw number.
This commit is contained in:
Jamie Holdstock 2021-06-08 21:47:07 +08:00 committed by GitHub
parent 978b78e745
commit 9867f78385
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 70 additions and 56 deletions

View File

@ -54,7 +54,8 @@ when a VSP is closed will result in an error.
"voting":10,
"voted":25,
"revoked":3,
"blockheight":623212
"blockheight":623212,
"estimatednetworkproportion":0.048478414
}
```

View File

@ -224,17 +224,6 @@ 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) {

View File

@ -53,3 +53,7 @@ func indentJSON(input string) template.HTML {
func atomsToDCR(atoms int64) string {
return dcrutil.Amount(atoms).String()
}
func float32ToPercent(input float32) string {
return fmt.Sprintf("%.2f%%", input*100)
}

View File

@ -21,18 +21,20 @@ import (
// 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
Debug bool
Designation string
BlockHeight int64
PubKey string
Voting int64
Voted int64
Revoked int64
VSPFee float64
Network string
UpdateTime string
SupportEmail string
VspClosed bool
Debug bool
Designation string
BlockHeight uint32
NetworkProportion float32
RevokedProportion float32
}
var statsMtx sync.RWMutex
@ -80,7 +82,7 @@ func updateVSPStats(ctx context.Context, db *database.VspDatabase,
return err
}
blockHeight, err := dcrdClient.GetBestBlockHeight()
bestBlock, err := dcrdClient.GetBestBlockHeader()
if err != nil {
return err
}
@ -92,7 +94,9 @@ func updateVSPStats(ctx context.Context, db *database.VspDatabase,
stats.Voting = voting
stats.Voted = voted
stats.Revoked = revoked
stats.BlockHeight = blockHeight
stats.BlockHeight = bestBlock.Height
stats.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize)
stats.RevokedProportion = float32(revoked) / float32(voted)
return nil
}

View File

@ -53,6 +53,11 @@ body {
color: #091440;
}
.vsp-stats .stat-value .text-muted{
font-size: 18px;
color: #8997A5;
}
footer {
flex-shrink: 0;
font-size: 0.8rem;

View File

@ -14,7 +14,10 @@
<div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Revoked tickets</div>
<div class="stat-value">{{ .Revoked }}</div>
<div class="stat-value">
{{ .Revoked }}
<span class="text-muted">({{ float32ToPercent .RevokedProportion }})</span>
</div>
</div>
<div class="col-6 col-sm-4 col-lg-2 py-3">
@ -27,6 +30,11 @@
<div class="stat-value">{{ .Network }}</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>
</div>
{{ end }}

View File

@ -5,17 +5,18 @@
package webapi
type vspInfoResponse struct {
APIVersions []int64 `json:"apiversions"`
Timestamp int64 `json:"timestamp"`
PubKey []byte `json:"pubkey"`
FeePercentage float64 `json:"feepercentage"`
VspClosed bool `json:"vspclosed"`
Network string `json:"network"`
VspdVersion string `json:"vspdversion"`
Voting int64 `json:"voting"`
Voted int64 `json:"voted"`
Revoked int64 `json:"revoked"`
BlockHeight int64 `json:"blockheight"`
APIVersions []int64 `json:"apiversions"`
Timestamp int64 `json:"timestamp"`
PubKey []byte `json:"pubkey"`
FeePercentage float64 `json:"feepercentage"`
VspClosed bool `json:"vspclosed"`
Network string `json:"network"`
VspdVersion string `json:"vspdversion"`
Voting int64 `json:"voting"`
Voted int64 `json:"voted"`
Revoked int64 `json:"revoked"`
BlockHeight uint32 `json:"blockheight"`
NetworkProportion float32 `json:"estimatednetworkproportion"`
}
type feeAddressRequest struct {

View File

@ -15,16 +15,17 @@ import (
func vspInfo(c *gin.Context) {
cachedStats := getVSPStats()
sendJSONResponse(vspInfoResponse{
APIVersions: []int64{3},
Timestamp: time.Now().Unix(),
PubKey: signPubKey,
FeePercentage: cfg.VSPFee,
Network: cfg.NetParams.Name,
VspClosed: cfg.VspClosed,
VspdVersion: version.String(),
Voting: cachedStats.Voting,
Voted: cachedStats.Voted,
Revoked: cachedStats.Revoked,
BlockHeight: cachedStats.BlockHeight,
APIVersions: []int64{3},
Timestamp: time.Now().Unix(),
PubKey: signPubKey,
FeePercentage: cfg.VSPFee,
Network: cfg.NetParams.Name,
VspClosed: cfg.VspClosed,
VspdVersion: version.String(),
Voting: cachedStats.Voting,
Voted: cachedStats.Voted,
Revoked: cachedStats.Revoked,
BlockHeight: cachedStats.BlockHeight,
NetworkProportion: cachedStats.NetworkProportion,
}, c)
}

View File

@ -177,13 +177,14 @@ func router(debugMode bool, cookieSecret []byte, dcrd rpc.DcrdConnect, wallets r
// Add custom functions for use in templates.
router.SetFuncMap(template.FuncMap{
"txURL": txURL(cfg.BlockExplorerURL),
"addressURL": addressURL(cfg.BlockExplorerURL),
"blockURL": blockURL(cfg.BlockExplorerURL),
"dateTime": dateTime,
"stripWss": stripWss,
"indentJSON": indentJSON,
"atomsToDCR": atomsToDCR,
"txURL": txURL(cfg.BlockExplorerURL),
"addressURL": addressURL(cfg.BlockExplorerURL),
"blockURL": blockURL(cfg.BlockExplorerURL),
"dateTime": dateTime,
"stripWss": stripWss,
"indentJSON": indentJSON,
"atomsToDCR": atomsToDCR,
"float32ToPercent": float32ToPercent,
})
router.LoadHTMLGlob("webapi/templates/*.html")