github.com/lbryio/lbcd@v0.22.119/blockchain/indexers/manager.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package indexers
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  
    11  	"github.com/lbryio/lbcd/blockchain"
    12  	"github.com/lbryio/lbcd/chaincfg/chainhash"
    13  	"github.com/lbryio/lbcd/database"
    14  	"github.com/lbryio/lbcd/wire"
    15  	btcutil "github.com/lbryio/lbcutil"
    16  )
    17  
    18  var (
    19  	// indexTipsBucketName is the name of the db bucket used to house the
    20  	// current tip of each index.
    21  	indexTipsBucketName = []byte("idxtips")
    22  )
    23  
    24  // -----------------------------------------------------------------------------
    25  // The index manager tracks the current tip of each index by using a parent
    26  // bucket that contains an entry for index.
    27  //
    28  // The serialized format for an index tip is:
    29  //
    30  //   [<block hash><block height>],...
    31  //
    32  //   Field           Type             Size
    33  //   block hash      chainhash.Hash   chainhash.HashSize
    34  //   block height    uint32           4 bytes
    35  // -----------------------------------------------------------------------------
    36  
    37  // dbPutIndexerTip uses an existing database transaction to update or add the
    38  // current tip for the given index to the provided values.
    39  func dbPutIndexerTip(dbTx database.Tx, idxKey []byte, hash *chainhash.Hash, height int32) error {
    40  	serialized := make([]byte, chainhash.HashSize+4)
    41  	copy(serialized, hash[:])
    42  	byteOrder.PutUint32(serialized[chainhash.HashSize:], uint32(height))
    43  
    44  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    45  	return indexesBucket.Put(idxKey, serialized)
    46  }
    47  
    48  // dbFetchIndexerTip uses an existing database transaction to retrieve the
    49  // hash and height of the current tip for the provided index.
    50  func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32, error) {
    51  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    52  	serialized := indexesBucket.Get(idxKey)
    53  	if len(serialized) < chainhash.HashSize+4 {
    54  		return nil, 0, database.Error{
    55  			ErrorCode: database.ErrCorruption,
    56  			Description: fmt.Sprintf("unexpected end of data for "+
    57  				"index %q tip", string(idxKey)),
    58  		}
    59  	}
    60  
    61  	var hash chainhash.Hash
    62  	copy(hash[:], serialized[:chainhash.HashSize])
    63  	height := int32(byteOrder.Uint32(serialized[chainhash.HashSize:]))
    64  	return &hash, height, nil
    65  }
    66  
    67  // dbIndexConnectBlock adds all of the index entries associated with the
    68  // given block using the provided indexer and updates the tip of the indexer
    69  // accordingly.  An error will be returned if the current tip for the indexer is
    70  // not the previous block for the passed block.
    71  func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block,
    72  	stxo []blockchain.SpentTxOut) error {
    73  
    74  	// Assert that the block being connected properly connects to the
    75  	// current tip of the index.
    76  	idxKey := indexer.Key()
    77  	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	if !curTipHash.IsEqual(&block.MsgBlock().Header.PrevBlock) {
    82  		return AssertError(fmt.Sprintf("dbIndexConnectBlock must be "+
    83  			"called with a block that extends the current index "+
    84  			"tip (%s, tip %s, block %s)", indexer.Name(),
    85  			curTipHash, block.Hash()))
    86  	}
    87  
    88  	// Notify the indexer with the connected block so it can index it.
    89  	if err := indexer.ConnectBlock(dbTx, block, stxo); err != nil {
    90  		return err
    91  	}
    92  
    93  	// Update the current index tip.
    94  	return dbPutIndexerTip(dbTx, idxKey, block.Hash(), block.Height())
    95  }
    96  
    97  // dbIndexDisconnectBlock removes all of the index entries associated with the
    98  // given block using the provided indexer and updates the tip of the indexer
    99  // accordingly.  An error will be returned if the current tip for the indexer is
   100  // not the passed block.
   101  func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *btcutil.Block,
   102  	stxo []blockchain.SpentTxOut) error {
   103  
   104  	// Assert that the block being disconnected is the current tip of the
   105  	// index.
   106  	idxKey := indexer.Key()
   107  	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	if !curTipHash.IsEqual(block.Hash()) {
   112  		return AssertError(fmt.Sprintf("dbIndexDisconnectBlock must "+
   113  			"be called with the block at the current index tip "+
   114  			"(%s, tip %s, block %s)", indexer.Name(),
   115  			curTipHash, block.Hash()))
   116  	}
   117  
   118  	// Notify the indexer with the disconnected block so it can remove all
   119  	// of the appropriate entries.
   120  	if err := indexer.DisconnectBlock(dbTx, block, stxo); err != nil {
   121  		return err
   122  	}
   123  
   124  	// Update the current index tip.
   125  	prevHash := &block.MsgBlock().Header.PrevBlock
   126  	return dbPutIndexerTip(dbTx, idxKey, prevHash, block.Height()-1)
   127  }
   128  
   129  // Manager defines an index manager that manages multiple optional indexes and
   130  // implements the blockchain.IndexManager interface so it can be seamlessly
   131  // plugged into normal chain processing.
   132  type Manager struct {
   133  	db             database.DB
   134  	enabledIndexes []Indexer
   135  }
   136  
   137  // Ensure the Manager type implements the blockchain.IndexManager interface.
   138  var _ blockchain.IndexManager = (*Manager)(nil)
   139  
   140  // indexDropKey returns the key for an index which indicates it is in the
   141  // process of being dropped.
   142  func indexDropKey(idxKey []byte) []byte {
   143  	dropKey := make([]byte, len(idxKey)+1)
   144  	dropKey[0] = 'd'
   145  	copy(dropKey[1:], idxKey)
   146  	return dropKey
   147  }
   148  
   149  // maybeFinishDrops determines if each of the enabled indexes are in the middle
   150  // of being dropped and finishes dropping them when the are.  This is necessary
   151  // because dropping and index has to be done in several atomic steps rather than
   152  // one big atomic step due to the massive number of entries.
   153  func (m *Manager) maybeFinishDrops(interrupt <-chan struct{}) error {
   154  	indexNeedsDrop := make([]bool, len(m.enabledIndexes))
   155  	err := m.db.View(func(dbTx database.Tx) error {
   156  		// None of the indexes needs to be dropped if the index tips
   157  		// bucket hasn't been created yet.
   158  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   159  		if indexesBucket == nil {
   160  			return nil
   161  		}
   162  
   163  		// Mark the indexer as requiring a drop if one is already in
   164  		// progress.
   165  		for i, indexer := range m.enabledIndexes {
   166  			dropKey := indexDropKey(indexer.Key())
   167  			if indexesBucket.Get(dropKey) != nil {
   168  				indexNeedsDrop[i] = true
   169  			}
   170  		}
   171  
   172  		return nil
   173  	})
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	if interruptRequested(interrupt) {
   179  		return errInterruptRequested
   180  	}
   181  
   182  	// Finish dropping any of the enabled indexes that are already in the
   183  	// middle of being dropped.
   184  	for i, indexer := range m.enabledIndexes {
   185  		if !indexNeedsDrop[i] {
   186  			continue
   187  		}
   188  
   189  		log.Infof("Resuming %s drop", indexer.Name())
   190  		err := dropIndex(m.db, indexer.Key(), indexer.Name(), interrupt)
   191  		if err != nil {
   192  			return err
   193  		}
   194  	}
   195  
   196  	return nil
   197  }
   198  
   199  // maybeCreateIndexes determines if each of the enabled indexes have already
   200  // been created and creates them if not.
   201  func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error {
   202  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   203  	for _, indexer := range m.enabledIndexes {
   204  		// Nothing to do if the index tip already exists.
   205  		idxKey := indexer.Key()
   206  		if indexesBucket.Get(idxKey) != nil {
   207  			continue
   208  		}
   209  
   210  		// The tip for the index does not exist, so create it and
   211  		// invoke the create callback for the index so it can perform
   212  		// any one-time initialization it requires.
   213  		if err := indexer.Create(dbTx); err != nil {
   214  			return err
   215  		}
   216  
   217  		// Set the tip for the index to values which represent an
   218  		// uninitialized index.
   219  		err := dbPutIndexerTip(dbTx, idxKey, &chainhash.Hash{}, -1)
   220  		if err != nil {
   221  			return err
   222  		}
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // Init initializes the enabled indexes.  This is called during chain
   229  // initialization and primarily consists of catching up all indexes to the
   230  // current best chain tip.  This is necessary since each index can be disabled
   231  // and re-enabled at any time and attempting to catch-up indexes at the same
   232  // time new blocks are being downloaded would lead to an overall longer time to
   233  // catch up due to the I/O contention.
   234  //
   235  // This is part of the blockchain.IndexManager interface.
   236  func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{}) error {
   237  	// Nothing to do when no indexes are enabled.
   238  	if len(m.enabledIndexes) == 0 {
   239  		return nil
   240  	}
   241  
   242  	if interruptRequested(interrupt) {
   243  		return errInterruptRequested
   244  	}
   245  
   246  	// Finish and drops that were previously interrupted.
   247  	if err := m.maybeFinishDrops(interrupt); err != nil {
   248  		return err
   249  	}
   250  
   251  	// Create the initial state for the indexes as needed.
   252  	err := m.db.Update(func(dbTx database.Tx) error {
   253  		// Create the bucket for the current tips as needed.
   254  		meta := dbTx.Metadata()
   255  		_, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
   256  		if err != nil {
   257  			return err
   258  		}
   259  
   260  		return m.maybeCreateIndexes(dbTx)
   261  	})
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	// Initialize each of the enabled indexes.
   267  	for _, indexer := range m.enabledIndexes {
   268  		if err := indexer.Init(); err != nil {
   269  			return err
   270  		}
   271  	}
   272  
   273  	// Rollback indexes to the main chain if their tip is an orphaned fork.
   274  	// This is fairly unlikely, but it can happen if the chain is
   275  	// reorganized while the index is disabled.  This has to be done in
   276  	// reverse order because later indexes can depend on earlier ones.
   277  	for i := len(m.enabledIndexes); i > 0; i-- {
   278  		indexer := m.enabledIndexes[i-1]
   279  
   280  		// Fetch the current tip for the index.
   281  		var height int32
   282  		var hash *chainhash.Hash
   283  		err := m.db.View(func(dbTx database.Tx) error {
   284  			idxKey := indexer.Key()
   285  			hash, height, err = dbFetchIndexerTip(dbTx, idxKey)
   286  			return err
   287  		})
   288  		if err != nil {
   289  			return err
   290  		}
   291  
   292  		// Nothing to do if the index does not have any entries yet.
   293  		if height == -1 {
   294  			continue
   295  		}
   296  
   297  		// Loop until the tip is a block that exists in the main chain.
   298  		initialHeight := height
   299  		for !chain.MainChainHasBlock(hash) {
   300  			// At this point the index tip is orphaned, so load the
   301  			// orphaned block from the database directly and
   302  			// disconnect it from the index.  The block has to be
   303  			// loaded directly since it is no longer in the main
   304  			// chain and thus the chain.BlockByHash function would
   305  			// error.
   306  			var block *btcutil.Block
   307  			err := m.db.View(func(dbTx database.Tx) error {
   308  				blockBytes, err := dbTx.FetchBlock(hash)
   309  				if err != nil {
   310  					return err
   311  				}
   312  				block, err = btcutil.NewBlockFromBytes(blockBytes)
   313  				if err != nil {
   314  					return err
   315  				}
   316  				block.SetHeight(height)
   317  				return err
   318  			})
   319  			if err != nil {
   320  				return err
   321  			}
   322  
   323  			// We'll also grab the set of outputs spent by this
   324  			// block so we can remove them from the index.
   325  			spentTxos, err := chain.FetchSpendJournal(block)
   326  			if err != nil {
   327  				return err
   328  			}
   329  
   330  			// With the block and stxo set for that block retrieved,
   331  			// we can now update the index itself.
   332  			err = m.db.Update(func(dbTx database.Tx) error {
   333  				// Remove all of the index entries associated
   334  				// with the block and update the indexer tip.
   335  				err = dbIndexDisconnectBlock(
   336  					dbTx, indexer, block, spentTxos,
   337  				)
   338  				if err != nil {
   339  					return err
   340  				}
   341  
   342  				// Update the tip to the previous block.
   343  				hash = &block.MsgBlock().Header.PrevBlock
   344  				height--
   345  
   346  				return nil
   347  			})
   348  			if err != nil {
   349  				return err
   350  			}
   351  
   352  			if interruptRequested(interrupt) {
   353  				return errInterruptRequested
   354  			}
   355  		}
   356  
   357  		if initialHeight != height {
   358  			log.Infof("Removed %d orphaned blocks from %s "+
   359  				"(heights %d to %d)", initialHeight-height,
   360  				indexer.Name(), height+1, initialHeight)
   361  		}
   362  	}
   363  
   364  	// Fetch the current tip heights for each index along with tracking the
   365  	// lowest one so the catchup code only needs to start at the earliest
   366  	// block and is able to skip connecting the block for the indexes that
   367  	// don't need it.
   368  	bestHeight := chain.BestSnapshot().Height
   369  	lowestHeight := bestHeight
   370  	indexerHeights := make([]int32, len(m.enabledIndexes))
   371  	err = m.db.View(func(dbTx database.Tx) error {
   372  		for i, indexer := range m.enabledIndexes {
   373  			idxKey := indexer.Key()
   374  			hash, height, err := dbFetchIndexerTip(dbTx, idxKey)
   375  			if err != nil {
   376  				return err
   377  			}
   378  
   379  			log.Debugf("Current %s tip (height %d, hash %v)",
   380  				indexer.Name(), height, hash)
   381  			indexerHeights[i] = height
   382  			if height < lowestHeight {
   383  				lowestHeight = height
   384  			}
   385  		}
   386  		return nil
   387  	})
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	// Nothing to index if all of the indexes are caught up.
   393  	if lowestHeight == bestHeight {
   394  		return nil
   395  	}
   396  
   397  	// Create a progress logger for the indexing process below.
   398  	progressLogger := newBlockProgressLogger("Indexed", log)
   399  
   400  	// At this point, one or more indexes are behind the current best chain
   401  	// tip and need to be caught up, so log the details and loop through
   402  	// each block that needs to be indexed.
   403  	log.Infof("Catching up indexes from height %d to %d", lowestHeight,
   404  		bestHeight)
   405  	for height := lowestHeight + 1; height <= bestHeight; height++ {
   406  		// Load the block for the height since it is required to index
   407  		// it.
   408  		block, err := chain.BlockByHeight(height)
   409  		if err != nil {
   410  			return err
   411  		}
   412  
   413  		if interruptRequested(interrupt) {
   414  			return errInterruptRequested
   415  		}
   416  
   417  		// Connect the block for all indexes that need it.
   418  		var spentTxos []blockchain.SpentTxOut
   419  		for i, indexer := range m.enabledIndexes {
   420  			// Skip indexes that don't need to be updated with this
   421  			// block.
   422  			if indexerHeights[i] >= height {
   423  				continue
   424  			}
   425  
   426  			// When the index requires all of the referenced txouts
   427  			// and they haven't been loaded yet, they need to be
   428  			// retrieved from the spend journal.
   429  			if spentTxos == nil && indexNeedsInputs(indexer) {
   430  				spentTxos, err = chain.FetchSpendJournal(block)
   431  				if err != nil {
   432  					return err
   433  				}
   434  			}
   435  
   436  			err := m.db.Update(func(dbTx database.Tx) error {
   437  				return dbIndexConnectBlock(
   438  					dbTx, indexer, block, spentTxos,
   439  				)
   440  			})
   441  			if err != nil {
   442  				return err
   443  			}
   444  			indexerHeights[i] = height
   445  		}
   446  
   447  		// Log indexing progress.
   448  		progressLogger.LogBlockHeight(block)
   449  
   450  		if interruptRequested(interrupt) {
   451  			return errInterruptRequested
   452  		}
   453  	}
   454  
   455  	log.Infof("Indexes caught up to height %d", bestHeight)
   456  	return nil
   457  }
   458  
   459  // indexNeedsInputs returns whether or not the index needs access to the txouts
   460  // referenced by the transaction inputs being indexed.
   461  func indexNeedsInputs(index Indexer) bool {
   462  	if idx, ok := index.(NeedsInputser); ok {
   463  		return idx.NeedsInputs()
   464  	}
   465  
   466  	return false
   467  }
   468  
   469  // dbFetchTx looks up the passed transaction hash in the transaction index and
   470  // loads it from the database.
   471  func dbFetchTx(dbTx database.Tx, hash *chainhash.Hash) (*wire.MsgTx, error) {
   472  	// Look up the location of the transaction.
   473  	blockRegion, err := dbFetchTxIndexEntry(dbTx, hash)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	if blockRegion == nil {
   478  		return nil, fmt.Errorf("transaction %v not found", hash)
   479  	}
   480  
   481  	// Load the raw transaction bytes from the database.
   482  	txBytes, err := dbTx.FetchBlockRegion(blockRegion)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  
   487  	// Deserialize the transaction.
   488  	var msgTx wire.MsgTx
   489  	err = msgTx.Deserialize(bytes.NewReader(txBytes))
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  
   494  	return &msgTx, nil
   495  }
   496  
   497  // ConnectBlock must be invoked when a block is extending the main chain.  It
   498  // keeps track of the state of each index it is managing, performs some sanity
   499  // checks, and invokes each indexer.
   500  //
   501  // This is part of the blockchain.IndexManager interface.
   502  func (m *Manager) ConnectBlock(dbTx database.Tx, block *btcutil.Block,
   503  	stxos []blockchain.SpentTxOut) error {
   504  
   505  	// Call each of the currently active optional indexes with the block
   506  	// being connected so they can update accordingly.
   507  	for _, index := range m.enabledIndexes {
   508  		err := dbIndexConnectBlock(dbTx, index, block, stxos)
   509  		if err != nil {
   510  			return err
   511  		}
   512  	}
   513  	return nil
   514  }
   515  
   516  // DisconnectBlock must be invoked when a block is being disconnected from the
   517  // end of the main chain.  It keeps track of the state of each index it is
   518  // managing, performs some sanity checks, and invokes each indexer to remove
   519  // the index entries associated with the block.
   520  //
   521  // This is part of the blockchain.IndexManager interface.
   522  func (m *Manager) DisconnectBlock(dbTx database.Tx, block *btcutil.Block,
   523  	stxo []blockchain.SpentTxOut) error {
   524  
   525  	// Call each of the currently active optional indexes with the block
   526  	// being disconnected so they can update accordingly.
   527  	for _, index := range m.enabledIndexes {
   528  		err := dbIndexDisconnectBlock(dbTx, index, block, stxo)
   529  		if err != nil {
   530  			return err
   531  		}
   532  	}
   533  	return nil
   534  }
   535  
   536  // NewManager returns a new index manager with the provided indexes enabled.
   537  //
   538  // The manager returned satisfies the blockchain.IndexManager interface and thus
   539  // cleanly plugs into the normal blockchain processing path.
   540  func NewManager(db database.DB, enabledIndexes []Indexer) *Manager {
   541  	return &Manager{
   542  		db:             db,
   543  		enabledIndexes: enabledIndexes,
   544  	}
   545  }
   546  
   547  // dropIndex drops the passed index from the database.  Since indexes can be
   548  // massive, it deletes the index in multiple database transactions in order to
   549  // keep memory usage to reasonable levels.  It also marks the drop in progress
   550  // so the drop can be resumed if it is stopped before it is done before the
   551  // index can be used again.
   552  func dropIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan struct{}) error {
   553  	// Nothing to do if the index doesn't already exist.
   554  	var needsDelete bool
   555  	err := db.View(func(dbTx database.Tx) error {
   556  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   557  		if indexesBucket != nil && indexesBucket.Get(idxKey) != nil {
   558  			needsDelete = true
   559  		}
   560  		return nil
   561  	})
   562  	if err != nil {
   563  		return err
   564  	}
   565  	if !needsDelete {
   566  		log.Infof("Not dropping %s because it does not exist", idxName)
   567  		return nil
   568  	}
   569  
   570  	// Mark that the index is in the process of being dropped so that it
   571  	// can be resumed on the next start if interrupted before the process is
   572  	// complete.
   573  	log.Infof("Dropping all %s entries.  This might take a while...",
   574  		idxName)
   575  	err = db.Update(func(dbTx database.Tx) error {
   576  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   577  		return indexesBucket.Put(indexDropKey(idxKey), idxKey)
   578  	})
   579  	if err != nil {
   580  		return err
   581  	}
   582  
   583  	// Since the indexes can be so large, attempting to simply delete
   584  	// the bucket in a single database transaction would result in massive
   585  	// memory usage and likely crash many systems due to ulimits.  In order
   586  	// to avoid this, use a cursor to delete a maximum number of entries out
   587  	// of the bucket at a time. Recurse buckets depth-first to delete any
   588  	// sub-buckets.
   589  	const maxDeletions = 2000000
   590  	var totalDeleted uint64
   591  
   592  	// Recurse through all buckets in the index, cataloging each for
   593  	// later deletion.
   594  	var subBuckets [][][]byte
   595  	var subBucketClosure func(database.Tx, []byte, [][]byte) error
   596  	subBucketClosure = func(dbTx database.Tx,
   597  		subBucket []byte, tlBucket [][]byte) error {
   598  		// Get full bucket name and append to subBuckets for later
   599  		// deletion.
   600  		var bucketName [][]byte
   601  		if (tlBucket == nil) || (len(tlBucket) == 0) {
   602  			bucketName = append(bucketName, subBucket)
   603  		} else {
   604  			bucketName = append(tlBucket, subBucket)
   605  		}
   606  		subBuckets = append(subBuckets, bucketName)
   607  		// Recurse sub-buckets to append to subBuckets slice.
   608  		bucket := dbTx.Metadata()
   609  		for _, subBucketName := range bucketName {
   610  			bucket = bucket.Bucket(subBucketName)
   611  		}
   612  		return bucket.ForEachBucket(func(k []byte) error {
   613  			return subBucketClosure(dbTx, k, bucketName)
   614  		})
   615  	}
   616  
   617  	// Call subBucketClosure with top-level bucket.
   618  	err = db.View(func(dbTx database.Tx) error {
   619  		return subBucketClosure(dbTx, idxKey, nil)
   620  	})
   621  	if err != nil {
   622  		return nil
   623  	}
   624  
   625  	// Iterate through each sub-bucket in reverse, deepest-first, deleting
   626  	// all keys inside them and then dropping the buckets themselves.
   627  	for i := range subBuckets {
   628  		bucketName := subBuckets[len(subBuckets)-1-i]
   629  		// Delete maxDeletions key/value pairs at a time.
   630  		for numDeleted := maxDeletions; numDeleted == maxDeletions; {
   631  			numDeleted = 0
   632  			err := db.Update(func(dbTx database.Tx) error {
   633  				subBucket := dbTx.Metadata()
   634  				for _, subBucketName := range bucketName {
   635  					subBucket = subBucket.Bucket(subBucketName)
   636  				}
   637  				cursor := subBucket.Cursor()
   638  				for ok := cursor.First(); ok; ok = cursor.Next() &&
   639  					numDeleted < maxDeletions {
   640  
   641  					if err := cursor.Delete(); err != nil {
   642  						return err
   643  					}
   644  					numDeleted++
   645  				}
   646  				return nil
   647  			})
   648  			if err != nil {
   649  				return err
   650  			}
   651  
   652  			if numDeleted > 0 {
   653  				totalDeleted += uint64(numDeleted)
   654  				log.Infof("Deleted %d keys (%d total) from %s",
   655  					numDeleted, totalDeleted, idxName)
   656  			}
   657  		}
   658  
   659  		if interruptRequested(interrupt) {
   660  			return errInterruptRequested
   661  		}
   662  
   663  		// Drop the bucket itself.
   664  		err = db.Update(func(dbTx database.Tx) error {
   665  			bucket := dbTx.Metadata()
   666  			for j := 0; j < len(bucketName)-1; j++ {
   667  				bucket = bucket.Bucket(bucketName[j])
   668  			}
   669  			return bucket.DeleteBucket(bucketName[len(bucketName)-1])
   670  		})
   671  	}
   672  
   673  	// Call extra index specific deinitialization for the transaction index.
   674  	if idxName == txIndexName {
   675  		if err := dropBlockIDIndex(db); err != nil {
   676  			return err
   677  		}
   678  	}
   679  
   680  	// Remove the index tip, index bucket, and in-progress drop flag now
   681  	// that all index entries have been removed.
   682  	err = db.Update(func(dbTx database.Tx) error {
   683  		meta := dbTx.Metadata()
   684  		indexesBucket := meta.Bucket(indexTipsBucketName)
   685  		if err := indexesBucket.Delete(idxKey); err != nil {
   686  			return err
   687  		}
   688  
   689  		return indexesBucket.Delete(indexDropKey(idxKey))
   690  	})
   691  	if err != nil {
   692  		return err
   693  	}
   694  
   695  	log.Infof("Dropped %s", idxName)
   696  	return nil
   697  }