Wallet consistency checks & setting ticket outcome

This commit is contained in:
jholdstock 2020-07-27 17:04:58 +01:00 committed by David Hill
parent 8c3cab7942
commit 9d503e67ae
15 changed files with 367 additions and 75 deletions

View File

@ -27,6 +27,8 @@ var (
type NotificationHandler struct{}
const (
// consistencyInterval is the time period between wallet consistency checks.
consistencyInterval = 30 * time.Minute
// requiredConfs is the number of confirmations required to consider a
// ticket purchase or a fee transaction to be final.
requiredConfs = 6
@ -69,7 +71,7 @@ func blockConnected() {
return
}
// Step 1/3: Update the database with any tickets which now have 6+
// Step 1/4: Update the database with any tickets which now have 6+
// confirmations.
unconfirmed, err := db.GetUnconfirmedTickets()
@ -106,15 +108,16 @@ func blockConnected() {
ticket.Confirmed = true
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket error (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to set ticket as confirmed (ticketHash=%s): %v",
funcName, ticket.Hash, err)
continue
}
log.Debugf("%s: Ticket confirmed (ticketHash=%s)", funcName, ticket.Hash)
log.Infof("%s: Ticket confirmed (ticketHash=%s)", funcName, ticket.Hash)
}
}
// Step 2/3: Broadcast fee tx for tickets which are confirmed.
// Step 2/4: Broadcast fee tx for tickets which are confirmed.
pending, err := db.GetPendingFees()
if err != nil {
@ -128,36 +131,28 @@ func blockConnected() {
funcName, ticket.Hash, err)
ticket.FeeTxStatus = database.FeeError
} else {
log.Debugf("%s: Fee tx broadcast for ticket (ticketHash=%s, feeHash=%s)",
log.Infof("%s: Fee tx broadcast for ticket (ticketHash=%s, feeHash=%s)",
funcName, ticket.Hash, ticket.FeeTxHash)
ticket.FeeTxStatus = database.FeeBroadcast
}
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket error (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v",
funcName, ticket.Hash, err)
}
}
// Step 3/3: Add tickets with confirmed fees to voting wallets.
// Step 3/4: Add tickets with confirmed fees to voting wallets.
unconfirmedFees, err := db.GetUnconfirmedFees()
if err != nil {
log.Errorf("%s: db.GetUnconfirmedFees error: %v", funcName, err)
// If this fails, there is nothing more we can do. Return.
return
}
// If there are no confirmed fees, there is nothing more to do. Return.
if len(unconfirmedFees) == 0 {
return
}
walletClients, failedConnections := walletRPC.Clients(ctx, netParams)
if len(walletClients) == 0 {
// If no wallet clients, there is nothing more we can do. Return.
log.Errorf("%s: Could not connect to any wallets", funcName)
return
}
if len(failedConnections) > 0 {
log.Errorf("%s: Failed to connect to %d wallet(s), proceeding with only %d",
@ -178,10 +173,11 @@ func blockConnected() {
ticket.FeeTxStatus = database.FeeConfirmed
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket error (ticketHash=%s): %v", funcName, ticket.Hash, err)
return
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as confirmed (ticketHash=%s): %v",
funcName, ticket.Hash, err)
continue
}
log.Debugf("%s: Fee tx confirmed (ticketHash=%s)", funcName, ticket.Hash)
log.Infof("%s: Fee tx confirmed (ticketHash=%s)", funcName, ticket.Hash)
// Add ticket to the voting wallet.
@ -208,11 +204,63 @@ func blockConnected() {
continue
}
}
log.Debugf("%s: Ticket added to voting wallet (wallet=%s, ticketHash=%s)",
log.Infof("%s: Ticket added to voting wallet (wallet=%s, ticketHash=%s)",
funcName, walletClient.String(), ticket.Hash)
}
}
}
// Step 4/4: Set ticket outcome in database if any tickets are voted/revoked.
// Ticket status needs to be checked on every wallet. This is because only
// one of the voting wallets will actually succeed in voting/revoking
// tickets (the others will get errors like "tx already exists"). Only the
// successful wallet will have the most up-to-date ticket status, the others
// will be outdated.
for _, walletClient := range walletClients {
dbTickets, err := db.GetVotableTickets()
if err != nil {
log.Errorf("%s: db.GetVotableTickets failed: %v", funcName, err)
continue
}
ticketInfo, err := walletClient.TicketInfo()
if err != nil {
log.Errorf("%s: dcrwallet.TicketInfo failed (wallet=%s): %v",
funcName, walletClient.String(), err)
continue
}
for _, dbTicket := range dbTickets {
tInfo, ok := ticketInfo[dbTicket.Hash]
if !ok {
log.Warnf("%s: TicketInfo response did not include expected ticket (wallet=%s, ticketHash=%s)",
funcName, walletClient.String(), dbTicket.Hash)
continue
}
switch tInfo.Status {
case "revoked":
dbTicket.Outcome = database.Revoked
case "voted":
dbTicket.Outcome = database.Voted
default:
// Skip to next ticket.
continue
}
err = db.UpdateTicket(dbTicket)
if err != nil {
log.Errorf("%s: db.UpdateTicket error, failed to set ticket outcome (ticketHash=%s): %v",
funcName, dbTicket.Hash, err)
continue
}
log.Infof("%s: Ticket no longer votable: outcome=%s, ticketHash=%s", funcName,
dbTicket.Outcome, dbTicket.Hash)
}
}
}
func (n *NotificationHandler) Close() error {
@ -260,6 +308,24 @@ func Start(c context.Context, wg *sync.WaitGroup, vdb *database.VspDatabase, drp
// while vspd was shut down.
blockConnected()
// Run voting wallet consistency check now to ensure all wallets are up to
// date.
checkWalletConsistency()
// Run voting wallet consistency check periodically.
go func() {
ticker := time.NewTicker(consistencyInterval)
for {
select {
case <-ctx.Done():
ticker.Stop()
return
case <-ticker.C:
checkWalletConsistency()
}
}
}()
// Loop forever attempting to create a connection to the dcrd server for
// notifications.
go func() {
@ -280,3 +346,145 @@ func Start(c context.Context, wg *sync.WaitGroup, vdb *database.VspDatabase, drp
}
}()
}
// checkWalletConsistency will retrieve all votable tickets from the database
// and ensure they are all added to voting wallets with the correct vote
// choices.
func checkWalletConsistency() {
funcName := "checkWalletConsistency"
log.Info("Checking voting wallet consistency")
dcrdClient, err := dcrdRPC.Client(ctx, netParams)
if err != nil {
log.Errorf("%s: %v", funcName, err)
return
}
walletClients, failedConnections := walletRPC.Clients(ctx, netParams)
if len(walletClients) == 0 {
log.Errorf("%s: Could not connect to any wallets", funcName)
return
}
if len(failedConnections) > 0 {
log.Errorf("%s: Failed to connect to %d wallet(s), proceeding with only %d",
funcName, len(failedConnections), len(walletClients))
}
// Step 1/2: Check all tickets are added to all voting wallets.
votableTickets, err := db.GetVotableTickets()
if err != nil {
log.Errorf("%s: db.GetVotableTickets failed: %v", funcName, err)
return
}
// Iterate over each wallet and add any missing tickets.
for _, walletClient := range walletClients {
// Get all tickets the wallet is aware of.
walletTickets, err := walletClient.TicketInfo()
if err != nil {
log.Errorf("%s: dcrwallet.TicketInfo failed (wallet=%s): %v",
funcName, walletClient.String(), err)
continue
}
// If missing tickets are added, set a flag and keep track of the
// earliest purchase height.
var added bool
var minHeight int64
for _, dbTicket := range votableTickets {
// If wallet already knows this ticket, skip to the next one.
_, exists := walletTickets[dbTicket.Hash]
if exists {
continue
}
log.Debugf("%s: Adding missing ticket (wallet=%s, ticketHash=%s)",
funcName, walletClient.String(), dbTicket.Hash)
rawTicket, err := dcrdClient.GetRawTransaction(dbTicket.Hash)
if err != nil {
log.Errorf("%s: dcrd.GetRawTransaction error: %v", funcName, err)
continue
}
err = walletClient.AddTicketForVoting(dbTicket.VotingWIF, rawTicket.BlockHash, rawTicket.Hex)
if err != nil {
log.Errorf("%s: dcrwallet.AddTicketForVoting error (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), dbTicket.Hash, err)
continue
}
added = true
if minHeight == 0 || minHeight > rawTicket.BlockHeight {
minHeight = rawTicket.BlockHeight
}
}
// Perform a rescan if any missing tickets were added to this wallet.
if added {
log.Infof("%s: Performing a rescan on wallet %s (fromHeight=%d)",
funcName, walletClient.String(), minHeight)
err = walletClient.RescanFrom(minHeight)
if err != nil {
log.Errorf("%s: dcrwallet.RescanFrom failed (wallet=%s): %v",
funcName, walletClient.String(), err)
continue
}
}
}
// Step 2/2: Ensure vote choices are set correctly for all tickets on
// all wallets.
for _, walletClient := range walletClients {
// Get all tickets the wallet is aware of.
walletTickets, err := walletClient.TicketInfo()
if err != nil {
log.Errorf("%s: dcrwallet.TicketInfo failed (wallet=%s): %v",
funcName, walletClient.String(), err)
continue
}
for _, dbTicket := range votableTickets {
// All tickets should be added to all wallets at this point, so log
// a warning if any are still missing.
walletTicket, exists := walletTickets[dbTicket.Hash]
if !exists {
log.Warnf("%s: Ticket missing from voting wallet (wallet=%s, ticketHash=%s)",
funcName, walletClient.String, dbTicket.Hash)
continue
}
// Check if vote choices match
for dbAgenda, dbChoice := range dbTicket.VoteChoices {
match := false
for _, walletChoice := range walletTicket.Choices {
if walletChoice.AgendaID == dbAgenda && walletChoice.ChoiceID == dbChoice {
match = true
}
}
// Skip to next agenda if db and wallet are matching.
if match {
continue
}
log.Debugf("%s: Updating incorrect vote choices (wallet=%s, agenda=%s, ticketHash=%s)",
funcName, walletClient.String(), dbAgenda, dbTicket.Hash)
// If db and wallet are not matching, update wallet with correct
// choice.
err = walletClient.SetVoteChoice(dbAgenda, dbChoice, dbTicket.Hash)
if err != nil {
log.Errorf("%s: dcrwallet.SetVoteChoice error (wallet=%s, ticketHash=%s): %v",
funcName, walletClient.String(), dbTicket.Hash, err)
continue
}
}
}
}
}

View File

@ -25,6 +25,16 @@ const (
FeeError FeeStatus = "error"
)
// TicketOutcome describes the reason a ticket is no longer votable.
type TicketOutcome string
const (
// Ticket has been revoked, either because it was missed or it expired.
Revoked TicketOutcome = "revoked"
// Ticket has already voted.
Voted TicketOutcome = "voted"
)
// Ticket is serialized to json and stored in bbolt db. The json keys are
// deliberately kept short because they are duplicated many times in the db.
type Ticket struct {
@ -51,6 +61,10 @@ type Ticket struct {
// FeeTxStatus indicates the current state of the fee transaction.
FeeTxStatus FeeStatus `json:"fsts"`
// Outcome is set once a ticket is either voted or revoked. An empty outcome
// indicates that a ticket is still votable.
Outcome TicketOutcome `json:"otcme"`
}
func (t *Ticket) FeeExpired() bool {
@ -170,16 +184,15 @@ func (vdb *VspDatabase) GetTicketByHash(ticketHash string) (Ticket, bool, error)
return ticket, found, err
}
func (vdb *VspDatabase) CountTickets() (int, int, error) {
func (vdb *VspDatabase) CountTickets() (int64, int64, int64, error) {
defer vdb.ticketsMtx.RUnlock()
vdb.ticketsMtx.RLock()
var total, feePaid int
var voting, voted, revoked int64
err := vdb.db.View(func(tx *bolt.Tx) error {
ticketBkt := tx.Bucket(vspBktK).Bucket(ticketBktK)
return ticketBkt.ForEach(func(k, v []byte) error {
total++
var ticket Ticket
err := json.Unmarshal(v, &ticket)
if err != nil {
@ -187,14 +200,21 @@ func (vdb *VspDatabase) CountTickets() (int, int, error) {
}
if ticket.FeeTxStatus == FeeConfirmed {
feePaid++
switch ticket.Outcome {
case Voted:
voted++
case Revoked:
revoked++
default:
voting++
}
}
return nil
})
})
return total, feePaid, err
return voting, voted, revoked, err
}
// GetUnconfirmedTickets returns tickets which are not yet confirmed.
@ -229,6 +249,14 @@ func (vdb *VspDatabase) GetUnconfirmedFees() ([]Ticket, error) {
})
}
// GetVotableTickets returns tickets with a confirmed fee tx and no outcome (ie.
// not expired/voted/missed).
func (vdb *VspDatabase) GetVotableTickets() ([]Ticket, error) {
return vdb.filterTickets(func(t Ticket) bool {
return t.FeeTxStatus == FeeConfirmed && t.Outcome == ""
})
}
// filterTickets accepts a filter function and returns all tickets from the
// database which match the filter.
//

View File

@ -50,7 +50,10 @@ when a VSP is closed will result in an error.
"feepercentage":3.0,
"vspclosed":false,
"network":"testnet3",
"vspdversion":"1.0.0-pre"
"vspdversion":"1.0.0-pre",
"voting":10,
"voted":25,
"revoked":3
}
```

6
go.mod
View File

@ -3,9 +3,9 @@ module github.com/decred/vspd
go 1.13
require (
decred.org/dcrwallet v1.2.3-0.20200519180100-f1aa4c354e05
decred.org/dcrwallet v1.2.3-0.20200727154839-096e3bee25f2
github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200616182840-3baf1f590cb1
github.com/decred/dcrd/blockchain/v3 v3.0.0-20200311044114-143c1884e4c8
github.com/decred/dcrd/blockchain/v3 v3.0.0-20200608124004-b2f67c2dc475
github.com/decred/dcrd/chaincfg/chainhash v1.0.2
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200616182840-3baf1f590cb1
github.com/decred/dcrd/dcrec v1.0.0
@ -17,7 +17,7 @@ require (
github.com/decred/slog v1.0.0
github.com/gin-gonic/gin v1.6.3
github.com/gorilla/sessions v1.2.0
github.com/jessevdk/go-flags v1.4.0
github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7
github.com/jrick/bitset v1.0.0
github.com/jrick/logrotate v1.0.0
github.com/jrick/wsrpc/v2 v2.3.3

41
go.sum
View File

@ -1,7 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
decred.org/cspp v0.3.0 h1:2AkSsWzA7HIMZImfw0gT82Gdp8OXIM4NsBn7vna22uE=
decred.org/cspp v0.3.0/go.mod h1:UygjYilC94dER3BEU65Zzyoqy9ngJfWCD2rdJqvUs2A=
decred.org/dcrwallet v1.2.3-0.20200519180100-f1aa4c354e05 h1:xongFmW2UgEOGu4zQ4VcQFduExKVBa+dC4aLRQLCCnQ=
decred.org/dcrwallet v1.2.3-0.20200519180100-f1aa4c354e05/go.mod h1:V6pzOHJuuWZaUPUZZL2kiyx9Co3lVD0DRqDXJTWA+3c=
decred.org/dcrwallet v1.2.3-0.20200727154839-096e3bee25f2 h1:iwsnZPdoo1ownS4T2asE3rZAwkDwfufQ+LanREPsFBY=
decred.org/dcrwallet v1.2.3-0.20200727154839-096e3bee25f2/go.mod h1:3cZSQAgJzWXeSKMxWuyAPjxcheBYOB5fwTzD5exhvgs=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI=
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
@ -20,16 +21,18 @@ github.com/decred/base58 v1.0.1/go.mod h1:H2ENcsJjye1G7CbRa67kV9OFaui0LGr56ntKKo
github.com/decred/base58 v1.0.2/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E=
github.com/decred/base58 v1.0.3 h1:KGZuh8d1WEMIrK0leQRM47W85KqCAdl2N+uagbctdDI=
github.com/decred/base58 v1.0.3/go.mod h1:pXP9cXCfM2sFLb2viz2FNIdeMWmZDBKG3ZBYbiSM78E=
github.com/decred/dcrd/addrmgr v1.1.0 h1:VQkn1qmafZypfN2u7yi7J/girwz4ZDicquo7JzsoxdQ=
github.com/decred/dcrd/addrmgr v1.1.0/go.mod h1:exghL+0+QeVvO4MXezWJ1C2tcpBn3ngfuP6S1R+adB8=
github.com/decred/dcrd/blockchain/stake/v2 v2.0.2/go.mod h1:o2TT/l/YFdrt15waUdlZ3g90zfSwlA0WgQqHV9UGJF4=
github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:aDL94kcVJfaaJP+acWUJrlK7g7xEOqTSiFe6bSN3yRQ=
github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU=
github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:4zE60yDWlfCDtmqnyP5o1k1K0oyhNn3Tvqo6F93/+RU=
github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200616182840-3baf1f590cb1 h1:7fg/+PpoT/ecAnA/uvccRQQk7+JZ7gSOgcIHHqqZtIM=
github.com/decred/dcrd/blockchain/stake/v3 v3.0.0-20200616182840-3baf1f590cb1/go.mod h1:1e94ovQXEcOjIn7BRzkXpswA7pWQXqB2el5l0w0Srf8=
github.com/decred/dcrd/blockchain/standalone v1.1.0 h1:yclvVGEY09Gf8A4GSAo+NCtL1dW2TYJ4OKp4+g0ICI0=
github.com/decred/dcrd/blockchain/standalone v1.1.0/go.mod h1:6K8ZgzlWM1Kz2TwXbrtiAvfvIwfAmlzrtpA7CVPCUPE=
github.com/decred/dcrd/blockchain/v3 v3.0.0-20200311044114-143c1884e4c8 h1:I3psccIeKb9eld+TNd69SgUOy6940uflH/J3aLM2ctU=
github.com/decred/dcrd/blockchain/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:R9rIXU8kEJVC9Z4LAlh9bo9hiT3a+ihys3mCrz4PVao=
github.com/decred/dcrd/blockchain/v3 v3.0.0-20200608124004-b2f67c2dc475 h1:4VxMHgkwn9YTglLQyp7fvuP2/TWqBrvonjaOJComPIs=
github.com/decred/dcrd/blockchain/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:Jh6HF5q9YQHjV+0PHcwCWL7NCYj0LFyjnKlzPFO8/Zc=
github.com/decred/dcrd/certgen v1.1.0 h1:lAPE2OLYdYeXDCaji/+KC53j7/s7wF7RVGeQbXK//XA=
github.com/decred/dcrd/certgen v1.1.0/go.mod h1:ivkPLChfjdAgFh7ZQOtl6kJRqVkfrCq67dlq3AbZBQE=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2 h1:rt5Vlq/jM3ZawwiacWjPa+smINyLRN07EO0cNBV6DGU=
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
@ -39,10 +42,11 @@ github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200214194519-928737b3e580/go.mod h1:
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215015031-3283587e6add/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8=
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8=
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8=
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:v4oyBPQ/ZstYCV7+B0y6HogFByW76xTjr+72fOm66Y8=
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:OHbKBa6UZZOXCU1Y8f9Ta3O+GShto7nB1O0nuEutKq4=
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200616182840-3baf1f590cb1 h1:IfBqlTemNgX3Yax/lBBQSxDiVin1IitIXY6zegtXMps=
github.com/decred/dcrd/chaincfg/v3 v3.0.0-20200616182840-3baf1f590cb1/go.mod h1:OHbKBa6UZZOXCU1Y8f9Ta3O+GShto7nB1O0nuEutKq4=
github.com/decred/dcrd/connmgr/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:mvIMJsrOEngogmVrq+tdbPIZchHVgGnVBZeNwj1cW6E=
github.com/decred/dcrd/connmgr/v3 v3.0.0-20200608124004-b2f67c2dc475 h1:jOEkyTB8KqKrYRNS4PZUs13tf7UbiWspsNYix+eKbis=
github.com/decred/dcrd/connmgr/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:UWFfZ1MbPzBgNA3bRCkF0woOlXkv1EIFEkwD+mdUW5Y=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/crypto/ripemd160 v1.0.0 h1:MciTnR4NfBqDFRFjFkrn8WPLP4Vo7t6ww6ghfn6wcXQ=
@ -59,8 +63,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200214194519-928737b3e580/go.
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200215015031-3283587e6add/go.mod h1:Ej0/gOv8NpFfaczyXGndw7eRMJFVhmY2faSeyxztSUw=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:Ej0/gOv8NpFfaczyXGndw7eRMJFVhmY2faSeyxztSUw=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:Ej0/gOv8NpFfaczyXGndw7eRMJFVhmY2faSeyxztSUw=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200421213827-b60c60ffe98b h1:5/9ZtxOJ2scrLjNte19jKnN2n43WNj01+RGPp/j8L6Q=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200421213827-b60c60ffe98b/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200608124004-b2f67c2dc475 h1:N4p2A5SPMXm97Vc8LazxchwudeE5GDs6S0WxwcrRmog=
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8=
github.com/decred/dcrd/dcrjson/v3 v3.0.1 h1:b9cpplNJG+nutE2jS8K/BtSGIJihEQHhFjFAsvJF/iI=
github.com/decred/dcrd/dcrjson/v3 v3.0.1/go.mod h1:fnTHev/ABGp8IxFudDhjGi9ghLiXRff1qZz/wvq12Mg=
github.com/decred/dcrd/dcrutil/v2 v2.0.1 h1:aL+c7o7Q66HV1gIif+XkNYo9DeorN3l01Vns8mh0mqs=
@ -68,13 +72,13 @@ github.com/decred/dcrd/dcrutil/v2 v2.0.1/go.mod h1:JdEgF6eh0TTohPeiqDxqDSikTSvAc
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215015031-3283587e6add/go.mod h1:CibwaHcCfz1sedFseBYKt+1hSbqnWC4Oe95DM8dAOlA=
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:48ZLpNNrRIYfqYxmvzMgOZrnTZUU3aTJveWtamCkOxo=
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:jFxEd2LWDLvrWlrIiyx9ZGTQjvoFHZ0OVfBdyIX7jSw=
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200311044114-143c1884e4c8/go.mod h1:/CDBC1SOXKrmihavgXviaTr6eVZSAWKQqEbRmacDxgg=
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:WyoYp6FRgNAQL33CdcpvSnKcujH8wMzIRBSMCg64Egw=
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200616182840-3baf1f590cb1 h1:uiMnZy+YBJt0ZzTXHk0TZgIPTlOGgrIYrRoFET0kUwg=
github.com/decred/dcrd/dcrutil/v3 v3.0.0-20200616182840-3baf1f590cb1/go.mod h1:WyoYp6FRgNAQL33CdcpvSnKcujH8wMzIRBSMCg64Egw=
github.com/decred/dcrd/gcs/v2 v2.0.0/go.mod h1:3XjKcrtvB+r2ezhIsyNCLk6dRnXRJVyYmsd1P3SkU3o=
github.com/decred/dcrd/gcs/v2 v2.0.2-0.20200312171759-0a8cc56a776e h1:tBOk2P8F9JyRUSp0iRTs4nYEBro1FKBDIbg/UualLWw=
github.com/decred/dcrd/gcs/v2 v2.0.2-0.20200312171759-0a8cc56a776e/go.mod h1:JJGd1m0DrFgV4J2J8HKNB9YVkM06ewQHT6iINis39Z4=
github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200421213827-b60c60ffe98b/go.mod h1:qKN0WzeSEEZ4fUBsTwKzOPkLP7GqSM6jBUm5Auq9mrM=
github.com/decred/dcrd/gcs/v2 v2.0.2-0.20200608124004-b2f67c2dc475 h1:5Qd0LWsOKVAmZyFRI/enaIsPpD8NsIPSxYwJ7uywMqo=
github.com/decred/dcrd/gcs/v2 v2.0.2-0.20200608124004-b2f67c2dc475/go.mod h1:JJGd1m0DrFgV4J2J8HKNB9YVkM06ewQHT6iINis39Z4=
github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:OQQKlU+hzvOHVZfUJq1iqQ5IfyycGaSPm1lmqMOyMaQ=
github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200616182840-3baf1f590cb1 h1:kKZVUO9qWWWq5mJuKy9jiBPUMT6lFyY9MX1xOIaQn5c=
github.com/decred/dcrd/hdkeychain/v3 v3.0.0-20200616182840-3baf1f590cb1/go.mod h1:OQQKlU+hzvOHVZfUJq1iqQ5IfyycGaSPm1lmqMOyMaQ=
github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.0/go.mod h1:c5S+PtQWNIA2aUakgrLhrlopkMadcOv51dWhCEdo49c=
@ -83,11 +87,12 @@ github.com/decred/dcrd/rpc/jsonrpc/types/v2 v2.0.1-0.20200527025017-6fc98347d984
github.com/decred/dcrd/txscript/v2 v2.1.0/go.mod h1:XaJAVrZU4NWRx4UEzTiDAs86op1m8GRJLz24SDBKOi0=
github.com/decred/dcrd/txscript/v3 v3.0.0-20200215023918-6247af01d5e3/go.mod h1:ATMA8K0SOo+M9Wdbr6dMnAd8qICJi6pXjGLlKsJc99E=
github.com/decred/dcrd/txscript/v3 v3.0.0-20200215031403-6b2ce76f0986/go.mod h1:KsDS7McU1yFaCYR9LCIwk6YnE15YN3wJUDxhKdFqlsc=
github.com/decred/dcrd/txscript/v3 v3.0.0-20200421213827-b60c60ffe98b/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0=
github.com/decred/dcrd/txscript/v3 v3.0.0-20200608124004-b2f67c2dc475/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0=
github.com/decred/dcrd/txscript/v3 v3.0.0-20200616182840-3baf1f590cb1 h1:ZvPldwEQn42X4Wr2YkaOLNOkKMgtW8hS1mlRHnYfcQc=
github.com/decred/dcrd/txscript/v3 v3.0.0-20200616182840-3baf1f590cb1/go.mod h1:vrm3R/AesmA9slTf0rFcwhD0SduAJAWxocyaWVi8dM0=
github.com/decred/dcrd/wire v1.3.0 h1:X76I2/a8esUmxXmFpJpAvXEi014IA4twgwcOBeIS8lE=
github.com/decred/dcrd/wire v1.3.0/go.mod h1:fnKGlUY2IBuqnpxx5dYRU5Oiq392OBqAuVjRVSkIoXM=
github.com/decred/go-socks v1.1.0 h1:dnENcc0KIqQo3HSXdgboXAHgqsCIutkqq6ntQjYtm2U=
github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0=
github.com/decred/slog v1.0.0 h1:Dl+W8O6/JH6n2xIFN2p3DNjCmjYwvrXsjlSJTQQ4MhE=
github.com/decred/slog v1.0.0/go.mod h1:zR98rEZHSnbZ4WHZtO0iqmSZjDLKhkXfrPTZQKtAonQ=
@ -121,10 +126,14 @@ github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYb
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7 h1:Ug59miTxVKVg5Oi2S5uHlKOIV5jBx4Hb2u0jIxxDaSs=
github.com/jessevdk/go-flags v1.4.1-0.20200711081900-c17162fe8fd7/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/bitset v1.0.0 h1:Ws0PXV3PwXqWK2n7Vz6idCdrV/9OrBXgHEJi27ZB9Dw=
github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
@ -157,11 +166,11 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -201,9 +210,11 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -15,7 +15,7 @@ import (
)
const (
requiredDcrdVersion = "6.1.1"
requiredDcrdVersion = "6.1.2"
)
// These error codes are defined in dcrd/dcrjson. They are copied here so we

View File

@ -164,3 +164,24 @@ func (c *WalletRPC) GetBestBlockHeight() (int64, error) {
}
return block.Height, nil
}
func (c *WalletRPC) TicketInfo() (map[string]*wallettypes.TicketInfoResult, error) {
var result []*wallettypes.TicketInfoResult
err := c.Call(c.ctx, "ticketinfo", &result)
if err != nil {
return nil, err
}
// For easier access later on, store the tickets in a map using their hash
// as the key.
tickets := make(map[string]*wallettypes.TicketInfoResult, len(result))
for _, t := range result {
tickets[t.Hash] = t
}
return tickets, err
}
func (c *WalletRPC) RescanFrom(fromHeight int64) error {
return c.Call(c.ctx, "rescanwallet", nil, fromHeight)
}

View File

@ -131,7 +131,8 @@ func feeAddress(c *gin.Context) {
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to update fee expiry (ticketHash=%s): %v",
funcName, ticket.Hash, err)
sendError(errInternalError, c)
return
}

View File

@ -13,8 +13,9 @@ import (
type vspStats struct {
PubKey string
TotalTickets int
FeeConfirmedTickets int
Voting int64
Voted int64
Revoked int64
VSPFee float64
Network string
UpdateTime string
@ -35,7 +36,7 @@ func getVSPStats() *vspStats {
}
func updateVSPStats(db *database.VspDatabase, cfg Config) error {
total, feeConfirmed, err := db.CountTickets()
voting, voted, revoked, err := db.CountTickets()
if err != nil {
return err
}
@ -45,8 +46,9 @@ func updateVSPStats(db *database.VspDatabase, cfg Config) error {
stats = &vspStats{
PubKey: base64.StdEncoding.EncodeToString(signPubKey),
TotalTickets: total,
FeeConfirmedTickets: feeConfirmed,
Voting: voting,
Voted: voted,
Revoked: revoked,
VSPFee: cfg.VSPFee,
Network: cfg.NetParams.Name,
UpdateTime: time.Now().Format("Mon Jan _2 15:04:05 2006"),

View File

@ -212,7 +212,8 @@ findAddress:
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: InsertTicket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx (ticketHash=%s): %v",
funcName, ticket.Hash, err)
sendError(errInternalError, c)
return
}
@ -230,7 +231,8 @@ findAddress:
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx error (ticketHash=%s): %v",
funcName, ticket.Hash, err)
}
sendErrorWithMsg("could not broadcast fee transaction", errInvalidFeeTx, c)
@ -241,7 +243,8 @@ findAddress:
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket failed (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to set fee tx as broadcast (ticketHash=%s): %v",
funcName, ticket.Hash, err)
sendError(errInternalError, c)
return
}

View File

@ -51,7 +51,8 @@ func setVoteChoices(c *gin.Context) {
ticket.VoteChoices = voteChoices
err = db.UpdateTicket(ticket)
if err != nil {
log.Errorf("%s: db.UpdateTicket error (ticketHash=%s): %v", funcName, ticket.Hash, err)
log.Errorf("%s: db.UpdateTicket error, failed to set vote choices (ticketHash=%s): %v",
funcName, ticket.Hash, err)
sendError(errInternalError, c)
return
}

View File

@ -148,6 +148,8 @@
<td>{{ .FeeTxStatus }}</td>
</tr>
<tr>
<th>Ticket Outcome</th>
<td>{{ .Outcome }}</td>
</tr>
</table>
{{ end }}

View File

@ -3,13 +3,18 @@
<div class="row vsp-stats">
<div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Total tickets</div>
<div class="stat-value" id="vsp-hash-rate">{{ .TotalTickets }}</div>
<div class="stat-title">Live tickets</div>
<div class="stat-value">{{ .Voting }}</div>
</div>
<div class="col-6 col-sm-4 col-lg-2 py-3">
<div class="stat-title">Fee confirmed tickets</div>
<div class="stat-value" id="last-work-height">{{ .FeeConfirmedTickets }}</div>
<div class="stat-title">Voted tickets</div>
<div class="stat-value">{{ .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 }}</div>
</div>
<div class="col-6 col-sm-4 col-lg-2 py-3">

View File

@ -8,6 +8,9 @@ type vspInfoResponse struct {
VspClosed bool `json:"vspclosed"`
Network string `json:"network"`
VspdVersion string `json:"vspdversion"`
Voting int64 `json:"voting"`
Voted int64 `json:"voted"`
Revoked int64 `json:"revoked"`
}
type FeeAddressRequest struct {

View File

@ -9,6 +9,7 @@ import (
// vspInfo is the handler for "GET /api/v3/vspinfo".
func vspInfo(c *gin.Context) {
cachedStats := getVSPStats()
sendJSONResponse(vspInfoResponse{
APIVersions: []int64{3},
Timestamp: time.Now().Unix(),
@ -17,5 +18,8 @@ func vspInfo(c *gin.Context) {
Network: cfg.NetParams.Name,
VspClosed: cfg.VspClosed,
VspdVersion: version.String(),
Voting: cachedStats.Voting,
Voted: cachedStats.Voted,
Revoked: cachedStats.Revoked,
}, c)
}