github.com/decred/dcrlnd@v0.7.6/chainntnfs/interface.go (about)

     1  package chainntnfs
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/decred/dcrd/chaincfg/chainhash"
    12  	"github.com/decred/dcrd/dcrjson/v4"
    13  	jsontypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4"
    14  	"github.com/decred/dcrd/wire"
    15  )
    16  
    17  var (
    18  	// ErrChainNotifierShuttingDown is used when we are trying to
    19  	// measure a spend notification when notifier is already stopped.
    20  	ErrChainNotifierShuttingDown = errors.New("chain notifier shutting down")
    21  )
    22  
    23  // TxConfStatus denotes the status of a transaction's lookup.
    24  type TxConfStatus uint8
    25  
    26  const (
    27  	// TxFoundMempool denotes that the transaction was found within the
    28  	// backend node's mempool.
    29  	TxFoundMempool TxConfStatus = iota
    30  
    31  	// TxFoundIndex denotes that the transaction was found within the
    32  	// backend node's txindex.
    33  	TxFoundIndex
    34  
    35  	// TxNotFoundIndex denotes that the transaction was not found within the
    36  	// backend node's txindex.
    37  	TxNotFoundIndex
    38  
    39  	// TxFoundManually denotes that the transaction was found within the
    40  	// chain by scanning for it manually.
    41  	TxFoundManually
    42  
    43  	// TxNotFoundManually denotes that the transaction was not found within
    44  	// the chain by scanning for it manually.
    45  	TxNotFoundManually
    46  )
    47  
    48  // String returns the string representation of the TxConfStatus.
    49  func (t TxConfStatus) String() string {
    50  	switch t {
    51  	case TxFoundMempool:
    52  		return "TxFoundMempool"
    53  
    54  	case TxFoundIndex:
    55  		return "TxFoundIndex"
    56  
    57  	case TxNotFoundIndex:
    58  		return "TxNotFoundIndex"
    59  
    60  	case TxFoundManually:
    61  		return "TxFoundManually"
    62  
    63  	case TxNotFoundManually:
    64  		return "TxNotFoundManually"
    65  
    66  	default:
    67  		return "unknown"
    68  	}
    69  }
    70  
    71  // ChainNotifier represents a trusted source to receive notifications concerning
    72  // targeted events on the Decred blockchain. The interface specification is
    73  // intentionally general in order to support a wide array of chain notification
    74  // implementations such as: dcrd's websockets notifications, various Decred API
    75  // services, Electrum servers, etc.
    76  //
    77  // Concrete implementations of ChainNotifier should be able to support multiple
    78  // concurrent client requests, as well as multiple concurrent notification
    79  // events.
    80  type ChainNotifier interface {
    81  	// RegisterConfirmationsNtfn registers an intent to be notified once
    82  	// txid reaches numConfs confirmations. We also pass in the pkScript as
    83  	// the default light client instead needs to match on scripts created in
    84  	// the block. If a nil txid is passed in, then not only should we match
    85  	// on the script, but we should also dispatch once the transaction
    86  	// containing the script reaches numConfs confirmations. This can be
    87  	// useful in instances where we only know the script in advance, but not
    88  	// the transaction containing it.
    89  	//
    90  	// The returned ConfirmationEvent should properly notify the client once
    91  	// the specified number of confirmations has been reached for the txid,
    92  	// as well as if the original tx gets re-org'd out of the mainchain. The
    93  	// heightHint parameter is provided as a convenience to light clients.
    94  	// It heightHint denotes the earliest height in the blockchain in which
    95  	// the target txid _could_ have been included in the chain. This can be
    96  	// used to bound the search space when checking to see if a notification
    97  	// can immediately be dispatched due to historical data.
    98  	//
    99  	// NOTE: Dispatching notifications to multiple clients subscribed to
   100  	// the same (txid, numConfs) tuple MUST be supported.
   101  	RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte,
   102  		numConfs, heightHint uint32) (*ConfirmationEvent, error)
   103  
   104  	// RegisterSpendNtfn registers an intent to be notified once the target
   105  	// outpoint is successfully spent within a transaction. The script that
   106  	// the outpoint creates must also be specified. This allows this
   107  	// interface to be implemented by BIP 158-like filtering. If a nil
   108  	// outpoint is passed in, then not only should we match on the script,
   109  	// but we should also dispatch once a transaction spends the output
   110  	// containing said script. This can be useful in instances where we only
   111  	// know the script in advance, but not the outpoint itself.
   112  	//
   113  	// The returned SpendEvent will receive a send on the 'Spend'
   114  	// transaction once a transaction spending the input is detected on the
   115  	// blockchain. The heightHint parameter is provided as a convenience to
   116  	// light clients. It denotes the earliest height in the blockchain in
   117  	// which the target output could have been spent.
   118  	//
   119  	// NOTE: The notification should only be triggered when the spending
   120  	// transaction receives a single confirmation.
   121  	//
   122  	// NOTE: Dispatching notifications to multiple clients subscribed to a
   123  	// spend of the same outpoint MUST be supported.
   124  	RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte,
   125  		heightHint uint32) (*SpendEvent, error)
   126  
   127  	// RegisterBlockEpochNtfn registers an intent to be notified of each
   128  	// new block connected to the tip of the main chain. The returned
   129  	// BlockEpochEvent struct contains a channel which will be sent upon
   130  	// for each new block discovered.
   131  	//
   132  	// Clients have the option of passing in their best known block.
   133  	// If they specify a block, the ChainNotifier checks whether the client
   134  	// is behind on blocks. If they are, the ChainNotifier sends a backlog
   135  	// of block notifications for the missed blocks. If they do not provide
   136  	// one, then a notification will be dispatched immediately for the
   137  	// current tip of the chain upon a successful registration.
   138  	RegisterBlockEpochNtfn(*BlockEpoch) (*BlockEpochEvent, error)
   139  
   140  	// Start the ChainNotifier. Once started, the implementation should be
   141  	// ready, and able to receive notification registrations from clients.
   142  	Start() error
   143  
   144  	// Started returns true if this instance has been started, and false otherwise.
   145  	Started() bool
   146  
   147  	// Stops the concrete ChainNotifier. Once stopped, the ChainNotifier
   148  	// should disallow any future requests from potential clients.
   149  	// Additionally, all pending client notifications will be canceled
   150  	// by closing the related channels on the *Event's.
   151  	Stop() error
   152  }
   153  
   154  // TxConfirmation carries some additional block-level details of the exact
   155  // block that specified transactions was confirmed within.
   156  type TxConfirmation struct {
   157  	// BlockHash is the hash of the block that confirmed the original
   158  	// transition.
   159  	BlockHash *chainhash.Hash
   160  
   161  	// BlockHeight is the height of the block in which the transaction was
   162  	// confirmed within.
   163  	BlockHeight uint32
   164  
   165  	// TxIndex is the index within the block of the ultimate confirmed
   166  	// transaction.
   167  	TxIndex uint32
   168  
   169  	// Tx is the transaction for which the notification was requested for.
   170  	Tx *wire.MsgTx
   171  }
   172  
   173  // ConfirmationEvent encapsulates a confirmation notification. With this struct,
   174  // callers can be notified of: the instance the target txid reaches the targeted
   175  // number of confirmations, how many confirmations are left for the target txid
   176  // to be fully confirmed at every new block height, and also in the event that
   177  // the original txid becomes disconnected from the blockchain as a result of a
   178  // re-org.
   179  //
   180  // Once the txid reaches the specified number of confirmations, the 'Confirmed'
   181  // channel will be sent upon fulfilling the notification.
   182  //
   183  // If the event that the original transaction becomes re-org'd out of the main
   184  // chain, the 'NegativeConf' will be sent upon with a value representing the
   185  // depth of the re-org.
   186  //
   187  // NOTE: If the caller wishes to cancel their registered spend notification,
   188  // the Cancel closure MUST be called.
   189  type ConfirmationEvent struct {
   190  	// Confirmed is a channel that will be sent upon once the transaction
   191  	// has been fully confirmed. The struct sent will contain all the
   192  	// details of the channel's confirmation.
   193  	//
   194  	// NOTE: This channel must be buffered.
   195  	Confirmed chan *TxConfirmation
   196  
   197  	// Updates is a channel that will sent upon, at every incremental
   198  	// confirmation, how many confirmations are left to declare the
   199  	// transaction as fully confirmed.
   200  	//
   201  	// NOTE: This channel must be buffered with the number of required
   202  	// confirmations.
   203  	Updates chan uint32
   204  
   205  	// NegativeConf is a channel that will be sent upon if the transaction
   206  	// confirms, but is later reorged out of the chain. The integer sent
   207  	// through the channel represents the reorg depth.
   208  	//
   209  	// NOTE: This channel must be buffered.
   210  	NegativeConf chan int32
   211  
   212  	// Done is a channel that gets sent upon once the confirmation request
   213  	// is no longer under the risk of being reorged out of the chain.
   214  	//
   215  	// NOTE: This channel must be buffered.
   216  	Done chan struct{}
   217  
   218  	// Cancel is a closure that should be executed by the caller in the case
   219  	// that they wish to prematurely abandon their registered confirmation
   220  	// notification.
   221  	Cancel func()
   222  }
   223  
   224  // NewConfirmationEvent constructs a new ConfirmationEvent with newly opened
   225  // channels.
   226  func NewConfirmationEvent(numConfs uint32, cancel func()) *ConfirmationEvent {
   227  	return &ConfirmationEvent{
   228  		Confirmed:    make(chan *TxConfirmation, 1),
   229  		Updates:      make(chan uint32, numConfs),
   230  		NegativeConf: make(chan int32, 1),
   231  		Done:         make(chan struct{}, 1),
   232  		Cancel:       cancel,
   233  	}
   234  }
   235  
   236  // SpendDetail contains details pertaining to a spent output. This struct itself
   237  // is the spentness notification. It includes the original outpoint which
   238  // triggered the notification, the hash of the transaction spending the output,
   239  // the spending transaction itself, the height of the spending transaction, and
   240  // finally the input index which spent the target output.
   241  type SpendDetail struct {
   242  	SpentOutPoint     *wire.OutPoint
   243  	SpenderTxHash     *chainhash.Hash
   244  	SpendingTx        *wire.MsgTx
   245  	SpenderInputIndex uint32
   246  	SpendingHeight    int32
   247  }
   248  
   249  // String returns a string representation of SpendDetail.
   250  func (s *SpendDetail) String() string {
   251  	return fmt.Sprintf("%v[%d] spending %v at height=%v", s.SpenderTxHash,
   252  		s.SpenderInputIndex, s.SpentOutPoint, s.SpendingHeight)
   253  }
   254  
   255  // SpendEvent encapsulates a spentness notification. Its only field 'Spend' will
   256  // be sent upon once the target output passed into RegisterSpendNtfn has been
   257  // spent on the blockchain.
   258  //
   259  // NOTE: If the caller wishes to cancel their registered spend notification,
   260  // the Cancel closure MUST be called.
   261  type SpendEvent struct {
   262  	// Spend is a receive only channel which will be sent upon once the
   263  	// target outpoint has been spent.
   264  	//
   265  	// NOTE: This channel must be buffered.
   266  	Spend chan *SpendDetail
   267  
   268  	// Reorg is a channel that will be sent upon once we detect the spending
   269  	// transaction of the outpoint in question has been reorged out of the
   270  	// chain.
   271  	//
   272  	// NOTE: This channel must be buffered.
   273  	Reorg chan struct{}
   274  
   275  	// Done is a channel that gets sent upon once the confirmation request
   276  	// is no longer under the risk of being reorged out of the chain.
   277  	//
   278  	// NOTE: This channel must be buffered.
   279  	Done chan struct{}
   280  
   281  	// Cancel is a closure that should be executed by the caller in the case
   282  	// that they wish to prematurely abandon their registered spend
   283  	// notification.
   284  	Cancel func()
   285  }
   286  
   287  // NewSpendEvent constructs a new SpendEvent with newly opened channels.
   288  func NewSpendEvent(cancel func()) *SpendEvent {
   289  	return &SpendEvent{
   290  		Spend:  make(chan *SpendDetail, 1),
   291  		Reorg:  make(chan struct{}, 1),
   292  		Done:   make(chan struct{}, 1),
   293  		Cancel: cancel,
   294  	}
   295  }
   296  
   297  // BlockEpoch represents metadata concerning each new block connected to the
   298  // main chain.
   299  type BlockEpoch struct {
   300  	// Hash is the block hash of the latest block to be added to the tip of
   301  	// the main chain.
   302  	Hash *chainhash.Hash
   303  
   304  	// Height is the height of the latest block to be added to the tip of
   305  	// the main chain.
   306  	Height int32
   307  
   308  	// BlockHeader is the block header of this new height.
   309  	BlockHeader *wire.BlockHeader
   310  }
   311  
   312  // BlockEpochEvent encapsulates an on-going stream of block epoch
   313  // notifications. Its only field 'Epochs' will be sent upon for each new block
   314  // connected to the main-chain.
   315  //
   316  // NOTE: If the caller wishes to cancel their registered block epoch
   317  // notification, the Cancel closure MUST be called.
   318  type BlockEpochEvent struct {
   319  	// Epochs is a receive only channel that will be sent upon each time a
   320  	// new block is connected to the end of the main chain.
   321  	//
   322  	// NOTE: This channel must be buffered.
   323  	Epochs <-chan *BlockEpoch
   324  
   325  	// Cancel is a closure that should be executed by the caller in the case
   326  	// that they wish to abandon their registered block epochs notification.
   327  	Cancel func()
   328  }
   329  
   330  // NotifierDriver represents a "driver" for a particular interface. A driver is
   331  // identified by a globally unique string identifier along with a 'New()'
   332  // method which is responsible for initializing a particular ChainNotifier
   333  // concrete implementation.
   334  type NotifierDriver struct {
   335  	// NotifierType is a string which uniquely identifies the ChainNotifier
   336  	// that this driver, drives.
   337  	NotifierType string
   338  
   339  	// New creates a new instance of a concrete ChainNotifier
   340  	// implementation given a variadic set up arguments. The function takes
   341  	// a variadic number of interface parameters in order to provide
   342  	// initialization flexibility, thereby accommodating several potential
   343  	// ChainNotifier implementations.
   344  	New func(args ...interface{}) (ChainNotifier, error)
   345  }
   346  
   347  var (
   348  	notifiers   = make(map[string]*NotifierDriver)
   349  	registerMtx sync.Mutex
   350  )
   351  
   352  // RegisteredNotifiers returns a slice of all currently registered notifiers.
   353  //
   354  // NOTE: This function is safe for concurrent access.
   355  func RegisteredNotifiers() []*NotifierDriver {
   356  	registerMtx.Lock()
   357  	defer registerMtx.Unlock()
   358  
   359  	drivers := make([]*NotifierDriver, 0, len(notifiers))
   360  	for _, driver := range notifiers {
   361  		drivers = append(drivers, driver)
   362  	}
   363  
   364  	return drivers
   365  }
   366  
   367  // RegisterNotifier registers a NotifierDriver which is capable of driving a
   368  // concrete ChainNotifier interface. In the case that this driver has already
   369  // been registered, an error is returned.
   370  //
   371  // NOTE: This function is safe for concurrent access.
   372  func RegisterNotifier(driver *NotifierDriver) error {
   373  	registerMtx.Lock()
   374  	defer registerMtx.Unlock()
   375  
   376  	if _, ok := notifiers[driver.NotifierType]; ok {
   377  		return fmt.Errorf("notifier already registered")
   378  	}
   379  
   380  	notifiers[driver.NotifierType] = driver
   381  
   382  	return nil
   383  }
   384  
   385  // SupportedNotifiers returns a slice of strings that represent the database
   386  // drivers that have been registered and are therefore supported.
   387  //
   388  // NOTE: This function is safe for concurrent access.
   389  func SupportedNotifiers() []string {
   390  	registerMtx.Lock()
   391  	defer registerMtx.Unlock()
   392  
   393  	supportedNotifiers := make([]string, 0, len(notifiers))
   394  	for driverName := range notifiers {
   395  		supportedNotifiers = append(supportedNotifiers, driverName)
   396  	}
   397  
   398  	return supportedNotifiers
   399  }
   400  
   401  // NotifierByName returns the notifier with the given name, or nil if it is not
   402  // registered.
   403  func NotifierByName(name string) *NotifierDriver {
   404  	registerMtx.Lock()
   405  	defer registerMtx.Unlock()
   406  
   407  	for driverName, driver := range notifiers {
   408  		if driverName == name {
   409  			return driver
   410  		}
   411  	}
   412  
   413  	return nil
   414  }
   415  
   416  // ChainConn enables notifiers to pass in their chain backend to interface
   417  // functions that require it.
   418  type ChainConn interface {
   419  	// GetBlockHeader returns the block header for a hash.
   420  	GetBlockHeader(blockHash *chainhash.Hash) (*wire.BlockHeader, error)
   421  
   422  	// GetBlockHash returns the hash from a block height.
   423  	GetBlockHash(blockHeight int64) (*chainhash.Hash, error)
   424  }
   425  
   426  // GetCommonBlockAncestorHeight takes in:
   427  // (1) the hash of a block that has been reorged out of the main chain
   428  // (2) the hash of the block of the same height from the main chain
   429  // It returns the height of the nearest common ancestor between the two hashes,
   430  // or an error
   431  func GetCommonBlockAncestorHeight(chainConn ChainConn, reorgHash,
   432  	chainHash chainhash.Hash) (int32, error) {
   433  
   434  	for reorgHash != chainHash {
   435  		reorgHeader, err := chainConn.GetBlockHeader(&reorgHash)
   436  		if err != nil {
   437  			return 0, fmt.Errorf("unable to get header for hash=%v: %v",
   438  				reorgHash, err)
   439  		}
   440  		chainHeader, err := chainConn.GetBlockHeader(&chainHash)
   441  		if err != nil {
   442  			return 0, fmt.Errorf("unable to get header for hash=%v: %v",
   443  				chainHash, err)
   444  		}
   445  		reorgHash = reorgHeader.PrevBlock
   446  		chainHash = chainHeader.PrevBlock
   447  	}
   448  
   449  	header, err := chainConn.GetBlockHeader(&chainHash)
   450  	if err != nil {
   451  		return 0, fmt.Errorf("unable to get header for hash=%v: %v",
   452  			chainHash, err)
   453  	}
   454  
   455  	// TODO(decred): Deal with block height differences
   456  	return int32(header.Height), nil
   457  }
   458  
   459  // GetClientMissedBlocks uses a client's best block to determine what blocks
   460  // it missed being notified about, and returns them in a slice. The
   461  // backendStoresReorgs parameter tells it whether or not the notifier's
   462  // chainConn stores information about blocks that have been reorged out of the
   463  // chain, which allows GetClientMissedBlocks to find out whether the client's
   464  // best block has been reorged out of the chain, rewind to the common ancestor
   465  // and return blocks starting right after the common ancestor.
   466  func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
   467  	notifierBestHeight int32, backendStoresReorgs bool) ([]BlockEpoch, error) {
   468  
   469  	startingHeight := clientBestBlock.Height
   470  	if backendStoresReorgs {
   471  		// If a reorg causes the client's best hash to be incorrect,
   472  		// retrieve the closest common ancestor and dispatch
   473  		// notifications from there.
   474  		hashAtBestHeight, err := chainConn.GetBlockHash(
   475  			int64(clientBestBlock.Height))
   476  		if err != nil {
   477  			return nil, fmt.Errorf("unable to find blockhash for "+
   478  				"height=%d: %v", clientBestBlock.Height, err)
   479  		}
   480  
   481  		startingHeight, err = GetCommonBlockAncestorHeight(
   482  			chainConn, *clientBestBlock.Hash, *hashAtBestHeight,
   483  		)
   484  		if err != nil {
   485  			return nil, fmt.Errorf("unable to find common ancestor: "+
   486  				"%v", err)
   487  		}
   488  	}
   489  
   490  	// We want to start dispatching historical notifications from the block
   491  	// right after the client's best block, to avoid a redundant notification.
   492  	missedBlocks, err := getMissedBlocks(
   493  		chainConn, startingHeight+1, notifierBestHeight+1,
   494  	)
   495  	if err != nil {
   496  		return nil, fmt.Errorf("unable to get missed blocks: %v", err)
   497  	}
   498  
   499  	return missedBlocks, nil
   500  }
   501  
   502  // RewindChain handles internal state updates for the notifier's TxNotifier. It
   503  // has no effect if given a height greater than or equal to our current best
   504  // known height. It returns the new best block for the notifier.
   505  func RewindChain(chainConn ChainConn, txNotifier *TxNotifier,
   506  	currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
   507  
   508  	newBestBlock := BlockEpoch{
   509  		Height:      currBestBlock.Height,
   510  		Hash:        currBestBlock.Hash,
   511  		BlockHeader: currBestBlock.BlockHeader,
   512  	}
   513  
   514  	for height := currBestBlock.Height; height > targetHeight; height-- {
   515  		hash, err := chainConn.GetBlockHash(int64(height - 1))
   516  		if err != nil {
   517  			return newBestBlock, fmt.Errorf("unable to "+
   518  				"find blockhash for disconnected height=%d: %v",
   519  				height, err)
   520  		}
   521  		header, err := chainConn.GetBlockHeader(hash)
   522  		if err != nil {
   523  			return newBestBlock, fmt.Errorf("unable to get block "+
   524  				"header for height=%v", height-1)
   525  		}
   526  
   527  		Log.Infof("Block disconnected from main chain: "+
   528  			"height=%v, sha=%v", height, newBestBlock.Hash)
   529  
   530  		err = txNotifier.DisconnectTip(uint32(height))
   531  		if err != nil {
   532  			return newBestBlock, fmt.Errorf("unable to "+
   533  				" disconnect tip for height=%d: %v",
   534  				height, err)
   535  		}
   536  		newBestBlock.Height = height - 1
   537  		newBestBlock.Hash = hash
   538  		newBestBlock.BlockHeader = header
   539  	}
   540  
   541  	return newBestBlock, nil
   542  }
   543  
   544  // HandleMissedBlocks is called when the chain backend for a notifier misses a
   545  // series of blocks, handling a reorg if necessary. Its backendStoresReorgs
   546  // parameter tells it whether or not the notifier's chainConn stores
   547  // information about blocks that have been reorged out of the chain, which allows
   548  // HandleMissedBlocks to check whether the notifier's best block has been
   549  // reorged out, and rewind the chain accordingly. It returns the best block for
   550  // the notifier and a slice of the missed blocks. The new best block needs to be
   551  // returned in case a chain rewind occurs and partially completes before
   552  // erroring. In the case where there is no rewind, the notifier's
   553  // current best block is returned.
   554  func HandleMissedBlocks(chainConn ChainConn, txNotifier *TxNotifier,
   555  	currBestBlock BlockEpoch, newHeight int32,
   556  	backendStoresReorgs bool) (BlockEpoch, []BlockEpoch, error) {
   557  
   558  	startingHeight := currBestBlock.Height
   559  
   560  	if backendStoresReorgs {
   561  		// If a reorg causes our best hash to be incorrect, rewind the
   562  		// chain so our best block is set to the closest common
   563  		// ancestor, then dispatch notifications from there.
   564  		hashAtBestHeight, err := chainConn.GetBlockHash(
   565  			int64(currBestBlock.Height),
   566  		)
   567  		if err != nil {
   568  			return currBestBlock, nil, fmt.Errorf("unable to find "+
   569  				"blockhash for height=%d: %v",
   570  				currBestBlock.Height, err)
   571  		}
   572  
   573  		startingHeight, err = GetCommonBlockAncestorHeight(
   574  			chainConn, *currBestBlock.Hash, *hashAtBestHeight,
   575  		)
   576  		if err != nil {
   577  			return currBestBlock, nil, fmt.Errorf("unable to find "+
   578  				"common ancestor: %v", err)
   579  		}
   580  
   581  		currBestBlock, err = RewindChain(
   582  			chainConn, txNotifier, currBestBlock, startingHeight,
   583  		)
   584  		if err != nil {
   585  			return currBestBlock, nil, fmt.Errorf("unable to "+
   586  				"rewind chain: %v", err)
   587  		}
   588  	}
   589  
   590  	// We want to start dispatching historical notifications from the block
   591  	// right after our best block, to avoid a redundant notification.
   592  	missedBlocks, err := getMissedBlocks(chainConn, startingHeight+1, newHeight)
   593  	if err != nil {
   594  		return currBestBlock, nil, fmt.Errorf("unable to get missed "+
   595  			"blocks: %v", err)
   596  	}
   597  
   598  	return currBestBlock, missedBlocks, nil
   599  }
   600  
   601  // getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
   602  // fetched from the chain.
   603  func getMissedBlocks(chainConn ChainConn, startingHeight,
   604  	endingHeight int32) ([]BlockEpoch, error) {
   605  
   606  	numMissedBlocks := endingHeight - startingHeight
   607  	if numMissedBlocks < 0 {
   608  		return nil, fmt.Errorf("starting height %d is greater than "+
   609  			"ending height %d", startingHeight, endingHeight)
   610  	}
   611  
   612  	missedBlocks := make([]BlockEpoch, 0, numMissedBlocks)
   613  	for height := startingHeight; height < endingHeight; height++ {
   614  		hash, err := chainConn.GetBlockHash(int64(height))
   615  		if err != nil {
   616  			return nil, fmt.Errorf("unable to find blockhash for "+
   617  				"height=%d: %v", height, err)
   618  		}
   619  		header, err := chainConn.GetBlockHeader(hash)
   620  		if err != nil {
   621  			return nil, fmt.Errorf("unable to find block header "+
   622  				"for height=%d: %v", height, err)
   623  		}
   624  
   625  		missedBlocks = append(
   626  			missedBlocks,
   627  			BlockEpoch{
   628  				Hash:        hash,
   629  				Height:      height,
   630  				BlockHeader: header,
   631  			},
   632  		)
   633  	}
   634  
   635  	return missedBlocks, nil
   636  }
   637  
   638  // TxIndexConn abstracts an RPC backend with txindex enabled.
   639  type TxIndexConn interface {
   640  	// GetRawTransactionVerbose returns the transaction identified by the
   641  	// passed chain hash, and returns additional information such as the
   642  	// block that the transaction confirmed.
   643  	GetRawTransactionVerbose(*chainhash.Hash) (*jsontypes.TxRawResult, error)
   644  
   645  	// GetBlockVerbose returns the block identified by the chain hash along
   646  	// with additional information such as the block's height in the chain.
   647  	GetBlockVerbose(*chainhash.Hash, bool) (*jsontypes.GetBlockVerboseResult, error)
   648  }
   649  
   650  // IsTxIndexDisabledError returns true if the provided error means the
   651  // transaction index has been disabled.
   652  func IsTxIndexDisabledError(err error) bool {
   653  	if err == nil {
   654  		return false
   655  	}
   656  
   657  	errNoTxIndexMsg := "the transaction index must be enabled"
   658  	return strings.Contains(err.Error(), errNoTxIndexMsg)
   659  }
   660  
   661  // ConfDetailsFromTxIndex looks up whether a transaction is already included in
   662  // a block in the active chain by using the backend node's transaction index.
   663  // If the transaction is found its TxConfStatus is returned. If it was found in
   664  // the mempool this will be TxFoundMempool, if it is found in a block this will
   665  // be TxFoundIndex. Otherwise TxNotFoundIndex is returned. If the tx is found
   666  // in a block its confirmation details are also returned.
   667  func ConfDetailsFromTxIndex(chainConn TxIndexConn, r ConfRequest,
   668  	txNotFoundErr string) (*TxConfirmation, TxConfStatus, error) {
   669  
   670  	// If the transaction has some or all of its confirmations required,
   671  	// then we may be able to dispatch it immediately.
   672  	rawTxRes, err := chainConn.GetRawTransactionVerbose(&r.TxID)
   673  	if err != nil {
   674  		// If the transaction lookup was successful, but it wasn't
   675  		// found within the index itself, then we can exit early. We'll
   676  		// also need to look at the error message returned as the error
   677  		// code is used for multiple errors.
   678  		jsonErr, ok := err.(*dcrjson.RPCError)
   679  		if ok && jsonErr.Code == dcrjson.ErrRPCNoTxInfo &&
   680  			strings.Contains(jsonErr.Message, txNotFoundErr) {
   681  
   682  			return nil, TxNotFoundIndex, nil
   683  		}
   684  
   685  		return nil, TxNotFoundIndex,
   686  			fmt.Errorf("unable to query for txid %v: %v",
   687  				r.TxID, err)
   688  	}
   689  
   690  	// Deserialize the hex-encoded transaction to include it in the
   691  	// confirmation details.
   692  	rawTx, err := hex.DecodeString(rawTxRes.Hex)
   693  	if err != nil {
   694  		return nil, TxNotFoundIndex,
   695  			fmt.Errorf("unable to deserialize tx %v: %v",
   696  				r.TxID, err)
   697  	}
   698  	var tx wire.MsgTx
   699  	if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil {
   700  		return nil, TxNotFoundIndex,
   701  			fmt.Errorf("unable to deserialize tx %v: %v",
   702  				r.TxID, err)
   703  	}
   704  
   705  	// Ensure the transaction matches our confirmation request in terms of
   706  	// txid and pkscript.
   707  	if !r.MatchesTx(&tx) {
   708  		return nil, TxNotFoundIndex,
   709  			fmt.Errorf("unable to locate tx %v", r.TxID)
   710  	}
   711  
   712  	// Make sure we actually retrieved a transaction that is included in a
   713  	// block. If not, the transaction must be unconfirmed (in the mempool),
   714  	// and we'll return TxFoundMempool together with a nil TxConfirmation.
   715  	if rawTxRes.BlockHash == "" {
   716  		return nil, TxFoundMempool, nil
   717  	}
   718  
   719  	// As we need to fully populate the returned TxConfirmation struct,
   720  	// grab the block in which the transaction was confirmed so we can
   721  	// locate its exact index within the block.
   722  	blockHash, err := chainhash.NewHashFromStr(rawTxRes.BlockHash)
   723  	if err != nil {
   724  		return nil, TxNotFoundIndex,
   725  			fmt.Errorf("unable to get block hash %v for "+
   726  				"historical dispatch: %v", rawTxRes.BlockHash, err)
   727  	}
   728  	block, err := chainConn.GetBlockVerbose(blockHash, false)
   729  	if err != nil {
   730  		return nil, TxNotFoundIndex,
   731  			fmt.Errorf("unable to get block with hash %v for "+
   732  				"historical dispatch: %v", blockHash, err)
   733  	}
   734  
   735  	// If the block was obtained, locate the transaction's index within the
   736  	// block so we can give the subscriber full confirmation details.
   737  	txidStr := r.TxID.String()
   738  	for txIndex, txHash := range block.Tx {
   739  		if txHash != txidStr {
   740  			continue
   741  		}
   742  
   743  		return &TxConfirmation{
   744  			Tx:          &tx,
   745  			BlockHash:   blockHash,
   746  			BlockHeight: uint32(block.Height),
   747  			TxIndex:     uint32(txIndex),
   748  		}, TxFoundIndex, nil
   749  	}
   750  
   751  	// We return an error because we should have found the transaction
   752  	// within the block, but didn't.
   753  	return nil, TxNotFoundIndex, fmt.Errorf("unable to locate "+
   754  		"tx %v in block %v", r.TxID, blockHash)
   755  }