diff --git a/background/background.go b/background/background.go index 9cee958..6628fa1 100644 --- a/background/background.go +++ b/background/background.go @@ -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 + } + } + } + + } +} diff --git a/database/ticket.go b/database/ticket.go index c6aa5a6..d71fdc3 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -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. // diff --git a/docs/api.md b/docs/api.md index ce65941..cba2f41 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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 } ``` diff --git a/go.mod b/go.mod index b53e923..df5f29f 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index f1b5c3f..22498fe 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/rpc/dcrd.go b/rpc/dcrd.go index eb62f02..6c7e2c3 100644 --- a/rpc/dcrd.go +++ b/rpc/dcrd.go @@ -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 diff --git a/rpc/dcrwallet.go b/rpc/dcrwallet.go index fd34407..08f463c 100644 --- a/rpc/dcrwallet.go +++ b/rpc/dcrwallet.go @@ -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) +} diff --git a/webapi/getfeeaddress.go b/webapi/getfeeaddress.go index 77c2d54..ab33880 100644 --- a/webapi/getfeeaddress.go +++ b/webapi/getfeeaddress.go @@ -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 } diff --git a/webapi/homepage.go b/webapi/homepage.go index fd9a7da..78ddfcb 100644 --- a/webapi/homepage.go +++ b/webapi/homepage.go @@ -12,16 +12,17 @@ import ( ) type vspStats struct { - PubKey string - TotalTickets int - FeeConfirmedTickets int - VSPFee float64 - Network string - UpdateTime string - SupportEmail string - VspClosed bool - Debug bool - Designation string + PubKey string + Voting int64 + Voted int64 + Revoked int64 + VSPFee float64 + Network string + UpdateTime string + SupportEmail string + VspClosed bool + Debug bool + Designation string } var statsMtx sync.RWMutex @@ -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 } @@ -44,16 +45,17 @@ func updateVSPStats(db *database.VspDatabase, cfg Config) error { defer statsMtx.Unlock() stats = &vspStats{ - PubKey: base64.StdEncoding.EncodeToString(signPubKey), - TotalTickets: total, - FeeConfirmedTickets: feeConfirmed, - VSPFee: cfg.VSPFee, - Network: cfg.NetParams.Name, - UpdateTime: time.Now().Format("Mon Jan _2 15:04:05 2006"), - SupportEmail: cfg.SupportEmail, - VspClosed: cfg.VspClosed, - Debug: cfg.Debug, - Designation: cfg.Designation, + PubKey: base64.StdEncoding.EncodeToString(signPubKey), + Voting: voting, + Voted: voted, + Revoked: revoked, + VSPFee: cfg.VSPFee, + Network: cfg.NetParams.Name, + UpdateTime: time.Now().Format("Mon Jan _2 15:04:05 2006"), + SupportEmail: cfg.SupportEmail, + VspClosed: cfg.VspClosed, + Debug: cfg.Debug, + Designation: cfg.Designation, } return nil diff --git a/webapi/payfee.go b/webapi/payfee.go index dffa190..e517583 100644 --- a/webapi/payfee.go +++ b/webapi/payfee.go @@ -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 } diff --git a/webapi/setvotechoices.go b/webapi/setvotechoices.go index 1b94ae3..5eb648f 100644 --- a/webapi/setvotechoices.go +++ b/webapi/setvotechoices.go @@ -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 } diff --git a/webapi/templates/admin.html b/webapi/templates/admin.html index 54bf710..8c0b80e 100644 --- a/webapi/templates/admin.html +++ b/webapi/templates/admin.html @@ -148,6 +148,8 @@ {{ .FeeTxStatus }} + Ticket Outcome + {{ .Outcome }} {{ end }} diff --git a/webapi/templates/vsp-stats.html b/webapi/templates/vsp-stats.html index 3dee4c6..9d7584c 100644 --- a/webapi/templates/vsp-stats.html +++ b/webapi/templates/vsp-stats.html @@ -3,13 +3,18 @@
-
Total tickets
-
{{ .TotalTickets }}
+
Live tickets
+
{{ .Voting }}
-
Fee confirmed tickets
-
{{ .FeeConfirmedTickets }}
+
Voted tickets
+
{{ .Voted }}
+
+ +
+
Revoked tickets
+
{{ .Revoked }}
diff --git a/webapi/types.go b/webapi/types.go index fee243b..400fe0d 100644 --- a/webapi/types.go +++ b/webapi/types.go @@ -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 { diff --git a/webapi/vspinfo.go b/webapi/vspinfo.go index 844717b..29f567a 100644 --- a/webapi/vspinfo.go +++ b/webapi/vspinfo.go @@ -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) }