mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
multi: Migration for utxo set semantics reversal.
This adds code to migrate the database to the new schema required by the recent utxo set semantics reversal changes. It involves removing the existing utxo set, spend journal, ticket database, various indexes, resetting the best chain tip back to the genesis block, and finally performing a full reindex. The process can be interrupted at any point and future invocations will resume from the point it was interrupted. In addition, some new infrastructure to remove the ticket database and support for index versions is added to support the migration.
This commit is contained in:
parent
9c4569ae12
commit
e87549549a
@ -2165,14 +2165,20 @@ func New(config *Config) (*BlockChain, error) {
|
||||
}
|
||||
}
|
||||
|
||||
tip := b.bestChain.Tip()
|
||||
b.subsidyCache = NewSubsidyCache(tip.height, b.chainParams)
|
||||
b.subsidyCache = NewSubsidyCache(b.bestChain.Tip().height, b.chainParams)
|
||||
b.pruner = newChainPruner(&b)
|
||||
|
||||
// The version 5 database upgrade requires a full reindex. Perform, or
|
||||
// resume, the reindex as needed.
|
||||
if err := b.maybeFinishV5Upgrade(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("Blockchain database version info: chain: %d, compression: "+
|
||||
"%d, block index: %d", b.dbInfo.version, b.dbInfo.compVer,
|
||||
b.dbInfo.bidxVer)
|
||||
|
||||
tip := b.bestChain.Tip()
|
||||
log.Infof("Chain state: height %d, hash %v, total transactions %d, "+
|
||||
"work %v, stake version %v", tip.height, tip.hash,
|
||||
b.stateSnapshot.TotalTxns, tip.workSum, 0)
|
||||
|
||||
@ -1711,20 +1711,6 @@ func (b *BlockChain) initChainState() error {
|
||||
}
|
||||
b.bestChain.SetTip(tip)
|
||||
|
||||
// Ensure all ancestors of the current best chain tip are marked as
|
||||
// valid. This is necessary due to older software versions not marking
|
||||
// nodes before the final checkpoint as valid.
|
||||
//
|
||||
// Note that the nodes are not marked as modified here, so the database
|
||||
// is not updated unless the node is otherwise modified and written back
|
||||
// out a later point. Ultimately, the nodes should be updated in the
|
||||
// database accordingly as part of a database upgrade, however, since
|
||||
// the nodes are all in memory, they can be updated very quickly here
|
||||
// without requiring a database version bump.
|
||||
for node := tip; node != nil; node = node.parent {
|
||||
node.status |= statusValid
|
||||
}
|
||||
|
||||
log.Debugf("Block index loaded in %v", time.Since(bidxStart))
|
||||
|
||||
// Exception for version 1 blockchains: skip loading the stake
|
||||
|
||||
@ -25,6 +25,9 @@ const (
|
||||
// addrIndexName is the human-readable name for the index.
|
||||
addrIndexName = "address index"
|
||||
|
||||
// addrIndexVersion is the current version of the address index.
|
||||
addrIndexVersion = 2
|
||||
|
||||
// level0MaxEntries is the maximum number of transactions that are
|
||||
// stored in level 0 of an address index entry. Subsequent levels store
|
||||
// 2^n * level0MaxEntries entries, or in words, double the maximum of
|
||||
@ -652,6 +655,13 @@ func (idx *AddrIndex) Name() string {
|
||||
return addrIndexName
|
||||
}
|
||||
|
||||
// Version returns the current version of the index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AddrIndex) Version() uint32 {
|
||||
return addrIndexVersion
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs
|
||||
// to be created for the first time. It creates the bucket for the address
|
||||
// index.
|
||||
|
||||
@ -22,6 +22,9 @@ import (
|
||||
const (
|
||||
// cfIndexName is the human-readable name for the index.
|
||||
cfIndexName = "committed filter index"
|
||||
|
||||
// cfIndexVersion is the current version of the committed filter index.
|
||||
cfIndexVersion = 2
|
||||
)
|
||||
|
||||
// Committed filters come in two flavors: basic and extended. They are
|
||||
@ -126,6 +129,13 @@ func (idx *CFIndex) Name() string {
|
||||
return cfIndexName
|
||||
}
|
||||
|
||||
// Version returns the current version of the index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *CFIndex) Version() uint32 {
|
||||
return cfIndexVersion
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs to
|
||||
// be created for the first time. It creates buckets for the two hash-based cf
|
||||
// indexes (simple, extended).
|
||||
|
||||
@ -42,6 +42,9 @@ type Indexer interface {
|
||||
// Name returns the human-readable name of the index.
|
||||
Name() string
|
||||
|
||||
// Return the current version of the index.
|
||||
Version() uint32
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs
|
||||
// to be created for the first time.
|
||||
Create(dbTx database.Tx) error
|
||||
|
||||
@ -16,10 +16,16 @@ import (
|
||||
"github.com/decred/dcrd/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
const (
|
||||
// existsAddressIndexName is the human-readable name for the index.
|
||||
existsAddressIndexName = "exists address index"
|
||||
|
||||
// existsAddrIndexVersion is the current version of the exists address
|
||||
// index.
|
||||
existsAddrIndexVersion = 2
|
||||
)
|
||||
|
||||
var (
|
||||
// existsAddrIndexKey is the key of the ever seen address index and
|
||||
// the db bucket used to house it.
|
||||
existsAddrIndexKey = []byte("existsaddridx")
|
||||
@ -99,6 +105,13 @@ func (idx *ExistsAddrIndex) Name() string {
|
||||
return existsAddressIndexName
|
||||
}
|
||||
|
||||
// Version returns the current version of the index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *ExistsAddrIndex) Version() uint32 {
|
||||
return existsAddrIndexVersion
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs
|
||||
// to be created for the first time. It creates the bucket for the address
|
||||
// index.
|
||||
|
||||
@ -26,8 +26,9 @@ var (
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The index manager tracks the current tip of each index by using a parent
|
||||
// bucket that contains an entry for index.
|
||||
// The index manager tracks the current tip and version of each index by using a
|
||||
// parent bucket that contains an entry for index and a separate entry for its
|
||||
// version.
|
||||
//
|
||||
// The serialized format for an index tip is:
|
||||
//
|
||||
@ -36,6 +37,13 @@ var (
|
||||
// Field Type Size
|
||||
// block hash chainhash.Hash chainhash.HashSize
|
||||
// block height uint32 4 bytes
|
||||
//
|
||||
// The serialized format for an index version is:
|
||||
//
|
||||
// [<version>]
|
||||
//
|
||||
// Field Type Size
|
||||
// index version uint32 4 bytes
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// dbPutIndexerTip uses an existing database transaction to update or add the
|
||||
@ -68,6 +76,47 @@ func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32,
|
||||
return &hash, height, nil
|
||||
}
|
||||
|
||||
// indexVersionKey returns the key for an index which houses the current version
|
||||
// of the index.
|
||||
func indexVersionKey(idxKey []byte) []byte {
|
||||
verKey := make([]byte, len(idxKey)+1)
|
||||
verKey[0] = 'v'
|
||||
copy(verKey[1:], idxKey)
|
||||
return verKey
|
||||
}
|
||||
|
||||
// dbPutIndexerVersion uses an existing database transaction to update the
|
||||
// version for the given index to the provided value.
|
||||
func dbPutIndexerVersion(dbTx database.Tx, idxKey []byte, version uint32) error {
|
||||
serialized := make([]byte, 4)
|
||||
byteOrder.PutUint32(serialized[0:4], version)
|
||||
|
||||
indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
|
||||
return indexesBucket.Put(indexVersionKey(idxKey), serialized)
|
||||
}
|
||||
|
||||
// dbFetchIndexerVersion uses an existing database transaction to retrieve the
|
||||
// version of the provided index. It will return one if the version has not
|
||||
// previously been stored.
|
||||
func dbFetchIndexerVersion(dbTx database.Tx, idxKey []byte) (uint32, error) {
|
||||
indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
|
||||
serialized := indexesBucket.Get(indexVersionKey(idxKey))
|
||||
if len(serialized) == 0 {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
if len(serialized) < 4 {
|
||||
return 0, database.Error{
|
||||
ErrorCode: database.ErrCorruption,
|
||||
Description: fmt.Sprintf("unexpected end of data for "+
|
||||
"index %q version", string(idxKey)),
|
||||
}
|
||||
}
|
||||
|
||||
version := byteOrder.Uint32(serialized)
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// dbIndexConnectBlock adds all of the index entries associated with the
|
||||
// given block using the provided indexer and updates the tip of the indexer
|
||||
// accordingly. An error will be returned if the current tip for the indexer is
|
||||
@ -208,32 +257,107 @@ func (m *Manager) maybeFinishDrops(interrupt <-chan struct{}) error {
|
||||
|
||||
// maybeCreateIndexes determines if each of the enabled indexes have already
|
||||
// been created and creates them if not.
|
||||
func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error {
|
||||
indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
|
||||
for _, indexer := range m.enabledIndexes {
|
||||
// Nothing to do if the index tip already exists.
|
||||
idxKey := indexer.Key()
|
||||
if indexesBucket.Get(idxKey) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// The tip for the index does not exist, so create it and
|
||||
// invoke the create callback for the index so it can perform
|
||||
// any one-time initialization it requires.
|
||||
if err := indexer.Create(dbTx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the tip for the index to values which represent an
|
||||
// uninitialized index (the genesis block hash and height).
|
||||
genesisBlockHash := m.params.GenesisBlock.BlockHash()
|
||||
err := dbPutIndexerTip(dbTx, idxKey, &genesisBlockHash, 0)
|
||||
func (m *Manager) maybeCreateIndexes() error {
|
||||
return m.db.Update(func(dbTx database.Tx) error {
|
||||
// Create the bucket for the current tips as needed.
|
||||
meta := dbTx.Metadata()
|
||||
indexesBucket, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, indexer := range m.enabledIndexes {
|
||||
// Nothing to do if the index tip already exists.
|
||||
idxKey := indexer.Key()
|
||||
if indexesBucket.Get(idxKey) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Store the index version.
|
||||
err := dbPutIndexerVersion(dbTx, idxKey, indexer.Version())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The tip for the index does not exist, so create it and
|
||||
// invoke the create callback for the index so it can perform
|
||||
// any one-time initialization it requires.
|
||||
if err := indexer.Create(dbTx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the tip for the index to values which represent an
|
||||
// uninitialized index (the genesis block hash and height).
|
||||
genesisBlockHash := m.params.GenesisBlock.BlockHash()
|
||||
err = dbPutIndexerTip(dbTx, idxKey, &genesisBlockHash, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// upgradeIndexes determines if each of the enabled indexes need to be upgraded
|
||||
// and drops them when they do.
|
||||
func (m *Manager) upgradeIndexes(interrupt <-chan struct{}) error {
|
||||
indexNeedsDrop := make([]bool, len(m.enabledIndexes))
|
||||
err := m.db.View(func(dbTx database.Tx) error {
|
||||
// None of the indexes needs to be updated if the index tips bucket
|
||||
// hasn't been created yet.
|
||||
indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
|
||||
if indexesBucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, indexer := range m.enabledIndexes {
|
||||
idxKey := indexer.Key()
|
||||
version, err := dbFetchIndexerVersion(dbTx, idxKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Upgrade is not needed if the index hasn't been created yet.
|
||||
if version < indexer.Version() {
|
||||
indexNeedsDrop[i] = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if interruptRequested(interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
|
||||
// Drop any of the enabled indexes that have bumped their version.
|
||||
for i, indexer := range m.enabledIndexes {
|
||||
if !indexNeedsDrop[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Infof("Dropping %s due to new version", indexer.Name())
|
||||
|
||||
switch d := indexer.(type) {
|
||||
case IndexDropper:
|
||||
err := d.DropIndex(m.db, interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
err := dropIndex(m.db, indexer.Key(), indexer.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the initial state for the indexes that were dropped as needed.
|
||||
return m.maybeCreateIndexes()
|
||||
}
|
||||
|
||||
// dbFetchBlockByHash uses an existing database transaction to retrieve the raw
|
||||
@ -270,17 +394,12 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
|
||||
}
|
||||
|
||||
// Create the initial state for the indexes as needed.
|
||||
err := m.db.Update(func(dbTx database.Tx) error {
|
||||
// Create the bucket for the current tips as needed.
|
||||
meta := dbTx.Metadata()
|
||||
_, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.maybeCreateIndexes(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.maybeCreateIndexes(dbTx)
|
||||
})
|
||||
if err != nil {
|
||||
// Upgrade the indexes as needed.
|
||||
if err := m.upgradeIndexes(interrupt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -295,6 +414,7 @@ func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{})
|
||||
// This is fairly unlikely, but it can happen if the chain is
|
||||
// reorganized while the index is disabled. This has to be done in
|
||||
// reverse order because later indexes can depend on earlier ones.
|
||||
var err error
|
||||
var cachedBlock *dcrutil.Block
|
||||
for i := len(m.enabledIndexes); i > 0; i-- {
|
||||
indexer := m.enabledIndexes[i-1]
|
||||
@ -734,6 +854,11 @@ func dropIndexMetadata(db database.DB, idxKey []byte, idxName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = indexesBucket.Delete(indexVersionKey(idxKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return indexesBucket.Delete(indexDropKey(idxKey))
|
||||
})
|
||||
}
|
||||
@ -776,8 +901,8 @@ func dropFlatIndex(db database.DB, idxKey []byte, idxName string, interrupt <-ch
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the index tip, index bucket, and in-progress drop flag now
|
||||
// that all index entries have been removed.
|
||||
// Remove the index tip, version, bucket, and in-progress drop flag now that
|
||||
// all index entries have been removed.
|
||||
err = dropIndexMetadata(db, idxKey, idxName)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -812,8 +937,9 @@ func dropIndex(db database.DB, idxKey []byte, idxName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the index tip, index bucket, and in-progress drop flag. Removing
|
||||
// the index bucket also recursively removes all values saved to the index.
|
||||
// Remove the index tip, version, bucket, and in-progress drop flag.
|
||||
// Removing the index bucket also recursively removes all values saved to
|
||||
// the index.
|
||||
err = dropIndexMetadata(db, idxKey, idxName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -20,6 +20,9 @@ const (
|
||||
// txIndexName is the human-readable name for the index.
|
||||
txIndexName = "transaction index"
|
||||
|
||||
// txIndexVersion is the current version of the transaction index.
|
||||
txIndexVersion = 2
|
||||
|
||||
// txEntrySize is the size of a transaction entry. It consists of 4
|
||||
// bytes block id + 4 bytes offset + 4 bytes length + 4 bytes block
|
||||
// index.
|
||||
@ -408,6 +411,13 @@ func (idx *TxIndex) Name() string {
|
||||
return txIndexName
|
||||
}
|
||||
|
||||
// Version returns the current version of the index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *TxIndex) Version() uint32 {
|
||||
return txIndexVersion
|
||||
}
|
||||
|
||||
// Create is invoked when the indexer manager determines the index needs
|
||||
// to be created for the first time. It creates the buckets for the hash-based
|
||||
// transaction index and the internal block ID indexes.
|
||||
@ -577,7 +587,7 @@ func DropTxIndex(db database.DB, interrupt <-chan struct{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the index tip, index bucket, and in-progress drop flag now
|
||||
// Remove the index tip, version, bucket, and in-progress drop flag now
|
||||
// that all index entries have been removed.
|
||||
err = dropIndexMetadata(db, txIndexKey, txIndexName)
|
||||
if err != nil {
|
||||
|
||||
@ -662,6 +662,32 @@ func DbLoadAllTickets(dbTx database.Tx, ticketBucket []byte) (*tickettreap.Immut
|
||||
return treap, nil
|
||||
}
|
||||
|
||||
// DbRemoveAllBuckets removes all buckets from the database.
|
||||
func DbRemoveAllBuckets(dbTx database.Tx) error {
|
||||
meta := dbTx.Metadata()
|
||||
err := meta.DeleteBucket(dbnamespace.StakeDbInfoBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = meta.DeleteBucket(dbnamespace.LiveTicketsBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = meta.DeleteBucket(dbnamespace.MissedTicketsBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = meta.DeleteBucket(dbnamespace.RevokedTicketsBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = meta.DeleteBucket(dbnamespace.StakeBlockUndoDataBucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return meta.DeleteBucket(dbnamespace.TicketsInBlockBucketName)
|
||||
}
|
||||
|
||||
// DbCreate initializes all the buckets required for the database and stores
|
||||
// the current database version information.
|
||||
func DbCreate(dbTx database.Tx) error {
|
||||
|
||||
@ -225,6 +225,19 @@ func genesisNode(params *chaincfg.Params) *Node {
|
||||
}
|
||||
}
|
||||
|
||||
// ResetDatabase resets the ticket database back to the genesis block.
|
||||
func ResetDatabase(dbTx database.Tx, params *chaincfg.Params) error {
|
||||
// Remove all of the database buckets.
|
||||
err := ticketdb.DbRemoveAllBuckets(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the database.
|
||||
_, err = InitDatabaseState(dbTx, params)
|
||||
return err
|
||||
}
|
||||
|
||||
// InitDatabaseState initializes the chain with the best state being the
|
||||
// genesis block.
|
||||
func InitDatabaseState(dbTx database.Tx, params *chaincfg.Params) (*Node, error) {
|
||||
|
||||
@ -509,6 +509,215 @@ func upgradeToVersion4(db database.DB, dbInfo *databaseInfo, interrupt <-chan st
|
||||
})
|
||||
}
|
||||
|
||||
// incrementalFlatDrop uses multiple database updates to remove key/value pairs
|
||||
// saved to a flag bucket.
|
||||
func incrementalFlatDrop(db database.DB, bucketKey []byte, humanName string, interrupt <-chan struct{}) error {
|
||||
const maxDeletions = 2000000
|
||||
var totalDeleted uint64
|
||||
for numDeleted := maxDeletions; numDeleted == maxDeletions; {
|
||||
numDeleted = 0
|
||||
err := db.Update(func(dbTx database.Tx) error {
|
||||
bucket := dbTx.Metadata().Bucket(bucketKey)
|
||||
cursor := bucket.Cursor()
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() &&
|
||||
numDeleted < maxDeletions {
|
||||
|
||||
if err := cursor.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
numDeleted++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if numDeleted > 0 {
|
||||
totalDeleted += uint64(numDeleted)
|
||||
log.Infof("Deleted %d keys (%d total) from %s", numDeleted,
|
||||
totalDeleted, humanName)
|
||||
}
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeToVersion5 upgrades a version 4 blockchain database to version 5.
|
||||
func upgradeToVersion5(db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInfo, interrupt <-chan struct{}) error {
|
||||
// Hardcoded bucket and key names so updates to the global values do not
|
||||
// affect old upgrades.
|
||||
utxoSetBucketName := []byte("utxoset")
|
||||
spendJournalBucketName := []byte("spendjournal")
|
||||
chainStateKeyName := []byte("chainstate")
|
||||
v5ReindexTipKeyName := []byte("v5reindextip")
|
||||
|
||||
log.Info("Clearing database utxoset and spend journal for upgrade...")
|
||||
start := time.Now()
|
||||
|
||||
// Clear the utxoset.
|
||||
err := incrementalFlatDrop(db, utxoSetBucketName, "utxoset", interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Cleared utxoset.")
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
|
||||
// Clear the spend journal.
|
||||
err = incrementalFlatDrop(db, spendJournalBucketName, "spend journal",
|
||||
interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Cleared spend journal.")
|
||||
|
||||
if interruptRequested(interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
|
||||
err = db.Update(func(dbTx database.Tx) error {
|
||||
// Reset the ticket database to the genesis block.
|
||||
log.Infof("Resetting the ticket database. This might take a while...")
|
||||
if err := stake.ResetDatabase(dbTx, chainParams); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch the stored best chain state from the database metadata.
|
||||
meta := dbTx.Metadata()
|
||||
serializedData := meta.Get(chainStateKeyName)
|
||||
best, err := deserializeBestChainState(serializedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the current best chain tip as the reindex target.
|
||||
if err := meta.Put(v5ReindexTipKeyName, best.hash[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset the state related to the best block to the genesis block.
|
||||
genesisBlock := chainParams.GenesisBlock
|
||||
numTxns := uint64(len(genesisBlock.Transactions))
|
||||
serializedData = serializeBestChainState(bestChainState{
|
||||
hash: genesisBlock.BlockHash(),
|
||||
height: 0,
|
||||
totalTxns: numTxns,
|
||||
totalSubsidy: 0,
|
||||
workSum: CalcWork(genesisBlock.Header.Bits),
|
||||
})
|
||||
return meta.Put(chainStateKeyName, serializedData)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
elapsed := time.Since(start).Round(time.Millisecond)
|
||||
log.Infof("Done upgrading database in %v.", elapsed)
|
||||
|
||||
// Update and persist the updated database versions.
|
||||
dbInfo.version = 5
|
||||
return db.Update(func(dbTx database.Tx) error {
|
||||
return dbPutDatabaseInfo(dbTx, dbInfo)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// maybeFinishV5Upgrade potentially reindexes the chain due to a version 5
|
||||
// database upgrade. It will resume previously uncompleted attempts.
|
||||
func (b *BlockChain) maybeFinishV5Upgrade() error {
|
||||
// Nothing to do if the database is not version 5.
|
||||
if b.dbInfo.version != 5 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hardcoded key name so updates to the global values do not affect old
|
||||
// upgrades.
|
||||
v5ReindexTipKeyName := []byte("v5reindextip")
|
||||
|
||||
// Finish the version 5 reindex as needed.
|
||||
var v5ReindexTipHash *chainhash.Hash
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
hash := dbTx.Metadata().Get(v5ReindexTipKeyName)
|
||||
if hash != nil {
|
||||
v5ReindexTipHash = new(chainhash.Hash)
|
||||
copy(v5ReindexTipHash[:], hash)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v5ReindexTipHash != nil {
|
||||
// Look up the final target tip to reindex to in the block index.
|
||||
targetTip := b.index.LookupNode(v5ReindexTipHash)
|
||||
if targetTip == nil {
|
||||
return AssertError(fmt.Sprintf("maybeFinishV5Upgrade: cannot find "+
|
||||
"chain tip %s in block index", v5ReindexTipHash))
|
||||
}
|
||||
|
||||
// Ensure all ancestors of the current best chain tip are marked as
|
||||
// valid. This is necessary due to older software versions not marking
|
||||
// nodes before the final checkpoint as valid.
|
||||
for node := targetTip; node != nil; node = node.parent {
|
||||
b.index.SetStatusFlags(node, statusValid)
|
||||
}
|
||||
if err := b.index.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable notifications during the reindex.
|
||||
ntfnCallback := b.notifications
|
||||
b.notifications = nil
|
||||
defer func() {
|
||||
b.notifications = ntfnCallback
|
||||
}()
|
||||
|
||||
tip := b.bestChain.Tip()
|
||||
for tip != targetTip {
|
||||
if interruptRequested(b.interrupt) {
|
||||
return errInterruptRequested
|
||||
}
|
||||
|
||||
// Limit to a reasonable number of blocks at a time.
|
||||
const maxReindexBlocks = 250
|
||||
intermediateTip := targetTip
|
||||
if intermediateTip.height-tip.height > maxReindexBlocks {
|
||||
intermediateTip = intermediateTip.Ancestor(tip.height +
|
||||
maxReindexBlocks)
|
||||
}
|
||||
|
||||
log.Infof("Reindexing to height %d of %d (progress %.2f%%)...",
|
||||
intermediateTip.height, targetTip.height,
|
||||
float64(intermediateTip.height)/float64(targetTip.height)*100)
|
||||
b.chainLock.Lock()
|
||||
if err := b.reorganizeChainInternal(intermediateTip); err != nil {
|
||||
b.chainLock.Unlock()
|
||||
return err
|
||||
}
|
||||
b.chainLock.Unlock()
|
||||
|
||||
tip = b.bestChain.Tip()
|
||||
}
|
||||
|
||||
// Mark the v5 reindex as complete by removing the associated key.
|
||||
err := b.db.Update(func(dbTx database.Tx) error {
|
||||
return dbTx.Metadata().Delete(v5ReindexTipKeyName)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeDB upgrades old database versions to the newest version by applying
|
||||
// all possible upgrades iteratively.
|
||||
//
|
||||
@ -536,18 +745,13 @@ func upgradeDB(db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInf
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: The next time a new database version is needed, the code in
|
||||
// initChainState which marks all ancestors of the current chain tip as
|
||||
// valid should be converted to updgrade all nodes in the database in the
|
||||
// upgrade path here and removed from the chain init. The version was not
|
||||
// bumped when applying the update since it is possible to perform very
|
||||
// quickly at startup on the block nodes in memory without requiring a
|
||||
// database version bump.
|
||||
|
||||
// TODO(davec): Replace with proper upgrade code for utxo set semantics
|
||||
// reversal and index updates.
|
||||
// Clear the utxoset, clear the spend journal, reset the best chain back to
|
||||
// the genesis block, and mark that a v5 reindex is required if needed.
|
||||
if dbInfo.version == 4 {
|
||||
return errors.New("Upgrade from version 4 database not supported yet")
|
||||
err := upgradeToVersion5(db, chainParams, dbInfo, interrupt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Loading…
Reference in New Issue
Block a user