From 1b8c661be036d8d8325ec217ad48d85951892d3e Mon Sep 17 00:00:00 2001 From: C Jepson Date: Fri, 6 May 2016 09:11:52 -0400 Subject: [PATCH] Fix resyncing the ticket database after unexpected shutdown (#157) The ticket database would restore from the genesis block if it failed the check to see if the tip matched with the database best block. This corrects the resyncing such that if it finds a matching block for the ticket database top block in the database, it instead resyncs from that block instead of the genesis block. --- blockchain/stake/ticketdb.go | 98 +++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/blockchain/stake/ticketdb.go b/blockchain/stake/ticketdb.go index 1b6133ef8f..714d7278c7 100644 --- a/blockchain/stake/ticketdb.go +++ b/blockchain/stake/ticketdb.go @@ -1528,17 +1528,13 @@ func (tmdb *TicketDB) unpushMatureTicketsAtHeight(height int64) (SStxMemMap, return tempTickets, nil } -// RemoveBlockToHeight is the main work horse for removing blocks from the +// removeBlockToHeight is the main work horse for removing blocks from the // TicketDB. This function will remove all blocks until reaching the block at // height. // -// This function is safe for concurrent access. -func (tmdb *TicketDB) RemoveBlockToHeight(height int64) (map[int64]SStxMemMap, +// This function is unsafe for concurrent access. +func (tmdb *TicketDB) removeBlockToHeight(height int64) (map[int64]SStxMemMap, map[int64]SStxMemMap, map[int64]SStxMemMap, error) { - - tmdb.mtx.Lock() - defer tmdb.mtx.Unlock() - if height < tmdb.StakeEnabledHeight { return nil, nil, nil, fmt.Errorf("TicketDB Error: tried to remove " + "blocks to before minimum maturation height!") @@ -1588,6 +1584,17 @@ func (tmdb *TicketDB) RemoveBlockToHeight(height int64) (map[int64]SStxMemMap, return unmaturedTicketMap, unrevokedTicketMap, unspentTicketMap, nil } +// RemoveBlockToHeight is the exported version of removeBlockToHeight. +// +// This function is safe for concurrent access. +func (tmdb *TicketDB) RemoveBlockToHeight(height int64) (map[int64]SStxMemMap, + map[int64]SStxMemMap, map[int64]SStxMemMap, error) { + tmdb.mtx.Lock() + defer tmdb.mtx.Unlock() + + return tmdb.removeBlockToHeight(height) +} + // rescanTicketDB is the internal function which implements the public // RescanTicketDB. See the comment for RescanTicketDB for more details. // @@ -1603,6 +1610,83 @@ func (tmdb *TicketDB) rescanTicketDB() error { return nil } + // Find out what block the TMDB was previously synced to. + curTmdbHeight := tmdb.getTopBlock() + + // If we're synced to some old height that the database + // already has, begin resyncing from this height instead + // of the genesis block. + if curTmdbHeight > 0 { + // The spent ticket map doesn't store the hashes of the + // blocks, so instead review the listed votes. The votes + // are dependent on the block hash of the previous block, + // so if they line up we know the previous block before + // curTmdbHeight is correct. + spentBl, _ := tmdb.maps.spentTicketMap[curTmdbHeight] + spendHashes := make(map[chainhash.Hash]struct{}) + for _, td := range spentBl { + spendHashes[td.SpendHash] = struct{}{} + } + + h, err := tmdb.database.FetchBlockShaByHeight(curTmdbHeight) + if err != nil { + return err + } + + blCur, err := tmdb.database.FetchBlockBySha(h) + if err != nil { + return err + } + + failedToFindBlock := false + for _, stx := range blCur.STransactions() { + if is, _ := IsSSGen(stx); is { + voteHash := stx.Sha() + _, ok := spendHashes[*voteHash] + if !ok { + failedToFindBlock = true + } + } + } + + // We found a matching block in the database at + // curTmdbHeight-1, so sync to it. + if !failedToFindBlock { + log.Infof("Found a previously good height %v in the old "+ + "stake database, attempting to sync to tip from it", + curTmdbHeight) + + // Remove the top block. + _, _, _, err = tmdb.removeBlockToHeight(curTmdbHeight - 1) + if err != nil { + return err + } + + // Reinsert the top block and sync to the best chain + // height. + for i := curTmdbHeight; i <= height; i++ { + h, err := tmdb.database.FetchBlockShaByHeight(i) + if err != nil { + return err + } + + bl, err := tmdb.database.FetchBlockBySha(h) + if err != nil { + return err + } + + _, _, _, err = tmdb.insertBlock(bl) + if err != nil { + return err + } + } + + return nil + } + } + + // We aren't able to resync from a previous height, so do the + // entire thing from the genesis block. var freshTms TicketMaps freshTms.ticketMap = make([]SStxMemMap, BucketsSize, BucketsSize) freshTms.spentTicketMap = make(map[int64]SStxMemMap)