multi: Display expired/missed tickets on webpage.

Revoked ticket count is replaced by separate counts for expired/missed
tickets.

/vspinfo API response remains unchanged.
This commit is contained in:
jholdstock 2023-09-04 16:01:56 +01:00 committed by Jamie Holdstock
parent fc1f7f2955
commit 54e243526e
5 changed files with 84 additions and 35 deletions

View File

@ -308,13 +308,14 @@ func (vdb *VspDatabase) Size() (uint64, error) {
return size, err
}
// CountTickets returns the total number of voted, revoked, and currently voting
// tickets. This func iterates over every ticket so should be used sparingly.
func (vdb *VspDatabase) CountTickets() (int64, int64, int64, error) {
// CountTickets returns the total number of voted, expired, missed, and
// currently voting tickets. This func iterates over every ticket so should be
// used sparingly.
func (vdb *VspDatabase) CountTickets() (int64, int64, int64, int64, error) {
vdb.ticketsMtx.RLock()
defer vdb.ticketsMtx.RUnlock()
var voting, voted, revoked int64
var voting, voted, expired, missed int64
err := vdb.db.View(func(tx *bolt.Tx) error {
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
@ -325,8 +326,15 @@ func (vdb *VspDatabase) CountTickets() (int64, int64, int64, error) {
switch TicketOutcome(tBkt.Get(outcomeK)) {
case Voted:
voted++
case Revoked, Expired, Missed:
revoked++
case Expired:
expired++
case Missed:
missed++
case Revoked:
// There shouldn't be any revoked tickets in the db, they
// should have been updated to expired/missed. Give benefit
// of doubt to VSP admin and count these as expired.
expired++
default:
voting++
}
@ -336,7 +344,7 @@ func (vdb *VspDatabase) CountTickets() (int64, int64, int64, error) {
})
})
return voting, voted, revoked, err
return voting, voted, expired, missed, err
}
// GetUnconfirmedTickets returns tickets which are not yet confirmed.

View File

@ -1,4 +1,4 @@
// Copyright (c) 2020-2022 The Decred developers
// Copyright (c) 2020-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -228,8 +228,8 @@ func testFilterTickets(t *testing.T) {
}
func testCountTickets(t *testing.T) {
count := func(test string, expectedVoting, expectedVoted, expectedRevoked int64) {
voting, voted, revoked, err := db.CountTickets()
count := func(test string, expectedVoting, expectedVoted, expectedExpired, expectedMissed int64) {
voting, voted, expired, missed, err := db.CountTickets()
if err != nil {
t.Fatalf("error counting tickets: %v", err)
}
@ -242,14 +242,18 @@ func testCountTickets(t *testing.T) {
t.Fatalf("test %s: expected %d voted tickets, got %d",
test, expectedVoted, voted)
}
if revoked != expectedRevoked {
t.Fatalf("test %s: expected %d revoked tickets, got %d",
test, expectedRevoked, revoked)
if expired != expectedExpired {
t.Fatalf("test %s: expected %d expired tickets, got %d",
test, expectedExpired, expired)
}
if missed != expectedMissed {
t.Fatalf("test %s: expected %d missed tickets, got %d",
test, expectedMissed, missed)
}
}
// Initial counts should all be zero.
count("empty db", 0, 0, 0)
count("empty db", 0, 0, 0, 0)
// Insert a ticket with non-confirmed fee into the database.
// This should not be counted.
@ -260,7 +264,7 @@ func testCountTickets(t *testing.T) {
t.Fatalf("error storing ticket in database: %v", err)
}
count("unconfirmed fee", 0, 0, 0)
count("unconfirmed fee", 0, 0, 0, 0)
// Insert a ticket with confirmed fee into the database.
// This should be counted.
@ -271,7 +275,7 @@ func testCountTickets(t *testing.T) {
t.Fatalf("error storing ticket in database: %v", err)
}
count("confirmed fee", 1, 0, 0)
count("confirmed fee", 1, 0, 0, 0)
// Insert a voted ticket into the database.
// This should be counted.
@ -283,17 +287,41 @@ func testCountTickets(t *testing.T) {
t.Fatalf("error storing ticket in database: %v", err)
}
count("voted", 1, 1, 0)
count("voted", 1, 1, 0, 0)
// Insert a revoked ticket into the database.
// Insert an expired ticket into the database.
// This should be counted.
ticket4 := exampleTicket()
ticket4.FeeTxStatus = FeeConfirmed
ticket4.Outcome = Revoked
ticket4.Outcome = Expired
err = db.InsertNewTicket(ticket4)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("revoked", 1, 1, 1)
count("expired", 1, 1, 1, 0)
// Insert a missed ticket into the database.
// This should be counted.
ticket5 := exampleTicket()
ticket5.FeeTxStatus = FeeConfirmed
ticket5.Outcome = Missed
err = db.InsertNewTicket(ticket5)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("missed", 1, 1, 1, 1)
// Insert a revoked ticket into the database.
// This should be counted as expired.
ticket6 := exampleTicket()
ticket6.FeeTxStatus = FeeConfirmed
ticket6.Outcome = Revoked
err = db.InsertNewTicket(ticket6)
if err != nil {
t.Fatalf("error storing ticket in database: %v", err)
}
count("revoked", 1, 1, 2, 1)
}

View File

@ -1,4 +1,4 @@
// Copyright (c) 2020-2022 The Decred developers
// Copyright (c) 2020-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -31,12 +31,14 @@ type cacheData struct {
DatabaseSize string
Voting int64
Voted int64
Revoked int64
Expired int64
Missed int64
VotingWalletsOnline int64
TotalVotingWallets int64
BlockHeight uint32
NetworkProportion float32
RevokedProportion float32
ExpiredProportion float32
MissedProportion float32
}
func (c *cache) getData() cacheData {
@ -66,8 +68,8 @@ func (c *cache) update(db *database.VspDatabase, dcrd rpc.DcrdConnect,
return err
}
// Get latest counts of voting, voted and revoked tickets.
voting, voted, revoked, err := db.CountTickets()
// Get latest counts of voting, voted, expired and missed tickets.
voting, voted, expired, missed, err := db.CountTickets()
if err != nil {
return err
}
@ -104,17 +106,20 @@ func (c *cache) update(db *database.VspDatabase, dcrd rpc.DcrdConnect,
c.data.Voted = voted
c.data.TotalVotingWallets = int64(len(clients) + len(failedConnections))
c.data.VotingWalletsOnline = int64(len(clients))
c.data.Revoked = revoked
c.data.Expired = expired
c.data.Missed = missed
c.data.BlockHeight = bestBlock.Height
c.data.NetworkProportion = float32(voting) / float32(bestBlock.PoolSize)
total := voted + revoked
total := voted + expired + missed
// Prevent dividing by zero when pool has no voted/revoked tickets.
// Prevent dividing by zero when pool has no voted/expired/missed tickets.
if total == 0 {
c.data.RevokedProportion = 0
c.data.ExpiredProportion = 0
c.data.MissedProportion = 0
} else {
c.data.RevokedProportion = float32(revoked) / float32(total)
c.data.ExpiredProportion = float32(expired) / float32(total)
c.data.MissedProportion = float32(missed) / float32(total)
}
return nil

View File

@ -13,10 +13,18 @@
</div>
<div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Revoked tickets</div>
<div class="stat-title">Expired tickets</div>
<div class="stat-value">
{{ comma .WebApiCache.Revoked }}
<span class="text-muted">({{ float32ToPercent .WebApiCache.RevokedProportion }})</span>
{{ comma .WebApiCache.Expired }}
<span class="text-muted">({{ float32ToPercent .WebApiCache.ExpiredProportion }})</span>
</div>
</div>
<div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Missed tickets</div>
<div class="stat-value">
{{ comma .WebApiCache.Missed }}
<span class="text-muted">({{ float32ToPercent .WebApiCache.MissedProportion }})</span>
</div>
</div>

View File

@ -1,4 +1,4 @@
// Copyright (c) 2020-2022 The Decred developers
// Copyright (c) 2020-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
@ -28,7 +28,7 @@ func (s *Server) vspInfo(c *gin.Context) {
Voted: cachedStats.Voted,
TotalVotingWallets: cachedStats.TotalVotingWallets,
VotingWalletsOnline: cachedStats.VotingWalletsOnline,
Revoked: cachedStats.Revoked,
Revoked: cachedStats.Expired + cachedStats.Missed,
BlockHeight: cachedStats.BlockHeight,
NetworkProportion: cachedStats.NetworkProportion,
}, c)