diff --git a/database/database.go b/database/database.go index 1e8ee1e..5e52bd0 100644 --- a/database/database.go +++ b/database/database.go @@ -17,6 +17,8 @@ import ( "sync" "time" + "github.com/decred/dcrd/chaincfg/v3" + "github.com/decred/vspd/rpc" bolt "go.etcd.io/bbolt" ) @@ -414,3 +416,42 @@ func (vdb *VspDatabase) BackupDB(w http.ResponseWriter) error { return err } + +// CheckIntegrity will ensure that all data in the database is present and up to +// date. +func (vdb *VspDatabase) CheckIntegrity(ctx context.Context, params *chaincfg.Params, dcrd rpc.DcrdConnect) error { + + // Ensure all confirmed tickets have a purchase height. + // This is necessary because of an old bug which, in some circumstances, + // would prevent purchase height from being stored. + + missing, err := vdb.GetMissingPurchaseHeight() + if err != nil { + return fmt.Errorf("GetMissingPurchaseHeight error: %w", err) + } + + if len(missing) == 0 { + return nil + } + + dcrdClient, err := dcrd.Client(ctx, params) + if err != nil { + return err + } + + for _, ticket := range missing { + tktTx, err := dcrdClient.GetRawTransaction(ticket.Hash) + if err != nil { + return fmt.Errorf("dcrd.GetRawTransaction error: %w", err) + } + ticket.PurchaseHeight = tktTx.BlockHeight + err = vdb.UpdateTicket(ticket) + if err != nil { + return fmt.Errorf("UpdateTicket error: %w", err) + } + } + + log.Infof("Added missing purchase height to %d tickets", len(missing)) + + return nil +} diff --git a/database/ticket.go b/database/ticket.go index 0473b21..35541d2 100644 --- a/database/ticket.go +++ b/database/ticket.go @@ -353,6 +353,14 @@ func (vdb *VspDatabase) GetVotableTickets() ([]Ticket, error) { }) } +// GetMissingPurchaseHeight returns tickets which are confirmed but do not have +// a purchase height. +func (vdb *VspDatabase) GetMissingPurchaseHeight() ([]Ticket, error) { + return vdb.filterTickets(func(t *bolt.Bucket) bool { + return bytesToBool(t.Get(confirmedK)) && bytesToInt64(t.Get(purchaseHeightK)) == 0 + }) +} + // filterTickets accepts a filter function and returns all tickets from the // database which match the filter. // diff --git a/vspd.go b/vspd.go index d36e44c..3eba344 100644 --- a/vspd.go +++ b/vspd.go @@ -60,7 +60,7 @@ func run(ctx context.Context) error { "accepting new tickets.") } - // Waitgroup for services to signal when they have shutdown cleanly. + // WaitGroup for services to signal when they have shutdown cleanly. var shutdownWg sync.WaitGroup defer log.Info("Shutdown complete") @@ -83,6 +83,13 @@ func run(ctx context.Context) error { wallets := rpc.SetupWallet(cfg.walletUsers, cfg.walletPasswords, cfg.walletHosts, cfg.walletCerts) defer wallets.Close() + // Ensure all data in database is present and up-to-date. + err = db.CheckIntegrity(ctx, cfg.netParams.Params, dcrd) + if err != nil { + // vspd should still start if this fails, so just log an error. + log.Errorf("Could not check database integrity: %v", err) + } + // Create and start webapi server. apiCfg := webapi.Config{ VSPFee: cfg.VSPFee,