Repository: decred/dcrd
Pull request #2288 (2020-07-22 12:02:53)

rpcserver: Move to internal.
This moves rpcserver from the main module to a standalone internal package.

This is broken up into several commits to make the majority of the dependency updates separately from actually moving the files.  Can be split into multiple PRs but leaving it as one for now since this likely won't be merged until post 1.6.0.

Closes https://github.com/decred/dcrd/issues/2263

rstaudt2: Thanks for the reviews.  Updated per the review comments and also rebased to the latest master.
davecgh: Also, I marked this as 1.6.0 since I plan to get it in for that given it will help with the release process.  The goal is to avoid leaving anything in the root module this is likely to cause a major semver bump.
davecgh: With the exception of the aforementioned extra committed file, I've reviewed all the updates to this point and they look good.
rstaudt2: Removed the unintentionally committed file and also reworked [this commit](https://github.com/decred/dcrd/pull/2288/commits/975fb8cafa6a78c58e376ff5800540a26a1e042a) to provide exported delegated functions for the `NotifyXYZ` functions rather than exposing `wsNotificationManager`.
davecgh: Mind rebasing this one last time so I can merge this?  I missed that it was going to conflict with the one I just merged.
rstaudt2: Rebased to latest master 

Patch:
diff --git a/blockmanager.go b/blockmanager.go
index 7ec030cd..c810af2e 100644
--- a/blockmanager.go
+++ b/blockmanager.go
@@ -25,6 +25,7 @@ import (
 	"github.com/decred/dcrd/fees/v2"
 	"github.com/decred/dcrd/internal/mempool"
 	"github.com/decred/dcrd/internal/mining"
+	"github.com/decred/dcrd/internal/rpcserver"
 	peerpkg "github.com/decred/dcrd/peer/v2"
 	"github.com/decred/dcrd/wire"
 )
@@ -275,9 +276,9 @@ type blockManagerConfig struct {
 	BgBlkTmplGenerator *mining.BgBlkTmplGenerator
 
 	// The following fields are blockManager callbacks.
-	NotifyWinningTickets      func(*WinningTicketsNtfnData)
+	NotifyWinningTickets      func(*rpcserver.WinningTicketsNtfnData)
 	PruneRebroadcastInventory func()
-	RpcServer                 func() *rpcServer
+	RpcServer                 func() *rpcserver.Server
 }
 
 // peerSyncState stores additional information that the blockManager tracks
@@ -362,7 +363,7 @@ func (b *blockManager) resetHeaderState(newestHash *chainhash.Hash, newestHeight
 // for block notification processing.
 func (b *blockManager) NotifyWork(templateNtfn *mining.TemplateNtfn) {
 	if r := b.cfg.RpcServer(); r != nil {
-		r.ntfnMgr.NotifyWork(templateNtfn)
+		r.NotifyWork(templateNtfn)
 	}
 }
 
@@ -1117,11 +1118,11 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
 			if r != nil {
 				// Update registered websocket clients on the
 				// current stake difficulty.
-				r.ntfnMgr.NotifyStakeDifficulty(
-					&StakeDifficultyNtfnData{
-						best.Hash,
-						best.Height,
-						best.NextStakeDiff,
+				r.NotifyStakeDifficulty(
+					&rpcserver.StakeDifficultyNtfnData{
+						BlockHash:       best.Hash,
+						BlockHeight:     best.Height,
+						StakeDifficulty: best.NextStakeDiff,
 					})
 			}
 			b.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff, best.Height)
@@ -1682,11 +1683,11 @@ out:
 					best := b.cfg.Chain.BestSnapshot()
 					r := b.cfg.RpcServer()
 					if r != nil {
-						r.ntfnMgr.NotifyStakeDifficulty(
-							&StakeDifficultyNtfnData{
-								best.Hash,
-								best.Height,
-								best.NextStakeDiff,
+						r.NotifyStakeDifficulty(
+							&rpcserver.StakeDifficultyNtfnData{
+								BlockHash:       best.Hash,
+								BlockHeight:     best.Height,
+								StakeDifficulty: best.NextStakeDiff,
 							})
 					}
 					b.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff,
@@ -1724,11 +1725,11 @@ out:
 					// invalidated transactions.
 					best := b.cfg.Chain.BestSnapshot()
 					if r != nil {
-						r.ntfnMgr.NotifyStakeDifficulty(
-							&StakeDifficultyNtfnData{
-								best.Hash,
-								best.Height,
-								best.NextStakeDiff,
+						r.NotifyStakeDifficulty(
+							&rpcserver.StakeDifficultyNtfnData{
+								BlockHash:       best.Hash,
+								BlockHeight:     best.Height,
+								StakeDifficulty: best.NextStakeDiff,
 							})
 					}
 					b.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff,
@@ -1895,7 +1896,7 @@ func (b *blockManager) handleBlockchainNotification(notification *blockchain.Not
 			} else {
 				// Notify registered websocket clients of newly
 				// eligible tickets to vote on.
-				b.cfg.NotifyWinningTickets(&WinningTicketsNtfnData{
+				b.cfg.NotifyWinningTickets(&rpcserver.WinningTicketsNtfnData{
 					BlockHash:   *blockHash,
 					BlockHeight: blockHeight,
 					Tickets:     wt,
@@ -2030,7 +2031,7 @@ func (b *blockManager) handleBlockchainNotification(notification *blockchain.Not
 			b.cfg.PruneRebroadcastInventory()
 
 			// Notify registered websocket clients of incoming block.
-			r.ntfnMgr.NotifyBlockConnected(block)
+			r.NotifyBlockConnected(block)
 		}
 
 		if b.cfg.BgBlkTmplGenerator != nil {
@@ -2047,7 +2048,7 @@ func (b *blockManager) handleBlockchainNotification(notification *blockchain.Not
 		}
 
 		if r := b.cfg.RpcServer(); r != nil {
-			r.ntfnMgr.NotifySpentAndMissedTickets(tnd)
+			r.NotifySpentAndMissedTickets(tnd)
 		}
 
 	// Stake tickets are matured from the most recently connected block.
@@ -2060,7 +2061,7 @@ func (b *blockManager) handleBlockchainNotification(notification *blockchain.Not
 		}
 
 		if r := b.cfg.RpcServer(); r != nil {
-			r.ntfnMgr.NotifyNewTickets(tnd)
+			r.NotifyNewTickets(tnd)
 		}
 
 	// A block has been disconnected from the main block chain.
@@ -2139,7 +2140,7 @@ func (b *blockManager) handleBlockchainNotification(notification *blockchain.Not
 			b.cfg.PruneRebroadcastInventory()
 
 			// Notify registered websocket clients.
-			r.ntfnMgr.NotifyBlockDisconnected(block)
+			r.NotifyBlockDisconnected(block)
 		}
 
 	// Chain reorganization has commenced.
@@ -2164,7 +2165,7 @@ func (b *blockManager) handleBlockchainNotification(notification *blockchain.Not
 
 		// Notify registered websocket clients.
 		if r := b.cfg.RpcServer(); r != nil {
-			r.ntfnMgr.NotifyReorganization(rd)
+			r.NotifyReorganization(rd)
 		}
 	}
 }
diff --git a/internal/rpcserver/README.md b/internal/rpcserver/README.md
index a086b386..2fd0787f 100644
--- a/internal/rpcserver/README.md
+++ b/internal/rpcserver/README.md
@@ -7,7 +7,8 @@ rpcserver
 
 ## Overview
 
-This package is currently a work in progress.
+Package rpcserver includes all RPC server interfaces, types, and pieces of code
+pertaining to implementing the RPC server.
 
 ## License
 
diff --git a/internal/rpcserver/doc.go b/internal/rpcserver/doc.go
index 96dc3e67..db31935c 100644
--- a/internal/rpcserver/doc.go
+++ b/internal/rpcserver/doc.go
@@ -3,11 +3,7 @@
 // license that can be found in the LICENSE file.
 
 /*
-Package rpcserver includes all RPC server interfaces and types, and will house
-the RPC server in the future.
-
-This package currently contains 3 interfaces used to allow the various systems
-the RPC server interacts with to be loosely coupled.  In the future it will
-contain all of the pieces of code pertaining implementing the RPC server.
+Package rpcserver includes all RPC server interfaces, types, and pieces of code
+pertaining to implementing the RPC server.
 */
 package rpcserver
diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go
index bbdaec6e..364dc095 100644
--- a/internal/rpcserver/interface.go
+++ b/internal/rpcserver/interface.go
@@ -127,6 +127,9 @@ type ConnManager interface {
 
 	// AddedNodeInfo returns information describing persistent (added) nodes.
 	AddedNodeInfo() []Peer
+
+	// Lookup defines the DNS lookup function to be used.
+	Lookup(host string) ([]net.IP, error)
 }
 
 // SyncManager represents a sync manager for use with the RPC server.
@@ -437,3 +440,18 @@ type FeeEstimator interface {
 	// high degree of certainty.
 	EstimateFee(targetConfs int32) (dcrutil.Amount, error)
 }
+
+// LogManager represents a log manager for use with the RPC server.
+//
+// The interface contract does NOT require that these methods are safe for
+// concurrent access.
+type LogManager interface {
+	// SupportedSubsystems returns a sorted slice of the supported subsystems for
+	// logging purposes.
+	SupportedSubsystems() []string
+
+	// ParseAndSetDebugLevels attempts to parse the specified debug level and set
+	// the levels accordingly.  An appropriate error must be returned if anything
+	// is invalid.
+	ParseAndSetDebugLevels(debugLevel string) error
+}
diff --git a/rpcserver.go b/internal/rpcserver/rpcserver.go
similarity index 90%
rename from rpcserver.go
rename to internal/rpcserver/rpcserver.go
index 7339aef8..bc72d227 100644
--- a/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -3,12 +3,11 @@
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
-package main
+package rpcserver
 
 import (
 	"bytes"
 	"context"
-	"crypto/elliptic"
 	"crypto/sha256"
 	"crypto/subtle"
 	"encoding/base64"
@@ -21,10 +20,8 @@ import (
 	"io/ioutil"
 	"math"
 	"math/big"
-	"math/rand"
 	"net"
 	"net/http"
-	"os"
 	"runtime"
 	"sort"
 	"strconv"
@@ -39,7 +36,6 @@ import (
 	"github.com/decred/dcrd/blockchain/standalone/v2"
 	"github.com/decred/dcrd/blockchain/v3"
 	"github.com/decred/dcrd/blockchain/v3/indexers"
-	"github.com/decred/dcrd/certgen"
 	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/chaincfg/v3"
 	"github.com/decred/dcrd/database/v2"
@@ -49,7 +45,6 @@ import (
 	"github.com/decred/dcrd/internal/mempool"
 	"github.com/decred/dcrd/internal/mining"
 	"github.com/decred/dcrd/internal/mining/cpuminer"
-	"github.com/decred/dcrd/internal/rpcserver"
 	"github.com/decred/dcrd/internal/version"
 	"github.com/decred/dcrd/rpc/jsonrpc/types/v2"
 	"github.com/decred/dcrd/txscript/v3"
@@ -112,6 +107,11 @@ var (
 
 	// JSON 2.0 batched request prefix
 	batchedRequestPrefix = []byte("[")
+
+	// zeroHash is the zero value for a chainhash.Hash and is defined as
+	// a package level variable to avoid the need to create a new instance
+	// every time a check is needed.
+	zeroHash chainhash.Hash
 )
 
 // Errors
@@ -131,7 +131,7 @@ var (
 	}
 )
 
-type commandHandler func(context.Context, *rpcServer, interface{}) (interface{}, error)
+type commandHandler func(context.Context, *Server, interface{}) (interface{}, error)
 
 // rpcHandlers maps RPC command strings to appropriate handler functions.
 // This is set by init because help references rpcHandlers and thus causes
@@ -372,7 +372,7 @@ func rpcInternalError(errStr, context string) *dcrjson.RPCError {
 	if context != "" {
 		logStr = context + ": " + errStr
 	}
-	rpcsLog.Error(logStr)
+	log.Error(logStr)
 	return dcrjson.NewRPCError(dcrjson.ErrRPCInternal.Code, errStr)
 }
 
@@ -438,6 +438,25 @@ func rpcMiscError(message string) *dcrjson.RPCError {
 	return dcrjson.NewRPCError(dcrjson.ErrRPCMisc, message)
 }
 
+// directionString is a helper function that returns a string that represents
+// the direction of a connection (inbound or outbound).
+func directionString(inbound bool) string {
+	if inbound {
+		return "inbound"
+	}
+	return "outbound"
+}
+
+// normalizeAddress returns addr with the passed default port appended if
+// there is not already a port specified.
+func normalizeAddress(addr, defaultPort string) string {
+	_, _, err := net.SplitHostPort(addr)
+	if err != nil {
+		return net.JoinHostPort(addr, defaultPort)
+	}
+	return addr
+}
+
 // workState houses state that is used in between multiple RPC invocations to
 // getwork.
 type workState struct {
@@ -456,19 +475,19 @@ func newWorkState() *workState {
 
 // handleUnimplemented is the handler for commands that should ultimately be
 // supported but are not yet implemented.
-func handleUnimplemented(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleUnimplemented(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return nil, ErrRPCUnimplemented
 }
 
 // handleAskWallet is the handler for commands that are recognized as valid, but
 // are unable to answer correctly since it involves wallet state.
 // These commands will be implemented in dcrwallet.
-func handleAskWallet(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleAskWallet(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return nil, ErrRPCNoWallet
 }
 
 // handleAddNode handles addnode commands.
-func handleAddNode(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleAddNode(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.AddNodeCmd)
 
 	addr := normalizeAddress(c.Addr, s.cfg.ChainParams.DefaultPort)
@@ -494,7 +513,7 @@ func handleAddNode(_ context.Context, s *rpcServer, cmd interface{}) (interface{
 }
 
 // handleNode handles node commands.
-func handleNode(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleNode(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.NodeCmd)
 
 	connMgr := s.cfg.ConnMgr
@@ -577,7 +596,7 @@ func handleNode(_ context.Context, s *rpcServer, cmd interface{}) (interface{},
 // peerExists determines if a certain peer is currently connected given
 // information about all currently connected peers. Peer existence is
 // determined using either a target address or node id.
-func peerExists(connMgr rpcserver.ConnManager, addr string, nodeID int32) bool {
+func peerExists(connMgr ConnManager, addr string, nodeID int32) bool {
 	for _, p := range connMgr.ConnectedPeers() {
 		if p.ID() == nodeID || p.Addr() == addr {
 			return true
@@ -588,9 +607,9 @@ func peerExists(connMgr rpcserver.ConnManager, addr string, nodeID int32) bool {
 
 // messageToHex serializes a message to the wire protocol encoding using the
 // latest protocol version and returns a hex-encoded string of the result.
-func messageToHex(msg wire.Message) (string, error) {
+func (s *Server) messageToHex(msg wire.Message) (string, error) {
 	var buf bytes.Buffer
-	if err := msg.BtcEncode(&buf, maxProtocolVersion); err != nil {
+	if err := msg.BtcEncode(&buf, s.cfg.MaxProtocolVersion); err != nil {
 		context := fmt.Sprintf("Failed to encode msg of type %T", msg)
 		return "", rpcInternalError(err.Error(), context)
 	}
@@ -599,7 +618,7 @@ func messageToHex(msg wire.Message) (string, error) {
 }
 
 // handleCreateRawTransaction handles createrawtransaction commands.
-func handleCreateRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleCreateRawTransaction(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.CreateRawTransactionCmd)
 
 	// Validate expiry, if given.
@@ -701,7 +720,7 @@ func handleCreateRawTransaction(_ context.Context, s *rpcServer, cmd interface{}
 	// is intentionally not directly returning because the first return
 	// value is a string and it would result in returning an empty string to
 	// the client instead of nothing (nil) in the case of an error.
-	mtxHex, err := messageToHex(mtx)
+	mtxHex, err := s.messageToHex(mtx)
 	if err != nil {
 		return nil, err
 	}
@@ -709,7 +728,7 @@ func handleCreateRawTransaction(_ context.Context, s *rpcServer, cmd interface{}
 }
 
 // handleCreateRawSStx handles createrawsstx commands.
-func handleCreateRawSStx(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleCreateRawSStx(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.CreateRawSStxCmd)
 
 	// Basic sanity checks for the information coming from the cmd.
@@ -892,7 +911,7 @@ func handleCreateRawSStx(_ context.Context, s *rpcServer, cmd interface{}) (inte
 	}
 
 	// Return the serialized and hex-encoded transaction.
-	mtxHex, err := messageToHex(mtx)
+	mtxHex, err := s.messageToHex(mtx)
 	if err != nil {
 		return nil, err
 	}
@@ -900,7 +919,7 @@ func handleCreateRawSStx(_ context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handleCreateRawSSRtx handles createrawssrtx commands.
-func handleCreateRawSSRtx(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleCreateRawSSRtx(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.CreateRawSSRtxCmd)
 
 	// Only a single SStx should be given
@@ -1017,7 +1036,7 @@ func handleCreateRawSSRtx(_ context.Context, s *rpcServer, cmd interface{}) (int
 	}
 
 	// Return the serialized and hex-encoded transaction.
-	mtxHex, err := messageToHex(mtx)
+	mtxHex, err := s.messageToHex(mtx)
 	if err != nil {
 		return nil, err
 	}
@@ -1025,16 +1044,16 @@ func handleCreateRawSSRtx(_ context.Context, s *rpcServer, cmd interface{}) (int
 }
 
 // handleDebugLevel handles debuglevel commands.
-func handleDebugLevel(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleDebugLevel(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.DebugLevelCmd)
 
 	// Special show command to list supported subsystems.
 	if c.LevelSpec == "show" {
 		return fmt.Sprintf("Supported subsystems %v",
-			supportedSubsystems()), nil
+			s.cfg.LogManager.SupportedSubsystems()), nil
 	}
 
-	err := parseAndSetDebugLevels(c.LevelSpec)
+	err := s.cfg.LogManager.ParseAndSetDebugLevels(c.LevelSpec)
 	if err != nil {
 		return nil, rpcInvalidError("Invalid debug level %v: %v",
 			c.LevelSpec, err)
@@ -1121,7 +1140,7 @@ func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap
 			addr, err := stake.AddrFromSStxPkScrCommitment(v.PkScript,
 				chainParams)
 			if err != nil {
-				rpcsLog.Warnf("failed to decode ticket "+
+				log.Warnf("failed to decode ticket "+
 					"commitment addr output for tx hash "+
 					"%v, output idx %v", mtx.TxHash(), i)
 			} else {
@@ -1129,7 +1148,7 @@ func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap
 			}
 			amt, err := stake.AmountFromSStxPkScrCommitment(v.PkScript)
 			if err != nil {
-				rpcsLog.Warnf("failed to decode ticket "+
+				log.Warnf("failed to decode ticket "+
 					"commitment amt output for tx hash %v"+
 					", output idx %v", mtx.TxHash(), i)
 			} else {
@@ -1189,8 +1208,8 @@ func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap
 
 // createTxRawResult converts the passed transaction and associated parameters
 // to a raw transaction JSON object.
-func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, txHash string, blkIdx uint32, blkHeader *wire.BlockHeader, blkHash string, blkHeight int64, confirmations int64) (*types.TxRawResult, error) {
-	mtxHex, err := messageToHex(mtx)
+func (s *Server) createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, txHash string, blkIdx uint32, blkHeader *wire.BlockHeader, blkHash string, blkHeight int64, confirmations int64) (*types.TxRawResult, error) {
+	mtxHex, err := s.messageToHex(mtx)
 	if err != nil {
 		return nil, err
 	}
@@ -1224,7 +1243,7 @@ func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, txHash str
 }
 
 // handleDecodeRawTransaction handles decoderawtransaction commands.
-func handleDecodeRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleDecodeRawTransaction(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.DecodeRawTransactionCmd)
 
 	// Deserialize the transaction.
@@ -1256,7 +1275,7 @@ func handleDecodeRawTransaction(_ context.Context, s *rpcServer, cmd interface{}
 }
 
 // handleDecodeScript handles decodescript commands.
-func handleDecodeScript(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleDecodeScript(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.DecodeScriptCmd)
 
 	// Convert the hex script to bytes.
@@ -1312,15 +1331,15 @@ func handleDecodeScript(_ context.Context, s *rpcServer, cmd interface{}) (inter
 // handleEstimateFee implements the estimatefee command.
 // TODO this is a very basic implementation.  It should be
 // modified to match the bitcoin-core one.
-func handleEstimateFee(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
-	return cfg.minRelayTxFee.ToCoin(), nil
+func handleEstimateFee(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
+	return s.cfg.MinRelayTxFee.ToCoin(), nil
 }
 
 // handleEstimateSmartFee implements the estimatesmartfee command.
 //
 // The default estimation mode when unset is assumed as "conservative". As of
 // 2018-12, the only supported mode is "conservative".
-func handleEstimateSmartFee(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleEstimateSmartFee(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.EstimateSmartFeeCmd)
 
 	mode := types.EstimateSmartFeeConservative
@@ -1342,7 +1361,7 @@ func handleEstimateSmartFee(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleEstimateStakeDiff implements the estimatestakediff command.
-func handleEstimateStakeDiff(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleEstimateStakeDiff(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.EstimateStakeDiffCmd)
 
 	// Minimum possible stake difficulty.
@@ -1411,7 +1430,7 @@ func handleEstimateStakeDiff(_ context.Context, s *rpcServer, cmd interface{}) (
 }
 
 // handleExistsAddress implements the existsaddress command.
-func handleExistsAddress(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsAddress(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	existsAddrIndex := s.cfg.SyncMgr.ExistsAddrIndex()
 	if existsAddrIndex == nil {
 		return nil, rpcInternalError("Exists address index disabled",
@@ -1437,7 +1456,7 @@ func handleExistsAddress(_ context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handleExistsAddresses implements the existsaddresses command.
-func handleExistsAddresses(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsAddresses(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	existsAddrIndex := s.cfg.SyncMgr.ExistsAddrIndex()
 	if existsAddrIndex == nil {
 		return nil, rpcInternalError("Exists address index disabled",
@@ -1503,7 +1522,7 @@ func decodeHashPointers(strs []string) ([]*chainhash.Hash, error) {
 }
 
 // handleExistsMissedTickets implements the existsmissedtickets command.
-func handleExistsMissedTickets(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsMissedTickets(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.ExistsMissedTicketsCmd)
 
 	hashes, err := decodeHashes(c.TxHashes)
@@ -1529,7 +1548,7 @@ func handleExistsMissedTickets(_ context.Context, s *rpcServer, cmd interface{})
 }
 
 // handleExistsExpiredTickets implements the existsexpiredtickets command.
-func handleExistsExpiredTickets(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsExpiredTickets(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.ExistsExpiredTicketsCmd)
 
 	hashes, err := decodeHashes(c.TxHashes)
@@ -1555,7 +1574,7 @@ func handleExistsExpiredTickets(_ context.Context, s *rpcServer, cmd interface{}
 }
 
 // handleExistsLiveTicket implements the existsliveticket command.
-func handleExistsLiveTicket(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsLiveTicket(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.ExistsLiveTicketCmd)
 
 	hash, err := chainhash.NewHashFromStr(c.TxHash)
@@ -1567,7 +1586,7 @@ func handleExistsLiveTicket(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleExistsLiveTickets implements the existslivetickets command.
-func handleExistsLiveTickets(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsLiveTickets(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.ExistsLiveTicketsCmd)
 
 	hashes, err := decodeHashes(c.TxHashes)
@@ -1593,7 +1612,7 @@ func handleExistsLiveTickets(_ context.Context, s *rpcServer, cmd interface{}) (
 }
 
 // handleExistsMempoolTxs implements the existsmempooltxs command.
-func handleExistsMempoolTxs(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleExistsMempoolTxs(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.ExistsMempoolTxsCmd)
 
 	hashes, err := decodeHashPointers(c.TxHashes)
@@ -1620,10 +1639,10 @@ func handleExistsMempoolTxs(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleGenerate handles generate commands.
-func handleGenerate(ctx context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGenerate(ctx context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	// Respond with an error if there are no addresses to pay the
 	// created blocks to.
-	if len(cfg.miningAddrs) == 0 {
+	if len(s.cfg.MiningAddrs) == 0 {
 		return nil, rpcInternalError("No payment addresses specified "+
 			"via --miningaddr", "Configuration")
 	}
@@ -1661,7 +1680,7 @@ func handleGenerate(ctx context.Context, s *rpcServer, cmd interface{}) (interfa
 }
 
 // handleGetAddedNodeInfo handles getaddednodeinfo commands.
-func handleGetAddedNodeInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetAddedNodeInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetAddedNodeInfoCmd)
 
 	// Retrieve a list of persistent (added) peers from the Decred server
@@ -1711,7 +1730,7 @@ func handleGetAddedNodeInfo(_ context.Context, s *rpcServer, cmd interface{}) (i
 		// Do a DNS lookup for the address.  If the lookup fails, just
 		// use the host.
 		var ipList []string
-		ips, err := dcrdLookup(host)
+		ips, err := s.cfg.ConnMgr.Lookup(host)
 		if err == nil {
 			ipList = make([]string, 0, len(ips))
 			for _, ip := range ips {
@@ -1741,7 +1760,7 @@ func handleGetAddedNodeInfo(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleGetBestBlock implements the getbestblock command.
-func handleGetBestBlock(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBestBlock(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	// All other "get block" commands give either the height, the hash, or
 	// both but require the block SHA.  This gets both for the best block.
 	best := s.cfg.Chain.BestSnapshot()
@@ -1753,7 +1772,7 @@ func handleGetBestBlock(_ context.Context, s *rpcServer, cmd interface{}) (inter
 }
 
 // handleGetBestBlockHash implements the getbestblockhash command.
-func handleGetBestBlockHash(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBestBlockHash(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	best := s.cfg.Chain.BestSnapshot()
 	return best.Hash.String(), nil
 }
@@ -1772,14 +1791,14 @@ func getDifficultyRatio(bits uint32, params *chaincfg.Params) float64 {
 	outString := difficulty.FloatString(8)
 	diff, err := strconv.ParseFloat(outString, 64)
 	if err != nil {
-		rpcsLog.Errorf("Cannot get difficulty: %v", err)
+		log.Errorf("Cannot get difficulty: %v", err)
 		return 0
 	}
 	return diff
 }
 
 // handleGetBlock implements the getblock command.
-func handleGetBlock(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBlock(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetBlockCmd)
 
 	// Load the raw block bytes from the database.
@@ -1880,7 +1899,7 @@ func handleGetBlock(_ context.Context, s *rpcServer, cmd interface{}) (interface
 		chainParams := s.cfg.ChainParams
 		rawTxns := make([]types.TxRawResult, len(txns))
 		for i, tx := range txns {
-			rawTxn, err := createTxRawResult(chainParams,
+			rawTxn, err := s.createTxRawResult(chainParams,
 				tx.MsgTx(), tx.Hash().String(), uint32(i),
 				blockHeader, blk.Hash().String(),
 				int64(blockHeader.Height), confirmations)
@@ -1895,7 +1914,7 @@ func handleGetBlock(_ context.Context, s *rpcServer, cmd interface{}) (interface
 		stxns := blk.STransactions()
 		rawSTxns := make([]types.TxRawResult, len(stxns))
 		for i, tx := range stxns {
-			rawSTxn, err := createTxRawResult(chainParams,
+			rawSTxn, err := s.createTxRawResult(chainParams,
 				tx.MsgTx(), tx.Hash().String(), uint32(i),
 				blockHeader, blk.Hash().String(),
 				int64(blockHeader.Height), confirmations)
@@ -1912,7 +1931,7 @@ func handleGetBlock(_ context.Context, s *rpcServer, cmd interface{}) (interface
 }
 
 // handleGetBlockchainInfo implements the getblockchaininfo command.
-func handleGetBlockchainInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBlockchainInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	chain := s.cfg.Chain
 	best := chain.BestSnapshot()
 
@@ -2000,13 +2019,13 @@ func handleGetBlockchainInfo(_ context.Context, s *rpcServer, cmd interface{}) (
 }
 
 // handleGetBlockCount implements the getblockcount command.
-func handleGetBlockCount(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBlockCount(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	best := s.cfg.Chain.BestSnapshot()
 	return best.Height, nil
 }
 
 // handleGetBlockHash implements the getblockhash command.
-func handleGetBlockHash(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBlockHash(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetBlockHashCmd)
 	hash, err := s.cfg.Chain.BlockHashByHeight(c.Index)
 	if err != nil {
@@ -2021,7 +2040,7 @@ func handleGetBlockHash(_ context.Context, s *rpcServer, cmd interface{}) (inter
 }
 
 // handleGetBlockHeader implements the getblockheader command.
-func handleGetBlockHeader(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBlockHeader(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetBlockHeaderCmd)
 
 	// Fetch the header from chain.
@@ -2106,7 +2125,7 @@ func handleGetBlockHeader(_ context.Context, s *rpcServer, cmd interface{}) (int
 }
 
 // handleGetBlockSubsidy implements the getblocksubsidy command.
-func handleGetBlockSubsidy(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetBlockSubsidy(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetBlockSubsidyCmd)
 
 	height := c.Height
@@ -2128,7 +2147,7 @@ func handleGetBlockSubsidy(_ context.Context, s *rpcServer, cmd interface{}) (in
 }
 
 // handleGetChainTips implements the getchaintips command.
-func handleGetChainTips(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetChainTips(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	chainTips := s.cfg.Chain.ChainTips()
 	result := make([]types.GetChainTipsResult, 0, len(chainTips))
 	for _, tip := range chainTips {
@@ -2143,38 +2162,38 @@ func handleGetChainTips(_ context.Context, s *rpcServer, cmd interface{}) (inter
 }
 
 // handleGetCoinSupply implements the getcoinsupply command.
-func handleGetCoinSupply(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetCoinSupply(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return s.cfg.Chain.BestSnapshot().TotalSubsidy, nil
 }
 
 // handleGetConnectionCount implements the getconnectioncount command.
-func handleGetConnectionCount(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetConnectionCount(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return s.cfg.ConnMgr.ConnectedCount(), nil
 }
 
 // handleGetCurrentNet implements the getcurrentnet command.
-func handleGetCurrentNet(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetCurrentNet(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return s.cfg.ChainParams.Net, nil
 }
 
 // handleGetDifficulty implements the getdifficulty command.
-func handleGetDifficulty(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetDifficulty(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	best := s.cfg.Chain.BestSnapshot()
 	return getDifficultyRatio(best.Bits, s.cfg.ChainParams), nil
 }
 
 // handleGetGenerate implements the getgenerate command.
-func handleGetGenerate(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetGenerate(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return s.cfg.CPUMiner.IsMining(), nil
 }
 
 // handleGetHashesPerSec implements the gethashespersec command.
-func handleGetHashesPerSec(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetHashesPerSec(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	return int64(s.cfg.CPUMiner.HashesPerSecond()), nil
 }
 
 // handleGetCFilter implements the getcfilter command.
-func handleGetCFilter(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetCFilter(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	cfIndex := s.cfg.SyncMgr.CFIndex()
 	if cfIndex == nil {
 		return nil, &dcrjson.RPCError{
@@ -2212,12 +2231,12 @@ func handleGetCFilter(_ context.Context, s *rpcServer, cmd interface{}) (interfa
 		}
 	}
 
-	rpcsLog.Debugf("Found committed filter for %v", hash)
+	log.Debugf("Found committed filter for %v", hash)
 	return hex.EncodeToString(filterBytes), nil
 }
 
 // handleGetCFilterHeader implements the getcfilterheader command.
-func handleGetCFilterHeader(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetCFilterHeader(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	cfIndex := s.cfg.SyncMgr.CFIndex()
 	if cfIndex == nil {
 		return nil, &dcrjson.RPCError{
@@ -2263,7 +2282,7 @@ func handleGetCFilterHeader(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleGetHeaders implements the getheaders command.
-func handleGetHeaders(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetHeaders(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetHeadersCmd)
 	blockLocators, err := decodeHashes(c.BlockLocators)
 	if err != nil {
@@ -2308,7 +2327,7 @@ func handleGetHeaders(_ context.Context, s *rpcServer, cmd interface{}) (interfa
 }
 
 // handleGetCFilterV2 implements the getcfilterv2 command.
-func handleGetCFilterV2(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetCFilterV2(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetCFilterV2Cmd)
 	hash, err := chainhash.NewHashFromStr(c.BlockHash)
 	if err != nil {
@@ -2346,28 +2365,28 @@ func handleGetCFilterV2(_ context.Context, s *rpcServer, cmd interface{}) (inter
 
 // handleGetInfo implements the getinfo command. We only return the fields
 // that are not related to wallet functionality.
-func handleGetInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	best := s.cfg.Chain.BestSnapshot()
 	ret := &types.InfoChainResult{
 		Version: int32(1000000*version.Major + 10000*version.Minor +
 			100*version.Patch),
-		ProtocolVersion: int32(maxProtocolVersion),
+		ProtocolVersion: int32(s.cfg.MaxProtocolVersion),
 		Blocks:          best.Height,
 		TimeOffset:      int64(s.cfg.TimeSource.Offset().Seconds()),
 		Connections:     s.cfg.ConnMgr.ConnectedCount(),
-		Proxy:           cfg.Proxy,
+		Proxy:           s.cfg.Proxy,
 		Difficulty:      getDifficultyRatio(best.Bits, s.cfg.ChainParams),
-		TestNet:         cfg.TestNet,
-		RelayFee:        cfg.minRelayTxFee.ToCoin(),
-		AddrIndex:       cfg.AddrIndex,
-		TxIndex:         cfg.TxIndex,
+		TestNet:         s.cfg.TestNet,
+		RelayFee:        s.cfg.MinRelayTxFee.ToCoin(),
+		AddrIndex:       s.cfg.AddrIndex != nil,
+		TxIndex:         s.cfg.TxIndex != nil,
 	}
 
 	return ret, nil
 }
 
 // handleGetMempoolInfo implements the getmempoolinfo command.
-func handleGetMempoolInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetMempoolInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	mempoolTxns := s.cfg.TxMemPool.TxDescs()
 
 	var numBytes int64
@@ -2385,7 +2404,7 @@ func handleGetMempoolInfo(_ context.Context, s *rpcServer, cmd interface{}) (int
 
 // handleGetMiningInfo implements the getmininginfo command. We only return the
 // fields that are not related to wallet functionality.
-func handleGetMiningInfo(ctx context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetMiningInfo(ctx context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	// Create a default getnetworkhashps command to use defaults and make
 	// use of the existing getnetworkhashps handler.
 	gnhpsCmd := types.NewGetNetworkHashPSCmd(nil, nil)
@@ -2418,13 +2437,13 @@ func handleGetMiningInfo(ctx context.Context, s *rpcServer, cmd interface{}) (in
 		HashesPerSec:     int64(s.cfg.CPUMiner.HashesPerSecond()),
 		NetworkHashPS:    networkHashesPerSec,
 		PooledTx:         uint64(s.cfg.TxMemPool.Count()),
-		TestNet:          cfg.TestNet,
+		TestNet:          s.cfg.TestNet,
 	}
 	return &result, nil
 }
 
 // handleGetNetTotals implements the getnettotals command.
-func handleGetNetTotals(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetNetTotals(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	totalBytesRecv, totalBytesSent := s.cfg.ConnMgr.NetTotals()
 	reply := &types.GetNetTotalsResult{
 		TotalBytesRecv: totalBytesRecv,
@@ -2435,7 +2454,7 @@ func handleGetNetTotals(_ context.Context, s *rpcServer, cmd interface{}) (inter
 }
 
 // handleGetNetworkHashPS implements the getnetworkhashps command.
-func handleGetNetworkHashPS(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetNetworkHashPS(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	// Note: All valid error return paths should return an int64.  Literal
 	// zeros are inferred as int, and won't coerce to int64 because the
 	// return value is an interface{}.
@@ -2483,7 +2502,7 @@ func handleGetNetworkHashPS(_ context.Context, s *rpcServer, cmd interface{}) (i
 	if startHeight < 0 {
 		startHeight = 0
 	}
-	rpcsLog.Debugf("Calculating network hashes per second from %d to %d",
+	log.Debugf("Calculating network hashes per second from %d to %d",
 		startHeight, endHeight)
 
 	// Find the min and max block timestamps as well as calculate the total
@@ -2532,8 +2551,7 @@ func handleGetNetworkHashPS(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleGetNetworkInfo implements the getnetworkinfo command.
-func handleGetNetworkInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
-	networks := cfg.generateNetworkInfo()
+func handleGetNetworkInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	lAddrs := s.cfg.AddrManager.LocalAddresses()
 	localAddrs := make([]types.LocalAddressesResult, len(lAddrs))
 	for idx, entry := range lAddrs {
@@ -2547,12 +2565,12 @@ func handleGetNetworkInfo(_ context.Context, s *rpcServer, cmd interface{}) (int
 	info := types.GetNetworkInfoResult{
 		Version: int32(1000000*version.Major + 10000*version.Minor +
 			100*version.Patch),
-		SubVersion:      userAgentVersion,
-		ProtocolVersion: int32(maxProtocolVersion),
+		SubVersion:      s.cfg.UserAgentVersion,
+		ProtocolVersion: int32(s.cfg.MaxProtocolVersion),
 		TimeOffset:      int64(s.cfg.TimeSource.Offset().Seconds()),
 		Connections:     s.cfg.ConnMgr.ConnectedCount(),
-		RelayFee:        cfg.minRelayTxFee.ToCoin(),
-		Networks:        networks,
+		RelayFee:        s.cfg.MinRelayTxFee.ToCoin(),
+		Networks:        s.cfg.NetInfo,
 		LocalAddresses:  localAddrs,
 		LocalServices:   fmt.Sprintf("%016x", uint64(s.cfg.Services)),
 	}
@@ -2561,7 +2579,7 @@ func handleGetNetworkInfo(_ context.Context, s *rpcServer, cmd interface{}) (int
 }
 
 // handleGetPeerInfo implements the getpeerinfo command.
-func handleGetPeerInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetPeerInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	peers := s.cfg.ConnMgr.ConnectedPeers()
 	syncPeerID := s.cfg.SyncMgr.SyncPeerID()
 	infos := make([]*types.GetPeerInfoResult, 0, len(peers))
@@ -2599,7 +2617,7 @@ func handleGetPeerInfo(_ context.Context, s *rpcServer, cmd interface{}) (interf
 }
 
 // handleGetRawMempool implements the getrawmempool command.
-func handleGetRawMempool(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetRawMempool(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetRawMempoolCmd)
 
 	// Choose the type to filter the results by based on the provided param.
@@ -2675,7 +2693,7 @@ func handleGetRawMempool(_ context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handleGetRawTransaction implements the getrawtransaction command.
-func handleGetRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetRawTransaction(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetRawTransactionCmd)
 
 	// Convert the provided transaction hash hex to a Hash.
@@ -2759,7 +2777,7 @@ func handleGetRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (
 			// string and it would result in returning an empty
 			// string to the client instead of nothing (nil) in the
 			// case of an error.
-			mtxHex, err := messageToHex(tx.MsgTx())
+			mtxHex, err := s.messageToHex(tx.MsgTx())
 			if err != nil {
 				return nil, err
 			}
@@ -2788,7 +2806,7 @@ func handleGetRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (
 		confirmations = 1 + s.cfg.Chain.BestSnapshot().Height - blkHeight
 	}
 
-	rawTxn, err := createTxRawResult(s.cfg.ChainParams, mtx, txHash.String(),
+	rawTxn, err := s.createTxRawResult(s.cfg.ChainParams, mtx, txHash.String(),
 		blkIndex, blkHeader, blkHashStr, blkHeight, confirmations)
 	if err != nil {
 		return nil, err
@@ -2797,12 +2815,12 @@ func handleGetRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (
 }
 
 // handleGetStakeDifficulty implements the getstakedifficulty command.
-func handleGetStakeDifficulty(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetStakeDifficulty(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	chain := s.cfg.Chain
 	best := chain.BestSnapshot()
 	blockHeader, err := chain.HeaderByHeight(best.Height)
 	if err != nil {
-		rpcsLog.Errorf("Error getting block: %v", err)
+		log.Errorf("Error getting block: %v", err)
 		return nil, &dcrjson.RPCError{
 			Code:    dcrjson.ErrRPCDifficulty,
 			Message: "Error getting stake difficulty: " + err.Error(),
@@ -2844,7 +2862,7 @@ func convertVersionMap(m map[int]int) []types.VersionCount {
 }
 
 // handleGetStakeVersionInfo implements the getstakeversioninfo command.
-func handleGetStakeVersionInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetStakeVersionInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetStakeVersionInfoCmd)
 
 	chain := s.cfg.Chain
@@ -2923,7 +2941,7 @@ func handleGetStakeVersionInfo(_ context.Context, s *rpcServer, cmd interface{})
 }
 
 // handleGetStakeVersions implements the getstakeversions command.
-func handleGetStakeVersions(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetStakeVersions(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetStakeVersionsCmd)
 
 	hash, err := chainhash.NewHashFromStr(c.Hash)
@@ -2965,7 +2983,7 @@ func handleGetStakeVersions(_ context.Context, s *rpcServer, cmd interface{}) (i
 }
 
 // handleGetTicketPoolValue implements the getticketpoolvalue command.
-func handleGetTicketPoolValue(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetTicketPoolValue(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	amt, err := s.cfg.Chain.TicketPoolValue()
 	if err != nil {
 		return nil, rpcInternalError(err.Error(),
@@ -2976,7 +2994,7 @@ func handleGetTicketPoolValue(_ context.Context, s *rpcServer, cmd interface{})
 }
 
 // handleGetVoteInfo implements the getvoteinfo command.
-func handleGetVoteInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetVoteInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetVoteInfoCmd)
 
 	// Shorter versions of some parameters for convenience.
@@ -3101,7 +3119,7 @@ func bigToLEUint256(n *big.Int) [uint256Size]byte {
 }
 
 // handleGetTxOut handles gettxout commands.
-func handleGetTxOut(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetTxOut(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.GetTxOutCmd)
 
 	// Convert the provided transaction hash hex to a Hash.
@@ -3213,7 +3231,7 @@ func handleGetTxOut(_ context.Context, s *rpcServer, cmd interface{}) (interface
 }
 
 // handleGetTxOutSetInfo returns statistics on the current unspent transaction output set.
-func handleGetTxOutSetInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetTxOutSetInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	best := s.cfg.Chain.BestSnapshot()
 	stats, err := s.cfg.Chain.FetchUtxoStats()
 	if err != nil {
@@ -3263,7 +3281,7 @@ func getWorkTemplateKey(header *wire.BlockHeader) [merkleRootPairSize]byte {
 // generating and returning work to the caller.
 //
 // This function MUST be called with the RPC workstate locked.
-func handleGetWorkRequest(s *rpcServer) (interface{}, error) {
+func handleGetWorkRequest(s *Server) (interface{}, error) {
 	// Prune old templates and wait for updated templates when the current best
 	// chain changes.
 	state := s.workState
@@ -3367,7 +3385,7 @@ func handleGetWorkRequest(s *rpcServer) (interface{}, error) {
 // the calling submitting work to be verified and processed.
 //
 // This function MUST be called with the RPC workstate locked.
-func handleGetWorkSubmission(_ context.Context, s *rpcServer, hexData string) (interface{}, error) {
+func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (interface{}, error) {
 	// Ensure the provided data is sane.
 	if len(hexData)%2 != 0 {
 		hexData = "0" + hexData
@@ -3402,7 +3420,7 @@ func handleGetWorkSubmission(_ context.Context, s *rpcServer, hexData string) (i
 			return false, rpcInternalError(err.Error(), context)
 		}
 
-		rpcsLog.Errorf("Block submitted via getwork does not meet the "+
+		log.Errorf("Block submitted via getwork does not meet the "+
 			"required proof of work: %v", err)
 		return false, nil
 	}
@@ -3413,7 +3431,7 @@ func handleGetWorkSubmission(_ context.Context, s *rpcServer, hexData string) (i
 	templateKey := getWorkTemplateKey(&submittedHeader)
 	templateBlock, ok := s.workState.templatePool[templateKey]
 	if !ok || templateBlock == nil {
-		rpcsLog.Errorf("Block submitted via getwork has no matching template "+
+		log.Errorf("Block submitted via getwork has no matching template "+
 			"for merkle root %s, stake root %s",
 			submittedHeader.MerkleRoot, submittedHeader.StakeRoot)
 		return false, nil
@@ -3438,24 +3456,24 @@ func handleGetWorkSubmission(_ context.Context, s *rpcServer, hexData string) (i
 			return false, rpcInternalError(err.Error(), context)
 		}
 
-		rpcsLog.Infof("Block submitted via getwork rejected: %v", err)
+		log.Infof("Block submitted via getwork rejected: %v", err)
 		return false, nil
 	}
 
 	if isOrphan {
-		rpcsLog.Infof("Block submitted via getwork rejected: an orphan "+
+		log.Infof("Block submitted via getwork rejected: an orphan "+
 			"building on parent %v", block.MsgBlock().Header.PrevBlock)
 		return false, nil
 	}
 
 	// The block was accepted.
-	rpcsLog.Infof("Block submitted via getwork accepted: %s (height %d)",
+	log.Infof("Block submitted via getwork accepted: %s (height %d)",
 		block.Hash(), msgBlock.Header.Height)
 	return true, nil
 }
 
 // handleGetWork implements the getwork command.
-func handleGetWork(ctx context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleGetWork(ctx context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	if s.cfg.CPUMiner.IsMining() {
 		return nil, rpcMiscError("getwork polling is disallowed " +
 			"while CPU mining is enabled. Please disable CPU " +
@@ -3464,7 +3482,7 @@ func handleGetWork(ctx context.Context, s *rpcServer, cmd interface{}) (interfac
 
 	// Respond with an error if there are no addresses to pay the created
 	// blocks to.
-	if len(cfg.miningAddrs) == 0 {
+	if len(s.cfg.MiningAddrs) == 0 {
 		return nil, rpcInternalError("No payment addresses specified "+
 			"via --miningaddr", "Configuration")
 	}
@@ -3472,7 +3490,7 @@ func handleGetWork(ctx context.Context, s *rpcServer, cmd interface{}) (interfac
 	// Return an error if there are no peers connected since there is no way to
 	// relay a found block or receive transactions to work on unless
 	// unsynchronized mining has specifically been allowed.
-	if !cfg.AllowUnsyncedMining && s.cfg.ConnMgr.ConnectedCount() == 0 {
+	if !s.cfg.AllowUnsyncedMining && s.cfg.ConnMgr.ConnectedCount() == 0 {
 		return nil, &dcrjson.RPCError{
 			Code:    dcrjson.ErrRPCClientNotConnected,
 			Message: "Decred is not connected",
@@ -3482,7 +3500,7 @@ func handleGetWork(ctx context.Context, s *rpcServer, cmd interface{}) (interfac
 	// No point in generating or accepting work before the chain is synced
 	// unless unsynchronized mining has specifically been allowed.
 	bestHeight := s.cfg.Chain.BestSnapshot().Height
-	if !cfg.AllowUnsyncedMining && bestHeight != 0 && !s.cfg.Chain.IsCurrent() {
+	if !s.cfg.AllowUnsyncedMining && bestHeight != 0 && !s.cfg.Chain.IsCurrent() {
 		return nil, &dcrjson.RPCError{
 			Code:    dcrjson.ErrRPCClientInInitialDownload,
 			Message: "Decred is downloading blocks...",
@@ -3508,7 +3526,7 @@ func handleGetWork(ctx context.Context, s *rpcServer, cmd interface{}) (interfac
 }
 
 // handleHelp implements the help command.
-func handleHelp(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleHelp(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.HelpCmd)
 
 	// Provide a usage overview of all commands when no specific command
@@ -3544,7 +3562,7 @@ func handleHelp(_ context.Context, s *rpcServer, cmd interface{}) (interface{},
 }
 
 // handleLiveTickets implements the livetickets command.
-func handleLiveTickets(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleLiveTickets(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	lt, err := s.cfg.Chain.LiveTickets()
 	if err != nil {
 		return nil, rpcInternalError("Could not get live tickets "+
@@ -3560,7 +3578,7 @@ func handleLiveTickets(_ context.Context, s *rpcServer, cmd interface{}) (interf
 }
 
 // handleMissedTickets implements the missedtickets command.
-func handleMissedTickets(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleMissedTickets(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	mt, err := s.cfg.Chain.MissedTickets()
 	if err != nil {
 		return nil, rpcInternalError("Could not get missed tickets "+
@@ -3576,7 +3594,7 @@ func handleMissedTickets(_ context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handlePing implements the ping command.
-func handlePing(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handlePing(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	// Ask server to ping \o_
 	nonce, err := wire.RandomUint64()
 	if err != nil {
@@ -3589,7 +3607,7 @@ func handlePing(_ context.Context, s *rpcServer, cmd interface{}) (interface{},
 }
 
 // handleRegenTemplate implements the regentemplate command.
-func handleRegenTemplate(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleRegenTemplate(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	bg := s.cfg.BgBlkTmplGenerator()
 	if bg == nil {
 		return nil, rpcInternalError("Node is not configured for mining", "")
@@ -3615,7 +3633,7 @@ type retrievedTx struct {
 // fetchInputTxos fetches the outpoints from all transactions referenced by the
 // inputs to the passed transaction by checking the transaction mempool first
 // then the transaction index for those already mined into blocks.
-func fetchInputTxos(s *rpcServer, tx *wire.MsgTx) (map[wire.OutPoint]wire.TxOut, error) {
+func fetchInputTxos(s *Server, tx *wire.MsgTx) (map[wire.OutPoint]wire.TxOut, error) {
 	mp := s.cfg.TxMemPool
 	originOutputs := make(map[wire.OutPoint]wire.TxOut)
 	voteTx := stake.IsSSGen(tx)
@@ -3687,7 +3705,7 @@ func fetchInputTxos(s *rpcServer, tx *wire.MsgTx) (map[wire.OutPoint]wire.TxOut,
 
 // createVinListPrevOut returns a slice of JSON objects for the inputs of the
 // passed transaction.
-func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.Params, vinExtra bool, filterAddrMap map[string]struct{}) ([]types.VinPrevOut, error) {
+func createVinListPrevOut(s *Server, mtx *wire.MsgTx, chainParams *chaincfg.Params, vinExtra bool, filterAddrMap map[string]struct{}) ([]types.VinPrevOut, error) {
 	// Coinbase transactions only have a single txin by definition.
 	if standalone.IsCoinBaseTx(mtx) {
 		// Only include the transaction if the filter map is empty
@@ -3828,7 +3846,7 @@ func createVinListPrevOut(s *rpcServer, mtx *wire.MsgTx, chainParams *chaincfg.P
 // fetchMempoolTxnsForAddress queries the address index for all unconfirmed
 // transactions that involve the provided address.  The results will be limited
 // by the number to skip and the number requested.
-func fetchMempoolTxnsForAddress(s *rpcServer, addr dcrutil.Address, numToSkip, numRequested uint32) ([]*dcrutil.Tx, uint32) {
+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)
@@ -3847,7 +3865,7 @@ func fetchMempoolTxnsForAddress(s *rpcServer, addr dcrutil.Address, numToSkip, n
 }
 
 // handleSearchRawTransactions implements the searchrawtransactions command.
-func handleSearchRawTransactions(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+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 {
@@ -4010,7 +4028,7 @@ func handleSearchRawTransactions(_ context.Context, s *rpcServer, cmd interface{
 
 		// Serialize the transaction first and convert to hex when the
 		// retrieved transaction is the deserialized structure.
-		hexTxns[i], err = messageToHex(rtx.tx.MsgTx())
+		hexTxns[i], err = s.messageToHex(rtx.tx.MsgTx())
 		if err != nil {
 			return nil, err
 		}
@@ -4116,7 +4134,7 @@ func handleSearchRawTransactions(_ context.Context, s *rpcServer, cmd interface{
 }
 
 // handleSendRawTransaction implements the sendrawtransaction command.
-func handleSendRawTransaction(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleSendRawTransaction(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.SendRawTransactionCmd)
 	// Deserialize and send off to tx relay
 
@@ -4151,7 +4169,7 @@ func handleSendRawTransaction(_ context.Context, s *rpcServer, cmd interface{})
 		if errors.As(err, &rErr) {
 			err = fmt.Errorf("rejected transaction %v: %v", tx.Hash(),
 				err)
-			rpcsLog.Debugf("%v", err)
+			log.Debugf("%v", err)
 			if mempool.IsErrorCode(rErr, mempool.ErrDuplicate) {
 				// This is an actual exact duplicate tx, so
 				// return the specific duplicate tx error.
@@ -4164,11 +4182,16 @@ func handleSendRawTransaction(_ context.Context, s *rpcServer, cmd interface{})
 
 		err = fmt.Errorf("failed to process transaction %v: %v",
 			tx.Hash(), err)
-		rpcsLog.Errorf("%v", err)
+		log.Errorf("%v", err)
 		return nil, rpcDeserializationError("rejected: %v", err)
 	}
 
-	s.cfg.PeerNotifier.AnnounceNewTransactions(acceptedTxs)
+	// Generate and relay inventory vectors for all newly accepted
+	// transactions.
+	s.cfg.ConnMgr.RelayTransactions(acceptedTxs)
+
+	// Notify websocket clients of all newly accepted transactions.
+	s.NotifyNewTransactions(acceptedTxs)
 
 	// Keep track of all the regular sendrawtransaction request txns so that
 	// they can be rebroadcast if they don't make their way into a block.
@@ -4184,7 +4207,7 @@ func handleSendRawTransaction(_ context.Context, s *rpcServer, cmd interface{})
 }
 
 // handleSetGenerate implements the setgenerate command.
-func handleSetGenerate(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleSetGenerate(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.SetGenerateCmd)
 
 	// Disable generation regardless of the provided generate flag if the
@@ -4207,7 +4230,7 @@ func handleSetGenerate(_ context.Context, s *rpcServer, cmd interface{}) (interf
 	} else {
 		// Respond with an error if there are no addresses to pay the
 		// created blocks to.
-		if len(cfg.miningAddrs) == 0 {
+		if len(s.cfg.MiningAddrs) == 0 {
 			return nil, rpcInternalError("No payment addresses "+
 				"specified via --miningaddr", "Configuration")
 		}
@@ -4218,7 +4241,7 @@ func handleSetGenerate(_ context.Context, s *rpcServer, cmd interface{}) (interf
 }
 
 // handleStop implements the stop command.
-func handleStop(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleStop(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	select {
 	case s.requestProcessShutdown <- struct{}{}:
 	default:
@@ -4227,7 +4250,7 @@ func handleStop(_ context.Context, s *rpcServer, cmd interface{}) (interface{},
 }
 
 // handleSubmitBlock implements the submitblock command.
-func handleSubmitBlock(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleSubmitBlock(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.SubmitBlockCmd)
 
 	// Deserialize the submitted block.
@@ -4250,7 +4273,7 @@ func handleSubmitBlock(_ context.Context, s *rpcServer, cmd interface{}) (interf
 		return fmt.Sprintf("rejected: %v", err), nil
 	}
 
-	rpcsLog.Infof("Accepted block %s via submitblock", block.Hash())
+	log.Infof("Accepted block %s via submitblock", block.Hash())
 	return nil, nil
 }
 
@@ -4336,7 +4359,7 @@ func stdDev(s []dcrutil.Amount) dcrutil.Amount {
 
 // feeInfoForMempool returns the fee information for the passed tx type in the
 // memory pool.
-func feeInfoForMempool(s *rpcServer, txType stake.TxType) *types.FeeInfoMempool {
+func feeInfoForMempool(s *Server, txType stake.TxType) *types.FeeInfoMempool {
 	txDs := s.cfg.TxMemPool.TxDescs()
 	ticketFees := make([]dcrutil.Amount, 0, len(txDs))
 	for _, txD := range txDs {
@@ -4374,7 +4397,7 @@ func calcFeePerKb(tx *dcrutil.Tx) dcrutil.Amount {
 
 // feeInfoForBlock fetches the ticket fee information for a given tx type in a
 // block.
-func ticketFeeInfoForBlock(s *rpcServer, height int64, txType stake.TxType) (*types.FeeInfoBlock, error) {
+func ticketFeeInfoForBlock(s *Server, height int64, txType stake.TxType) (*types.FeeInfoBlock, error) {
 	bl, err := s.cfg.Chain.BlockByHeight(height)
 	if err != nil {
 		return nil, err
@@ -4427,7 +4450,7 @@ func ticketFeeInfoForBlock(s *rpcServer, height int64, txType stake.TxType) (*ty
 
 // ticketFeeInfoForRange fetches the ticket fee information for a given range
 // from [start, end).
-func ticketFeeInfoForRange(s *rpcServer, start int64, end int64, txType stake.TxType) (*types.FeeInfoWindow, error) {
+func ticketFeeInfoForRange(s *Server, start int64, end int64, txType stake.TxType) (*types.FeeInfoWindow, error) {
 	hashes, err := s.cfg.Chain.HeightRange(start, end)
 	if err != nil {
 		return nil, err
@@ -4472,7 +4495,7 @@ func ticketFeeInfoForRange(s *rpcServer, start int64, end int64, txType stake.Tx
 }
 
 // handleTicketFeeInfo implements the ticketfeeinfo command.
-func handleTicketFeeInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleTicketFeeInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.TicketFeeInfoCmd)
 
 	bestHeight := s.cfg.Chain.BestSnapshot().Height
@@ -4553,7 +4576,7 @@ func handleTicketFeeInfo(_ context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handleTicketsForAddress implements the ticketsforaddress command.
-func handleTicketsForAddress(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleTicketsForAddress(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.TicketsForAddressCmd)
 
 	// Decode the provided address.  This also ensures the network encoded
@@ -4582,7 +4605,7 @@ func handleTicketsForAddress(_ context.Context, s *rpcServer, cmd interface{}) (
 }
 
 // handleTicketVWAP implements the ticketvwap command.
-func handleTicketVWAP(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleTicketVWAP(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.TicketVWAPCmd)
 
 	// The default VWAP is for the past WorkDiffWindows * WorkDiffWindowSize
@@ -4640,7 +4663,7 @@ func handleTicketVWAP(_ context.Context, s *rpcServer, cmd interface{}) (interfa
 }
 
 // handleTxFeeInfo implements the txfeeinfo command.
-func handleTxFeeInfo(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleTxFeeInfo(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.TxFeeInfoCmd)
 
 	bestHeight := s.cfg.Chain.BestSnapshot().Height
@@ -4725,7 +4748,7 @@ func handleTxFeeInfo(_ context.Context, s *rpcServer, cmd interface{}) (interfac
 }
 
 // handleValidateAddress implements the validateaddress command.
-func handleValidateAddress(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleValidateAddress(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.ValidateAddressCmd)
 	result := types.ValidateAddressChainResult{}
 	addr, err := dcrutil.DecodeAddress(c.Address, s.cfg.ChainParams)
@@ -4739,20 +4762,20 @@ func handleValidateAddress(_ context.Context, s *rpcServer, cmd interface{}) (in
 	return result, nil
 }
 
-func verifyChain(_ context.Context, s *rpcServer, level, depth int64) error {
+func verifyChain(_ context.Context, s *Server, level, depth int64) error {
 	best := s.cfg.Chain.BestSnapshot()
 	finishHeight := best.Height - depth
 	if finishHeight < 0 {
 		finishHeight = 0
 	}
-	rpcsLog.Infof("Verifying chain for %d blocks at level %d",
+	log.Infof("Verifying chain for %d blocks at level %d",
 		best.Height-finishHeight, level)
 
 	for height := best.Height; height > finishHeight; height-- {
 		// Level 0 just looks up the block.
 		block, err := s.cfg.Chain.BlockByHeight(height)
 		if err != nil {
-			rpcsLog.Errorf("Verify is unable to fetch block at "+
+			log.Errorf("Verify is unable to fetch block at "+
 				"height %d: %v", height, err)
 			return err
 		}
@@ -4762,20 +4785,20 @@ func verifyChain(_ context.Context, s *rpcServer, level, depth int64) error {
 			err := blockchain.CheckBlockSanity(block, s.cfg.TimeSource,
 				s.cfg.ChainParams)
 			if err != nil {
-				rpcsLog.Errorf("Verify is unable to validate "+
+				log.Errorf("Verify is unable to validate "+
 					"block at hash %v height %d: %v",
 					block.Hash(), height, err)
 				return err
 			}
 		}
 	}
-	rpcsLog.Infof("Chain verify completed successfully")
+	log.Infof("Chain verify completed successfully")
 
 	return nil
 }
 
 // handleVerifyChain implements the verifychain command.
-func handleVerifyChain(ctx context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleVerifyChain(ctx context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.VerifyChainCmd)
 
 	var checkLevel, checkDepth int64
@@ -4791,7 +4814,7 @@ func handleVerifyChain(ctx context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handleVerifyMessage implements the verifymessage command.
-func handleVerifyMessage(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleVerifyMessage(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	c := cmd.(*types.VerifyMessageCmd)
 
 	// Decode the provided address.  This also ensures the network encoded with
@@ -4854,7 +4877,7 @@ func handleVerifyMessage(_ context.Context, s *rpcServer, cmd interface{}) (inte
 }
 
 // handleVersion implements the version command.
-func handleVersion(_ context.Context, s *rpcServer, cmd interface{}) (interface{}, error) {
+func handleVersion(_ context.Context, s *Server, cmd interface{}) (interface{}, error) {
 	runtimeVer := strings.Replace(runtime.Version(), ".", "-", -1)
 	buildMeta := version.NormalizeBuildString(runtimeVer)
 	build := version.NormalizeBuildString(version.BuildMetadata)
@@ -4880,9 +4903,9 @@ func handleVersion(_ context.Context, s *rpcServer, cmd interface{}) (interface{
 	return result, nil
 }
 
-// rpcServer provides a concurrent safe RPC server to a chain server.
-type rpcServer struct {
-	cfg                    rpcserverConfig
+// Server provides a concurrent safe RPC server to a chain server.
+type Server struct {
+	cfg                    Config
 	authsha                [sha256.Size]byte
 	limitauthsha           [sha256.Size]byte
 	ntfnMgr                *wsNotificationManager
@@ -4898,7 +4921,7 @@ type rpcServer struct {
 // httpStatusLine returns a response Status-Line (RFC 2616 Section 6.1) for the
 // given request and response status code.  This function was lifted and
 // adapted from the standard library HTTP server code since it's not exported.
-func (s *rpcServer) httpStatusLine(req *http.Request, code int) string {
+func (s *Server) httpStatusLine(req *http.Request, code int) string {
 	// Fast path:
 	key := code
 	proto11 := req.ProtoAtLeast(1, 1)
@@ -4935,7 +4958,7 @@ func (s *rpcServer) httpStatusLine(req *http.Request, code int) string {
 // writeHTTPResponseHeaders writes the necessary response headers prior to
 // writing an HTTP body given a request to use for protocol negotiation,
 // headers to write, a status code, and a writer.
-func (s *rpcServer) writeHTTPResponseHeaders(req *http.Request, headers http.Header, code int, w io.Writer) error {
+func (s *Server) writeHTTPResponseHeaders(req *http.Request, headers http.Header, code int, w io.Writer) error {
 	_, err := io.WriteString(w, s.httpStatusLine(req, code))
 	if err != nil {
 		return err
@@ -4951,45 +4974,93 @@ func (s *rpcServer) writeHTTPResponseHeaders(req *http.Request, headers http.Hea
 }
 
 // shutdown terminates the processes of the rpc server.
-func (s *rpcServer) shutdown() error {
-	rpcsLog.Warnf("RPC server shutting down")
+func (s *Server) shutdown() error {
+	log.Warnf("RPC server shutting down")
 	for _, listener := range s.cfg.Listeners {
 		err := listener.Close()
 		if err != nil {
-			rpcsLog.Errorf("Problem shutting down rpc: %v", err)
+			log.Errorf("Problem shutting down rpc: %v", err)
 			return err
 		}
 	}
 	s.wg.Wait()
-	rpcsLog.Infof("RPC server shutdown complete")
+	log.Infof("RPC server shutdown complete")
 	return nil
 }
 
 // RequestedProcessShutdown returns a channel that is sent to when an
 // authorized RPC client requests the process to shutdown.  If the request can
 // not be read immediately, it is dropped.
-func (s *rpcServer) RequestedProcessShutdown() <-chan struct{} {
+func (s *Server) RequestedProcessShutdown() <-chan struct{} {
 	return s.requestProcessShutdown
 }
 
 // NotifyNewTransactions notifies both websocket and getblocktemplate long
 // poll clients of the passed transactions.  This function should be called
 // whenever new transactions are added to the mempool.
-func (s *rpcServer) NotifyNewTransactions(txns []*dcrutil.Tx) {
+func (s *Server) NotifyNewTransactions(txns []*dcrutil.Tx) {
 	for _, tx := range txns {
 		// Notify websocket clients about mempool transactions.
 		s.ntfnMgr.NotifyMempoolTx(tx, true)
 	}
 }
 
+// NotifyWork notifies websocket clients that have registered for new mining
+// work.
+func (s *Server) NotifyWork(templateNtfn *mining.TemplateNtfn) {
+	s.ntfnMgr.NotifyWork(templateNtfn)
+}
+
+// NotifyStakeDifficulty notifies websocket clients that have registered for
+// stake difficulty updates.
+func (s *Server) NotifyStakeDifficulty(stnd *StakeDifficultyNtfnData) {
+	s.ntfnMgr.NotifyStakeDifficulty(stnd)
+}
+
+// NotifyNewTickets notifies websocket clients that have registered for maturing
+// ticket updates.
+func (s *Server) NotifyNewTickets(tnd *blockchain.TicketNotificationsData) {
+	s.ntfnMgr.NotifyNewTickets(tnd)
+}
+
+// NotifyBlockConnected notifies websocket clients that have registered for
+// block updates when a block is connected to the main chain.
+func (s *Server) NotifyBlockConnected(block *dcrutil.Block) {
+	s.ntfnMgr.NotifyBlockConnected(block)
+}
+
+// NotifySpentAndMissedTickets notifies websocket clients that have registered
+// for spent and missed ticket updates.
+func (s *Server) NotifySpentAndMissedTickets(tnd *blockchain.TicketNotificationsData) {
+	s.ntfnMgr.NotifySpentAndMissedTickets(tnd)
+}
+
+// NotifyBlockDisconnected notifies websocket clients that have registered for
+// block updates when a block is disconnected from the main chain.
+func (s *Server) NotifyBlockDisconnected(block *dcrutil.Block) {
+	s.ntfnMgr.NotifyBlockDisconnected(block)
+}
+
+// NotifyReorganization notifies websocket clients that have registered for
+// block updates when the blockchain is beginning a reorganization.
+func (s *Server) NotifyReorganization(rd *blockchain.ReorganizationNtfnsData) {
+	s.ntfnMgr.NotifyReorganization(rd)
+}
+
+// NotifyWinningTickets notifies websocket clients that have registered for
+// winning ticket updates.
+func (s *Server) NotifyWinningTickets(wtnd *WinningTicketsNtfnData) {
+	s.ntfnMgr.NotifyWinningTickets(wtnd)
+}
+
 // limitConnections responds with a 503 service unavailable and returns true if
 // adding another client would exceed the maximum allow RPC clients.
 //
 // This function is safe for concurrent access.
-func (s *rpcServer) limitConnections(w http.ResponseWriter, remoteAddr string) bool {
-	if int(atomic.LoadInt32(&s.numClients)+1) > cfg.RPCMaxClients {
-		rpcsLog.Infof("Max RPC clients exceeded [%d] - "+
-			"disconnecting client %s", cfg.RPCMaxClients,
+func (s *Server) limitConnections(w http.ResponseWriter, remoteAddr string) bool {
+	if int(atomic.LoadInt32(&s.numClients)+1) > s.cfg.RPCMaxClients {
+		log.Infof("Max RPC clients exceeded [%d] - "+
+			"disconnecting client %s", s.cfg.RPCMaxClients,
 			remoteAddr)
 		http.Error(w, "503 Too busy.  Try again later.",
 			http.StatusServiceUnavailable)
@@ -5003,7 +5074,7 @@ func (s *rpcServer) limitConnections(w http.ResponseWriter, remoteAddr string) b
 // and are tracked separately.
 //
 // This function is safe for concurrent access.
-func (s *rpcServer) incrementClients() {
+func (s *Server) incrementClients() {
 	atomic.AddInt32(&s.numClients, 1)
 }
 
@@ -5012,7 +5083,7 @@ func (s *rpcServer) incrementClients() {
 // own limits and are tracked separately.
 //
 // This function is safe for concurrent access.
-func (s *rpcServer) decrementClients() {
+func (s *Server) decrementClients() {
 	atomic.AddInt32(&s.numClients, -1)
 }
 
@@ -5026,11 +5097,11 @@ func (s *rpcServer) decrementClients() {
 // the second bool return value specifies whether the user can change the state
 // of the server (true) or whether the user is limited (false). The second is
 // always false if the first is.
-func (s *rpcServer) checkAuth(r *http.Request, require bool) (bool, bool, error) {
+func (s *Server) checkAuth(r *http.Request, require bool) (bool, bool, error) {
 	authhdr := r.Header["Authorization"]
 	if len(authhdr) <= 0 {
 		if require {
-			rpcsLog.Warnf("RPC authentication failure from %s",
+			log.Warnf("RPC authentication failure from %s",
 				r.RemoteAddr)
 			return false, false, errors.New("auth failure")
 		}
@@ -5054,7 +5125,7 @@ func (s *rpcServer) checkAuth(r *http.Request, require bool) (bool, bool, error)
 	}
 
 	// Request's auth doesn't match either user
-	rpcsLog.Warnf("RPC authentication failure from %s", r.RemoteAddr)
+	log.Warnf("RPC authentication failure from %s", r.RemoteAddr)
 	return false, false, errors.New("auth failure")
 }
 
@@ -5073,7 +5144,7 @@ type parsedRPCCmd struct {
 // JSON-RPC command and runs the appropriate handler to reply to the command.
 // Any commands which are not recognized or not implemented will return an
 // error suitable for use in replies.
-func (s *rpcServer) standardCmdResult(ctx context.Context, cmd *parsedRPCCmd) (interface{}, error) {
+func (s *Server) standardCmdResult(ctx context.Context, cmd *parsedRPCCmd) (interface{}, error) {
 	handler, ok := rpcHandlers[cmd.method]
 	if ok {
 		goto handled
@@ -5139,7 +5210,7 @@ func createMarshalledReply(rpcVersion string, id interface{}, result interface{}
 
 // processRequest determines the incoming request type (single or batched),
 // parses it and returns a marshalled response.
-func (s *rpcServer) processRequest(ctx context.Context, request *dcrjson.Request, isAdmin bool) []byte {
+func (s *Server) processRequest(ctx context.Context, request *dcrjson.Request, isAdmin bool) []byte {
 	var result interface{}
 	var jsonErr error
 
@@ -5158,7 +5229,7 @@ func (s *rpcServer) processRequest(ctx context.Context, request *dcrjson.Request
 			}
 			msg, err := createMarshalledReply(request.Jsonrpc, request.ID, result, jsonErr)
 			if err != nil {
-				rpcsLog.Errorf("Failed to marshal reply: %v", err)
+				log.Errorf("Failed to marshal reply: %v", err)
 				return nil
 			}
 			return msg
@@ -5183,14 +5254,14 @@ func (s *rpcServer) processRequest(ctx context.Context, request *dcrjson.Request
 	// Marshal the response.
 	msg, err := createMarshalledReply(request.Jsonrpc, request.ID, result, jsonErr)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal reply: %v", err)
+		log.Errorf("Failed to marshal reply: %v", err)
 		return nil
 	}
 	return msg
 }
 
 // jsonRPCRead handles reading and responding to RPC messages.
-func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *http.Request, isAdmin bool) {
+func (s *Server) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *http.Request, isAdmin bool) {
 	select {
 	case <-sCtx.Done():
 		return
@@ -5217,7 +5288,7 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 	hj, ok := w.(http.Hijacker)
 	if !ok {
 		errMsg := "webserver doesn't support hijacking"
-		rpcsLog.Warnf(errMsg)
+		log.Warnf(errMsg)
 		errCode := http.StatusInternalServerError
 		http.Error(w, strconv.Itoa(errCode)+" "+errMsg,
 			errCode)
@@ -5226,7 +5297,7 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 
 	conn, buf, err := hj.Hijack()
 	if err != nil {
-		rpcsLog.Warnf("Failed to hijack HTTP connection: %v", err)
+		log.Warnf("Failed to hijack HTTP connection: %v", err)
 		errCode := http.StatusInternalServerError
 		http.Error(w, strconv.Itoa(errCode)+" "+
 			err.Error(), errCode)
@@ -5269,7 +5340,7 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 			}
 			resp, err = dcrjson.MarshalResponse("1.0", nil, nil, jsonErr)
 			if err != nil {
-				rpcsLog.Errorf("Failed to create reply: %v", err)
+				log.Errorf("Failed to create reply: %v", err)
 			}
 		} else {
 			resp = s.processRequest(ctx, &req, isAdmin)
@@ -5292,7 +5363,7 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 			}
 			resp, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
 			if err != nil {
-				rpcsLog.Errorf("Failed to create reply: %v", err)
+				log.Errorf("Failed to create reply: %v", err)
 			}
 
 			if resp != nil {
@@ -5309,7 +5380,7 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 				}
 				resp, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
 				if err != nil {
-					rpcsLog.Errorf("Failed to marshal reply: %v", err)
+					log.Errorf("Failed to marshal reply: %v", err)
 				}
 
 				if resp != nil {
@@ -5332,7 +5403,7 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 						}
 						resp, err = dcrjson.MarshalResponse("", nil, nil, jsonErr)
 						if err != nil {
-							rpcsLog.Errorf("Failed to create reply: %v", err)
+							log.Errorf("Failed to create reply: %v", err)
 						}
 
 						if resp != nil {
@@ -5379,16 +5450,16 @@ func (s *rpcServer) jsonRPCRead(sCtx context.Context, w http.ResponseWriter, r *
 	// Write the response.
 	err = s.writeHTTPResponseHeaders(r, w.Header(), http.StatusOK, buf)
 	if err != nil {
-		rpcsLog.Error(err)
+		log.Error(err)
 		return
 	}
 	if _, err := buf.Write(msg); err != nil {
-		rpcsLog.Errorf("Failed to write marshalled reply: %v", err)
+		log.Errorf("Failed to write marshalled reply: %v", err)
 	}
 
 	// Terminate with newline to maintain compatibility with Bitcoin Core.
 	if err := buf.WriteByte('\n'); err != nil {
-		rpcsLog.Errorf("Failed to append terminating newline to reply: %v", err)
+		log.Errorf("Failed to append terminating newline to reply: %v", err)
 	}
 }
 
@@ -5399,7 +5470,7 @@ func jsonAuthFail(w http.ResponseWriter) {
 }
 
 // route sets up the endpoints of the rpc server.
-func (s *rpcServer) route(ctx context.Context) *http.Server {
+func (s *Server) route(ctx context.Context) *http.Server {
 	rpcServeMux := http.NewServeMux()
 	httpServer := &http.Server{
 		Handler: rpcServeMux,
@@ -5445,7 +5516,7 @@ func (s *rpcServer) route(ctx context.Context) *http.Server {
 		if err != nil {
 			var herr websocket.HandshakeError
 			if !errors.As(err, &herr) {
-				rpcsLog.Errorf("Unexpected websocket error: %v",
+				log.Errorf("Unexpected websocket error: %v",
 					err)
 			}
 			http.Error(w, "400 Bad Request.", http.StatusBadRequest)
@@ -5454,17 +5525,17 @@ func (s *rpcServer) route(ctx context.Context) *http.Server {
 		ws.SetPingHandler(func(payload string) error {
 			err := ws.WriteControl(websocket.PongMessage, []byte(payload),
 				time.Now().Add(time.Second))
-			rpcsLog.Debugf("ping received: len %d", len(payload))
-			rpcsLog.Tracef("ping payload: %s", payload)
+			log.Debugf("ping received: len %d", len(payload))
+			log.Tracef("ping payload: %s", payload)
 			if err != nil {
-				rpcsLog.Errorf("Failed to send pong: %v", err)
+				log.Errorf("Failed to send pong: %v", err)
 				return err
 			}
 			return nil
 		})
 		ws.SetPongHandler(func(payload string) error {
-			rpcsLog.Debugf("pong received: len %d", len(payload))
-			rpcsLog.Tracef("pong payload: %s", payload)
+			log.Debugf("pong received: len %d", len(payload))
+			log.Tracef("pong payload: %s", payload)
 			return nil
 		})
 		s.WebsocketHandler(ws, r.RemoteAddr, authenticated, isAdmin)
@@ -5474,15 +5545,15 @@ func (s *rpcServer) route(ctx context.Context) *http.Server {
 
 // Run starts the rpc server and its listeners. It blocks until the
 // provided context is cancelled.
-func (s *rpcServer) Run(ctx context.Context) {
-	rpcsLog.Trace("Starting RPC server")
+func (s *Server) Run(ctx context.Context) {
+	log.Trace("Starting RPC server")
 	server := s.route(ctx)
 	for _, listener := range s.cfg.Listeners {
 		s.wg.Add(1)
 		go func(listener net.Listener) {
-			rpcsLog.Infof("RPC server listening on %s", listener.Addr())
+			log.Infof("RPC server listening on %s", listener.Addr())
 			server.Serve(listener)
-			rpcsLog.Tracef("RPC listener done for %s", listener.Addr())
+			log.Tracef("RPC listener done for %s", listener.Addr())
 			s.wg.Done()
 		}(listener)
 	}
@@ -5490,38 +5561,13 @@ func (s *rpcServer) Run(ctx context.Context) {
 	s.ntfnMgr.Run(ctx)
 	err := s.shutdown()
 	if err != nil {
-		rpcsLog.Error(err)
+		log.Error(err)
 		return
 	}
 }
 
-// genCertPair generates a key/cert pair to the paths provided.
-func genCertPair(certFile, keyFile string, altDNSNames []string, tlsCurve elliptic.Curve) error {
-	rpcsLog.Infof("Generating TLS certificates...")
-
-	org := "dcrd autogenerated cert"
-	validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
-	cert, key, err := certgen.NewTLSCertPair(tlsCurve, org,
-		validUntil, altDNSNames)
-	if err != nil {
-		return err
-	}
-
-	// Write cert and key files.
-	if err = ioutil.WriteFile(certFile, cert, 0644); err != nil {
-		return err
-	}
-	if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
-		os.Remove(certFile)
-		return err
-	}
-
-	rpcsLog.Infof("Done generating TLS certificates")
-	return nil
-}
-
-// rpcserverConfig is a descriptor containing the RPC server configuration.
-type rpcserverConfig struct {
+// Config is a descriptor containing the RPC server configuration.
+type Config struct {
 	// Listeners defines a slice of listeners for which the RPC server will
 	// take ownership of and accept connections.  Since the RPC server takes
 	// ownership of these listeners, they will be closed when the RPC server
@@ -5536,21 +5582,18 @@ type rpcserverConfig struct {
 	// provides the RPC server with a means to do things such as add,
 	// remove, connect, disconnect, and query peers as well as other
 	// connection-related data and tasks.
-	ConnMgr rpcserver.ConnManager
+	ConnMgr ConnManager
 
 	// SyncMgr defines the sync manager for the RPC server to use.
-	SyncMgr rpcserver.SyncManager
-
-	// PeerNotifier provides an interface for server peer notifications.
-	PeerNotifier PeerNotifier
+	SyncMgr SyncManager
 
 	// These fields allow the RPC server to interface with the local block
 	// chain data and state.
 	TimeSource   blockchain.MedianTimeSource
-	Chain        rpcserver.Chain
+	Chain        Chain
 	ChainParams  *chaincfg.Params
 	DB           database.DB
-	FeeEstimator rpcserver.FeeEstimator
+	FeeEstimator FeeEstimator
 	Services     wire.ServiceFlag
 
 	// SubsidyCache defines a cache for efficient access to consensus-critical
@@ -5559,10 +5602,10 @@ type rpcserverConfig struct {
 
 	// AddrManager defines a concurrency safe address manager for caching
 	// potential peers on the network.
-	AddrManager rpcserver.AddrManager
+	AddrManager AddrManager
 
 	// Clock defines the clock for the RPC server to use.
-	Clock rpcserver.Clock
+	Clock Clock
 
 	// TxMemPool defines the transaction memory pool to interact with.
 	TxMemPool *mempool.TxPool
@@ -5579,25 +5622,73 @@ type rpcserverConfig struct {
 	// of to provide additional data when queried.
 	TxIndex   *indexers.TxIndex
 	AddrIndex *indexers.AddrIndex
+
+	// NetInfo defines a slice of the available networks.
+	NetInfo []types.NetworksResult
+
+	// MinRelayTxFee defines the minimum transaction fee in Atoms/1000 bytes to be
+	// considered a non-zero fee.
+	MinRelayTxFee dcrutil.Amount
+
+	// Proxy defines the proxy that is being used for connections.
+	Proxy string
+
+	// These fields define the username and password for RPC connections and
+	// limited RPC connections.
+	RPCUser      string
+	RPCPass      string
+	RPCLimitUser string
+	RPCLimitPass string
+
+	// RPCMaxClients defines the max number of RPC clients for standard
+	// connections.
+	RPCMaxClients int
+
+	// RPCMaxConcurrentReqs defines the max number of RPC requests that may be
+	// processed concurrently.
+	RPCMaxConcurrentReqs int
+
+	// RPCMaxWebsockets defines the max number of RPC websocket connections.
+	RPCMaxWebsockets int
+
+	// TestNet represents whether or not the server is using testnet.
+	TestNet bool
+
+	// MiningAddrs is a list of payment addresses to use for the generated blocks.
+	MiningAddrs []dcrutil.Address
+
+	// AllowUnsyncedMining indicates whether block templates should be created even
+	// when the chain is not fully synced.
+	AllowUnsyncedMining bool
+
+	// MaxProtocolVersion is the max protocol version that the server supports.
+	MaxProtocolVersion uint32
+
+	// UserAgentVersion is the user agent version and is used to help identify
+	// ourselves to other peers.
+	UserAgentVersion string
+
+	// LogManager defines the log manager for the RPC server to use.
+	LogManager LogManager
 }
 
-// newRPCServer returns a new instance of the rpcServer struct.
-func newRPCServer(config *rpcserverConfig) (*rpcServer, error) {
-	rpc := rpcServer{
+// New returns a new instance of the Server struct.
+func New(config *Config) (*Server, error) {
+	rpc := Server{
 		cfg:                    *config,
 		statusLines:            make(map[int]string),
 		workState:              newWorkState(),
 		helpCacher:             newHelpCacher(),
 		requestProcessShutdown: make(chan struct{}),
 	}
-	if cfg.RPCUser != "" && cfg.RPCPass != "" {
-		login := cfg.RPCUser + ":" + cfg.RPCPass
+	if config.RPCUser != "" && config.RPCPass != "" {
+		login := config.RPCUser + ":" + config.RPCPass
 		auth := "Basic " +
 			base64.StdEncoding.EncodeToString([]byte(login))
 		rpc.authsha = sha256.Sum256([]byte(auth))
 	}
-	if cfg.RPCLimitUser != "" && cfg.RPCLimitPass != "" {
-		login := cfg.RPCLimitUser + ":" + cfg.RPCLimitPass
+	if config.RPCLimitUser != "" && config.RPCLimitPass != "" {
+		login := config.RPCLimitUser + ":" + config.RPCLimitPass
 		auth := "Basic " +
 			base64.StdEncoding.EncodeToString([]byte(login))
 		rpc.limitauthsha = sha256.Sum256([]byte(auth))
@@ -5609,7 +5700,6 @@ func newRPCServer(config *rpcserverConfig) (*rpcServer, error) {
 
 func init() {
 	rpcHandlers = rpcHandlersBeforeInit
-	rand.Seed(time.Now().UnixNano())
 
 	// blake256Pad is the extra blake256 internal padding needed for the
 	// data of the getwork RPC.  The internal blake256 padding consists of
diff --git a/rpcserverhelp.go b/internal/rpcserver/rpcserverhelp.go
similarity index 99%
rename from rpcserverhelp.go
rename to internal/rpcserver/rpcserverhelp.go
index 1ea934d9..324177b1 100644
--- a/rpcserverhelp.go
+++ b/internal/rpcserver/rpcserverhelp.go
@@ -1,9 +1,9 @@
 // Copyright (c) 2015 The btcsuite developers
-// Copyright (c) 2015-2019 The Decred developers
+// Copyright (c) 2015-2020 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
-package main
+package rpcserver
 
 import (
 	"errors"
diff --git a/rpcwebsocket.go b/internal/rpcserver/rpcwebsocket.go
similarity index 95%
rename from rpcwebsocket.go
rename to internal/rpcserver/rpcwebsocket.go
index f1a0eb11..f324d088 100644
--- a/rpcwebsocket.go
+++ b/internal/rpcserver/rpcwebsocket.go
@@ -3,7 +3,7 @@
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
-package main
+package rpcserver
 
 import (
 	"bytes"
@@ -90,16 +90,16 @@ var wsHandlersBeforeInit = map[types.Method]wsCommandHandler{
 // must be run in a separate goroutine.  It should be invoked from the websocket
 // server handler which runs each new connection in a new goroutine thereby
 // satisfying the requirement.
-func (s *rpcServer) WebsocketHandler(conn *websocket.Conn, remoteAddr string, authenticated bool, isAdmin bool) {
+func (s *Server) WebsocketHandler(conn *websocket.Conn, remoteAddr string, authenticated bool, isAdmin bool) {
 	// Clear the read deadline that was set before the websocket hijacked
 	// the connection.
 	conn.SetReadDeadline(timeZeroVal)
 
 	// Limit max number of websocket clients.
-	rpcsLog.Infof("New websocket client %s", remoteAddr)
-	if s.ntfnMgr.NumClients()+1 > cfg.RPCMaxWebsockets {
-		rpcsLog.Infof("Max websocket clients exceeded [%d] - "+
-			"disconnecting client %s", cfg.RPCMaxWebsockets,
+	log.Infof("New websocket client %s", remoteAddr)
+	if s.ntfnMgr.NumClients()+1 > s.cfg.RPCMaxWebsockets {
+		log.Infof("Max websocket clients exceeded [%d] - "+
+			"disconnecting client %s", s.cfg.RPCMaxWebsockets,
 			remoteAddr)
 		conn.Close()
 		return
@@ -110,7 +110,7 @@ func (s *rpcServer) WebsocketHandler(conn *websocket.Conn, remoteAddr string, au
 	// disconnected), remove it and any notifications it registered for.
 	client, err := newWebsocketClient(s, conn, remoteAddr, authenticated, isAdmin)
 	if err != nil {
-		rpcsLog.Errorf("Failed to serve client %s: %v", remoteAddr, err)
+		log.Errorf("Failed to serve client %s: %v", remoteAddr, err)
 		conn.Close()
 		return
 	}
@@ -118,7 +118,7 @@ func (s *rpcServer) WebsocketHandler(conn *websocket.Conn, remoteAddr string, au
 	client.Start()
 	client.WaitForShutdown()
 	s.ntfnMgr.RemoveClient(client)
-	rpcsLog.Infof("Disconnected websocket client %s", remoteAddr)
+	log.Infof("Disconnected websocket client %s", remoteAddr)
 }
 
 // wsNotificationManager is a connection and notification manager used for
@@ -131,7 +131,7 @@ func (s *rpcServer) WebsocketHandler(conn *websocket.Conn, remoteAddr string, au
 // track of all connected websocket clients.
 type wsNotificationManager struct {
 	// server is the RPC server the notification manager is associated with.
-	server *rpcServer
+	server *Server
 
 	// queueNotification queues a notification for handling.
 	queueNotification chan interface{}
@@ -357,8 +357,8 @@ func (m *wsNotificationManager) NotifyNewTickets(
 	}
 }
 
-// NotifyNewTickets passes a new ticket data for an incoming block from the best
-// chain to the notification manager for block notification processing.
+// NotifyStakeDifficulty notifies websocket clients that have registered for
+// stake difficulty updates.
 func (m *wsNotificationManager) NotifyStakeDifficulty(
 	stnd *StakeDifficultyNtfnData) {
 	m.mtx.RLock()
@@ -370,7 +370,7 @@ func (m *wsNotificationManager) NotifyStakeDifficulty(
 	ctx := m.ctx
 	m.mtx.RUnlock()
 
-	// As NotifyNewTickets will be called by the block manager
+	// As NotifyStakeDifficulty will be called by the block manager
 	// and the RPC server may no longer be running, use a select
 	// statement to unblock enqueuing the notification once the RPC
 	// server has begun shutting down.
@@ -733,7 +733,7 @@ out:
 				delete(txNotifications, wsc.quit)
 
 			default:
-				rpcsLog.Warn("Unhandled notification type")
+				log.Warn("Unhandled notification type")
 			}
 
 		case m.numClients <- len(clients):
@@ -837,7 +837,7 @@ func (m *wsNotificationManager) subscribedClients(tx *dcrutil.Tx, clients map[ch
 				addr, err := stake.AddrFromSStxPkScrCommitment(
 					output.PkScript, m.server.cfg.ChainParams)
 				if err != nil {
-					rpcsLog.Errorf("Failed to read commitment from "+
+					log.Errorf("Failed to read commitment from "+
 						"previously-validated ticket: %v", err)
 					continue
 				}
@@ -913,7 +913,7 @@ func (m *wsNotificationManager) notifyBlockConnected(clients map[chan struct{}]*
 		// Marshal and queue notification.
 		marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, &ntfn)
 		if err != nil {
-			rpcsLog.Errorf("Failed to marshal block connected "+
+			log.Errorf("Failed to marshal block connected "+
 				"notification: %v", err)
 			continue
 		}
@@ -945,7 +945,7 @@ func (*wsNotificationManager) notifyBlockDisconnected(clients map[chan struct{}]
 	}
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, &ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal block disconnected "+
+		log.Errorf("Failed to marshal block disconnected "+
 			"notification: %v", err)
 		return
 	}
@@ -993,7 +993,7 @@ func (m *wsNotificationManager) notifyWork(clients map[chan struct{}]*wsClient,
 	buf := bytes.NewBuffer(data)
 	err := header.Serialize(buf)
 	if err != nil {
-		rpcsLog.Errorf("Failed to serialize data: %v", err)
+		log.Errorf("Failed to serialize data: %v", err)
 		return
 	}
 
@@ -1024,7 +1024,7 @@ func (m *wsNotificationManager) notifyWork(clients map[chan struct{}]*wsClient,
 	// Notify interested websocket clients about new mining work.
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, &ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal new work notification: %v", err)
+		log.Errorf("Failed to marshal new work notification: %v", err)
 		return
 	}
 
@@ -1058,7 +1058,7 @@ func (m *wsNotificationManager) notifyReorganization(clients map[chan struct{}]*
 		int32(rd.NewHeight))
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal reorganization "+
+		log.Errorf("Failed to marshal reorganization "+
 			"notification: %v", err)
 		return
 	}
@@ -1096,7 +1096,7 @@ func (*wsNotificationManager) notifyWinningTickets(
 
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal winning tickets notification: "+
+		log.Errorf("Failed to marshal winning tickets notification: "+
 			"%v", err)
 		return
 	}
@@ -1136,7 +1136,7 @@ func (*wsNotificationManager) notifySpentAndMissedTickets(clients map[chan struc
 
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal spent and missed tickets "+
+		log.Errorf("Failed to marshal spent and missed tickets "+
 			"notification: %v", err)
 		return
 	}
@@ -1185,7 +1185,7 @@ func (*wsNotificationManager) notifyNewTickets(clients map[chan struct{}]*wsClie
 
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal new tickets notification: "+
+		log.Errorf("Failed to marshal new tickets notification: "+
 			"%v", err)
 		return
 	}
@@ -1204,7 +1204,7 @@ func (*wsNotificationManager) notifyStakeDifficulty(clients map[chan struct{}]*w
 
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal stake difficulty notification: "+
+		log.Errorf("Failed to marshal stake difficulty notification: "+
 			"%v", err)
 		return
 	}
@@ -1240,7 +1240,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
 		dcrutil.Amount(amount).ToCoin())
 	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal tx notification: %s",
+		log.Errorf("Failed to marshal tx notification: %s",
 			err.Error())
 		return
 	}
@@ -1255,7 +1255,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
 			}
 
 			net := m.server.cfg.ChainParams
-			rawTx, err := createTxRawResult(net, mtx, txHashStr,
+			rawTx, err := m.server.createTxRawResult(net, mtx, txHashStr,
 				wire.NullBlockIndex, nil, "", 0, 0)
 			if err != nil {
 				return
@@ -1265,7 +1265,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
 			marshalledJSONVerbose, err = dcrjson.MarshalCmd("1.0", nil,
 				verboseNtfn)
 			if err != nil {
-				rpcsLog.Errorf("Failed to marshal verbose tx "+
+				log.Errorf("Failed to marshal verbose tx "+
 					"notification: %s", err.Error())
 				return
 			}
@@ -1343,7 +1343,7 @@ func (m *wsNotificationManager) notifyRelevantTxAccepted(tx *dcrutil.Tx,
 		n := types.NewRelevantTxAcceptedNtfn(txHexString(msgTx))
 		marshalled, err := dcrjson.MarshalCmd("1.0", nil, n)
 		if err != nil {
-			rpcsLog.Errorf("Failed to marshal notification: %v", err)
+			log.Errorf("Failed to marshal notification: %v", err)
 			return
 		}
 		for _, c := range clientsToNotify {
@@ -1391,7 +1391,7 @@ func (m *wsNotificationManager) Run(ctx context.Context) {
 
 // newWsNotificationManager returns a new notification manager ready for use.
 // See wsNotificationManager for more details.
-func newWsNotificationManager(server *rpcServer) *wsNotificationManager {
+func newWsNotificationManager(server *Server) *wsNotificationManager {
 	return &wsNotificationManager{
 		server:            server,
 		queueNotification: make(chan interface{}),
@@ -1423,7 +1423,7 @@ type wsClient struct {
 	sync.Mutex
 
 	// server is the RPC server that is servicing the client.
-	rpcServer *rpcServer
+	rpcServer *Server
 
 	// conn is the underlying websocket connection.
 	conn *websocket.Conn
@@ -1479,7 +1479,7 @@ out:
 		if err != nil {
 			// Log the error if it's not due to disconnecting.
 			if !errors.Is(err, io.EOF) {
-				rpcsLog.Errorf("Websocket receive error from "+
+				log.Errorf("Websocket receive error from "+
 					"%s: %v", c.addr, err)
 			}
 			break out
@@ -1509,7 +1509,7 @@ out:
 				}
 				reply, err = createMarshalledReply("1.0", nil, nil, jsonErr)
 				if err != nil {
-					rpcsLog.Errorf("Failed to marshal reply: %v", err)
+					log.Errorf("Failed to marshal reply: %v", err)
 					continue
 				}
 				c.SendMessage(reply, nil)
@@ -1523,7 +1523,7 @@ out:
 				}
 				reply, err := createMarshalledReply(req.Jsonrpc, req.ID, nil, jsonErr)
 				if err != nil {
-					rpcsLog.Errorf("Failed to marshal reply: %v", err)
+					log.Errorf("Failed to marshal reply: %v", err)
 					continue
 				}
 				c.SendMessage(reply, nil)
@@ -1548,14 +1548,14 @@ out:
 
 				reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, cmd.err)
 				if err != nil {
-					rpcsLog.Errorf("Failed to marshal reply: %v", err)
+					log.Errorf("Failed to marshal reply: %v", err)
 					continue
 				}
 				c.SendMessage(reply, nil)
 				continue
 			}
 
-			rpcsLog.Debugf("Received command <%s> from %s", cmd.method, c.addr)
+			log.Debugf("Received command <%s> from %s", cmd.method, c.addr)
 
 			// Check auth.  The client is immediately disconnected if the
 			// first request of an unauthenticated websocket client is not
@@ -1564,11 +1564,11 @@ out:
 			// authentication credentials are provided in the request.
 			switch authCmd, ok := cmd.params.(*types.AuthenticateCmd); {
 			case c.authenticated && ok:
-				rpcsLog.Warnf("Websocket client %s is already authenticated",
+				log.Warnf("Websocket client %s is already authenticated",
 					c.addr)
 				break out
 			case !c.authenticated && !ok:
-				rpcsLog.Warnf("Unauthenticated websocket message " +
+				log.Warnf("Unauthenticated websocket message " +
 					"received")
 				break out
 			case !c.authenticated:
@@ -1579,7 +1579,7 @@ out:
 				cmp := subtle.ConstantTimeCompare(authSha[:], c.rpcServer.authsha[:])
 				limitcmp := subtle.ConstantTimeCompare(authSha[:], c.rpcServer.limitauthsha[:])
 				if cmp != 1 && limitcmp != 1 {
-					rpcsLog.Warnf("Auth failure.")
+					log.Warnf("Auth failure.")
 					break out
 				}
 				c.authenticated = true
@@ -1588,7 +1588,7 @@ out:
 				// Marshal and send response.
 				reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, nil)
 				if err != nil {
-					rpcsLog.Errorf("Failed to marshal authenticate reply: "+
+					log.Errorf("Failed to marshal authenticate reply: "+
 						"%v", err.Error())
 					continue
 				}
@@ -1607,7 +1607,7 @@ out:
 					// Marshal and send response.
 					reply, err = createMarshalledReply("", req.ID, nil, jsonErr)
 					if err != nil {
-						rpcsLog.Errorf("Failed to marshal parse failure "+
+						log.Errorf("Failed to marshal parse failure "+
 							"reply: %v", err)
 						continue
 					}
@@ -1664,7 +1664,7 @@ out:
 				}
 				reply, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
 				if err != nil {
-					rpcsLog.Errorf("Failed to create reply: %v", err)
+					log.Errorf("Failed to create reply: %v", err)
 				}
 
 				if reply != nil {
@@ -1685,7 +1685,7 @@ out:
 					}
 					reply, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
 					if err != nil {
-						rpcsLog.Errorf("Failed to marshal reply: %v", err)
+						log.Errorf("Failed to marshal reply: %v", err)
 					}
 
 					if reply != nil {
@@ -1712,7 +1712,7 @@ out:
 							}
 							reply, err = dcrjson.MarshalResponse("2.0", nil, nil, jsonErr)
 							if err != nil {
-								rpcsLog.Errorf("Failed to create reply: %v", err)
+								log.Errorf("Failed to create reply: %v", err)
 								continue
 							}
 
@@ -1729,7 +1729,7 @@ out:
 							}
 							reply, err := createMarshalledReply(req.Jsonrpc, req.ID, nil, jsonErr)
 							if err != nil {
-								rpcsLog.Errorf("Failed to marshal reply: %v", err)
+								log.Errorf("Failed to marshal reply: %v", err)
 								continue
 							}
 
@@ -1757,7 +1757,7 @@ out:
 
 							reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, cmd.err)
 							if err != nil {
-								rpcsLog.Errorf("Failed to marshal reply: %v", err)
+								log.Errorf("Failed to marshal reply: %v", err)
 								continue
 							}
 
@@ -1767,7 +1767,7 @@ out:
 							continue
 						}
 
-						rpcsLog.Debugf("Received command <%s> from %s", cmd.method, c.addr)
+						log.Debugf("Received command <%s> from %s", cmd.method, c.addr)
 
 						// Check auth.  The client is immediately disconnected if the
 						// first request of an unauthenticated websocket client is not
@@ -1776,11 +1776,11 @@ out:
 						// authentication credentials are provided in the request.
 						switch authCmd, ok := cmd.params.(*types.AuthenticateCmd); {
 						case c.authenticated && ok:
-							rpcsLog.Warnf("Websocket client %s is already authenticated",
+							log.Warnf("Websocket client %s is already authenticated",
 								c.addr)
 							break out
 						case !c.authenticated && !ok:
-							rpcsLog.Warnf("Unauthenticated websocket message " +
+							log.Warnf("Unauthenticated websocket message " +
 								"received")
 							break out
 						case !c.authenticated:
@@ -1791,7 +1791,7 @@ out:
 							cmp := subtle.ConstantTimeCompare(authSha[:], c.rpcServer.authsha[:])
 							limitcmp := subtle.ConstantTimeCompare(authSha[:], c.rpcServer.limitauthsha[:])
 							if cmp != 1 && limitcmp != 1 {
-								rpcsLog.Warnf("Auth failure.")
+								log.Warnf("Auth failure.")
 								break out
 							}
 
@@ -1801,7 +1801,7 @@ out:
 							// Marshal and send response.
 							reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, nil)
 							if err != nil {
-								rpcsLog.Errorf("Failed to marshal authenticate reply: "+
+								log.Errorf("Failed to marshal authenticate reply: "+
 									"%v", err.Error())
 								continue
 							}
@@ -1823,7 +1823,7 @@ out:
 								// Marshal and send response.
 								reply, err = createMarshalledReply(req.Jsonrpc, req.ID, nil, jsonErr)
 								if err != nil {
-									rpcsLog.Errorf("Failed to marshal parse failure "+
+									log.Errorf("Failed to marshal parse failure "+
 										"reply: %v", err)
 									continue
 								}
@@ -1849,7 +1849,7 @@ out:
 						// Marshal request output.
 						reply, err := createMarshalledReply(cmd.jsonrpc, cmd.id, resp, err)
 						if err != nil {
-							rpcsLog.Errorf("Failed to marshal reply for <%s> "+
+							log.Errorf("Failed to marshal reply for <%s> "+
 								"command: %v", cmd.method, err)
 							return
 						}
@@ -1896,7 +1896,7 @@ out:
 	// Ensure the connection is closed.
 	c.Disconnect()
 	c.wg.Done()
-	rpcsLog.Tracef("Websocket client input handler done for %s", c.addr)
+	log.Tracef("Websocket client input handler done for %s", c.addr)
 }
 
 // serviceRequest services a parsed RPC request by looking up and executing the
@@ -1918,7 +1918,7 @@ func (c *wsClient) serviceRequest(ctx context.Context, r *parsedRPCCmd) {
 	}
 	reply, err := createMarshalledReply(r.jsonrpc, r.id, result, err)
 	if err != nil {
-		rpcsLog.Errorf("Failed to marshal reply for <%s> "+
+		log.Errorf("Failed to marshal reply for <%s> "+
 			"command: %v", r.method, err)
 		return
 	}
@@ -1995,7 +1995,7 @@ cleanup:
 		}
 	}
 	c.wg.Done()
-	rpcsLog.Tracef("Websocket client notification queue handler done "+
+	log.Tracef("Websocket client notification queue handler done "+
 		"for %s", c.addr)
 }
 
@@ -2038,7 +2038,7 @@ cleanup:
 		}
 	}
 	c.wg.Done()
-	rpcsLog.Tracef("Websocket client output handler done for %s", c.addr)
+	log.Tracef("Websocket client output handler done for %s", c.addr)
 }
 
 // SendMessage sends the passed json to the websocket client.  It is backed
@@ -2116,7 +2116,7 @@ func (c *wsClient) Disconnect() {
 		return
 	}
 
-	rpcsLog.Tracef("Disconnecting websocket client %s", c.addr)
+	log.Tracef("Disconnecting websocket client %s", c.addr)
 	close(c.quit)
 	c.conn.Close()
 	c.disconnected = true
@@ -2124,7 +2124,7 @@ func (c *wsClient) Disconnect() {
 
 // Start begins processing input and output messages.
 func (c *wsClient) Start() {
-	rpcsLog.Tracef("Starting websocket client %s", c.addr)
+	log.Tracef("Starting websocket client %s", c.addr)
 
 	// Start processing input and output.
 	c.wg.Add(3)
@@ -2145,7 +2145,7 @@ func (c *wsClient) WaitForShutdown() {
 // returned client is ready to start.  Once started, the client will process
 // incoming and outgoing messages in separate goroutines complete with queuing
 // and asynchronous handling for long-running operations.
-func newWebsocketClient(server *rpcServer, conn *websocket.Conn,
+func newWebsocketClient(server *Server, conn *websocket.Conn,
 	remoteAddr string, authenticated bool, isAdmin bool) (*wsClient, error) {
 
 	sessionID, err := wire.RandomUint64()
@@ -2160,7 +2160,7 @@ func newWebsocketClient(server *rpcServer, conn *websocket.Conn,
 		isAdmin:           isAdmin,
 		sessionID:         sessionID,
 		rpcServer:         server,
-		serviceRequestSem: makeSemaphore(cfg.RPCMaxConcurrentReqs),
+		serviceRequestSem: makeSemaphore(server.cfg.RPCMaxConcurrentReqs),
 		ntfnChan:          make(chan []byte, 1), // nonblocking sync
 		sendChan:          make(chan wsResponse, websocketSendBufferSize),
 		quit:              make(chan struct{}),
diff --git a/rpcadaptors.go b/rpcadaptors.go
index 76d01081..5f377ea3 100644
--- a/rpcadaptors.go
+++ b/rpcadaptors.go
@@ -304,6 +304,14 @@ func (cm *rpcConnManager) AddedNodeInfo() []rpcserver.Peer {
 	return peers
 }
 
+// Lookup defines the DNS lookup function to be used.
+//
+// This function is safe for concurrent access and is part of the
+// rpcserver.ConnManager interface implementation.
+func (*rpcConnManager) Lookup(host string) ([]net.IP, error) {
+	return dcrdLookup(host)
+}
+
 // rpcSyncMgr provides a block manager for use with the RPC server and
 // implements the rpcserver.SyncManager interface.
 type rpcSyncMgr struct {
@@ -464,3 +472,27 @@ var _ rpcserver.FeeEstimator = (*rpcFeeEstimator)(nil)
 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{}
+
+// SupportedSubsystems returns a sorted slice of the supported subsystems for
+// logging purposes.
+//
+// This function is part of the rpcserver.LogManager interface implementation.
+func (*rpcLogManager) SupportedSubsystems() []string {
+	return supportedSubsystems()
+}
+
+// ParseAndSetDebugLevels attempts to parse the specified debug level and set
+// the levels accordingly.  An appropriate error is returned if anything is
+// invalid.
+//
+// This function is part of the rpcserver.LogManager interface implementation.
+func (*rpcLogManager) ParseAndSetDebugLevels(debugLevel string) error {
+	return parseAndSetDebugLevels(debugLevel)
+}
diff --git a/server.go b/server.go
index 9027398b..2f480047 100644
--- a/server.go
+++ b/server.go
@@ -7,13 +7,16 @@ package main
 
 import (
 	"context"
+	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/tls"
 	"encoding/binary"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"math"
 	"net"
+	"os"
 	"path"
 	"runtime"
 	"strconv"
@@ -27,6 +30,7 @@ import (
 	"github.com/decred/dcrd/blockchain/standalone/v2"
 	"github.com/decred/dcrd/blockchain/v3"
 	"github.com/decred/dcrd/blockchain/v3/indexers"
+	"github.com/decred/dcrd/certgen"
 	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/chaincfg/v3"
 	"github.com/decred/dcrd/connmgr/v3"
@@ -38,6 +42,7 @@ import (
 	"github.com/decred/dcrd/internal/mempool"
 	"github.com/decred/dcrd/internal/mining"
 	"github.com/decred/dcrd/internal/mining/cpuminer"
+	"github.com/decred/dcrd/internal/rpcserver"
 	"github.com/decred/dcrd/internal/version"
 	"github.com/decred/dcrd/lru"
 	"github.com/decred/dcrd/peer/v2"
@@ -441,7 +446,7 @@ type server struct {
 	connManager          *connmgr.ConnManager
 	sigCache             *txscript.SigCache
 	subsidyCache         *standalone.SubsidyCache
-	rpcServer            *rpcServer
+	rpcServer            *rpcserver.Server
 	blockManager         *blockManager
 	bg                   *mining.BgBlkTmplGenerator
 	chain                *blockchain.BlockChain
@@ -2832,6 +2837,31 @@ func standardScriptVerifyFlags(chain *blockchain.BlockChain) (txscript.ScriptFla
 	return scriptFlags, nil
 }
 
+// genCertPair generates a key/cert pair to the paths provided.
+func genCertPair(certFile, keyFile string, altDNSNames []string, tlsCurve elliptic.Curve) error {
+	rpcsLog.Infof("Generating TLS certificates...")
+
+	org := "dcrd autogenerated cert"
+	validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
+	cert, key, err := certgen.NewTLSCertPair(tlsCurve, org,
+		validUntil, altDNSNames)
+	if err != nil {
+		return err
+	}
+
+	// Write cert and key files.
+	if err = ioutil.WriteFile(certFile, cert, 0644); err != nil {
+		return err
+	}
+	if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
+		os.Remove(certFile)
+		return err
+	}
+
+	rpcsLog.Infof("Done generating TLS certificates")
+	return nil
+}
+
 // setupRPCListeners returns a slice of listeners that are configured for use
 // with the RPC server depending on the configuration settings for listen
 // addresses and TLS.
@@ -3087,13 +3117,13 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 		FeeEstimator:       s.feeEstimator,
 		TxMemPool:          s.txMemPool,
 		BgBlkTmplGenerator: nil, // Created later.
-		NotifyWinningTickets: func(wtnd *WinningTicketsNtfnData) {
+		NotifyWinningTickets: func(wtnd *rpcserver.WinningTicketsNtfnData) {
 			if s.rpcServer != nil {
-				s.rpcServer.ntfnMgr.NotifyWinningTickets(wtnd)
+				s.rpcServer.NotifyWinningTickets(wtnd)
 			}
 		},
 		PruneRebroadcastInventory: s.PruneRebroadcastInventory,
-		RpcServer: func() *rpcServer {
+		RpcServer: func() *rpcserver.Server {
 			return s.rpcServer
 		},
 	})
@@ -3234,11 +3264,10 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 			return nil, errors.New("no usable rpc listen addresses")
 		}
 
-		s.rpcServer, err = newRPCServer(&rpcserverConfig{
+		s.rpcServer, err = rpcserver.New(&rpcserver.Config{
 			Listeners:    rpcListeners,
 			ConnMgr:      &rpcConnManager{&s},
 			SyncMgr:      &rpcSyncMgr{&s, s.blockManager},
-			PeerNotifier: &s,
 			FeeEstimator: &rpcFeeEstimator{s.feeEstimator},
 			TimeSource:   s.timeSource,
 			Services:     s.services,
@@ -3252,9 +3281,25 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 			BgBlkTmplGenerator: func() *mining.BgBlkTmplGenerator {
 				return s.bg
 			},
-			CPUMiner:  s.cpuMiner,
-			TxIndex:   s.txIndex,
-			AddrIndex: s.addrIndex,
+			CPUMiner:             s.cpuMiner,
+			TxIndex:              s.txIndex,
+			AddrIndex:            s.addrIndex,
+			NetInfo:              cfg.generateNetworkInfo(),
+			MinRelayTxFee:        cfg.minRelayTxFee,
+			Proxy:                cfg.Proxy,
+			RPCUser:              cfg.RPCUser,
+			RPCPass:              cfg.RPCPass,
+			RPCLimitUser:         cfg.RPCLimitUser,
+			RPCLimitPass:         cfg.RPCLimitPass,
+			RPCMaxClients:        cfg.RPCMaxClients,
+			RPCMaxConcurrentReqs: cfg.RPCMaxConcurrentReqs,
+			RPCMaxWebsockets:     cfg.RPCMaxWebsockets,
+			TestNet:              cfg.TestNet,
+			MiningAddrs:          cfg.miningAddrs,
+			AllowUnsyncedMining:  cfg.AllowUnsyncedMining,
+			MaxProtocolVersion:   maxProtocolVersion,
+			UserAgentVersion:     userAgentVersion,
+			LogManager:           &rpcLogManager{},
 		})
 		if err != nil {
 			return nil, err


Test patch:
diff --git a/rpcserver_test.go b/internal/rpcserver/rpcserver_test.go
similarity index 99%
rename from rpcserver_test.go
rename to internal/rpcserver/rpcserver_test.go
index 00d2a622..589e6bc2 100644
--- a/rpcserver_test.go
+++ b/internal/rpcserver/rpcserver_test.go
@@ -7,7 +7,7 @@
 // This file is ignored during the regular tests due to the following build tag.
 // +build rpctest
 
-package main
+package rpcserver
 
 import (
 	"bytes"
diff --git a/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
similarity index 97%
rename from rpcserverhandlers_test.go
rename to internal/rpcserver/rpcserverhandlers_test.go
index c44735ce..aa719742 100644
--- a/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
-package main
+package rpcserver
 
 import (
 	"bytes"
@@ -18,7 +18,6 @@ import (
 	"path/filepath"
 	"reflect"
 	"strconv"
-	"sync"
 	"testing"
 	"time"
 
@@ -34,7 +33,6 @@ import (
 	"github.com/decred/dcrd/dcrutil/v3"
 	"github.com/decred/dcrd/gcs/v2"
 	"github.com/decred/dcrd/internal/mempool"
-	"github.com/decred/dcrd/internal/rpcserver"
 	"github.com/decred/dcrd/internal/version"
 	"github.com/decred/dcrd/peer/v2"
 	"github.com/decred/dcrd/rpc/jsonrpc/types/v2"
@@ -42,7 +40,7 @@ import (
 )
 
 // testDataPath is the path where all rpcserver test fixtures reside.
-var testDataPath = filepath.Join("internal", "rpcserver", "testdata")
+const testDataPath = "testdata"
 
 // testRPCUtxoEntry provides a mock utxo entry by implementing the UtxoEntry interface.
 type testRPCUtxoEntry struct {
@@ -128,7 +126,7 @@ type testRPCChain struct {
 	convertUtxosToMinimalOutputs    []*stake.MinimalOutput
 	countVoteVersion                uint32
 	estimateNextStakeDifficultyFn   func(newTickets int64, useMaxTickets bool) (diff int64, err error)
-	fetchUtxoEntry                  rpcserver.UtxoEntry
+	fetchUtxoEntry                  UtxoEntry
 	fetchUtxoStats                  *blockchain.UtxoStats
 	filterByBlockHash               *gcs.FilterV2
 	getStakeVersions                []blockchain.StakeVersions
@@ -232,7 +230,7 @@ func (c *testRPCChain) CheckMissedTickets(hashes []chainhash.Hash) []bool {
 }
 
 // ConvertUtxosToMinimalOutputs returns a mocked MinimalOutput slice.
-func (c *testRPCChain) ConvertUtxosToMinimalOutputs(entry rpcserver.UtxoEntry) []*stake.MinimalOutput {
+func (c *testRPCChain) ConvertUtxosToMinimalOutputs(entry UtxoEntry) []*stake.MinimalOutput {
 	return c.convertUtxosToMinimalOutputs
 }
 
@@ -248,7 +246,7 @@ func (c *testRPCChain) EstimateNextStakeDifficulty(newTickets int64, useMaxTicke
 }
 
 // FetchUtxoEntry returns a mocked UtxoEntry.
-func (c *testRPCChain) FetchUtxoEntry(txHash *chainhash.Hash) (rpcserver.UtxoEntry, error) {
+func (c *testRPCChain) FetchUtxoEntry(txHash *chainhash.Hash) (UtxoEntry, error) {
 	return c.fetchUtxoEntry, nil
 }
 
@@ -520,9 +518,10 @@ type testConnManager struct {
 	connectedCount      int32
 	netTotalReceived    uint64
 	netTotalSent        uint64
-	connectedPeers      []rpcserver.Peer
-	persistentPeers     []rpcserver.Peer
-	addedNodeInfo       []rpcserver.Peer
+	connectedPeers      []Peer
+	persistentPeers     []Peer
+	addedNodeInfo       []Peer
+	lookup              func(host string) ([]net.IP, error)
 }
 
 // Connect provides a mock implementation for adding the provided address as a
@@ -567,36 +566,38 @@ func (c *testConnManager) NetTotals() (uint64, uint64) {
 }
 
 // ConnectedPeers returns a mocked slice of all connected peers.
-func (c *testConnManager) ConnectedPeers() []rpcserver.Peer {
+func (c *testConnManager) ConnectedPeers() []Peer {
 	return c.connectedPeers
 }
 
 // PersistentPeers returns a mocked slice of all persistent peers.
-func (c *testConnManager) PersistentPeers() []rpcserver.Peer {
+func (c *testConnManager) PersistentPeers() []Peer {
 	return c.persistentPeers
 }
 
 // BroadcastMessage provides a mock implementation for sending the provided
 // message to all currently connected peers.
-func (c *testConnManager) BroadcastMessage(msg wire.Message) {
-}
+func (c *testConnManager) BroadcastMessage(msg wire.Message) {}
 
 // AddRebroadcastInventory provides a mock implementation for adding the
 // provided inventory to the list of inventories to be rebroadcast at random
 // intervals until they show up in a block.
-func (c *testConnManager) AddRebroadcastInventory(iv *wire.InvVect, data interface{}) {
-}
+func (c *testConnManager) AddRebroadcastInventory(iv *wire.InvVect, data interface{}) {}
 
 // RelayTransactions provides a mock implementation for generating and relaying
 // inventory vectors for all of the passed transactions to all connected peers.
-func (c *testConnManager) RelayTransactions(txns []*dcrutil.Tx) {
-}
+func (c *testConnManager) RelayTransactions(txns []*dcrutil.Tx) {}
 
 // AddedNodeInfo returns a mocked slice of persistent (added) peers.
-func (c *testConnManager) AddedNodeInfo() []rpcserver.Peer {
+func (c *testConnManager) AddedNodeInfo() []Peer {
 	return c.addedNodeInfo
 }
 
+// Lookup defines a mocked DNS lookup function to be used.
+func (c *testConnManager) Lookup(host string) ([]net.IP, error) {
+	return c.lookup(host)
+}
+
 // testAddr implements the net.Addr interface.
 type testAddr struct {
 	net, addr string
@@ -629,7 +630,7 @@ func (c *testClock) Since(t time.Time) time.Duration {
 }
 
 // testFeeEstimator provides a mock fee estimator by implementing the
-// rpcserver.FeeEstimator interface.
+// FeeEstimator interface.
 type testFeeEstimator struct {
 	estimateFeeAmt dcrutil.Amount
 	estimateFeeErr error
@@ -641,6 +642,24 @@ func (e *testFeeEstimator) EstimateFee(targetConfs int32) (dcrutil.Amount, error
 	return e.estimateFeeAmt, e.estimateFeeErr
 }
 
+// testLogManager provides a mock log manager by implementing the LogManager
+// interface.
+type testLogManager struct {
+	supportedSubsystems       []string
+	parseAndSetDebugLevelsErr error
+}
+
+// SupportedSubsystems returns a mocked slice of supported subsystems.
+func (l *testLogManager) SupportedSubsystems() []string {
+	return l.supportedSubsystems
+}
+
+// ParseAndSetDebugLevels provides a mock implementation for parsing the
+// specified debug level and setting the levels accordingly.
+func (l *testLogManager) ParseAndSetDebugLevels(debugLevel string) error {
+	return l.parseAndSetDebugLevelsErr
+}
+
 // mustParseHash converts the passed big-endian hex string into a
 // chainhash.Hash and will panic if there is an error.  It only differs from the
 // one available in chainhash in that it will panic so errors in the source code
@@ -710,48 +729,12 @@ type rpcTest struct {
 	mockSyncManager  *testSyncManager
 	mockConnManager  *testConnManager
 	mockClock        *testClock
+	mockLogManager   *testLogManager
 	result           interface{}
 	wantErr          bool
 	errCode          dcrjson.RPCErrorCode
 }
 
-// setCfgOnce is used to set cfg once safely.
-var setCfgOnce sync.Once
-
-// defaultCfg provides a default config that is used throughout the tests.
-func defaultCfg() *config {
-	return &config{
-		ipv4NetInfo: types.NetworksResult{
-			Name:                      "IPV4",
-			Limited:                   false,
-			Reachable:                 true,
-			Proxy:                     "",
-			ProxyRandomizeCredentials: false,
-		},
-		ipv6NetInfo: types.NetworksResult{
-			Name:                      "IPV6",
-			Limited:                   false,
-			Reachable:                 true,
-			Proxy:                     "",
-			ProxyRandomizeCredentials: false,
-		},
-		onionNetInfo: types.NetworksResult{
-			Name:                      "Onion",
-			Limited:                   false,
-			Reachable:                 false,
-			Proxy:                     "",
-			ProxyRandomizeCredentials: false,
-		},
-		lookup: func(host string) ([]net.IP, error) {
-			if host == "mydomain.org" {
-				return []net.IP{net.ParseIP("127.0.0.211")}, nil
-			}
-			return nil, errors.New("host not found")
-		},
-		minRelayTxFee: dcrutil.Amount(int64(10000)),
-	}
-}
-
 // defaultChainParams provides a default chaincfg.Params to be used throughout
 // the tests.  It should be cloned using cloneParams, updated as necessary, and
 // then assigned to rpcTest.mockChainParams if it needs to be overridden by a
@@ -956,24 +939,30 @@ func defaultMockConnManager() *testConnManager {
 		connectedCount:   4,
 		netTotalReceived: 9598159,
 		netTotalSent:     4783802,
-		connectedPeers: []rpcserver.Peer{
+		connectedPeers: []Peer{
 			testPeer1,
 			testPeer2,
 			testPeer3,
 			testPeer4,
 		},
-		persistentPeers: []rpcserver.Peer{
+		persistentPeers: []Peer{
 			testPeer1,
 			testPeer2,
 			testPeer3,
 			testPeer4,
 		},
-		addedNodeInfo: []rpcserver.Peer{
+		addedNodeInfo: []Peer{
 			testPeer1,
 			testPeer2,
 			testPeer3,
 			testPeer4,
 		},
+		lookup: func(host string) ([]net.IP, error) {
+			if host == "mydomain.org" {
+				return []net.IP{net.ParseIP("127.0.0.211")}, nil
+			}
+			return nil, errors.New("host not found")
+		},
 	}
 }
 
@@ -986,10 +975,21 @@ func defaultMockFeeEstimator() *testFeeEstimator {
 	return &testFeeEstimator{}
 }
 
-// defaultMockConfig provides a default rpcserverConfig that is used throughout
+// defaultMockLogManager provides a default mock log manager to be used
+// throughout the tests. Tests can override these defaults by calling
+// defaultMockLogManager, updating fields as necessary on the returned
+// *testLogManager, and then setting rpcTest.mockLogManager as that
+// *testLogManager.
+func defaultMockLogManager() *testLogManager {
+	return &testLogManager{
+		supportedSubsystems: []string{"DCRD", "PEER", "RPCS"},
+	}
+}
+
+// defaultMockConfig provides a default Config that is used throughout
 // the tests.  Defaults can be overridden by tests through the rpcTest struct.
-func defaultMockConfig(chainParams *chaincfg.Params) *rpcserverConfig {
-	return &rpcserverConfig{
+func defaultMockConfig(chainParams *chaincfg.Params) *Config {
+	return &Config{
 		ChainParams:  chainParams,
 		Chain:        defaultMockRPCChain(),
 		AddrManager:  defaultMockAddrManager(),
@@ -997,9 +997,33 @@ func defaultMockConfig(chainParams *chaincfg.Params) *rpcserverConfig {
 		SyncMgr:      defaultMockSyncManager(),
 		ConnMgr:      defaultMockConnManager(),
 		Clock:        &testClock{},
+		LogManager:   defaultMockLogManager(),
 		TimeSource:   blockchain.NewMedianTime(),
 		Services:     wire.SFNodeNetwork | wire.SFNodeCF,
 		SubsidyCache: standalone.NewSubsidyCache(chainParams),
+		NetInfo: []types.NetworksResult{{
+			Name:                      "IPV4",
+			Limited:                   false,
+			Reachable:                 true,
+			Proxy:                     "",
+			ProxyRandomizeCredentials: false,
+		}, {
+			Name:                      "IPV6",
+			Limited:                   false,
+			Reachable:                 true,
+			Proxy:                     "",
+			ProxyRandomizeCredentials: false,
+		}, {
+			Name:                      "Onion",
+			Limited:                   false,
+			Reachable:                 false,
+			Proxy:                     "",
+			ProxyRandomizeCredentials: false,
+		}},
+		MinRelayTxFee:      dcrutil.Amount(10000),
+		MaxProtocolVersion: wire.CFilterV2Version,
+		UserAgentVersion: fmt.Sprintf("%d.%d.%d", version.Major, version.Minor,
+			version.Patch),
 	}
 }
 
@@ -1694,19 +1718,25 @@ func TestHandleCreateRawTransaction(t *testing.T) {
 func TestHandleDebugLevel(t *testing.T) {
 	t.Parallel()
 
+	logMgr := defaultMockLogManager()
 	testRPCServerHandler(t, []rpcTest{{
 		name:    "handleDebugLevel: show",
 		handler: handleDebugLevel,
 		cmd: &types.DebugLevelCmd{
 			LevelSpec: "show",
 		},
-		result: fmt.Sprintf("Supported subsystems %v", supportedSubsystems()),
+		result: fmt.Sprintf("Supported subsystems %v", logMgr.supportedSubsystems),
 	}, {
 		name:    "handleDebugLevel: invalidDebugLevel",
 		handler: handleDebugLevel,
 		cmd: &types.DebugLevelCmd{
 			LevelSpec: "invalidDebugLevel",
 		},
+		mockLogManager: func() *testLogManager {
+			logManager := defaultMockLogManager()
+			logManager.parseAndSetDebugLevelsErr = errors.New("invalidDebugLevel")
+			return logManager
+		}(),
 		wantErr: true,
 		errCode: dcrjson.ErrRPCInvalidParameter,
 	}, {
@@ -2697,8 +2727,9 @@ func TestHandleGetBlock(t *testing.T) {
 	// createTxRawResult should be tested independently as well.
 	txns := blk.Transactions()
 	rawTxns := make([]types.TxRawResult, len(txns))
+	testServer := &Server{cfg: *defaultMockConfig(defaultChainParams)}
 	for i, tx := range txns {
-		rawTxn, err := createTxRawResult(defaultChainParams, tx.MsgTx(),
+		rawTxn, err := testServer.createTxRawResult(defaultChainParams, tx.MsgTx(),
 			tx.Hash().String(), uint32(i), &blkHeader, blk.Hash().String(),
 			int64(blkHeader.Height), confirmations)
 		if err != nil {
@@ -2709,7 +2740,7 @@ func TestHandleGetBlock(t *testing.T) {
 	stxns := blk.STransactions()
 	rawSTxns := make([]types.TxRawResult, len(stxns))
 	for i, tx := range stxns {
-		rawSTxn, err := createTxRawResult(defaultChainParams, tx.MsgTx(),
+		rawSTxn, err := testServer.createTxRawResult(defaultChainParams, tx.MsgTx(),
 			tx.Hash().String(), uint32(i), &blkHeader, blk.Hash().String(),
 			int64(blkHeader.Height), confirmations)
 		if err != nil {
@@ -3124,7 +3155,7 @@ func TestHandleGetInfo(t *testing.T) {
 		result: &types.InfoChainResult{
 			Version: int32(1000000*version.Major + 10000*version.Minor +
 				100*version.Patch),
-			ProtocolVersion: int32(maxProtocolVersion),
+			ProtocolVersion: int32(wire.CFilterV2Version),
 			Blocks:          int64(block432100.Header.Height),
 			TimeOffset:      int64(0),
 			Connections:     int32(4),
@@ -3166,8 +3197,9 @@ func TestHandleGetNetworkInfo(t *testing.T) {
 		result: types.GetNetworkInfoResult{
 			Version: int32(1000000*version.Major + 10000*version.Minor +
 				100*version.Patch),
-			SubVersion:      userAgentVersion,
-			ProtocolVersion: int32(maxProtocolVersion),
+			SubVersion: fmt.Sprintf("%d.%d.%d", version.Major, version.Minor,
+				version.Patch),
+			ProtocolVersion: int32(wire.CFilterV2Version),
 			TimeOffset:      int64(0),
 			Connections:     int32(4),
 			Networks: []types.NetworksResult{{
@@ -3209,7 +3241,7 @@ func TestHandleGetPeerInfo(t *testing.T) {
 		cmd:     &types.GetPeerInfoCmd{},
 		mockConnManager: func() *testConnManager {
 			connManager := defaultMockConnManager()
-			connManager.connectedPeers = []rpcserver.Peer{
+			connManager.connectedPeers = []Peer{
 				&testPeer{
 					localAddr: testAddr{
 						net:  "tcp",
@@ -3848,10 +3880,6 @@ func TestHandleValidateAddress(t *testing.T) {
 func testRPCServerHandler(t *testing.T, tests []rpcTest) {
 	t.Helper()
 
-	setCfgOnce.Do(func() {
-		cfg = defaultCfg()
-	})
-
 	for _, test := range tests {
 		test := test // capture range variable
 		t.Run(test.name, func(t *testing.T) {
@@ -3882,8 +3910,11 @@ func testRPCServerHandler(t *testing.T, tests []rpcTest) {
 			if test.mockFeeEstimator != nil {
 				rpcserverConfig.FeeEstimator = test.mockFeeEstimator
 			}
+			if test.mockLogManager != nil {
+				rpcserverConfig.LogManager = test.mockLogManager
+			}
 
-			testServer := &rpcServer{cfg: *rpcserverConfig}
+			testServer := &Server{cfg: *rpcserverConfig}
 			result, err := test.handler(nil, testServer, test.cmd)
 			if test.wantErr {
 				var rpcErr *dcrjson.RPCError
diff --git a/rpcserverhelp_test.go b/internal/rpcserver/rpcserverhelp_test.go
similarity index 96%
rename from rpcserverhelp_test.go
rename to internal/rpcserver/rpcserverhelp_test.go
index 33095252..b5269741 100644
--- a/rpcserverhelp_test.go
+++ b/internal/rpcserver/rpcserverhelp_test.go
@@ -1,9 +1,9 @@
 // Copyright (c) 2015 The btcsuite developers
-// Copyright (c) 2015-2016 The Decred developers
+// Copyright (c) 2015-2020 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
-package main
+package rpcserver
 
 import "testing"