Skip to content

Commit

Permalink
Fix resyncing the ticket database after unexpected shutdown (#157)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
cjepson authored and alexlyp committed May 6, 2016
1 parent 7701a7a commit 1b8c661
Showing 1 changed file with 91 additions and 7 deletions.
98 changes: 91 additions & 7 deletions blockchain/stake/ticketdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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!")
Expand Down Expand Up @@ -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.
//
Expand All @@ -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)
Expand Down

0 comments on commit 1b8c661

Please sign in to comment.