func dropIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan struct{}) error {
	var needsDelete bool
	err := db.View(func(dbTx database.Tx) error {
		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
		if indexesBucket != nil && indexesBucket.Get(idxKey) != nil {
			needsDelete = true
		}
		return nil
	})
	if err != nil {
		return err
	}
	if !needsDelete {
		log.Infof("Not dropping %s because it does not exist", idxName)
		return nil
	}
	log.Infof("Dropping all %s entries.  This might take a while...",
		idxName)
	err = db.Update(func(dbTx database.Tx) error {
		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
		return indexesBucket.Put(indexDropKey(idxKey), idxKey)
	})
	if err != nil {
		return err
	}
	const maxDeletions = 2000000
	var totalDeleted uint64
	var subBuckets [][][]byte
	var subBucketClosure func(database.Tx, []byte, [][]byte) error
	subBucketClosure = func(dbTx database.Tx,
		subBucket []byte, tlBucket [][]byte) error {
		var bucketName [][]byte
		if (tlBucket == nil) || (len(tlBucket) == 0) {
			bucketName = append(bucketName, subBucket)
		} else {
			bucketName = append(tlBucket, subBucket)
		}
		subBuckets = append(subBuckets, bucketName)
		bucket := dbTx.Metadata()
		for _, subBucketName := range bucketName {
			bucket = bucket.Bucket(subBucketName)
		}
		return bucket.ForEachBucket(func(k []byte) error {
			return subBucketClosure(dbTx, k, bucketName)
		})
	}
	err = db.View(func(dbTx database.Tx) error {
		return subBucketClosure(dbTx, idxKey, nil)
	})
	if err != nil {
		return nil
	}
	for i := range subBuckets {
		bucketName := subBuckets[len(subBuckets)-1-i]
		for numDeleted := maxDeletions; numDeleted == maxDeletions; {
			numDeleted = 0
			err := db.Update(func(dbTx database.Tx) error {
				subBucket := dbTx.Metadata()
				for _, subBucketName := range bucketName {
					subBucket = subBucket.Bucket(subBucketName)
				}
				cursor := subBucket.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, idxName)
			}
		}
		if interruptRequested(interrupt) {
			return errInterruptRequested
		}
		err = db.Update(func(dbTx database.Tx) error {
			bucket := dbTx.Metadata()
			for j := 0; j < len(bucketName)-1; j++ {
				bucket = bucket.Bucket(bucketName[j])
			}
			return bucket.DeleteBucket(bucketName[len(bucketName)-1])
		})
	}
	if idxName == txIndexName {
		if err := dropBlockIDIndex(db); err != nil {
			return err
		}
	}
	err = db.Update(func(dbTx database.Tx) error {
		meta := dbTx.Metadata()
		indexesBucket := meta.Bucket(indexTipsBucketName)
		if err := indexesBucket.Delete(idxKey); err != nil {
			return err
		}
		return indexesBucket.Delete(indexDropKey(idxKey))
	})
	if err != nil {
		return err
	}
	log.Infof("Dropped %s", idxName)
	return nil
}
dropIndex drops the passed index from the database.  Since indexes can be
massive, it deletes the index in multiple database transactions in order to
keep memory usage to reasonable levels.  It also marks the drop in progress
so the drop can be resumed if it is stopped before it is done before the
index can be used again.
package indexers
import (
	"bytes"
	"fmt"
	"github.com/soteria-dag/soterd/blockchain"
	"github.com/soteria-dag/soterd/chaincfg/chainhash"
	"github.com/soteria-dag/soterd/database"
	"github.com/soteria-dag/soterd/wire"
	"github.com/soteria-dag/soterd/soterutil"
)
var (
	indexTipsBucketName = []byte("idxtips")
)
func dbPutIndexerTip(dbTx database.Tx, idxKey []byte, hash *chainhash.Hash, height int32) error {
	serialized := make([]byte, chainhash.HashSize+4)
	copy(serialized, hash[:])
	byteOrder.PutUint32(serialized[chainhash.HashSize:], uint32(height))
	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
	return indexesBucket.Put(idxKey, serialized)
}
func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32, error) {
	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
	serialized := indexesBucket.Get(idxKey)
	if len(serialized) < chainhash.HashSize+4 {
		return nil, 0, database.Error{
			ErrorCode: database.ErrCorruption,
			Description: fmt.Sprintf("unexpected end of data for "+
				"index %q tip", string(idxKey)),
		}
	}
	var hash chainhash.Hash
	copy(hash[:], serialized[:chainhash.HashSize])
	height := int32(byteOrder.Uint32(serialized[chainhash.HashSize:]))
	return &hash, height, nil
}
func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *soterutil.Block,
	stxo []blockchain.SpentTxOut) error {
	idxKey := indexer.Key()
	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
	if err != nil {
		return err
	}
	if !curTipHash.IsEqual(&block.MsgBlock().Header.PrevBlock) {
		return AssertError(fmt.Sprintf("dbIndexConnectBlock must be "+
			"called with a block that extends the current index "+
			"tip (%s, tip %s, block %s)", indexer.Name(),
			curTipHash, block.Hash()))
	}
	if err := indexer.ConnectBlock(dbTx, block, stxo); err != nil {
		return err
	}
	return dbPutIndexerTip(dbTx, idxKey, block.Hash(), block.Height())
}
func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *soterutil.Block,
	stxo []blockchain.SpentTxOut) error {
	idxKey := indexer.Key()
	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
	if err != nil {
		return err
	}
	if !curTipHash.IsEqual(block.Hash()) {
		return AssertError(fmt.Sprintf("dbIndexDisconnectBlock must "+
			"be called with the block at the current index tip "+
			"(%s, tip %s, block %s)", indexer.Name(),
			curTipHash, block.Hash()))
	}
	if err := indexer.DisconnectBlock(dbTx, block, stxo); err != nil {
		return err
	}
	prevHash := &block.MsgBlock().Header.PrevBlock
	return dbPutIndexerTip(dbTx, idxKey, prevHash, block.Height()-1)
}
type Manager struct {
	db             database.DB
	enabledIndexes []Indexer
}
var _ blockchain.IndexManager = (*Manager)(nil)
func indexDropKey(idxKey []byte) []byte {
	dropKey := make([]byte, len(idxKey)+1)
	dropKey[0] = 'd'
	copy(dropKey[1:], idxKey)
	return dropKey
}
func (m *Manager) maybeFinishDrops(interrupt <-chan struct{}) error {
	indexNeedsDrop := make([]bool, len(m.enabledIndexes))
	err := m.db.View(func(dbTx database.Tx) error {
		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
		if indexesBucket == nil {
			return nil
		}
		for i, indexer := range m.enabledIndexes {
			dropKey := indexDropKey(indexer.Key())
			if indexesBucket.Get(dropKey) != nil {
				indexNeedsDrop[i] = true
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if interruptRequested(interrupt) {
		return errInterruptRequested
	}
	for i, indexer := range m.enabledIndexes {
		if !indexNeedsDrop[i] {
			continue
		}
		log.Infof("Resuming %s drop", indexer.Name())
		err := dropIndex(m.db, indexer.Key(), indexer.Name(), interrupt)
		if err != nil {
			return err
		}
	}
	return nil
}
func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error {
	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
	for _, indexer := range m.enabledIndexes {
		idxKey := indexer.Key()
		if indexesBucket.Get(idxKey) != nil {
			continue
		}
		if err := indexer.Create(dbTx); err != nil {
			return err
		}
		err := dbPutIndexerTip(dbTx, idxKey, &chainhash.Hash{}, -1)
		if err != nil {
			return err
		}
	}
	return nil
}
func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{}) error {
	if len(m.enabledIndexes) == 0 {
		return nil
	}
	if interruptRequested(interrupt) {
		return errInterruptRequested
	}
	if err := m.maybeFinishDrops(interrupt); err != nil {
		return err
	}
	err := m.db.Update(func(dbTx database.Tx) error {
		meta := dbTx.Metadata()
		_, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
		if err != nil {
			return err
		}
		return m.maybeCreateIndexes(dbTx)
	})
	if err != nil {
		return err
	}
	for _, indexer := range m.enabledIndexes {
		if err := indexer.Init(); err != nil {
			return err
		}
	}
	for i := len(m.enabledIndexes); i > 0; i-- {
		indexer := m.enabledIndexes[i-1]
		var height int32
		var hash *chainhash.Hash
		err := m.db.View(func(dbTx database.Tx) error {
			idxKey := indexer.Key()
			hash, height, err = dbFetchIndexerTip(dbTx, idxKey)
			return err
		})
		if err != nil {
			return err
		}
		if height == -1 {
			continue
		}
		initialHeight := height
		for !chain.MainChainHasBlock(hash) {
			var block *soterutil.Block
			err := m.db.View(func(dbTx database.Tx) error {
				blockBytes, err := dbTx.FetchBlock(hash)
				if err != nil {
					return err
				}
				block, err = soterutil.NewBlockFromBytes(blockBytes)
				if err != nil {
					return err
				}
				block.SetHeight(height)
				return err
			})
			if err != nil {
				return err
			}
			spentTxos, err := chain.FetchSpendJournal(block)
			if err != nil {
				return err
			}
			err = m.db.Update(func(dbTx database.Tx) error {
				err = dbIndexDisconnectBlock(
					dbTx, indexer, block, spentTxos,
				)
				if err != nil {
					return err
				}
				hash = &block.MsgBlock().Header.PrevBlock
				height--
				return nil
			})
			if err != nil {
				return err
			}
			if interruptRequested(interrupt) {
				return errInterruptRequested
			}
		}
		if initialHeight != height {
			log.Infof("Removed %d orphaned blocks from %s "+
				"(heights %d to %d)", initialHeight-height,
				indexer.Name(), height+1, initialHeight)
		}
	}
	bestHeight := chain.BestSnapshot().Height
	lowestHeight := bestHeight
	indexerHeights := make([]int32, len(m.enabledIndexes))
	err = m.db.View(func(dbTx database.Tx) error {
		for i, indexer := range m.enabledIndexes {
			idxKey := indexer.Key()
			hash, height, err := dbFetchIndexerTip(dbTx, idxKey)
			if err != nil {
				return err
			}
			log.Debugf("Current %s tip (height %d, hash %v)",
				indexer.Name(), height, hash)
			indexerHeights[i] = height
			if height < lowestHeight {
				lowestHeight = height
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if lowestHeight == bestHeight {
		return nil
	}
	progressLogger := newBlockProgressLogger("Indexed", log)
	log.Infof("Catching up indexes from height %d to %d", lowestHeight,
		bestHeight)
	for height := lowestHeight + 1; height <= bestHeight; height++ {
		block, err := chain.BlockByHeight(height)
		if err != nil {
			return err
		}
		if interruptRequested(interrupt) {
			return errInterruptRequested
		}
		var spentTxos []blockchain.SpentTxOut
		for i, indexer := range m.enabledIndexes {
			if indexerHeights[i] >= height {
				continue
			}
			if spentTxos == nil && indexNeedsInputs(indexer) {
				spentTxos, err = chain.FetchSpendJournal(block)
				if err != nil {
					return err
				}
			}
			err := m.db.Update(func(dbTx database.Tx) error {
				return dbIndexConnectBlock(
					dbTx, indexer, block, spentTxos,
				)
			})
			if err != nil {
				return err
			}
			indexerHeights[i] = height
		}
		progressLogger.LogBlockHeight(block)
		if interruptRequested(interrupt) {
			return errInterruptRequested
		}
	}
	log.Infof("Indexes caught up to height %d", bestHeight)
	return nil
}
func indexNeedsInputs(index Indexer) bool {
	if idx, ok := index.(NeedsInputser); ok {
		return idx.NeedsInputs()
	}
	return false
}
func dbFetchTx(dbTx database.Tx, hash *chainhash.Hash) (*wire.MsgTx, error) {
	blockRegion, err := dbFetchTxIndexEntry(dbTx, hash)
	if err != nil {
		return nil, err
	}
	if blockRegion == nil {
		return nil, fmt.Errorf("transaction %v not found", hash)
	}
	txBytes, err := dbTx.FetchBlockRegion(blockRegion)
	if err != nil {
		return nil, err
	}
	var msgTx wire.MsgTx
	err = msgTx.Deserialize(bytes.NewReader(txBytes))
	if err != nil {
		return nil, err
	}
	return &msgTx, nil
}
func (m *Manager) ConnectBlock(dbTx database.Tx, block *soterutil.Block,
	stxos []blockchain.SpentTxOut) error {
	for _, index := range m.enabledIndexes {
		err := dbIndexConnectBlock(dbTx, index, block, stxos)
		if err != nil {
			return err
		}
	}
	return nil
}
func (m *Manager) DisconnectBlock(dbTx database.Tx, block *soterutil.Block,
	stxo []blockchain.SpentTxOut) error {
	for _, index := range m.enabledIndexes {
		err := dbIndexDisconnectBlock(dbTx, index, block, stxo)
		if err != nil {
			return err
		}
	}
	return nil
}
func NewManager(db database.DB, enabledIndexes []Indexer) *Manager {
	return &Manager{
		db:             db,
		enabledIndexes: enabledIndexes,
	}
}