func indexNeedsInputs(index Indexer) bool {
	if idx, ok := index.(NeedsInputser); ok {
		return idx.NeedsInputs()
	}
	return false
}
indexNeedsInputs returns whether or not the index needs access to the txouts
referenced by the transaction inputs being indexed.
package indexers
import (
	"bytes"
	"fmt"
	"github.com/HcashOrg/hcashd/blockchain"
	"github.com/HcashOrg/hcashd/blockchain/internal/progresslog"
	"github.com/HcashOrg/hcashd/blockchain/stake"
	"github.com/HcashOrg/hcashd/chaincfg"
	"github.com/HcashOrg/hcashd/chaincfg/chainhash"
	"github.com/HcashOrg/hcashd/database"
	"github.com/HcashOrg/hcashd/wire"
	"github.com/HcashOrg/hcashutil"
)
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, parent *hcashutil.Block, view *blockchain.UtxoViewpoint) 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, parent, view); err != nil {
		return err
	}
	return dbPutIndexerTip(dbTx, idxKey, block.Hash(), int32(block.Height()))
}
func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block, parent *hcashutil.Block, view *blockchain.UtxoViewpoint) 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, parent, view); err != nil {
		return err
	}
	prevHash := &block.MsgBlock().Header.PrevBlock
	return dbPutIndexerTip(dbTx, idxKey, prevHash, int32(block.Height()-1))
}
type Manager struct {
	params         *chaincfg.Params
	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() 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
	}
	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())
		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
		}
		genesisBlockHash := m.params.GenesisBlock.BlockHash()
		err := dbPutIndexerTip(dbTx, idxKey, &genesisBlockHash, 0)
		if err != nil {
			return err
		}
	}
	return nil
}
func (m *Manager) Init(chain *blockchain.BlockChain) error {
	if len(m.enabledIndexes) == 0 {
		return nil
	}
	if err := m.maybeFinishDrops(); 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
		}
	}
	var cachedBlock *hcashutil.Block
	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 == 0 {
			continue
		}
		initialHeight := height
		err = m.db.Update(func(dbTx database.Tx) error {
			for {
				if blockchain.DBMainChainHasBlock(dbTx, hash) {
					break
				}
				var block *hcashutil.Block
				if cachedBlock == nil && height > 0 {
					block, err = blockchain.DBFetchBlockByHeight(dbTx,
						int64(height))
					if err != nil {
						return err
					}
				} else {
					block = cachedBlock
				}
				parent, err := blockchain.DBFetchBlockByHeight(dbTx,
					int64(height)-1)
				if err != nil {
					return err
				}
				cachedBlock = parent
				var view *blockchain.UtxoViewpoint
				if indexNeedsInputs(indexer) {
					var err error
					view, err = makeUtxoView(dbTx, block, parent)
					if err != nil {
						return err
					}
				}
				err = dbIndexDisconnectBlock(dbTx, indexer,
					block, parent, view)
				if err != nil {
					return err
				}
				hash = &block.MsgBlock().Header.PrevBlock
				height--
			}
			return nil
		})
		if err != nil {
			return err
		}
		if initialHeight != height {
			log.Infof("Removed %d orphaned blocks from %s "+
				"(heights %d to %d)", initialHeight-height,
				indexer.Name(), height+1, initialHeight)
		}
	}
	bestHeight := int32(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 := progresslog.NewBlockProgressLogger("Indexed", log)
	log.Infof("Catching up indexes from height %d to %d", lowestHeight,
		bestHeight)
	var cachedParent *hcashutil.Block
	for height := lowestHeight + 1; height <= bestHeight; height++ {
		var block, parent *hcashutil.Block
		err = m.db.Update(func(dbTx database.Tx) error {
			if cachedParent == nil && height > 0 {
				parent, err = blockchain.DBFetchBlockByHeight(
					dbTx, int64(height-1))
				if err != nil {
					return err
				}
			} else {
				parent = cachedParent
			}
			block, err = blockchain.DBFetchBlockByHeight(dbTx,
				int64(height))
			if err != nil {
				return err
			}
			cachedParent = block
			var view *blockchain.UtxoViewpoint
			for i, indexer := range m.enabledIndexes {
				if indexerHeights[i] >= height {
					continue
				}
				if view == nil && indexNeedsInputs(indexer) {
					var errMakeView error
					view, errMakeView = makeUtxoView(dbTx, block, parent)
					if errMakeView != nil {
						return errMakeView
					}
				}
				err = dbIndexConnectBlock(dbTx, indexer, block,
					parent, view)
				if err != nil {
					return err
				}
				indexerHeights[i] = height
			}
			return nil
		})
		if err != nil {
			return err
		}
		progressLogger.LogBlockHeight(block.MsgBlock(), parent.MsgBlock())
	}
	log.Infof("Indexes caught up to height %d", bestHeight)
	return nil
}