Repository: decred/dcrd
Pull request #2342 (2020-08-22 02:12:27)

rpcserver: Add handleGenerate test.


Patch:
diff --git a/docs/json_rpc_api.mediawiki b/docs/json_rpc_api.mediawiki
index 7ed9ee89..83ccebb0 100644
--- a/docs/json_rpc_api.mediawiki
+++ b/docs/json_rpc_api.mediawiki
@@ -2224,6 +2224,8 @@ of the best block.
 # <code>skip</code>: <code>(int, optional, default=0)</code> the number of leading transactions to leave out of the final response.
 # <code>count</code>: <code>(int, optional, default=100)</code> the maximum number of transactions to return.
 # <code>vinextra</code>: <code>(int, optional, default=0)</code> specify that extra data from previous output will be returned in vin.
+# <code>reverse</code>: <code>(boolean, optional, default=false)</code> specify that the transactions should be returned in reverse chronological order.
+# <code>filteraddrs</code>: <code>(json array of strings, optional)</code> specify that only inputs or outputs with matching addresses should be returned.
 |-
 !Description
 |Returns raw data for transactions involving the passed address. Returned transactions are pulled from both the database, and transactions currently in the mempool. Transactions pulled from the mempool will have the <code>"confirmations"</code> field set to 0. Usage of this RPC requires the optional <code>--addrindex</code> flag to be activated, otherwise all responses will simply return with an error stating the address index has not yet been built up. Similarly, until the address index has caught up with the current best height, all requests will return an error response in order to avoid serving stale data.
diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go
index 93bda8d6..a9f83cad 100644
--- a/internal/rpcserver/interface.go
+++ b/internal/rpcserver/interface.go
@@ -13,7 +13,9 @@ import (
 	"github.com/decred/dcrd/addrmgr"
 	"github.com/decred/dcrd/blockchain/stake/v3"
 	"github.com/decred/dcrd/blockchain/v3"
+	"github.com/decred/dcrd/blockchain/v3/indexers"
 	"github.com/decred/dcrd/chaincfg/chainhash"
+	"github.com/decred/dcrd/database/v2"
 	"github.com/decred/dcrd/dcrutil/v3"
 	"github.com/decred/dcrd/gcs/v2"
 	"github.com/decred/dcrd/internal/mempool"
@@ -595,3 +597,40 @@ type TxMempooler interface {
 	// and does not include orphans.
 	FetchTransaction(txHash *chainhash.Hash) (*dcrutil.Tx, error)
 }
+
+// AddrIndexer provides an interface for retrieving transactions for a given
+// address.
+//
+// The interface contract requires that all of these methods are safe for
+// concurrent access.
+type AddrIndexer interface {
+	// EntriesForAddress returns a slice of details which identify each transaction,
+	// including a block region, that involves the passed address according to the
+	// specified number to skip, number requested, and whether or not the results
+	// should be reversed.  It also returns the number actually skipped since it
+	// could be less in the case where there are not enough entries.
+	//
+	// NOTE: These results only include transactions confirmed in blocks.  See the
+	// UnconfirmedTxnsForAddress method for obtaining unconfirmed transactions
+	// that involve a given address.
+	EntriesForAddress(dbTx database.Tx, addr dcrutil.Address, numToSkip,
+		numRequested uint32, reverse bool) ([]indexers.TxIndexEntry, uint32, error)
+
+	// UnconfirmedTxnsForAddress returns all transactions currently in the
+	// unconfirmed (memory-only) address index that involve the passed address.
+	// Unsupported address types are ignored and will result in no results.
+	UnconfirmedTxnsForAddress(addr dcrutil.Address) []*dcrutil.Tx
+}
+
+// TxIndexer provides an interface for retrieving details for a given
+// transaction hash.
+//
+// The interface contract requires that all of these methods are safe for
+// concurrent access.
+type TxIndexer interface {
+	// Entry returns details for the provided transaction hash from the transaction
+	// index.  The block region contained in the result can in turn be used to load
+	// the raw transaction bytes.  When there is no entry for the provided hash, nil
+	// must be returned for the both the entry and the error.
+	Entry(hash *chainhash.Hash) (*indexers.TxIndexEntry, error)
+}
diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go
index 19fd9296..e27ed631 100644
--- a/internal/rpcserver/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -35,7 +35,6 @@ import (
 	"github.com/decred/dcrd/blockchain/stake/v3"
 	"github.com/decred/dcrd/blockchain/standalone/v2"
 	"github.com/decred/dcrd/blockchain/v3"
-	"github.com/decred/dcrd/blockchain/v3/indexers"
 	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/chaincfg/v3"
 	"github.com/decred/dcrd/database/v2"
@@ -1669,7 +1668,7 @@ func handleGenerate(ctx context.Context, s *Server, cmd interface{}) (interface{
 		return nil, &dcrjson.RPCError{
 			Code: dcrjson.ErrRPCDifficulty,
 			Message: fmt.Sprintf("No support for `generate` on the current "+
-				"network, %s, as it's unlikely to be possible to main a block "+
+				"network, %s, as it's unlikely to be possible to mine a block "+
 				"with the CPU.", params.Net),
 		}
 	}
@@ -2398,8 +2397,8 @@ func handleGetInfo(_ context.Context, s *Server, cmd interface{}) (interface{},
 		Difficulty:      getDifficultyRatio(best.Bits, s.cfg.ChainParams),
 		TestNet:         s.cfg.TestNet,
 		RelayFee:        s.cfg.MinRelayTxFee.ToCoin(),
-		AddrIndex:       s.cfg.AddrIndex != nil,
-		TxIndex:         s.cfg.TxIndex != nil,
+		AddrIndex:       s.cfg.AddrIndexer != nil,
+		TxIndex:         s.cfg.TxIndexer != nil,
 	}
 
 	return ret, nil
@@ -2737,15 +2736,14 @@ func handleGetRawTransaction(_ context.Context, s *Server, cmd interface{}) (int
 	var blkIndex uint32
 	tx, err := s.cfg.TxMempooler.FetchTransaction(txHash)
 	if err != nil {
-		txIndex := s.cfg.TxIndex
-		if txIndex == nil {
+		if s.cfg.TxIndexer == nil {
 			return nil, rpcInternalError("The transaction index "+
 				"must be enabled to query the blockchain "+
 				"(specify --txindex)", "Configuration")
 		}
 
 		// Look up the location of the transaction.
-		idxEntry, err := txIndex.Entry(txHash)
+		idxEntry, err := s.cfg.TxIndexer.Entry(txHash)
 		if err != nil {
 			context := "Failed to retrieve transaction location"
 			return nil, rpcInternalError(err.Error(), context)
@@ -3682,7 +3680,7 @@ func fetchInputTxos(s *Server, tx *wire.MsgTx) (map[wire.OutPoint]wire.TxOut, er
 		}
 
 		// Look up the location of the transaction.
-		idxEntry, err := s.cfg.TxIndex.Entry(&origin.Hash)
+		idxEntry, err := s.cfg.TxIndexer.Entry(&origin.Hash)
 		if err != nil {
 			context := "Failed to retrieve transaction location"
 			return nil, rpcInternalError(err.Error(), context)
@@ -3870,7 +3868,7 @@ func createVinListPrevOut(s *Server, mtx *wire.MsgTx, chainParams *chaincfg.Para
 func fetchMempoolTxnsForAddress(s *Server, addr dcrutil.Address, numToSkip, numRequested uint32) ([]*dcrutil.Tx, uint32) {
 	// There are no entries to return when there are less available than
 	// the number being skipped.
-	mpTxns := s.cfg.AddrIndex.UnconfirmedTxnsForAddress(addr)
+	mpTxns := s.cfg.AddrIndexer.UnconfirmedTxnsForAddress(addr)
 	numAvailable := uint32(len(mpTxns))
 	if numToSkip > numAvailable {
 		return nil, numAvailable
@@ -3888,8 +3886,7 @@ func fetchMempoolTxnsForAddress(s *Server, addr dcrutil.Address, numToSkip, numR
 // handleSearchRawTransactions implements the searchrawtransactions command.
 func handleSearchRawTransactions(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	// Respond with an error if the address index is not enabled.
-	addrIndex := s.cfg.AddrIndex
-	if addrIndex == nil {
+	if s.cfg.AddrIndexer == nil {
 		return nil, rpcInternalError("Address index must be "+
 			"enabled (--addrindex)", "Configuration")
 	}
@@ -3906,7 +3903,7 @@ func handleSearchRawTransactions(_ context.Context, s *Server, cmd interface{})
 	// transaction index.  Currently the address index relies on the
 	// transaction index, so this check is redundant, but it's better to be
 	// safe in case the address index is ever changed to not rely on it.
-	if vinExtra && s.cfg.TxIndex == nil {
+	if vinExtra && s.cfg.TxIndexer == nil {
 		return nil, rpcInternalError("Transaction index must be "+
 			"enabled (--txindex)", "Configuration")
 	}
@@ -3973,7 +3970,7 @@ func handleSearchRawTransactions(_ context.Context, s *Server, cmd interface{})
 	// are needed.
 	if len(addressTxns) < numRequested {
 		err = s.cfg.DB.View(func(dbTx database.Tx) error {
-			idxEntries, dbSkipped, err := addrIndex.EntriesForAddress(
+			idxEntries, dbSkipped, err := s.cfg.AddrIndexer.EntriesForAddress(
 				dbTx, addr, uint32(numToSkip)-numSkipped,
 				uint32(numRequested-len(addressTxns)), reverse)
 			if err != nil {
@@ -5641,10 +5638,12 @@ type Config struct {
 	BlockTemplater BlockTemplater
 	CPUMiner       CPUMiner
 
-	// These fields define any optional indexes the RPC server can make use
-	// of to provide additional data when queried.
-	TxIndex   *indexers.TxIndex
-	AddrIndex *indexers.AddrIndex
+	// TxIndexer defines the optional transaction indexer for the RPC server to
+	// use.
+	TxIndexer TxIndexer
+
+	// AddrIndexer defines the optional address indexer for the RPC server to use.
+	AddrIndexer AddrIndexer
 
 	// NetInfo defines a slice of the available networks.
 	NetInfo []types.NetworksResult
diff --git a/rpcadaptors.go b/rpcadaptors.go
index 9afca691..c28f3899 100644
--- a/rpcadaptors.go
+++ b/rpcadaptors.go
@@ -11,14 +11,11 @@ import (
 	"net"
 	"time"
 
-	"github.com/decred/dcrd/addrmgr"
 	"github.com/decred/dcrd/blockchain/stake/v3"
 	"github.com/decred/dcrd/blockchain/v3"
-	"github.com/decred/dcrd/blockchain/v3/indexers"
 	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/chaincfg/v3"
 	"github.com/decred/dcrd/dcrutil/v3"
-	"github.com/decred/dcrd/internal/fees"
 	"github.com/decred/dcrd/internal/mempool"
 	"github.com/decred/dcrd/internal/mining"
 	"github.com/decred/dcrd/internal/mining/cpuminer"
@@ -108,15 +105,6 @@ func (p *rpcPeer) BanScore() uint32 {
 	return (*serverPeer)(p).banScore.Int()
 }
 
-// rpcAddrManager provides an address manager for use with the RPC server and
-// implements the rpcserver.AddrManager interface.
-type rpcAddrManager struct {
-	*addrmgr.AddrManager
-}
-
-// Ensure rpcAddrManager implements the rpcserver.AddrManager interface.
-var _ rpcserver.AddrManager = (*rpcAddrManager)(nil)
-
 // rpcConnManager provides a connection manager for use with the RPC server and
 // implements the rpcserver.ConnManager interface.
 type rpcConnManager struct {
@@ -453,22 +441,13 @@ func (*rpcClock) Since(t time.Time) time.Duration {
 	return time.Since(t)
 }
 
-// Ensure rpcFeeEstimator implements the rpcserver.FeeEstimator interface.
-var _ rpcserver.FeeEstimator = (*rpcFeeEstimator)(nil)
-
-// rpcFeeEstimator provides a fee estimator for use with the RPC server and
-// implements the rpcserver.FeeEstimator interface.
-type rpcFeeEstimator struct {
-	*fees.Estimator
-}
-
-// Ensure rpcLogManager implements the rpcserver.LogManager interface.
-var _ rpcserver.LogManager = (*rpcLogManager)(nil)
-
 // rpcLogManager provides a log manager for use with the RPC server and
 // implements the rpcserver.LogManager interface.
 type rpcLogManager struct{}
 
+// Ensure rpcLogManager implements the rpcserver.LogManager interface.
+var _ rpcserver.LogManager = (*rpcLogManager)(nil)
+
 // SupportedSubsystems returns a sorted slice of the supported subsystems for
 // logging purposes.
 //
@@ -505,15 +484,15 @@ func (s *rpcSanityChecker) CheckBlockSanity(block *dcrutil.Block) error {
 	return blockchain.CheckBlockSanity(block, s.timeSource, s.chainParams)
 }
 
-// Ensure rpcBlockTemplater implements the rpcserver.BlockTemplater interface.
-var _ rpcserver.BlockTemplater = (*rpcBlockTemplater)(nil)
-
 // rpcBlockTemplater provides a block template generator for use with the
 // RPC server and implements the rpcserver.BlockTemplater interface.
 type rpcBlockTemplater struct {
 	*mining.BgBlkTmplGenerator
 }
 
+// Ensure rpcBlockTemplater implements the rpcserver.BlockTemplater interface.
+var _ rpcserver.BlockTemplater = (*rpcBlockTemplater)(nil)
+
 // Subscribe returns a TemplateSubber which has functions to retrieve
 // a channel that produces the stream of block templates and to stop
 // the stream when the caller no longer wishes to receive new templates.
@@ -575,54 +554,3 @@ func (c *rpcCPUMiner) SetNumWorkers(numWorkers int32) {
 		c.miner.SetNumWorkers(numWorkers)
 	}
 }
-
-// rpcFilterer provides methods for retrieving a block's committed filter or
-// committed filter header and implements the rpcserver.Filterer interface.
-//
-// Deprecated: This will be removed in the next major version.  Use
-// rpcFiltererV2 instead.
-type rpcFilterer struct {
-	*indexers.CFIndex
-}
-
-// Ensure rpcFilterer implements the rpcserver.Filterer interface.
-var _ rpcserver.Filterer = (*rpcFilterer)(nil)
-
-// rpcFiltererV2 provides methods for retrieving a block's version 2 GCS filter
-// and implements the rpcserver.FiltererV2 interface.
-type rpcFiltererV2 struct {
-	*blockchain.BlockChain
-}
-
-// Ensure rpcFiltererV2 implements the rpcserver.FiltererV2 interface.
-var _ rpcserver.FiltererV2 = (*rpcFiltererV2)(nil)
-
-// rpcExistsAddresser provides exists address methods for use with the RPC
-// server and implements the rpcserver.ExistsAddresser interface. It should be
-// constructed by calling newRPCExistsAddresser.
-type rpcExistsAddresser struct {
-	*indexers.ExistsAddrIndex
-}
-
-// Ensure rpcExistsAddresser implements the rpcserver.ExistsAddresser interface.
-var _ rpcserver.ExistsAddresser = (*rpcExistsAddresser)(nil)
-
-// newRPCExistsAddresser is a constructor for a new rpcserver.ExistsAddresser
-// that wraps *rpcExistsAddresser. If the server's existsAddrIndex is nil, nil
-// is returned. Consumers must check for nil before calling methods on the
-// returned rpcserver.ExistsAddresser.
-func newRPCExistsAddresser(e *indexers.ExistsAddrIndex) rpcserver.ExistsAddresser {
-	if e == nil {
-		return nil
-	}
-	return &rpcExistsAddresser{e}
-}
-
-// rpcTxMempooler provides a mempool transaction data source for use with the
-// RPC server and implements the rpcserver.TxMempooler interface.
-type rpcTxMempooler struct {
-	*mempool.TxPool
-}
-
-// Ensure rpcTxMempooler implements the rpcserver.TxMempooler interface.
-var _ rpcserver.TxMempooler = (*rpcTxMempooler)(nil)
diff --git a/server.go b/server.go
index c6ec9d4f..1f6f68b9 100644
--- a/server.go
+++ b/server.go
@@ -3267,31 +3267,22 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 			return nil, errors.New("no usable rpc listen addresses")
 		}
 
-		s.rpcServer, err = rpcserver.New(&rpcserver.Config{
-			Listeners:       rpcListeners,
-			ConnMgr:         &rpcConnManager{&s},
-			SyncMgr:         &rpcSyncMgr{server: &s, blockMgr: s.blockManager},
-			ExistsAddresser: newRPCExistsAddresser(s.existsAddrIndex),
-			FeeEstimator:    &rpcFeeEstimator{s.feeEstimator},
-			TimeSource:      s.timeSource,
-			Services:        s.services,
-			AddrManager:     &rpcAddrManager{s.addrManager},
-			Clock:           &rpcClock{},
-			SubsidyCache:    s.subsidyCache,
-			Chain:           &rpcChain{s.chain},
-			ChainParams:     chainParams,
-			SanityChecker:   &rpcSanityChecker{s.timeSource, chainParams},
-			DB:              db,
-			TxMempooler:     &rpcTxMempooler{s.txMemPool},
-			BlockTemplater: func() rpcserver.BlockTemplater {
-				if s.bg == nil {
-					return nil
-				}
-				return &rpcBlockTemplater{s.bg}
-			}(),
+		rpcsConfig := rpcserver.Config{
+			Listeners:            rpcListeners,
+			ConnMgr:              &rpcConnManager{&s},
+			SyncMgr:              &rpcSyncMgr{server: &s, blockMgr: s.blockManager},
+			FeeEstimator:         s.feeEstimator,
+			TimeSource:           s.timeSource,
+			Services:             s.services,
+			AddrManager:          s.addrManager,
+			Clock:                &rpcClock{},
+			SubsidyCache:         s.subsidyCache,
+			Chain:                &rpcChain{s.chain},
+			ChainParams:          chainParams,
+			SanityChecker:        &rpcSanityChecker{s.timeSource, chainParams},
+			DB:                   db,
+			TxMempooler:          s.txMemPool,
 			CPUMiner:             &rpcCPUMiner{s.cpuMiner},
-			TxIndex:              s.txIndex,
-			AddrIndex:            s.addrIndex,
 			NetInfo:              cfg.generateNetworkInfo(),
 			MinRelayTxFee:        cfg.minRelayTxFee,
 			Proxy:                cfg.Proxy,
@@ -3308,14 +3299,25 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 			MaxProtocolVersion:   maxProtocolVersion,
 			UserAgentVersion:     userAgentVersion,
 			LogManager:           &rpcLogManager{},
-			Filterer: func() rpcserver.Filterer {
-				if s.cfIndex == nil {
-					return nil
-				}
-				return &rpcFilterer{s.cfIndex}
-			}(),
-			FiltererV2: &rpcFiltererV2{s.chain},
-		})
+			FiltererV2:           s.chain,
+		}
+		if s.existsAddrIndex != nil {
+			rpcsConfig.ExistsAddresser = s.existsAddrIndex
+		}
+		if s.bg != nil {
+			rpcsConfig.BlockTemplater = &rpcBlockTemplater{s.bg}
+		}
+		if s.txIndex != nil {
+			rpcsConfig.TxIndexer = s.txIndex
+		}
+		if s.addrIndex != nil {
+			rpcsConfig.AddrIndexer = s.addrIndex
+		}
+		if s.cfIndex != nil {
+			rpcsConfig.Filterer = s.cfIndex
+		}
+
+		s.rpcServer, err = rpcserver.New(&rpcsConfig)
 		if err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/blockchain/common_test.go b/blockchain/common_test.go
index 01433494..05525335 100644
--- a/blockchain/common_test.go
+++ b/blockchain/common_test.go
@@ -253,7 +253,7 @@ func findDeployment(params *chaincfg.Params, voteID string) (uint32, *chaincfg.C
 		}
 	}
 
-	return 0, nil, fmt.Errorf("unable to find deployement for id %q", voteID)
+	return 0, nil, fmt.Errorf("unable to find deployment for id %q", voteID)
 }
 
 // findDeploymentChoice finds the provided choice ID within the given
@@ -270,6 +270,33 @@ func findDeploymentChoice(deployment *chaincfg.ConsensusDeployment, choiceID str
 	return nil, fmt.Errorf("unable to find vote choice for id %q", choiceID)
 }
 
+// Make the linter happy.  This should be removed once the function is actually
+// used.
+var _ = removeDeployment
+
+// removeDeployment modifies the passed parameters to remove all deployments for
+// the provided vote ID.  An error is returned when not found.
+func removeDeployment(params *chaincfg.Params, voteID string) error {
+	// Remove the deployment(s) for the passed vote ID.
+	var found bool
+	for version, deployments := range params.Deployments {
+		for i, deployment := range deployments {
+			if deployment.Vote.Id == voteID {
+				copy(deployments[i:], deployments[i+1:])
+				deployments[len(deployments)-1] = chaincfg.ConsensusDeployment{}
+				deployments = deployments[:len(deployments)-1]
+				params.Deployments[version] = deployments
+				found = true
+			}
+		}
+	}
+	if found {
+		return nil
+	}
+
+	return fmt.Errorf("unable to find deployment for id %q", voteID)
+}
+
 // removeDeploymentTimeConstraints modifies the passed deployment to remove the
 // voting time constraints by making it always available to vote and to never
 // expire.
diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
index 7ce70a65..c4dc0f62 100644
--- a/internal/rpcserver/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -12,6 +12,7 @@ import (
 	"encoding/hex"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"math"
 	"math/big"
 	"net"
@@ -27,8 +28,10 @@ import (
 	"github.com/decred/dcrd/blockchain/stake/v3"
 	"github.com/decred/dcrd/blockchain/standalone/v2"
 	"github.com/decred/dcrd/blockchain/v3"
+	"github.com/decred/dcrd/blockchain/v3/indexers"
 	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/chaincfg/v3"
+	"github.com/decred/dcrd/database/v2"
 	"github.com/decred/dcrd/dcrjson/v3"
 	"github.com/decred/dcrd/dcrutil/v3"
 	"github.com/decred/dcrd/gcs/v2"
@@ -117,6 +120,7 @@ type testRPCChain struct {
 	blockHashByHeight               *chainhash.Hash
 	blockHashByHeightErr            error
 	blockHeightByHash               int64
+	blockHeightByHashErr            error
 	calcNextRequiredStakeDifficulty int64
 	calcWantHeight                  int64
 	chainTips                       []blockchain.ChainTipInfo
@@ -182,7 +186,7 @@ func (c *testRPCChain) BlockHashByHeight(height int64) (*chainhash.Hash, error)
 
 // BlockHeightByHash returns a mocked height of the block with the given hash.
 func (c *testRPCChain) BlockHeightByHash(hash *chainhash.Hash) (int64, error) {
-	return c.blockHeightByHash, nil
+	return c.blockHeightByHash, c.blockHeightByHashErr
 }
 
 // CalcNextRequiredStakeDifficulty returns a mocked required stake difficulty.
@@ -512,6 +516,172 @@ func (e *testExistsAddresser) ExistsAddresses(addrs []dcrutil.Address) ([]bool,
 	return e.existsAddresses, e.existsAddressesErr
 }
 
+// testAddrIndexer provides a mock address indexer by implementing the
+// AddrIndexer interface.
+type testAddrIndexer struct {
+	entriesForAddress         []indexers.TxIndexEntry
+	entriesForAddressSkipped  uint32
+	entriesForAddressErr      error
+	unconfirmedTxnsForAddress []*dcrutil.Tx
+}
+
+// EntriesForAddress returns a mocked slice of indexers.TxIndexEntry that
+// involve the given address.
+func (a *testAddrIndexer) EntriesForAddress(dbTx database.Tx,
+	addr dcrutil.Address, numToSkip, numRequested uint32, reverse bool) (
+	[]indexers.TxIndexEntry, uint32, error) {
+	return a.entriesForAddress, a.entriesForAddressSkipped, a.entriesForAddressErr
+}
+
+// UnconfirmedTxnsForAddress returns a mocked slice of transactions that are
+// currently in the unconfirmed (memory-only) address index.
+func (a *testAddrIndexer) UnconfirmedTxnsForAddress(addr dcrutil.Address) []*dcrutil.Tx {
+	return a.unconfirmedTxnsForAddress
+}
+
+// testTxIndexer provides a mock transaction indexer by implementing the
+// TxIndexer interface.
+type testTxIndexer struct {
+	entry func(hash *chainhash.Hash) (*indexers.TxIndexEntry, error)
+}
+
+// Entry returns mocked details for the provided transaction hash from the
+// transaction index.
+func (t *testTxIndexer) Entry(hash *chainhash.Hash) (*indexers.TxIndexEntry, error) {
+	return t.entry(hash)
+}
+
+// testDB provides a mock database by implementing the database.DB interface.
+type testDB struct {
+	dbType   string
+	beginTx  database.Tx
+	beginErr error
+	viewTx   database.Tx
+	updateTx database.Tx
+	closeErr error
+}
+
+// Type returns the mocked database driver type.
+func (d *testDB) Type() string {
+	return d.dbType
+}
+
+// Begin returns a mocked database transaction.
+func (d *testDB) Begin(writable bool) (database.Tx, error) {
+	return d.beginTx, d.beginErr
+}
+
+// View invokes the passed function in the context of a mocked read-only
+// database transaction.
+func (d *testDB) View(fn func(tx database.Tx) error) error {
+	return fn(d.viewTx)
+}
+
+// Update invokes the passed function in the context of a mocked read-write
+// database transaction.
+func (d *testDB) Update(fn func(tx database.Tx) error) error {
+	return fn(d.updateTx)
+}
+
+// Close provides a mock implementation for the shut down of the database.
+func (d *testDB) Close() error {
+	return d.closeErr
+}
+
+// testDatabaseTx provides a mock database transaction by implementing the
+// database.Tx interface.
+type testDatabaseTx struct {
+	metadata             database.Bucket
+	storeBlockErr        error
+	hasBlock             bool
+	hasBlockErr          error
+	hasBlocks            []bool
+	hasBlocksErr         error
+	fetchBlockHeader     []byte
+	fetchBlockHeaderErr  error
+	fetchBlockHeaders    [][]byte
+	fetchBlockHeadersErr error
+	fetchBlock           []byte
+	fetchBlockErr        error
+	fetchBlocks          [][]byte
+	fetchBlocksErr       error
+	fetchBlockRegion     func(region *database.BlockRegion) ([]byte, error)
+	fetchBlockRegions    func(regions []database.BlockRegion) ([][]byte, error)
+	commitErr            error
+	rollbackErr          error
+}
+
+// Metadata returns a mocked top-most bucket for all metadata storage.
+func (t *testDatabaseTx) Metadata() database.Bucket {
+	return t.metadata
+}
+
+// StoreBlock provides a mock implementation for storing the provided block
+// in the database.
+func (t *testDatabaseTx) StoreBlock(block database.BlockSerializer) error {
+	return t.storeBlockErr
+}
+
+// HasBlock returns a mocked bool representing whether or not a block with the
+// given hash exists in the database.
+func (t *testDatabaseTx) HasBlock(hash *chainhash.Hash) (bool, error) {
+	return t.hasBlock, t.hasBlockErr
+}
+
+// HasBlocks returns a mocked slice of bools representing whether or not the
+// blocks with the provided hashes exist in the database.
+func (t *testDatabaseTx) HasBlocks(hashes []chainhash.Hash) ([]bool, error) {
+	return t.hasBlocks, t.hasBlocksErr
+}
+
+// FetchBlockHeader returns mocked raw serialized bytes for the block header
+// identified by the given hash.
+func (t *testDatabaseTx) FetchBlockHeader(hash *chainhash.Hash) ([]byte, error) {
+	return t.fetchBlockHeader, t.fetchBlockHeaderErr
+}
+
+// FetchBlockHeaders returns mocked raw serialized bytes for the block headers
+// identified by the given hashes.
+func (t *testDatabaseTx) FetchBlockHeaders(hashes []chainhash.Hash) ([][]byte, error) {
+	return t.fetchBlockHeaders, t.fetchBlockHeadersErr
+}
+
+// FetchBlock returns mocked raw serialized bytes for the block identified by
+// the given hash.
+func (t *testDatabaseTx) FetchBlock(hash *chainhash.Hash) ([]byte, error) {
+	return t.fetchBlock, t.fetchBlockErr
+}
+
+// FetchBlocks returns mocked raw serialized bytes for the blocks identified by
+// the given hashes.
+func (t *testDatabaseTx) FetchBlocks(hashes []chainhash.Hash) ([][]byte, error) {
+	return t.fetchBlocks, t.fetchBlocksErr
+}
+
+// FetchBlockRegion returns mocked raw serialized bytes for the given block
+// region.
+func (t *testDatabaseTx) FetchBlockRegion(region *database.BlockRegion) ([]byte, error) {
+	return t.fetchBlockRegion(region)
+}
+
+// FetchBlockRegions returns mocked raw serialized bytes for the given block
+// regions.
+func (t *testDatabaseTx) FetchBlockRegions(regions []database.BlockRegion) ([][]byte, error) {
+	return t.fetchBlockRegions(regions)
+}
+
+// Commit provides a mock implementation for committing all changes that have
+// been made.
+func (t *testDatabaseTx) Commit() error {
+	return t.commitErr
+}
+
+// Rollback provides a mock implementation for undoing all changes that have
+// been made.
+func (t *testDatabaseTx) Rollback() error {
+	return t.rollbackErr
+}
+
 // testConnManager provides a mock connection manager by implementing the
 // ConnManager interface.
 type testConnManager struct {
@@ -893,6 +1063,19 @@ func hexToBytes(s string) []byte {
 	return b
 }
 
+// hexToMsgTx converts the passed hex string into a wire.MsgTx and will panic if
+// there is an error.  This is only provided for hard-coded constants so errors
+// in the source code can be detected.  It will only (and must only) be called
+// with hard-coded values.
+func hexToMsgTx(s string) *wire.MsgTx {
+	var msgTx wire.MsgTx
+	err := msgTx.Deserialize(bytes.NewReader(hexToBytes(s)))
+	if err != nil {
+		panic("invalid tx hex in source file: " + s)
+	}
+	return &msgTx
+}
+
 // cloneParams returns a deep copy of the provided parameters so the caller is
 // free to modify them without worrying about interfering with other tests.
 func cloneParams(params *chaincfg.Params) *chaincfg.Params {
@@ -926,6 +1109,17 @@ var block432100 = func() wire.MsgBlock {
 	return block
 }()
 
+// hexFromFile loads a hex string from a file and will panic if there is an
+// error.  This is provided for test files that contain hard-coded data.
+func hexFromFile(filename string) string {
+	filePath := filepath.Join(testDataPath, filename)
+	hex, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		panic(err)
+	}
+	return string(hex)
+}
+
 type rpcTest struct {
 	name                  string
 	handler               commandHandler
@@ -942,12 +1136,18 @@ type rpcTest struct {
 	mockSyncManager       *testSyncManager
 	mockExistsAddresser   *testExistsAddresser
 	setExistsAddresserNil bool
+	mockAddrIndexer       *testAddrIndexer
+	setAddrIndexerNil     bool
+	mockTxIndexer         *testTxIndexer
+	setTxIndexerNil       bool
+	mockDB                *testDB
 	mockConnManager       *testConnManager
 	mockClock             *testClock
 	mockLogManager        *testLogManager
 	mockFilterer          *testFilterer
 	mockFiltererV2        *testFiltererV2
 	mockTxMempooler       *testTxMempooler
+	mockMiningAddrs       []dcrutil.Address
 	result                interface{}
 	wantErr               bool
 	errCode               dcrjson.RPCErrorCode
@@ -1158,6 +1358,44 @@ func defaultMockExistsAddresser() *testExistsAddresser {
 	return &testExistsAddresser{}
 }
 
+// defaultMockAddrIndexer provides a default mock address indexer to be
+// used throughout the tests. Tests can override these defaults by calling
+// defaultMockAddrIndexer, updating fields as necessary on the returned
+// *testAddrIndexer, and then setting rpcTest.mockAddrIndexer as that
+// *testAddrIndexer.
+func defaultMockAddrIndexer() *testAddrIndexer {
+	return &testAddrIndexer{}
+}
+
+// defaultMockTxIndexer provides a default mock transaction indexer to be
+// used throughout the tests. Tests can override these defaults by calling
+// defaultMockTxIndexer, updating fields as necessary on the returned
+// *testTxIndexer, and then setting rpcTest.mockTxIndexer as that
+// *testTxIndexer.
+func defaultMockTxIndexer() *testTxIndexer {
+	return &testTxIndexer{}
+}
+
+// defaultMockDB provides a default mock database to be used throughout the
+// tests. Tests can override these defaults by calling defaultMockDB, updating
+// fields as necessary on the returned *testDB, and then setting rpcTest.mockDB
+// as that *testDB.
+func defaultMockDB() *testDB {
+	defaultDatabaseTx := &testDatabaseTx{
+		fetchBlockRegion: func(region *database.BlockRegion) ([]byte, error) {
+			return nil, errors.New("block not found")
+		},
+		fetchBlockRegions: func(regions []database.BlockRegion) ([][]byte, error) {
+			return nil, errors.New("block not found")
+		},
+	}
+	return &testDB{
+		beginTx:  defaultDatabaseTx,
+		viewTx:   defaultDatabaseTx,
+		updateTx: defaultDatabaseTx,
+	}
+}
+
 // defaultMockSyncManager provides a default mock sync manager to be used
 // throughout the tests. Tests can override these defaults by calling
 // defaultMockSyncManager, updating fields as necessary on the returned
@@ -1280,7 +1518,9 @@ func defaultMockCPUMiner() *testCPUMiner {
 // *testTxMempooler, and then setting rpcTest.mockTxMempooler as that
 // *testTxMempooler.
 func defaultMockTxMempooler() *testTxMempooler {
-	return &testTxMempooler{}
+	return &testTxMempooler{
+		fetchTransactionErr: errors.New("transaction is not in the pool"),
+	}
 }
 
 // defaultMockConfig provides a default Config that is used throughout
@@ -1295,6 +1535,9 @@ func defaultMockConfig(chainParams *chaincfg.Params) *Config {
 		FeeEstimator:    defaultMockFeeEstimator(),
 		SyncMgr:         defaultMockSyncManager(),
 		ExistsAddresser: defaultMockExistsAddresser(),
+		AddrIndexer:     defaultMockAddrIndexer(),
+		TxIndexer:       defaultMockTxIndexer(),
+		DB:              defaultMockDB(),
 		ConnMgr:         defaultMockConnManager(),
 		CPUMiner:        defaultMockCPUMiner(),
 		TxMempooler:     defaultMockTxMempooler(),
@@ -2873,6 +3116,79 @@ func TestHandleExistsMissedTickets(t *testing.T) {
 	}})
 }
 
+func TestHandleGenerate(t *testing.T) {
+	t.Parallel()
+
+	hashStrOne := "00000000000000001e6ec1501c858506de1de4703d1be8bab4061126e8f61480"
+	hashStrTwo := "00000000000000001a1ec2becd0dd90bfbd0c65f42fdaf608dd9ceac2a3aee1d"
+	generatedBlocks := []*chainhash.Hash{mustParseHash(hashStrOne), mustParseHash(hashStrTwo)}
+	res := []string{hashStrOne, hashStrTwo}
+	miningAddr, err := dcrutil.DecodeAddress("DcurAwesomeAddressmqDctW5wJCW1Cn2MF", defaultChainParams)
+	if err != nil {
+		t.Fatalf("[DecodeAddress] unexpected error: %v", err)
+	}
+	miningAddrs := []dcrutil.Address{miningAddr}
+	chainParams := cloneParams(defaultChainParams)
+	chainParams.GenerateSupported = true
+	cpu := defaultMockCPUMiner()
+	cpu.generatedBlocks = generatedBlocks
+	testRPCServerHandler(t, []rpcTest{{
+		name:    "handleGenerate: ok",
+		handler: handleGenerate,
+		cmd: &types.GenerateCmd{
+			NumBlocks: 2,
+		},
+		mockMiningAddrs: miningAddrs,
+		mockChainParams: chainParams,
+		mockCPUMiner:    cpu,
+		result:          res,
+	}, {
+		name:    "handleGenerate: no mining addrs",
+		handler: handleGenerate,
+		cmd: &types.GenerateCmd{
+			NumBlocks: 2,
+		},
+		mockChainParams: chainParams,
+		mockCPUMiner:    cpu,
+		wantErr:         true,
+		errCode:         dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleGenerate: generate not supported for network",
+		handler: handleGenerate,
+		cmd: &types.GenerateCmd{
+			NumBlocks: 2,
+		},
+		mockMiningAddrs: miningAddrs,
+		mockCPUMiner:    cpu,
+		wantErr:         true,
+		errCode:         dcrjson.ErrRPCDifficulty,
+	}, {
+		name:            "handleGenerate: generate 0 blocks",
+		handler:         handleGenerate,
+		cmd:             &types.GenerateCmd{},
+		mockMiningAddrs: miningAddrs,
+		mockChainParams: chainParams,
+		mockCPUMiner:    cpu,
+		wantErr:         true,
+		errCode:         dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleGenerate: generate n blocks error",
+		handler: handleGenerate,
+		cmd: &types.GenerateCmd{
+			NumBlocks: 2,
+		},
+		mockMiningAddrs: miningAddrs,
+		mockChainParams: chainParams,
+		mockCPUMiner: func() *testCPUMiner {
+			cpu := defaultMockCPUMiner()
+			cpu.generateNBlocksErr = errors.New("")
+			return cpu
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInternal.Code,
+	}})
+}
+
 func TestHandleGetAddedNodeInfo(t *testing.T) {
 	t.Parallel()
 
@@ -3878,8 +4194,8 @@ func TestHandleGetInfo(t *testing.T) {
 			Difficulty:      float64(28147398026.656624),
 			TestNet:         false,
 			RelayFee:        float64(0.0001),
-			AddrIndex:       false,
-			TxIndex:         false,
+			AddrIndex:       true,
+			TxIndex:         true,
 		},
 	}})
 }
@@ -4504,6 +4820,443 @@ func TestHandlePing(t *testing.T) {
 	}})
 }
 
+// testTx holds test transaction info and is used for mocking transaction
+// details for handleSearchRawTransactions.
+type testTx struct {
+	hex        string
+	indexEntry *indexers.TxIndexEntry
+	vinList    []testTx
+}
+
+// searchRawTransactionsResult returns a types.SearchRawTransactionsResult
+// for the given testTx and handleSearchRawTransactions parameters.  This uses
+// createVinListPrevOut and createVoutList, so ideally those should be tested
+// independently as well.
+func searchRawTransactionsResult(t *testing.T, s *Server, tx testTx,
+	vinExtra bool, filterAddr map[string]struct{},
+) types.SearchRawTransactionsResult {
+	msgTx := hexToMsgTx(tx.hex)
+	vinList, err := createVinListPrevOut(s, msgTx, defaultChainParams, vinExtra,
+		filterAddr)
+	if err != nil {
+		t.Fatalf("error creating vinList: %+v", err)
+	}
+	result := types.SearchRawTransactionsResult{
+		Hex:      tx.hex,
+		Txid:     msgTx.TxHash().String(),
+		Version:  int32(msgTx.Version),
+		LockTime: msgTx.LockTime,
+		Expiry:   msgTx.Expiry,
+		Vin:      vinList,
+		Vout:     createVoutList(msgTx, defaultChainParams, filterAddr),
+	}
+	if tx.indexEntry != nil {
+		result.BlockHash = tx.indexEntry.BlockRegion.Hash.String()
+		result.BlockHeight = int64(block432100.Header.Height)
+		result.BlockIndex = tx.indexEntry.BlockIndex
+		result.Confirmations = uint64(1)
+		result.Time = block432100.Header.Timestamp.Unix()
+		result.Blocktime = block432100.Header.Timestamp.Unix()
+	}
+	return result
+}
+
+func TestHandleSearchRawTransactions(t *testing.T) {
+	t.Parallel()
+
+	// Create a testTx that exists in a block.
+	tx0TestTx := testTx{
+		hex: hexFromFile("tx432098-11.hex"),
+		indexEntry: &indexers.TxIndexEntry{
+			BlockRegion: database.BlockRegion{
+				Hash: mustParseHash("00000000000000001fc4c4c7a3f2ec6d552dda16a3a928f27bd6" +
+					"bd16d8f1e9b3"),
+				Offset: 52508,
+				Len:    453,
+			},
+			BlockIndex: 11,
+		},
+	}
+	tx0TestTx.vinList = []testTx{{
+		hex: hexFromFile("tx431841-1.hex"),
+		indexEntry: &indexers.TxIndexEntry{
+			BlockRegion: database.BlockRegion{
+				Hash: mustParseHash("00000000000000000a5f89c78fd0136d6efb065f5a9b8e763df2" +
+					"62fcbb64a93c"),
+				Offset: 55568,
+				Len:    420,
+			},
+			BlockIndex: 1,
+		},
+	}, {
+		hex: hexFromFile("tx432083-2.hex"),
+		indexEntry: &indexers.TxIndexEntry{
+			BlockRegion: database.BlockRegion{
+				Hash: mustParseHash("00000000000000000bcc2bff8a7d978e1ed9b7763e17402cff3e" +
+					"58cb4a1d3aa3"),
+				Offset: 3405,
+				Len:    453,
+			},
+			BlockIndex: 2,
+		},
+	}}
+
+	// Create a testTx that exists in the mempool.
+	tx1TestTx := testTx{
+		hex: hexFromFile("tx432100-1.hex"),
+	}
+	tx1TestTx.vinList = []testTx{{
+		hex: hexFromFile("tx431843-1.hex"),
+		indexEntry: &indexers.TxIndexEntry{
+			BlockRegion: database.BlockRegion{
+				Hash: mustParseHash("0000000000000000107e144664020dc7f503de2f34c5a297e7bf" +
+					"b8ab7ce73ee1"),
+				Offset: 2226,
+				Len:    419,
+			},
+			BlockIndex: 1,
+		},
+	}, {
+		hex: hexFromFile("tx432098-11.hex"),
+		indexEntry: &indexers.TxIndexEntry{
+			BlockRegion: database.BlockRegion{
+				Hash: mustParseHash("00000000000000001fc4c4c7a3f2ec6d552dda16a3a928f27bd6" +
+					"bd16d8f1e9b3"),
+				Offset: 52508,
+				Len:    453,
+			},
+			BlockIndex: 11,
+		},
+	}}
+
+	// Define the test address and testTxs that involve that address.
+	address := "Dsi8CRt85xYyempXs7ZPL1rBxvDdAGZmgsg"
+	testTxs := []testTx{
+		tx0TestTx,
+		tx1TestTx,
+	}
+
+	// Mock AddrIndexer, TxIndexer, and DB methods based on the testTxs.
+	entriesForAddress := make([]indexers.TxIndexEntry, 0, len(testTxs))
+	unconfirmedTxnsForAddress := make([]*dcrutil.Tx, 0, len(testTxs))
+	txIndexEntryMap := make(map[chainhash.Hash]indexers.TxIndexEntry)
+	fetchBlockRegionMap := make(map[database.BlockRegion][]byte)
+	fetchBlockRegions := make([][]byte, 0, len(testTxs))
+	for _, testTx := range testTxs {
+		msgTx := hexToMsgTx(testTx.hex)
+		if testTx.indexEntry != nil {
+			entriesForAddress = append(entriesForAddress, *testTx.indexEntry)
+			fetchBlockRegions = append(fetchBlockRegions, hexToBytes(testTx.hex))
+		} else {
+			unconfirmedTxnsForAddress = append(unconfirmedTxnsForAddress,
+				dcrutil.NewTx(msgTx))
+		}
+		for _, vinTestTx := range testTx.vinList {
+			vinMtx := hexToMsgTx(vinTestTx.hex)
+			txIndexEntryMap[vinMtx.TxHash()] = *vinTestTx.indexEntry
+			blockRegion := vinTestTx.indexEntry.BlockRegion
+			fetchBlockRegionMap[blockRegion] = hexToBytes(vinTestTx.hex)
+		}
+	}
+	addrIndexer := defaultMockAddrIndexer()
+	addrIndexer.entriesForAddress = entriesForAddress
+	addrIndexer.unconfirmedTxnsForAddress = unconfirmedTxnsForAddress
+	txIndexer := defaultMockTxIndexer()
+	txIndexer.entry = func(hash *chainhash.Hash) (*indexers.TxIndexEntry, error) {
+		txIndexEntry := txIndexEntryMap[*hash]
+		return &txIndexEntry, nil
+	}
+	db := defaultMockDB()
+	db.viewTx = &testDatabaseTx{
+		fetchBlockRegion: func(region *database.BlockRegion) ([]byte, error) {
+			return fetchBlockRegionMap[*region], nil
+		},
+		fetchBlockRegions: func(regions []database.BlockRegion) ([][]byte, error) {
+			return fetchBlockRegions, nil
+		},
+	}
+
+	// Set result and filteredResult to the results that are expected.
+	result := make([]types.SearchRawTransactionsResult, len(testTxs))
+	filteredResult := make([]types.SearchRawTransactionsResult, len(testTxs))
+	vinExtra := true
+	filterAddr := map[string]struct{}{address: {}}
+	s := &Server{cfg: *defaultMockConfig(defaultChainParams)}
+	s.cfg.AddrIndexer = addrIndexer
+	s.cfg.DB = db
+	s.cfg.TxIndexer = txIndexer
+	for i, tx := range testTxs {
+		result[i] = searchRawTransactionsResult(t, s, tx, vinExtra, nil)
+		filteredResult[i] = searchRawTransactionsResult(t, s, tx, vinExtra,
+			filterAddr)
+	}
+
+	testRPCServerHandler(t, []rpcTest{{
+		name:    "handleSearchRawTransactions: ok",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(0),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		result: []string{
+			tx0TestTx.hex,
+			tx1TestTx.hex,
+		},
+	}, {
+		name:    "handleSearchRawTransactions: ok verbose",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		mockTxIndexer:   txIndexer,
+		result:          result,
+	}, {
+		name:    "handleSearchRawTransactions: ok verbose with addr filter",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(1),
+			FilterAddrs: &[]string{
+				address,
+			},
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		mockTxIndexer:   txIndexer,
+		result:          filteredResult,
+	}, {
+		name:    "handleSearchRawTransactions: ok with count < 0",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(0),
+			Count:    dcrjson.Int(-1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		result: []string{
+			tx0TestTx.hex,
+		},
+	}, {
+		name:    "handleSearchRawTransactions: ok with skip < 0",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(0),
+			Skip:     dcrjson.Int(-1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		result: []string{
+			tx0TestTx.hex,
+			tx1TestTx.hex,
+		},
+	}, {
+		name:    "handleSearchRawTransactions: ok with skip > available mempool txns",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(0),
+			Skip:     dcrjson.Int(2),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		result: []string{
+			tx0TestTx.hex,
+		},
+	}, {
+		name:    "handleSearchRawTransactions: ok reversed",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(0),
+			Reverse:  dcrjson.Bool(true),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		result: []string{
+			tx1TestTx.hex,
+			tx0TestTx.hex,
+		},
+	}, {
+		name:    "handleSearchRawTransactions: address index not enabled",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+		},
+		setAddrIndexerNil: true,
+		wantErr:           true,
+		errCode:           dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: transaction index not enabled",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+		},
+		setTxIndexerNil: true,
+		wantErr:         true,
+		errCode:         dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: invalid address",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  "invalid",
+			VinExtra: dcrjson.Int(1),
+		},
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInvalidAddressOrKey,
+	}, {
+		name:    "handleSearchRawTransactions: count == 0",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Count:    dcrjson.Int(0),
+		},
+		result: nil,
+	}, {
+		name:    "handleSearchRawTransactions: entries for address err",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+		},
+		mockAddrIndexer: func() *testAddrIndexer {
+			addrIndexer := defaultMockAddrIndexer()
+			addrIndexer.entriesForAddressErr = errors.New("entries for address err")
+			return addrIndexer
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: fetch block regions err",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+		},
+		mockDB: func() *testDB {
+			db := defaultMockDB()
+			db.viewTx = &testDatabaseTx{
+				fetchBlockRegions: func(regions []database.BlockRegion) ([][]byte, error) {
+					return nil, errors.New("fetch block regions err")
+				},
+			}
+			return db
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: no txns available",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+		},
+		mockDB: func() *testDB {
+			db := defaultMockDB()
+			db.viewTx = &testDatabaseTx{
+				fetchBlockRegions: func(regions []database.BlockRegion) ([][]byte, error) {
+					return [][]byte{}, nil
+				},
+			}
+			return db
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: failed to deserialize transaction",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB: func() *testDB {
+			db := defaultMockDB()
+			db.viewTx = &testDatabaseTx{
+				fetchBlockRegions: func(regions []database.BlockRegion) ([][]byte, error) {
+					return [][]byte{hexToBytes("bad1")}, nil
+				},
+			}
+			return db
+		}(),
+		mockTxIndexer: txIndexer,
+		wantErr:       true,
+		errCode:       dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: block not found",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		mockTxIndexer:   txIndexer,
+		mockChain: func() *testRPCChain {
+			chain := defaultMockRPCChain()
+			chain.headerByHashErr = errors.New("block not found")
+			return chain
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCBlockNotFound,
+	}, {
+		name:    "handleSearchRawTransactions: failed to obtain block height",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		mockTxIndexer:   txIndexer,
+		mockChain: func() *testRPCChain {
+			chain := defaultMockRPCChain()
+			chain.blockHeightByHashErr = errors.New("failed to obtain block height")
+			return chain
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInternal.Code,
+	}, {
+		name:    "handleSearchRawTransactions: failed to retrieve tx location",
+		handler: handleSearchRawTransactions,
+		cmd: &types.SearchRawTransactionsCmd{
+			Address:  address,
+			VinExtra: dcrjson.Int(1),
+			Verbose:  dcrjson.Int(1),
+		},
+		mockAddrIndexer: addrIndexer,
+		mockDB:          db,
+		mockTxIndexer: func() *testTxIndexer {
+			txIndexer := defaultMockTxIndexer()
+			txIndexer.entry = func(hash *chainhash.Hash) (*indexers.TxIndexEntry, error) {
+				return nil, errors.New("failed to retrieve tx location")
+			}
+			return txIndexer
+		}(),
+		wantErr: true,
+		errCode: dcrjson.ErrRPCInternal.Code,
+	}})
+}
+
 func TestHandleSubmitBlock(t *testing.T) {
 	t.Parallel()
 
@@ -4986,6 +5739,21 @@ func testRPCServerHandler(t *testing.T, tests []rpcTest) {
 			if test.setExistsAddresserNil {
 				rpcserverConfig.ExistsAddresser = nil
 			}
+			if test.mockAddrIndexer != nil {
+				rpcserverConfig.AddrIndexer = test.mockAddrIndexer
+			}
+			if test.setAddrIndexerNil {
+				rpcserverConfig.AddrIndexer = nil
+			}
+			if test.mockTxIndexer != nil {
+				rpcserverConfig.TxIndexer = test.mockTxIndexer
+			}
+			if test.setTxIndexerNil {
+				rpcserverConfig.TxIndexer = nil
+			}
+			if test.mockDB != nil {
+				rpcserverConfig.DB = test.mockDB
+			}
 			if test.mockConnManager != nil {
 				rpcserverConfig.ConnMgr = test.mockConnManager
 			}
@@ -5018,6 +5786,9 @@ func testRPCServerHandler(t *testing.T, tests []rpcTest) {
 					workState = ms.workState
 				}
 			}
+			if test.mockMiningAddrs != nil {
+				rpcserverConfig.MiningAddrs = test.mockMiningAddrs
+			}
 			if test.mockBlockTemplater != nil {
 				rpcserverConfig.BlockTemplater = test.mockBlockTemplater
 			}
diff --git a/internal/rpcserver/testdata/tx431841-1.hex b/internal/rpcserver/testdata/tx431841-1.hex
new file mode 100644
index 00000000..ab07cf10
--- /dev/null
+++ b/internal/rpcserver/testdata/tx431841-1.hex
@@ -0,0 +1,1 @@
+01000000020000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff76813944ac024cda9f99fb34403356a4bd4ea396807cfebb7648ab3d4db9ff1d0000000001ffffffff0400000000000000000000266a24b93a17ff14be64756dac12b4985f7f5a86e27749981c8e270000000000000000e096060000000000000000000000086a060100070000001b460b000000000000001abb76a914bee010b0d6369633d6fb12d67559440e2146c8db88ac772383e90200000000001abb76a914b4f547c307543cb91b9169e3ee11b9034061d20188ac000000000000000002fd378f050000000000000000ffffffff0200009631ffe302000000cc7c06000700000091483045022100e4d4a2a2ee86074560905938040183be815a3aa1002e5aded8208687f9bee54e02205aaf1f4c7013548fb5e6c8b0cabaeda7a0c2b64d081ba5f494913a2fceb6a66c014751210291b56f0c534e3754c51ec881fa2034fd228ad2d36f94ec37a34975b1235135cf2103e861db04dec874e59e489885170edfcb35d363adcaf2631c7d54dc09b10a451852ae
\ No newline at end of file
diff --git a/internal/rpcserver/testdata/tx431843-1.hex b/internal/rpcserver/testdata/tx431843-1.hex
new file mode 100644
index 00000000..80c5069b
--- /dev/null
+++ b/internal/rpcserver/testdata/tx431843-1.hex
@@ -0,0 +1,1 @@
+01000000020000000000000000000000000000000000000000000000000000000000000000ffffffff00ffffffff8a5ed6e6eb9be6b6e3d26f3294540087096229a26eafedec5c4f43c4a8606c5c0000000001ffffffff0400000000000000000000266a24c827d0958f34b1d19842c6710f78d5e9708ea6ce4e582a120000000000000000e296060000000000000000000000086a060100070000001c460b000000000000001abb76a914bee010b0d6369633d6fb12d67559440e2146c8db88acc8f1e3d10200000000001abb76a9144ad2bfb09f0faceb9b96cdb65a9d164a3d6ae0b888ac000000000000000002fd378f050000000000000000ffffffff020000e8ff5fcc02000000987f0600100000009047304402206db57d3c70cefbe006b45a440568a5822b72d46273448602ea0d06d9395fb36102203a56a1ffc3280b8f9a7400902e1642e0960ef33d9d690f82cf48202f6484bd1c014751210291b56f0c534e3754c51ec881fa2034fd228ad2d36f94ec37a34975b1235135cf2103e861db04dec874e59e489885170edfcb35d363adcaf2631c7d54dc09b10a451852ae
\ No newline at end of file
diff --git a/internal/rpcserver/testdata/tx432083-2.hex b/internal/rpcserver/testdata/tx432083-2.hex
new file mode 100644
index 00000000..88db95f7
--- /dev/null
+++ b/internal/rpcserver/testdata/tx432083-2.hex
@@ -0,0 +1,1 @@
+010000000297d75063280ff985f7027329f21d7a5a98faf19f16dc76d9d1bf3985831f859f0400000000ffffffffe632d52177a2ea85c5f8b7ea01e029215f1313a487814008abbde4d2db5e25830300000001ffffffff0353170b000000000000001976a914f0a9c1af516719a7a76c433a061ecbce7ae196e788acf747f15b0300000000001976a914f0a9c1af516719a7a76c433a061ecbce7ae196e788ac53d4cf070200000000001976a9141c30d05fc919a7be49dc000f47025d1477cb24d588ac0000000000000000023a741cbd01000000c5970600040000006b483045022100fcee8a83c7c5e4496127d6415cad05c1e8b3d9af8147d497cdd31f909b3feab302207e3ef314374c96bb3f1140a9e7288b62bec874cac1b558a26f6f05fb08943de3012103505b972163bf0faa7f0c0439233913e70eb2d08d375ae586a13984da51e3e32129d1afa603000000d2960600010000006b4830450221008b952f025082a43927a66e55c72d7ed6f20d3b6fd073ae2feabf912fed68b2a302202b70023a7c2051a5e5e9fd5a44b1650549aa0e9affbc750c4a38e5190a5b806c012103fa80f5c013283f5e97acfabe0842afb84b310f53d5a6ac0807a0f2489cb2bc49
\ No newline at end of file
diff --git a/internal/rpcserver/testdata/tx432098-11.hex b/internal/rpcserver/testdata/tx432098-11.hex
new file mode 100644
index 00000000..21088771
--- /dev/null
+++ b/internal/rpcserver/testdata/tx432098-11.hex
@@ -0,0 +1,1 @@
+0100000002b761292042421b09196a2a9cdf56001a95df8c508dacf1170bba8b0c813fc8210300000001ffffffffdca0b996c9078ed14749774aba78d6e1df78e29486deb59d1894ed16a53b74080200000000ffffffff0353170b000000000000001976a914762432e9619f5ddaf122ac663684152ffe9eb0ec88acf747f15b0300000000001976a914762432e9619f5ddaf122ac663684152ffe9eb0ec88acba8656950100000000001976a914bc3c059489f447afbf542ff33432adb9ded7f8e988ac000000000000000002772383e902000000e1960600010000006b483045022100ba5b20f9148273717deba544348f0595750e12cb57d7a818914c66453c9dfb930220486feb328c8f171cce5cfbf88382114282f9041ba53209a12ad48ac86ceb8eb2012102b9ff45cb72132bdf41cf97e96afa90102a4c96ef11fafe43e01983050880aab953d4cf0702000000d3970600020000006b483045022100b29ff29f99ae5b8e3fad72efe6dd13968b7d1c6b3fe2e0fb7eb7656cf7de75f202200888c36da42a0cc948770e3be29b0c75465096bc47cbac27f7c19be7b33287760121039e58379edbbc239e965d7715a9834f6870d9e5e6bf7ebae8d12a30f7282ea5a5
\ No newline at end of file
diff --git a/internal/rpcserver/testdata/tx432100-1.hex b/internal/rpcserver/testdata/tx432100-1.hex
new file mode 100644
index 00000000..247c50ae
--- /dev/null
+++ b/internal/rpcserver/testdata/tx432100-1.hex
@@ -0,0 +1,1 @@
+010000000283067d34267d265ebb85cd83ca30bfd7ec01a8c90828d8945c30bcbb62841e5d0300000001ffffffffd5946c5856ba3d62422df236cd155a53fcf8badb7c605838e145331e99b820c70200000000ffffffff0353170b000000000000001976a9140bb407413316b66d7648f4d938d701159b34dfd688acf747f15b0300000000001976a9140bb407413316b66d7648f4d938d701159b34dfd688ac72073e0b0100000000001976a914065660dc3514f60a08fb32c37b2fa39863dd053788ac000000000000000002c8f1e3d102000000e3960600010000006a4730440220141d93135fdecd27c9e78178a79eed674df2e1086a21487226bbb6cb7500a63102201be67161b2f72496627e89735ff3cbd44c25f5aae4fd82d230ca41bd32b397ab01210202142453cda983d5adfa6c5b015604c1d55f74a3f9eb3196235a3b19cf208d1aba86569501000000e29706000b0000006a47304402203f0968d5605ca45bf9a5c4dbf7b0a8c6ed9568d0d728d24eb070e3f4e971be330220070353a48feb56dee1c2677aee8d78c2b978f5bb5c10d39e57b4c6aa02d88f3301210399d10781e598f7551ce3b8e6feb494c82b3a1d0e01b377eddb52fa75d13c5e78
\ No newline at end of file