OSDN Git Service

Merge pull request #201 from Bytom/v0.1
[bytom/vapor.git] / vendor / github.com / btcsuite / btcd / blockchain / indexers / manager.go
diff --git a/vendor/github.com/btcsuite/btcd/blockchain/indexers/manager.go b/vendor/github.com/btcsuite/btcd/blockchain/indexers/manager.go
deleted file mode 100644 (file)
index 7de6f5f..0000000
+++ /dev/null
@@ -1,643 +0,0 @@
-// Copyright (c) 2016 The btcsuite developers
-// Use of this source code is governed by an ISC
-// license that can be found in the LICENSE file.
-
-package indexers
-
-import (
-       "bytes"
-       "fmt"
-
-       "github.com/btcsuite/btcd/blockchain"
-       "github.com/btcsuite/btcd/chaincfg/chainhash"
-       "github.com/btcsuite/btcd/database"
-       "github.com/btcsuite/btcd/wire"
-       "github.com/btcsuite/btcutil"
-)
-
-var (
-       // indexTipsBucketName is the name of the db bucket used to house the
-       // current tip of each index.
-       indexTipsBucketName = []byte("idxtips")
-)
-
-// -----------------------------------------------------------------------------
-// The index manager tracks the current tip of each index by using a parent
-// bucket that contains an entry for index.
-//
-// The serialized format for an index tip is:
-//
-//   [<block hash><block height>],...
-//
-//   Field           Type             Size
-//   block hash      chainhash.Hash   chainhash.HashSize
-//   block height    uint32           4 bytes
-// -----------------------------------------------------------------------------
-
-// dbPutIndexerTip uses an existing database transaction to update or add the
-// current tip for the given index to the provided values.
-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)
-}
-
-// dbFetchIndexerTip uses an existing database transaction to retrieve the
-// hash and height of the current tip for the provided index.
-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
-}
-
-// dbIndexConnectBlock adds all of the index entries associated with the
-// given block using the provided indexer and updates the tip of the indexer
-// accordingly.  An error will be returned if the current tip for the indexer is
-// not the previous block for the passed block.
-func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
-       // Assert that the block being connected properly connects to the
-       // current tip of the index.
-       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()))
-       }
-
-       // Notify the indexer with the connected block so it can index it.
-       if err := indexer.ConnectBlock(dbTx, block, view); err != nil {
-               return err
-       }
-
-       // Update the current index tip.
-       return dbPutIndexerTip(dbTx, idxKey, block.Hash(), block.Height())
-}
-
-// dbIndexDisconnectBlock removes all of the index entries associated with the
-// given block using the provided indexer and updates the tip of the indexer
-// accordingly.  An error will be returned if the current tip for the indexer is
-// not the passed block.
-func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
-       // Assert that the block being disconnected is the current tip of the
-       // index.
-       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()))
-       }
-
-       // Notify the indexer with the disconnected block so it can remove all
-       // of the appropriate entries.
-       if err := indexer.DisconnectBlock(dbTx, block, view); err != nil {
-               return err
-       }
-
-       // Update the current index tip.
-       prevHash := &block.MsgBlock().Header.PrevBlock
-       return dbPutIndexerTip(dbTx, idxKey, prevHash, block.Height()-1)
-}
-
-// Manager defines an index manager that manages multiple optional indexes and
-// implements the blockchain.IndexManager interface so it can be seamlessly
-// plugged into normal chain processing.
-type Manager struct {
-       db             database.DB
-       enabledIndexes []Indexer
-}
-
-// Ensure the Manager type implements the blockchain.IndexManager interface.
-var _ blockchain.IndexManager = (*Manager)(nil)
-
-// indexDropKey returns the key for an index which indicates it is in the
-// process of being dropped.
-func indexDropKey(idxKey []byte) []byte {
-       dropKey := make([]byte, len(idxKey)+1)
-       dropKey[0] = 'd'
-       copy(dropKey[1:], idxKey)
-       return dropKey
-}
-
-// maybeFinishDrops determines if each of the enabled indexes are in the middle
-// of being dropped and finishes dropping them when the are.  This is necessary
-// because dropping and index has to be done in several atomic steps rather than
-// one big atomic step due to the massive number of entries.
-func (m *Manager) maybeFinishDrops() error {
-       indexNeedsDrop := make([]bool, len(m.enabledIndexes))
-       err := m.db.View(func(dbTx database.Tx) error {
-               // None of the indexes needs to be dropped if the index tips
-               // bucket hasn't been created yet.
-               indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
-               if indexesBucket == nil {
-                       return nil
-               }
-
-               // Make the indexer as requiring a drop if one is already in
-               // progress.
-               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
-       }
-
-       // Finish dropping any of the enabled indexes that are already in the
-       // middle of being dropped.
-       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
-}
-
-// maybeCreateIndexes determines if each of the enabled indexes have already
-// been created and creates them if not.
-func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error {
-       indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
-       for _, indexer := range m.enabledIndexes {
-               // Nothing to do if the index tip already exists.
-               idxKey := indexer.Key()
-               if indexesBucket.Get(idxKey) != nil {
-                       continue
-               }
-
-               // The tip for the index does not exist, so create it and
-               // invoke the create callback for the index so it can perform
-               // any one-time initialization it requires.
-               if err := indexer.Create(dbTx); err != nil {
-                       return err
-               }
-
-               // Set the tip for the index to values which represent an
-               // uninitialized index.
-               err := dbPutIndexerTip(dbTx, idxKey, &chainhash.Hash{}, -1)
-               if err != nil {
-                       return err
-               }
-       }
-
-       return nil
-}
-
-// Init initializes the enabled indexes.  This is called during chain
-// initialization and primarily consists of catching up all indexes to the
-// current best chain tip.  This is necessary since each index can be disabled
-// and re-enabled at any time and attempting to catch-up indexes at the same
-// time new blocks are being downloaded would lead to an overall longer time to
-// catch up due to the I/O contention.
-//
-// This is part of the blockchain.IndexManager interface.
-func (m *Manager) Init(chain *blockchain.BlockChain) error {
-       // Nothing to do when no indexes are enabled.
-       if len(m.enabledIndexes) == 0 {
-               return nil
-       }
-
-       // Finish and drops that were previously interrupted.
-       if err := m.maybeFinishDrops(); err != nil {
-               return err
-       }
-
-       // Create the initial state for the indexes as needed.
-       err := m.db.Update(func(dbTx database.Tx) error {
-               // Create the bucket for the current tips as needed.
-               meta := dbTx.Metadata()
-               _, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
-               if err != nil {
-                       return err
-               }
-
-               return m.maybeCreateIndexes(dbTx)
-       })
-       if err != nil {
-               return err
-       }
-
-       // Initialize each of the enabled indexes.
-       for _, indexer := range m.enabledIndexes {
-               if err := indexer.Init(); err != nil {
-                       return err
-               }
-       }
-
-       // Rollback indexes to the main chain if their tip is an orphaned fork.
-       // This is fairly unlikely, but it can happen if the chain is
-       // reorganized while the index is disabled.  This has to be done in
-       // reverse order because later indexes can depend on earlier ones.
-       for i := len(m.enabledIndexes); i > 0; i-- {
-               indexer := m.enabledIndexes[i-1]
-
-               // Fetch the current tip for the index.
-               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
-               }
-
-               // Nothing to do if the index does not have any entries yet.
-               if height == -1 {
-                       continue
-               }
-
-               // Loop until the tip is a block that exists in the main chain.
-               initialHeight := height
-               for !chain.MainChainHasBlock(hash) {
-                       // At this point the index tip is orphaned, so load the
-                       // orphaned block from the database directly and
-                       // disconnect it from the index.  The block has to be
-                       // loaded directly since it is no longer in the main
-                       // chain and thus the chain.BlockByHash function would
-                       // error.
-                       err = m.db.Update(func(dbTx database.Tx) error {
-                               blockBytes, err := dbTx.FetchBlock(hash)
-                               if err != nil {
-                                       return err
-                               }
-                               block, err := btcutil.NewBlockFromBytes(blockBytes)
-                               if err != nil {
-                                       return err
-                               }
-                               block.SetHeight(height)
-
-                               // When the index requires all of the referenced
-                               // txouts they need to be retrieved from the
-                               // transaction index.
-                               var view *blockchain.UtxoViewpoint
-                               if indexNeedsInputs(indexer) {
-                                       var err error
-                                       view, err = makeUtxoView(dbTx, block)
-                                       if err != nil {
-                                               return err
-                                       }
-                               }
-
-                               // Remove all of the index entries associated
-                               // with the block and update the indexer tip.
-                               err = dbIndexDisconnectBlock(dbTx, indexer,
-                                       block, view)
-                               if err != nil {
-                                       return err
-                               }
-
-                               // Update the tip to the previous block.
-                               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)
-               }
-       }
-
-       // Fetch the current tip heights for each index along with tracking the
-       // lowest one so the catchup code only needs to start at the earliest
-       // block and is able to skip connecting the block for the indexes that
-       // don't need it.
-       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
-       }
-
-       // Nothing to index if all of the indexes are caught up.
-       if lowestHeight == bestHeight {
-               return nil
-       }
-
-       // Create a progress logger for the indexing process below.
-       progressLogger := newBlockProgressLogger("Indexed", log)
-
-       // At this point, one or more indexes are behind the current best chain
-       // tip and need to be caught up, so log the details and loop through
-       // each block that needs to be indexed.
-       log.Infof("Catching up indexes from height %d to %d", lowestHeight,
-               bestHeight)
-       for height := lowestHeight + 1; height <= bestHeight; height++ {
-               // Load the block for the height since it is required to index
-               // it.
-               block, err := chain.BlockByHeight(height)
-               if err != nil {
-                       return err
-               }
-
-               // Connect the block for all indexes that need it.
-               var view *blockchain.UtxoViewpoint
-               for i, indexer := range m.enabledIndexes {
-                       // Skip indexes that don't need to be updated with this
-                       // block.
-                       if indexerHeights[i] >= height {
-                               continue
-                       }
-
-                       err := m.db.Update(func(dbTx database.Tx) error {
-                               // When the index requires all of the referenced
-                               // txouts and they haven't been loaded yet, they
-                               // need to be retrieved from the transaction
-                               // index.
-                               if view == nil && indexNeedsInputs(indexer) {
-                                       var err error
-                                       view, err = makeUtxoView(dbTx, block)
-                                       if err != nil {
-                                               return err
-                                       }
-                               }
-                               return dbIndexConnectBlock(dbTx, indexer, block,
-                                       view)
-                       })
-                       if err != nil {
-                               return err
-                       }
-                       indexerHeights[i] = height
-               }
-
-               // Log indexing progress.
-               progressLogger.LogBlockHeight(block)
-       }
-
-       log.Infof("Indexes caught up to height %d", bestHeight)
-       return nil
-}
-
-// indexNeedsInputs returns whether or not the index needs access to the txouts
-// referenced by the transaction inputs being indexed.
-func indexNeedsInputs(index Indexer) bool {
-       if idx, ok := index.(NeedsInputser); ok {
-               return idx.NeedsInputs()
-       }
-
-       return false
-}
-
-// dbFetchTx looks up the passed transaction hash in the transaction index and
-// loads it from the database.
-func dbFetchTx(dbTx database.Tx, hash *chainhash.Hash) (*wire.MsgTx, error) {
-       // Look up the location of the transaction.
-       blockRegion, err := dbFetchTxIndexEntry(dbTx, hash)
-       if err != nil {
-               return nil, err
-       }
-       if blockRegion == nil {
-               return nil, fmt.Errorf("transaction %v not found", hash)
-       }
-
-       // Load the raw transaction bytes from the database.
-       txBytes, err := dbTx.FetchBlockRegion(blockRegion)
-       if err != nil {
-               return nil, err
-       }
-
-       // Deserialize the transaction.
-       var msgTx wire.MsgTx
-       err = msgTx.Deserialize(bytes.NewReader(txBytes))
-       if err != nil {
-               return nil, err
-       }
-
-       return &msgTx, nil
-}
-
-// makeUtxoView creates a mock unspent transaction output view by using the
-// transaction index in order to look up all inputs referenced by the
-// transactions in the block.  This is sometimes needed when catching indexes up
-// because many of the txouts could actually already be spent however the
-// associated scripts are still required to index them.
-func makeUtxoView(dbTx database.Tx, block *btcutil.Block) (*blockchain.UtxoViewpoint, error) {
-       view := blockchain.NewUtxoViewpoint()
-       for txIdx, tx := range block.Transactions() {
-               // Coinbases do not reference any inputs.  Since the block is
-               // required to have already gone through full validation, it has
-               // already been proven on the first transaction in the block is
-               // a coinbase.
-               if txIdx == 0 {
-                       continue
-               }
-
-               // Use the transaction index to load all of the referenced
-               // inputs and add their outputs to the view.
-               for _, txIn := range tx.MsgTx().TxIn {
-                       originOut := &txIn.PreviousOutPoint
-                       originTx, err := dbFetchTx(dbTx, &originOut.Hash)
-                       if err != nil {
-                               return nil, err
-                       }
-
-                       view.AddTxOuts(btcutil.NewTx(originTx), 0)
-               }
-       }
-
-       return view, nil
-}
-
-// ConnectBlock must be invoked when a block is extending the main chain.  It
-// keeps track of the state of each index it is managing, performs some sanity
-// checks, and invokes each indexer.
-//
-// This is part of the blockchain.IndexManager interface.
-func (m *Manager) ConnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
-       // Call each of the currently active optional indexes with the block
-       // being connected so they can update accordingly.
-       for _, index := range m.enabledIndexes {
-               err := dbIndexConnectBlock(dbTx, index, block, view)
-               if err != nil {
-                       return err
-               }
-       }
-       return nil
-}
-
-// DisconnectBlock must be invoked when a block is being disconnected from the
-// end of the main chain.  It keeps track of the state of each index it is
-// managing, performs some sanity checks, and invokes each indexer to remove
-// the index entries associated with the block.
-//
-// This is part of the blockchain.IndexManager interface.
-func (m *Manager) DisconnectBlock(dbTx database.Tx, block *btcutil.Block, view *blockchain.UtxoViewpoint) error {
-       // Call each of the currently active optional indexes with the block
-       // being disconnected so they can update accordingly.
-       for _, index := range m.enabledIndexes {
-               err := dbIndexDisconnectBlock(dbTx, index, block, view)
-               if err != nil {
-                       return err
-               }
-       }
-       return nil
-}
-
-// NewManager returns a new index manager with the provided indexes enabled.
-//
-// The manager returned satisfies the blockchain.IndexManager interface and thus
-// cleanly plugs into the normal blockchain processing path.
-func NewManager(db database.DB, enabledIndexes []Indexer) *Manager {
-       return &Manager{
-               db:             db,
-               enabledIndexes: enabledIndexes,
-       }
-}
-
-// 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.
-func dropIndex(db database.DB, idxKey []byte, idxName string) error {
-       // Nothing to do if the index doesn't already exist.
-       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
-       }
-
-       // Mark that the index is in the process of being dropped so that it
-       // can be resumed on the next start if interrupted before the process is
-       // complete.
-       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
-       }
-
-       // Since the indexes can be so large, attempting to simply delete
-       // the bucket in a single database transaction would result in massive
-       // memory usage and likely crash many systems due to ulimits.  In order
-       // to avoid this, use a cursor to delete a maximum number of entries out
-       // of the bucket at a time.
-       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(idxKey)
-                       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, idxName)
-               }
-       }
-
-       // Call extra index specific deinitialization for the transaction index.
-       if idxName == txIndexName {
-               if err := dropBlockIDIndex(db); err != nil {
-                       return err
-               }
-       }
-
-       // Remove the index tip, index bucket, and in-progress drop flag now
-       // that all index entries have been removed.
-       err = db.Update(func(dbTx database.Tx) error {
-               meta := dbTx.Metadata()
-               indexesBucket := meta.Bucket(indexTipsBucketName)
-               if err := indexesBucket.Delete(idxKey); err != nil {
-                       return err
-               }
-
-               if err := meta.DeleteBucket(idxKey); err != nil {
-                       return err
-               }
-
-               return indexesBucket.Delete(indexDropKey(idxKey))
-       })
-       if err != nil {
-               return err
-       }
-
-       log.Infof("Dropped %s", idxName)
-       return nil
-}