github.com/decred/dcrd/blockchain@v1.2.1/indexers/manager.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Copyright (c) 2016-2017 The Decred developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package indexers
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  
    12  	"github.com/decred/dcrd/blockchain"
    13  	"github.com/decred/dcrd/blockchain/internal/progresslog"
    14  	"github.com/decred/dcrd/blockchain/stake"
    15  	"github.com/decred/dcrd/chaincfg"
    16  	"github.com/decred/dcrd/chaincfg/chainhash"
    17  	"github.com/decred/dcrd/database"
    18  	"github.com/decred/dcrd/dcrutil"
    19  	"github.com/decred/dcrd/wire"
    20  )
    21  
    22  var (
    23  	// indexTipsBucketName is the name of the db bucket used to house the
    24  	// current tip of each index.
    25  	indexTipsBucketName = []byte("idxtips")
    26  )
    27  
    28  // -----------------------------------------------------------------------------
    29  // The index manager tracks the current tip and version of each index by using a
    30  // parent bucket that contains an entry for index and a separate entry for its
    31  // version.
    32  //
    33  // The serialized format for an index tip is:
    34  //
    35  //   [<block hash><block height>],...
    36  //
    37  //   Field           Type             Size
    38  //   block hash      chainhash.Hash   chainhash.HashSize
    39  //   block height    uint32           4 bytes
    40  //
    41  // The serialized format for an index version is:
    42  //
    43  //   [<version>]
    44  //
    45  //   Field           Type             Size
    46  //   index version   uint32           4 bytes
    47  // -----------------------------------------------------------------------------
    48  
    49  // dbPutIndexerTip uses an existing database transaction to update or add the
    50  // current tip for the given index to the provided values.
    51  func dbPutIndexerTip(dbTx database.Tx, idxKey []byte, hash *chainhash.Hash, height int32) error {
    52  	serialized := make([]byte, chainhash.HashSize+4)
    53  	copy(serialized, hash[:])
    54  	byteOrder.PutUint32(serialized[chainhash.HashSize:], uint32(height))
    55  
    56  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    57  	return indexesBucket.Put(idxKey, serialized)
    58  }
    59  
    60  // dbFetchIndexerTip uses an existing database transaction to retrieve the
    61  // hash and height of the current tip for the provided index.
    62  func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*chainhash.Hash, int32, error) {
    63  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    64  	serialized := indexesBucket.Get(idxKey)
    65  	if len(serialized) < chainhash.HashSize+4 {
    66  		return nil, 0, database.Error{
    67  			ErrorCode: database.ErrCorruption,
    68  			Description: fmt.Sprintf("unexpected end of data for "+
    69  				"index %q tip", string(idxKey)),
    70  		}
    71  	}
    72  
    73  	var hash chainhash.Hash
    74  	copy(hash[:], serialized[:chainhash.HashSize])
    75  	height := int32(byteOrder.Uint32(serialized[chainhash.HashSize:]))
    76  	return &hash, height, nil
    77  }
    78  
    79  // indexVersionKey returns the key for an index which houses the current version
    80  // of the index.
    81  func indexVersionKey(idxKey []byte) []byte {
    82  	verKey := make([]byte, len(idxKey)+1)
    83  	verKey[0] = 'v'
    84  	copy(verKey[1:], idxKey)
    85  	return verKey
    86  }
    87  
    88  // dbPutIndexerVersion uses an existing database transaction to update the
    89  // version for the given index to the provided value.
    90  func dbPutIndexerVersion(dbTx database.Tx, idxKey []byte, version uint32) error {
    91  	serialized := make([]byte, 4)
    92  	byteOrder.PutUint32(serialized[0:4], version)
    93  
    94  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    95  	return indexesBucket.Put(indexVersionKey(idxKey), serialized)
    96  }
    97  
    98  // dbFetchIndexerVersion uses an existing database transaction to retrieve the
    99  // version of the provided index.  It will return one if the version has not
   100  // previously been stored.
   101  func dbFetchIndexerVersion(dbTx database.Tx, idxKey []byte) (uint32, error) {
   102  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   103  	serialized := indexesBucket.Get(indexVersionKey(idxKey))
   104  	if len(serialized) == 0 {
   105  		return 1, nil
   106  	}
   107  
   108  	if len(serialized) < 4 {
   109  		return 0, database.Error{
   110  			ErrorCode: database.ErrCorruption,
   111  			Description: fmt.Sprintf("unexpected end of data for "+
   112  				"index %q version", string(idxKey)),
   113  		}
   114  	}
   115  
   116  	version := byteOrder.Uint32(serialized)
   117  	return version, nil
   118  }
   119  
   120  // dbIndexConnectBlock adds all of the index entries associated with the
   121  // given block using the provided indexer and updates the tip of the indexer
   122  // accordingly.  An error will be returned if the current tip for the indexer is
   123  // not the previous block for the passed block.
   124  func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error {
   125  	// Assert that the block being connected properly connects to the
   126  	// current tip of the index.
   127  	idxKey := indexer.Key()
   128  	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
   129  	if err != nil {
   130  		return err
   131  	}
   132  	if !curTipHash.IsEqual(&block.MsgBlock().Header.PrevBlock) {
   133  		return AssertError(fmt.Sprintf("dbIndexConnectBlock must be "+
   134  			"called with a block that extends the current index "+
   135  			"tip (%s, tip %s, block %s)", indexer.Name(),
   136  			curTipHash, block.Hash()))
   137  	}
   138  
   139  	// Notify the indexer with the connected block so it can index it.
   140  	if err := indexer.ConnectBlock(dbTx, block, parent, view); err != nil {
   141  		return err
   142  	}
   143  
   144  	// Update the current index tip.
   145  	return dbPutIndexerTip(dbTx, idxKey, block.Hash(), int32(block.Height()))
   146  }
   147  
   148  // dbIndexDisconnectBlock removes all of the index entries associated with the
   149  // given block using the provided indexer and updates the tip of the indexer
   150  // accordingly.  An error will be returned if the current tip for the indexer is
   151  // not the passed block.
   152  func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error {
   153  	// Assert that the block being disconnected is the current tip of the
   154  	// index.
   155  	idxKey := indexer.Key()
   156  	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	if !curTipHash.IsEqual(block.Hash()) {
   161  		return AssertError(fmt.Sprintf("dbIndexDisconnectBlock must "+
   162  			"be called with the block at the current index tip "+
   163  			"(%s, tip %s, block %s)", indexer.Name(),
   164  			curTipHash, block.Hash()))
   165  	}
   166  
   167  	// Notify the indexer with the disconnected block so it can remove all
   168  	// of the appropriate entries.
   169  	if err := indexer.DisconnectBlock(dbTx, block, parent, view); err != nil {
   170  		return err
   171  	}
   172  
   173  	// Update the current index tip.
   174  	prevHash := &block.MsgBlock().Header.PrevBlock
   175  	return dbPutIndexerTip(dbTx, idxKey, prevHash, int32(block.Height()-1))
   176  }
   177  
   178  // Manager defines an index manager that manages multiple optional indexes and
   179  // implements the blockchain.IndexManager interface so it can be seamlessly
   180  // plugged into normal chain processing.
   181  type Manager struct {
   182  	params         *chaincfg.Params
   183  	db             database.DB
   184  	enabledIndexes []Indexer
   185  }
   186  
   187  // Ensure the Manager type implements the blockchain.IndexManager interface.
   188  var _ blockchain.IndexManager = (*Manager)(nil)
   189  
   190  // indexDropKey returns the key for an index which indicates it is in the
   191  // process of being dropped.
   192  func indexDropKey(idxKey []byte) []byte {
   193  	dropKey := make([]byte, len(idxKey)+1)
   194  	dropKey[0] = 'd'
   195  	copy(dropKey[1:], idxKey)
   196  	return dropKey
   197  }
   198  
   199  // maybeFinishDrops determines if each of the enabled indexes are in the middle
   200  // of being dropped and finishes dropping them when the are.  This is necessary
   201  // because dropping and index has to be done in several atomic steps rather than
   202  // one big atomic step due to the massive number of entries.
   203  func (m *Manager) maybeFinishDrops(interrupt <-chan struct{}) error {
   204  	indexNeedsDrop := make([]bool, len(m.enabledIndexes))
   205  	err := m.db.View(func(dbTx database.Tx) error {
   206  		// None of the indexes needs to be dropped if the index tips
   207  		// bucket hasn't been created yet.
   208  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   209  		if indexesBucket == nil {
   210  			return nil
   211  		}
   212  
   213  		// Mark the indexer as requiring a drop if one is already in
   214  		// progress.
   215  		for i, indexer := range m.enabledIndexes {
   216  			dropKey := indexDropKey(indexer.Key())
   217  			if indexesBucket.Get(dropKey) != nil {
   218  				indexNeedsDrop[i] = true
   219  			}
   220  		}
   221  
   222  		return nil
   223  	})
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	if interruptRequested(interrupt) {
   229  		return errInterruptRequested
   230  	}
   231  
   232  	// Finish dropping any of the enabled indexes that are already in the
   233  	// middle of being dropped.
   234  	for i, indexer := range m.enabledIndexes {
   235  		if !indexNeedsDrop[i] {
   236  			continue
   237  		}
   238  
   239  		log.Infof("Resuming %s drop", indexer.Name())
   240  
   241  		switch d := indexer.(type) {
   242  		case IndexDropper:
   243  			err := d.DropIndex(m.db, interrupt)
   244  			if err != nil {
   245  				return err
   246  			}
   247  		default:
   248  			err := dropIndex(m.db, indexer.Key(), indexer.Name())
   249  			if err != nil {
   250  				return err
   251  			}
   252  		}
   253  	}
   254  
   255  	return nil
   256  }
   257  
   258  // maybeCreateIndexes determines if each of the enabled indexes have already
   259  // been created and creates them if not.
   260  func (m *Manager) maybeCreateIndexes() error {
   261  	return m.db.Update(func(dbTx database.Tx) error {
   262  		// Create the bucket for the current tips as needed.
   263  		meta := dbTx.Metadata()
   264  		indexesBucket, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
   265  		if err != nil {
   266  			return err
   267  		}
   268  
   269  		for _, indexer := range m.enabledIndexes {
   270  			// Nothing to do if the index tip already exists.
   271  			idxKey := indexer.Key()
   272  			if indexesBucket.Get(idxKey) != nil {
   273  				continue
   274  			}
   275  
   276  			// Store the index version.
   277  			err := dbPutIndexerVersion(dbTx, idxKey, indexer.Version())
   278  			if err != nil {
   279  				return err
   280  			}
   281  
   282  			// The tip for the index does not exist, so create it and
   283  			// invoke the create callback for the index so it can perform
   284  			// any one-time initialization it requires.
   285  			if err := indexer.Create(dbTx); err != nil {
   286  				return err
   287  			}
   288  
   289  			// Set the tip for the index to values which represent an
   290  			// uninitialized index (the genesis block hash and height).
   291  			genesisBlockHash := m.params.GenesisBlock.BlockHash()
   292  			err = dbPutIndexerTip(dbTx, idxKey, &genesisBlockHash, 0)
   293  			if err != nil {
   294  				return err
   295  			}
   296  		}
   297  
   298  		return nil
   299  	})
   300  }
   301  
   302  // upgradeIndexes determines if each of the enabled indexes need to be upgraded
   303  // and drops them when they do.
   304  func (m *Manager) upgradeIndexes(interrupt <-chan struct{}) error {
   305  	indexNeedsDrop := make([]bool, len(m.enabledIndexes))
   306  	err := m.db.View(func(dbTx database.Tx) error {
   307  		// None of the indexes needs to be updated if the index tips bucket
   308  		// hasn't been created yet.
   309  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   310  		if indexesBucket == nil {
   311  			return nil
   312  		}
   313  
   314  		for i, indexer := range m.enabledIndexes {
   315  			idxKey := indexer.Key()
   316  			version, err := dbFetchIndexerVersion(dbTx, idxKey)
   317  			if err != nil {
   318  				return err
   319  			}
   320  
   321  			// Upgrade is not needed if the index hasn't been created yet.
   322  			if version < indexer.Version() {
   323  				indexNeedsDrop[i] = true
   324  			}
   325  		}
   326  
   327  		return nil
   328  	})
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	if interruptRequested(interrupt) {
   334  		return errInterruptRequested
   335  	}
   336  
   337  	// Drop any of the enabled indexes that have bumped their version.
   338  	for i, indexer := range m.enabledIndexes {
   339  		if !indexNeedsDrop[i] {
   340  			continue
   341  		}
   342  
   343  		log.Infof("Dropping %s due to new version", indexer.Name())
   344  
   345  		switch d := indexer.(type) {
   346  		case IndexDropper:
   347  			err := d.DropIndex(m.db, interrupt)
   348  			if err != nil {
   349  				return err
   350  			}
   351  		default:
   352  			err := dropIndex(m.db, indexer.Key(), indexer.Name())
   353  			if err != nil {
   354  				return err
   355  			}
   356  		}
   357  	}
   358  
   359  	// Create the initial state for the indexes that were dropped as needed.
   360  	return m.maybeCreateIndexes()
   361  }
   362  
   363  // dbFetchBlockByHash uses an existing database transaction to retrieve the raw
   364  // block for the provided hash, deserialize it, and return a dcrutil.Block.
   365  func dbFetchBlockByHash(dbTx database.Tx, hash *chainhash.Hash) (*dcrutil.Block, error) {
   366  	blockBytes, err := dbTx.FetchBlock(hash)
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  	return dcrutil.NewBlockFromBytes(blockBytes)
   371  }
   372  
   373  // Init initializes the enabled indexes.  This is called during chain
   374  // initialization and primarily consists of catching up all indexes to the
   375  // current best chain tip.  This is necessary since each index can be disabled
   376  // and re-enabled at any time and attempting to catch-up indexes at the same
   377  // time new blocks are being downloaded would lead to an overall longer time to
   378  // catch up due to the I/O contention.
   379  //
   380  // This is part of the blockchain.IndexManager interface.
   381  func (m *Manager) Init(chain *blockchain.BlockChain, interrupt <-chan struct{}) error {
   382  	// Nothing to do when no indexes are enabled.
   383  	if len(m.enabledIndexes) == 0 {
   384  		return nil
   385  	}
   386  
   387  	if interruptRequested(interrupt) {
   388  		return errInterruptRequested
   389  	}
   390  
   391  	// Finish any drops that were previously interrupted.
   392  	if err := m.maybeFinishDrops(interrupt); err != nil {
   393  		return err
   394  	}
   395  
   396  	// Create the initial state for the indexes as needed.
   397  	if err := m.maybeCreateIndexes(); err != nil {
   398  		return err
   399  	}
   400  
   401  	// Upgrade the indexes as needed.
   402  	if err := m.upgradeIndexes(interrupt); err != nil {
   403  		return err
   404  	}
   405  
   406  	// Initialize each of the enabled indexes.
   407  	for _, indexer := range m.enabledIndexes {
   408  		if err := indexer.Init(); err != nil {
   409  			return err
   410  		}
   411  	}
   412  
   413  	// Rollback indexes to the main chain if their tip is an orphaned fork.
   414  	// This is fairly unlikely, but it can happen if the chain is
   415  	// reorganized while the index is disabled.  This has to be done in
   416  	// reverse order because later indexes can depend on earlier ones.
   417  	var err error
   418  	var cachedBlock *dcrutil.Block
   419  	for i := len(m.enabledIndexes); i > 0; i-- {
   420  		indexer := m.enabledIndexes[i-1]
   421  
   422  		// Fetch the current tip for the index.
   423  		var height int32
   424  		var hash *chainhash.Hash
   425  		err := m.db.View(func(dbTx database.Tx) error {
   426  			idxKey := indexer.Key()
   427  			hash, height, err = dbFetchIndexerTip(dbTx, idxKey)
   428  			return err
   429  		})
   430  		if err != nil {
   431  			return err
   432  		}
   433  
   434  		// Nothing to do if the index does not have any entries yet.
   435  		if height == 0 {
   436  			continue
   437  		}
   438  
   439  		// Loop until the tip is a block that exists in the main chain.
   440  		var interrupted bool
   441  		initialHeight := height
   442  		err = m.db.Update(func(dbTx database.Tx) error {
   443  			for !chain.MainChainHasBlock(hash) {
   444  				// Get the block, unless it's already cached.
   445  				var block *dcrutil.Block
   446  				if cachedBlock == nil && height > 0 {
   447  					block, err = dbFetchBlockByHash(dbTx, hash)
   448  					if err != nil {
   449  						return err
   450  					}
   451  				} else {
   452  					block = cachedBlock
   453  				}
   454  
   455  				// Load the parent block for the height since it is
   456  				// required to remove it.
   457  				parentHash := &block.MsgBlock().Header.PrevBlock
   458  				parent, err := dbFetchBlockByHash(dbTx, parentHash)
   459  				if err != nil {
   460  					return err
   461  				}
   462  				cachedBlock = parent
   463  
   464  				// When the index requires all of the referenced
   465  				// txouts they need to be retrieved from the
   466  				// transaction index.
   467  				var view *blockchain.UtxoViewpoint
   468  				if indexNeedsInputs(indexer) {
   469  					var err error
   470  					view, err = makeUtxoView(dbTx, block,
   471  						interrupt)
   472  					if err != nil {
   473  						return err
   474  					}
   475  				}
   476  
   477  				// Remove all of the index entries associated
   478  				// with the block and update the indexer tip.
   479  				err = dbIndexDisconnectBlock(dbTx, indexer,
   480  					block, parent, view)
   481  				if err != nil {
   482  					return err
   483  				}
   484  
   485  				// Update the tip to the previous block.
   486  				hash = &block.MsgBlock().Header.PrevBlock
   487  				height--
   488  
   489  				// NOTE: This does not return as it does
   490  				// elsewhere since it would cause the
   491  				// database transaction to rollback and
   492  				// undo all work that has been done.
   493  				if interruptRequested(interrupt) {
   494  					interrupted = true
   495  					break
   496  				}
   497  			}
   498  
   499  			return nil
   500  		})
   501  		if err != nil {
   502  			return err
   503  		}
   504  		if interrupted {
   505  			return errInterruptRequested
   506  		}
   507  
   508  		if initialHeight != height {
   509  			log.Infof("Removed %d orphaned blocks from %s "+
   510  				"(heights %d to %d)", initialHeight-height,
   511  				indexer.Name(), height+1, initialHeight)
   512  		}
   513  	}
   514  
   515  	// Fetch the current tip heights for each index along with tracking the
   516  	// lowest one so the catchup code only needs to start at the earliest
   517  	// block and is able to skip connecting the block for the indexes that
   518  	// don't need it.
   519  	bestHeight := int32(chain.BestSnapshot().Height)
   520  	lowestHeight := bestHeight
   521  	indexerHeights := make([]int32, len(m.enabledIndexes))
   522  	err = m.db.View(func(dbTx database.Tx) error {
   523  		for i, indexer := range m.enabledIndexes {
   524  			idxKey := indexer.Key()
   525  			hash, height, err := dbFetchIndexerTip(dbTx, idxKey)
   526  			if err != nil {
   527  				return err
   528  			}
   529  
   530  			log.Debugf("Current %s tip (height %d, hash %v)",
   531  				indexer.Name(), height, hash)
   532  			indexerHeights[i] = height
   533  			if height < lowestHeight {
   534  				lowestHeight = height
   535  			}
   536  		}
   537  		return nil
   538  	})
   539  	if err != nil {
   540  		return err
   541  	}
   542  
   543  	// Nothing to index if all of the indexes are caught up.
   544  	if lowestHeight == bestHeight {
   545  		return nil
   546  	}
   547  
   548  	// Create a progress logger for the indexing process below.
   549  	progressLogger := progresslog.NewBlockProgressLogger("Indexed", log)
   550  
   551  	// At this point, one or more indexes are behind the current best chain
   552  	// tip and need to be caught up, so log the details and loop through
   553  	// each block that needs to be indexed.
   554  	log.Infof("Catching up indexes from height %d to %d", lowestHeight,
   555  		bestHeight)
   556  
   557  	var cachedParent *dcrutil.Block
   558  	for height := lowestHeight + 1; height <= bestHeight; height++ {
   559  		if interruptRequested(interrupt) {
   560  			return errInterruptRequested
   561  		}
   562  
   563  		var block, parent *dcrutil.Block
   564  		err = m.db.Update(func(dbTx database.Tx) error {
   565  			// Get the parent of the block, unless it's already cached.
   566  			if cachedParent == nil && height > 0 {
   567  				parentHash, err := chain.BlockHashByHeight(int64(height - 1))
   568  				if err != nil {
   569  					return err
   570  				}
   571  				parent, err = dbFetchBlockByHash(dbTx, parentHash)
   572  				if err != nil {
   573  					return err
   574  				}
   575  			} else {
   576  				parent = cachedParent
   577  			}
   578  
   579  			// Load the block for the height since it is required to index
   580  			// it.
   581  			hash, err := chain.BlockHashByHeight(int64(height))
   582  			if err != nil {
   583  				return err
   584  			}
   585  			block, err = dbFetchBlockByHash(dbTx, hash)
   586  			if err != nil {
   587  				return err
   588  			}
   589  			cachedParent = block
   590  
   591  			if interruptRequested(interrupt) {
   592  				return errInterruptRequested
   593  			}
   594  
   595  			// Connect the block for all indexes that need it.
   596  			var view *blockchain.UtxoViewpoint
   597  			for i, indexer := range m.enabledIndexes {
   598  				// Skip indexes that don't need to be updated with this
   599  				// block.
   600  				if indexerHeights[i] >= height {
   601  					continue
   602  				}
   603  
   604  				// When the index requires all of the referenced
   605  				// txouts and they haven't been loaded yet, they
   606  				// need to be retrieved from the transaction
   607  				// index.
   608  				if view == nil && indexNeedsInputs(indexer) {
   609  					var errMakeView error
   610  					view, errMakeView = makeUtxoView(dbTx, block, interrupt)
   611  					if errMakeView != nil {
   612  						return errMakeView
   613  					}
   614  				}
   615  				err = dbIndexConnectBlock(dbTx, indexer, block,
   616  					parent, view)
   617  				if err != nil {
   618  					return err
   619  				}
   620  
   621  				indexerHeights[i] = height
   622  			}
   623  
   624  			return nil
   625  		})
   626  		if err != nil {
   627  			return err
   628  		}
   629  		progressLogger.LogBlockHeight(block.MsgBlock(), parent.MsgBlock())
   630  	}
   631  
   632  	log.Infof("Indexes caught up to height %d", bestHeight)
   633  	return nil
   634  }
   635  
   636  // indexNeedsInputs returns whether or not the index needs access to the txouts
   637  // referenced by the transaction inputs being indexed.
   638  func indexNeedsInputs(index Indexer) bool {
   639  	if idx, ok := index.(NeedsInputser); ok {
   640  		return idx.NeedsInputs()
   641  	}
   642  
   643  	return false
   644  }
   645  
   646  // dbFetchTx looks up the passed transaction hash in the transaction index and
   647  // loads it from the database.
   648  func dbFetchTx(dbTx database.Tx, hash *chainhash.Hash) (*wire.MsgTx, error) {
   649  	// Look up the location of the transaction.
   650  	entry, err := dbFetchTxIndexEntry(dbTx, hash)
   651  	if err != nil {
   652  		return nil, err
   653  	}
   654  	if entry == nil {
   655  		return nil, fmt.Errorf("transaction %v not found in the txindex", hash)
   656  	}
   657  
   658  	// Load the raw transaction bytes from the database.
   659  	txBytes, err := dbTx.FetchBlockRegion(&entry.BlockRegion)
   660  	if err != nil {
   661  		return nil, err
   662  	}
   663  
   664  	// Deserialize the transaction.
   665  	var msgTx wire.MsgTx
   666  	err = msgTx.Deserialize(bytes.NewReader(txBytes))
   667  	if err != nil {
   668  		return nil, err
   669  	}
   670  
   671  	return &msgTx, nil
   672  }
   673  
   674  // makeUtxoView creates a mock unspent transaction output view by using the
   675  // transaction index in order to look up all inputs referenced by the
   676  // transactions in the block.  This is sometimes needed when catching indexes up
   677  // because many of the txouts could actually already be spent however the
   678  // associated scripts are still required to index them.
   679  func makeUtxoView(dbTx database.Tx, block *dcrutil.Block, interrupt <-chan struct{}) (*blockchain.UtxoViewpoint, error) {
   680  	view := blockchain.NewUtxoViewpoint()
   681  	processTxns := func(txns []*dcrutil.Tx, regularTree bool) error {
   682  		for txIdx, tx := range txns {
   683  			// Coinbases do not reference any inputs.  Since the block is
   684  			// required to have already gone through full validation, it has
   685  			// already been proven on the first transaction in the block is a
   686  			// coinbase.
   687  			if regularTree && txIdx == 0 {
   688  				continue
   689  			}
   690  			msgTx := tx.MsgTx()
   691  			isVote := !regularTree && stake.IsSSGen(msgTx)
   692  
   693  			// Use the transaction index to load all of the referenced inputs
   694  			// and add their outputs to the view.
   695  			for txInIdx, txIn := range msgTx.TxIn {
   696  				// Ignore stakebase since it has no input.
   697  				if isVote && txInIdx == 0 {
   698  					continue
   699  				}
   700  
   701  				// Skip already fetched outputs.
   702  				originOut := &txIn.PreviousOutPoint
   703  				if view.LookupEntry(&originOut.Hash) != nil {
   704  					continue
   705  				}
   706  
   707  				originTx, err := dbFetchTx(dbTx, &originOut.Hash)
   708  				if err != nil {
   709  					return err
   710  				}
   711  
   712  				view.AddTxOuts(dcrutil.NewTx(originTx), int64(txIn.BlockHeight),
   713  					txIn.BlockIndex)
   714  			}
   715  
   716  			if interruptRequested(interrupt) {
   717  				return errInterruptRequested
   718  			}
   719  		}
   720  
   721  		return nil
   722  	}
   723  
   724  	if err := processTxns(block.STransactions(), false); err != nil {
   725  		return nil, err
   726  	}
   727  	if err := processTxns(block.Transactions(), true); err != nil {
   728  		return nil, err
   729  	}
   730  
   731  	return view, nil
   732  }
   733  
   734  // ConnectBlock must be invoked when a block is extending the main chain.  It
   735  // keeps track of the state of each index it is managing, performs some sanity
   736  // checks, and invokes each indexer.
   737  //
   738  // This is part of the blockchain.IndexManager interface.
   739  func (m *Manager) ConnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error {
   740  	// Call each of the currently active optional indexes with the block
   741  	// being connected so they can update accordingly.
   742  	for _, index := range m.enabledIndexes {
   743  		err := dbIndexConnectBlock(dbTx, index, block, parent, view)
   744  		if err != nil {
   745  			return err
   746  		}
   747  	}
   748  	return nil
   749  }
   750  
   751  // DisconnectBlock must be invoked when a block is being disconnected from the
   752  // end of the main chain.  It keeps track of the state of each index it is
   753  // managing, performs some sanity checks, and invokes each indexer to remove
   754  // the index entries associated with the block.
   755  //
   756  // This is part of the blockchain.IndexManager interface.
   757  func (m *Manager) DisconnectBlock(dbTx database.Tx, block, parent *dcrutil.Block, view *blockchain.UtxoViewpoint) error {
   758  	// Call each of the currently active optional indexes with the block
   759  	// being disconnected so they can update accordingly.
   760  	for _, index := range m.enabledIndexes {
   761  		err := dbIndexDisconnectBlock(dbTx, index, block, parent, view)
   762  		if err != nil {
   763  			return err
   764  		}
   765  	}
   766  	return nil
   767  }
   768  
   769  // NewManager returns a new index manager with the provided indexes enabled.
   770  //
   771  // The manager returned satisfies the blockchain.IndexManager interface and thus
   772  // cleanly plugs into the normal blockchain processing path.
   773  func NewManager(db database.DB, enabledIndexes []Indexer, params *chaincfg.Params) *Manager {
   774  	return &Manager{
   775  		db:             db,
   776  		enabledIndexes: enabledIndexes,
   777  		params:         params,
   778  	}
   779  }
   780  
   781  // existsIndex returns whether the index keyed by idxKey exists in the database.
   782  func existsIndex(db database.DB, idxKey []byte, idxName string) (bool, error) {
   783  	var exists bool
   784  	err := db.View(func(dbTx database.Tx) error {
   785  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   786  		if indexesBucket != nil && indexesBucket.Get(idxKey) != nil {
   787  			exists = true
   788  		}
   789  		return nil
   790  	})
   791  	return exists, err
   792  }
   793  
   794  // markIndexDeletion marks the index identified by idxKey for deletion.  Marking
   795  // an index for deletion allows deletion to resume next startup if an
   796  // incremental deletion was interrupted.
   797  func markIndexDeletion(db database.DB, idxKey []byte) error {
   798  	return db.Update(func(dbTx database.Tx) error {
   799  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   800  		return indexesBucket.Put(indexDropKey(idxKey), idxKey)
   801  	})
   802  }
   803  
   804  // incrementalFlatDrop uses multiple database updates to remove key/value pairs
   805  // saved to a flat index.
   806  func incrementalFlatDrop(db database.DB, idxKey []byte, idxName string, interrupt <-chan struct{}) error {
   807  	const maxDeletions = 2000000
   808  	var totalDeleted uint64
   809  	for numDeleted := maxDeletions; numDeleted == maxDeletions; {
   810  		numDeleted = 0
   811  		err := db.Update(func(dbTx database.Tx) error {
   812  			bucket := dbTx.Metadata().Bucket(idxKey)
   813  			cursor := bucket.Cursor()
   814  			for ok := cursor.First(); ok; ok = cursor.Next() &&
   815  				numDeleted < maxDeletions {
   816  
   817  				if err := cursor.Delete(); err != nil {
   818  					return err
   819  				}
   820  				numDeleted++
   821  			}
   822  			return nil
   823  		})
   824  		if err != nil {
   825  			return err
   826  		}
   827  
   828  		if numDeleted > 0 {
   829  			totalDeleted += uint64(numDeleted)
   830  			log.Infof("Deleted %d keys (%d total) from %s",
   831  				numDeleted, totalDeleted, idxName)
   832  		}
   833  
   834  		if interruptRequested(interrupt) {
   835  			return errInterruptRequested
   836  		}
   837  	}
   838  	return nil
   839  }
   840  
   841  // dropIndexMetadata drops the passed index from the database by removing the
   842  // top level bucket for the index, the index tip, and any in-progress drop flag.
   843  func dropIndexMetadata(db database.DB, idxKey []byte, idxName string) error {
   844  	return db.Update(func(dbTx database.Tx) error {
   845  		meta := dbTx.Metadata()
   846  		indexesBucket := meta.Bucket(indexTipsBucketName)
   847  		err := indexesBucket.Delete(idxKey)
   848  		if err != nil {
   849  			return err
   850  		}
   851  
   852  		err = meta.DeleteBucket(idxKey)
   853  		if err != nil && !database.IsError(err, database.ErrBucketNotFound) {
   854  			return err
   855  		}
   856  
   857  		err = indexesBucket.Delete(indexVersionKey(idxKey))
   858  		if err != nil {
   859  			return err
   860  		}
   861  
   862  		return indexesBucket.Delete(indexDropKey(idxKey))
   863  	})
   864  }
   865  
   866  // dropFlatIndex incrementally drops the passed index from the database.  Since
   867  // indexes can be massive, it deletes the index in multiple database
   868  // transactions in order to keep memory usage to reasonable levels.  For this
   869  // algorithm to work, the index must be "flat" (have no nested buckets).  It
   870  // also marks the drop in progress so the drop can be resumed if it is stopped
   871  // before it is done before the index can be used again.
   872  func dropFlatIndex(db database.DB, idxKey []byte, idxName string, interrupt <-chan struct{}) error {
   873  	// Nothing to do if the index doesn't already exist.
   874  	exists, err := existsIndex(db, idxKey, idxName)
   875  	if err != nil {
   876  		return err
   877  	}
   878  	if !exists {
   879  		log.Infof("Not dropping %s because it does not exist", idxName)
   880  		return nil
   881  	}
   882  
   883  	log.Infof("Dropping all %s entries.  This might take a while...",
   884  		idxName)
   885  
   886  	// Mark that the index is in the process of being dropped so that it
   887  	// can be resumed on the next start if interrupted before the process is
   888  	// complete.
   889  	err = markIndexDeletion(db, idxKey)
   890  	if err != nil {
   891  		return err
   892  	}
   893  
   894  	// Since the indexes can be so large, attempting to simply delete
   895  	// the bucket in a single database transaction would result in massive
   896  	// memory usage and likely crash many systems due to ulimits.  In order
   897  	// to avoid this, use a cursor to delete a maximum number of entries out
   898  	// of the bucket at a time.
   899  	err = incrementalFlatDrop(db, idxKey, idxName, interrupt)
   900  	if err != nil {
   901  		return err
   902  	}
   903  
   904  	// Remove the index tip, version, bucket, and in-progress drop flag now that
   905  	// all index entries have been removed.
   906  	err = dropIndexMetadata(db, idxKey, idxName)
   907  	if err != nil {
   908  		return err
   909  	}
   910  
   911  	log.Infof("Dropped %s", idxName)
   912  	return nil
   913  }
   914  
   915  // dropIndex drops the passed index from the database without using incremental
   916  // deletion.  This should be used to drop indexes containing nested buckets,
   917  // which can not be deleted with dropFlatIndex.
   918  func dropIndex(db database.DB, idxKey []byte, idxName string) error {
   919  	// Nothing to do if the index doesn't already exist.
   920  	exists, err := existsIndex(db, idxKey, idxName)
   921  	if err != nil {
   922  		return err
   923  	}
   924  	if !exists {
   925  		log.Infof("Not dropping %s because it does not exist", idxName)
   926  		return nil
   927  	}
   928  
   929  	log.Infof("Dropping all %s entries.  This might take a while...",
   930  		idxName)
   931  
   932  	// Mark that the index is in the process of being dropped so that it
   933  	// can be resumed on the next start if interrupted before the process is
   934  	// complete.
   935  	err = markIndexDeletion(db, idxKey)
   936  	if err != nil {
   937  		return err
   938  	}
   939  
   940  	// Remove the index tip, version, bucket, and in-progress drop flag.
   941  	// Removing the index bucket also recursively removes all values saved to
   942  	// the index.
   943  	err = dropIndexMetadata(db, idxKey, idxName)
   944  	if err != nil {
   945  		return err
   946  	}
   947  
   948  	log.Infof("Dropped %s", idxName)
   949  	return nil
   950  }