This allows RPC clients to be reused more easily in the case that the caller of findSpentTickets already has a connected dcrd RPC client.
135 lines
3.6 KiB
Go
135 lines
3.6 KiB
Go
// 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.
|
|
|
|
package vspd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/decred/vspd/database"
|
|
)
|
|
|
|
// checkDatabaseIntegrity starts the process of ensuring that all data expected
|
|
// to be in the database is present and up to date.
|
|
func (v *Vspd) checkDatabaseIntegrity(ctx context.Context) error {
|
|
err := v.checkPurchaseHeights()
|
|
if err != nil {
|
|
return fmt.Errorf("checkPurchaseHeights error: %w", err)
|
|
}
|
|
|
|
err = v.checkRevoked(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("checkRevoked error: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checkPurchaseHeights ensures a purchase height is recorded for all confirmed
|
|
// tickets in the database. This is necessary because of an old bug which, in
|
|
// some circumstances, would prevent purchase height from being stored.
|
|
func (v *Vspd) checkPurchaseHeights() error {
|
|
missing, err := v.db.GetMissingPurchaseHeight()
|
|
if err != nil {
|
|
// Cannot proceed if this fails, return.
|
|
return fmt.Errorf("db.GetMissingPurchaseHeight error: %w", err)
|
|
}
|
|
|
|
if len(missing) == 0 {
|
|
// Nothing to do, return.
|
|
return nil
|
|
}
|
|
|
|
v.log.Warnf("%d tickets are missing purchase heights", len(missing))
|
|
|
|
dcrdClient, _, err := v.dcrd.Client()
|
|
if err != nil {
|
|
// Cannot proceed if this fails, return.
|
|
return err
|
|
}
|
|
|
|
fixed := 0
|
|
for _, ticket := range missing {
|
|
tktTx, err := dcrdClient.GetRawTransaction(ticket.Hash)
|
|
if err != nil {
|
|
// Just log and continue, other tickets might succeed.
|
|
v.log.Errorf("Could not get raw tx for ticket %s: %v", ticket.Hash, err)
|
|
continue
|
|
}
|
|
ticket.PurchaseHeight = tktTx.BlockHeight
|
|
err = v.db.UpdateTicket(ticket)
|
|
if err != nil {
|
|
// Just log and continue, other tickets might succeed.
|
|
v.log.Errorf("Could not insert purchase height for ticket %s: %v", ticket.Hash, err)
|
|
continue
|
|
}
|
|
fixed++
|
|
}
|
|
|
|
v.log.Infof("Added missing purchase height to %d tickets", fixed)
|
|
return nil
|
|
}
|
|
|
|
// checkRevoked ensures that any tickets in the database with outcome set to
|
|
// revoked are updated to either expired or missed.
|
|
func (v *Vspd) checkRevoked(ctx context.Context) error {
|
|
revoked, err := v.db.GetRevokedTickets()
|
|
if err != nil {
|
|
return fmt.Errorf("db.GetRevoked error: %w", err)
|
|
}
|
|
|
|
if len(revoked) == 0 {
|
|
// Nothing to do, return.
|
|
return nil
|
|
}
|
|
|
|
v.log.Warnf("Updating %s in revoked status, this may take a while...",
|
|
pluralize(len(revoked), "ticket"))
|
|
|
|
dcrdClient, _, err := v.dcrd.Client()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Search for the transactions which spend these tickets, starting at the
|
|
// earliest height one of them matured.
|
|
startHeight := revoked.EarliestPurchaseHeight() + int64(v.network.TicketMaturity)
|
|
|
|
spent, _, err := v.findSpentTickets(ctx, dcrdClient, revoked, startHeight)
|
|
if err != nil {
|
|
return fmt.Errorf("findSpentTickets error: %w", err)
|
|
}
|
|
|
|
fixedMissed := 0
|
|
fixedExpired := 0
|
|
|
|
// Update database with correct voted status.
|
|
for hash, spentTicket := range spent {
|
|
switch {
|
|
case spentTicket.voted():
|
|
v.log.Errorf("Ticket voted but was recorded as revoked. Please contact "+
|
|
"developers so this can be investigated (ticketHash=%s)", hash)
|
|
continue
|
|
case spentTicket.missed():
|
|
spentTicket.dbTicket.Outcome = database.Missed
|
|
fixedMissed++
|
|
default:
|
|
spentTicket.dbTicket.Outcome = database.Expired
|
|
fixedExpired++
|
|
}
|
|
|
|
err = v.db.UpdateTicket(spentTicket.dbTicket)
|
|
if err != nil {
|
|
v.log.Errorf("Could not update status of ticket %s: %v", hash, err)
|
|
}
|
|
}
|
|
|
|
v.log.Infof("%s updated (%d missed, %d expired)",
|
|
pluralize(fixedExpired+fixedMissed, "revoked ticket"),
|
|
fixedMissed, fixedExpired)
|
|
|
|
return nil
|
|
}
|