// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package blockchain

import (
	"bytes"
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"math"
	"os"
	"sort"
	"time"

	"github.com/decred/dcrd/blockchain/stake/v5"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/chaincfg/v3"
	"github.com/decred/dcrd/crypto/blake256"
	"github.com/decred/dcrd/database/v3"
	"github.com/decred/dcrd/dcrec/secp256k1/v4"
	"github.com/decred/dcrd/gcs/v4"
	"github.com/decred/dcrd/gcs/v4/blockcf2"
	"github.com/decred/dcrd/txscript/v4"
	"github.com/decred/dcrd/wire"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/filter"
	"github.com/syndtr/goleveldb/leveldb/opt"
)

// errInterruptRequested indicates that an operation was cancelled due
// to a user-requested interrupt.
var errInterruptRequested = errors.New("interrupt requested")

// errBatchFinished indicates that a foreach database loop was exited due to
// reaching the maximum batch size.
var errBatchFinished = errors.New("batch finished")

// interruptRequested returns true when the provided channel has been closed.
// This simplifies early shutdown slightly since the caller can just use an if
// statement instead of a select.
func interruptRequested(ctx context.Context) bool {
	return ctx.Err() != nil
}

// deserializeDatabaseInfoV2 deserializes a database information struct from the
// passed serialized byte slice according to the legacy version 2 format.
//
// The legacy format is as follows:
//
//	Field      Type     Size      Description
//	version    uint32   4 bytes   The version of the database
//	compVer    uint32   4 bytes   The script compression version of the database
//	created    uint32   4 bytes   The date of the creation of the database
//
// The high bit (0x80000000) is used on version to indicate that an upgrade
// is in progress and used to confirm the database fidelity on start up.
func deserializeDatabaseInfoV2(dbInfoBytes []byte) (*databaseInfo, error) {
	// upgradeStartedBit if the bit flag for whether or not a database
	// upgrade is in progress. It is used to determine if the database
	// is in an inconsistent state from the update.
	const upgradeStartedBit = 0x80000000

	byteOrder := binary.LittleEndian

	rawVersion := byteOrder.Uint32(dbInfoBytes[0:4])
	upgradeStarted := (upgradeStartedBit & rawVersion) > 0
	version := rawVersion &^ upgradeStartedBit
	compVer := byteOrder.Uint32(dbInfoBytes[4:8])
	ts := byteOrder.Uint32(dbInfoBytes[8:12])

	if upgradeStarted {
		return nil, AssertError("database is in the upgrade started " +
			"state before resumable upgrades were supported - " +
			"delete the database and resync the blockchain")
	}

	return &databaseInfo{
		version: version,
		compVer: compVer,
		created: time.Unix(int64(ts), 0),
	}, nil
}

// -----------------------------------------------------------------------------
// The legacy version 2 block index consists of an entry for every known block.
// which includes information such as the block header and hashes of tickets
// voted and revoked.
//
// The serialized key format is:
//
//   <block height><block hash>
//
//   Field           Type              Size
//   block height    uint32            4 bytes
//   block hash      chainhash.Hash    chainhash.HashSize
//
// The serialized value format is:
//
//   <block header><status><num votes><votes info><num revoked><revoked tickets>
//
//   Field              Type                Size
//   block header       wire.BlockHeader    180 bytes
//   status             byte                1 byte
//   num votes          VLQ                 variable
//   vote info
//     ticket hash      chainhash.Hash      chainhash.HashSize
//     vote version     VLQ                 variable
//     vote bits        VLQ                 variable
//   num revoked        VLQ                 variable
//   revoked tickets
//     ticket hash      chainhash.Hash      chainhash.HashSize
//
// The version 2 block status flags format is:
//
//   bit  0   - block payload is stored on disk
//   bit  1   - block and all of its ancestors have been fully validated
//   bit  2   - block failed validation
//   bit  3   - an ancestor of the block failed validation
//   bits 4-7 - unused
// -----------------------------------------------------------------------------

// blockIndexVoteVersionTuple houses the extracted vote bits and version from
// votes for use in block index database entries.
type blockIndexVoteVersionTuple struct {
	version uint32
	bits    uint16
}

// blockIndexEntryV2 represents a legacy version 2 block index database entry.
type blockIndexEntryV2 struct {
	header         wire.BlockHeader
	status         byte
	voteInfo       []blockIndexVoteVersionTuple
	ticketsVoted   []chainhash.Hash
	ticketsRevoked []chainhash.Hash
}

// blockIndexEntrySerializeSizeV2 returns the number of bytes it would take to
// serialize the passed block index entry according to the legacy version 2
// format described above.
func blockIndexEntrySerializeSizeV2(entry *blockIndexEntryV2) int {
	voteInfoSize := 0
	for i := range entry.voteInfo {
		voteInfoSize += chainhash.HashSize +
			serializeSizeVLQ(uint64(entry.voteInfo[i].version)) +
			serializeSizeVLQ(uint64(entry.voteInfo[i].bits))
	}

	return blockHdrSize + 1 + serializeSizeVLQ(uint64(len(entry.voteInfo))) +
		voteInfoSize + serializeSizeVLQ(uint64(len(entry.ticketsRevoked))) +
		chainhash.HashSize*len(entry.ticketsRevoked)
}

// putBlockIndexEntryV2 serializes the passed block index entry according to the
// legacy version 2 format described above directly into the passed target byte
// slice.  The target byte slice must be at least large enough to handle the
// number of bytes returned by the blockIndexEntrySerializeSizeV2 function or it
// will panic.
func putBlockIndexEntryV2(target []byte, entry *blockIndexEntryV2) (int, error) {
	if len(entry.voteInfo) != len(entry.ticketsVoted) {
		return 0, AssertError("putBlockIndexEntry called with " +
			"mismatched number of tickets voted and vote info")
	}

	// Serialize the entire block header.
	w := bytes.NewBuffer(target[0:0])
	if err := entry.header.Serialize(w); err != nil {
		return 0, err
	}

	// Serialize the status.
	offset := blockHdrSize
	target[offset] = entry.status
	offset++

	// Serialize the number of votes and associated vote information.
	offset += putVLQ(target[offset:], uint64(len(entry.voteInfo)))
	for i := range entry.voteInfo {
		offset += copy(target[offset:], entry.ticketsVoted[i][:])
		offset += putVLQ(target[offset:], uint64(entry.voteInfo[i].version))
		offset += putVLQ(target[offset:], uint64(entry.voteInfo[i].bits))
	}

	// Serialize the number of revocations and associated revocation
	// information.
	offset += putVLQ(target[offset:], uint64(len(entry.ticketsRevoked)))
	for i := range entry.ticketsRevoked {
		offset += copy(target[offset:], entry.ticketsRevoked[i][:])
	}

	return offset, nil
}

// decodeBlockIndexEntryV2 decodes the passed serialized block index entry into
// the passed struct according to the legacy version 2 format described above.
// It returns the number of bytes read.
func decodeBlockIndexEntryV2(serialized []byte, entry *blockIndexEntryV2) (int, error) {
	// Hardcoded value so updates do not affect old upgrades.
	const blockHdrSize = 180

	// Ensure there are enough bytes to decode header.
	if len(serialized) < blockHdrSize {
		return 0, errDeserialize("unexpected end of data while reading block " +
			"header")
	}
	hB := serialized[0:blockHdrSize]

	// Deserialize the header.
	var header wire.BlockHeader
	if err := header.Deserialize(bytes.NewReader(hB)); err != nil {
		return 0, err
	}
	offset := blockHdrSize

	// Deserialize the status.
	if offset+1 > len(serialized) {
		return offset, errDeserialize("unexpected end of data while reading " +
			"status")
	}
	status := serialized[offset]
	offset++

	// Deserialize the number of tickets spent.
	var ticketsVoted []chainhash.Hash
	var votes []blockIndexVoteVersionTuple
	numVotes, bytesRead := deserializeVLQ(serialized[offset:])
	if bytesRead == 0 {
		return offset, errDeserialize("unexpected end of data while reading " +
			"num votes")
	}
	offset += bytesRead
	if numVotes > 0 {
		ticketsVoted = make([]chainhash.Hash, numVotes)
		votes = make([]blockIndexVoteVersionTuple, numVotes)
		for i := uint64(0); i < numVotes; i++ {
			// Deserialize the ticket hash associated with the vote.
			if offset+chainhash.HashSize > len(serialized) {
				return offset, errDeserialize(fmt.Sprintf("unexpected end of "+
					"data while reading vote #%d hash", i))
			}
			copy(ticketsVoted[i][:], serialized[offset:])
			offset += chainhash.HashSize

			// Deserialize the vote version.
			version, bytesRead := deserializeVLQ(serialized[offset:])
			if bytesRead == 0 {
				return offset, errDeserialize(fmt.Sprintf("unexpected end of "+
					"data while reading vote #%d version", i))
			}
			offset += bytesRead

			// Deserialize the vote bits.
			voteBits, bytesRead := deserializeVLQ(serialized[offset:])
			if bytesRead == 0 {
				return offset, errDeserialize(fmt.Sprintf("unexpected end of "+
					"data while reading vote #%d bits", i))
			}
			offset += bytesRead

			votes[i].version = uint32(version)
			votes[i].bits = uint16(voteBits)
		}
	}

	// Deserialize the number of tickets revoked.
	var ticketsRevoked []chainhash.Hash
	numTicketsRevoked, bytesRead := deserializeVLQ(serialized[offset:])
	if bytesRead == 0 {
		return offset, errDeserialize("unexpected end of data while reading " +
			"num tickets revoked")
	}
	offset += bytesRead
	if numTicketsRevoked > 0 {
		ticketsRevoked = make([]chainhash.Hash, numTicketsRevoked)
		for i := uint64(0); i < numTicketsRevoked; i++ {
			// Deserialize the ticket hash associated with the
			// revocation.
			if offset+chainhash.HashSize > len(serialized) {
				return offset, errDeserialize(fmt.Sprintf("unexpected end of "+
					"data while reading revocation #%d", i))
			}
			copy(ticketsRevoked[i][:], serialized[offset:])
			offset += chainhash.HashSize
		}
	}

	entry.header = header
	entry.status = status
	entry.voteInfo = votes
	entry.ticketsVoted = ticketsVoted
	entry.ticketsRevoked = ticketsRevoked
	return offset, nil
}

// incrementalFlatDrop uses multiple database updates to remove key/value pairs
// saved to a flag bucket.
func incrementalFlatDrop(ctx context.Context, db database.DB, bucketKey []byte, humanName string) 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(ctx) {
			return errInterruptRequested
		}
	}
	return nil
}

// runUpgradeStageOnce ensures the provided function is only run one time by
// checking if the provided key already exists in the database and writing it to
// the database upon successful completion of the provided function when it is
// not.
//
// This is useful to ensure upgrades that consist of multiple stages can be
// interrupted without redoing all of the work associated with stages that were
// previously completed successfully.
func runUpgradeStageOnce(ctx context.Context, db database.DB, doneKeyName []byte, fn func() error) error {
	// Don't run again if the provided key already exists.
	var alreadyDone bool
	err := db.View(func(dbTx database.Tx) error {
		alreadyDone = dbTx.Metadata().Get(doneKeyName) != nil
		return nil
	})
	if err != nil || alreadyDone {
		return err
	}

	if err := fn(); err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Save the key to mark the update fully complete in case of interruption.
	return db.Update(func(dbTx database.Tx) error {
		return dbTx.Metadata().Put(doneKeyName, nil)
	})
}

// batchFn represents the batch function used by the batched update function.
type batchFn func(dbTx database.Tx) (bool, error)

// batchedUpdate calls the provided batch function repeatedly until it either
// returns an error other than the special ones described in this comment or
// its return indicates no more calls are necessary.
//
// In order to ensure the database is updated with the results of the batch that
// have already been successfully completed, it is allowed to return
// errBatchFinished and errInterruptRequested.  In the case of the former, the
// error will be ignored.  In the case of the latter, the database will be
// updated and the error will be returned accordingly.  The database will NOT
// be updated if any other errors are returned.
func batchedUpdate(ctx context.Context, db database.DB, doBatch batchFn) error {
	var isFullyDone bool
	for !isFullyDone {
		err := db.Update(func(dbTx database.Tx) error {
			var err error
			isFullyDone, err = doBatch(dbTx)
			if errors.Is(err, errInterruptRequested) ||
				errors.Is(err, errBatchFinished) {

				// No error here so the database transaction is not cancelled
				// and therefore outstanding work is written to disk.  The outer
				// function will exit with an interrupted error below due to
				// another interrupted check.
				err = nil
			}
			return err
		})
		if err != nil {
			return err
		}

		if interruptRequested(ctx) {
			return errInterruptRequested
		}
	}

	return nil
}

// utxoBackendBatchFn represents the batch function used by the UTXO backend
// batched update function.
type utxoBackendBatchFn func(tx UtxoBackendTx) (bool, error)

// utxoBackendBatchedUpdate calls the provided batch function repeatedly until
// it either returns an error other than the special ones described in this
// comment or its return indicates no more calls are necessary.
//
// In order to ensure the backend is updated with the results of the batch that
// have already been successfully completed, it is allowed to return
// errBatchFinished and errInterruptRequested.  In the case of the former, the
// error will be ignored.  In the case of the latter, the backend will be
// updated and the error will be returned accordingly.  The backend will NOT
// be updated if any other errors are returned.
func utxoBackendBatchedUpdate(ctx context.Context,
	utxoBackend UtxoBackend, doBatch utxoBackendBatchFn) error {

	var isFullyDone bool
	for !isFullyDone {
		err := utxoBackend.Update(func(tx UtxoBackendTx) error {
			var err error
			isFullyDone, err = doBatch(tx)
			if errors.Is(err, errInterruptRequested) ||
				errors.Is(err, errBatchFinished) {

				// No error here so the database transaction is not cancelled
				// and therefore outstanding work is written to disk.  The outer
				// function will exit with an interrupted error below due to
				// another interrupted check.
				return nil
			}
			return err
		})
		if err != nil {
			return err
		}

		if interruptRequested(ctx) {
			return errInterruptRequested
		}
	}

	return nil
}

// clearFailedBlockFlagsV2 unmarks all blocks in a version 2 block index
// previously marked failed so they are eligible for validation again under new
// consensus rules.  This ensures clients that did not update prior to new rules
// activating are able to automatically recover under the new rules without
// having to download the entire chain again.
func clearFailedBlockFlagsV2(ctx context.Context, db database.DB) error {
	// Hardcoded bucket name so updates do not affect old upgrades.
	v2BucketName := []byte("blockidx")

	log.Info("Reindexing block information in the database.  This may take a " +
		"while...")
	start := time.Now()

	// doBatch contains the primary logic for updating the block index in
	// batches.  This is done because attempting to migrate in a single database
	// transaction could result in massive memory usage and could potentially
	// crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalUpdated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v2BlockIdxBucket := meta.Bucket(v2BucketName)
		if v2BlockIdxBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v2BucketName)
		}

		// Update block index entries so long as the max number of entries for
		// this batch has not been exceeded.
		var logProgress bool
		var numUpdated, numIterated uint32
		err := v2BlockIdxBucket.ForEach(func(key, oldSerialized []byte) error {
			if interruptRequested(ctx) {
				logProgress = true
				return errInterruptRequested
			}

			if numUpdated >= maxEntries {
				logProgress = true
				return errBatchFinished
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				return nil
			}
			resumeOffset++

			// Decode the old block index entry.
			var entry blockIndexEntryV2
			_, err := decodeBlockIndexEntryV2(oldSerialized, &entry)
			if err != nil {
				return err
			}

			// Mark the block index entry as eligible for validation again.
			const (
				v2StatusValidateFailed  = 1 << 2
				v2StatusInvalidAncestor = 1 << 3
			)
			origStatus := entry.status
			entry.status &^= v2StatusValidateFailed | v2StatusInvalidAncestor
			if entry.status != origStatus {
				targetSize := blockIndexEntrySerializeSizeV2(&entry)
				serialized := make([]byte, targetSize)
				_, err = putBlockIndexEntryV2(serialized, &entry)
				if err != nil {
					return err
				}
				err = v2BlockIdxBucket.Put(key, serialized)
				if err != nil {
					return err
				}
			}

			numUpdated++
			return nil
		})
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numUpdated > 0 {
			totalUpdated += uint64(numUpdated)
			log.Infof("Updated %d entries (%d total)", numUpdated, totalUpdated)
		}
		return isFullyDone, err
	}

	// Update all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done updating block index.  Total entries: %d in %v",
		totalUpdated, elapsed)
	return nil
}

// scriptSourceEntry houses a script and its associated version.
type scriptSourceEntry struct {
	version uint16
	script  []byte
}

// scriptSource provides a source of transaction output scripts and their
// associated script version for given outpoints and implements the PrevScripter
// interface so it may be used in cases that require access to said scripts.
type scriptSource map[wire.OutPoint]scriptSourceEntry

// PrevScript returns the script and script version associated with the provided
// previous outpoint along with a bool that indicates whether or not the
// requested entry exists.  This ensures the caller is able to distinguish
// between missing entries and empty v0 scripts.
func (s scriptSource) PrevScript(prevOut *wire.OutPoint) (uint16, []byte, bool) {
	entry, ok := s[*prevOut]
	if !ok {
		return 0, nil, false
	}
	return entry.version, entry.script, true
}

// determineMinimalOutputsSizeV1 determines and returns the size of the stored
// set of minimal outputs in a version 1 spend journal entry.
func determineMinimalOutputsSizeV1(serialized []byte) (int, error) {
	numOutputs, offset := deserializeVLQ(serialized)
	if offset == 0 {
		return offset, errDeserialize("unexpected end of data during " +
			"decoding (num outputs)")
	}
	for i := 0; i < int(numOutputs); i++ {
		// Amount.
		_, bytesRead := deserializeVLQ(serialized[offset:])
		if bytesRead == 0 {
			return offset, errDeserialize("unexpected end of data during " +
				"decoding (output amount)")
		}
		offset += bytesRead

		// Script version.
		_, bytesRead = deserializeVLQ(serialized[offset:])
		if bytesRead == 0 {
			return offset, errDeserialize("unexpected end of data during " +
				"decoding (output script version)")
		}
		offset += bytesRead

		// Script.
		var scriptSize uint64
		scriptSize, bytesRead = deserializeVLQ(serialized[offset:])
		if bytesRead == 0 {
			return offset, errDeserialize("unexpected end of data during " +
				"decoding (output script size)")
		}
		offset += bytesRead

		if uint64(len(serialized[offset:])) < scriptSize {
			return offset, errDeserialize("unexpected end of data during " +
				"decoding (output script)")
		}

		offset += int(scriptSize)
	}

	return offset, nil
}

// decodeCompressedScriptSizeV1 treats the passed serialized bytes as a v1
// compressed script, possibly followed by other data, and returns the number of
// bytes it occupies taking into account the special encoding of the script size
// by the domain specific compression algorithm described above.
func decodeCompressedScriptSizeV1(serialized []byte) int {
	const (
		// Hardcoded constants so updates do not affect old upgrades.
		cstPayToPubKeyHash       = 0
		cstPayToScriptHash       = 1
		cstPayToPubKeyCompEven   = 2
		cstPayToPubKeyCompOdd    = 3
		cstPayToPubKeyUncompEven = 4
		cstPayToPubKeyUncompOdd  = 5
		numSpecialScripts        = 64
	)

	scriptSize, bytesRead := deserializeVLQ(serialized)
	if bytesRead == 0 {
		return 0
	}

	switch scriptSize {
	case cstPayToPubKeyHash:
		return 21

	case cstPayToScriptHash:
		return 21

	case cstPayToPubKeyCompEven, cstPayToPubKeyCompOdd,
		cstPayToPubKeyUncompEven, cstPayToPubKeyUncompOdd:
		return 33
	}

	scriptSize -= numSpecialScripts
	scriptSize += uint64(bytesRead)
	return int(scriptSize)
}

// decompressScriptV1 returns the original script obtained by decompressing the
// passed v1 compressed script according to the domain specific compression
// algorithm described above.
//
// NOTE: The script parameter must already have been proven to be long enough
// to contain the number of bytes returned by decodeCompressedScriptSize or it
// will panic.  This is acceptable since it is only an internal function.
func decompressScriptV1(compressedPkScript []byte) []byte {
	const (
		// Hardcoded constants so updates do not affect old upgrades.
		cstPayToPubKeyHash       = 0
		cstPayToScriptHash       = 1
		cstPayToPubKeyCompEven   = 2
		cstPayToPubKeyCompOdd    = 3
		cstPayToPubKeyUncompEven = 4
		cstPayToPubKeyUncompOdd  = 5
		numSpecialScripts        = 64
	)

	// Empty scripts, specified by 0x00, are considered nil.
	if len(compressedPkScript) == 0 {
		return nil
	}

	// Decode the script size and examine it for the special cases.
	encodedScriptSize, bytesRead := deserializeVLQ(compressedPkScript)
	switch encodedScriptSize {
	// Pay-to-pubkey-hash script.  The resulting script is:
	// <OP_DUP><OP_HASH160><20 byte hash><OP_EQUALVERIFY><OP_CHECKSIG>
	case cstPayToPubKeyHash:
		pkScript := make([]byte, 25)
		pkScript[0] = txscript.OP_DUP
		pkScript[1] = txscript.OP_HASH160
		pkScript[2] = txscript.OP_DATA_20
		copy(pkScript[3:], compressedPkScript[bytesRead:bytesRead+20])
		pkScript[23] = txscript.OP_EQUALVERIFY
		pkScript[24] = txscript.OP_CHECKSIG
		return pkScript

	// Pay-to-script-hash script.  The resulting script is:
	// <OP_HASH160><20 byte script hash><OP_EQUAL>
	case cstPayToScriptHash:
		pkScript := make([]byte, 23)
		pkScript[0] = txscript.OP_HASH160
		pkScript[1] = txscript.OP_DATA_20
		copy(pkScript[2:], compressedPkScript[bytesRead:bytesRead+20])
		pkScript[22] = txscript.OP_EQUAL
		return pkScript

	// Pay-to-compressed-pubkey script.  The resulting script is:
	// <OP_DATA_33><33 byte compressed pubkey><OP_CHECKSIG>
	case cstPayToPubKeyCompEven, cstPayToPubKeyCompOdd:
		pkScript := make([]byte, 35)
		pkScript[0] = txscript.OP_DATA_33
		oddness := byte(0x02)
		if encodedScriptSize == cstPayToPubKeyCompOdd {
			oddness = 0x03
		}
		pkScript[1] = oddness
		copy(pkScript[2:], compressedPkScript[bytesRead:bytesRead+32])
		pkScript[34] = txscript.OP_CHECKSIG
		return pkScript

	// Pay-to-uncompressed-pubkey script.  The resulting script is:
	// <OP_DATA_65><65 byte uncompressed pubkey><OP_CHECKSIG>
	case cstPayToPubKeyUncompEven, cstPayToPubKeyUncompOdd:
		// Change the leading byte to the appropriate compressed pubkey
		// identifier (0x02 or 0x03) so it can be decoded as a
		// compressed pubkey.  This really should never fail since the
		// encoding ensures it is valid before compressing to this type.
		compressedKey := make([]byte, 33)
		oddness := byte(0x02)
		if encodedScriptSize == cstPayToPubKeyUncompOdd {
			oddness = 0x03
		}
		compressedKey[0] = oddness
		copy(compressedKey[1:], compressedPkScript[1:])
		key, err := secp256k1.ParsePubKey(compressedKey)
		if err != nil {
			return nil
		}

		pkScript := make([]byte, 67)
		pkScript[0] = txscript.OP_DATA_65
		copy(pkScript[1:], key.SerializeUncompressed())
		pkScript[66] = txscript.OP_CHECKSIG
		return pkScript
	}

	// When none of the special cases apply, the script was encoded using
	// the general format, so reduce the script size by the number of
	// special cases and return the unmodified script.
	scriptSize := int(encodedScriptSize - numSpecialScripts)
	pkScript := make([]byte, scriptSize)
	copy(pkScript, compressedPkScript[bytesRead:bytesRead+scriptSize])
	return pkScript
}

// scriptSourceFromSpendJournalV1 uses the legacy v1 spend journal along with
// the provided block to create a source of previous transaction scripts and
// versions spent by the block.
func scriptSourceFromSpendJournalV1(dbTx database.Tx, block *wire.MsgBlock) (scriptSource, error) {
	// Load the serialized spend journal entry from the database, construct the
	// full list of transactions that spend outputs (notice the coinbase
	// transaction is excluded since it can't spend anything), and perform an
	// initial sanity check to ensure there is serialized data for the block
	// when there are transactions that spend outputs.
	blockHash := block.BlockHash()
	v1SpendJournalBucketName := []byte("spendjournal")
	spendBucket := dbTx.Metadata().Bucket(v1SpendJournalBucketName)
	serialized := spendBucket.Get(blockHash[:])

	txns := make([]*wire.MsgTx, 0, len(block.STransactions)+
		len(block.Transactions[1:]))
	txns = append(txns, block.STransactions...)
	txns = append(txns, block.Transactions[1:]...)
	if len(txns) > 0 && len(serialized) == 0 {
		str := fmt.Sprintf("missing spend journal data for %s", blockHash)
		return nil, errDeserialize(str)
	}

	// The legacy version 1 transaction spend journal consists of an entry for
	// each block connected to the main chain which contains the transaction
	// outputs the block spends serialized such that the order is the reverse of
	// the order they were spent.
	//
	// The legacy format for this entry is roughly:
	//
	//   [<flags><script version><compressed pkscript><optional data>],...
	//
	// The legacy optional data is only present if the flags indicate the
	// transaction is fully spent (bit 4 in legacy format) and its format is
	// roughly:
	//
	//   <tx version><optional stake data>
	//
	// The legacy optional stake data is only present if the flags indicate the
	// transaction type is a ticket and its format is roughly:
	//
	//   <num outputs>[<amount><script version><script len><script>],...
	//
	//   Field                   Type           Size
	//   flags                   VLQ            variable (always 1 byte)
	//   script version          VLQ            variable
	//   compressed pkscript     []byte         variable
	//   optional data (only present if flags indicates fully spent)
	//     transaction version   VLQ            variable
	//     stake data (only present if flags indicates tx type ticket)
	//       num outputs         VLQ            variable
	//       output info
	//         amount            VLQ            variable
	//         script version    VLQ            variable
	//         script len        VLQ            variable
	//         script            []byte         variable
	//
	// The legacy serialized flags code format is:
	//
	//   bit  0   - containing transaction is a coinbase
	//   bit  1   - containing transaction has an expiry
	//   bits 2-3 - transaction type
	//   bit  4   - is fully spent
	//   bits 5-7 - unused
	//
	// Given the only information needed is the script version and associated
	// pkscript, the following specifically finds the relevant information while
	// skipping everything else.
	const (
		v1FullySpentFlag = 1 << 4
		v1TxTypeMask     = 0x0c
		v1TxTypeShift    = 2
		v1TxTypeTicket   = 1
	)

	// Loop backwards through all transactions so everything is read in reverse
	// order to match the serialization order.
	source := make(scriptSource)
	var offset int
	for txIdx := len(txns) - 1; txIdx > -1; txIdx-- {
		tx := txns[txIdx]
		isVote := stake.IsSSGen(tx)

		// Loop backwards through all of the transaction inputs and read the
		// associated stxo.
		for txInIdx := len(tx.TxIn) - 1; txInIdx > -1; txInIdx-- {
			// Skip stakebase since it has no input.
			if txInIdx == 0 && isVote {
				continue
			}

			txIn := tx.TxIn[txInIdx]

			// Deserialize the flags.
			if offset >= len(serialized) {
				str := "unexpected end of spend journal entry"
				return nil, errDeserialize(str)
			}
			flags64, bytesRead := deserializeVLQ(serialized[offset:])
			offset += bytesRead
			if bytesRead != 1 {
				str := fmt.Sprintf("unexpected flags size -- got %d, want 1",
					bytesRead)
				return nil, errDeserialize(str)
			}
			flags := byte(flags64)
			fullySpent := flags&v1FullySpentFlag != 0
			txType := (flags & v1TxTypeMask) >> v1TxTypeShift

			// Deserialize the script version.
			if offset >= len(serialized) {
				str := "unexpected end of data after flags"
				return nil, errDeserialize(str)
			}
			scriptVersion, bytesRead := deserializeVLQ(serialized[offset:])
			offset += bytesRead

			// Decode the compressed script size and ensure there are enough
			// bytes left in the slice for it.
			if offset >= len(serialized) {
				str := "unexpected end of data after script version"
				return nil, errDeserialize(str)
			}
			scriptSize := decodeCompressedScriptSizeV1(serialized[offset:])
			if scriptSize < 0 {
				str := "negative script size"
				return nil, errDeserialize(str)
			}
			if offset+scriptSize > len(serialized) {
				str := "unexpected end of data after script size"
				return nil, errDeserialize(str)
			}
			pkScript := serialized[offset : offset+scriptSize]
			offset += scriptSize

			// Create an output in the script source for the referenced script
			// and version using the data from the spend journal.
			prevOut := &txIn.PreviousOutPoint
			source[*prevOut] = scriptSourceEntry{
				version: uint16(scriptVersion),
				script:  decompressScriptV1(pkScript),
			}

			// Deserialize the tx version and minimal outputs for tickets as
			// needed to locate the offset of the next entry.
			if fullySpent {
				if offset >= len(serialized) {
					str := "unexpected end of data after script size"
					return nil, errDeserialize(str)
				}
				_, bytesRead := deserializeVLQ(serialized[offset:])
				offset += bytesRead
				if txType == v1TxTypeTicket {
					if offset >= len(serialized) {
						str := "unexpected end of data after tx version"
						return nil, errDeserialize(str)
					}

					sz, err := determineMinimalOutputsSizeV1(serialized[offset:])
					if err != nil {
						return nil, err
					}
					offset += sz
				}
			}
		}
	}

	return source, nil
}

// initializeGCSFilters creates and stores version 2 GCS filters for all blocks
// in the main chain.  This ensures they are immediately available to clients
// and simplifies the rest of the related code since it can rely on the filters
// being available once the upgrade completes.
//
// The database is guaranteed to have a filter entry for every block in the
// main chain if this returns without failure.
func initializeGCSFilters(ctx context.Context, db database.DB, genesisHash *chainhash.Hash) error {
	log.Info("Creating and storing GCS filters.  This will take a while...")
	start := time.Now()

	// Determine the blocks in the main chain using the version 2 block index
	// and version 1 chain state.
	var mainChainBlocks []chainhash.Hash
	err := db.View(func(dbTx database.Tx) error {
		// Hardcoded bucket names and keys so updates do not affect old
		// upgrades.
		v2BucketName := []byte("blockidx")
		v1ChainStateKeyName := []byte("chainstate")

		// Load the current best chain tip hash and height from the v1 chain
		// state.
		//
		// The serialized format of the v1 chain state is roughly:
		//
		//   <block hash><rest of data>
		//
		//   Field             Type             Size
		//   block hash        chainhash.Hash   chainhash.HashSize
		//   rest of data...
		meta := dbTx.Metadata()
		serializedChainState := meta.Get(v1ChainStateKeyName)
		if serializedChainState == nil {
			str := fmt.Sprintf("chain state with key %s does not exist",
				v1ChainStateKeyName)
			return errDeserialize(str)
		}
		if len(serializedChainState) < chainhash.HashSize {
			str := "version 1 chain state is malformed"
			return errDeserialize(str)
		}
		var tipHash chainhash.Hash
		copy(tipHash[:], serializedChainState[0:chainhash.HashSize])

		// blockTreeEntry represents a version 2 block index entry with just
		// enough information to be able to determine which blocks comprise the
		// main chain.
		type blockTreeEntry struct {
			parent *blockTreeEntry
			hash   chainhash.Hash
			height uint32
		}

		// Construct a full block tree from the version 2 block index by mapping
		// each block to its parent block.
		var lastEntry, parent *blockTreeEntry
		blockTree := make(map[chainhash.Hash]*blockTreeEntry)
		v2BlockIdxBucket := meta.Bucket(v2BucketName)
		if v2BlockIdxBucket == nil {
			return fmt.Errorf("bucket %s does not exist", v2BucketName)
		}
		err := v2BlockIdxBucket.ForEach(func(_, serialized []byte) error {
			// Decode the block index entry.
			var entry blockIndexEntryV2
			_, err := decodeBlockIndexEntryV2(serialized, &entry)
			if err != nil {
				return err
			}
			header := &entry.header

			// Determine the parent block node.  Since the entries are iterated
			// in order of height, there is a very good chance the previous
			// one processed is the parent.
			blockHash := header.BlockHash()
			if lastEntry == nil {
				if blockHash != *genesisHash {
					str := fmt.Sprintf("initializeGCSFilters: expected first "+
						"entry in block index to be genesis block, found %s",
						blockHash)
					return errDeserialize(str)
				}
			} else if header.PrevBlock == lastEntry.hash {
				parent = lastEntry
			} else {
				parent = blockTree[header.PrevBlock]
				if parent == nil {
					str := fmt.Sprintf("initializeGCSFilters: could not find "+
						"parent for block %s", blockHash)
					return errDeserialize(str)
				}
			}

			// Add the block to the block tree.
			treeEntry := &blockTreeEntry{
				parent: parent,
				hash:   blockHash,
				height: header.Height,
			}
			blockTree[blockHash] = treeEntry

			lastEntry = treeEntry
			return nil
		})
		if err != nil {
			return err
		}

		// Construct a view of the blocks that comprise the main chain by
		// starting at the best tip and walking backwards to the genesis block
		// while assigning each one to its respective height.
		tipEntry := blockTree[tipHash]
		if tipEntry == nil {
			str := fmt.Sprintf("chain tip %s is not in block index", tipHash)
			return errDeserialize(str)
		}
		mainChainBlocks = make([]chainhash.Hash, tipEntry.height+1)
		for entry := tipEntry; entry != nil; entry = entry.parent {
			mainChainBlocks[entry.height] = entry.hash
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Create the new filter bucket as needed.
	gcsBucketName := []byte("gcsfilters")
	err = db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(gcsBucketName)
		return err
	})
	if err != nil {
		return err
	}

	// newFilter loads the full block for the provided node from the db along
	// with its spend journal information and uses it to create a v2 GCS filter.
	newFilter := func(dbTx database.Tx, blockHash *chainhash.Hash) (*gcs.FilterV2, error) {
		// Load the full block from the database.
		blockBytes, err := dbTx.FetchBlock(blockHash)
		if err != nil {
			return nil, err
		}
		var block wire.MsgBlock
		if err := block.FromBytes(blockBytes); err != nil {
			return nil, err
		}

		// Use the combination of the block and the spent transaction output
		// data from the database to create a source of previous scripts spent
		// by the block needed to create the filter.
		prevScripts, err := scriptSourceFromSpendJournalV1(dbTx, &block)
		if err != nil {
			return nil, err
		}

		// Create the filter from the block and referenced previous output
		// scripts.
		filter, err := blockcf2.Regular(&block, prevScripts)
		if err != nil {
			return nil, err
		}

		return filter, nil
	}

	// doBatch contains the primary logic for creating the GCS filters when
	// moving from database version 5 to 6 in batches.  This is done because
	// attempting to create them all in a single database transaction could
	// result in massive memory usage and could potentially crash on many
	// systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	blockHeight := int64(0)
	var totalCreated, totalFilterBytes uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		filterBucket := dbTx.Metadata().Bucket(gcsBucketName)
		if filterBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", gcsBucketName)
		}

		var logProgress bool
		var numCreated, totalBytes uint64
		err := func() error {
			for ; blockHeight < int64(len(mainChainBlocks)); blockHeight++ {
				if interruptRequested(ctx) {
					logProgress = true
					return errInterruptRequested
				}

				if numCreated >= maxEntries {
					logProgress = true
					return errBatchFinished
				}

				// Create the filter from the block and referenced previous output
				// scripts.
				blockHash := &mainChainBlocks[blockHeight]
				filter, err := newFilter(dbTx, blockHash)
				if err != nil {
					return err
				}

				// Store the filter to the database.
				serialized := filter.Bytes()
				err = filterBucket.Put(blockHash[:], serialized)
				if err != nil {
					return err
				}
				totalBytes += uint64(len(serialized))

				numCreated++
			}
			return nil
		}()
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numCreated > 0 {
			totalCreated += numCreated
			totalFilterBytes += totalBytes
			log.Infof("Created %d entries (%d total)", numCreated, totalCreated)
		}
		return isFullyDone, err
	}

	// Create the filters in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done creating GCS filters in %v.  Total entries: %d (%d bytes)",
		elapsed, totalCreated, totalFilterBytes)
	return nil
}

// -----------------------------------------------------------------------------
// The version 3 block index consists of an entry for every known block.  It
// consists of information such as the block header and information about votes.
//
// The serialized key format is:
//
//   <block height><block hash>
//
//   Field           Type              Size
//   block height    uint32            4 bytes
//   block hash      chainhash.Hash    chainhash.HashSize
//
// The serialized value format is:
//
//   <block header><status><num votes><votes info>
//
//   Field              Type                Size
//   block header       wire.BlockHeader    180 bytes
//   status             byte                1 byte
//   num votes          VLQ                 variable
//   vote info
//     vote version     VLQ                 variable
//     vote bits        VLQ                 variable
//
// The version 3 block status flags format is:
//
//   bit  0   - block payload is stored on disk
//   bit  1   - block and all of its ancestors have been fully validated
//   bit  2   - block failed validation
//   bit  3   - an ancestor of the block failed validation
//   bits 4-7 - unused
// -----------------------------------------------------------------------------

// blockIndexEntryV3 represents a version 3 block index database entry.
type blockIndexEntryV3 struct {
	header   wire.BlockHeader
	status   byte
	voteInfo []blockIndexVoteVersionTuple
}

// blockIndexKeyV3 generates the binary key for an entry in the version 3 block
// index bucket.  The key is composed of the block height encoded as a
// big-endian 32-bit unsigned int followed by the 32 byte block hash.  Big
// endian is used here so the entries can easily be iterated by height.
func blockIndexKeyV3(blockHash *chainhash.Hash, blockHeight uint32) []byte {
	indexKey := make([]byte, chainhash.HashSize+4)
	binary.BigEndian.PutUint32(indexKey[0:4], blockHeight)
	copy(indexKey[4:chainhash.HashSize+4], blockHash[:])
	return indexKey
}

// blockIndexEntrySerializeSizeV3 returns the number of bytes it would take to
// serialize the passed block index entry according to the version 3 format
// described above.
func blockIndexEntrySerializeSizeV3(entry *blockIndexEntryV3) int {
	voteInfoSize := 0
	for i := range entry.voteInfo {
		voteInfoSize += serializeSizeVLQ(uint64(entry.voteInfo[i].version)) +
			serializeSizeVLQ(uint64(entry.voteInfo[i].bits))
	}

	const blockHdrSize = 180
	return blockHdrSize + 1 + serializeSizeVLQ(uint64(len(entry.voteInfo))) +
		voteInfoSize
}

// putBlockIndexEntryV3 serializes the passed block index entry according to the
// version 3 format described above directly into the passed target byte slice.
// The target byte slice must be at least large enough to handle the number of
// bytes returned by the blockIndexEntrySerializeSize function or it will panic.
func putBlockIndexEntryV3(target []byte, entry *blockIndexEntryV3) (int, error) {
	// Serialize the entire block header.
	w := bytes.NewBuffer(target[0:0])
	if err := entry.header.Serialize(w); err != nil {
		return 0, err
	}

	// Serialize the status.
	const blockHdrSize = 180
	offset := blockHdrSize
	target[offset] = entry.status
	offset++

	// Serialize the number of votes and associated vote information.
	offset += putVLQ(target[offset:], uint64(len(entry.voteInfo)))
	for i := range entry.voteInfo {
		offset += putVLQ(target[offset:], uint64(entry.voteInfo[i].version))
		offset += putVLQ(target[offset:], uint64(entry.voteInfo[i].bits))
	}

	return offset, nil
}

// serializeBlockIndexEntryV3 serializes the passed block index entry into a
// single byte slice according to the version 3 format described in detail
// above.
func serializeBlockIndexEntryV3(entry *blockIndexEntryV3) ([]byte, error) {
	serialized := make([]byte, blockIndexEntrySerializeSizeV3(entry))
	_, err := putBlockIndexEntryV3(serialized, entry)
	return serialized, err
}

// upgradeToVersion6 upgrades a version 5 blockchain database to version 6.
func upgradeToVersion6(ctx context.Context, db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 6...")
	start := time.Now()

	// Unmark all blocks previously marked failed so they are eligible for
	// validation again under the new consensus rules.
	v2ClearFailedDoneKeyName := []byte("blockidxv2clearfaileddone")
	err := runUpgradeStageOnce(ctx, db, v2ClearFailedDoneKeyName, func() error {
		return clearFailedBlockFlagsV2(ctx, db)
	})
	if err != nil {
		return err
	}

	// Create and store version 2 GCS filters for all blocks in the main chain.
	err = initializeGCSFilters(ctx, db, &chainParams.GenesisHash)
	if err != nil {
		return err
	}

	// Update and persist the database versions and remove upgrade progress
	// tracking keys.
	err = db.Update(func(dbTx database.Tx) error {
		err := dbTx.Metadata().Delete(v2ClearFailedDoneKeyName)
		if err != nil {
			return err
		}

		dbInfo.version = 6
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// migrateBlockIndexVersion2To3 migrates all block entries from the v2 block
// index bucket to a v3 bucket and removes the old v2 bucket.  As compared to
// the v2 block index, the v3 index removes the ticket hashes associated with
// vote info and revocations.
//
// The new block index is guaranteed to be fully updated if this returns without
// failure.
func migrateBlockIndexVersion2To3(ctx context.Context, db database.DB, dbInfo *databaseInfo) error {
	// Hardcoded bucket names so updates do not affect old upgrades.
	v2BucketName := []byte("blockidx")
	v3BucketName := []byte("blockidxv3")

	log.Info("Reindexing block information in the database.  This may take a " +
		"while...")
	start := time.Now()

	// Create the new block index bucket as needed.
	err := db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(v3BucketName)
		return err
	})
	if err != nil {
		return err
	}

	// doBatch contains the primary logic for upgrading the block index from
	// version 2 to 3 in batches.  This is done because attempting to migrate in
	// a single database transaction could result in massive memory usage and
	// could potentially crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v2BlockIdxBucket := meta.Bucket(v2BucketName)
		if v2BlockIdxBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v2BucketName)
		}

		v3BlockIdxBucket := meta.Bucket(v3BucketName)
		if v3BlockIdxBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v3BucketName)
		}

		// Migrate block index entries so long as the max number of entries for
		// this batch has not been exceeded.
		var logProgress bool
		var numMigrated, numIterated uint32
		err := v2BlockIdxBucket.ForEach(func(key, oldSerialized []byte) error {
			if interruptRequested(ctx) {
				logProgress = true
				return errInterruptRequested
			}

			if numMigrated >= maxEntries {
				logProgress = true
				return errBatchFinished
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				return nil
			}
			resumeOffset++

			// Skip entries that have already been migrated in previous
			// interrupted upgrades.
			if v3BlockIdxBucket.Get(key) != nil {
				return nil
			}

			// Decode the old block index entry.
			var entry blockIndexEntryV2
			_, err := decodeBlockIndexEntryV2(oldSerialized, &entry)
			if err != nil {
				return err
			}

			// Write the block index entry serialized with the new format to the
			// new bucket.
			serialized, err := serializeBlockIndexEntryV3(&blockIndexEntryV3{
				header:   entry.header,
				status:   entry.status,
				voteInfo: entry.voteInfo,
			})
			if err != nil {
				return err
			}
			err = v3BlockIdxBucket.Put(key, serialized)
			if err != nil {
				return err
			}

			numMigrated++
			return nil
		})
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating block index.  Total entries: %d in %v",
		totalMigrated, elapsed)

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Drop version 2 block index.
	log.Info("Removing old block index entries...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, db, v2BucketName, "old block index")
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing old block index entries in %v", elapsed)

	// Update and persist the database versions.
	err = db.Update(func(dbTx database.Tx) error {
		dbInfo.bidxVer = 3
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	return err
}

// migrateUtxoSetVersion1To2 migrates all utxoset entries from the v1 bucket to
// a v2 bucket and removes the old v1 bucket.  As compared to the v1 utxoset,
// the v2 utxoset moves the bit which defines whether or not a tx is fully spent
// from bit 4 to bit 6.
//
// The utxoset is guaranteed to be fully updated if this returns without
// failure.
func migrateUtxoSetVersion1To2(ctx context.Context, db database.DB) error {
	// Hardcoded bucket and key names so updates do not affect old upgrades.
	v1BucketName := []byte("utxoset")
	v2BucketName := []byte("utxosetv2")

	log.Info("Migrating database utxo set.  This may take a while...")
	start := time.Now()

	// Create the new utxoset bucket as needed.
	err := db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(v2BucketName)
		return err
	})
	if err != nil {
		return err
	}

	// doBatch contains the primary logic for upgrading the utxoset from version
	// 1 to 2 in batches.  This is done because attempting to migrate in a
	// single database transaction could result in massive memory usage and
	// could potentially crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v1Bucket := meta.Bucket(v1BucketName)
		if v1Bucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v1BucketName)
		}

		v2Bucket := meta.Bucket(v2BucketName)
		if v2Bucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v2BucketName)
		}

		// Migrate utxoset entries so long as the max number of entries for this
		// batch has not been exceeded.
		var logProgress bool
		var numMigrated, numIterated uint32
		err := v1Bucket.ForEach(func(key, oldSerialized []byte) error {
			if interruptRequested(ctx) {
				logProgress = true
				return errInterruptRequested
			}

			if numMigrated >= maxEntries {
				logProgress = true
				return errBatchFinished
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				return nil
			}
			resumeOffset++

			// Skip entries that have already been migrated in previous
			// interrupted upgrades.
			if v2Bucket.Get(key) != nil {
				return nil
			}

			// Copy the existing serialized bytes so they can be mutated and
			// rewritten to the new bucket as needed.
			serialized := make([]byte, len(oldSerialized))
			copy(serialized, oldSerialized)

			// The legacy version 1 unspent transaction output (utxo) set
			// consists of an entry for each transaction which contains a utxo
			// serialized using a format that is highly optimized to reduce
			// space using domain specific compression algorithms.
			//
			// The legacy format for this entry is roughly:
			//
			//   <version><height><index><flags><rest of data>
			//
			//   Field                 Type     Size
			//   transaction version   VLQ      variable
			//   block height          VLQ      variable
			//   block index           VLQ      variable
			//   flags                 VLQ      variable (only ever 1 byte)
			//   rest of data...
			//
			// The legacy serialized flags code format is:
			//   bit  0   - containing transaction is a coinbase
			//   bit  1   - containing transaction has an expiry
			//   bits 2-3 - transaction type
			//   bit  4   - is fully spent
			//   bits 5-7 - unused
			//
			// Given the migration only needs to move the fully spent bit from
			// bit 4 to bit 6, the following specifically finds and modifies the
			// relevant byte while leaving everything else untouched.

			// Deserialize the tx version, block height, and block index to
			// locate the offset of the flags byte that needs to be modified.
			_, bytesRead := deserializeVLQ(serialized)
			offset := bytesRead
			if offset >= len(serialized) {
				return errDeserialize("unexpected end of data after version")
			}
			_, bytesRead = deserializeVLQ(serialized[offset:])
			offset += bytesRead
			if offset >= len(serialized) {
				return errDeserialize("unexpected end of data after height")
			}
			_, bytesRead = deserializeVLQ(serialized[offset:])
			offset += bytesRead
			if offset >= len(serialized) {
				return errDeserialize("unexpected end of data after index")
			}

			// Migrate flags to the new format.
			const v1FullySpentFlag = 1 << 4
			const v2FullySpentFlag = 1 << 6
			flags64, bytesRead := deserializeVLQ(serialized[offset:])
			if bytesRead != 1 {
				str := fmt.Sprintf("unexpected flags size -- got %d, want 1",
					bytesRead)
				return errDeserialize(str)
			}
			flags := byte(flags64)
			fullySpent := flags&v1FullySpentFlag != 0
			flags &^= v1FullySpentFlag
			if fullySpent {
				flags |= v2FullySpentFlag
			}
			serialized[offset] = flags

			// Write the entry serialized with the new format to the new bucket.
			err = v2Bucket.Put(key, serialized)
			if err != nil {
				return err
			}

			numMigrated++
			return nil
		})
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating utxoset.  Total entries: %d in %v", totalMigrated,
		elapsed)

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Drop version 1 utxoset.
	log.Info("Removing old utxoset entries...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, db, v1BucketName, "old utxoset")
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing old utxoset entries in %v", elapsed)
	return nil
}

// migrateSpendJournalVersion1To2 migrates all spend journal entries from the v1
// bucket to a v2 bucket and removes the old v1 bucket.  As compared to the v1
// spend journal, the v2 spend journal moves the bit which defines whether or
// not a tx is fully spent from bit 4 to bit 6.
//
// The spend journal is guaranteed to be fully updated if this returns without
// failure.
func migrateSpendJournalVersion1To2(ctx context.Context, db database.DB) error {
	// Hardcoded bucket and key names so updates do not affect old upgrades.
	v1BucketName := []byte("spendjournal")
	v2BucketName := []byte("spendjournalv2")

	log.Info("Migrating database spend journal.  This may take a while...")
	start := time.Now()

	// Create the new spend journal bucket as needed.
	err := db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(v2BucketName)
		return err
	})
	if err != nil {
		return err
	}

	// doBatch contains the primary logic for upgrading the spend journal from
	// version 1 to 2 in batches.  This is done because attempting to migrate in
	// a single database transaction could result in massive memory usage and
	// could potentially crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v1Bucket := meta.Bucket(v1BucketName)
		if v1Bucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v1BucketName)
		}

		v2Bucket := meta.Bucket(v2BucketName)
		if v2Bucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v2BucketName)
		}

		// Migrate spend journal entries so long as the max number of entries
		// for this batch has not been exceeded.
		var logProgress bool
		var numMigrated, numIterated uint32
		err := v1Bucket.ForEach(func(key, oldSerialized []byte) error {
			if interruptRequested(ctx) {
				logProgress = true
				return errInterruptRequested
			}

			if numMigrated >= maxEntries {
				logProgress = true
				return errBatchFinished
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				return nil
			}
			resumeOffset++

			// Skip entries that have already been migrated in previous
			// interrupted upgrades.
			if v2Bucket.Get(key) != nil {
				return nil
			}

			// Copy the existing serialized bytes so they can be mutated and
			// rewritten to the new bucket as needed.
			serialized := make([]byte, len(oldSerialized))
			copy(serialized, oldSerialized)

			// The legacy version 1 transaction spend journal consists of an
			// entry for each block connected to the main chain which contains
			// the transaction outputs the block spends serialized such that the
			// order is the reverse of the order they were spent.
			//
			// The legacy format for this entry is roughly:
			//
			//   [<flags><script version><compressed pkscript><optional data>],...
			//
			// The legacy optional data is only present if the flags indicate
			// the transaction is fully spent (bit 4 in legacy format) and its
			// format is roughly:
			//
			//   <tx version><optional stake data>
			//
			// The legacy optional stake data is only present if the flags
			// indicate the transaction type is a ticket and its format is
			// roughly:
			//
			//   <num outputs>[<amount><script version><script len><script>],...
			//
			//   Field                   Type           Size
			//   flags                   VLQ            variable (always 1 byte)
			//   script version          VLQ            variable
			//   compressed pkscript     []byte         variable
			//   optional data (only present if flags indicates fully spent)
			//     transaction version   VLQ            variable
			//     stake data (only present if flags indicates tx type ticket)
			//       num outputs         VLQ            variable
			//       output info
			//         amount            VLQ            variable
			//         script version    VLQ            variable
			//         script len        VLQ            variable
			//         script            []byte         variable
			//
			// The legacy serialized flags code format is:
			//
			//   bit  0   - containing transaction is a coinbase
			//   bit  1   - containing transaction has an expiry
			//   bits 2-3 - transaction type
			//   bit  4   - is fully spent
			//   bits 5-7 - unused
			//
			// Given the migration only needs to move the fully spent bit from
			// bit 4 to bit 6, the following specifically finds and modifies the
			// relevant flags bytes while leaving everything else untouched.
			const (
				v1FullySpentFlag = 1 << 4
				v1TxTypeMask     = 0x0c
				v1TxTypeShift    = 2
				v1TxTypeTicket   = 1
				v1CompressionVer = 1
				v2FullySpentFlag = 1 << 6
			)
			var offset int
			for offset != len(serialized) {
				// Migrate the flags for the entry to the new format.
				if offset >= len(serialized) {
					str := "unexpected end of spend journal entry"
					return errDeserialize(str)
				}
				flags64, bytesRead := deserializeVLQ(serialized[offset:])
				if bytesRead != 1 {
					str := fmt.Sprintf("unexpected flags size -- got %d, want 1",
						bytesRead)
					return errDeserialize(str)
				}
				flags := byte(flags64)
				fullySpent := flags&v1FullySpentFlag != 0
				txType := (flags & v1TxTypeMask) >> v1TxTypeShift
				flags &^= v1FullySpentFlag
				if fullySpent {
					flags |= v2FullySpentFlag
				}
				serialized[offset] = flags
				offset += bytesRead

				// Deserialize the compressed txout, tx version, and minimal
				// outputs for tickets as needed to locate the offset of the
				// next flags byte that needs to be modified.
				if offset >= len(serialized) {
					str := "unexpected end of data after flags"
					return errDeserialize(str)
				}
				_, bytesRead = deserializeVLQ(serialized[offset:])
				offset += bytesRead
				if offset >= len(serialized) {
					str := "unexpected end of data after script version"
					return errDeserialize(str)
				}
				scriptSize := decodeCompressedScriptSizeV1(serialized[offset:])
				offset += scriptSize
				if fullySpent {
					if offset >= len(serialized) {
						str := "unexpected end of data after script size"
						return errDeserialize(str)
					}
					_, bytesRead := deserializeVLQ(serialized[offset:])
					offset += bytesRead
					if txType == v1TxTypeTicket {
						if offset >= len(serialized) {
							str := "unexpected end of data after tx version"
							return errDeserialize(str)
						}
						sz, err := determineMinimalOutputsSizeV1(
							serialized[offset:])
						if err != nil {
							return err
						}
						offset += sz
					}
				}
			}

			// Write the entry serialized with the new format to the new bucket.
			err = v2Bucket.Put(key, serialized)
			if err != nil {
				return err
			}

			numMigrated++
			return nil
		})
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating spend journal.  Total entries: %d in %v",
		totalMigrated, elapsed)

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Drop version 1 spend journal.
	log.Info("Removing old spend journal entries...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, db, v1BucketName, "old spend journal")
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing old spend journal entries in %v", elapsed)

	return nil
}

// initializeTreasuryBuckets creates the buckets that house the treasury account
// and spend information as needed.
func initializeTreasuryBuckets(db database.DB) error {
	// Hardcoded key names so updates do not affect old upgrades.
	treasuryBucketName := []byte("treasury")
	treasuryTSpendBucketName := []byte("tspend")

	// Create the new treasury buckets as needed.
	return db.Update(func(dbTx database.Tx) error {
		meta := dbTx.Metadata()
		_, err := meta.CreateBucketIfNotExists(treasuryBucketName)
		if err != nil {
			return err
		}
		_, err = meta.CreateBucketIfNotExists(treasuryTSpendBucketName)
		return err
	})
}

// upgradeToVersion7 upgrades a version 6 blockchain database to version 7.
func upgradeToVersion7(ctx context.Context, db database.DB, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 7...")
	start := time.Now()

	// Create the new treasury buckets as needed.
	if err := initializeTreasuryBuckets(db); err != nil {
		return err
	}

	// Migrate the utxoset to version 2.
	v2DoneKeyName := []byte("utxosetv2done")
	err := runUpgradeStageOnce(ctx, db, v2DoneKeyName, func() error {
		return migrateUtxoSetVersion1To2(ctx, db)
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Migrate the spend journal to version 2.
	if err := migrateSpendJournalVersion1To2(ctx, db); err != nil {
		return err
	}

	// Update and persist the database versions and remove upgrade progress
	// tracking keys.
	err = db.Update(func(dbTx database.Tx) error {
		err := dbTx.Metadata().Delete(v2DoneKeyName)
		if err != nil {
			return err
		}

		dbInfo.version = 7
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// clearFailedBlockFlagsV3 unmarks all blocks in a version 3 block index
// previously marked failed so they are eligible for validation again under new
// consensus rules.  This ensures clients that did not update prior to new rules
// activating are able to automatically recover under the new rules without
// having to download the entire chain again.
func clearFailedBlockFlagsV3(ctx context.Context, db database.DB) error {
	// Hardcoded bucket name so updates do not affect old upgrades.
	v3BucketName := []byte("blockidxv3")

	log.Info("Reindexing block information in the database.  This may take a " +
		"while...")
	start := time.Now()

	// doBatch contains the primary logic for updating the block index in
	// batches.  This is done because attempting to migrate in a single database
	// transaction could result in massive memory usage and could potentially
	// crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalUpdated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v3BlockIdxBucket := meta.Bucket(v3BucketName)
		if v3BlockIdxBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v3BucketName)
		}

		// Update block index entries so long as the max number of entries for
		// this batch has not been exceeded.
		var logProgress bool
		var numUpdated, numIterated uint32
		err := v3BlockIdxBucket.ForEach(func(key, oldSerialized []byte) error {
			if interruptRequested(ctx) {
				logProgress = true
				return errInterruptRequested
			}

			if numUpdated >= maxEntries {
				logProgress = true
				return errBatchFinished
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				return nil
			}
			resumeOffset++

			// Copy the existing serialized bytes so they can be mutated and
			// rewritten to the new bucket as needed.
			serialized := make([]byte, len(oldSerialized))
			copy(serialized, oldSerialized)

			// The version 3 block index consists of an entry for every known
			// block.
			//
			// The serialized value format is roughly:
			//
			//   <block header><status><rest of data>
			//
			//   Field              Type                Size
			//   block header       wire.BlockHeader    180 bytes
			//   status             blockStatus         1 byte
			//   rest of data...
			//
			// Given the status field is the only thing that needs to be
			// modified, the following specifically finds and modifies the
			// relevant byte while leaving everything else untouched.

			// Mark the block index entry as eligible for validation again.
			const (
				blockHdrSize            = 180
				v3StatusValidateFailed  = 1 << 2
				v3StatusInvalidAncestor = 1 << 3
			)
			offset := blockHdrSize
			if offset+1 > len(serialized) {
				return errDeserialize("unexpected end of data while reading " +
					"status")
			}
			origStatus := serialized[offset]
			newStatus := origStatus
			newStatus &^= v3StatusValidateFailed | v3StatusInvalidAncestor
			serialized[offset] = newStatus
			if newStatus != origStatus {
				err := v3BlockIdxBucket.Put(key, serialized)
				if err != nil {
					return err
				}
			}

			numUpdated++
			return nil
		})
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numUpdated > 0 {
			totalUpdated += uint64(numUpdated)
			log.Infof("Updated %d entries (%d total)", numUpdated, totalUpdated)
		}
		return isFullyDone, err
	}

	// Update all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done updating block index.  Total entries: %d in %v",
		totalUpdated, elapsed)
	return nil
}

// upgradeToVersion8 upgrades a version 7 blockchain database to version 8.
func upgradeToVersion8(ctx context.Context, db database.DB, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 8...")
	start := time.Now()

	// Ensure the treasury buckets are created for version 7 databases.  They
	// ordinarily will have already been created during the upgrade to version 7
	// above, however, due to a bug in a release candidate, they might not have
	// been, so this is a relatively simple hack to ensure anyone in that
	// intermediate state is upgraded properly without needing to redownload the
	// chain.
	if err := initializeTreasuryBuckets(db); err != nil {
		return err
	}

	// Unmark all blocks previously marked failed so they are eligible for
	// validation again under the new consensus rules.
	if err := clearFailedBlockFlagsV3(ctx, db); err != nil {
		return err
	}

	// Update and persist the database versions.
	err := db.Update(func(dbTx database.Tx) error {
		dbInfo.version = 8
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// migrateUtxoSetVersion2To3 migrates all utxoset entries from the v2 bucket to
// a v3 bucket and removes the old v2 bucket.
//
// The utxoset is guaranteed to be fully updated if this returns without
// failure.
func migrateUtxoSetVersion2To3(ctx context.Context, db database.DB) error {
	// Hardcoded bucket and key names so updates do not affect old upgrades.
	v2BucketName := []byte("utxosetv2")
	v3BucketName := []byte("utxosetv3")

	log.Info("Migrating database utxo set.  This may take a while...")
	start := time.Now()

	// Create the new utxoset bucket as needed.
	err := db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(v3BucketName)
		return err
	})
	if err != nil {
		return err
	}

	// doBatch contains the primary logic for upgrading the utxoset from version
	// 2 to 3 in batches.  This is done because attempting to migrate in a
	// single database transaction could result in massive memory usage and
	// could potentially crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v2Bucket := meta.Bucket(v2BucketName)
		if v2Bucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v2BucketName)
		}

		v3Bucket := meta.Bucket(v3BucketName)
		if v3Bucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", v3BucketName)
		}

		// Migrate utxoset entries so long as the max number of entries for this
		// batch has not been exceeded.
		var logProgress bool
		var numMigrated, numIterated uint32
		err := v2Bucket.ForEach(func(oldKey, oldSerialized []byte) error {
			if interruptRequested(ctx) {
				logProgress = true
				return errInterruptRequested
			}

			if numMigrated >= maxEntries {
				logProgress = true
				return errBatchFinished
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				return nil
			}
			resumeOffset++

			// Old key was the transaction hash.
			var txHash chainhash.Hash
			copy(txHash[:], oldKey)

			// Deserialize the legacy V2 entry which included all utxos for the given
			// transaction.
			//
			// The legacy V2 format is as follows:
			//
			//   <version><height><header code><unspentness bitmap>
			//     [<compressed txouts>,...]
			//
			//   Field                 Type     Size
			//   transaction version   VLQ      variable
			//   block height          VLQ      variable
			//   block index           VLQ      variable
			//   flags                 VLQ      variable (currently 1 byte)
			//   header code           VLQ      variable
			//   unspentness bitmap    []byte   variable
			//   compressed txouts
			//     compressed amount   VLQ      variable
			//     script version      VLQ      variable
			//     compressed script   []byte   variable
			//   stakeExtra            []byte   variable
			//
			// The serialized flags code format is:
			//   bit  0   - containing transaction is a coinbase
			//   bit  1   - containing transaction has an expiry
			//   bits 2-4 - transaction type
			//   bit  5   - unused
			//   bit  6   - is fully spent
			//   bit  7   - unused
			//
			// The serialized header code format is:
			//   bit 0 - output zero is unspent
			//   bit 1 - output one is unspent
			//   bits 2-x - number of bytes in unspentness bitmap.  When both bits 1
			//     and 2 are unset, it encodes N-1 since there must be at least one
			//     unspent output.
			//
			// The stake extra field contains minimally encoded outputs for all
			// consensus-related outputs in the stake transaction. It is only
			// encoded for tickets.

			// Deserialize the version.
			//
			// NOTE: Ignore version since it is no longer used in the new format.
			_, bytesRead := deserializeVLQ(oldSerialized)
			offset := bytesRead
			if offset >= len(oldSerialized) {
				return errDeserialize("unexpected end of data after version")
			}

			// Deserialize the block height.
			blockHeight, bytesRead := deserializeVLQ(oldSerialized[offset:])
			offset += bytesRead
			if offset >= len(oldSerialized) {
				return errDeserialize("unexpected end of data after height")
			}

			// Deserialize the block index.
			blockIndex, bytesRead := deserializeVLQ(oldSerialized[offset:])
			offset += bytesRead
			if offset >= len(oldSerialized) {
				return errDeserialize("unexpected end of data after index")
			}

			// Deserialize the flags.
			flags, bytesRead := deserializeVLQ(oldSerialized[offset:])
			offset += bytesRead
			if offset >= len(oldSerialized) {
				return errDeserialize("unexpected end of data after flags")
			}
			// Decode the flags.  The format is:
			//     0: Is coinbase
			//     1: Has an expiry
			//   2-4: Transaction type
			//     5: Unused
			//     6: Fully spent
			//     7: Unused
			isCoinBase := flags&0x01 != 0
			hasExpiry := flags&(1<<1) != 0
			txType := stake.TxType((flags & 0x1c) >> 2)

			// Deserialize the header code.
			code, bytesRead := deserializeVLQ(oldSerialized[offset:])
			offset += bytesRead
			if offset >= len(oldSerialized) {
				return errDeserialize("unexpected end of data after header")
			}

			// Decode the header code.
			//
			// Bit 0 indicates output 0 is unspent.
			// Bit 1 indicates output 1 is unspent.
			// Bits 2-x encodes the number of non-zero unspentness bitmap bytes that
			// follow.  When both output 0 and 1 are spent, it encodes N-1.
			output0Unspent := code&0x01 != 0
			output1Unspent := code&0x02 != 0
			numBitmapBytes := code >> 2
			if !output0Unspent && !output1Unspent {
				numBitmapBytes++
			}

			// Ensure there are enough bytes left to deserialize the unspentness
			// bitmap.
			if uint64(len(oldSerialized[offset:])) < numBitmapBytes {
				return errDeserialize("unexpected end of data for " +
					"unspentness bitmap")
			}

			// Add sparse outputs for unspent outputs 0 and 1 as needed based on the
			// details provided by the header code.
			var outputIndexes []uint32
			if output0Unspent {
				outputIndexes = append(outputIndexes, 0)
			}
			if output1Unspent {
				outputIndexes = append(outputIndexes, 1)
			}

			// Decode the unspentness bitmap adding a sparse output for each unspent
			// output.
			for i := uint32(0); i < uint32(numBitmapBytes); i++ {
				unspentBits := oldSerialized[offset]
				for j := uint32(0); j < 8; j++ {
					if unspentBits&0x01 != 0 {
						// The first 2 outputs are encoded via the
						// header code, so adjust the output number
						// accordingly.
						outputNum := 2 + i*8 + j
						outputIndexes = append(outputIndexes, outputNum)
					}
					unspentBits >>= 1
				}
				offset++
			}

			// Create a map to hold all of the converted outputs for the entry.
			type convertedOut struct {
				compressedAmount uint64
				compressedScript []byte
				scriptVersion    uint64
			}
			outputs := make(map[uint32]*convertedOut)

			// Decode and add all of the outputs.
			for _, outputIndex := range outputIndexes {
				// Deserialize the compressed amount and ensure there are bytes
				// remaining for the compressed script.
				compressedAmount, bytesRead := deserializeVLQ(oldSerialized[offset:])
				if bytesRead == 0 {
					return errDeserialize("unexpected end of data during decoding " +
						"(compressed amount)")
				}
				offset += bytesRead

				// Decode the script version.
				scriptVersion, bytesRead := deserializeVLQ(oldSerialized[offset:])
				if bytesRead == 0 {
					return errDeserialize("unexpected end of data during decoding " +
						"(script version)")
				}
				offset += bytesRead

				// Decode the compressed script size and ensure there are enough bytes
				// left in the slice for it.
				scriptSize := decodeCompressedScriptSizeV1(oldSerialized[offset:])
				// Note: scriptSize == 0 is OK (an empty compressed script is valid)
				if scriptSize < 0 {
					return errDeserialize("negative script size")
				}
				if len(oldSerialized[offset:]) < scriptSize {
					return errDeserialize(fmt.Sprintf("unexpected end of "+
						"data after script size (got %v, need %v)",
						len(oldSerialized[offset:]), scriptSize))
				}
				compressedScript := oldSerialized[offset : offset+scriptSize]

				offset += scriptSize

				// Create a converted utxo entry with the details deserialized above.
				outputs[outputIndex] = &convertedOut{
					compressedAmount: compressedAmount,
					compressedScript: compressedScript,
					scriptVersion:    scriptVersion,
				}
			}
			// Read the minimal outputs if this was a ticket.
			var ticketMinOuts []byte
			if txType == stake.TxTypeSStx {
				sz, err := determineMinimalOutputsSizeV1(oldSerialized[offset:])
				if err != nil {
					return errDeserialize(fmt.Sprintf("unable to decode "+
						"ticket outputs: %v", err))
				}

				// Read the ticket minimal outputs.
				ticketMinOuts = oldSerialized[offset : offset+sz]
			}

			// Create V3 utxo entries with the details deserialized above.
			//
			// The V3 serialized key format is:
			//
			//   <hash><tree><output index>
			//
			//   Field                Type             Size
			//   hash                 chainhash.Hash   chainhash.HashSize
			//   tree                 VLQ              variable
			//   output index         VLQ              variable
			//
			// The V3 serialized value format is:
			//
			//   <block height><block index><flags><compressed txout>
			//   OPTIONAL: [<ticket min outs>]
			//
			//   Field                Type     Size
			//   block height         VLQ      variable
			//   block index          VLQ      variable
			//   flags                VLQ      variable
			//   compressed txout
			//     compressed amount   VLQ      variable
			//     script version      VLQ      variable
			//     compressed script   []byte   variable
			//
			//   OPTIONAL
			//     ticketMinOuts      []byte         variable
			//
			// The serialized flags format is:
			//   bit  0     - containing transaction is a coinbase
			//   bit  1     - containing transaction has an expiry
			//   bits 2-5   - transaction type
			//   bits 6-7   - unused
			//
			// The ticket min outs field contains minimally encoded outputs for all
			// outputs of a ticket transaction. It is only encoded for ticket outputs.
			for outputIndex, output := range outputs {
				// Encode the V3 utxo flags.
				encodedFlags := uint8(txType) << 2
				if isCoinBase {
					encodedFlags |= 1
				}
				if hasExpiry {
					encodedFlags |= 1 << 1
				}

				// Calculate the size needed to serialize the entry.
				size := serializeSizeVLQ(blockHeight) +
					serializeSizeVLQ(blockIndex) +
					serializeSizeVLQ(uint64(encodedFlags)) +
					serializeSizeVLQ(output.compressedAmount) +
					serializeSizeVLQ(output.scriptVersion) +
					len(output.compressedScript)

				// Only store the ticket minimal outputs in the ticket submission
				// output (output index 0).
				if outputIndex == 0 {
					size += len(ticketMinOuts)
				}

				// Serialize the entry.
				reserialized := make([]byte, size)
				reserializedOffset := putVLQ(reserialized, blockHeight)
				reserializedOffset += putVLQ(reserialized[reserializedOffset:],
					blockIndex)
				reserializedOffset += putVLQ(reserialized[reserializedOffset:],
					uint64(encodedFlags))
				reserializedOffset += putVLQ(reserialized[reserializedOffset:],
					output.compressedAmount)
				reserializedOffset += putVLQ(reserialized[reserializedOffset:],
					output.scriptVersion)
				copy(reserialized[reserializedOffset:], output.compressedScript)
				reserializedOffset += len(output.compressedScript)

				// Only store the ticket minimal outputs in the ticket submission
				// output (output index 0).
				if ticketMinOuts != nil && outputIndex == 0 {
					copy(reserialized[reserializedOffset:], ticketMinOuts)
				}

				// Create the key for the new entry.
				tree := wire.TxTreeRegular
				if txType != stake.TxTypeRegular {
					tree = wire.TxTreeStake
				}
				keySize := chainhash.HashSize + serializeSizeVLQ(uint64(tree)) +
					serializeSizeVLQ(uint64(outputIndex))
				key := make([]byte, keySize)
				copy(key, txHash[:])
				keyOffset := chainhash.HashSize
				keyOffset += putVLQ(key[keyOffset:], uint64(tree))
				putVLQ(key[keyOffset:], uint64(outputIndex))

				// Create the new entry in the V3 bucket.
				err = v3Bucket.Put(key, reserialized)
				if err != nil {
					return err
				}
			}

			numMigrated++
			return nil
		})
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating utxoset.  Total entries: %d in %v", totalMigrated,
		elapsed)

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Drop version 2 utxoset.
	log.Info("Removing old utxoset entries...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, db, v2BucketName, "old utxoset")
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing old utxoset entries in %v", elapsed)
	return nil
}

// migrateSpendJournalVersion2To3 migrates all spend journal entries from the v2
// bucket to a v3 bucket and removes the old v2 bucket.
//
// The spend journal entries are guaranteed to be fully updated if this returns
// without failure.
func migrateSpendJournalVersion2To3(ctx context.Context, b *BlockChain) error {
	// Hardcoded bucket and key names so updates do not affect old upgrades.
	v2SpendJournalBucketName := []byte("spendjournalv2")
	v3SpendJournalBucketName := []byte("spendjournalv3")
	v3BlockIndexBucketName := []byte("blockidxv3")
	v2UtxoSetBucketName := []byte("utxosetv2")
	tmpTxInfoBucketName := []byte("tmpTxInfo")

	log.Info("Migrating database spend journal.  This may take a while...")
	start := time.Now()

	// Create the new spend journal bucket as needed.
	err := b.db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(v3SpendJournalBucketName)
		return err
	})
	if err != nil {
		return err
	}

	// Create the temp bucket as needed.  This bucket is used to temporarily store
	// tx info for fully spent transactions.
	err = b.db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(tmpTxInfoBucketName)
		return err
	})
	if err != nil {
		return err
	}

	// doBatch contains the primary logic for upgrading the spend journal from
	// version 2 to 3 in batches.  This is done because attempting to migrate in a
	// single database transaction could result in massive memory usage and
	// could potentially crash on many systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()

		v2SpendJournalBucket := meta.Bucket(v2SpendJournalBucketName)
		if v2SpendJournalBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist",
				v2SpendJournalBucketName)
		}

		v3SpendJournalBucket := meta.Bucket(v3SpendJournalBucketName)
		if v3SpendJournalBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist",
				v3SpendJournalBucketName)
		}

		v3BlockIndexBucket := meta.Bucket(v3BlockIndexBucketName)
		if v3BlockIndexBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist",
				v3BlockIndexBucketName)
		}

		v2UtxoSetBucket := meta.Bucket(v2UtxoSetBucketName)
		if v2UtxoSetBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist",
				v2UtxoSetBucketName)
		}

		tmpTxInfoBucket := meta.Bucket(tmpTxInfoBucketName)
		if tmpTxInfoBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist",
				tmpTxInfoBucketName)
		}

		// Migrate spend journal entries so long as the max number of entries for
		// this batch has not been exceeded.
		//
		// Use the block index to iterate through every block in reverse order.  It
		// is necessary to read the blocks in reverse order since the V2 spend
		// journal conditionally stored tx info when the output was the last spent
		// output of the containing transaction.  Therefore, we need to find the
		// last spent outputs first and temporarily store the associated tx info so
		// that it is available when creating the corresponding V3 entries.
		var logProgress bool
		var numMigrated, numIterated uint32
		cursor := v3BlockIndexBucket.Cursor()
		for ok := cursor.Last(); ok; ok = cursor.Prev() {
			// Reset err on each iteration.
			err = nil

			if interruptRequested(ctx) {
				logProgress = true
				err = errInterruptRequested
				break
			}

			if numMigrated >= maxEntries {
				logProgress = true
				err = errBatchFinished
				break
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				continue
			}
			resumeOffset++

			// Deserialize the header from the V3 block index entry.  The header is
			// the first item in the serialized entry.
			serializedBlockIndexEntry := cursor.Value()
			if len(serializedBlockIndexEntry) < blockHdrSize {
				return false, errDeserialize("unexpected end of data while " +
					"reading block header")
			}
			hB := serializedBlockIndexEntry[0:blockHdrSize]
			var header wire.BlockHeader
			if err := header.Deserialize(bytes.NewReader(hB)); err != nil {
				return false, err
			}

			// Skip entries that have already been migrated in previous interrupted
			// upgrades.
			blockHash := header.BlockHash()
			if v3SpendJournalBucket.Get(blockHash[:]) != nil {
				continue
			}

			// Load the full block from the database.
			blockBytes, err := dbTx.FetchBlock(&blockHash)
			if err != nil {
				break
			}
			var msgBlock wire.MsgBlock
			if err = msgBlock.FromBytes(blockBytes); err != nil {
				break
			}

			// Determine if treasury agenda is active.
			isTreasuryEnabled := false
			if msgBlock.Header.Height > 0 {
				parentHash := msgBlock.Header.PrevBlock
				isTreasuryEnabled, err = b.IsTreasuryAgendaActive(&parentHash)
				if err != nil {
					break
				}
			}

			// Deserialize the legacy V2 spend journal entry.
			//
			// The legacy V2 format is as follows:
			//
			//   [<flags><script version><compressed pk script>],...
			//   OPTIONAL: [<txVersion><stakeExtra>]
			//
			//   Field                Type           Size
			//   flags                VLQ            byte
			//   scriptVersion        uint16         2 bytes
			//   pkScript             VLQ+[]byte     variable
			//
			//   OPTIONAL
			//     txVersion          VLQ            variable
			//     stakeExtra         []byte         variable
			//
			// The serialized flags code format is:
			//   bit  0   - containing transaction is a coinbase
			//   bit  1   - containing transaction has an expiry
			//   bits 2-4 - transaction type
			//   bit  5   - unused
			//   bit  6   - is fully spent
			//   bit  7   - unused
			//
			// The stake extra field contains minimally encoded outputs for all
			// consensus-related outputs in the stake transaction. It is only
			// encoded for tickets.
			//
			//   NOTE: The transaction version and flags are only encoded when the
			//   spent txout was the final unspent output of the containing
			//   transaction.  Otherwise, the header code will be 0 and the version is
			//   not serialized at all. This is done because that information is only
			//   needed when the utxo set no longer has it.

			// Get the serialized V2 data.
			v2Serialized := v2SpendJournalBucket.Get(blockHash[:])

			// Continue if there is no spend journal entry for the block.
			if v2Serialized == nil {
				continue
			}

			// Exclude the coinbase transaction since it can't spend anything.
			blockTxns := make([]*wire.MsgTx, 0, len(msgBlock.STransactions)+
				len(msgBlock.Transactions[1:]))
			if len(msgBlock.STransactions) > 0 && isTreasuryEnabled {
				// Skip treasury base and remove tspends.
				for _, v := range msgBlock.STransactions[1:] {
					if stake.IsTSpend(v) {
						continue
					}
					blockTxns = append(blockTxns, v)
				}
			} else {
				blockTxns = append(blockTxns, msgBlock.STransactions...)
			}
			blockTxns = append(blockTxns, msgBlock.Transactions[1:]...)

			// Calculate the total number of stxos.
			var numStxos int
			for _, tx := range blockTxns {
				if stake.IsSSGen(tx) {
					numStxos++
					continue
				}
				numStxos += len(tx.TxIn)
			}

			// If there is an empty spend journal entry for the block, create an empty
			// entry in the V3 bucket and continue.
			if len(v2Serialized) == 0 {
				// Ensure the block actually has no stxos.  This should never
				// happen unless there is database corruption or an empty entry
				// erroneously made its way into the database.
				if numStxos != 0 {
					return false, AssertError(fmt.Sprintf("mismatched spend "+
						"journal serialization - no serialization for "+
						"expected %d stxos", numStxos))
				}

				err = v3SpendJournalBucket.Put(blockHash[:], nil)
				if err != nil {
					return false, err
				}

				continue
			}

			// Create a slice to hold all of the converted stxos.
			type convertedStxo struct {
				compScript    []byte
				ticketMinOuts []byte
				scriptVersion uint16
				flags         uint8
				txOutIndex    uint32
			}
			stxos := make([]convertedStxo, numStxos)

			// Loop backwards through all transactions so everything is read in
			// reverse order to match the serialization order.
			stxoIdx := numStxos - 1
			offset := 0
			for txIdx := len(blockTxns) - 1; txIdx > -1; txIdx-- {
				tx := blockTxns[txIdx]
				isVote := stake.IsSSGen(tx)

				// Loop backwards through all of the transaction inputs and read
				// the associated stxo.
				for txInIdx := len(tx.TxIn) - 1; txInIdx > -1; txInIdx-- {
					// Skip stakebase since it has no input.
					if txInIdx == 0 && isVote {
						continue
					}

					txIn := tx.TxIn[txInIdx]
					stxo := &stxos[stxoIdx]
					stxoIdx--

					// Set the tx out index that the stxo is associated with.
					stxo.txOutIndex = txIn.PreviousOutPoint.Index

					// Deserialize the flags.
					flags, bytesRead := deserializeVLQ(v2Serialized[offset:])
					if bytesRead == 0 {
						return false, errDeserialize("unexpected end of data during " +
							"decoding (flags)")
					}
					offset += bytesRead

					// Decode the script version.
					scriptVersion, bytesRead := deserializeVLQ(v2Serialized[offset:])
					if bytesRead == 0 {
						return false, errDeserialize("unexpected end of " +
							"data during decoding (script version)")
					}
					offset += bytesRead
					stxo.scriptVersion = uint16(scriptVersion)

					// Decode the compressed script size and ensure there are enough bytes
					// left in the slice for it.
					scriptSize := decodeCompressedScriptSizeV1(v2Serialized[offset:])
					// Note: scriptSize == 0 is OK (an empty compressed script is valid)
					if scriptSize < 0 {
						return false, errDeserialize("negative script size")
					}
					if len(v2Serialized[offset:]) < scriptSize {
						return false, errDeserialize(fmt.Sprintf("unexpected end of "+
							"data after script size (got %v, need %v)",
							len(v2Serialized[offset:]), scriptSize))
					}
					compressedScript := v2Serialized[offset : offset+scriptSize]
					offset += scriptSize
					stxo.compScript = compressedScript

					// Deserialize the containing transaction if the flags indicate that
					// the transaction has been fully spent.
					// The flags format is:
					//   bit  0   - containing transaction is a coinbase
					//   bit  1   - containing transaction has an expiry
					//   bits 2-4 - transaction type
					//   bit  5   - unused
					//   bit  6   - is fully spent
					//   bit  7   - unused
					fullySpent := flags&(1<<6) != 0
					if fullySpent {
						txType := stake.TxType((flags & 0x1c) >> 2)
						stxo.flags = uint8(flags)

						// Unset bit 5 in case it was unexpectedly set, since bit 5 will be
						// used for the transaction type in the new format.
						stxo.flags &^= 1 << 5

						// Unset the spent flag since it is no longer needed in the new
						// version.
						stxo.flags &^= 1 << 6

						// Deserialize the version and ignore it since it is no longer
						// used in the new format.
						_, bytesRead := deserializeVLQ(v2Serialized[offset:])
						if bytesRead == 0 {
							return false, errDeserialize("unexpected end of " +
								"data during decoding (tx version)")
						}
						offset += bytesRead

						if txType == stake.TxTypeSStx {
							sz, err := determineMinimalOutputsSizeV1(v2Serialized[offset:])
							if err != nil {
								return false, errDeserialize(fmt.Sprintf("unable to decode "+
									"ticket outputs: %v", err))
							}

							// Read the ticket minimal outputs.
							stxo.ticketMinOuts = v2Serialized[offset : offset+sz]
							offset += sz
						}

						// Save the fully spent tx info in the temp bucket.  Store the
						// fields that were conditionally stored in V2, which includes flags
						// and ticket minimal outputs.
						//
						// The serialized format is:
						//   flags             VLQ            byte
						//   ticketMinOuts     []byte         variable
						//
						// The flags format is:
						//   bit  0   - containing transaction is a coinbase
						//   bit  1   - containing transaction has an expiry
						//   bits 2-5 - transaction type
						//   bits 6-7 - unused
						//
						// The ticket min outs field is only stored for tickets.
						size := serializeSizeVLQ(uint64(stxo.flags))
						if txType == stake.TxTypeSStx {
							size += len(stxo.ticketMinOuts)
						}
						target := make([]byte, size)
						tmpOffset := putVLQ(target, uint64(stxo.flags))
						if txType == stake.TxTypeSStx {
							copy(target[tmpOffset:], stxo.ticketMinOuts)
						}
						err = tmpTxInfoBucket.Put(txIn.PreviousOutPoint.Hash[:], target)
						if err != nil {
							return false, err
						}

						// Continue since we have everything we need for stxos that were
						// marked as fully spent.
						continue
					}

					// The stxo was not marked as fully spent (otherwise we already
					// continued above).  To get the missing tx info (flags and ticket
					// min outs), first check the tmp bucket, and if not found, then
					// check the utxo set.

					// First, check the temp bucket for the tx info.
					tmpSerialized := tmpTxInfoBucket.Get(txIn.PreviousOutPoint.Hash[:])
					if len(tmpSerialized) != 0 {
						flags, bytesRead := deserializeVLQ(tmpSerialized)
						tOffset := bytesRead
						stxo.flags = uint8(flags)

						// Decode the flags.  The format is:
						//   bit  0   - containing transaction is a coinbase
						//   bit  1   - containing transaction has an expiry
						//   bits 2-5 - transaction type
						//   bits 6-7 - unused
						txType := stake.TxType((flags & 0x3c) >> 2)
						// Read the minimal outputs if this was a ticket submission output.
						if txType == stake.TxTypeSStx && stxo.txOutIndex == 0 {
							sz, err := determineMinimalOutputsSizeV1(tmpSerialized[tOffset:])
							if err != nil {
								return false, errDeserialize(fmt.Sprintf("unable to decode "+
									"ticket outputs: %v", err))
							}

							// Read the ticket minimal outputs.
							stxo.ticketMinOuts = tmpSerialized[tOffset : tOffset+sz]
						}

						// Continue to the next stxo since the flags and ticket min outs
						// have now been set.
						continue
					}

					// If the temp bucket didn't have the tx info, check the utxo set for
					// the tx info.  The key for the V2 utxo set is the transaction hash.
					utxoSerialized := v2UtxoSetBucket.Get(txIn.PreviousOutPoint.Hash[:])

					// Deserialize the legacy V2 entry which included all utxos for the
					// given transaction.
					//
					// The legacy V2 format is as follows:
					//
					//   <version><height><header code><unspentness bitmap>
					//     [<compressed txouts>,...]
					//
					//   Field                 Type     Size
					//   transaction version   VLQ      variable
					//   block height          VLQ      variable
					//   block index           VLQ      variable
					//   flags                 VLQ      variable (currently 1 byte)
					//   header code           VLQ      variable
					//   unspentness bitmap    []byte   variable
					//   compressed txouts
					//     compressed amount   VLQ      variable
					//     script version      VLQ      variable
					//     compressed script   []byte   variable
					//   stakeExtra            []byte   variable
					//
					// The serialized flags code format is:
					//   bit  0   - containing transaction is a coinbase
					//   bit  1   - containing transaction has an expiry
					//   bits 2-4 - transaction type
					//   bit  5   - unused
					//   bit  6   - is fully spent
					//   bit  7   - unused
					//
					// The serialized header code format is:
					//   bit 0 - output zero is unspent
					//   bit 1 - output one is unspent
					//   bits 2-x - number of bytes in unspentness bitmap.  When both bits
					//     1 and 2 are unset, it encodes N-1 since there must be at least
					//     one unspent output.
					//
					// The stake extra field contains minimally encoded outputs for all
					// consensus-related outputs in the stake transaction. It is only
					// encoded for tickets.

					// Deserialize the version.  Ignore it since we don't need it.
					_, bytesRead = deserializeVLQ(utxoSerialized)
					utxoOffset := bytesRead
					if utxoOffset >= len(utxoSerialized) {
						return false, errDeserialize("unexpected end of data after version")
					}

					// Deserialize the block height.  Ignore it since we don't need it.
					_, bytesRead = deserializeVLQ(utxoSerialized[utxoOffset:])
					utxoOffset += bytesRead
					if utxoOffset >= len(utxoSerialized) {
						return false, errDeserialize("unexpected end of data after height")
					}

					// Deserialize the block index. Ignore it since we don't need it.
					_, bytesRead = deserializeVLQ(utxoSerialized[utxoOffset:])
					utxoOffset += bytesRead
					if utxoOffset >= len(utxoSerialized) {
						return false, errDeserialize("unexpected end of data after index")
					}

					// Deserialize the flags.  The flags format is:
					//     0: Is coinbase
					//     1: Has an expiry
					//   2-4: Transaction type
					//     5: Unused
					//     6: Fully spent
					//     7: Unused
					v2UtxoFlags, bytesRead := deserializeVLQ(utxoSerialized[utxoOffset:])
					utxoOffset += bytesRead
					if utxoOffset >= len(utxoSerialized) {
						return false, errDeserialize("unexpected end of data after flags")
					}

					// Unset bit 5 in case it was unexpectedly set, since bit 5 will be
					// used for the transaction type in the new format.
					v2UtxoFlags &^= 1 << 5

					// Unset the fully spent flag since it is no longer needed in the new
					// version.  It shouldn't have ever been set, since spent utxos are
					// not serialized, but unset it just in case.
					v2UtxoFlags &^= 1 << 6

					// Set the flags on the stxo.
					stxo.flags = uint8(v2UtxoFlags)

					// Deserialize the header code.
					code, bytesRead := deserializeVLQ(utxoSerialized[utxoOffset:])
					utxoOffset += bytesRead
					if utxoOffset >= len(utxoSerialized) {
						return false, errDeserialize("unexpected end of data after header")
					}

					// Decode the header code.
					//
					// Bit 0 indicates output 0 is unspent.
					// Bit 1 indicates output 1 is unspent.
					// Bits 2-x encodes the number of non-zero unspentness bitmap bytes
					// that follow.  When both output 0 and 1 are spent, it encodes N-1.
					output0Unspent := code&0x01 != 0
					output1Unspent := code&0x02 != 0
					numBitmapBytes := code >> 2
					if !output0Unspent && !output1Unspent {
						numBitmapBytes++
					}

					// Ensure there are enough bytes left to deserialize the unspentness
					// bitmap.
					if uint64(len(utxoSerialized[utxoOffset:])) < numBitmapBytes {
						return false, errDeserialize("unexpected end of data for " +
							"unspentness bitmap")
					}

					// Add sparse outputs for unspent outputs 0 and 1 as needed based on
					// the details provided by the header code.
					var outputIndexes []uint32
					if output0Unspent {
						outputIndexes = append(outputIndexes, 0)
					}
					if output1Unspent {
						outputIndexes = append(outputIndexes, 1)
					}

					// Decode the unspentness bitmap adding a sparse output for each
					// unspent output.
					for i := uint32(0); i < uint32(numBitmapBytes); i++ {
						unspentBits := utxoSerialized[utxoOffset]
						for j := uint32(0); j < 8; j++ {
							if unspentBits&0x01 != 0 {
								// The first 2 outputs are encoded via the
								// header code, so adjust the output number
								// accordingly.
								outputNum := 2 + i*8 + j
								outputIndexes = append(outputIndexes, outputNum)
							}
							unspentBits >>= 1
						}
						utxoOffset++
					}

					// Decode and add all of the outputs.
					for range outputIndexes {
						// Deserialize the compressed amount.  Ignore it since we don't need
						// it.
						_, bytesRead = deserializeVLQ(utxoSerialized[utxoOffset:])
						if bytesRead == 0 {
							return false, errDeserialize("unexpected end of data during " +
								"decoding (compressed amount)")
						}
						utxoOffset += bytesRead

						// Decode the script version.  Ignore it since we don't need it.
						_, bytesRead = deserializeVLQ(utxoSerialized[utxoOffset:])
						if bytesRead == 0 {
							return false, errDeserialize("unexpected end of data during " +
								"decoding (script version)")
						}
						utxoOffset += bytesRead

						// Decode the compressed script size and ensure there are enough
						// bytes left in the slice for it.
						size := decodeCompressedScriptSizeV1(utxoSerialized[utxoOffset:])
						// Note: size == 0 is OK (an empty compressed script is valid)
						if size < 0 {
							return false, errDeserialize("negative script size")
						}
						if len(utxoSerialized[utxoOffset:]) < size {
							return false, errDeserialize(fmt.Sprintf("unexpected end of "+
								"data after script size (got %v, need %v)",
								len(utxoSerialized[utxoOffset:]), size))
						}

						utxoOffset += size
					}

					// Determine the tx type from the flags.  The flags format is:
					//     0: Is coinbase
					//     1: Has an expiry
					//   2-4: Transaction type
					//     5: Unused
					//     6: Fully spent
					//     7: Unused
					txType := stake.TxType((v2UtxoFlags & 0x1c) >> 2)

					// Read the minimal outputs if this was a ticket.
					if txType == stake.TxTypeSStx {
						sz, err := determineMinimalOutputsSizeV1(utxoSerialized[utxoOffset:])
						if err != nil {
							return false, errDeserialize(fmt.Sprintf("unable to decode "+
								"ticket outputs: %v", err))
						}

						// Read the ticket minimal outputs.  We only need the ticket minimal
						// outputs for the ticket submission output (output 0) in the new
						// format.
						if stxo.txOutIndex == 0 {
							stxo.ticketMinOuts = utxoSerialized[utxoOffset : utxoOffset+sz]
						}
					}
				}
			}

			// Create a V3 spend journal entry with the details deserialized above.
			//
			// The V3 serialized format is:
			//
			//   [<flags><script version><compressed pk script>],...
			//   OPTIONAL: <ticket min outs>
			//
			//   Field                Type           Size
			//   flags                VLQ            byte
			//   scriptVersion        uint16         2 bytes
			//   pkScript             VLQ+[]byte     variable
			//
			//   OPTIONAL
			//     ticketMinOuts      []byte         variable
			//
			// The serialized flags format is:
			//   bit  0     - containing transaction is a coinbase
			//   bit  1     - containing transaction has an expiry
			//   bits 2-5   - transaction type
			//   bits 6-7   - unused
			//
			// The ticket min outs field contains minimally encoded outputs for all
			// outputs of a ticket transaction. It is only encoded for ticket
			// submission outputs.

			// Calculate the size needed to serialize the entire journal entry.
			var size int
			sizes := make([]int, 0, len(stxos))
			for i := range stxos {
				stxo := &stxos[i]
				sz := serializeSizeVLQ(uint64(stxo.flags)) +
					serializeSizeVLQ(uint64(stxo.scriptVersion)) +
					len(stxo.compScript)

				// Determine the tx type from the flags.  The flags format is:
				//   bit  0     - containing transaction is a coinbase
				//   bit  1     - containing transaction has an expiry
				//   bits 2-5   - transaction type
				//   bits 6-7   - unused
				txType := stake.TxType((stxo.flags & 0x3c) >> 2)

				// Only store the minimal outputs if this was a ticket submission
				// output.
				if txType == stake.TxTypeSStx && stxo.txOutIndex == 0 {
					sz += len(stxo.ticketMinOuts)
				}

				sizes = append(sizes, sz)
				size += sz
			}
			reserialized := make([]byte, size)

			// Serialize each individual stxo directly into the slice in reverse
			// order one after the other.
			offset = 0
			for i := len(stxos) - 1; i > -1; i-- {
				oldOffset := offset
				stxo := &stxos[i]
				offset += putVLQ(reserialized[offset:], uint64(stxo.flags))
				offset += putVLQ(reserialized[offset:], uint64(stxo.scriptVersion))
				copy(reserialized[offset:], stxo.compScript)
				offset += len(stxo.compScript)

				// Determine the tx type from the flags.  The flags format is:
				//   bit  0     - containing transaction is a coinbase
				//   bit  1     - containing transaction has an expiry
				//   bits 2-5   - transaction type
				//   bits 6-7   - unused
				txType := stake.TxType((stxo.flags & 0x3c) >> 2)

				// Only store the minimal outputs if this was a ticket submission
				// output.
				if txType == stake.TxTypeSStx && stxo.txOutIndex == 0 {
					if len(stxo.ticketMinOuts) == 0 {
						return false, errDeserialize("missing ticket minimal output data " +
							"when serializing V3 stxo entry")
					}

					copy(reserialized[offset:], stxo.ticketMinOuts)
					offset += len(stxo.ticketMinOuts)
				}

				if offset-oldOffset != sizes[i] {
					return false, AssertError(fmt.Sprintf("bad write; expect sz %v, "+
						"got sz %v (wrote %x)", sizes[i], offset-oldOffset,
						reserialized[oldOffset:offset]))
				}
			}

			// Create the new entry in the V3 bucket.
			err = v3SpendJournalBucket.Put(blockHash[:], reserialized)
			if err != nil {
				return false, err
			}

			numMigrated++
		}
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, b.db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating spend journal.  Total entries: %d in %v",
		totalMigrated, elapsed)

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Drop temp bucket.
	log.Info("Removing temp data...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, b.db, tmpTxInfoBucketName, "temp data")
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing temp data in %v", elapsed)

	// Drop version 2 spend journal.
	log.Info("Removing old spend journal...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, b.db, v2SpendJournalBucketName, "old journal")
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing old spend journal in %v", elapsed)
	return nil
}

// upgradeSpendJournalToVersion3 upgrades a version 2 spend journal to version
// 3.
func upgradeSpendJournalToVersion3(ctx context.Context, b *BlockChain) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading spend journal to version 3...")
	start := time.Now()

	// Migrate the spend journal to version 3.
	err := migrateSpendJournalVersion2To3(ctx, b)
	if err != nil {
		return err
	}

	// Update and persist the spend journal database version.
	err = b.db.Update(func(dbTx database.Tx) error {
		b.dbInfo.stxoVer = 3
		return dbPutDatabaseInfo(dbTx, b.dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading spend journal in %v.", elapsed)
	return nil
}

// upgradeUtxoSetToVersion3 upgrades a version 2 utxo set to version 3.
func upgradeUtxoSetToVersion3(ctx context.Context, db database.DB,
	utxoBackend UtxoBackend, utxoDbInfo *UtxoBackendInfo) error {

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading utxo set to version 3...")
	start := time.Now()

	// Migrate the utxoset to version 3.
	err := migrateUtxoSetVersion2To3(ctx, db)
	if err != nil {
		return err
	}

	// Update and persist the UTXO set database version.
	utxoDbInfo.utxoVer = 3
	err = utxoBackend.PutInfo(utxoDbInfo)
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading utxo set in %v.", elapsed)
	return nil
}

// upgradeToVersion10 upgrades a version 8 or version 9 blockchain database to
// version 10.  This entails writing the database spend journal version to the
// database so that it can be decoupled from the overall version of the block
// database.
func upgradeToVersion10(ctx context.Context, db database.DB, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 10...")
	start := time.Now()

	// Update and persist the database spend journal version and overall block
	// database version.
	err := db.Update(func(dbTx database.Tx) error {
		dbInfo.stxoVer = 2
		if dbInfo.version == 9 {
			dbInfo.stxoVer = 3
		}

		dbInfo.version = 10
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// upgradeToVersion11 upgrades a version 10 blockchain database to version 11.
// This entails bumping the overall block database version to 11 to prevent
// downgrades as the UTXO database is being moved in this same set of changes.
func upgradeToVersion11(ctx context.Context, db database.DB, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 11...")
	start := time.Now()

	// Update and persist the overall block database version.
	err := db.Update(func(dbTx database.Tx) error {
		dbInfo.version = 11
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// -----------------------------------------------------------------------------
// The version 1 stake database stores information about the state of tickets,
// potentially along with other information, using the shared format described
// here in several different places such as the stake undo data and the various
// category-based sets of tickets.
//
// The version 1 serialized format of the ticket state information is:
//
//   <block height><flags>
//
//   Field             Type             Size
//   block height      uint32           4 bytes
//   flags             byte             1 byte
//
// The version 1 serialized flags format is:
//   bit  0   - ticket is missed
//   bit  1   - ticket is revoked
//   bit  2   - ticket is spent
//   bit  3   - ticket is expired
//   bits 4-7 - unused
// -----------------------------------------------------------------------------

// Hardcoded constants so updates do not affect old upgrades.
const (
	// Version 1 ticket status flags.
	ticketMissedFlagV1  = 1 << 0
	ticketRevokedFlagV1 = 1 << 1
	ticketSpentFlagV1   = 1 << 2
	ticketExpiredFlagV1 = 1 << 3

	// ticketInfoSerializeSizeV1 is the number of bytes required to serialize
	// ticket state information in the version 1 format described above.
	ticketInfoSerializeSizeV1 = 5
)

// ticketInfoV1 represents the state of a ticket as used in version 1 stake
// database entries.
type ticketInfoV1 struct {
	hash    chainhash.Hash
	height  uint32
	missed  bool
	revoked bool
	spent   bool
	expired bool
}

// encodeTicketInfoFlagsV1 encodes the state flags of the provided ticket
// according to the version 1 format described above.
func encodeTicketInfoFlagsV1(ticket *ticketInfoV1) byte {
	var encodedFlags byte
	if ticket.missed {
		encodedFlags |= ticketMissedFlagV1
	}
	if ticket.revoked {
		encodedFlags |= ticketRevokedFlagV1
	}
	if ticket.spent {
		encodedFlags |= ticketSpentFlagV1
	}
	if ticket.expired {
		encodedFlags |= ticketExpiredFlagV1
	}
	return encodedFlags
}

// putTicketInfoV1 serializes the passed ticket state info according to the
// version 1 format described above directly into the passed target byte slice.
// The target byte slice must be at least large enough to handle the number of
// bytes specified by ticketInfoSerializeSizeV1 or it will panic.
func putTicketInfoV1(target []byte, ticket *ticketInfoV1) {
	binary.LittleEndian.PutUint32(target[0:4], ticket.height)
	target[4] = encodeTicketInfoFlagsV1(ticket)
}

// decodeTicketInfoV1 decodes the passed serialized ticket state info into the
// passed struct according to the version 1 format described above.  It returns
// the number of bytes read.
func decodeTicketInfoV1(serialized []byte, ticket *ticketInfoV1) (int, error) {
	// Ensure there are enough bytes to decode the ticket info.
	if len(serialized) < 4 {
		return 0, errDeserialize("unexpected end of data while reading ticket " +
			"info")
	}

	// Deserialize the block height associated with the ticket.
	height := binary.LittleEndian.Uint32(serialized[0:4])
	offset := 4

	// Deserialize the encoded flags.
	if offset+1 > len(serialized) {
		return offset, errDeserialize("unexpected end of data while reading " +
			"flags")
	}
	encodedFlags := serialized[offset]
	offset++

	ticket.height = height
	ticket.missed = encodedFlags&ticketMissedFlagV1 != 0
	ticket.revoked = encodedFlags&ticketRevokedFlagV1 != 0
	ticket.spent = encodedFlags&ticketSpentFlagV1 != 0
	ticket.expired = encodedFlags&ticketExpiredFlagV1 != 0
	return offset, nil
}

// -----------------------------------------------------------------------------
// The version 1 stake database undo data consists of an entry for each main
// chain block that contains data for all tickets modified by the block along
// with their updated state info in the order that they were spent, meaning that
// each element needs to be applied in reverse order to undo the effects of the
// block.
//
// The version 1 serialized format is:
//
//   [<ticket hash><ticket info>,...]
//
//   Field            Type              Size
//   ticket hash      chainhash.Hash    chainhash.HashSize
//   ticket info      ticketInfoV1      5 bytes
// -----------------------------------------------------------------------------

// v1UndoElementSize is the size of each individual element in a version 1 stake
// database undo record.
const v1UndoElementSize = chainhash.HashSize + ticketInfoSerializeSizeV1

// deserializeTicketDBUndoEntryV1 deserializes stake undo data from the passed
// serialized byte slice according to the version 1 format described in detail
// above.
func deserializeTicketDBUndoEntryV1(serialized []byte, blockHeight uint32) ([]ticketInfoV1, error) {
	// Ensure the undo data is a multiple of the entry size to detect corruption
	// and avoid the need to check additional size constraints while decoding.
	if len(serialized)%v1UndoElementSize != 0 {
		str := fmt.Sprintf("corrupt undo data for height %d", blockHeight)
		return nil, errDeserialize(str)
	}

	numEntries := len(serialized) / v1UndoElementSize
	undoEntries := make([]ticketInfoV1, 0, numEntries)
	offset := 0
	for i := 0; i < numEntries; i++ {
		var ticket ticketInfoV1
		copy(ticket.hash[:], serialized[offset:offset+chainhash.HashSize])
		offset += chainhash.HashSize
		bytesRead, err := decodeTicketInfoV1(serialized[offset:], &ticket)
		if err != nil {
			return nil, err
		}
		offset += bytesRead

		undoEntries = append(undoEntries, ticket)
	}

	return undoEntries, nil
}

// serializeTicketDBUndoEntryV1 serializes the passed undo data into a single
// byte slice according to the version 1 format described in detail above.
func serializeTicketDBUndoEntryV1(undoEntries []ticketInfoV1) []byte {
	serialized := make([]byte, len(undoEntries)*v1UndoElementSize)
	offset := 0
	for _, entry := range undoEntries {
		copy(serialized[offset:], entry.hash[:])
		offset += chainhash.HashSize
		putTicketInfoV1(serialized[offset:], &entry)
		offset += ticketInfoSerializeSizeV1
	}
	return serialized
}

// correctTreasurySpendVoteData corrects block index and ticket database entries
// to account for an issue in legacy code that caused votes which also include
// treasury spend vote data to be incorrectly marked as missed.
func correctTreasurySpendVoteData(ctx context.Context, db database.DB, params *chaincfg.Params) error {
	log.Info("Reindexing ticket database.  This will take a while...")
	start := time.Now()

	// Hardcoded data so updates do not affect old upgrades.
	const hashSize = chainhash.HashSize
	v1ChainStateKeyName := []byte("chainstate")
	v3BlockIdxBucketName := []byte("blockidxv3")
	v1StakeChainStateKeyName := []byte("stakechainstate")
	v1LiveTicketsBucketName := []byte("livetickets")
	v1MissedTicketsBucketName := []byte("missedtickets")
	v1RevokedTicketsBucketName := []byte("revokedtickets")
	v1StakeBlockUndoBucketName := []byte("stakeblockundo")
	v1TicketsInBlockBucketName := []byte("ticketsinblock")

	// uint32Bytes is a helper function to convert a uint32 to a little endian
	// byte slice.
	byteOrder := binary.LittleEndian
	uint32Bytes := func(ui32 uint32) []byte {
		var b [4]byte
		byteOrder.PutUint32(b[:], ui32)
		return b[:]
	}

	// Determine the current best chain tip using the version 1 chain state.
	var bestTipHash chainhash.Hash
	var bestTipHeight uint32
	err := db.View(func(dbTx database.Tx) error {
		// Load the current best chain tip hash and height from the v1 chain
		// state.
		//
		// The serialized format of the v1 chain state is roughly:
		//
		//   <block hash><block height><rest of data>
		//
		//   Field             Type             Size
		//   block hash        chainhash.Hash   hashSize
		//   block height      uint32           4 bytes
		//   rest of data...
		meta := dbTx.Metadata()
		serializedChainState := meta.Get(v1ChainStateKeyName)
		if serializedChainState == nil {
			str := fmt.Sprintf("chain state with key %s does not exist",
				v1ChainStateKeyName)
			return errDeserialize(str)
		}
		if len(serializedChainState) < hashSize+4 {
			str := "version 1 chain state is malformed"
			return errDeserialize(str)
		}
		copy(bestTipHash[:], serializedChainState[0:hashSize])
		offset := hashSize
		bestTipHeight = byteOrder.Uint32(serializedChainState[offset : offset+4])
		return nil
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Determine the minimum bound to disconnect back to in order to undo the
	// incorrect information so it can then be corrected.
	//
	// The only entries that might be incorrect are those after the treasury
	// agenda activated, so a lot of work can be avoided by only considering
	// those entries.  However, determining if the treasury agenda is active and
	// the point at which it activated requires a bunch of data that is not
	// readily available here in the upgrade code, so hard code the data for the
	// main and test networks to significantly optimize those cases and just
	// redo everything back to the genesis block for other networks.  While this
	// does potentially do more work than is strictly necessary for those other
	// networks, it is quite rare for them to have old databases in practice, so
	// it is a reasonable tradeoff.
	//
	// Finally, note that the heights here are for the block _before_ the
	// treasury activated on their respective networks since the block in which
	// it activated also needs to be undone.
	var disconnectUntilHeight uint32
	switch params.Net {
	case wire.MainNet:
		disconnectUntilHeight = 552447
	case wire.TestNet3:
		disconnectUntilHeight = 560207
	}

	// There is nothing to do if the best chain tip is prior to the point after
	// where potentially incorrect entries exist.
	if bestTipHeight < disconnectUntilHeight {
		return nil
	}

	// blockTreeEntry represents a version 3 block index entry with the details
	// needed to be able to determine which blocks need to have the treasury
	// spend vote data corrected as well as which ones comprise the main chain.
	type blockTreeEntry struct {
		parent    *blockTreeEntry
		children  []*blockTreeEntry
		hash      chainhash.Hash
		height    uint32
		mainChain bool
		header    wire.BlockHeader
		status    byte
	}

	// Load the block tree to determine all blocks and ticket database entries
	// that need to be corrected as well as the required order using the version
	// 3 block index along with the information determined above.
	var bestTip *blockTreeEntry
	blockTree := make(map[chainhash.Hash]*blockTreeEntry)
	err = db.View(func(dbTx database.Tx) error {
		// Hardcoded data so updates do not affect old upgrades.
		const blockHdrSize = 180

		// Construct a full block tree from the version 2 block index by mapping
		// each block to its parent block.
		var lastEntry, parent *blockTreeEntry
		meta := dbTx.Metadata()
		v3BlockIdxBucket := meta.Bucket(v3BlockIdxBucketName)
		if v3BlockIdxBucket == nil {
			return fmt.Errorf("bucket %s does not exist", v3BlockIdxBucketName)
		}
		cursor := v3BlockIdxBucket.Cursor()
		for ok := cursor.First(); ok; ok = cursor.Next() {
			if interruptRequested(ctx) {
				return errInterruptRequested
			}

			// Deserialize the header from the version 3 block index entry.
			//
			// The serialized value format of a v3 block index entry is roughly:
			//
			//   <block header><status><rest of data>
			//
			//   Field              Type                Size
			//   block header       wire.BlockHeader    180 bytes
			//   status             byte                1 byte
			//   rest of data...
			serializedBlkIdxEntry := cursor.Value()
			if len(serializedBlkIdxEntry) < blockHdrSize {
				return errDeserialize("unexpected end of data while reading " +
					"block header")
			}
			hB := serializedBlkIdxEntry[0:blockHdrSize]
			var header wire.BlockHeader
			if err := header.Deserialize(bytes.NewReader(hB)); err != nil {
				return err
			}
			if blockHdrSize+1 > len(serializedBlkIdxEntry) {
				return errDeserialize("unexpected end of data while reading " +
					"status")
			}
			status := serializedBlkIdxEntry[blockHdrSize]

			// Stop loading if the best chain tip height is exceeded.  This
			// can happen when there are block index entries for known headers
			// that haven't had their associated block data fully validated and
			// connected yet and therefore are not relevant for this upgrade
			// code.
			blockHeight := header.Height
			if blockHeight > bestTipHeight {
				break
			}

			// Determine the parent block node.  Since the entries are iterated
			// in order of height, there is a very good chance the previous
			// one processed is the parent.
			blockHash := header.BlockHash()
			if lastEntry == nil {
				if blockHash != params.GenesisHash {
					str := fmt.Sprintf("correctTreasurySpendVoteData: expected "+
						"first entry to be genesis block, found %s", blockHash)
					return errDeserialize(str)
				}
			} else if header.PrevBlock == lastEntry.hash {
				parent = lastEntry
			} else {
				parent = blockTree[header.PrevBlock]
				if parent == nil {
					str := fmt.Sprintf("correctTreasurySpendVoteData: could "+
						"not find parent for block %s", blockHash)
					return errDeserialize(str)
				}
			}

			// Add the block to the block tree.
			treeEntry := &blockTreeEntry{
				parent: parent,
				hash:   blockHash,
				height: blockHeight,
				header: header,
				status: status,
			}
			blockTree[blockHash] = treeEntry
			if parent != nil {
				parent.children = append(parent.children, treeEntry)
			}

			lastEntry = treeEntry
		}

		// Determine the blocks that comprise the main chain by starting at the
		// best tip and walking backwards to the oldest tracked block.
		bestTip = blockTree[bestTipHash]
		if bestTip == nil {
			str := fmt.Sprintf("chain tip %s is not in block index", bestTipHash)
			return errDeserialize(str)
		}
		for entry := bestTip; entry != nil; entry = entry.parent {
			entry.mainChain = true
		}
		return nil
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Ensure all of the buckets used repeatedly in the remaining code below
	// exist now to avoid the need to check multiple times.
	err = db.View(func(dbTx database.Tx) error {
		meta := dbTx.Metadata()
		bucketNames := [][]byte{v1StakeBlockUndoBucketName,
			v1LiveTicketsBucketName, v1MissedTicketsBucketName,
			v1RevokedTicketsBucketName, v1TicketsInBlockBucketName,
		}
		for _, bucketName := range bucketNames {
			if bucket := meta.Bucket(bucketName); bucket == nil {
				return fmt.Errorf("bucket %s does not exist", bucketName)
			}
		}
		return nil
	})
	if err != nil {
		return err
	}

	// ticketDBInfo represents a version 1 ticket database with only the details
	// needed to be able to correct the treasury spend vote data.
	type ticketDBInfo struct {
		tip            *blockTreeEntry
		liveTickets    map[chainhash.Hash]ticketInfoV1
		missedTickets  map[chainhash.Hash]ticketInfoV1
		revokedTickets map[chainhash.Hash]ticketInfoV1
	}

	// calcNextWinners returns a slice of tickets that are eligible to vote in
	// the next block given the current state of the provided ticket database.
	// It will return nil when there are not any winners for the next block due
	// to it being prior to the point where voting starts.
	calcNextWinners := func(ticketDB *ticketDBInfo) ([]chainhash.Hash, error) {
		// Voting does not start until stake validation height, so there are not
		// any winners prior to that point.
		if ticketDB.tip.height < uint32(params.StakeValidationHeight-1) {
			return nil, nil
		}

		// Assert there are enough live tickets to produce the required number
		// of winning tickets.
		numLiveTickets := uint32(len(ticketDB.liveTickets))
		if numLiveTickets < uint32(params.TicketsPerBlock) {
			return nil, AssertError(fmt.Sprintf("the live ticket pool as of "+
				"block %s (height %d) only has %d tickets which is less than "+
				"the required minimum to choose %d winners", ticketDB.tip.hash,
				ticketDB.tip.height, numLiveTickets, params.TicketsPerBlock))
		}

		// Sort the live tickets.
		liveTickets := make([]chainhash.Hash, 0, numLiveTickets)
		for ticketHash := range ticketDB.liveTickets {
			liveTickets = append(liveTickets, ticketHash)
		}
		sort.Slice(liveTickets, func(i, j int) bool {
			return bytes.Compare(liveTickets[i][:], liveTickets[j][:]) < 0
		})

		// Determine the winning tickets.
		nextWinners := make([]chainhash.Hash, 0, params.TicketsPerBlock)
		hB, _ := ticketDB.tip.header.Bytes()
		prng := stake.NewHash256PRNG(hB)
		usedOffsets := make(map[uint32]struct{})
		for uint16(len(nextWinners)) < params.TicketsPerBlock {
			ticketIndex := prng.UniformRandom(numLiveTickets)
			if _, exists := usedOffsets[ticketIndex]; !exists {
				usedOffsets[ticketIndex] = struct{}{}
				nextWinners = append(nextWinners, liveTickets[ticketIndex])
			}
		}

		return nextWinners, nil
	}

	// serializeStakeChainStateV1 serializes the stake chain state for the
	// passed ticket database into a single byte slice according to the version
	// 1 format described below.
	//
	// The version 1 stake chain state consists of the best block hash and
	// height, the total number of live, missed, and revoked tickets, the number
	// of winning tickets per block, and the tickets that are eligible to vote
	// in the next block (aka winning tickets).
	//
	// The serialized format is:
	//
	//   <block hash><block height><live><missed><revoked><per block><winners>
	//
	//   Field              Type              Size
	//   block hash         chainhash.Hash    hashSize
	//   block height       uint32            4 bytes
	//   live tickets       uint32            4 bytes
	//   missed tickets     uint64            8 bytes
	//   revoked tickets    uint64            8 bytes
	//   winners per block  uint16            2 bytes
	//   next winners       []chainhash.Hash  hashSize * winners per block
	//
	// NOTE: The next winners are populated with zero hashes prior to stake
	// validation height.
	serializeStakeChainStateV1 := func(ticketDB *ticketDBInfo) ([]byte, error) {
		numLiveTickets := uint32(len(ticketDB.liveTickets))
		numMissedTickets := uint64(len(ticketDB.missedTickets))
		numRevokedTickets := uint64(len(ticketDB.revokedTickets))

		const baseSize = hashSize + 4 + 4 + 8 + 8 + 2
		serialized := make([]byte, baseSize+params.TicketsPerBlock*hashSize)
		copy(serialized[0:hashSize], ticketDB.tip.hash[:])
		offset := hashSize
		byteOrder.PutUint32(serialized[offset:offset+4], ticketDB.tip.height)
		offset += 4
		byteOrder.PutUint32(serialized[offset:offset+4], numLiveTickets)
		offset += 4
		byteOrder.PutUint64(serialized[offset:offset+8], numMissedTickets)
		offset += 8
		byteOrder.PutUint64(serialized[offset:offset+8], numRevokedTickets)
		offset += 8
		byteOrder.PutUint16(serialized[offset:offset+2], params.TicketsPerBlock)
		offset += 2

		// Determine and serialize the next winning tickets, if any.  There will
		// not be any winners prior to the point where voting starts (stake
		// validation height).  In that case, since the slice is already sized
		// to include them, the hashes will be all zero as expected.
		nextWinners, err := calcNextWinners(ticketDB)
		if err != nil {
			return nil, err
		}
		for _, winner := range nextWinners {
			copy(serialized[offset:offset+hashSize], winner[:])
			offset += hashSize
		}

		return serialized, nil
	}

	// Load information about the current state of the version 1 ticket database
	// including the current tip and all of the associated live, missed, and
	// revoked tickets.
	ticketDB := ticketDBInfo{
		liveTickets:    make(map[chainhash.Hash]ticketInfoV1),
		missedTickets:  make(map[chainhash.Hash]ticketInfoV1),
		revokedTickets: make(map[chainhash.Hash]ticketInfoV1),
	}
	err = db.View(func(dbTx database.Tx) error {
		// Load the current ticket db chain tip hash and height from the v1
		// stake chain state.
		//
		// The serialized format of the v1 stake chain state is roughly:
		//
		//   <block hash><block height><rest of data>
		//
		//   Field             Type             Size
		//   block hash        chainhash.Hash   hashSize
		//   block height      uint32           4 bytes
		//   rest of data...
		var ticketDBTipHash chainhash.Hash
		var ticketDBTipHeight uint32
		meta := dbTx.Metadata()
		serializedState := meta.Get(v1StakeChainStateKeyName)
		if serializedState == nil {
			str := fmt.Sprintf("chain state with key %s does not exist",
				v1StakeChainStateKeyName)
			return errDeserialize(str)
		}
		if len(serializedState) < hashSize+4 {
			str := "version 1 stake chain state is malformed"
			return errDeserialize(str)
		}
		copy(ticketDBTipHash[:], serializedState[0:hashSize])
		offset := hashSize
		ticketDBTipHeight = byteOrder.Uint32(serializedState[offset : offset+4])

		// Look up ticket database tip in the block index and assert it exists.
		tip, ok := blockTree[ticketDBTipHash]
		if !ok {
			str := fmt.Sprintf("ticket db tip %s is not in block index",
				ticketDBTipHash)
			return errDeserialize(str)
		}
		if tip.height != ticketDBTipHeight {
			str := fmt.Sprintf("corrupt ticket db tip height for %s",
				ticketDBTipHash)
			return errDeserialize(str)
		}
		ticketDB.tip = tip

		// Load all of the live, missed, and revoked tickets.
		//
		// The version 1 stake database stores each of these sets in their own
		// bucket with an entry for every ticket keyed by its hash and the value
		// serialized according to the shared version 1 stake database ticket
		// state.
		loadTickets := func(key []byte, m map[chainhash.Hash]ticketInfoV1) error {
			bucket := meta.Bucket(key)
			return bucket.ForEach(func(k []byte, v []byte) error {
				var ticket ticketInfoV1
				copy(ticket.hash[:], k)
				_, err := decodeTicketInfoV1(v, &ticket)
				if err != nil {
					return err
				}
				m[ticket.hash] = ticket
				return nil
			})
		}
		err := loadTickets(v1LiveTicketsBucketName, ticketDB.liveTickets)
		if err != nil {
			return err
		}
		err = loadTickets(v1MissedTicketsBucketName, ticketDB.missedTickets)
		if err != nil {
			return err
		}
		return loadTickets(v1RevokedTicketsBucketName, ticketDB.revokedTickets)
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Determine the known good block to disconnect back to by following the
	// ticket database tip back to the known good height.  This approach ensures
	// that any corner cases, such as the local best chain being on a side chain
	// for which the treasury agenda may or may not be active, are handled
	// properly at the expense of potentially doing more work than is strictly
	// necessary in those highly unlikely cases.
	disconnectTo := ticketDB.tip
	for disconnectTo.height != disconnectUntilHeight {
		disconnectTo = disconnectTo.parent
	}

	// dbPutTicketV1 writes the provided ticket info into the provided bucket
	// keyed by the ticket hash.
	dbPutTicketV1 := func(bucket database.Bucket, ticket ticketInfoV1) error {
		var serialized [5]byte
		putTicketInfoV1(serialized[:], &ticket)
		return bucket.Put(ticket.hash[:], serialized[:])
	}

	// doDisconnectBatch contains the logic for reverting the ticket database
	// back to a known good point by undoing all blocks in between in batches.
	// This is done because attempting to undo them all in a single database
	// transaction could result in massive memory usage and could potentially
	// crash on many systems due to ulimits.
	//
	// Note that the current ticket database tip is updated as the batches
	// either are interrupted or successfully complete so interrupted upgrades
	// start from the most recent tip.
	//
	// It returns whether or not all entries have been updated.
	const maxDisconnectEntries = 10000
	var totalUpdated uint64
	doDisconnectBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v1UndoDataBucket := meta.Bucket(v1StakeBlockUndoBucketName)
		v1LiveTicketsBucket := meta.Bucket(v1LiveTicketsBucketName)
		v1MissedTicketsBucket := meta.Bucket(v1MissedTicketsBucketName)
		v1RevokedTicketsBucket := meta.Bucket(v1RevokedTicketsBucketName)

		// loadUndoV1 loads the undo data for the given height from the version
		// 1 stake database, deserializes the data, and returns it.
		loadUndoV1 := func(height uint32) ([]ticketInfoV1, error) {
			heightAsKey := uint32Bytes(height)
			undoData := v1UndoDataBucket.Get(heightAsKey)
			if undoData == nil {
				str := fmt.Sprintf("missing undo data for height %d", height)
				return nil, errDeserialize(str)
			}

			return deserializeTicketDBUndoEntryV1(undoData, height)
		}

		var logProgress bool
		var numUpdated uint64
		err := func() error {
			// Loop backwards from the current ticket database tip to the known
			// good point.
			var batchErr error
			for ticketDB.tip != disconnectTo {
				if interruptRequested(ctx) {
					logProgress = true
					batchErr = errInterruptRequested
					break
				}

				if numUpdated >= maxDisconnectEntries {
					logProgress = true
					batchErr = errBatchFinished
					break
				}

				// Load all the undo entries for the current ticket database tip
				// and apply all of them in reverse order.
				undoEntries, err := loadUndoV1(ticketDB.tip.height)
				if err != nil {
					return err
				}
				for undoIdx := len(undoEntries) - 1; undoIdx >= 0; undoIdx-- {
					undo := &undoEntries[undoIdx]

					switch {
					// Remove tickets that became live in the block from the set
					// of live tickets.
					case !undo.missed && !undo.revoked && !undo.spent:
						delete(ticketDB.liveTickets, undo.hash)
						err := v1LiveTicketsBucket.Delete(undo.hash[:])
						if err != nil {
							return err
						}

					// Move tickets revoked in the block back to the set of
					// missed tickets.
					case undo.missed && undo.revoked:
						delete(ticketDB.revokedTickets, undo.hash)
						err := v1RevokedTicketsBucket.Delete(undo.hash[:])
						if err != nil {
							return err
						}

						ticket := ticketInfoV1{
							hash:    undo.hash,
							height:  undo.height,
							missed:  true,
							revoked: false,
							spent:   false,
							expired: undo.expired,
						}
						ticketDB.missedTickets[undo.hash] = ticket
						err = dbPutTicketV1(v1MissedTicketsBucket, ticket)
						if err != nil {
							return err
						}

					// Move tickets that became missed as of the block back to
					// the set of live tickets.
					case undo.missed && !undo.revoked:
						delete(ticketDB.missedTickets, undo.hash)
						err := v1MissedTicketsBucket.Delete(undo.hash[:])
						if err != nil {
							return err
						}

						ticket := ticketInfoV1{
							hash:    undo.hash,
							height:  undo.height,
							missed:  false,
							revoked: false,
							spent:   false,
							expired: false,
						}
						ticketDB.liveTickets[undo.hash] = ticket
						err = dbPutTicketV1(v1LiveTicketsBucket, ticket)
						if err != nil {
							return err
						}

					// Move tickets spent in the block back to the set of live
					// tickets.
					case undo.spent:
						ticket := ticketInfoV1{
							hash:    undo.hash,
							height:  undo.height,
							missed:  false,
							revoked: false,
							spent:   false,
							expired: false,
						}
						ticketDB.liveTickets[undo.hash] = ticket
						err := dbPutTicketV1(v1LiveTicketsBucket, ticket)
						if err != nil {
							return err
						}

					default:
						str := fmt.Sprintf("unknown ticket state in undo data "+
							"(missed=%v, revoked=%v, spent=%v, expired=%v)",
							undo.missed, undo.revoked, undo.spent, undo.expired)
						return errDeserialize(str)
					}
				}

				// NOTE: The undo data for the block is not dropped here to save
				// additional database writes because it will be overwritten
				// with the correct data later when the block is reconnected.
				//
				// Also, the list of tickets maturing in the block is correct,
				// so it is kept and used when the block is reconnected later to
				// avoid the otherwise much more expensive need to load the
				// historical block to determine them.

				// The ticket database tip is now the previous block.
				ticketDB.tip = ticketDB.tip.parent

				numUpdated++
			}

			// Write the new stake chain state to the database so it will resume
			// at the correct place if interrupted.
			serialized, err := serializeStakeChainStateV1(&ticketDB)
			if err != nil {
				return err
			}
			err = meta.Put(v1StakeChainStateKeyName, serialized)
			if err != nil {
				return err
			}

			return batchErr
		}()
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numUpdated > 0 {
			totalUpdated += numUpdated
			log.Infof("Updated %d entries (%d total)", numUpdated, totalUpdated)
		}
		return isFullyDone, err
	}

	// Revert the ticket database back to a known good point by undoing all
	// blocks from the current ticket database back to that point in batches.
	//
	// Note that the disconnect is done in an upgrade stage so that interrupting
	// the upgrade after the disconnect process successfully completes will not
	// attempt to undo again.
	disconnectDoneKey := []byte("tsvfdisconnectdone")
	err = runUpgradeStageOnce(ctx, db, disconnectDoneKey, func() error {
		// Update all entries in batches for the reasons mentioned above.
		return batchedUpdate(ctx, db, doDisconnectBatch)
	})
	if err != nil {
		return err
	}

	// dbFetchBlock uses an existing database transaction to retrieve the block
	// for the provided block tree entry.
	dbFetchBlock := func(dbTx database.Tx, entry *blockTreeEntry) (*wire.MsgBlock, error) {
		// Load the raw block bytes from the database.
		blockBytes, err := dbTx.FetchBlock(&entry.hash)
		if err != nil {
			return nil, err
		}

		var block wire.MsgBlock
		if err := block.FromBytes(blockBytes); err != nil {
			return nil, err
		}

		return &block, nil
	}

	// findSpentTicketsInBlock returns information about tickets spent in the
	// provided block including voted and revoked tickets as well as the vote
	// bits of each vote.
	type spentTicketsInBlock struct {
		votedTickets   []chainhash.Hash
		votes          []blockIndexVoteVersionTuple
		revokedTickets []chainhash.Hash
	}
	findSpentTicketsInBlock := func(block *wire.MsgBlock) spentTicketsInBlock {
		votes := make([]blockIndexVoteVersionTuple, 0, block.Header.Voters)
		voters := make([]chainhash.Hash, 0, block.Header.Voters)
		revocations := make([]chainhash.Hash, 0, block.Header.Revocations)

		for _, stx := range block.STransactions {
			if stake.IsSSGen(stx) {
				voters = append(voters, stx.TxIn[1].PreviousOutPoint.Hash)
				votes = append(votes, blockIndexVoteVersionTuple{
					version: stake.SSGenVersion(stx),
					bits:    stake.SSGenVoteBits(stx),
				})
				continue
			}
			if stake.IsSSRtx(stx) {
				spentTicketHash := stx.TxIn[0].PreviousOutPoint.Hash
				revocations = append(revocations, spentTicketHash)
				continue
			}
		}
		return spentTicketsInBlock{
			votedTickets:   voters,
			votes:          votes,
			revokedTickets: revocations,
		}
	}

	// nextMainChainBlock returns the block in the main chain after the provided
	// one.
	nextMainChainBlock := func(n *blockTreeEntry) *blockTreeEntry {
		for _, child := range n.children {
			if child.mainChain {
				return child
			}
		}
		return nil
	}

	// doConnectBatch contains the logic for catching the ticket database back
	// up to the current best chain tip by replaying all blocks in between in
	// batches.  This is done because attempting to undo them all in a single
	// database transaction could result in massive memory usage and could
	// potentially crash on many systems due to ulimits.
	//
	// Note that the current ticket database tip is updated as the batches
	// either are interrupted or successfully complete so interrupted upgrades
	// start from the most recent tip.
	//
	// It returns whether or not all entries have been updated.
	const maxConnectEntries = 1000
	doConnectBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		v1LiveTicketsBucket := meta.Bucket(v1LiveTicketsBucketName)
		v1MissedTicketsBucket := meta.Bucket(v1MissedTicketsBucketName)
		v1RevokedTicketsBucket := meta.Bucket(v1RevokedTicketsBucketName)
		v1TicketsInBlockBucket := meta.Bucket(v1TicketsInBlockBucketName)
		v1UndoDataBucket := meta.Bucket(v1StakeBlockUndoBucketName)
		v3BlockIdxBucket := meta.Bucket(v3BlockIdxBucketName)

		var logProgress bool
		var numUpdated uint64
		err := func() error {
			// Loop forwards from the current ticket database tip to the current
			// best chain tip.
			var batchErr error
			for ticketDB.tip != bestTip {
				if interruptRequested(ctx) {
					logProgress = true
					batchErr = errInterruptRequested
					break
				}

				if numUpdated >= maxConnectEntries {
					logProgress = true
					batchErr = errBatchFinished
					break
				}

				// Determine the next main chain block, load it from the block
				// database and extract information about the spent tickets in
				// order to construct all of the stake info needed to update the
				// ticket database and block index.
				nextTip := nextMainChainBlock(ticketDB.tip)
				block, err := dbFetchBlock(dbTx, nextTip)
				if err != nil {
					return err
				}
				spentTickets := findSpentTicketsInBlock(block)

				// Load the list of tickets that are maturing in the block that
				// is being connected.
				//
				// The version 1 serialized format of the tickets in the block
				// is:
				//
				//   [<block hash>,...]
				//
				//   Field         Type              Size
				//   block hash    chainhash.Hash    hashSize
				heightAsKey := uint32Bytes(nextTip.height)
				serializedHashes := v1TicketsInBlockBucket.Get(heightAsKey)
				if serializedHashes == nil {
					str := fmt.Sprintf("missing maturing ticket data for "+
						"block height %d", nextTip.height)
					return errDeserialize(str)
				}
				if len(serializedHashes)%hashSize != 0 {
					str := fmt.Sprintf("malformed maturing ticket data for "+
						"block height %d", nextTip.height)
					return errDeserialize(str)
				}
				var newTickets []chainhash.Hash
				numEntries := len(serializedHashes) / hashSize
				if numEntries > 0 {
					newTickets = make([]chainhash.Hash, numEntries)
				}
				offset := 0
				for i := 0; i < numEntries; i++ {
					copy(newTickets[i][:], serializedHashes[offset:])
					offset += hashSize
				}

				// Determine the winning tickets eligible to vote in the block
				// that is being connected and remove them from the set of live
				// tickets, add any that missed to the set of missed tickets,
				// and track the state changes needed for the undo entry.
				//
				// Note that there will not be any winners prior to the point
				// where voting starts (stake validation height).
				nextWinners, err := calcNextWinners(&ticketDB)
				if err != nil {
					return err
				}
				undoEntriesSizeHint := len(nextWinners) + len(newTickets) +
					len(spentTickets.revokedTickets)
				undoEntries := make([]ticketInfoV1, 0, undoEntriesSizeHint)
				for _, winner := range nextWinners {
					ticket, ok := ticketDB.liveTickets[winner]
					if !ok {
						return AssertError(fmt.Sprintf("winning ticket %s "+
							"voting on block %s (height %d) is not in the set "+
							"of live tickets", winner, ticketDB.tip.hash,
							ticketDB.tip.height))
					}
					delete(ticketDB.liveTickets, ticket.hash)
					err := v1LiveTicketsBucket.Delete(ticket.hash[:])
					if err != nil {
						return err
					}

					// The ticket is either spent and not missed when it voted
					// or unspent and missed when it did not.
					var voted bool
					for _, votedTicket := range spentTickets.votedTickets {
						if votedTicket == ticket.hash {
							voted = true
							break
						}
					}
					ticket.missed = !voted
					ticket.revoked = false
					ticket.spent = voted
					ticket.expired = false
					if ticket.missed {
						ticketDB.missedTickets[ticket.hash] = ticket
						err := dbPutTicketV1(v1MissedTicketsBucket, ticket)
						if err != nil {
							return err
						}
					}
					undoEntries = append(undoEntries, ticket)
				}

				// Determine tickets that are expiring as of the block that is
				// being connected, move them from the set of live tickets to
				// the set of missed tickets, and track the state changes needed
				// for the undo entry.
				var expiredTickets []chainhash.Hash
				if nextTip.height >= params.TicketExpiry {
					expiringHeight := nextTip.height - params.TicketExpiry
					for _, ticket := range ticketDB.liveTickets {
						if ticket.height > expiringHeight {
							continue
						}
						expiredTickets = append(expiredTickets, ticket.hash)
					}
				}
				for _, expiredTicket := range expiredTickets {
					ticket := ticketDB.liveTickets[expiredTicket]
					delete(ticketDB.liveTickets, ticket.hash)
					err := v1LiveTicketsBucket.Delete(ticket.hash[:])
					if err != nil {
						return err
					}

					// The ticket is now missed and expired.
					ticket.missed = true
					ticket.revoked = false
					ticket.spent = false
					ticket.expired = true
					ticketDB.missedTickets[ticket.hash] = ticket
					err = dbPutTicketV1(v1MissedTicketsBucket, ticket)
					if err != nil {
						return err
					}
					undoEntries = append(undoEntries, ticket)
				}

				// Move revocations in the block that is being connected from
				// the set of missed tickets to the set of revoked tickets and
				// track the state changes needed for the undo entry.
				for _, revokedTicket := range spentTickets.revokedTickets {
					ticket, ok := ticketDB.missedTickets[revokedTicket]
					if !ok {
						return AssertError(fmt.Sprintf("ticket %s revoked by "+
							"block %s (height %d) is not in the set of missed "+
							"tickets", revokedTicket, nextTip.hash,
							nextTip.height))
					}
					delete(ticketDB.missedTickets, ticket.hash)
					err := v1MissedTicketsBucket.Delete(ticket.hash[:])
					if err != nil {
						return err
					}

					// The ticket is now revoked.  Note that it will already be
					// marked missed, but set it explicitly for clarity.
					ticket.missed = true
					ticket.revoked = true
					ticket.spent = false
					ticket.expired = false
					ticketDB.revokedTickets[ticket.hash] = ticket
					err = dbPutTicketV1(v1RevokedTicketsBucket, ticket)
					if err != nil {
						return err
					}
					undoEntries = append(undoEntries, ticket)
				}

				// Add tickets that are maturing in the block that is being
				// connected to the set of live tickets and track the state
				// changes needed for the undo entry.
				for _, newTicket := range newTickets {
					ticket := ticketInfoV1{
						hash:    newTicket,
						height:  nextTip.height,
						missed:  false,
						revoked: false,
						spent:   false,
						expired: false,
					}
					ticketDB.liveTickets[ticket.hash] = ticket
					err := dbPutTicketV1(v1LiveTicketsBucket, ticket)
					if err != nil {
						return err
					}
					undoEntries = append(undoEntries, ticket)
				}

				// Store the undo data for the block that is being connected.
				serialized := serializeTicketDBUndoEntryV1(undoEntries)
				err = v1UndoDataBucket.Put(heightAsKey, serialized)
				if err != nil {
					return err
				}

				// Correct the vote information for the block being connected as
				// well as any side chain blocks at the same height (siblings).
				for _, tip := range ticketDB.tip.children {
					// The vote data will already be loaded for the main chain
					// block, so make use of it.  Otherwise, load the associated
					// side chain block and extract if the data for the block
					// has been stored.
					votes := spentTickets.votes
					if tip != nextTip {
						// Nothing to do if the block data is not available.
						const v3StatusDataStored = 1 << 0
						if tip.status&v3StatusDataStored == 0 {
							continue
						}

						// Load the block from the database and extract the
						// vote information from it.
						block, err := dbFetchBlock(dbTx, tip)
						if err != nil {
							return err
						}
						spentTickets := findSpentTicketsInBlock(block)
						votes = spentTickets.votes
					}

					// Write the corrected block index entry to the database.
					v3Entry := &blockIndexEntryV3{
						header:   tip.header,
						status:   tip.status,
						voteInfo: votes,
					}
					serialized, err := serializeBlockIndexEntryV3(v3Entry)
					if err != nil {
						return err
					}
					indexKey := blockIndexKeyV3(&tip.hash, tip.height)
					err = v3BlockIdxBucket.Put(indexKey, serialized)
					if err != nil {
						return err
					}
				}

				// The ticket database tip is now the next main chain block.
				ticketDB.tip = nextTip

				numUpdated++
			}

			// Write the new stake chain state to the database.  This ensures it
			// will resume at the correct place if interrupted as well as match
			// the best chain state as required once the entire process is
			// finished.
			serialized, err := serializeStakeChainStateV1(&ticketDB)
			if err != nil {
				return err
			}
			err = meta.Put(v1StakeChainStateKeyName, serialized)
			if err != nil {
				return err
			}

			return batchErr
		}()
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numUpdated > 0 {
			totalUpdated += numUpdated
			log.Infof("Updated %d entries (%d total)", numUpdated, totalUpdated)
		}
		return isFullyDone, err
	}

	// Catch the ticket database back up to the current best chain tip in
	// batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doConnectBatch); err != nil {
		return err
	}

	// Remove upgrade progress tracking keys.
	err = db.Update(func(dbTx database.Tx) error {
		return dbTx.Metadata().Delete(disconnectDoneKey)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done updating ticket database in %v.  Total entries: %d",
		elapsed, totalUpdated)
	return nil
}

// upgradeToVersion12 upgrades a version 11 blockchain database to version 12.
// This entails correcting block index and ticket database entries to account
// for an issue in legacy code that caused votes which also include treasury
// spend vote data to be incorrectly marked as missed as well as unmarking all
// blocks previously marked failed so they are eligible for validation again
// under the new consensus rules.
func upgradeToVersion12(ctx context.Context, db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 12...")
	start := time.Now()

	// Correct the block index and ticket database entries to account for an
	// issue in legacy code as needed.
	tsvfDoneKeyName := []byte("v12tsvfdone")
	err := runUpgradeStageOnce(ctx, db, tsvfDoneKeyName, func() error {
		return correctTreasurySpendVoteData(ctx, db, chainParams)
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Unmark all blocks previously marked failed so they are eligible for
	// validation again under the new consensus rules.
	if err := clearFailedBlockFlagsV3(ctx, db); err != nil {
		return err
	}

	// Update and persist the overall block database version and remove upgrade
	// progress tracking keys.
	err = db.Update(func(dbTx database.Tx) error {
		err := dbTx.Metadata().Delete(tsvfDoneKeyName)
		if err != nil {
			return err
		}

		dbInfo.version = 12
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// serializeHeaderCommitmentsV1 serializes the passed commitment hashes into a
// single byte slice according to the version 1 format.
func serializeHeaderCommitmentsV1(commitments []chainhash.Hash) []byte {
	// Nothing to serialize when there are no commitments.
	if len(commitments) == 0 {
		return nil
	}

	// Calculate the full size needed to serialize the commitments.
	numCommitments := len(commitments)
	serializedLen := serializeSizeVLQ(uint64(numCommitments)) +
		numCommitments*chainhash.HashSize

	// Serialize the commitments.
	serialized := make([]byte, serializedLen)
	offset := putVLQ(serialized, uint64(numCommitments))
	for i := range commitments {
		copy(serialized[offset:], commitments[i][:])
		offset += chainhash.HashSize
	}
	return serialized
}

// initializeHeaderCmts creates and stores the hashes that comprise the header
// commitments for all applicable blocks.  This ensures the commitments are
// immediately available for use when constructing inclusion proofs for clients
// and simplifies the rest of the related code since it can rely on the data
// being available once the upgrade completes.
//
// The database is guaranteed to have the header commitments for all blocks
// after the point that header commitments activated and also have both the
// block data and associated filter data available.  In practice, that includes
// all blocks that are part of the main chain as well as any side chain blocks
// that were fully connected at some point.
func initializeHeaderCmts(ctx context.Context, db database.DB, params *chaincfg.Params) error {
	log.Info("Storing header commitments.  This may take a while...")
	start := time.Now()

	// Hardcoded data so updates do not affect old upgrades.
	const hashSize = chainhash.HashSize
	v1ChainStateKeyName := []byte("chainstate")
	v3BlockIdxBucketName := []byte("blockidxv3")
	gcsBucketName := []byte("gcsfilters")
	hdrCmtProgressKeyName := []byte("hdrcmtprogress")
	byteOrder := binary.LittleEndian

	// Determine the current best chain tip using the version 1 chain state.
	var bestTipHash chainhash.Hash
	var bestTipHeight uint32
	err := db.View(func(dbTx database.Tx) error {
		// Load the current best chain tip hash and height from the v1 chain
		// state.
		//
		// The serialized format of the v1 chain state is roughly:
		//
		//   <block hash><block height><rest of data>
		//
		//   Field             Type             Size
		//   block hash        chainhash.Hash   hashSize
		//   block height      uint32           4 bytes
		//   rest of data...
		meta := dbTx.Metadata()
		serializedChainState := meta.Get(v1ChainStateKeyName)
		if serializedChainState == nil {
			str := fmt.Sprintf("chain state with key %s does not exist",
				v1ChainStateKeyName)
			return errDeserialize(str)
		}
		if len(serializedChainState) < hashSize+4 {
			str := "version 1 chain state is malformed"
			return errDeserialize(str)
		}
		copy(bestTipHash[:], serializedChainState[0:hashSize])
		offset := hashSize
		bestTipHeight = byteOrder.Uint32(serializedChainState[offset : offset+4])
		return nil
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Determine the minimum block height that possibly needs to have header
	// commitments stored since it only applies to blocks once the header
	// commitments agenda activated.  However, determining if the agenda is
	// active and the point at which it activated requires a bunch of data that
	// is not readily available here in the upgrade code, so hard code the data
	// for the main and test networks to significantly optimize those cases and
	// just check everything for other networks.  While this does potentially do
	// more work than is strictly necessary for those other networks, it is
	// quite rare for them to have old databases in practice, so it is a
	// reasonable tradeoff.
	storeFromHeight := uint32(1)
	switch params.Net {
	case wire.MainNet:
		storeFromHeight = 431488
	case wire.TestNet3:
		storeFromHeight = 323328
	}

	// There is nothing to do if the best chain tip is prior to the point after
	// where header commitments need to be stored.
	if bestTipHeight < storeFromHeight {
		return nil
	}

	// Resume from in-progress upgrades.
	err = db.View(func(dbTx database.Tx) error {
		progressHeightBytes := dbTx.Metadata().Get(hdrCmtProgressKeyName)
		if progressHeightBytes != nil {
			storeFromHeight = byteOrder.Uint32(progressHeightBytes)
			log.Infof("Resuming upgrade at height %d", storeFromHeight+1)
		}
		return nil
	})
	if err != nil {
		return err
	}

	// blockTreeEntry represents a version 3 block index entry with the details
	// needed to be able to determine which blocks need to have the header
	// commitments stored as well as which ones comprise the main chain.
	type blockTreeEntry struct {
		parent    *blockTreeEntry
		children  []*blockTreeEntry
		hash      chainhash.Hash
		height    uint32
		mainChain bool
		cmtRoot   chainhash.Hash
		status    byte
	}

	// Load the block tree to determine all blocks that need to have the header
	// commitments stored using the version 3 block index along with the
	// information determined above.
	var startParent *blockTreeEntry
	blockTree := make(map[chainhash.Hash]*blockTreeEntry)
	err = db.View(func(dbTx database.Tx) error {
		// Hardcoded data so updates do not affect old upgrades.
		const blockHdrSize = 180

		// Construct a full block tree from the version 3 block index by mapping
		// each block to its parent block.
		var lastEntry, parent *blockTreeEntry
		meta := dbTx.Metadata()
		v3BlockIdxBucket := meta.Bucket(v3BlockIdxBucketName)
		if v3BlockIdxBucket == nil {
			return fmt.Errorf("bucket %s does not exist", v3BlockIdxBucketName)
		}
		cursor := v3BlockIdxBucket.Cursor()
		for ok := cursor.First(); ok; ok = cursor.Next() {
			if interruptRequested(ctx) {
				return errInterruptRequested
			}

			// Deserialize the header from the version 3 block index entry.
			//
			// The serialized value format of a v3 block index entry is roughly:
			//
			//   <block header><status><rest of data>
			//
			//   Field              Type                Size
			//   block header       wire.BlockHeader    180 bytes
			//   status             byte                1 byte
			//   rest of data...
			serializedBlkIdxEntry := cursor.Value()
			if len(serializedBlkIdxEntry) < blockHdrSize {
				return errDeserialize("unexpected end of data while reading " +
					"block header")
			}
			hB := serializedBlkIdxEntry[0:blockHdrSize]
			var header wire.BlockHeader
			if err := header.Deserialize(bytes.NewReader(hB)); err != nil {
				return err
			}
			if blockHdrSize+1 > len(serializedBlkIdxEntry) {
				return errDeserialize("unexpected end of data while reading " +
					"status")
			}
			status := serializedBlkIdxEntry[blockHdrSize]

			// Stop loading if the best chain tip height is exceeded.  This
			// can happen when there are block index entries for known headers
			// that haven't had their associated block data fully validated and
			// connected yet and therefore are not relevant for this upgrade
			// code.
			blockHeight := header.Height
			if blockHeight > bestTipHeight {
				break
			}

			// Determine the parent block node.  Since the entries are iterated
			// in order of height, there is a very good chance the previous
			// one processed is the parent.
			blockHash := header.BlockHash()
			if lastEntry == nil {
				if blockHash != params.GenesisHash {
					str := fmt.Sprintf("initializeHeaderCmts: expected first "+
						"entry to be genesis block, found %s", blockHash)
					return errDeserialize(str)
				}
			} else if header.PrevBlock == lastEntry.hash {
				parent = lastEntry
			} else {
				parent = blockTree[header.PrevBlock]
				if parent == nil {
					str := fmt.Sprintf("initializeHeaderCmts: could not find "+
						"parent for block %s", blockHash)
					return errDeserialize(str)
				}
			}

			// Add the block to the block tree.
			treeEntry := &blockTreeEntry{
				parent:  parent,
				hash:    blockHash,
				height:  blockHeight,
				cmtRoot: header.StakeRoot,
				status:  status,
			}
			blockTree[blockHash] = treeEntry
			if parent != nil {
				parent.children = append(parent.children, treeEntry)
			}

			// Determine the parent of the block to start storing the header
			// commitments.
			if blockHeight == storeFromHeight && startParent == nil {
				startParent = parent
			}

			lastEntry = treeEntry
		}

		// Determine the blocks that comprise the main chain by starting at the
		// best tip and walking backwards to the oldest tracked block.
		bestTip := blockTree[bestTipHash]
		if bestTip == nil {
			str := fmt.Sprintf("chain tip %s is not in block index", bestTipHash)
			return errDeserialize(str)
		}
		for entry := bestTip; entry != nil; entry = entry.parent {
			entry.mainChain = true
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Start from block 1 when no other starting block was found above.
	if startParent == nil {
		startParent = blockTree[params.GenesisHash]
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Create the new header commitments bucket as needed.
	headerCmtsBucketName := []byte("hdrcmts")
	err = db.Update(func(dbTx database.Tx) error {
		_, err := dbTx.Metadata().CreateBucketIfNotExists(headerCmtsBucketName)
		return err
	})
	if err != nil {
		return err
	}

	// doBatch contains the primary logic for storing the header commitments
	// when moving from database version 12 to 13 in batches.  This is done
	// because attempting to store them all in a single database transaction
	// could result in massive memory usage and could potentially crash on many
	// systems due to ulimits.
	//
	// It returns whether or not all entries have been stored.
	const maxEntries = 20000
	processQueue := make([]*blockTreeEntry, 0, 5)
	processQueue = append(processQueue, startParent.children...)
	var totalStored uint64
	doBatch := func(dbTx database.Tx) (bool, error) {
		meta := dbTx.Metadata()
		commitmentsBucket := meta.Bucket(headerCmtsBucketName)
		if commitmentsBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist",
				headerCmtsBucketName)
		}
		filterBucket := meta.Bucket(gcsBucketName)
		if filterBucket == nil {
			return false, fmt.Errorf("bucket %s does not exist", gcsBucketName)
		}

		var logProgress bool
		var numStored uint64
		err := func() error {
			for len(processQueue) > 0 {
				if interruptRequested(ctx) {
					logProgress = true
					return errInterruptRequested
				}

				if numStored >= maxEntries {
					logProgress = true
					return errBatchFinished
				}

				node := processQueue[0]
				processQueue = processQueue[1:]
				processQueue = append(processQueue, node.children...)

				// Skip side chain blocks that do not have block data available
				// since they do not have the filters stored.
				const v3StatusDataStored = 1 << 0
				if node.status&v3StatusDataStored == 0 {
					if node.mainChain {
						return AssertError(fmt.Sprintf("no block data for "+
							"main chain block %s (height %d)", node.hash,
							node.height))
					}
					continue
				}

				// Attempt to load the filter for the block while ensuring that
				// the data is available for main chain blocks and skipping any
				// side chain blocks that do not have it available.  Blocks that
				// were never part of the main chain will not have any filter
				// data stored.
				filterBytes := filterBucket.Get(node.hash[:])
				if len(filterBytes) == 0 {
					if node.mainChain {
						return AssertError(fmt.Sprintf("no filter is stored "+
							"for main chain block %s (height %d)", node.hash,
							node.height))
					}
					continue
				}

				// Store the filter hash as the one and only commitment when the
				// header commits to it.
				//
				// This approach is used because the headers for all blocks at
				// database version 12 only committed to the filter once the
				// header commitments agenda activated and agenda information is
				// not readily available here in the upgrade code.
				filterHash := blake256.Sum256(filterBytes)
				if filterHash != node.cmtRoot {
					continue
				}

				// Store the commitment in the database.
				commitments := []chainhash.Hash{filterHash}
				serialized := serializeHeaderCommitmentsV1(commitments)
				if len(serialized) != 0 {
					err := commitmentsBucket.Put(node.hash[:], serialized)
					if err != nil {
						return err
					}
				}

				// Update in-progress tracking height for resume.
				var serializedProgress [4]byte
				byteOrder.PutUint32(serializedProgress[:], node.height)
				err = meta.Put(hdrCmtProgressKeyName, serializedProgress[:])
				if err != nil {
					return err
				}

				numStored++
			}

			return nil
		}()
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numStored > 0 {
			totalStored += numStored
			log.Infof("Stored %d entries (%d total)", numStored, totalStored)
		}
		// Remove in-progress tracking key when the upgrade is done.
		if isFullyDone {
			_ = meta.Delete(hdrCmtProgressKeyName)
		}
		return isFullyDone, err
	}

	// Store the commitments in batches for the reasons mentioned above.
	if err := batchedUpdate(ctx, db, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done storing header commitments.  Total entries: %d in %v",
		totalStored, elapsed)
	return nil
}

// upgradeToVersion13 upgrades a version 12 blockchain database to version 13.
// This entails populating the header commitments journal for all blocks after
// the point that header commitments activated and also have the block data
// and associated filter data available.
func upgradeToVersion13(ctx context.Context, db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 13...")
	start := time.Now()

	// Store header commitments for all applicable blocks.
	err := initializeHeaderCmts(ctx, db, chainParams)
	if err != nil {
		return err
	}

	// Update and persist the overall block database version.
	err = db.Update(func(dbTx database.Tx) error {
		dbInfo.version = 13
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// correctTotalSubsidyChainState corrects the total subsidy value in the chain
// state to account for an issue in legacy code whereby treasurybases were not
// included in the tally.
func correctTotalSubsidyChainState(ctx context.Context, db database.DB, params *chaincfg.Params) error {
	const funcName = "correctTotalSubsidyChainState"

	log.Info("Recalculating total subsidy.  This will take a while...")
	start := time.Now()

	// Hardcoded data so updates do not affect old upgrades.
	const (
		hashSize     = chainhash.HashSize
		blockHdrSize = 180
	)
	v1ChainStateKeyName := []byte("chainstate")
	v3BlockIdxBucketName := []byte("blockidxv3")
	byteOrder := binary.LittleEndian

	// Ensure all of the buckets used repeatedly in the remaining code below
	// exist now to avoid the need to check multiple times.
	err := db.View(func(dbTx database.Tx) error {
		meta := dbTx.Metadata()
		bucketNames := [][]byte{v3BlockIdxBucketName}
		for _, bucketName := range bucketNames {
			if bucket := meta.Bucket(bucketName); bucket == nil {
				return fmt.Errorf("%s: bucket %s does not exist", funcName,
					bucketName)
			}
		}
		return nil
	})
	if err != nil {
		return err
	}

	// blockTreeEntry represents a version 3 block index entry with just
	// enough information to be able to determine which blocks comprise the
	// main chain.
	type blockTreeEntry struct {
		parent *blockTreeEntry
		hash   chainhash.Hash
		height uint32
	}

	// Determine the blocks in the main chain using the version 3 block index
	// and version 1 chain state.
	blockTree := make(map[chainhash.Hash]*blockTreeEntry)
	var mainChainBlocks []blockTreeEntry
	err = db.View(func(dbTx database.Tx) error {
		// Load the current best chain tip hash and height from the v1 chain
		// state.
		//
		// The serialized format of the v1 chain state is roughly:
		//
		//   <block hash><block height><rest of data>
		//
		//   Field             Type             Size
		//   block hash        chainhash.Hash   hashSize
		//   block height      uint32           4 bytes
		//   rest of data...
		meta := dbTx.Metadata()
		serializedChainState := meta.Get(v1ChainStateKeyName)
		if serializedChainState == nil {
			str := fmt.Sprintf("%s: chain state with key %s does not exist",
				funcName, v1ChainStateKeyName)
			return errDeserialize(str)
		}
		if len(serializedChainState) < hashSize+4 {
			str := fmt.Sprintf("%s: version 1 chain state is malformed",
				funcName)
			return errDeserialize(str)
		}
		var bestTipHash chainhash.Hash
		copy(bestTipHash[:], serializedChainState[0:hashSize])
		offset := hashSize
		bestTipHeight := byteOrder.Uint32(serializedChainState[offset : offset+4])

		// Construct a full block tree from the version 3 block index by mapping
		// each block to its parent block.
		var lastEntry, parent *blockTreeEntry
		v3BlockIdxBucket := meta.Bucket(v3BlockIdxBucketName)
		cursor := v3BlockIdxBucket.Cursor()
		for ok := cursor.First(); ok; ok = cursor.Next() {
			if interruptRequested(ctx) {
				return errInterruptRequested
			}

			// Deserialize the header from the version 3 block index entry.
			//
			// The serializedBlkIdxEntry value format of a v3 block index entry is roughly:
			//
			//   <block header><status><rest of data>
			//
			//   Field              Type                Size
			//   block header       wire.BlockHeader    180 bytes
			//   rest of data...
			serializedBlkIdxEntry := cursor.Value()
			if len(serializedBlkIdxEntry) < blockHdrSize {
				str := fmt.Sprintf("%s: unexpected end of data while reading "+
					"block header", funcName)
				return errDeserialize(str)
			}
			hB := serializedBlkIdxEntry[0:blockHdrSize]
			var header wire.BlockHeader
			if err := header.Deserialize(bytes.NewReader(hB)); err != nil {
				return err
			}

			// Stop loading if the best chain tip height is exceeded.  This
			// can happen when there are block index entries for known headers
			// that haven't had their associated block data fully validated and
			// connected yet and therefore are not relevant for this upgrade
			// code.
			blockHeight := header.Height
			if blockHeight > bestTipHeight {
				break
			}

			// Determine the parent block node.  Since the entries are iterated
			// in order of height, there is a very good chance the previous
			// one processed is the parent.
			blockHash := header.BlockHash()
			if lastEntry == nil {
				if blockHash != params.GenesisHash {
					str := fmt.Sprintf("%s: expected first entry in block "+
						"index to be genesis block, found %s", funcName,
						blockHash)
					return errDeserialize(str)
				}
			} else if header.PrevBlock == lastEntry.hash {
				parent = lastEntry
			} else {
				parent = blockTree[header.PrevBlock]
				if parent == nil {
					str := fmt.Sprintf("%s: could not find parent for block %s",
						funcName, blockHash)
					return errDeserialize(str)
				}
			}

			// Add the block to the block tree.
			treeEntry := &blockTreeEntry{
				parent: parent,
				hash:   blockHash,
				height: header.Height,
			}
			blockTree[blockHash] = treeEntry

			lastEntry = treeEntry
		}

		// Construct a view of the blocks that comprise the main chain by
		// starting at the best tip and walking backwards to the genesis block
		// while assigning each one to its respective height.
		bestTip := blockTree[bestTipHash]
		if bestTip == nil {
			str := fmt.Sprintf("%s: chain tip %s is not in block index",
				funcName, bestTipHash)
			return errDeserialize(str)
		}
		mainChainBlocks = make([]blockTreeEntry, bestTip.height+1)
		for entry := bestTip; entry != nil; entry = entry.parent {
			mainChainBlocks[entry.height] = *entry
		}

		return nil
	})
	if err != nil {
		return err
	}

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// dbFetchBlock uses an existing database transaction to retrieve the block
	// for the provided block tree entry.
	dbFetchBlock := func(dbTx database.Tx, entry *blockTreeEntry) (*wire.MsgBlock, error) {
		// Load the raw block bytes from the database.
		blockBytes, err := dbTx.FetchBlock(&entry.hash)
		if err != nil {
			return nil, err
		}

		var block wire.MsgBlock
		if err := block.FromBytes(blockBytes); err != nil {
			return nil, err
		}

		return &block, nil
	}

	// calcAddedSubsidyV1 calculates the amount of subsidy added by a block and
	// its parent according to the details of the network as they existed for
	// the version of the database being upgraded.
	calcAddedSubsidyV1 := func(block, parent *wire.MsgBlock) int64 {
		var subsidy int64
		if headerApprovesParent(&block.Header) {
			subsidy += parent.Transactions[0].TxIn[0].ValueIn
		}

		for txIdx, stx := range block.STransactions {
			if (txIdx == 0 && stake.IsTreasuryBase(stx)) || stake.IsSSGen(stx) {
				subsidy += stx.TxIn[0].ValueIn
			}
		}

		return subsidy
	}

	// Recalculate the total subsidy by looping through all of the main chain
	// blocks and tallying the subsidy produced by them.
	const logInterval = 20000
	var totalSubsidy int64
	err = db.View(func(dbTx database.Tx) error {
		parent, err := dbFetchBlock(dbTx, &mainChainBlocks[0])
		if err != nil {
			return err
		}
		totalBlocks := int64(len(mainChainBlocks))
		for height := int64(1); height < totalBlocks; height++ {
			if interruptRequested(ctx) {
				return errInterruptRequested
			}

			if height%logInterval == 0 {
				progress := float64(height) / float64(totalBlocks)
				progress = math.Min(progress, 1.0) * 100
				log.Infof("Recalculated subsidy for %d blocks (progress %0.2f%%)",
					height, progress)
			}

			block, err := dbFetchBlock(dbTx, &mainChainBlocks[height])
			if err != nil {
				return err
			}
			totalSubsidy += calcAddedSubsidyV1(block, parent)
			parent = block
		}

		return nil
	})
	if err != nil {
		return err
	}

	// Update the version 1 chain state with the updated total subsidy.
	err = db.Update(func(dbTx database.Tx) error {
		// Load the current v1 chain state.
		//
		// The serialized format of the v1 chain state is roughly:
		//
		//   <block hash><block height><total txns><total subsidy><rest of data>
		//
		//   Field             Type             Size
		//   block hash        chainhash.Hash   hashSize
		//   block height      uint32           4 bytes
		//   total txns        uint64           8 bytes
		//   total subsidy     int64            8 bytes
		//   rest of data...
		meta := dbTx.Metadata()
		serialized := meta.Get(v1ChainStateKeyName)
		if len(serialized) < hashSize+20 {
			str := "version 1 chain state is malformed"
			return errDeserialize(str)
		}

		// Update the raw serialized chain state directly with the corrected
		// total subsidy and write the result to the database.
		byteOrder.PutUint64(serialized[hashSize+12:], uint64(totalSubsidy))
		return meta.Put(v1ChainStateKeyName, serialized)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done updating total subsidy in %v", elapsed)
	return nil
}

// upgradeToVersion14 upgrades a version 13 blockchain database to version 14.
// This entails removing the legacy spend consumer dependencies bucket if needed
// and correcting the total subsidy value in the chain state.
func upgradeToVersion14(ctx context.Context, db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading database to version 14...")
	start := time.Now()

	// Remove legacy spend consumer dependencies bucket if needed.
	err := db.Update(func(dbTx database.Tx) error {
		spendConsumerDepsBucketName := []byte("spendconsumerdeps")
		meta := dbTx.Metadata()
		if meta.Bucket(spendConsumerDepsBucketName) == nil {
			return nil
		}
		return meta.DeleteBucket(spendConsumerDepsBucketName)
	})
	if err != nil {
		return err
	}

	// Correct the total subsidy value in the chain state as needed.
	err = correctTotalSubsidyChainState(ctx, db, chainParams)
	if err != nil {
		return err
	}

	// Update and persist the overall block database version.
	err = db.Update(func(dbTx database.Tx) error {
		dbInfo.version = 14
		return dbPutDatabaseInfo(dbTx, dbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// separateUtxoDatabase moves the UTXO set and state from the block database to
// the UTXO database.
func separateUtxoDatabase(ctx context.Context, db database.DB,
	utxoBackend UtxoBackend) error {

	// Key names, versions, and prefixes are hardcoded below so that updates do
	// not affect old upgrades.

	// Legacy buckets and key names.
	v3UtxoSetBucketName := []byte("utxosetv3")
	utxoSetStateKeyNameV1 := []byte("utxosetstate")

	// ---------------------------------------------------------------------------
	// The new keys in the UTXO backend start with a serialized prefix consisting
	// of the key set and version of that key set as follows:
	//
	//   <key set><version>
	//
	//   Key        Value    Size      Description
	//   key set    uint8    1 byte    The key set identifier, as defined below
	//   version    uint8    1 byte    The version of the key set
	//
	// The key sets as of this migration are:
	//   utxoKeySetDbInfo:    1
	//   utxoKeySetUtxoState: 2
	//   utxoKeySetUtxoSet:   3
	//
	// The versions as of this migration are:
	//   utxoKeySetDbInfo:    0
	//   utxoKeySetUtxoState: 1
	//   utxoKeySetUtxoSet:   3
	// ---------------------------------------------------------------------------
	utxoSetStateKeyNew := []byte("\x02\x01utxosetstate")
	utxoPrefixUtxoSetV3 := []byte("\x03\x03")

	log.Info("Migrating UTXO database.  This may take a while...")
	start := time.Now()

	// doBatch contains the primary logic for migrating the UTXO database.  This
	// is done because attempting to migrate in a single database transaction
	// could result in massive memory usage and could potentially crash on many
	// systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	var err error
	doBatch := func(dbTx database.Tx, tx UtxoBackendTx) (bool, error) {
		// Get the UTXO set bucket for both the old and the new database.
		v3UtxoSetBucketOldDb := dbTx.Metadata().Bucket(v3UtxoSetBucketName)
		if v3UtxoSetBucketOldDb == nil {
			// If the UTXO set doesn't exist in the old database, return immediately
			// as there is nothing to do.
			return true, nil
		}

		// Migrate UTXO set entries so long as the max number of entries for this
		// batch has not been exceeded.
		var logProgress bool
		var numMigrated, numIterated uint32
		cursor := v3UtxoSetBucketOldDb.Cursor()
		for ok := cursor.Last(); ok; ok = cursor.Prev() {
			// Reset err on each iteration.
			err = nil

			if interruptRequested(ctx) {
				logProgress = true
				err = errInterruptRequested
				break
			}

			if numMigrated >= maxEntries {
				logProgress = true
				err = errBatchFinished
				break
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				continue
			}
			resumeOffset++

			// Create the new entry in the V3 bucket.
			newKey := prefixedKey(utxoPrefixUtxoSetV3, cursor.Key())
			err = tx.Put(newKey, cursor.Value())
			if err != nil {
				return false, err
			}

			numMigrated++
		}
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	var isFullyDone bool
	for !isFullyDone {
		err := db.View(func(dbTx database.Tx) error {
			return utxoBackend.Update(func(tx UtxoBackendTx) error {
				var err error
				isFullyDone, err = doBatch(dbTx, tx)
				if errors.Is(err, errInterruptRequested) ||
					errors.Is(err, errBatchFinished) {

					// No error here so the database transaction is not cancelled
					// and therefore outstanding work is written to disk.  The outer
					// function will exit with an interrupted error below due to
					// another interrupted check.
					return nil
				}
				return err
			})
		})
		if err != nil {
			return err
		}

		if interruptRequested(ctx) {
			return errInterruptRequested
		}
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating UTXO database.  Total entries: %d in %v",
		totalMigrated, elapsed)

	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	// Get the UTXO set state from the old database.
	var serialized []byte
	err = db.View(func(dbTx database.Tx) error {
		serialized = dbTx.Metadata().Get(utxoSetStateKeyNameV1)
		return nil
	})
	if err != nil {
		return err
	}

	if serialized != nil {
		// Set the UTXO set state in the new database.
		err = utxoBackend.Update(func(tx UtxoBackendTx) error {
			return tx.Put(utxoSetStateKeyNew, serialized)
		})
		if err != nil {
			return err
		}
	}

	if serialized != nil {
		// Delete the UTXO set state from the old database.
		err = db.Update(func(dbTx database.Tx) error {
			return dbTx.Metadata().Delete(utxoSetStateKeyNameV1)
		})
		if err != nil {
			return err
		}
	}

	// Drop UTXO set from the old database.
	log.Info("Removing old UTXO set...")
	start = time.Now()
	err = incrementalFlatDrop(ctx, db, v3UtxoSetBucketName, "old UTXO set")
	if err != nil {
		return err
	}
	err = db.Update(func(dbTx database.Tx) error {
		return dbTx.Metadata().DeleteBucket(v3UtxoSetBucketName)
	})
	if err != nil {
		return err
	}
	elapsed = time.Since(start).Round(time.Millisecond)
	log.Infof("Done removing old UTXO set in %v", elapsed)

	// Force the block database to flush to disk to avoid rerunning the migration
	// in the event of an unclean shutown.
	return db.Flush()
}

// fetchLegacyBucketID returns the legacy bucket id for the provided bucket
// name.  A Get function must be provided to retrieve key/value pairs from the
// underlying data store.
func fetchLegacyBucketID(getFn func(key []byte) ([]byte, error),
	bucketName []byte) ([]byte, error) {

	// bucketIndexPrefix is the prefix used for all entries in the bucket index.
	bucketIndexPrefix := []byte("bidx")

	// parentBucketID is the parent bucket id.  This is hardcoded to zero here
	// since none of the legacy UTXO database buckets had parent buckets.
	parentBucketID := []byte{0x00, 0x00, 0x00, 0x00}

	// Construct the key and fetch the corresponding bucket id from the database.
	// The serialized bucket index key format is:
	//   <bucketindexprefix><parentbucketid><bucketname>
	bucketIDKey := prefixedKey(bucketIndexPrefix, parentBucketID)
	bucketIDKey = append(bucketIDKey, bucketName...)
	bucketID, err := getFn(bucketIDKey)
	if err != nil {
		str := fmt.Sprintf("error fetching legacy bucket id for %v bucket",
			string(bucketName))
		return nil, convertLdbErr(err, str)
	}

	return bucketID, nil
}

// migrateUtxoDbBuckets migrates the UTXO data to use simple key prefixes rather
// than buckets.
func migrateUtxoDbBuckets(ctx context.Context, utxoBackend UtxoBackend) error {
	// Key names, versions, and prefixes are hardcoded below so that updates do
	// not affect old upgrades.

	// Legacy buckets and key names.
	metadataLegacyBucketID := []byte{0x00, 0x00, 0x00, 0x00}
	utxoSetLegacyBucketName := []byte("utxosetv3")
	utxoDbInfoLegacyBucketName := []byte("dbinfo")
	utxoDbInfoVersionKeyNameV1 := []byte("version")
	utxoDbInfoCompVerKeyNameV1 := []byte("compver")
	utxoDbInfoUtxoVerKeyNameV1 := []byte("utxover")
	utxoDbInfoCreatedKeyNameV1 := []byte("created")
	utxoSetStateKeyNameV1 := []byte("utxosetstate")

	// ---------------------------------------------------------------------------
	// The new keys in the UTXO backend start with a serialized prefix consisting
	// of the key set and version of that key set as follows:
	//
	//   <key set><version>
	//
	//   Key        Value    Size      Description
	//   key set    uint8    1 byte    The key set identifier, as defined below
	//   version    uint8    1 byte    The version of the key set
	//
	// The key sets as of this migration are:
	//   utxoKeySetDbInfo:    1
	//   utxoKeySetUtxoState: 2
	//   utxoKeySetUtxoSet:   3
	//
	// The versions as of this migration are:
	//   utxoKeySetDbInfo:    0
	//   utxoKeySetUtxoState: 1
	//   utxoKeySetUtxoSet:   3
	// ---------------------------------------------------------------------------
	utxoDbInfoVersionKeyNew := []byte("\x01\x00version")
	utxoDbInfoCompVerKeyNew := []byte("\x01\x00compver")
	utxoDbInfoUtxoVerKeyNew := []byte("\x01\x00utxover")
	utxoDbInfoCreatedKeyNew := []byte("\x01\x00created")
	utxoSetStateKeyNew := []byte("\x02\x01utxosetstate")
	utxoPrefixUtxoSetV3 := []byte("\x03\x03")

	// moveKey is a helper function that uses an existing UTXO backend transaction
	// to move a key.
	moveKey := func(tx UtxoBackendTx, oldKey, newKey []byte) error {
		serialized, err := tx.Get(oldKey)
		if err != nil {
			return err
		}

		// Return immediately if the entry does not exist at the old location.
		if serialized == nil {
			return nil
		}

		err = tx.Put(newKey, serialized)
		if err != nil {
			return err
		}
		return tx.Delete(oldKey)
	}

	// Move the database info from the legacy bucket to the new keys.
	bucketID, err := fetchLegacyBucketID(utxoBackend.Get,
		utxoDbInfoLegacyBucketName)
	if err != nil {
		return err
	}
	if bucketID != nil {
		err := utxoBackend.Update(func(tx UtxoBackendTx) error {
			// Move the database version from the legacy bucket to the new key.
			oldKey := prefixedKey(bucketID, utxoDbInfoVersionKeyNameV1)
			err = moveKey(tx, oldKey, utxoDbInfoVersionKeyNew)
			if err != nil {
				return fmt.Errorf("error migrating database version: %w", err)
			}

			// Move the database compression version from the legacy bucket to the new
			// key.
			oldKey = prefixedKey(bucketID, utxoDbInfoCompVerKeyNameV1)
			err = moveKey(tx, oldKey, utxoDbInfoCompVerKeyNew)
			if err != nil {
				return fmt.Errorf("error migrating database compression version: %w",
					err)
			}

			// Move the database UTXO set version from the legacy bucket to the new
			// key.
			oldKey = prefixedKey(bucketID, utxoDbInfoUtxoVerKeyNameV1)
			err = moveKey(tx, oldKey, utxoDbInfoUtxoVerKeyNew)
			if err != nil {
				return fmt.Errorf("error migrating UTXO set version: %w", err)
			}

			// Move the database creation date from the legacy bucket to the new key.
			oldKey = prefixedKey(bucketID, utxoDbInfoCreatedKeyNameV1)
			err = moveKey(tx, oldKey, utxoDbInfoCreatedKeyNew)
			if err != nil {
				return fmt.Errorf("error migrating database creation date: %w", err)
			}
			return nil
		})
		if err != nil {
			return err
		}
	}

	// Move the UTXO set state from the legacy bucket to the new key.
	err = utxoBackend.Update(func(tx UtxoBackendTx) error {
		oldKey := prefixedKey(metadataLegacyBucketID, utxoSetStateKeyNameV1)
		err = moveKey(tx, oldKey, utxoSetStateKeyNew)
		if err != nil {
			return fmt.Errorf("error migrating UTXO set state: %w", err)
		}
		return nil
	})
	if err != nil {
		return err
	}

	log.Info("Migrating UTXO database.  This may take a while...")
	start := time.Now()

	// Move the UTXO set from the legacy bucket to the new keys.
	bucketID, err = fetchLegacyBucketID(utxoBackend.Get, utxoSetLegacyBucketName)
	if err != nil {
		return err
	}

	// If the legacy bucket doesn't exist, return as there is nothing to do.
	if bucketID == nil {
		return nil
	}

	// doBatch contains the primary logic for migrating the UTXO set.  This is
	// done because attempting to migrate in a single database transaction could
	// result in massive memory usage and could potentially crash on many systems
	// due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var totalMigrated uint64
	doBatch := func(tx UtxoBackendTx) (bool, error) {
		// Migrate UTXO set entries so long as the max number of entries for this
		// batch has not been exceeded.
		var logProgress bool
		var numMigrated uint32

		iter := tx.NewIterator(bucketID)
		defer iter.Release()
		for iter.Next() {
			// Reset err on each iteration.
			err = nil

			if interruptRequested(ctx) {
				logProgress = true
				err = errInterruptRequested
				break
			}

			if numMigrated >= maxEntries {
				logProgress = true
				err = errBatchFinished
				break
			}

			// Move the UTXO set entry to the new key.
			oldKey := iter.Key()
			newKey := prefixedKey(utxoPrefixUtxoSetV3, oldKey[len(bucketID):])
			err = moveKey(tx, oldKey, newKey)
			if err != nil {
				return false, fmt.Errorf("error migrating UTXO set entry: %w", err)
			}

			numMigrated++
		}
		if iterErr := iter.Error(); iterErr != nil {
			return false, convertLdbErr(iterErr, "failed to run batch")
		}
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	if err := utxoBackendBatchedUpdate(ctx, utxoBackend, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done migrating UTXO database.  Total entries: %d in %v",
		totalMigrated, elapsed)

	return nil
}

// dbPutUtxoBackendInfoV1 uses an existing UTXO backend transaction to store the
// V1 UTXO backend information.
func dbPutUtxoBackendInfoV1(tx UtxoBackendTx, info *UtxoBackendInfo) error {
	// V1 database info keys.
	utxoDbInfoVersionKeyNameV1 := []byte("version")
	utxoDbInfoCompVerKeyNameV1 := []byte("compver")
	utxoDbInfoUtxoVerKeyNameV1 := []byte("utxover")
	utxoDbInfoCreatedKeyNameV1 := []byte("created")

	// V1 bucket info.
	bucketNameV1 := []byte("dbinfo")
	bucketIDV1, err := fetchLegacyBucketID(tx.Get, bucketNameV1)
	if err != nil {
		return err
	}

	// uint32Bytes is a helper function to convert a uint32 to a byte slice
	// using the byte order specified by the database namespace.
	byteOrder := binary.LittleEndian
	uint32Bytes := func(ui32 uint32) []byte {
		var b [4]byte
		byteOrder.PutUint32(b[:], ui32)
		return b[:]
	}

	// uint64Bytes is a helper function to convert a uint64 to a byte slice
	// using the byte order specified by the database namespace.
	uint64Bytes := func(ui64 uint64) []byte {
		var b [8]byte
		byteOrder.PutUint64(b[:], ui64)
		return b[:]
	}

	// Store the database version.
	verKey := prefixedKey(bucketIDV1, utxoDbInfoVersionKeyNameV1)
	err = tx.Put(verKey, uint32Bytes(info.version))
	if err != nil {
		return err
	}

	// Store the compression version.
	compVerKey := prefixedKey(bucketIDV1, utxoDbInfoCompVerKeyNameV1)
	err = tx.Put(compVerKey, uint32Bytes(info.compVer))
	if err != nil {
		return err
	}

	// Store the UTXO set version.
	utxoVerKey := prefixedKey(bucketIDV1, utxoDbInfoUtxoVerKeyNameV1)
	err = tx.Put(utxoVerKey, uint32Bytes(info.utxoVer))
	if err != nil {
		return err
	}

	// Store the database creation date.
	createdKey := prefixedKey(bucketIDV1, utxoDbInfoCreatedKeyNameV1)
	return tx.Put(createdKey, uint64Bytes(uint64(info.created.Unix())))
}

// upgradeUtxoDbToVersion2 upgrades a UTXO database from version 1 to version 2.
func upgradeUtxoDbToVersion2(ctx context.Context, utxoBackend UtxoBackend, utxoDbInfo *UtxoBackendInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading UTXO database to version 2...")
	start := time.Now()

	// Migrate the UTXO data to use simple key prefixes rather than buckets.
	err := migrateUtxoDbBuckets(ctx, utxoBackend)
	if err != nil {
		return err
	}

	// Update and persist the UTXO database version.
	utxoDbInfo.version = 2
	err = utxoBackend.PutInfo(utxoDbInfo)
	if err != nil {
		return err
	}

	// Update and persist the UTXO database version in the legacy V1 bucket.
	// This allows older versions to identify that a newer database version exists
	// in the case of a downgrade.
	err = utxoBackend.Update(func(tx UtxoBackendTx) error {
		return dbPutUtxoBackendInfoV1(tx, utxoDbInfo)
	})
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// moveUtxoDatabase moves the UTXO database from the provided old path to the
// provided new path.  The database must exist at the provided old path.
func moveUtxoDatabase(ctx context.Context, oldPath string, newPath string) error {
	log.Info("Moving UTXO database.  This may take a while...")
	start := time.Now()

	// Options for open the leveldb database.
	opts := opt.Options{
		Strict:      opt.DefaultStrict,
		Compression: opt.NoCompression,
		Filter:      filter.NewBloomFilter(10),
	}

	// Open the database at the old path.
	oldDb, err := leveldb.OpenFile(oldPath, &opts)
	if err != nil {
		str := "failed to open UTXO database at old path"
		return convertLdbErr(err, str)
	}

	// Create and open the database at the new path.
	newDb, err := leveldb.OpenFile(newPath, &opts)
	if err != nil {
		if err := oldDb.Close(); err != nil {
			return convertLdbErr(err, "failed to close UTXO database at old path")
		}
		str := "failed to open UTXO database at new path"
		return convertLdbErr(err, str)
	}

	// doBatch contains the primary logic for moving the UTXO database entries.
	// This is done because attempting to migrate in a single database transaction
	// could result in massive memory usage and could potentially crash on many
	// systems due to ulimits.
	//
	// It returns whether or not all entries have been updated.
	const maxEntries = 20000
	var resumeOffset uint32
	var totalMigrated uint64
	doBatch := func(tx UtxoBackendTx) (bool, error) {
		// Migrate UTXO database entries so long as the max number of entries for
		// this batch has not been exceeded.
		var logProgress bool
		var numMigrated, numIterated uint32

		iter := oldDb.NewIterator(nil, nil)
		defer iter.Release()
		for iter.Next() {
			// Reset err on each iteration.
			err = nil

			if interruptRequested(ctx) {
				logProgress = true
				err = errInterruptRequested
				break
			}

			if numMigrated >= maxEntries {
				logProgress = true
				err = errBatchFinished
				break
			}

			// Skip entries that have already been migrated in previous batches.
			numIterated++
			if numIterated-1 < resumeOffset {
				continue
			}
			resumeOffset++

			// Move the UTXO database entry to the new database.
			err := tx.Put(iter.Key(), iter.Value())
			if err != nil {
				return false, fmt.Errorf("error migrating UTXO database entry: %w", err)
			}

			numMigrated++
		}
		if iterErr := iter.Error(); iterErr != nil {
			return false, convertLdbErr(iterErr, "failed to run batch")
		}
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numMigrated > 0 {
			totalMigrated += uint64(numMigrated)
			log.Infof("Migrated %d entries (%d total)", numMigrated,
				totalMigrated)
		}
		return isFullyDone, err
	}

	// Migrate all entries in batches for the reasons mentioned above.
	utxoBackend := NewLevelDbUtxoBackend(newDb)
	if err := utxoBackendBatchedUpdate(ctx, utxoBackend, doBatch); err != nil {
		if err := oldDb.Close(); err != nil {
			return convertLdbErr(err, "failed to close UTXO database at old path")
		}
		if err := newDb.Close(); err != nil {
			return convertLdbErr(err, "failed to close UTXO database at new path")
		}
		return err
	}

	// Close the old and new databases.
	if err := oldDb.Close(); err != nil {
		return convertLdbErr(err, "failed to close UTXO database at old path")
	}
	if err := newDb.Close(); err != nil {
		return convertLdbErr(err, "failed to close UTXO database at new path")
	}

	// Remove the old database.
	fi, err := os.Stat(oldPath)
	if err != nil {
		return err
	}
	log.Infof("Removing old UTXO database from '%s'", oldPath)
	err = removeDB(oldPath, fi)
	if err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done moving UTXO database.  Total entries: %d in %v",
		totalMigrated, elapsed)

	return nil
}

// removeTreasuryBaseUtxos removes all utxos in the utxo set of a version 2 utxo
// database that are outputs of a treasurybase transaction since they are never
// directly spendable and thus should not be part of the utxo set.
func removeTreasuryBaseUtxos(ctx context.Context, utxoBackend UtxoBackend) error {
	// Hardcoded prefix so updates do not affect old upgrades.
	utxoPrefixUtxoSetV3 := []byte("\x03\x03")

	log.Info("Updating database utxo set.  This may take a while...")
	start := time.Now()

	// doBatch contains the primary logic for removing unspendable treasurybase
	// outputs from the version 3 utxoset in batches.  This is done because
	// attempting to do everything in a single database transaction could result
	// in massive memory usage and could potentially crash on many systems due
	// to ulimits.
	const maxEntries = 50000
	var totalRemoved uint64
	var resumeKey []byte
	doBatch := func(tx UtxoBackendTx) (bool, error) {
		var logProgress bool
		var numRemoved uint32
		err := func() error {
			// Remove treasurybase outputs so long as the max number of entries
			// for this batch has not been exceeded.
			iter := tx.NewIterator(utxoPrefixUtxoSetV3)
			defer iter.Release()

			// Iterate all entries in the utxo set while skipping entries
			// already examined in previous batches.
			for ok := iter.Seek(resumeKey); ok; ok = iter.Next() {
				if interruptRequested(ctx) {
					logProgress = true
					return errInterruptRequested
				}

				if numRemoved >= maxEntries {
					// Set the resume key so the next batch skips entries that
					// have already been examined.  It is necessary to make a
					// copy of the iterator key since it is no longer valid once
					// the db transaction is closed.
					iterKey := iter.Key()
					resumeKey = make([]byte, len(iterKey))
					copy(resumeKey, iterKey)

					logProgress = true
					return errBatchFinished
				}

				// The version 3 utxo set consists of an entry for each unspent
				// output.
				//
				// The serialized value format is roughly:
				//
				//   <block height><block index><flags><rest of data>
				//
				//   Field              Type    Size
				//   block height       VLQ     variable
				//   block index        VLQ     variable
				//   flags              VLQ     variable
				//   rest of data...
				//
				// The serialized flags format is:
				//   bit  0     - containing transaction is a coinbase
				//   bit  1     - containing transaction has an expiry
				//   bits 2-5   - transaction type
				//   bits 6-7   - unused
				//
				// Given the flags field is the only thing that needs to be
				// examined, the following specifically finds the relevant byte.
				// Deserialize the block height and block index to locate the
				// offset of the flags that need to examined.
				serialized := iter.Value()
				_, bytesRead := deserializeVLQ(serialized)
				offset := bytesRead
				if offset >= len(serialized) {
					return errDeserialize("unexpected end of data after height")
				}
				_, bytesRead = deserializeVLQ(serialized[offset:])
				offset += bytesRead
				if offset >= len(serialized) {
					return errDeserialize("unexpected end of data after index")
				}

				// Determine the transaction type from the flags.
				const (
					txOutFlagTxTypeBitmask  = 0x3c
					txOutFlagTxTypeShift    = 2
					stakeTxTypeTreasuryBase = 6
				)
				flags, bytesRead := deserializeVLQ(serialized[offset:])
				offset += bytesRead
				if offset >= len(serialized) {
					return errDeserialize("unexpected end of data after flags")
				}
				txType := (flags & txOutFlagTxTypeBitmask) >> txOutFlagTxTypeShift

				// Remove treasurybase outputs.
				if txType == stakeTxTypeTreasuryBase {
					if err := tx.Delete(iter.Key()); err != nil {
						return err
					}
					numRemoved++
				}
			}

			return nil
		}()
		isFullyDone := err == nil
		if (isFullyDone || logProgress) && numRemoved > 0 {
			totalRemoved += uint64(numRemoved)
			log.Infof("Removed %d unspendable outputs (%d total)", numRemoved,
				totalRemoved)
		}
		return isFullyDone, err
	}

	// Remove the entries in batches for the reasons mentioned above.
	if err := utxoBackendBatchedUpdate(ctx, utxoBackend, doBatch); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done updating UTXO database.  Total unspendable outputs "+
		"removed: %d in %v", totalRemoved, elapsed)

	return nil
}

// upgradeUtxoDbToVersion3 upgrades a UTXO database from version 2 to version 3.
// This entails removing all treasurybase outputs since they are never directly
// spendable and thus should not be part of the utxo set.
func upgradeUtxoDbToVersion3(ctx context.Context, utxoBackend UtxoBackend, utxoDbInfo *UtxoBackendInfo) error {
	if interruptRequested(ctx) {
		return errInterruptRequested
	}

	log.Info("Upgrading UTXO database to version 3...")
	start := time.Now()

	// Remove treasurybase outputs from the utxo set.
	if err := removeTreasuryBaseUtxos(ctx, utxoBackend); err != nil {
		return err
	}

	// Update and persist the UTXO database version.
	utxoDbInfo.version = 3
	if err := utxoBackend.PutInfo(utxoDbInfo); err != nil {
		return err
	}

	elapsed := time.Since(start).Round(time.Millisecond)
	log.Infof("Done upgrading database in %v.", elapsed)
	return nil
}

// checkDBTooOldToUpgrade returns an ErrDBTooOldToUpgrade error if the provided
// database version can no longer be upgraded due to being too old.
func checkDBTooOldToUpgrade(dbVersion uint32) error {
	const lowestSupportedUpgradeVer = 5
	if dbVersion < lowestSupportedUpgradeVer {
		str := fmt.Sprintf("database versions prior to version %d are no "+
			"longer supported (current version: %d)", lowestSupportedUpgradeVer,
			dbVersion)
		return contextError(ErrDBTooOldToUpgrade, str)
	}

	return nil
}

// CheckDBTooOldToUpgrade returns an ErrDBTooOldToUpgrade error if the provided
// database can no longer be upgraded due to being too old.
func CheckDBTooOldToUpgrade(db database.DB) error {
	// Fetch the database versioning information.
	var dbInfo *databaseInfo
	err := db.View(func(dbTx database.Tx) error {
		dbInfo = dbFetchDatabaseInfo(dbTx)
		return nil
	})
	if err != nil {
		return err
	}

	// The database has not been initialized and thus will be created at the
	// latest version.
	if dbInfo == nil {
		return nil
	}

	return checkDBTooOldToUpgrade(dbInfo.version)
}

// upgradeDB upgrades old database versions to the newest version by applying
// all possible upgrades iteratively.  Note that spend journal and utxo set
// upgrades are handled separately starting with version 3 of the spend journal
// and utxo set.
//
// NOTE: The passed database info will be updated with the latest versions.
func upgradeDB(ctx context.Context, db database.DB, chainParams *chaincfg.Params, dbInfo *databaseInfo) error {
	// Upgrading databases prior to version 5 is no longer supported due to a
	// major overhaul that took place at that version.
	if err := checkDBTooOldToUpgrade(dbInfo.version); err != nil {
		// Override the error with some additional instructions in this path
		// since it means the caller did not check up front before attempting
		// to create a chain instance.
		if errors.Is(err, ErrDBTooOldToUpgrade) {
			str := fmt.Sprintf("%s -- please delete the existing database "+
				"and restart the application to continue", err)
			err = contextError(ErrDBTooOldToUpgrade, str)
		}
		return err
	}

	// Update to a version 6 database if needed.  This entails unmarking all
	// blocks previously marked failed so they are eligible for validation again
	// under the new consensus rules and creating and storing version 2 GCS
	// filters for all blocks in the main chain.
	if dbInfo.version == 5 {
		err := upgradeToVersion6(ctx, db, chainParams, dbInfo)
		if err != nil {
			return err
		}
	}

	// Update to the version 3 block index format if needed.
	if dbInfo.version == 6 && dbInfo.bidxVer == 2 {
		err := migrateBlockIndexVersion2To3(ctx, db, dbInfo)
		if err != nil {
			return err
		}
	}

	// Update to a version 7 database if needed.  This entails migrating the
	// utxoset and spend journal to the v2 format.
	if dbInfo.version == 6 {
		err := upgradeToVersion7(ctx, db, dbInfo)
		if err != nil {
			return err
		}
	}

	// Update to a version 8 database if needed.  This entails ensuring the
	// treasury buckets from v7 are created and unmarking all blocks previously
	// marked failed so they are eligible for validation again under the new
	// consensus rules.
	if dbInfo.version == 7 {
		if err := upgradeToVersion8(ctx, db, dbInfo); err != nil {
			return err
		}
	}

	// Update to a version 10 database if needed.  This entails writing the
	// database spend journal version to the database so that it can be decoupled
	// from the overall version of the block database.
	//
	// This applies to both versions 8 and 9 because previously version 9 handled
	// the migration to version 3 of the spend journal and utxo set.  That
	// migration has now been decoupled to run based on the spend journal and utxo
	// set versions, but databases that are already upgraded to version 9 still
	// need to be handled here as well.
	if dbInfo.version == 8 || dbInfo.version == 9 {
		if err := upgradeToVersion10(ctx, db, dbInfo); err != nil {
			return err
		}
	}

	// Update to a version 11 database if needed.  This entails bumping the
	// overall block database version to 11 to prevent downgrades as the UTXO
	// database is being moved in this same set of changes.
	if dbInfo.version == 10 {
		if err := upgradeToVersion11(ctx, db, dbInfo); err != nil {
			return err
		}
	}

	// Update to a version 12 database if needed.  This entails correcting block
	// index and ticket database entries to account for an issue in legacy code
	// that caused votes which also include treasury spend vote data to be
	// incorrectly marked as missed as well as unmarking all blocks previously
	// marked failed so they are eligible for validation again under the new
	// consensus rules.
	if dbInfo.version == 11 {
		if err := upgradeToVersion12(ctx, db, chainParams, dbInfo); err != nil {
			return err
		}
	}

	// Update to a version 13 database if needed.  This entails populating the
	// header commitments journal for all blocks after the point that header
	// commitments activated and also have the block data and associated filter
	// data available.
	if dbInfo.version == 12 {
		if err := upgradeToVersion13(ctx, db, chainParams, dbInfo); err != nil {
			return err
		}
	}

	// Update to a version 14 database if needed.  This entails removing the
	// legacy spend consumer dependencies bucket if needed and correcting the
	// total subsidy value in the chain state.
	if dbInfo.version == 13 {
		if err := upgradeToVersion14(ctx, db, chainParams, dbInfo); err != nil {
			return err
		}
	}

	return nil
}

// upgradeSpendJournal upgrades old spend journal versions to the newest version
// by applying all possible upgrades iteratively.  The spend journal version was
// decoupled from the overall block database version as of spend journal version
// 3, so spend journal upgrades prior to version 3 are handled in the overall
// block database upgrade path.
//
// NOTE: The database info housed by the passed blockchain instance will be
// updated with the latest versions.
func upgradeSpendJournal(ctx context.Context, b *BlockChain) error {
	// Update to a version 3 spend journal as needed.
	if b.dbInfo.stxoVer == 2 {
		if err := upgradeSpendJournalToVersion3(ctx, b); err != nil {
			return err
		}
	}

	return nil
}

// upgradeUtxoDb upgrades old utxo database versions to the newest version by
// applying all possible upgrades iteratively.  The utxo set version was
// decoupled from the overall block database version as of utxo set version 3,
// so utxo set upgrades prior to version 3 are handled in the block database
// upgrade path.
//
// NOTE: The database info housed in the passed block database instance and
// backend info housed in the passed utxo backend instance will be updated with
// the latest versions.
func upgradeUtxoDb(ctx context.Context, db database.DB, utxoBackend UtxoBackend) error {
	// Fetch the backend versioning info.
	utxoDbInfo, err := utxoBackend.FetchInfo()
	if err != nil {
		return err
	}

	// Update to a version 3 utxo set as needed.
	if utxoDbInfo.utxoVer == 2 {
		err := upgradeUtxoSetToVersion3(ctx, db, utxoBackend, utxoDbInfo)
		if err != nil {
			return err
		}
	}

	// Update to a version 2 UTXO database as needed.
	if utxoDbInfo.version == 1 {
		err := upgradeUtxoDbToVersion2(ctx, utxoBackend, utxoDbInfo)
		if err != nil {
			return err
		}
	}

	// Check if the block database contains the UTXO set or state.  If it does,
	// move the UTXO set and state from the block database to the UTXO database.
	blockDbUtxoSetExists := false
	db.View(func(dbTx database.Tx) error {
		if dbTx.Metadata().Bucket([]byte("utxosetv3")) != nil ||
			dbTx.Metadata().Get([]byte("utxosetstate")) != nil {

			blockDbUtxoSetExists = true
		}
		return nil
	})
	if blockDbUtxoSetExists {
		err := separateUtxoDatabase(ctx, db, utxoBackend)
		if err != nil {
			return err
		}
	}

	// Update to a version 3 utxo database if needed.  This entails removing all
	// treasurybase outputs since they are never directly spendable and thus
	// should not be part of the utxo set.
	if utxoDbInfo.version == 2 {
		err := upgradeUtxoDbToVersion3(ctx, utxoBackend, utxoDbInfo)
		if err != nil {
			return err
		}
	}

	return nil
}
