Repository: decred/dcrd
Pull request #2319 (2020-08-02 02:15:02)

mempool: Store staged transactions as TxDesc
This optimization modifies the mempool to store staged transactions as a *TxDesc rather than a *dcrutil.Tx. The advantage of doing this is to avoid recalculating transaction types through stake.DetermineTxType since this would have been cached in a TxDesc when the transaction initially entered the mempool.

This concern was raised by @davecgh in #1852 (https://github.com/decred/dcrd/pull/1852#discussion_r379311798). 



sefbkn: One undesirable behavior with this PR is that the height captured when an SSTX enters the mempool is reset to `bestHeight` when it re-enters the main pool though `maybeAcceptTransaction`. Adding another parameter to that function and a condition around the assignment of bestHeight appears to be the most straightforward option, though I'd prefer a cleaner solution.
davecgh: Now that the development cycle for 1.7.0 has started, I'm going to take a closer look at this again.  Mind rebasing it to resolve conflicts first?
sefbkn: > One undesirable behavior with this PR is that the height captured when an SSTX enters the mempool is reset to bestHeight when it re-enters the main pool though maybeAcceptTransaction

This is fixed

> Now that the development cycle for 1.7.0 has started, I'm going to take a closer look at this again. Mind rebasing it to resolve conflicts first?

Rebased

Patch:
diff --git a/bech32/README.md b/bech32/README.md
index aba41aaf..8e19f669 100644
--- a/bech32/README.md
+++ b/bech32/README.md
@@ -12,9 +12,8 @@ Test vectors from BIP 173 are added to ensure compatibility with the BIP.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/bech32
-```
+This package is part of the `github.com/decred/dcrd/bech32` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/blockchain/README.md b/blockchain/README.md
index e994eab9..e9ff64b1 100644
--- a/blockchain/README.md
+++ b/blockchain/README.md
@@ -81,9 +81,8 @@ mean the block being processed is the one that failed validation.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/blockchain
-```
+This package is part of the `github.com/decred/dcrd/blockchain/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/blockchain/chain.go b/blockchain/chain.go
index d1d984b9..92f1f40a 100644
--- a/blockchain/chain.go
+++ b/blockchain/chain.go
@@ -348,6 +348,19 @@ func (b *BlockChain) DisableVerify(disable bool) {
 	b.chainLock.Unlock()
 }
 
+// HaveHeader returns whether or not the chain instance has the block header
+// represented by the passed hash.  Note that this will return true for both the
+// main chain and any side chains.
+//
+// This function is safe for concurrent access.
+func (b *BlockChain) HaveHeader(hash *chainhash.Hash) bool {
+	b.index.RLock()
+	node := b.index.index[*hash]
+	headerKnown := node != nil
+	b.index.RUnlock()
+	return headerKnown
+}
+
 // HaveBlock returns whether or not the chain instance has the block represented
 // by the passed hash.  This includes checking the various places a block can
 // be like part of the main chain or on a side chain.
@@ -1436,7 +1449,7 @@ func (b *BlockChain) isOldTimestamp(node *blockNode) bool {
 }
 
 // maybeUpdateIsCurrent potentially updates whether or not the chain believes it
-// is current.
+// is current using the provided best chain tip.
 //
 // It makes use of a latching approach such that once the chain becomes current
 // it will only switch back to false in the case no new blocks have been seen
@@ -1474,7 +1487,20 @@ func (b *BlockChain) maybeUpdateIsCurrent(curBest *blockNode) {
 		log.Debugf("Chain latched to current at block %s (height %d)",
 			curBest.hash, curBest.height)
 	}
+}
 
+// MaybeUpdateIsCurrent potentially updates whether or not the chain believes it
+// is current.
+//
+// It makes use of a latching approach such that once the chain becomes current
+// it will only switch back to false in the case no new blocks have been seen
+// for an extended period of time.
+//
+// This function is safe for concurrent access.
+func (b *BlockChain) MaybeUpdateIsCurrent() {
+	b.chainLock.Lock()
+	b.maybeUpdateIsCurrent(b.bestChain.Tip())
+	b.chainLock.Unlock()
 }
 
 // isCurrent returns whether or not the chain believes it is current based on
@@ -2188,10 +2214,16 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
 		"%d, block index: %d", b.dbInfo.version, b.dbInfo.compVer,
 		b.dbInfo.bidxVer)
 
+	b.index.RLock()
+	bestHdr := b.index.bestHeader
+	b.index.RUnlock()
+	log.Infof("Best known header: height %d, hash %v", bestHdr.height,
+		bestHdr.hash)
+
 	tip := b.bestChain.Tip()
-	log.Infof("Chain state: height %d, hash %v, total transactions %d, "+
-		"work %v, stake version %v", tip.height, tip.hash,
-		b.stateSnapshot.TotalTxns, tip.workSum, 0)
+	log.Infof("Chain state: height %d, hash %v, total transactions %d, work "+
+		"%v, progress %0.2f%%", tip.height, tip.hash,
+		b.stateSnapshot.TotalTxns, tip.workSum, b.VerifyProgress())
 
 	return &b, nil
 }
diff --git a/blockchain/chaingen/README.md b/blockchain/chaingen/README.md
index df6419eb..ed99c4f2 100644
--- a/blockchain/chaingen/README.md
+++ b/blockchain/chaingen/README.md
@@ -30,11 +30,10 @@ functions.
   required first block and enough blocks to have mature coinbase outputs to
   work with along with asserting the generator state along the way.
 
-## Installation
+## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/blockchain/chaingen
-```
+This package is part of the `github.com/decred/dcrd/blockchain/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/blockchain/chainquery.go b/blockchain/chainquery.go
index dd3ffe33..00008ea8 100644
--- a/blockchain/chainquery.go
+++ b/blockchain/chainquery.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2018-2020 The Decred developers
+// Copyright (c) 2018-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -6,6 +6,7 @@ package blockchain
 
 import (
 	"bytes"
+	"math"
 	"sort"
 
 	"github.com/decred/dcrd/chaincfg/chainhash"
@@ -154,3 +155,98 @@ func (b *BlockChain) BestInvalidHeader() chainhash.Hash {
 	b.index.RUnlock()
 	return hash
 }
+
+// NextNeededBlocks returns hashes for the next blocks after the current best
+// chain tip that are needed to make progress towards the current best known
+// header skipping any blocks that are already known or in the provided map of
+// blocks to exclude.  Typically the caller would want to exclude all blocks
+// that have outstanding requests.
+//
+// The maximum number of results is limited to the provided value or the maximum
+// allowed by the internal lookahead buffer in the case the requested number of
+// max results exceeds that value.
+//
+// This function is safe for concurrent access.
+func (b *BlockChain) NextNeededBlocks(maxResults uint8, exclude map[chainhash.Hash]struct{}) []*chainhash.Hash {
+	// Nothing to do when no results are requested.
+	if maxResults == 0 {
+		return nil
+	}
+
+	// Determine the common ancestor between the current best chain tip and the
+	// current best known header.  In practice this should never be nil because
+	// orphan headers are not allowed into the block index, but be paranoid and
+	// check anyway in case things change in the future.
+	b.index.RLock()
+	bestHeader := b.index.bestHeader
+	fork := b.bestChain.FindFork(bestHeader)
+	if fork == nil {
+		b.index.RUnlock()
+		return nil
+	}
+
+	// Determine the final block to consider for determining the next needed
+	// blocks by determining the descendants of the current best chain tip on
+	// the branch that leads to the best known header while clamping the number
+	// of descendants to consider to a lookahead buffer.
+	const lookaheadBuffer = 512
+	numBlocksToConsider := bestHeader.height - fork.height
+	if numBlocksToConsider == 0 {
+		b.index.RUnlock()
+		return nil
+	}
+	if numBlocksToConsider > lookaheadBuffer {
+		bestHeader = bestHeader.Ancestor(fork.height + lookaheadBuffer)
+		numBlocksToConsider = lookaheadBuffer
+	}
+
+	// Walk backwards from the final block to consider to the current best chain
+	// tip excluding any blocks that already have their data available or that
+	// the caller asked to be excluded (likely because they've already been
+	// requested).
+	neededBlocks := make([]*chainhash.Hash, 0, numBlocksToConsider)
+	for node := bestHeader; node != nil && node != fork; node = node.parent {
+		_, isExcluded := exclude[node.hash]
+		if isExcluded || node.status.HaveData() {
+			continue
+		}
+
+		neededBlocks = append(neededBlocks, &node.hash)
+	}
+	b.index.RUnlock()
+
+	// Reverse the needed blocks so they are in forwards order.
+	reverse := func(s []*chainhash.Hash) {
+		slen := len(s)
+		for i := 0; i < slen/2; i++ {
+			s[i], s[slen-1-i] = s[slen-1-i], s[i]
+		}
+	}
+	reverse(neededBlocks)
+
+	// Clamp the number of results to the lower of the requested max or number
+	// available.
+	if int64(maxResults) > numBlocksToConsider {
+		maxResults = uint8(numBlocksToConsider)
+	}
+	if uint16(len(neededBlocks)) > uint16(maxResults) {
+		neededBlocks = neededBlocks[:maxResults]
+	}
+	return neededBlocks
+}
+
+// VerifyProgress returns a percentage that is a guess of the progress of the
+// chain verification process.
+//
+// This function is safe for concurrent access.
+func (b *BlockChain) VerifyProgress() float64 {
+	b.index.RLock()
+	bestHdr := b.index.bestHeader
+	b.index.RUnlock()
+	if bestHdr.height == 0 {
+		return 0.0
+	}
+
+	tip := b.bestChain.Tip()
+	return math.Min(float64(tip.height)/float64(bestHdr.height), 1.0) * 100
+}
diff --git a/blockchain/checkpoints.go b/blockchain/checkpoints.go
index e674a3f1..cbeecb32 100644
--- a/blockchain/checkpoints.go
+++ b/blockchain/checkpoints.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2013-2016 The btcsuite developers
-// Copyright (c) 2015-2020 The Decred developers
+// Copyright (c) 2015-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -65,7 +65,7 @@ func (b *BlockChain) verifyCheckpoint(height int64, hash *chainhash.Hash) bool {
 		return false
 	}
 
-	log.Infof("Verified checkpoint at height %d/block %s", checkpoint.Height,
+	log.Debugf("Verified checkpoint at height %d/block %s", checkpoint.Height,
 		checkpoint.Hash)
 	return true
 }
diff --git a/blockchain/indexers/README.md b/blockchain/indexers/README.md
index 4007ff34..c1e7cffe 100644
--- a/blockchain/indexers/README.md
+++ b/blockchain/indexers/README.md
@@ -26,11 +26,10 @@ via an RPC interface.
   - Stores all committed filters and committed filter headers for all blocks in
     the main chain
 
-## Installation
+## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/blockchain/indexers
-```
+This package is part of the `github.com/decred/dcrd/blockchain/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/blockchain/standalone/README.md b/blockchain/standalone/README.md
index aa9aa76e..2c941e99 100644
--- a/blockchain/standalone/README.md
+++ b/blockchain/standalone/README.md
@@ -3,7 +3,7 @@ standalone
 
 [![Build Status](https://github.com/decred/dcrd/workflows/Build%20and%20Test/badge.svg)](https://github.com/decred/dcrd/actions)
 [![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
-[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/blockchain/standalone)
+[![Doc](https://img.shields.io/badge/doc-reference-blue.svg)](https://pkg.go.dev/github.com/decred/dcrd/blockchain/standalone/v2)
 
 Package standalone provides standalone functions useful for working with the
 Decred blockchain consensus rules.
@@ -37,9 +37,8 @@ The provided functions fall into the following categories:
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/blockchain/standalone
-```
+This package is part of the `github.com/decred/dcrd/blockchain/standalone/v2`
+module.  Use the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/blockdb.go b/blockdb.go
index 45a6f02e..977ace03 100644
--- a/blockdb.go
+++ b/blockdb.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2013-2016 The btcsuite developers
-// Copyright (c) 2015-2020 The Decred developers
+// Copyright (c) 2015-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -238,7 +238,11 @@ func dumpBlockChain(params *chaincfg.Params, b *blockchain.BlockChain) error {
 			return err
 		}
 
-		progressLogger.LogBlockHeight(bl.MsgBlock(), tipHeight)
+		msgBlock := bl.MsgBlock()
+		forceLog := int64(msgBlock.Header.Height) >= tipHeight
+		progressLogger.LogProgress(msgBlock, forceLog, func() float64 {
+			return float64(msgBlock.Header.Height) / float64(tipHeight) * 100
+		})
 	}
 
 	srvrLog.Infof("Successfully dumped the blockchain (%v blocks) to %v.",
diff --git a/certgen/README.md b/certgen/README.md
index bda2346a..ae8b63c5 100644
--- a/certgen/README.md
+++ b/certgen/README.md
@@ -16,9 +16,8 @@ this package additionally includes support for Ed25519 certificates.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/certgen
-```
+This package is part of the `github.com/decred/dcrd/certgen` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/chaincfg/README.md b/chaincfg/README.md
index 66f46557..7c5a71a3 100644
--- a/chaincfg/README.md
+++ b/chaincfg/README.md
@@ -54,9 +54,8 @@ func main() {
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/chaincfg
-```
+This package is part of the `github.com/decred/dcrd/chaincfg/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/chaincfg/chainhash/README.md b/chaincfg/chainhash/README.md
index e988db56..75e2f8f0 100644
--- a/chaincfg/chainhash/README.md
+++ b/chaincfg/chainhash/README.md
@@ -8,11 +8,10 @@ chainhash
 chainhash provides a generic hash type and associated functions that allows the
 specific hash algorithm to be abstracted.
 
-## Installation and updating
+## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/chaincfg/chainhash
-```
+This package is part of the `github.com/decred/dcrd/chaincfg/chainhash` module.
+Use the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/chaincfg/mainnetparams.go b/chaincfg/mainnetparams.go
index 93571ab1..254e963d 100644
--- a/chaincfg/mainnetparams.go
+++ b/chaincfg/mainnetparams.go
@@ -420,7 +420,7 @@ func MainNetParams() *Params {
 			"mainnet-seed-2.decred.org",
 			"mainnet-seed.planetdecred.org",
 			"mainnet-seed.dcrdata.org",
-			"mainnet-seed.jamieholdstock.com",
+			"mainnet-seed.jholdstock.uk",
 		},
 	}
 }
diff --git a/connmgr/README.md b/connmgr/README.md
index 6257d026..10058db5 100644
--- a/connmgr/README.md
+++ b/connmgr/README.md
@@ -28,9 +28,8 @@ In addition the connection manager provides the following utilities:
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/connmgr
-```
+This package is part of the `github.com/decred/dcrd/connmgr/v3` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/dcrec/secp256k1/README.md b/dcrec/secp256k1/README.md
index 2c386eb9..4ae25dba 100644
--- a/dcrec/secp256k1/README.md
+++ b/dcrec/secp256k1/README.md
@@ -57,9 +57,8 @@ defined by the secp256k1 domain parameters.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/dcrec/secp256k1/v3
-```
+This package is part of the `github.com/decred/dcrd/dcrec/secp256k1/v3` module.
+Use the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/dcrec/secp256k1/ecdsa/README.md b/dcrec/secp256k1/ecdsa/README.md
index 0077efcc..c6c0374c 100644
--- a/dcrec/secp256k1/ecdsa/README.md
+++ b/dcrec/secp256k1/ecdsa/README.md
@@ -34,9 +34,8 @@ key without actually revealing it.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/dcrec/secp25k1/v3/ecdsa
-```
+This package is part of the `github.com/decred/dcrd/dcrec/secp256k1/v3` module.
+Use the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/dcrec/secp256k1/schnorr/README.md b/dcrec/secp256k1/schnorr/README.md
index 2e696a63..950c652a 100644
--- a/dcrec/secp256k1/schnorr/README.md
+++ b/dcrec/secp256k1/schnorr/README.md
@@ -314,9 +314,8 @@ standardization attempts:
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/dcrec/secp25k1/v3/schnorr
-```
+This package is part of the `github.com/decred/dcrd/dcrec/secp256k1/v3` module.
+Use the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/dcrjson/README.md b/dcrjson/README.md
index fb1a43c7..ca84bc8a 100644
--- a/dcrjson/README.md
+++ b/dcrjson/README.md
@@ -24,9 +24,8 @@ ints, etc) to higher-level types with many nice and useful properties.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/dcrjson
-```
+This package is part of the `github.com/decred/dcrd/dcrjson/v3` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/dcrutil/README.md b/dcrutil/README.md
index 55b6ec88..b2ae6b34 100644
--- a/dcrutil/README.md
+++ b/dcrutil/README.md
@@ -16,9 +16,8 @@ standalone package for any projects needing the functionality provided.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/dcrutil
-```
+This package is part of the `github.com/decred/dcrd/dcrutil/v3` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/dcrutil/txsort/README.md b/dcrutil/txsort/README.md
index 98a5c170..5c819421 100644
--- a/dcrutil/txsort/README.md
+++ b/dcrutil/txsort/README.md
@@ -28,9 +28,8 @@ A comprehensive suite of tests is provided to ensure proper functionality.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/dcrutil/txsort
-```
+This package is part of the `github.com/decred/dcrd/dcrutil/v3` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/docs/release-notes/release-notes-1.6.0.md b/docs/release-notes/release-notes-1.6.0.md
new file mode 100644
index 00000000..11d3398e
--- /dev/null
+++ b/docs/release-notes/release-notes-1.6.0.md
@@ -0,0 +1,947 @@
+# dcrd v1.6.0
+
+This release of dcrd introduces a large number of updates.  Some of the key
+highlights are:
+
+* A new consensus vote agenda which allows the stakeholders to decide whether or
+  not to activate support for a decentralized treasury
+* Aggregate fee transaction selection in block templates (Child Pays For Parent)
+* Improved peer discovery via HTTPS seeding with filtering capabilities
+* Major performance enhancements for signature validation and other
+  cryptographic operations
+* Approximately 15% less overall resident memory usage
+* Proactive signature cache eviction
+* Improved support for single-party Schnorr signatures
+* Ticket exhaustion prevention
+* Various updates to the RPC server such as:
+  * A new method to retrieve the current treasury balance
+  * A new method to query treasury spend transaction vote details
+* Infrastructure improvements
+* Quality assurance changes
+
+For those unfamiliar with the
+[voting process](https://docs.decred.org/governance/consensus-rule-voting/overview/)
+in Decred, all code needed in order to support a decentralized treasury is
+already included in this release, however it will remain dormant until the
+stakeholders vote to activate it.
+
+For reference, the decentralized treasury work was originally proposed and
+approved for initial implementation via the following Politeia proposal:
+- [Decentralized Treasury Consensus Change](https://proposals.decred.org/proposals/c96290a2478d0a1916284438ea2c59a1215fe768a87648d04d45f6b7ecb82c3f)
+
+The following Decred Change Proposal (DCP) describes the proposed changes in
+detail and provides a full technical specification:
+- [DCP0006](https://github.com/decred/dcps/blob/master/dcp-0006/dcp-0006.mediawiki)
+
+**It is important for everyone to upgrade their software to this latest release
+even if you don't intend to vote in favor of the agenda.**
+
+## Downgrade Warning
+
+The database format in v1.6.0 is not compatible with previous versions of the
+software.  This only affects downgrades as users upgrading from previous
+versions will see a one time database migration.
+
+Once this migration has been completed, it will no longer be possible to
+downgrade to a previous version of the software without having to delete the
+database and redownload the chain.
+
+The database migration typically takes about 5 to 10 minutes on HDDs and 2 to 4
+minutes on SSDs.
+
+## Notable Changes
+
+### Decentralized Treasury Vote
+
+A new vote with the id `treasury` is now available as of this release.  After
+upgrading, stakeholders may set their preferences through their wallet or Voting
+Service Provider's (VSP) website.
+
+The primary goal of this change is to fully decentralize treasury spending so
+that it is controlled by the stakeholders via ticket voting.
+
+See the initial
+[Politeia proposal](https://proposals.decred.org/proposals/c96290a2478d0a1916284438ea2c59a1215fe768a87648d04d45f6b7ecb82c3f)
+for more details.
+
+### Aggregate Fee Block Template Transaction Selection (Child Pays For Parent)
+
+The transactions that are selected for inclusion in block templates that
+Proof-of-Work miners solve now prioritize the overall fees of the entire
+transaction ancestor graph.
+
+This is beneficial for both miners and end users as it:
+
+- Helps maximize miner profit by ensuring that unconfirmed transaction chains
+  with higher aggregate fees are given priority over others with lower aggregate
+  fees
+- Provides a mechanism for users to increase the priority of an unconfirmed
+  transaction by spending its outputs with another transaction that pays higher
+  fees
+
+This is commonly referred to as Child Pays For Parent (CPFP) as the spending
+("child") transaction is able to increase the priority of the spent ("parent")
+transaction.
+
+### HTTPS Seeding
+
+The initial bootstrap process that contacts seeders to discover other nodes to
+connect to now uses a REST-based API over HTTPS.
+
+This change will be imperceptible for most users, with the exception that it
+accelerates the process of finding suitable candidate nodes that support desired
+services, particularly in the case of recently-introduced services that have not
+yet achieved widespread adoption on the network.
+
+The following are some key benefits of HTTPS seeders over the previous DNS-based
+seeders:
+
+- Support for non-standard ports
+- Advertisement of supported service
+- Better scalability both in terms of network load and new features
+- Native support for TLS-secured communication channels
+- Native support for proxies which allows the use of anonymous overlay networks
+  such as Tor and I2P
+- No need for a large DNSSEC dependency surface
+- Uses better audited infrastructure
+- More secure
+- Increases flexibility
+
+### Signature Validation And Other Crypto Operation Optimizations
+
+The underlying crypto code has been reworked to significantly improve its
+execution speed and reduce the number of memory allocations.  While this has
+more benefits than enumerated here, probably the most important ones for most
+stakeholders are:
+
+- Improved vote times since blocks and transactions propagate more quickly
+  throughout the network
+- The initial sync process is around 15% faster
+
+### Proactive Signature Cache Eviction
+
+Signature cache entries that are nearly guaranteed to no longer be useful are
+now immediately and proactively evicted resulting in overall faster validation
+during steady state operation due to fewer cache misses.
+
+The primary purpose of the cache is to avoid double checking signatures that are
+already known to be valid.
+
+### Orphan Transaction Relay Policy Refinement
+
+Transactions that spend outputs which are not known to nodes relaying them,
+known as orphan transactions, now have the same size restrictions applied to
+them as standard non-orphan transactions.
+
+This ensures that transactions chains are not artificially hindered from
+relaying regardless of the order they are received.
+
+In order to keep memory usage of the now potentially larger orphan transactions
+under control, more intelligent orphan eviction has been implemented and the
+maximum number of allowed orphans before random eviction occurs has been
+lowered.
+
+These changes, in conjunction with other related changes, mean that nodes are
+better about orphan transaction management and thus missing ancestors will
+typically either be broadcast or mined fairly quickly resulting in fewer overall
+orphans and smaller actual run-time orphan pools.
+
+### Ticket Exhaustion Prevention
+
+Mining templates that would lead to the chain becoming unrecoverable due to
+inevitable ticket exhaustion will no longer be generated.
+
+This is primarily aimed at the testing networks, but it could also theoretically
+affect the main network in some far future if the demand for tickets were to
+ever dry up for some unforeseen reason.
+
+### New Initial State Protocol Messages (`getinitstate`/`initstate`)
+
+This release introduces a pair of peer-to-peer protocol messages named
+`getinitstate` and `initstate` which support querying one or more pieces of
+information that are useful to acquire when a node first connects in a
+consolidated fashion.
+
+Some examples of the aforementioned information are the mining state as of the
+current tip block and, with the introduction of the decentralized treasury, any
+outstanding treasury spend transactions that are being voted on.
+
+### Mining State Protocol Messages Deprecated (`getminings`/`minings`)
+
+Due to the addition of the previously-described initial state peer-to-peer
+protocol messages, the `getminings` and `minings` protocol messages are now
+deprecated.  Use the new `getinitstate` and `initstate` messages with the
+`headblocks` and `headblockvotes` state types instead.
+
+### RPC Server Changes
+
+The RPC server version as of this release is 6.2.0.
+
+#### New Treasury Balance Query RPC (`gettreasurybalance`)
+
+A new RPC named `gettreasurybalance` is now available to query the current
+balance of the decentralized treasury.  Please note that this requires the
+decentralized treasury vote to pass and become active, so it will return an
+appropriate error indicating the decentralized treasury is inactive until that
+time.
+
+See the
+[gettreasurybalance JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#gettreasurybalance)
+for API details.
+
+#### New Treasury Spend Vote Query RPC (`gettreasuryspendvotes`)
+
+A new RPC named `gettreasuryspendvotes` is now available to query vote
+information about one or more treasury spend transactions.  Please note that
+this requires the decentralized treasury vote to pass and become active to
+produce a meaningful result since treasury spend transactions are invalid until
+that time.
+
+See the
+[gettreasuryspendvotes JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#gettreasuryspendvotes)
+for API details.
+
+#### New Force Mining Template Regeneration RPC (`regentemplate`)
+
+A new RPC named `regentemplate` is now available which can be used to force the
+current background block template to be regenerated.
+
+See the
+[regentemplate JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#regentemplate)
+for API details.
+
+#### New Unspent Transaction Output Set Query RPC (`gettxoutsetinfo`)
+
+A new RPC named `gettxoutsetinfo` is now available which can be used to retrieve
+statistics about the current global set of unspent transaction outputs (UTXOs).
+
+See the
+[gettxoutsetinfo JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#gettxoutsetinfo)
+for API details.
+
+#### Updates to Peer Information Query RPC (`getpeerinfo`)
+
+The results of the `getpeerinfo` RPC are now sorted by the `id` field.
+
+See the
+[getpeerinfo JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#getpeerinfo)
+for API details.
+
+#### Enforced Results Limit on Transaction Search RPC (`searchrawtransactions`)
+
+The maximum number of transactions returned by a single request to the
+`searchrawtransactions` RPC is now limited to 10,000 transactions.  This far
+exceeds the number of results for all typical cases; however, for the rare cases
+where it does not, the caller can make use of the `skip` parameter in subsequent
+requests to access additional data if they require access to more results.
+
+See the
+[searchrawtransactions JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#searchrawtransactions)
+for API details.
+
+#### New Index Status Fields on Info Query RPC (`getinfo`)
+
+The results of the `getinfo` RPC now include `txindex` and `addrindex` fields
+that specify whether or not the respective indexes are active.
+
+See the
+[getinfo JSON-RPC API Documentation](https://github.com/decred/dcrd/blob/master/docs/json_rpc_api.mediawiki#getinfo)
+for API details.
+
+### Version 1 Block Filters Deprecated
+
+Support for version 1 block filters is deprecated and is scheduled to be removed
+in the next release.   Use
+[version 2 block filters](https://github.com/decred/dcps/blob/master/dcp-0005/dcp-0005.mediawiki#version-2-block-filters)
+with their associated [block header commitment](https://github.com/decred/dcps/blob/master/dcp-0005/dcp-0005.mediawiki#block-header-commitments)
+and [inclusion proof](https://github.com/decred/dcps/blob/master/dcp-0005/dcp-0005.mediawiki#verifying-commitment-root-inclusion-proofs)
+instead.
+
+## Changelog
+
+This release consists of 616 commits from 17 contributors which total to 526
+files changed, 63090 additional lines of code, and 26279 deleted lines of code.
+
+All commits since the last release may be viewed on GitHub
+[here](https://github.com/decred/dcrd/compare/release-v1.5.2...release-v1.6.0).
+
+### Protocol and network:
+
+- chaincfg: Add checkpoints for upcoming release ([decred/dcrd#2370](https://github.com/decred/dcrd/pull/2370))
+- multi: Introduce initial sync min known chain work ([decred/dcrd#2000](https://github.com/decred/dcrd/pull/2000))
+- chaincfg: Update min known chain work for release ([decred/dcrd#2371](https://github.com/decred/dcrd/pull/2371))
+- server: improve address discovery ([decred/dcrd#1838](https://github.com/decred/dcrd/pull/1838))
+- connmgr: unexport newConnReq ([decred/dcrd#1729](https://github.com/decred/dcrd/pull/1729))
+- connmgr: Add context to Dial and DialAddr ([decred/dcrd#1729](https://github.com/decred/dcrd/pull/1729))
+- dcrd: adapt to new connmgr API ([decred/dcrd#1729](https://github.com/decred/dcrd/pull/1729))
+- server: Simplify logic to bind listeners ([decred/dcrd#1972](https://github.com/decred/dcrd/pull/1972))
+- server: Fix peer state update ([decred/dcrd#1981](https://github.com/decred/dcrd/pull/1981))
+- chaincfg: introduce Seeder ([decred/dcrd#2017](https://github.com/decred/dcrd/pull/2017))
+- connmgr: add SeedAddrs ([decred/dcrd#2017](https://github.com/decred/dcrd/pull/2017))
+- server: seed with https versus dns ([decred/dcrd#2017](https://github.com/decred/dcrd/pull/2017))
+- chaincfg: deprecate type DNSSeed and Params.DNSSeeds ([decred/dcrd#2017](https://github.com/decred/dcrd/pull/2017))
+- connmgr: Allow pending outbound conn removal ([decred/dcrd#2033](https://github.com/decred/dcrd/pull/2033))
+- connmgr: Cleanup pending outbound conn removal ([decred/dcrd#2033](https://github.com/decred/dcrd/pull/2033))
+- connmgr: add Timeout config option ([decred/dcrd#2068](https://github.com/decred/dcrd/pull/2068))
+- connmgr: Remove deprecated DisableLog func ([decred/dcrd#2187](https://github.com/decred/dcrd/pull/2187))
+- connmgr: Remove deprecated SeedFromDNS func ([decred/dcrd#2187](https://github.com/decred/dcrd/pull/2187))
+- connmgr: Remove deprecated TorLookupIP func ([decred/dcrd#2187](https://github.com/decred/dcrd/pull/2187))
+- connmgr: Rename TorLookupIPContext to TorLookupIP ([decred/dcrd#2191](https://github.com/decred/dcrd/pull/2191))
+- connmgr: Rework HTTPS seeding ([decred/dcrd#2188](https://github.com/decred/dcrd/pull/2188))
+- server: ban peers on wire protocol errors ([decred/dcrd#2110](https://github.com/decred/dcrd/pull/2110))
+- dcrd: use a context w/ timeout when fetching seeds ([decred/dcrd#2337](https://github.com/decred/dcrd/pull/2337))
+- multi: Add decentralized treasury support ([decred/dcrd#2170](https://github.com/decred/dcrd/pull/2170))
+- wire: Introduce InitState messages ([decred/dcrd#2349](https://github.com/decred/dcrd/pull/2349))
+- peer: Handle InitState messages ([decred/dcrd#2349](https://github.com/decred/dcrd/pull/2349))
+- server: Send and respond to InitState msgs ([decred/dcrd#2349](https://github.com/decred/dcrd/pull/2349))
+- connmgr: limit addresses returned by seeders ([decred/dcrd#2337](https://github.com/decred/dcrd/pull/2337))
+- connmgr: Enforce max http seeder response size ([decred/dcrd#2338](https://github.com/decred/dcrd/pull/2338))
+- chaincfg: Make simnet votes standard txs ([decred/dcrd#2348](https://github.com/decred/dcrd/pull/2348))
+- server: Check whitelist before ban on read errs ([decred/dcrd#2362](https://github.com/decred/dcrd/pull/2362))
+- server: Consolidate ban disable/whitelist logic ([decred/dcrd#2363](https://github.com/decred/dcrd/pull/2363))
+- blockmanager: handle notfound messages from peers ([decred/dcrd#2253](https://github.com/decred/dcrd/pull/2253))
+- blockmanager: limit the requested maps ([decred/dcrd#2253](https://github.com/decred/dcrd/pull/2253))
+- server: increase ban score for notfound messages ([decred/dcrd#2253](https://github.com/decred/dcrd/pull/2253))
+- server: return whether addBanScore disconnected the peer ([decred/dcrd#2253](https://github.com/decred/dcrd/pull/2253))
+- blockchain: Whitelist DCP0005 violations ([decred/dcrd#2533](https://github.com/decred/dcrd/pull/2533))
+
+### Transaction relay (memory pool):
+
+- mempool: Implement orphan expiration ([decred/dcrd#1974](https://github.com/decred/dcrd/pull/1974))
+- mempool: Associated tag with orphan txns ([decred/dcrd#1982](https://github.com/decred/dcrd/pull/1982))
+- mempool: Expose RemoveOrphansByTag function ([decred/dcrd#1982](https://github.com/decred/dcrd/pull/1982))
+- server/mempool: Evict orphans on peer disconnect ([decred/dcrd#1982](https://github.com/decred/dcrd/pull/1982))
+- mempool: Modify default orphan tx policy ([decred/dcrd#1984](https://github.com/decred/dcrd/pull/1984))
+- mempool: Tighten allowed votes range for mainnet ([decred/dcrd#2047](https://github.com/decred/dcrd/pull/2047))
+- multi: Track tickets with non-approved inputs ([decred/dcrd#1852](https://github.com/decred/dcrd/pull/1852))
+- mempool: Remove deprecated ErrToRejectErr func ([decred/dcrd#2273](https://github.com/decred/dcrd/pull/2273))
+- mempool: Remove deprecated tx rule err reject code ([decred/dcrd#2273](https://github.com/decred/dcrd/pull/2273))
+- mempool: Track tspends separately ([decred/dcrd#2350](https://github.com/decred/dcrd/pull/2350))
+- mempool: Special case tspends for insertion ([decred/dcrd#2350](https://github.com/decred/dcrd/pull/2350))
+- mempool: Fix wrong tx type in error message ([decred/dcrd#2350](https://github.com/decred/dcrd/pull/2350))
+- dcrd: trickle mempool response to peer ([decred/dcrd#2359](https://github.com/decred/dcrd/pull/2359))
+- mempool: Allow treasury txn vers as standard ([decred/dcrd#2412](https://github.com/decred/dcrd/pull/2412))
+- mempool: Limit ancestor tracking in mempool ([decred/dcrd#2468](https://github.com/decred/dcrd/pull/2468))
+
+### Mining:
+
+- mining: Introduce PriorityInputser interface ([decred/dcrd#1966](https://github.com/decred/dcrd/pull/1966))
+- mining: Correct priority calcs for Decred sizes ([decred/dcrd#1967](https://github.com/decred/dcrd/pull/1967))
+- cpuminer: convert from a quit channel to a context ([decred/dcrd#1978](https://github.com/decred/dcrd/pull/1978))
+- mining: Prevent potential shutdown hang ([decred/dcrd#2196](https://github.com/decred/dcrd/pull/2196))
+- mining: Improve comment for UpdateBlockTime ([decred/dcrd#2276](https://github.com/decred/dcrd/pull/2276))
+- cpuminer: Refactor code to its own package ([decred/dcrd#2276](https://github.com/decred/dcrd/pull/2276))
+- cpuminer: Rework to use bg template generator ([decred/dcrd#2277](https://github.com/decred/dcrd/pull/2277))
+- cpuminer: Improve already discrete mining error ([decred/dcrd#2341](https://github.com/decred/dcrd/pull/2341))
+- mining: Remove unneeded disapproval check ([decred/dcrd#2397](https://github.com/decred/dcrd/pull/2397))
+- mining: Add ticket exhaustion check ([decred/dcrd#2398](https://github.com/decred/dcrd/pull/2398))
+- mempool/mining: Implement aggregate fee sorting ([decred/dcrd#1829](https://github.com/decred/dcrd/pull/1829))
+- multi: Decouple blockManager from mining ([decred/dcrd#1965](https://github.com/decred/dcrd/pull/1965))
+- multi: Hide CPUMiner WaitGroup ([decred/dcrd#1965](https://github.com/decred/dcrd/pull/1965))
+- multi: Move mining code into mining package ([decred/dcrd#1965](https://github.com/decred/dcrd/pull/1965))
+- mining: Remove unused methods ([decred/dcrd#2419](https://github.com/decred/dcrd/pull/2419))
+- mining: Update to latest block vers for trsy vote ([decred/dcrd#2402](https://github.com/decred/dcrd/pull/2402))
+- multi: add rpcserver.CPUMiner ([decred/dcrd#2286](https://github.com/decred/dcrd/pull/2286))
+- mining: Prevent panic in child prio item handling ([decred/dcrd#2435](https://github.com/decred/dcrd/pull/2435))
+
+### RPC:
+
+- rpcserver: decouple from server ([decred/dcrd#1730](https://github.com/decred/dcrd/pull/1730))
+- rpcserver: refactor listener logic to server ([decred/dcrd#1734](https://github.com/decred/dcrd/pull/1734))
+- rpcserver: Start separate internal package impl ([decred/dcrd#1954](https://github.com/decred/dcrd/pull/1954))
+- rpcserver: Move rpc connmgr iface to internal pkg ([decred/dcrd#1954](https://github.com/decred/dcrd/pull/1954))
+- rpcserver: Move rpc syncmgr iface to internal pkg ([decred/dcrd#1954](https://github.com/decred/dcrd/pull/1954))
+- rpcserver: Add logging to internal package ([decred/dcrd#1954](https://github.com/decred/dcrd/pull/1954))
+- rpcserver: Add basic initial package documentation ([decred/dcrd#1954](https://github.com/decred/dcrd/pull/1954))
+- rpcserver: Cleanup getvoteinfo RPC ([decred/dcrd#1964](https://github.com/decred/dcrd/pull/1964))
+- rpcclient: add automatic pinging ([decred/dcrd#1898](https://github.com/decred/dcrd/pull/1898))
+- rpcserver: Bump to 6.1.1 ([decred/dcrd#1970](https://github.com/decred/dcrd/pull/1970))
+- rpcserver: Warn on alt DNS names when certs exist ([decred/dcrd#1971](https://github.com/decred/dcrd/pull/1971))
+- rpcserver: replace close channel with context ([decred/dcrd#1976](https://github.com/decred/dcrd/pull/1976))
+- websocket: attach context to inHandler ([decred/dcrd#1976](https://github.com/decred/dcrd/pull/1976))
+- multi: add gettxoutsetinfo JSON-RPC ([decred/dcrd#1909](https://github.com/decred/dcrd/pull/1909))
+- rpcserver: Move error check for generate RPC ([decred/dcrd#1977](https://github.com/decred/dcrd/pull/1977))
+- rpcserver: add ping and pong handers ([decred/dcrd#1995](https://github.com/decred/dcrd/pull/1995))
+- multi: Introduce regentemplate command ([decred/dcrd#1979](https://github.com/decred/dcrd/pull/1979))
+- rpcwebsocket: Remove client from missed maps ([decred/dcrd#2027](https://github.com/decred/dcrd/pull/2027))
+- rpcwebsocket: Use nonblocking messages and ntfns ([decred/dcrd#2026](https://github.com/decred/dcrd/pull/2026))
+- multi: fix rpc listener error ([decred/dcrd#2065](https://github.com/decred/dcrd/pull/2065))
+- rpcserver: Correctly assign TxIn amounts ([decred/dcrd#2071](https://github.com/decred/dcrd/pull/2071))
+- rpcclient: use NewRequestWithContext ([decred/dcrd#2101](https://github.com/decred/dcrd/pull/2101))
+- rpcclient: Resurrect validateaddress/verifymessage ([decred/dcrd#2205](https://github.com/decred/dcrd/pull/2205))
+- rpcclient: Stop client on ctx done ([decred/dcrd#2198](https://github.com/decred/dcrd/pull/2198))
+- rpcclient: Add a lifetime to requests ([decred/dcrd#2198](https://github.com/decred/dcrd/pull/2198))
+- rpc: Add AddrIndex and TxIndex bools to getinfo ([decred/dcrd#2207](https://github.com/decred/dcrd/pull/2207))
+- rpcserver: Avoid panic during hash decode ([decred/dcrd#2213](https://github.com/decred/dcrd/pull/2213))
+- rpcserver: Internal err on gettxout utxo fetch err ([decred/dcrd#2214](https://github.com/decred/dcrd/pull/2214))
+- rpcserver: Correct JSON-RPC request unmarshal ([decred/dcrd#2218](https://github.com/decred/dcrd/pull/2218))
+- rpcserver: Limit getstakeversioninfo count ([decred/dcrd#2221](https://github.com/decred/dcrd/pull/2221))
+- rpcclient: Reregister work ntfns on reconnect ([decred/dcrd#2228](https://github.com/decred/dcrd/pull/2228))
+- rpcserver: Remove global config dependency ([decred/dcrd#2228](https://github.com/decred/dcrd/pull/2228))
+- rpcserver: Remove server.go dependencies ([decred/dcrd#2228](https://github.com/decred/dcrd/pull/2228))
+- rpcserver: Remove log config dependencies ([decred/dcrd#2228](https://github.com/decred/dcrd/pull/2228))
+- rpcserver: Remove PeerNotifier dependency ([decred/dcrd#2228](https://github.com/decred/dcrd/pull/2228))
+- rpcserver: Handle genesis in getblockchaininfo ([decred/dcrd#2237](https://github.com/decred/dcrd/pull/2237))
+- rpcserver: Export RPC server, config, and new ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- rpcserver: Export rpcwebsocket Notify functions ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- rpcserver: Move genCertPair to server.go ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- rpcserver: Rename RpcserverConfig to Config ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- rpcserver: Rename NewRPCServer to New ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- rpcserver: Rename RPCServer to Server ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- rpcserver: Remove math/rand init and import ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- multi: add SanityChecker interface ([decred/dcrd#2289](https://github.com/decred/dcrd/pull/2289))
+- rpcserver: Use func for semver string ([decred/dcrd#2290](https://github.com/decred/dcrd/pull/2290))
+- rpcserver: Use internal quit chan for ws sync ([decred/dcrd#2297](https://github.com/decred/dcrd/pull/2297))
+- rpcserver: Sort getpeerinfo results by ID ([decred/dcrd#2311](https://github.com/decred/dcrd/pull/2311))
+- rpcserver: Add Filterer and FiltererV2 interfaces ([decred/dcrd#2312](https://github.com/decred/dcrd/pull/2312))
+- rpcserver: Add exists upper bounds TODOs ([decred/dcrd#2291](https://github.com/decred/dcrd/pull/2291))
+- multi: Fix incorrect RPC comments ([decred/dcrd#2332](https://github.com/decred/dcrd/pull/2332))
+- server: Remove unnecessary rpcadaptors ([decred/dcrd#2347](https://github.com/decred/dcrd/pull/2347))
+- jsonrpc/types: Register rebroadcast as websocket ([decred/dcrd#2355](https://github.com/decred/dcrd/pull/2355))
+- jsonrpc: Add gettreasuryspendvotes types ([decred/dcrd#2351](https://github.com/decred/dcrd/pull/2351))
+- rpcclient: Add GetTreasurySpendVotes command ([decred/dcrd#2351](https://github.com/decred/dcrd/pull/2351))
+- rpcserver: Add support for gettreasuryspendvotes ([decred/dcrd#2351](https://github.com/decred/dcrd/pull/2351))
+- rpcserver: Forward HTTP server err msgs to logger ([decred/dcrd#2378](https://github.com/decred/dcrd/pull/2378))
+- rpcserver: Add searchrawtransactions count limit ([decred/dcrd#2386](https://github.com/decred/dcrd/pull/2386))
+- rpcserver: Fix race in TestHandleTSpendVotes ([decred/dcrd#2393](https://github.com/decred/dcrd/pull/2393))
+- rpcserver: Correct known wallet method handling ([decred/dcrd#2416](https://github.com/decred/dcrd/pull/2416))
+- rpcserver: Update known wallet RPC methods ([decred/dcrd#2416](https://github.com/decred/dcrd/pull/2416))
+- multi: Add TAdd support to getrawmempool ([decred/dcrd#2448](https://github.com/decred/dcrd/pull/2448))
+- config: Use the P-256 curve by default for RPC ([decred/dcrd#2459](https://github.com/decred/dcrd/pull/2459))
+- rpcserver: Correct getpeerinfo for peers w/o conn ([decred/dcrd#2465](https://github.com/decred/dcrd/pull/2465))
+- rpcserver: Correct treasury vote status handling ([decred/dcrd#2469](https://github.com/decred/dcrd/pull/2469))
+- multi: Add tx inputs treasurybase RPC support ([decred/dcrd#2470](https://github.com/decred/dcrd/pull/2470))
+- multi: Add tx inputs treasuryspend RPC support ([decred/dcrd#2472](https://github.com/decred/dcrd/pull/2472))
+- rpcserver: Fix count tspend votes in mined block ([decred/dcrd#2565](https://github.com/decred/dcrd/pull/2565))
+
+### dcrd command-line flags and configuration:
+
+- server: Add tlscurve config parameter ([decred/dcrd#1983](https://github.com/decred/dcrd/pull/1983))
+- config: Add flag to allow unsynced testnet mining ([decred/dcrd#2023](https://github.com/decred/dcrd/pull/2023))
+- config: add --dialtimeout defaulting to 30 seconds ([decred/dcrd#2068](https://github.com/decred/dcrd/pull/2068))
+- multi: add --peeridletimeout defaulting to 120s ([decred/dcrd#2067](https://github.com/decred/dcrd/pull/2067))
+
+### gencerts utility changes:
+
+- gencerts: Rewrite for additional use cases ([decred/dcrd#2425](https://github.com/decred/dcrd/pull/2425))
+- gencerts: Add missing newline for unknown algorithm error ([decred/dcrd#2427](https://github.com/decred/dcrd/pull/2427))
+- gencerts: Use the P-256 curve by default ([decred/dcrd#2461](https://github.com/decred/dcrd/pull/2461))
+
+### dcrctl utility changes:
+
+- multi: Split dcrctl to own repo and update docs ([decred/dcrd#2175](https://github.com/decred/dcrd/pull/2175))
+
+### Documentation:
+
+- rpcserver: Refactor and update documentation ([decred/dcrd#2066](https://github.com/decred/dcrd/pull/2066))
+- multi: replace godoc.org with pkg.go.dev ([decred/dcrd#2091](https://github.com/decred/dcrd/pull/2091))
+- LICENSE: update year ([decred/dcrd#2092](https://github.com/decred/dcrd/pull/2092))
+- hdkeychain: Fix references to methods in package docs ([decred/dcrd#2115](https://github.com/decred/dcrd/pull/2115))
+- secp256k1: Update field val docs to public facing ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- schnorr: Add README.md ([decred/dcrd#2149](https://github.com/decred/dcrd/pull/2149))
+- schnorr: Add doc.go ([decred/dcrd#2149](https://github.com/decred/dcrd/pull/2149))
+- ecdsa: Correct README.md documentation links ([decred/dcrd#2165](https://github.com/decred/dcrd/pull/2165))
+- secp256k1: Update README.md and doc.go ([decred/dcrd#2166](https://github.com/decred/dcrd/pull/2166))
+- docs: Update README.md to reflect reality ([decred/dcrd#2168](https://github.com/decred/dcrd/pull/2168))
+- schnorr: Correct a couple of typos in README.md ([decred/dcrd#2169](https://github.com/decred/dcrd/pull/2169))
+- docs: Clarify README.md installation guides ([decred/dcrd#2171](https://github.com/decred/dcrd/pull/2171))
+- docs: Remove outdated btcd refs from README.md ([decred/dcrd#2172](https://github.com/decred/dcrd/pull/2172))
+- docs: Remove stray trailing spaces in README.md ([decred/dcrd#2172](https://github.com/decred/dcrd/pull/2172))
+- docs: Update Code Contribution Guidelines ([decred/dcrd#2200](https://github.com/decred/dcrd/pull/2200))
+- docs: Update links to avoid redirects ([decred/dcrd#2201](https://github.com/decred/dcrd/pull/2201))
+- docs: Update JSON-RPC spec link to latest ([decred/dcrd#2216](https://github.com/decred/dcrd/pull/2216))
+- docs: Fix chaingen broken markdown link ([decred/dcrd#2226](https://github.com/decred/dcrd/pull/2226))
+- indexers: Fix existsaddridx description ([decred/dcrd#2234](https://github.com/decred/dcrd/pull/2234))
+- docs: Update for removal of mempool module ([decred/dcrd#2274](https://github.com/decred/dcrd/pull/2274))
+- docs: Update for removal of mining module ([decred/dcrd#2275](https://github.com/decred/dcrd/pull/2275))
+- docs: Update for removal of fees module ([decred/dcrd#2287](https://github.com/decred/dcrd/pull/2287))
+- docs: Add documentation for getcfilterheader ([decred/dcrd#2312](https://github.com/decred/dcrd/pull/2312))
+- rpcserver: Document v1 cfilters as deprecated ([decred/dcrd#2314](https://github.com/decred/dcrd/pull/2314))
+- docs: Add several historical release notes ([decred/dcrd#2317](https://github.com/decred/dcrd/pull/2317))
+- contrib: Add README.md ([decred/dcrd#2312](https://github.com/decred/dcrd/pull/2312))
+- multi: Add simnet documentation and setup script ([decred/dcrd#2315](https://github.com/decred/dcrd/pull/2315))
+- docs: Document additional ws notifications ([decred/dcrd#2316](https://github.com/decred/dcrd/pull/2316))
+- contrib: Move service config examples to contrib ([decred/dcrd#2317](https://github.com/decred/dcrd/pull/2317))
+- peer: Update README.md/doc.go to reflect reality ([decred/dcrd#2325](https://github.com/decred/dcrd/pull/2325))
+- docs: Update README.md to require Go 1.14/1.15 ([decred/dcrd#2335](https://github.com/decred/dcrd/pull/2335))
+- docs: Update searchrawtransactions JSON-RPC docs ([decred/dcrd#2330](https://github.com/decred/dcrd/pull/2330))
+- sampleconfig: Make constant a function instead ([decred/dcrd#2340](https://github.com/decred/dcrd/pull/2340))
+- docs: Add release notes for v1.5.2 ([decred/dcrd#2346](https://github.com/decred/dcrd/pull/2346))
+- docs: Update rebroadcast JSON-RPC docs ([decred/dcrd#2355](https://github.com/decred/dcrd/pull/2355))
+- docs: Update README CLI suite link to ref latest ([decred/dcrd#2361](https://github.com/decred/dcrd/pull/2361))
+- docs: Add missing gettreasurybalance documentation ([decred/dcrd#2351](https://github.com/decred/dcrd/pull/2351))
+- contrib: More restrictive dcrd service privileges ([decred/dcrd#2357](https://github.com/decred/dcrd/pull/2357))
+- docs: Update for connmgr v3 module ([decred/dcrd#2376](https://github.com/decred/dcrd/pull/2376))
+- docs: Update for dcrec/secp256k1/v3 module ([decred/dcrd#2377](https://github.com/decred/dcrd/pull/2377))
+- docs: Update for chaincfg v3 module ([decred/dcrd#2381](https://github.com/decred/dcrd/pull/2381))
+- docs: Update for dcrutil v3 module ([decred/dcrd#2383](https://github.com/decred/dcrd/pull/2383))
+- docs: Update for txscript v3 module ([decred/dcrd#2384](https://github.com/decred/dcrd/pull/2384))
+- docs: Update for hdkeychain v3 module ([decred/dcrd#2392](https://github.com/decred/dcrd/pull/2392))
+- docs: Update for blockchain/standalone v2 module ([decred/dcrd#2395](https://github.com/decred/dcrd/pull/2395))
+- docs: Update simnet env docs for ticket exhaustion ([decred/dcrd#2403](https://github.com/decred/dcrd/pull/2403))
+- docs: Update JSON-RPC API examples ([decred/dcrd#2404](https://github.com/decred/dcrd/pull/2404))
+- docs: Update for blockchain/stake v3 module ([decred/dcrd#2418](https://github.com/decred/dcrd/pull/2418))
+- docs: Update for peer/v2 module ([decred/dcrd#2422](https://github.com/decred/dcrd/pull/2422))
+- docs: Update for rpcclient/v6 module ([decred/dcrd#2423](https://github.com/decred/dcrd/pull/2423))
+- docs: Update for blockchain v3 module ([decred/dcrd#2424](https://github.com/decred/dcrd/pull/2424))
+- docs: Update several JSON-RPC APIs ([decred/dcrd#2470](https://github.com/decred/dcrd/pull/2470))
+- docs: Update several JSON-RPC APIs ([decred/dcrd#2472](https://github.com/decred/dcrd/pull/2472))
+
+### Developer-related package and module changes:
+
+- blockmanager: remove serverPeer from blockmanager completely ([decred/dcrd#1735](https://github.com/decred/dcrd/pull/1735))
+- txscript: Add signature type to KeyClosure API ([decred/dcrd#1961](https://github.com/decred/dcrd/pull/1961))
+- server: Convert lifecycle to context ([decred/dcrd#1952](https://github.com/decred/dcrd/pull/1952))
+- dcrutil: drop chainec ([decred/dcrd#1957](https://github.com/decred/dcrd/pull/1957))
+- txscript: drop chainec ([decred/dcrd#1957](https://github.com/decred/dcrd/pull/1957))
+- blockchain: drop chainec ([decred/dcrd#1957](https://github.com/decred/dcrd/pull/1957))
+- mempool: drop chainec ([decred/dcrd#1957](https://github.com/decred/dcrd/pull/1957))
+- blockchain: removed unused params ([decred/dcrd#1973](https://github.com/decred/dcrd/pull/1973))
+- blockchain: Decouple indexers from blockchain ([decred/dcrd#1968](https://github.com/decred/dcrd/pull/1968))
+- indexers: Use spend journal for index catchup ([decred/dcrd#1969](https://github.com/decred/dcrd/pull/1969))
+- blockchain: replace scriptval quit channel with context ([decred/dcrd#1991](https://github.com/decred/dcrd/pull/1991))
+- indexers: Remove unused code ([decred/dcrd#1987](https://github.com/decred/dcrd/pull/1987))
+- chaincfg: Gate mustPayout with subsidy generation ([decred/dcrd#1988](https://github.com/decred/dcrd/pull/1988))
+- database: Remove unused code ([decred/dcrd#1989](https://github.com/decred/dcrd/pull/1989))
+- edwards: Remove unused code ([decred/dcrd#1990](https://github.com/decred/dcrd/pull/1990))
+- dcrd: attach shutdown context to listeners ([decred/dcrd#1992](https://github.com/decred/dcrd/pull/1992))
+- blockchain: Remove unconfigurable chain var ([decred/dcrd#1996](https://github.com/decred/dcrd/pull/1996))
+- multi: remove global activeNetParams ([decred/dcrd#1999](https://github.com/decred/dcrd/pull/1999))
+- lru: add kv cache ([decred/dcrd#2002](https://github.com/decred/dcrd/pull/2002))
+- sampleconfig: add export dcrctl sample config ([decred/dcrd#2003](https://github.com/decred/dcrd/pull/2003))
+- blockmanager: Simplify dynamic peer height updates ([decred/dcrd#1998](https://github.com/decred/dcrd/pull/1998))
+- indexers: convert to contexts ([decred/dcrd#1985](https://github.com/decred/dcrd/pull/1985))
+- blockchain: Rename KnownValid to HasValidated ([decred/dcrd#1997](https://github.com/decred/dcrd/pull/1997))
+- blockchain: Remove unused error from HaveBlock ([decred/dcrd#2007](https://github.com/decred/dcrd/pull/2007))
+- blockchain: Use skip list for ancestor traversal ([decred/dcrd#2010](https://github.com/decred/dcrd/pull/2010))
+- multi: Decouple orphan handling from blockchain ([decred/dcrd#2008](https://github.com/decred/dcrd/pull/2008))
+- blockchain: Remove easiest diff checkpoint checks ([decred/dcrd#2012](https://github.com/decred/dcrd/pull/2012))
+- blockchain: Make checkpoints configurable ([decred/dcrd#2013](https://github.com/decred/dcrd/pull/2013))
+- config: Use TorLookupIPContext ([decred/dcrd#2021](https://github.com/decred/dcrd/pull/2021))
+- bech32: Ensure HRP is lowercase when encoding ([decred/dcrd#2024](https://github.com/decred/dcrd/pull/2024))
+- bech32: Add base256 conversion convenience funcs ([decred/dcrd#2025](https://github.com/decred/dcrd/pull/2025))
+- blockchain: Explicit hash in next work diff calcs ([decred/dcrd#2022](https://github.com/decred/dcrd/pull/2022))
+- blockchain: Remove unused CalcNextRequiredDiffNode ([decred/dcrd#2022](https://github.com/decred/dcrd/pull/2022))
+- blockmanager: Remove unused diff calc code ([decred/dcrd#2022](https://github.com/decred/dcrd/pull/2022))
+- blockchain: Support hdr checkpoints and simplify ([decred/dcrd#2014](https://github.com/decred/dcrd/pull/2014))
+- txscript: Optimize conditional execution mem usage ([decred/dcrd#2011](https://github.com/decred/dcrd/pull/2011))
+- fix regenHandler shutdown ([decred/dcrd#2041](https://github.com/decred/dcrd/pull/2041))
+- secp256k1: Remove unused chainec code ([decred/dcrd#2042](https://github.com/decred/dcrd/pull/2042))
+- secp256k1: Consistent function formatting ([decred/dcrd#2044](https://github.com/decred/dcrd/pull/2044))
+- secp256k1: Optimize NonceRFC6979 ([decred/dcrd#2044](https://github.com/decred/dcrd/pull/2044))
+- secp256k1: Never fail signing ([decred/dcrd#2044](https://github.com/decred/dcrd/pull/2044))
+- schnorr: Remove unused threshold code ([decred/dcrd#2045](https://github.com/decred/dcrd/pull/2045))
+- rpcclient: add context ([decred/dcrd#1980](https://github.com/decred/dcrd/pull/1980))
+- multi: replace GetScriptClass consensus calls ([decred/dcrd#2031](https://github.com/decred/dcrd/pull/2031))
+- secp256k1: Split funcs for crypto/elliptic iface ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make params standalone ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Rename generation related code ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Move big int to field adaptor code ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make point doubling funcs standalone ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make point addition funcs standalone ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Move group operations to new curve.go ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Remove unnecessary QPlus1Div4 export ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make endormophism bits standalone ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Decouple internals from ecdsa.PublicKey ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Decouple signing from ecdsa.PrivateKey ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make k splitting func standalone ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make k mod reduce func standalone ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Move naf func to curve file ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Refactor isOnCurve logic from adaptor ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Refactor scalar mult logic from adaptor ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make private key independent type ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Make public key independent type ([decred/dcrd#2056](https://github.com/decred/dcrd/pull/2056))
+- secp256k1: Introduce jacobian point struct ([decred/dcrd#2057](https://github.com/decred/dcrd/pull/2057))
+- secp256k1: Implement direct signature verification ([decred/dcrd#2058](https://github.com/decred/dcrd/pull/2058))
+- secp256k1: Add specialized field check for one ([decred/dcrd#2059](https://github.com/decred/dcrd/pull/2059))
+- multi: Convert rpcserver lifecycle to context ([decred/dcrd#2043](https://github.com/decred/dcrd/pull/2043))
+- txscript: Don't use GetScriptClass in consensus ([decred/dcrd#2070](https://github.com/decred/dcrd/pull/2070))
+- txscript: Remove unused isStakeOutput function ([decred/dcrd#2070](https://github.com/decred/dcrd/pull/2070))
+- multi:  define wire error types ([decred/dcrd#2055](https://github.com/decred/dcrd/pull/2055))
+- hdkeychain: Provide SerializedPubKey method ([decred/dcrd#2073](https://github.com/decred/dcrd/pull/2073))
+- dcrutil: Provide privkey access for WIFs ([decred/dcrd#2078](https://github.com/decred/dcrd/pull/2078))
+- hdkeychain: Remove ECPubKey ([decred/dcrd#2080](https://github.com/decred/dcrd/pull/2080))
+- dcrutil: Use intended method names ([decred/dcrd#2079](https://github.com/decred/dcrd/pull/2079))
+- hdkeychain: ECPrivKey -> SerializedPrivKey ([decred/dcrd#2081](https://github.com/decred/dcrd/pull/2081))
+- hdkeychain: Use direct hashes and remove dcrutil dep ([decred/dcrd#2086](https://github.com/decred/dcrd/pull/2086))
+- stake: Remove exported FindTicketIdxs ([decred/dcrd#2089](https://github.com/decred/dcrd/pull/2089))
+- secp256k1: Add fixed-precision group order type ([decred/dcrd#2060](https://github.com/decred/dcrd/pull/2060))
+- secp256k1: Make private key opaque ([decred/dcrd#2061](https://github.com/decred/dcrd/pull/2061))
+- secp256k1: Follow RFC6979 for too large nonce data ([decred/dcrd#2062](https://github.com/decred/dcrd/pull/2062))
+- secp256k1: Return new scalar from NonceRFC6979 ([decred/dcrd#2063](https://github.com/decred/dcrd/pull/2063))
+- secp256k1: Use new mod n scalar in ec mults ([decred/dcrd#2064](https://github.com/decred/dcrd/pull/2064))
+- secp256k1: Add non-const inverse for mod n scalar ([decred/dcrd#2072](https://github.com/decred/dcrd/pull/2072))
+- secp256k1: Optimize sig verify with mod n scalar ([decred/dcrd#2083](https://github.com/decred/dcrd/pull/2083))
+- secp256k1: Make signature opaque ([decred/dcrd#2084](https://github.com/decred/dcrd/pull/2084))
+- secp256k1: Use mod n scalar when signing ([decred/dcrd#2085](https://github.com/decred/dcrd/pull/2085))
+- secp256k1: Use mod n scalar in sig serialization ([decred/dcrd#2087](https://github.com/decred/dcrd/pull/2087))
+- secp256k1: Add optimized sqrt field calc ([decred/dcrd#2088](https://github.com/decred/dcrd/pull/2088))
+- secp256k1: Add field func to determine when >= P-N ([decred/dcrd#2093](https://github.com/decred/dcrd/pull/2093))
+- secp256k1: Use field val for y coord decompression ([decred/dcrd#2094](https://github.com/decred/dcrd/pull/2094))
+- secp256k1: Return num instead of bool for overflow ([decred/dcrd#2095](https://github.com/decred/dcrd/pull/2095))
+- secp256k1: Overhaul compact signatures ([decred/dcrd#2095](https://github.com/decred/dcrd/pull/2095))
+- schnorr: Zero internal bytes of big ints ([decred/dcrd#2103](https://github.com/decred/dcrd/pull/2103))
+- edwards: Zero internal bytes of big ints ([decred/dcrd#2104](https://github.com/decred/dcrd/pull/2104))
+- secp256k1: Remove BER signature parsing ([decred/dcrd#2105](https://github.com/decred/dcrd/pull/2105))
+- secp256k1: Rework DER signature parsing code ([decred/dcrd#2106](https://github.com/decred/dcrd/pull/2106))
+- connmgr: Fix dynamic ban score stringer deadlock ([decred/dcrd#2114](https://github.com/decred/dcrd/pull/2114))
+- secp256k1: Use mod n scalar in signature type ([decred/dcrd#2107](https://github.com/decred/dcrd/pull/2107))
+- secp256k1: Make public keys opaque ([decred/dcrd#2108](https://github.com/decred/dcrd/pull/2108))
+- main: Use errors api and require go 1.13+ ([decred/dcrd#2096](https://github.com/decred/dcrd/pull/2096))
+- stake: Use errors api and require go 1.13 ([decred/dcrd#2097](https://github.com/decred/dcrd/pull/2097))
+- blockchain: Use errors api and require go 1.13+ ([decred/dcrd#2098](https://github.com/decred/dcrd/pull/2098))
+- hdkeychain: Remove Neuter error return ([decred/dcrd#2116](https://github.com/decred/dcrd/pull/2116))
+- secp256k1: Add Zero method to private key ([decred/dcrd#2117](https://github.com/decred/dcrd/pull/2117))
+- schnorr: Remove unused pubkey recovery bits ([decred/dcrd#2120](https://github.com/decred/dcrd/pull/2120))
+- schnorr: Remove deprecated chainec methods ([decred/dcrd#2122](https://github.com/decred/dcrd/pull/2122))
+- schnorr: Remove GetCode method from Error type ([decred/dcrd#2123](https://github.com/decred/dcrd/pull/2123))
+- schnorr: Remove generalized Verify ([decred/dcrd#2124](https://github.com/decred/dcrd/pull/2124))
+- schnorr: Make signature opaque ([decred/dcrd#2125](https://github.com/decred/dcrd/pull/2125))
+- schnorr: Move sig code to signature files ([decred/dcrd#2127](https://github.com/decred/dcrd/pull/2127))
+- schnorr: Remove unused internal signing params ([decred/dcrd#2121](https://github.com/decred/dcrd/pull/2121))
+- schnorr: Accept sig type in internal verify func ([decred/dcrd#2129](https://github.com/decred/dcrd/pull/2129))
+- schnorr: Remove internal hash func callback ([decred/dcrd#2130](https://github.com/decred/dcrd/pull/2130))
+- secp256k1: Reduce privkey copies ([decred/dcrd#2131](https://github.com/decred/dcrd/pull/2131))
+- schnorr: Remove unused GenerateKey ([decred/dcrd#2132](https://github.com/decred/dcrd/pull/2132))
+- mempool: Correct MaybeAcceptDependents mutex ([decred/dcrd#2135](https://github.com/decred/dcrd/pull/2135))
+- secp256k1: Avoid inversion in sig verify ([decred/dcrd#2118](https://github.com/decred/dcrd/pull/2118))
+- secp256k1: Reduce EC operation normalizes ([decred/dcrd#2119](https://github.com/decred/dcrd/pull/2119))
+- secp256k1: Remove unused q curve param ([decred/dcrd#2136](https://github.com/decred/dcrd/pull/2136))
+- secp256k1: Improve exported curve params ([decred/dcrd#2137](https://github.com/decred/dcrd/pull/2137))
+- secp256k1: Make field value set int take uint16 ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- secp256k1: Make field value add int take uint16 ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- secp256k1: Make field value mul int take uint8 ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- secp256k1: Make field set byte slice const time ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- secp256k1: Export field value type ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- secp256k1: Expose IsOddBit on field val type ([decred/dcrd#2138](https://github.com/decred/dcrd/pull/2138))
+- secp256k1: Expose IsOneBit on field val type ([decred/dcrd#2138](https://github.com/decred/dcrd/pull/2138))
+- secp256k1: Expose IsZeroBit on field val type ([decred/dcrd#2138](https://github.com/decred/dcrd/pull/2138))
+- schnorr: Remove internal verify func bool ret ([decred/dcrd#2142](https://github.com/decred/dcrd/pull/2142))
+- secp256k1: Export JacobianPoint type ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Export AddNonConst ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Export DoubleNonConst ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Export SclarMultNonConst ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Export ScalarBaseMultNonConst ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Export DecompressY ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Add AsJacobian method to pubkey ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Export scalar from PrivateKey ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1: Split nonce code into separate files ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- secp256k1/ecdsa: Decouple ECDSA from secp256k1 ([decred/dcrd#2139](https://github.com/decred/dcrd/pull/2139))
+- schnorr: Use extra data for RFC6979 nonces ([decred/dcrd#2143](https://github.com/decred/dcrd/pull/2143))
+- schnorr: Add error support for errors.Is/As ([decred/dcrd#2145](https://github.com/decred/dcrd/pull/2145))
+- hdkeychain: Use secp256k1 privkey to pubkey method ([decred/dcrd#2156](https://github.com/decred/dcrd/pull/2156))
+- secp256k1: Add overflow check to field val set ([decred/dcrd#2147](https://github.com/decred/dcrd/pull/2147))
+- schnorr: Rework signature parsing ([decred/dcrd#2148](https://github.com/decred/dcrd/pull/2148))
+- schnorr: Remove unused copyBytes func ([decred/dcrd#2148](https://github.com/decred/dcrd/pull/2148))
+- schnorr: Use specialized types when signing ([decred/dcrd#2150](https://github.com/decred/dcrd/pull/2150))
+- schnorr: Optimize sig verify with specialized types ([decred/dcrd#2151](https://github.com/decred/dcrd/pull/2151))
+- schnorr: Use specialized types in signature type ([decred/dcrd#2152](https://github.com/decred/dcrd/pull/2152))
+- schnorr: Remove unused error codes ([decred/dcrd#2153](https://github.com/decred/dcrd/pull/2153))
+- schnorr: Rename error codes to better match reality ([decred/dcrd#2153](https://github.com/decred/dcrd/pull/2153))
+- secp256k1: Add PutBytesUnchecked to FieldVal ([decred/dcrd#2154](https://github.com/decred/dcrd/pull/2154))
+- secp256k1: Add PutBytesUnchecked to ModNScalar ([decred/dcrd#2154](https://github.com/decred/dcrd/pull/2154))
+- hdkeychain: Use specialized secp256k1 types ([decred/dcrd#2157](https://github.com/decred/dcrd/pull/2157))
+- schnorr: Use PutBytesUnchecked for serialize ([decred/dcrd#2158](https://github.com/decred/dcrd/pull/2158))
+- ecdsa: Use PutBytesUnchecked for serialize ([decred/dcrd#2159](https://github.com/decred/dcrd/pull/2159))
+- secp256k1: Add pubkey parsing error infrastructure ([decred/dcrd#2160](https://github.com/decred/dcrd/pull/2160))
+- secp256k1: Add IsOnCurve method to PublicKey ([decred/dcrd#2162](https://github.com/decred/dcrd/pull/2162))
+- secp256k1: Use specialized types in public key ([decred/dcrd#2163](https://github.com/decred/dcrd/pull/2163))
+- schnorr: Add sign message example ([decred/dcrd#2164](https://github.com/decred/dcrd/pull/2164))
+- schnorr: Add verify signature example ([decred/dcrd#2164](https://github.com/decred/dcrd/pull/2164))
+- secp256k1: Optimize pubkey parse ([decred/dcrd#2167](https://github.com/decred/dcrd/pull/2167))
+- connmgr: Fix potential panic via RPC ([decred/dcrd#2177](https://github.com/decred/dcrd/pull/2177))
+- peer: Set a default idle timeout if not specified ([decred/dcrd#2180](https://github.com/decred/dcrd/pull/2180))
+- wire: Improve error handling ([decred/dcrd#2179](https://github.com/decred/dcrd/pull/2179))
+- rpcclient: Remove dcrwallet methods ([decred/dcrd#2178](https://github.com/decred/dcrd/pull/2178))
+- server: Remove unused interrupt chan param ([decred/dcrd#2186](https://github.com/decred/dcrd/pull/2186))
+- multi: CancelPending error for no pending conns ([decred/dcrd#2199](https://github.com/decred/dcrd/pull/2199))
+- connmgr: Convert lifecycle to context ([decred/dcrd#2195](https://github.com/decred/dcrd/pull/2195))
+- dcrutil: Add VerifyMessage API ([decred/dcrd#2203](https://github.com/decred/dcrd/pull/2203))
+- blocklogger: Always log when sync height reached ([decred/dcrd#2204](https://github.com/decred/dcrd/pull/2204))
+- connmgr: define connmgr error types ([decred/dcrd#2206](https://github.com/decred/dcrd/pull/2206))
+- connmgr: Finish recent connmgr err type additions ([decred/dcrd#2208](https://github.com/decred/dcrd/pull/2208))
+- stakeext: Fix comments on concurrency ([decred/dcrd#2210](https://github.com/decred/dcrd/pull/2210))
+- txscript: Add support for errors.Is/As ([decred/dcrd#2209](https://github.com/decred/dcrd/pull/2209))
+- secp256k1: Remove Encrypt/Decrypt functions ([decred/dcrd#2222](https://github.com/decred/dcrd/pull/2222))
+- rpcserver: Create Chain and UtxoEntry interfaces ([decred/dcrd#2211](https://github.com/decred/dcrd/pull/2211))
+- blockchain: Correct mempool view construction ([decred/dcrd#2232](https://github.com/decred/dcrd/pull/2232))
+- rpcserver: Correct adaptor for utxo entry fetch ([decred/dcrd#2233](https://github.com/decred/dcrd/pull/2233))
+- server: Log remote peer IP in several messages ([decred/dcrd#2233](https://github.com/decred/dcrd/pull/2233))
+- peer: Add IsKnownInventory ([decred/dcrd#2239](https://github.com/decred/dcrd/pull/2239))
+- txscript: Export several useful funcs for treasury ([decred/dcrd#2243](https://github.com/decred/dcrd/pull/2243))
+- peer: check all peer deadlines in the stall ticker ([decred/dcrd#2251](https://github.com/decred/dcrd/pull/2251))
+- txscript: Export script num type and constructor ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Export MathOpCodeMaxScriptNumLen ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Export CltvMaxScriptNumLen ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Export CsvMaxScriptNumLen ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Export IsSmallInt ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Export AsSmallInt ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Export ExtractScriptHash ([decred/dcrd#2240](https://github.com/decred/dcrd/pull/2240))
+- txscript: Remove deprecated code ([decred/dcrd#2241](https://github.com/decred/dcrd/pull/2241))
+- txscript: Optimize sig enc check with mod n scalar ([decred/dcrd#2246](https://github.com/decred/dcrd/pull/2246))
+- connmgr: Remain responsive with simul failed conns ([decred/dcrd#2254](https://github.com/decred/dcrd/pull/2254))
+- secp256k1: Harden const time field normalization ([decred/dcrd#2258](https://github.com/decred/dcrd/pull/2258))
+- rpcclient: Protect websocket connection with mutex ([decred/dcrd#2260](https://github.com/decred/dcrd/pull/2260))
+- wire: formatting fixes - no functional change ([decred/dcrd#2266](https://github.com/decred/dcrd/pull/2266))
+- wire: return detectable err from makeEmptyMessage ([decred/dcrd#2266](https://github.com/decred/dcrd/pull/2266))
+- blockchain: Rename last prune time field ([decred/dcrd#2294](https://github.com/decred/dcrd/pull/2294))
+- blockchain: Set pruning interval to tgt block time ([decred/dcrd#2294](https://github.com/decred/dcrd/pull/2294))
+- blockchain: Optimize stake node pruning ([decred/dcrd#2294](https://github.com/decred/dcrd/pull/2294))
+- txscript: Check equality via secp256k1 methods ([decred/dcrd#2299](https://github.com/decred/dcrd/pull/2299))
+- blockchain: Remove internal dbnamespace package ([decred/dcrd#2305](https://github.com/decred/dcrd/pull/2305))
+- txscript: Optimize alt stack drop ([decred/dcrd#2298](https://github.com/decred/dcrd/pull/2298))
+- txscript: Optimize trace logging ([decred/dcrd#2301](https://github.com/decred/dcrd/pull/2301))
+- peer: Optimize logging ([decred/dcrd#2303](https://github.com/decred/dcrd/pull/2303))
+- blockchain: Optimize chain tip tracking ([decred/dcrd#2302](https://github.com/decred/dcrd/pull/2302))
+- blockchain: Move stxo source to chain ([decred/dcrd#2304](https://github.com/decred/dcrd/pull/2304))
+- blockchain: Use static log funcs for static logs ([decred/dcrd#2321](https://github.com/decred/dcrd/pull/2321))
+- blockchain: Remove superfluous blockidx fields ([decred/dcrd#2321](https://github.com/decred/dcrd/pull/2321))
+- blockchain: Migration for v3 block index ([decred/dcrd#2321](https://github.com/decred/dcrd/pull/2321))
+- config: Categorize options in the code ([decred/dcrd#2320](https://github.com/decred/dcrd/pull/2320))
+- main: Unexport main package exports ([decred/dcrd#2339](https://github.com/decred/dcrd/pull/2339))
+- txscript: Correct JSON test data comment ([decred/dcrd#2354](https://github.com/decred/dcrd/pull/2354))
+- blockchain: Decentralized Treasury db migration ([decred/dcrd#2336](https://github.com/decred/dcrd/pull/2336))
+- blockchain: Add exported TSpendCountVotes func ([decred/dcrd#2351](https://github.com/decred/dcrd/pull/2351))
+- txscript: Add shortTxHash ([decred/dcrd#2358](https://github.com/decred/dcrd/pull/2358))
+- txscript: Store short tx hash in sigcache ([decred/dcrd#2358](https://github.com/decred/dcrd/pull/2358))
+- txscript: Proactively evict SigCache entries ([decred/dcrd#2358](https://github.com/decred/dcrd/pull/2358))
+- config: Consolidate error reporting ([decred/dcrd#2379](https://github.com/decred/dcrd/pull/2379))
+- dcrutil: Update example to avoid chaincfg dep ([decred/dcrd#2382](https://github.com/decred/dcrd/pull/2382))
+- blockchain: Remove need to RLock some treasury funcs ([decred/dcrd#2380](https://github.com/decred/dcrd/pull/2380))
+- multi: Fix treasury-related comments ([decred/dcrd#2380](https://github.com/decred/dcrd/pull/2380))
+- multi: update blockchain/standalone error types ([decred/dcrd#2380](https://github.com/decred/dcrd/pull/2380))
+- standalone: Retain coinbase detection semantics ([decred/dcrd#2391](https://github.com/decred/dcrd/pull/2391))
+- standalone: Introduce CalcTSpendWindow ([decred/dcrd#2389](https://github.com/decred/dcrd/pull/2389))
+- standalone: Rename CalcTSpendExpiry ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- standalone: IsTVI code consistency pass ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- standalone: Misc comment consistency cleanup ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- blockchain: Add ticket exhaustion check ([decred/dcrd#2398](https://github.com/decred/dcrd/pull/2398))
+- blockchain: Reject old block vers for tsry vote ([decred/dcrd#2400](https://github.com/decred/dcrd/pull/2400))
+- blockchain: Simplify old block ver upgrade checks ([decred/dcrd#2401](https://github.com/decred/dcrd/pull/2401))
+- multi: update blockchain and mempool error types ([decred/dcrd#2278](https://github.com/decred/dcrd/pull/2278))
+- blockchain/mempool: Update for recent err convrsn ([decred/dcrd#2421](https://github.com/decred/dcrd/pull/2421))
+- blockchain: Create treasury buckets during upgrade ([decred/dcrd#2441](https://github.com/decred/dcrd/pull/2441))
+- blockchain: Fix stxosToScriptSource ([decred/dcrd#2444](https://github.com/decred/dcrd/pull/2444))
+- blockchain: Make ver 5 to 6 db upgrades work again ([decred/dcrd#2446](https://github.com/decred/dcrd/pull/2446))
+- blockchain: Clear failed block flags for HF ([decred/dcrd#2447](https://github.com/decred/dcrd/pull/2447))
+- blockchain: Handle db upgrade paths for ver < 5 ([decred/dcrd#2449](https://github.com/decred/dcrd/pull/2449))
+- blockchain: No context dep checks for orphans ([decred/dcrd#2474](https://github.com/decred/dcrd/pull/2474))
+
+### Developer-related module management:
+
+- mining: Start v3 module dev cycle ([decred/dcrd#1955](https://github.com/decred/dcrd/pull/1955))
+- dcrutil: Start v3 module dev cycle ([decred/dcrd#1956](https://github.com/decred/dcrd/pull/1956))
+- txscript: Start v3 module dev cycle ([decred/dcrd#1958](https://github.com/decred/dcrd/pull/1958))
+- blockchain: Start v3 module dev cycle ([decred/dcrd#1959](https://github.com/decred/dcrd/pull/1959))
+- stake: Start v3 module dev cycle ([decred/dcrd#1960](https://github.com/decred/dcrd/pull/1960))
+- mempool: Start v4 module dev cycle ([decred/dcrd#1963](https://github.com/decred/dcrd/pull/1963))
+- connmgr: Start v3 module dev cycle ([decred/dcrd#1975](https://github.com/decred/dcrd/pull/1975))
+- multi: Use latest base58 module ([decred/dcrd#2016](https://github.com/decred/dcrd/pull/2016))
+- dcrctl: Update dcrwallet RPC types package ([decred/dcrd#2018](https://github.com/decred/dcrd/pull/2018))
+- multi: Update to prerel module release versions ([decred/dcrd#2032](https://github.com/decred/dcrd/pull/2032))
+- multi: switch to syndtr/goleveldb ([decred/dcrd#2034](https://github.com/decred/dcrd/pull/2034))
+- chaincfg: Start v3 module dev cycle ([decred/dcrd#2038](https://github.com/decred/dcrd/pull/2038))
+- chaincfg: Remove chainec package ([decred/dcrd#2039](https://github.com/decred/dcrd/pull/2039))
+- secp256k1: Start v3 module dev cycle ([decred/dcrd#2040](https://github.com/decred/dcrd/pull/2040))
+- rpcclient: Start v6 module dev cycle ([decred/dcrd#1980](https://github.com/decred/dcrd/pull/1980))
+- database, fees:  use latest leveldb ([decred/dcrd#2054](https://github.com/decred/dcrd/pull/2054))
+- multi: Update to prerel module release versions ([decred/dcrd#2074](https://github.com/decred/dcrd/pull/2074))
+- hdkeychain: Start v3 module dev cycle ([decred/dcrd#2076](https://github.com/decred/dcrd/pull/2076))
+- multi: Update all prerel module release versions ([decred/dcrd#2082](https://github.com/decred/dcrd/pull/2082))
+- multi: More prerel module release version updates ([decred/dcrd#2082](https://github.com/decred/dcrd/pull/2082))
+- multi: Round 3 prerel module release ver updates ([decred/dcrd#2082](https://github.com/decred/dcrd/pull/2082))
+- multi: Round 4 prerel module release ver updates ([decred/dcrd#2082](https://github.com/decred/dcrd/pull/2082))
+- chaincfg: Remove unused modules ([decred/dcrd#2144](https://github.com/decred/dcrd/pull/2144))
+- dcrutil: use errors api; require go 1.13+ ([decred/dcrd#2099](https://github.com/decred/dcrd/pull/2099))
+- mempool: use errors api; require go 1.13+ ([decred/dcrd#2100](https://github.com/decred/dcrd/pull/2100))
+- rpcclient: use errors api; require go 1.13+ ([decred/dcrd#2101](https://github.com/decred/dcrd/pull/2101))
+- txscript: use errors api; require go 1.13+ ([decred/dcrd#2102](https://github.com/decred/dcrd/pull/2102))
+- hdkeychain: Use errors api and require go 1.13+ ([decred/dcrd#2161](https://github.com/decred/dcrd/pull/2161))
+- wire: use std errors api ([decred/dcrd#2182](https://github.com/decred/dcrd/pull/2182))
+- rpcclient: bump to newer modules ([decred/dcrd#2190](https://github.com/decred/dcrd/pull/2190))
+- multi: Run go mod tidy on all modules ([decred/dcrd#2185](https://github.com/decred/dcrd/pull/2185))
+- main: Update go.mod for recent rpcclient bumps ([decred/dcrd#2194](https://github.com/decred/dcrd/pull/2194))
+- multi: Use latest base58 module ([decred/dcrd#2223](https://github.com/decred/dcrd/pull/2223))
+- standalone: Start v2 module dev cycle ([decred/dcrd#2224](https://github.com/decred/dcrd/pull/2224))
+- multi: go mod tidy cleanup and run in CI ([decred/dcrd#2225](https://github.com/decred/dcrd/pull/2225))
+- mempool: Move to internal ([decred/dcrd#2274](https://github.com/decred/dcrd/pull/2274))
+- mining: Move to internal ([decred/dcrd#2275](https://github.com/decred/dcrd/pull/2275))
+- rpcserver: Move to internal ([decred/dcrd#2288](https://github.com/decred/dcrd/pull/2288))
+- fees: Move to internal ([decred/dcrd#2287](https://github.com/decred/dcrd/pull/2287))
+- main: go mod tidy ([decred/dcrd#2367](https://github.com/decred/dcrd/pull/2367))
+- dcrjson: Prepare v3.1.0 ([decred/dcrd#2374](https://github.com/decred/dcrd/pull/2374))
+- addrmgr: Prepare v1.2.0 ([decred/dcrd#2375](https://github.com/decred/dcrd/pull/2375))
+- connmgr: Prepare v3.0.0 ([decred/dcrd#2376](https://github.com/decred/dcrd/pull/2376))
+- multi: Update chaincfg dependers to wire/v1.4.0 ([decred/dcrd#2381](https://github.com/decred/dcrd/pull/2381))
+- chaincfg: Prepare v3.0.0 ([decred/dcrd#2381](https://github.com/decred/dcrd/pull/2381))
+- dcrutil: Prepare v3.0.0 ([decred/dcrd#2383](https://github.com/decred/dcrd/pull/2383))
+- rpc/jsonrpc/types: Prepare v2.1.0 ([decred/dcrd#2385](https://github.com/decred/dcrd/pull/2385))
+- txscript: Prepare v3.0.0 ([decred/dcrd#2384](https://github.com/decred/dcrd/pull/2384))
+- blockchain: Update unreleased requires to master ([decred/dcrd#2364](https://github.com/decred/dcrd/pull/2364))
+- rpcclient: Update unreleased requires to master ([decred/dcrd#2369](https://github.com/decred/dcrd/pull/2369))
+- blockchain/standalone: Remove txscript dep ([decred/dcrd#2388](https://github.com/decred/dcrd/pull/2388))
+- database: Prepare v2.0.2 ([decred/dcrd#2387](https://github.com/decred/dcrd/pull/2387))
+- hdkeycahin: Prepare v3.0.0 ([decred/dcrd#2392](https://github.com/decred/dcrd/pull/2392))
+- blockchain/standalone: Prepare v2.0.0 ([decred/dcrd#2395](https://github.com/decred/dcrd/pull/2395))
+- blockchain/stake: Prepare v3.0.0 ([decred/dcrd#2418](https://github.com/decred/dcrd/pull/2418))
+- gcs: Prepare v2.1.0 ([decred/dcrd#2420](https://github.com/decred/dcrd/pull/2420))
+- peer: Prepare v2.2.0 ([decred/dcrd#2422](https://github.com/decred/dcrd/pull/2422))
+- rpcclient: Prepare v6.0.0 ([decred/dcrd#2423](https://github.com/decred/dcrd/pull/2423))
+- blockchain: Prepare v3.0.0 ([decred/dcrd#2424](https://github.com/decred/dcrd/pull/2424))
+- rpcclient: Prepare v6.0.1 ([decred/dcrd#2455](https://github.com/decred/dcrd/pull/2455))
+- main: Update to use all new module versions ([decred/dcrd#2426](https://github.com/decred/dcrd/pull/2426))
+- main: Remove module replacements ([decred/dcrd#2428](https://github.com/decred/dcrd/pull/2428))
+- main: Use backported module updates ([decred/dcrd#2456](https://github.com/decred/dcrd/pull/2456))
+
+### Testing and Quality Assurance:
+
+- build: update golangci-lint to v1.21.0 ([decred/dcrd#1951](https://github.com/decred/dcrd/pull/1951))
+- mining: Add priority calculation tests ([decred/dcrd#1967](https://github.com/decred/dcrd/pull/1967))
+- build: Add deadcode to linters for CI tests ([decred/dcrd#1993](https://github.com/decred/dcrd/pull/1993))
+- multi: Updates for staticcheck results ([decred/dcrd#1994](https://github.com/decred/dcrd/pull/1994))
+- blockchain: Separate processing order tests ([decred/dcrd#2004](https://github.com/decred/dcrd/pull/2004))
+- blockchain: Add benchmark for ancestor traversal ([decred/dcrd#2010](https://github.com/decred/dcrd/pull/2010))
+- multi: Address a bunch of lint issues ([decred/dcrd#2028](https://github.com/decred/dcrd/pull/2028))
+- build: golangci-lint v1.22.2 ([decred/dcrd#2029](https://github.com/decred/dcrd/pull/2029))
+- secpk256k1: Add benchmark for RFC6979 nonce gen ([decred/dcrd#2044](https://github.com/decred/dcrd/pull/2044))
+- secp256k1: Cleanup signature tests ([decred/dcrd#2048](https://github.com/decred/dcrd/pull/2048))
+- rpctest: adapt new API ([decred/dcrd#1980](https://github.com/decred/dcrd/pull/1980))
+- rpcserver: Add handlers test ([decred/dcrd#2066](https://github.com/decred/dcrd/pull/2066))
+- build: use golangci v1.23.6 ([decred/dcrd#2068](https://github.com/decred/dcrd/pull/2068))
+- rpctest: Update for hdkeychain API changes ([decred/dcrd#2092](https://github.com/decred/dcrd/pull/2092))
+- build: test against go 1.14 ([decred/dcrd#2092](https://github.com/decred/dcrd/pull/2092))
+- secp256k1: Add benchmark for signing ([decred/dcrd#2085](https://github.com/decred/dcrd/pull/2085))
+- seck256k1: Add benchmark for sig serialization ([decred/dcrd#2087](https://github.com/decred/dcrd/pull/2087))
+- secp256k1: Add benchmark for pubkey decompression ([decred/dcrd#2094](https://github.com/decred/dcrd/pull/2094))
+- secp256k1: Move sig benchmarks to separate file ([decred/dcrd#2095](https://github.com/decred/dcrd/pull/2095))
+- secp256k1: Add benchmark for SignCompact ([decred/dcrd#2095](https://github.com/decred/dcrd/pull/2095))
+- secp256k1: Add benchmark for RecoverCompact ([decred/dcrd#2095](https://github.com/decred/dcrd/pull/2095))
+- secp256k1: Rework DER sig parsing tests ([decred/dcrd#2109](https://github.com/decred/dcrd/pull/2109))
+- schnorr: Cleanup signature benchmarking ([decred/dcrd#2126](https://github.com/decred/dcrd/pull/2126))
+- schnorr: Rework signing tests ([decred/dcrd#2128](https://github.com/decred/dcrd/pull/2128))
+- secp256k1: Make field value tests more consistent ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- secp256k1: Move field val set hex to test file ([decred/dcrd#2134](https://github.com/decred/dcrd/pull/2134))
+- schnorr: Add negative tests for sig verification ([decred/dcrd#2145](https://github.com/decred/dcrd/pull/2145))
+- hdkeychain: Add child key with leading zeros test ([decred/dcrd#2155](https://github.com/decred/dcrd/pull/2155))
+- schnorr: Add benchmark for Signature.Serialize ([decred/dcrd#2158](https://github.com/decred/dcrd/pull/2158))
+- secp256k1: Rework pubkey tests ([decred/dcrd#2160](https://github.com/decred/dcrd/pull/2160))
+- secp256k1: Explicit pubkey parsing errors in tests ([decred/dcrd#2160](https://github.com/decred/dcrd/pull/2160))
+- secp256k1: Add compressed pubkey parse benchmark ([decred/dcrd#2167](https://github.com/decred/dcrd/pull/2167))
+- secp256k1: Add uncompressed pubkey parse benchmark ([decred/dcrd#2167](https://github.com/decred/dcrd/pull/2167))
+- build: use newer github and linter versions ([decred/dcrd#2182](https://github.com/decred/dcrd/pull/2182))
+- wire: Test no-relay case in TestVersionWire ([decred/dcrd#2184](https://github.com/decred/dcrd/pull/2184))
+- wire: Use new errors.Is capabilities in tests ([decred/dcrd#2183](https://github.com/decred/dcrd/pull/2183))
+- connmgr: Add test for dial timeout ([decred/dcrd#2189](https://github.com/decred/dcrd/pull/2189))
+- connmgr: Add test for connect context cancel ([decred/dcrd#2189](https://github.com/decred/dcrd/pull/2189))
+- connmgr: Refactor conn req ID/state test asserts ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Update tests to ensure clean shutdown ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Improve TestConnectMode robustness ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Increase timeout in TestTargetOutbound ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Shore up TestMaxRetryDuration ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Tighten TestNetworkFailure ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Tighten TestStopFailed ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Tighten TestRemovePendingConnection ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- connmgr: Cleanup TestCancelIgnoreDelayedConnection ([decred/dcrd#2192](https://github.com/decred/dcrd/pull/2192))
+- server: Actively prevent regnet network discovery ([decred/dcrd#2197](https://github.com/decred/dcrd/pull/2197))
+- Add debug and trace facility to rpctest ([decred/dcrd#2176](https://github.com/decred/dcrd/pull/2176))
+- build: use golangci-lint v1.27.0 ([decred/dcrd#2207](https://github.com/decred/dcrd/pull/2207))
+- rpcserver: Add handler test coverage ([decred/dcrd#2230](https://github.com/decred/dcrd/pull/2230))
+- rpcserver: Add handleDecodeScript test ([decred/dcrd#2238](https://github.com/decred/dcrd/pull/2238))
+- txscript: Add tests for new strict null data func ([decred/dcrd#2248](https://github.com/decred/dcrd/pull/2248))
+- rpcserver: Add default configs for tests ([decred/dcrd#2249](https://github.com/decred/dcrd/pull/2249))
+- txscript: Rework check signature encoding test ([decred/dcrd#2244](https://github.com/decred/dcrd/pull/2244))
+- rpcserver: Add tests for block related handlers ([decred/dcrd#2250](https://github.com/decred/dcrd/pull/2250))
+- txscript: Rework check pubkey encoding test ([decred/dcrd#2247](https://github.com/decred/dcrd/pull/2247))
+- txscript: Add benchmark for CheckSignatureEncoding ([decred/dcrd#2246](https://github.com/decred/dcrd/pull/2246))
+- connmgr: Use t.Fatal when there are no params ([decred/dcrd#2254](https://github.com/decred/dcrd/pull/2254))
+- rpcserver: Rework default configs for tests ([decred/dcrd#2257](https://github.com/decred/dcrd/pull/2257))
+- rpcserver: Update tests to use default configs ([decred/dcrd#2257](https://github.com/decred/dcrd/pull/2257))
+- rpcserver: Run tests in parallel ([decred/dcrd#2257](https://github.com/decred/dcrd/pull/2257))
+- rpcserver: Update error case handling in tests ([decred/dcrd#2257](https://github.com/decred/dcrd/pull/2257))
+- rpcserver: Add handleEstimateSmartFee test ([decred/dcrd#2255](https://github.com/decred/dcrd/pull/2255))
+- rpcserver: Add handleEstimateStakeDiff test ([decred/dcrd#2269](https://github.com/decred/dcrd/pull/2269))
+- rpcserver: Add handleGetTicketPoolValue test ([decred/dcrd#2272](https://github.com/decred/dcrd/pull/2272))
+- rpcserver: Add handleGetStakeVersions test ([decred/dcrd#2272](https://github.com/decred/dcrd/pull/2272))
+- rpcserver: Add handleGetStakeVersionInfo test ([decred/dcrd#2272](https://github.com/decred/dcrd/pull/2272))
+- mempool: Don't use deprecated reject code in tests ([decred/dcrd#2273](https://github.com/decred/dcrd/pull/2273))
+- build: golangci-lint v1.28.3 ([decred/dcrd#2266](https://github.com/decred/dcrd/pull/2266))
+- rpcserver: add missed and live tickets rpc tests ([decred/dcrd#2284](https://github.com/decred/dcrd/pull/2284))
+- rpcserver: add verifychain & getdifficulty tests ([decred/dcrd#2285](https://github.com/decred/dcrd/pull/2285))
+- multi: add BlockTemplater interface ([decred/dcrd#2292](https://github.com/decred/dcrd/pull/2292))
+- multi: add rpcCPUMiner adaptor ([decred/dcrd#2300](https://github.com/decred/dcrd/pull/2300))
+- connmgr: Improve dial timeout test synchronization ([decred/dcrd#2309](https://github.com/decred/dcrd/pull/2309))
+- rpcserver: Add handleGetCFilter tests ([decred/dcrd#2312](https://github.com/decred/dcrd/pull/2312))
+- rpcserver: Add handleGetCFilterHeader tests ([decred/dcrd#2312](https://github.com/decred/dcrd/pull/2312))
+- rpcserver: Add handleGetCFilterV2 tests ([decred/dcrd#2312](https://github.com/decred/dcrd/pull/2312))
+- rpcserver: Add handleExistsAddress test ([decred/dcrd#2291](https://github.com/decred/dcrd/pull/2291))
+- rpcserver: Add handleExistsAddresses test ([decred/dcrd#2291](https://github.com/decred/dcrd/pull/2291))
+- contrib: Respect quoted args in simnet ctl scripts ([decred/dcrd#2322](https://github.com/decred/dcrd/pull/2322))
+- contrib: Support MSYS2 in simnet setup script ([decred/dcrd#2323](https://github.com/decred/dcrd/pull/2323))
+- multi: add getwork tests ([decred/dcrd#2306](https://github.com/decred/dcrd/pull/2306))
+- rpcserver: add setgenerate & regentemplate tests ([decred/dcrd#2308](https://github.com/decred/dcrd/pull/2308))
+- rpcserver: Add TxMempooler interface ([decred/dcrd#2324](https://github.com/decred/dcrd/pull/2324))
+- rpcserver: Add handleExistsMempoolTxs test ([decred/dcrd#2324](https://github.com/decred/dcrd/pull/2324))
+- contrib: Update simnet script for dcrwallet master ([decred/dcrd#2327](https://github.com/decred/dcrd/pull/2327))
+- contrib: Support env var in simnet setup script ([decred/dcrd#2328](https://github.com/decred/dcrd/pull/2328))
+- contrib: Use var for simnet wallet create answers ([decred/dcrd#2328](https://github.com/decred/dcrd/pull/2328))
+- contrib: Update simnet script for wallet cointype ([decred/dcrd#2333](https://github.com/decred/dcrd/pull/2333))
+- build: test against go 1.15 ([decred/dcrd#2334](https://github.com/decred/dcrd/pull/2334))
+- blockchain: Add test func to remove deployment ([decred/dcrd#2343](https://github.com/decred/dcrd/pull/2343))
+- rpcserver: Add AddrIndexer interface ([decred/dcrd#2330](https://github.com/decred/dcrd/pull/2330))
+- rpcserver: Add TxIndexer interface ([decred/dcrd#2330](https://github.com/decred/dcrd/pull/2330))
+- rpcserver: Add testDB and testDatabaseTx ([decred/dcrd#2330](https://github.com/decred/dcrd/pull/2330))
+- rpcserver: Add handleSearchRawTransactions tests ([decred/dcrd#2330](https://github.com/decred/dcrd/pull/2330))
+- rpcserver: Add handleGenerate test ([decred/dcrd#2342](https://github.com/decred/dcrd/pull/2342))
+- mempool: Add TAdd Tests ([decred/dcrd#2350](https://github.com/decred/dcrd/pull/2350))
+- mempool: Improve tspend expiry handling and tests ([decred/dcrd#2350](https://github.com/decred/dcrd/pull/2350))
+- rpcserver: Verify tbase values in treasury rpctest ([decred/dcrd#2352](https://github.com/decred/dcrd/pull/2352))
+- rpctest: Add ability to limit VotingWallet votes ([decred/dcrd#2352](https://github.com/decred/dcrd/pull/2352))
+- rpcserver: Assert vote counts in treasury rpctest ([decred/dcrd#2351](https://github.com/decred/dcrd/pull/2351))
+- rpctest: Make votingwallet txs standard ([decred/dcrd#2373](https://github.com/decred/dcrd/pull/2373))
+- dcrutil: Cleanup verify tests and use mock params ([decred/dcrd#2382](https://github.com/decred/dcrd/pull/2382))
+- standalone: Add IsTreasuryVoteInterval tests ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- standalone: Rework and add CalcTSpendExpiry tests ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- standalone: Add InsideTSpendWindow tests ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- standalone: Add IsTreasuryBase tests ([decred/dcrd#2394](https://github.com/decred/dcrd/pull/2394))
+- chaingen: implement DCP0001 for generator ([decred/dcrd#2329](https://github.com/decred/dcrd/pull/2329))
+- blockchain: Add chaingen harness AdvanceToHeight ([decred/dcrd#2090](https://github.com/decred/dcrd/pull/2090))
+- blockchain: Rework AdvanceToHeight ([decred/dcrd#2090](https://github.com/decred/dcrd/pull/2090))
+- rpcserver: Add --rejectnonstd to rpctest ([decred/dcrd#2415](https://github.com/decred/dcrd/pull/2415))
+
+### Misc:
+
+- release: Bump for 1.6 release cycle ([decred/dcrd#1948](https://github.com/decred/dcrd/pull/1948))
+- multi: resolve todos ([decred/dcrd#1869](https://github.com/decred/dcrd/pull/1869))
+- multi: remove whitespace ([decred/dcrd#2009](https://github.com/decred/dcrd/pull/2009))
+- release: Add example OpenBSD rc.d service script ([decred/dcrd#2030](https://github.com/decred/dcrd/pull/2030))
+- release: Remove build metadata from master branch ([decred/dcrd#2053](https://github.com/decred/dcrd/pull/2053))
+- secp256k1: Improve NonceRFC6979 comment ([decred/dcrd#2044](https://github.com/decred/dcrd/pull/2044))
+- secp256k1: Correct comments in signature.go ([decred/dcrd#2046](https://github.com/decred/dcrd/pull/2046))
+- multi: Resolve go1.15 vet complaints ([decred/dcrd#2310](https://github.com/decred/dcrd/pull/2310))
+- multi: Address some linter complaints ([decred/dcrd#2399](https://github.com/decred/dcrd/pull/2399))
+- build: bump golangci-lint to 1.24.0 ([decred/dcrd#2141](https://github.com/decred/dcrd/pull/2141))
+- main: Simplify startup logic slightly ([decred/dcrd#2293](https://github.com/decred/dcrd/pull/2293))
+- docker: Update image to golang:1.14 ([decred/dcrd#2202](https://github.com/decred/dcrd/pull/2202))
+- release: Remove no longer used release bits ([decred/dcrd#2317](https://github.com/decred/dcrd/pull/2317))
+- docker: Update image to golang:1.15 ([decred/dcrd#2335](https://github.com/decred/dcrd/pull/2335))
+- release: Bump for 1.6.0 ([decred/dcrd#2340](https://github.com/decred/dcrd/pull/2340))
+
+### Code Contributors (alphabetical order):
+
+- Brian Stafford
+- Dave Collins
+- David Hill
+- degeri
+- Donald Adu-Poku
+- Jamie Holdstock
+- Joe Gruffins
+- Josh Rickmar
+- Julian Yap
+- Marco Peereboom
+- Matheus Degiovani
+- Matt Hawkins
+- Ryan Riley
+- Ryan Staudt
+- Wisdom Arerosuoghene
+- Youssef Boukenken
+- zhizhongzhiwai
\ No newline at end of file
diff --git a/gcs/README.md b/gcs/README.md
index 1304d53c..f62ed849 100644
--- a/gcs/README.md
+++ b/gcs/README.md
@@ -42,9 +42,8 @@ DCP0005](https://github.com/decred/dcps/blob/master/dcp-0005/dcp-0005.mediawiki#
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/gcs
-```
+This package is part of the `github.com/decred/dcrd/gcs/v2` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/hdkeychain/README.md b/hdkeychain/README.md
index b8c17d7c..ac655dcf 100644
--- a/hdkeychain/README.md
+++ b/hdkeychain/README.md
@@ -35,9 +35,8 @@ A comprehensive suite of tests is provided to ensure proper functionality.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/hdkeychain
-```
+This package is part of the `github.com/decred/dcrd/hdkeychain/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/internal/mempool/mempool.go b/internal/mempool/mempool.go
index c5473b20..c7291d33 100644
--- a/internal/mempool/mempool.go
+++ b/internal/mempool/mempool.go
@@ -272,7 +272,7 @@ type TxPool struct {
 	outpoints     map[wire.OutPoint]*dcrutil.Tx
 	miningView    *mining.TxMiningView
 
-	staged          map[chainhash.Hash]*dcrutil.Tx
+	staged          map[chainhash.Hash]*TxDesc
 	stagedOutpoints map[wire.OutPoint]*dcrutil.Tx
 
 	// Votes on blocks.
@@ -648,19 +648,21 @@ func (mp *TxPool) isTransactionStaged(hash *chainhash.Hash) bool {
 	return exists
 }
 
-// stageTransaction creates an entry for the provided
-// transaction in the stage pool.
+// stageTransaction adds the provided transaction to the stage pool.
 //
 // This function MUST be called with the mempool lock held (for writes).
-func (mp *TxPool) stageTransaction(tx *dcrutil.Tx) {
-	mp.staged[*tx.Hash()] = tx
+func (mp *TxPool) stageTransaction(txDesc *TxDesc) {
+	tx := txDesc.Tx
+	mp.staged[*tx.Hash()] = txDesc
 	for _, txIn := range tx.MsgTx().TxIn {
 		mp.stagedOutpoints[txIn.PreviousOutPoint] = tx
 	}
 }
 
-// removeStagedTransaction removes the provided transaction
-// from the stage pool.
+// removeStagedTransaction removes the provided transaction from the stage pool.
+// NOTE: Since unconfirmed tickets are currently the only type of staged
+// transaction, this method does not implement recursive removal of the provided
+// staged transaction's descendants.
 //
 // This function MUST be called with the mempool lock held (for writes).
 func (mp *TxPool) removeStagedTransaction(stagedTx *dcrutil.Tx) {
@@ -684,81 +686,53 @@ func (mp *TxPool) hasMempoolInput(tx *dcrutil.Tx) bool {
 	return false
 }
 
-// fetchRedeemers returns all transactions that reference an outpoint for
-// the provided regular transaction `tx`. Returns nil if a non-regular
-// transaction is provided.
+// forEachRedeemer scans the provided pool for transactions that have an
+// input referencing the provided regular transaction tx via the outpoint map,
+// and invokes the function f for each transaction in the set.
 //
 // This function MUST be called with the mempool lock held (for reads).
-func (mp *TxPool) fetchRedeemers(outpoints map[wire.OutPoint]*dcrutil.Tx, tx *dcrutil.Tx, isTreasuryEnabled bool) []*dcrutil.Tx {
-	txType := stake.DetermineTxType(tx.MsgTx(), isTreasuryEnabled)
-	if txType != stake.TxTypeRegular {
-		return nil
-	}
+func forEachRedeemer(tx *dcrutil.Tx, outpoints map[wire.OutPoint]*dcrutil.Tx,
+	pool map[chainhash.Hash]*TxDesc, f func(redeemerTxDesc *TxDesc)) {
 
 	tree := wire.TxTreeRegular
-	seen := map[chainhash.Hash]struct{}{}
-	redeemers := make([]*dcrutil.Tx, 0)
+	numOutputs := uint32(len(tx.MsgTx().TxOut))
+	seen := make(map[chainhash.Hash]struct{}, numOutputs)
 	outpoint := wire.OutPoint{Hash: *tx.Hash(), Tree: tree}
-	for i := uint32(0); i < uint32(len(tx.MsgTx().TxOut)); i++ {
+	for i := uint32(0); i < numOutputs; i++ {
 		outpoint.Index = i
-		txRedeemer, exists := outpoints[outpoint]
+		redeemerTx, exists := outpoints[outpoint]
 		if !exists {
 			continue
 		}
-		if _, exists := seen[*txRedeemer.Hash()]; exists {
-			continue
-		}
 
-		seen[*txRedeemer.Hash()] = struct{}{}
-		redeemers = append(redeemers, txRedeemer)
-	}
-
-	return redeemers
-}
-
-// MaybeAcceptDependents determines if there are any staged dependents of the
-// passed transaction and potentially accepts them to the memory pool.
-//
-// It returns a slice of transactions added to the mempool.  A nil slice means
-// no transactions were moved from the stage pool to the mempool.
-//
-// This function is safe for concurrent access.
-func (mp *TxPool) MaybeAcceptDependents(tx *dcrutil.Tx, isTreasuryEnabled bool) []*dcrutil.Tx {
-	mp.mtx.Lock()
-	defer mp.mtx.Unlock()
-
-	var acceptedTxns []*dcrutil.Tx
-	for _, redeemer := range mp.fetchRedeemers(mp.stagedOutpoints, tx,
-		isTreasuryEnabled) {
-		redeemerTxType := stake.DetermineTxType(redeemer.MsgTx(),
-			isTreasuryEnabled)
-		if redeemerTxType == stake.TxTypeSStx {
-			// Quick check to skip tickets with mempool inputs.
-			if mp.hasMempoolInput(redeemer) {
+		redeemerTxHash := redeemerTx.Hash()
+		if redeemerTxDesc, exist := pool[*redeemerTxHash]; exist {
+			// Skip previously seen redeemers.
+			if _, saw := seen[*redeemerTxHash]; saw {
 				continue
 			}
-
-			// Remove the dependent transaction and attempt to add it to the
-			// mempool or back to the stage pool. In the event of an error, the
-			// transaction will be discarded.
-			log.Tracef("Removing ticket %v with no mempool dependencies from "+
-				"stage pool", *redeemer.Hash())
-			mp.removeStagedTransaction(redeemer)
-			_, err := mp.maybeAcceptTransaction(
-				redeemer, true, true, true, true, isTreasuryEnabled)
-
-			if err != nil {
-				log.Debugf("Failed to add previously staged "+
-					"ticket %v to pool. %v", *redeemer.Hash(), err)
-			}
-
-			if mp.isTransactionInPool(redeemer.Hash()) {
-				acceptedTxns = append(acceptedTxns, redeemer)
-			}
+			seen[*redeemerTxHash] = struct{}{}
+			f(redeemerTxDesc)
 		}
 	}
+}
 
-	return acceptedTxns
+// forEachRedeemer scans the main pool for transactions that have an
+// input referencing the provided regular transaction tx, and invokes the
+// function f for each transaction in the set.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *TxPool) forEachRedeemer(tx *dcrutil.Tx, f func(redeemer *TxDesc)) {
+	forEachRedeemer(tx, mp.outpoints, mp.pool, f)
+}
+
+// forEachStagedRedeemer scans the stage pool for transactions that have an
+// input referencing the provided regular transaction tx, and invokes the
+// function f for each transaction in the set.
+//
+// This function MUST be called with the mempool lock held (for reads).
+func (mp *TxPool) forEachStagedRedeemer(tx *dcrutil.Tx, f func(redeemer *TxDesc)) {
+	forEachRedeemer(tx, mp.stagedOutpoints, mp.staged, f)
 }
 
 // haveTransaction returns whether or not the passed transaction already exists
@@ -943,11 +917,15 @@ func (mp *TxPool) findTx(txHash *chainhash.Hash) *mining.TxDesc {
 
 // addTransaction adds the passed transaction to the memory pool.  It should
 // not be called directly as it doesn't perform any validation.  This is a
-// helper for maybeAcceptTransaction.
+// helper for maybeAcceptTransaction and maybeUnstageTransaction.
 //
 // This function MUST be called with the mempool lock held (for writes).
-func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *dcrutil.Tx,
-	txType stake.TxType, height int64, fee int64, isTreasuryEnabled bool, totalSigOps int) {
+func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint,
+	txDesc *TxDesc, isTreasuryEnabled bool) {
+
+	tx := txDesc.Tx
+	txHash := tx.Hash()
+	txType := txDesc.Type
 
 	// Notify callback about vote if requested.
 	if mp.cfg.OnVoteReceived != nil && txType == stake.TxTypeSSGen {
@@ -956,23 +934,10 @@ func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *dcrutil
 
 	// Add the transaction to the pool and mark the referenced outpoints
 	// as spent by the pool.
-	msgTx := tx.MsgTx()
-	poolTxDesc := &TxDesc{
-		TxDesc: mining.TxDesc{
-			Tx:          tx,
-			Type:        txType,
-			Added:       time.Now(),
-			Height:      height,
-			Fee:         fee,
-			TotalSigOps: totalSigOps,
-			TxSize:      int64(tx.MsgTx().SerializeSize()),
-		},
-		StartingPriority: mining.CalcPriority(msgTx, utxoView, height),
-	}
-
-	mp.pool[*tx.Hash()] = poolTxDesc
-	mp.miningView.AddTransaction(&poolTxDesc.TxDesc, mp.findTx)
+	mp.pool[*txHash] = txDesc
+	mp.miningView.AddTransaction(&txDesc.TxDesc, mp.findTx)
 
+	msgTx := tx.MsgTx()
 	for _, txIn := range msgTx.TxIn {
 		mp.outpoints[txIn.PreviousOutPoint] = tx
 	}
@@ -988,10 +953,9 @@ func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *dcrutil
 	}
 
 	// Inform the associated fee estimator that a new transaction has been added
-	// to the mempool
+	// to the mempool.
 	if mp.cfg.AddTxToFeeEstimation != nil {
-		mp.cfg.AddTxToFeeEstimation(tx.Hash(), fee, int64(msgTx.SerializeSize()),
-			txType)
+		mp.cfg.AddTxToFeeEstimation(txHash, txDesc.Fee, txDesc.TxSize, txType)
 	}
 }
 
@@ -1024,7 +988,7 @@ func (mp *TxPool) checkPoolDoubleSpend(tx *dcrutil.Tx, txType stake.TxType, isTr
 		}
 
 		if txR, exists := mp.stagedOutpoints[txIn.PreviousOutPoint]; exists {
-			str := fmt.Sprintf("staged transaction %v in the pool "+
+			str := fmt.Sprintf("transaction %v in the stage pool "+
 				"already spends the same coins", txR.Hash())
 			return txRuleError(ErrMempoolDoubleSpend, str)
 		}
@@ -1112,6 +1076,10 @@ func (mp *TxPool) fetchInputUtxos(tx *dcrutil.Tx, isTreasuryEnabled bool) (*bloc
 	knownDisapproved := mp.IsRegTxTreeKnownDisapproved(mp.cfg.BestHash())
 	utxoView, err := mp.cfg.FetchUtxoView(tx, !knownDisapproved)
 	if err != nil {
+		var cerr blockchain.RuleError
+		if errors.As(err, &cerr) {
+			return nil, chainRuleError(cerr)
+		}
 		return nil, err
 	}
 
@@ -1130,10 +1098,10 @@ func (mp *TxPool) fetchInputUtxos(tx *dcrutil.Tx, isTreasuryEnabled bool) (*bloc
 				wire.NullBlockIndex, isTreasuryEnabled)
 		}
 
-		if stagedTx, exists := mp.staged[prevOut.Hash]; exists {
+		if stagedTxDesc, exists := mp.staged[prevOut.Hash]; exists {
 			// AddTxOut ignores out of range index values, so it is safe to call without
 			// bounds checking here.
-			utxoView.AddTxOut(stagedTx, prevOut.Index, mining.UnminedHeight,
+			utxoView.AddTxOut(stagedTxDesc.Tx, prevOut.Index, mining.UnminedHeight,
 				wire.NullBlockIndex, isTreasuryEnabled)
 		}
 	}
@@ -1142,31 +1110,100 @@ func (mp *TxPool) fetchInputUtxos(tx *dcrutil.Tx, isTreasuryEnabled bool) (*bloc
 }
 
 // FetchTransaction returns the requested transaction from the transaction pool.
-// This only fetches from the main transaction pool and does not include
-// orphans.
+// This only fetches from the main and stage transaction pools and does not
+// include orphans.
 //
 // This function is safe for concurrent access.
 func (mp *TxPool) FetchTransaction(txHash *chainhash.Hash) (*dcrutil.Tx, error) {
-	var tx *dcrutil.Tx
-
 	// Protect concurrent access.
 	mp.mtx.RLock()
 	txDesc, exists := mp.pool[*txHash]
-	if exists {
-		tx = txDesc.Tx
-	} else {
-		// Check if the transaction is in the stage pool.
-		tx, exists = mp.staged[*txHash]
+	if !exists {
+		// Attempt to fetch the transaction from the stage pool.
+		txDesc, exists = mp.staged[*txHash]
 	}
 	mp.mtx.RUnlock()
 
 	if exists {
-		return tx, nil
+		return txDesc.Tx, nil
 	}
 
 	return nil, fmt.Errorf("transaction is not in the pool")
 }
 
+// newTxDesc returns a new TxDesc instance that captures mempool state
+// relevant to the provided transaction at the current time.
+func (mp *TxPool) newTxDesc(utxoView *blockchain.UtxoViewpoint, tx *dcrutil.Tx,
+	txType stake.TxType, height int64, fee int64, totalSigOps int, txSize int64) *TxDesc {
+
+	msgTx := tx.MsgTx()
+	return &TxDesc{
+		TxDesc: mining.TxDesc{
+			Tx:          tx,
+			Type:        txType,
+			Added:       time.Now(),
+			Height:      height,
+			Fee:         fee,
+			TotalSigOps: totalSigOps,
+			TxSize:      txSize,
+		},
+		StartingPriority: mining.CalcPriority(msgTx, utxoView, height),
+	}
+}
+
+// maybeUnstageTransaction attempts to bring the staged transaction into the
+// main pool. Note that this does not perform all preliminary checks on the
+// transaction re-entering the main pool since the transaction must have already
+// passed those checks prior to entering the stage pool.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *TxPool) maybeUnstageTransaction(txDesc *TxDesc, isTreasuryEnabled bool) error {
+	tx := txDesc.Tx
+	if txDesc.Type == stake.TxTypeSStx && !mp.hasMempoolInput(tx) {
+		log.Tracef("Removing ticket %v with no mempool dependencies from "+
+			"stage pool", tx.Hash())
+
+		// Remove the dependent transaction and attempt to add it to the
+		// main pool or back to the stage pool. In the event of an error, the
+		// transaction will be discarded.
+		mp.removeStagedTransaction(tx)
+		utxoView, err := mp.fetchInputUtxos(txDesc.Tx, isTreasuryEnabled)
+		if err != nil {
+			return err
+		}
+		mp.addTransaction(utxoView, txDesc, isTreasuryEnabled)
+	}
+	return nil
+}
+
+// MaybeAcceptDependents determines if there are any staged dependents of the
+// passed transaction and potentially accepts them to the mempool.
+//
+// It returns a slice of transactions added to the main pool.  A nil slice means
+// no transactions were moved from the stage pool to the main pool.
+//
+// This function is safe for concurrent access.
+func (mp *TxPool) MaybeAcceptDependents(tx *dcrutil.Tx, isTreasuryEnabled bool) []*dcrutil.Tx {
+	mp.mtx.Lock()
+	defer mp.mtx.Unlock()
+
+	var acceptedTxns []*dcrutil.Tx
+	mp.forEachStagedRedeemer(tx, func(redeemerTxDesc *TxDesc) {
+		redeemerTx := redeemerTxDesc.Tx
+		redeemerTxHash := redeemerTx.Hash()
+		err := mp.maybeUnstageTransaction(redeemerTxDesc, isTreasuryEnabled)
+		if err != nil {
+			log.Debugf("Failed to add previously staged "+
+				"ticket %v to pool: %v", redeemerTxHash, err)
+		}
+		if mp.isTransactionInPool(redeemerTxHash) {
+			acceptedTxns = append(acceptedTxns, redeemerTx)
+		}
+	})
+
+	return acceptedTxns
+}
+
 // maybeAcceptTransaction is the internal function which implements the public
 // MaybeAcceptTransaction.  See the comment for MaybeAcceptTransaction for
 // more details.
@@ -1374,10 +1411,6 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow
 	// without needing to do a separate lookup.
 	utxoView, err := mp.fetchInputUtxos(tx, isTreasuryEnabled)
 	if err != nil {
-		var cerr blockchain.RuleError
-		if errors.As(err, &cerr) {
-			return nil, chainRuleError(cerr)
-		}
 		return nil, err
 	}
 
@@ -1414,13 +1447,12 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow
 			// transaction input is nil.
 			if entry == nil {
 				log.Tracef("Transaction %v uses unknown input %v "+
-					"and will be considered an orphan", txHash,
-					txIn.PreviousOutPoint.Hash)
+					"and will be considered an orphan", txHash, hashCopy)
 				continue
 			}
 			if entry.IsSpent() {
-				log.Tracef("Transaction %v uses spent input %v and will be considered "+
-					"an orphan", txHash, txIn.PreviousOutPoint.Hash)
+				log.Tracef("Transaction %v uses spent input %v and will be "+
+					"considered an orphan", txHash, hashCopy)
 			}
 		}
 	}
@@ -1621,23 +1653,6 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow
 		return nil, err
 	}
 
-	// Tickets cannot be included in a block until all inputs have
-	// been approved by stakeholders. Consensus rules dictate that stake
-	// transactions must precede regular transactions, and that inputs for any
-	// transaction must precede its redeemer. As a result, tickets with mempool
-	// inputs are placed in a separate `stage` pool rather than the main tx
-	// pool since they cannot be included in the next block.
-	//
-	// Note: The scenario where a mempool ticket spends from a known-disapproved
-	// regular transaction that is not in the mempool is accounted for during
-	// block template generation.
-	if isTicket && mp.hasMempoolInput(tx) {
-		mp.stageTransaction(tx)
-		log.Debugf("Accepted ticket %v with mempool dependency "+
-			"into stage pool", txHash)
-		return nil, nil
-	}
-
 	// Only allow TSpends that have a valid Expiry.
 	if isTreasuryEnabled && isTSpend {
 		// Shorter variable names for relevant chain parameters.
@@ -1711,25 +1726,41 @@ func (mp *TxPool) maybeAcceptTransaction(tx *dcrutil.Tx, isNew, rateLimit, allow
 			tvi, mul, tspends)
 	}
 
+	txDesc := mp.newTxDesc(utxoView, tx, txType, bestHeight, txFee, totalSigOps,
+		serializedSize)
+
+	// Tickets cannot be included in a block until all inputs have
+	// been approved by stakeholders. Consensus rules dictate that stake
+	// transactions must precede regular transactions, and that inputs for any
+	// transaction must precede its redeemer. As a result, tickets with mempool
+	// inputs are placed in a separate "stage" pool rather than the main tx
+	// pool since they cannot be included in the next block.
+	//
+	// Note: The scenario where a mempool ticket spends from a known-disapproved
+	// regular transaction that is not in the mempool is accounted for during
+	// block template generation.
+	if txType == stake.TxTypeSStx && mp.hasMempoolInput(tx) {
+		log.Debugf("Adding ticket %v with mempool dependency to stage pool",
+			txHash)
+		mp.stageTransaction(txDesc)
+		return nil, nil
+	}
+
 	// Add to transaction pool.
-	mp.addTransaction(utxoView, tx, txType, bestHeight, txFee, isTreasuryEnabled,
-		totalSigOps)
+	mp.addTransaction(utxoView, txDesc, isTreasuryEnabled)
 
-	// A regular transaction that is added back to the mempool causes
-	// any mempool tickets that redeem it to leave the main pool and enter the
-	// `stage` pool.
+	// A regular transaction entering the mempool causes
+	// mempool tickets that redeem it to move to the stage pool.
 	if !isNew && txType == stake.TxTypeRegular {
-		for _, redeemer := range mp.fetchRedeemers(mp.outpoints, tx,
-			isTreasuryEnabled) {
-			redeemerDesc, exists := mp.pool[*redeemer.Hash()]
-			if exists && redeemerDesc.Type == stake.TxTypeSStx {
-				mp.removeTransaction(redeemer, true,
-					isTreasuryEnabled)
-				mp.stageTransaction(redeemer)
+		mp.forEachRedeemer(tx, func(redeemerTxDesc *TxDesc) {
+			redeemerTx := redeemerTxDesc.Tx
+			if redeemerTxDesc.Type == stake.TxTypeSStx {
+				mp.removeTransaction(redeemerTx, true, isTreasuryEnabled)
+				mp.stageTransaction(redeemerTxDesc)
 				log.Debugf("Moved ticket %v dependent on %v into stage pool",
-					redeemer.Hash(), tx.Hash())
+					redeemerTx.Hash(), txHash)
 			}
-		}
+		})
 	}
 
 	// Keep track of votes separately.
@@ -1864,6 +1895,46 @@ func (mp *TxPool) processOrphans(acceptedTx *dcrutil.Tx, isTreasuryEnabled bool)
 	return acceptedTxns
 }
 
+// pruneStakeTx is the internal function which implements the public
+// PruneStakeTx.  See the comment for PruneStakeTx for more details.
+//
+// This function MUST be called with the mempool lock held (for writes).
+func (mp *TxPool) pruneStakeTx(requiredStakeDifficulty, height int64, isTreasuryEnabled bool) {
+	for _, txDesc := range mp.pool {
+		txType := txDesc.Type
+		if txType == stake.TxTypeSStx &&
+			txDesc.Height+int64(heightDiffToPruneTicket) < height {
+			mp.removeTransaction(txDesc.Tx, true, isTreasuryEnabled)
+			continue
+		}
+		if txType == stake.TxTypeSStx &&
+			txDesc.Tx.MsgTx().TxOut[0].Value < requiredStakeDifficulty {
+			mp.removeTransaction(txDesc.Tx, true, isTreasuryEnabled)
+			continue
+		}
+		if (txType == stake.TxTypeSSRtx || txType == stake.TxTypeSSGen) &&
+			txDesc.Height+int64(heightDiffToPruneVotes) < height {
+			mp.removeTransaction(txDesc.Tx, true, isTreasuryEnabled)
+		}
+	}
+	for _, txDesc := range mp.staged {
+		txType := txDesc.Type
+		if txType == stake.TxTypeSStx &&
+			txDesc.Tx.MsgTx().TxOut[0].Value < requiredStakeDifficulty {
+			log.Debugf("Pruning ticket %v with insufficient stake difficulty "+
+				"from stage pool", txDesc.Tx.Hash())
+			mp.removeStagedTransaction(txDesc.Tx)
+			continue
+		}
+		if txType == stake.TxTypeSStx &&
+			txDesc.Height+int64(heightDiffToPruneTicket) < height {
+			log.Debugf("Pruning old ticket %v added at height %v "+
+				"from stage pool", txDesc.Tx.Hash(), txDesc.Height)
+			mp.removeStagedTransaction(txDesc.Tx)
+		}
+	}
+}
+
 // PruneStakeTx is the function which is called every time a new block is
 // processed.  The idea is any outstanding SStx that hasn't been mined in a
 // certain period of time (CoinbaseMaturity) and the submitted SStx's
@@ -1882,33 +1953,6 @@ func (mp *TxPool) PruneStakeTx(requiredStakeDifficulty, height int64) {
 	mp.mtx.Unlock()
 }
 
-func (mp *TxPool) pruneStakeTx(requiredStakeDifficulty, height int64, isTreasuryEnabled bool) {
-	for _, tx := range mp.pool {
-		txType := stake.DetermineTxType(tx.Tx.MsgTx(), isTreasuryEnabled)
-		if txType == stake.TxTypeSStx &&
-			tx.Height+int64(heightDiffToPruneTicket) < height {
-			mp.removeTransaction(tx.Tx, true, isTreasuryEnabled)
-		}
-		if txType == stake.TxTypeSStx &&
-			tx.Tx.MsgTx().TxOut[0].Value < requiredStakeDifficulty {
-			mp.removeTransaction(tx.Tx, true, isTreasuryEnabled)
-		}
-		if (txType == stake.TxTypeSSRtx || txType == stake.TxTypeSSGen) &&
-			tx.Height+int64(heightDiffToPruneVotes) < height {
-			mp.removeTransaction(tx.Tx, true, isTreasuryEnabled)
-		}
-	}
-	for _, tx := range mp.staged {
-		txType := stake.DetermineTxType(tx.MsgTx(), isTreasuryEnabled)
-		if txType == stake.TxTypeSStx &&
-			tx.MsgTx().TxOut[0].Value < requiredStakeDifficulty {
-			log.Debugf("Pruning ticket %v with insufficient stake difficulty "+
-				"from stage pool", tx.Hash())
-			mp.removeStagedTransaction(tx)
-		}
-	}
-}
-
 // pruneExpiredTx prunes expired transactions from the mempool that are no
 // longer able to be included into a block.
 //
@@ -1916,15 +1960,17 @@ func (mp *TxPool) pruneStakeTx(requiredStakeDifficulty, height int64, isTreasury
 func (mp *TxPool) pruneExpiredTx(isTreasuryEnabled bool) {
 	nextBlockHeight := mp.cfg.BestHeight() + 1
 
-	for _, tx := range mp.pool {
-		if blockchain.IsExpired(tx.Tx, nextBlockHeight) {
+	for _, txDesc := range mp.pool {
+		tx := txDesc.Tx
+		if blockchain.IsExpired(tx, nextBlockHeight) {
 			log.Debugf("Pruning expired transaction %v from the mempool",
-				tx.Tx.Hash())
-			mp.removeTransaction(tx.Tx, true, isTreasuryEnabled)
+				tx.Hash())
+			mp.removeTransaction(tx, true, isTreasuryEnabled)
 		}
 	}
 
-	for _, tx := range mp.staged {
+	for _, txDesc := range mp.staged {
+		tx := txDesc.Tx
 		if blockchain.IsExpired(tx, nextBlockHeight) {
 			log.Debugf("Pruning expired transaction %v from the stage pool",
 				tx.Hash())
@@ -2152,7 +2198,7 @@ func (mp *TxPool) miningDescs() []*mining.TxDesc {
 }
 
 // LastUpdated returns the last time a transaction was added to or removed from
-// the main pool.  It does not include the orphan pool.
+// the main pool.  It does not include the orphan or stage pools.
 //
 // This function is safe for concurrent access.
 func (mp *TxPool) LastUpdated() time.Time {
@@ -2184,7 +2230,7 @@ func New(cfg *Config) *TxPool {
 		votes:           make(map[chainhash.Hash][]mining.VoteDesc),
 		tspends:         make(map[chainhash.Hash]*dcrutil.Tx),
 		nextExpireScan:  time.Now().Add(orphanExpireScanInterval),
-		staged:          make(map[chainhash.Hash]*dcrutil.Tx),
+		staged:          make(map[chainhash.Hash]*TxDesc),
 		stagedOutpoints: make(map[wire.OutPoint]*dcrutil.Tx),
 	}
 
diff --git a/internal/mining/cpuminer/cpuminer.go b/internal/mining/cpuminer/cpuminer.go
index 17ba42ce..8951d26d 100644
--- a/internal/mining/cpuminer/cpuminer.go
+++ b/internal/mining/cpuminer/cpuminer.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2014-2016 The btcsuite developers
-// Copyright (c) 2015-2020 The Decred developers
+// Copyright (c) 2015-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -96,7 +96,7 @@ type Config struct {
 	// ProcessBlock defines the function to call with any solved blocks.
 	// It typically must run the provided block through the same set of
 	// rules and handling as any other block coming from the network.
-	ProcessBlock func(*dcrutil.Block, blockchain.BehaviorFlags) (bool, error)
+	ProcessBlock func(*dcrutil.Block, blockchain.BehaviorFlags) error
 
 	// ConnectedCount defines the function to use to obtain how many other
 	// peers the server is connected to.  This is used by the automatic
@@ -200,8 +200,14 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {
 
 	// Process this block using the same rules as blocks coming from other
 	// nodes. This will in turn relay it to the network like normal.
-	isOrphan, err := m.cfg.ProcessBlock(block, blockchain.BFNone)
+	err := m.cfg.ProcessBlock(block, blockchain.BFNone)
 	if err != nil {
+		if errors.Is(err, blockchain.ErrMissingParent) {
+			log.Errorf("Block submitted via CPU miner is an orphan building "+
+				"on parent %v", block.MsgBlock().Header.PrevBlock)
+			return false
+		}
+
 		// Anything other than a rule violation is an unexpected error,
 		// so log that error as an internal error.
 		var rErr blockchain.RuleError
@@ -228,11 +234,6 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {
 		log.Errorf("Block submitted via CPU miner rejected: %v", err)
 		return false
 	}
-	if isOrphan {
-		log.Errorf("Block submitted via CPU miner is an orphan building on "+
-			"parent %v", block.MsgBlock().Header.PrevBlock)
-		return false
-	}
 
 	// The block was accepted.
 	coinbaseTxOuts := block.MsgBlock().Transactions[0].TxOut
diff --git a/internal/mining/mining.go b/internal/mining/mining.go
index f9fb981d..3b403f37 100644
--- a/internal/mining/mining.go
+++ b/internal/mining/mining.go
@@ -1082,32 +1082,32 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress dcrutil.Address) (*Bloc
 			"block on, proceeding to create a new block template",
 			eligibleParents[0])
 
-		// Force a reorganization to the parent with the most votes if we need
-		// to.
-		if eligibleParents[0] != prevHash {
-			for i := range eligibleParents {
-				newHead := &eligibleParents[i]
-				err := g.cfg.ForceHeadReorganization(prevHash, *newHead)
-				if err != nil {
-					log.Errorf("failed to reorganize to new parent: %v", err)
-					continue
-				}
+		// Force a reorganization to the parent with the most votes if needed.
+		for i := range eligibleParents {
+			newHead := &eligibleParents[i]
+			if *newHead == prevHash {
+				break
+			}
 
-				// Check to make sure we actually have the transactions
-				// (votes) we need in the mempool.
-				voteHashes := g.cfg.TxSource.VoteHashesForBlock(newHead)
-				if len(voteHashes) == 0 {
-					return nil, fmt.Errorf("no vote metadata for block %v",
-						newHead)
-				}
+			err := g.cfg.ForceHeadReorganization(prevHash, *newHead)
+			if err != nil {
+				log.Debugf("failed to reorganize to new parent: %v", err)
+				continue
+			}
 
-				if exist := g.cfg.TxSource.HaveAllTransactions(voteHashes); !exist {
-					continue
-				} else {
-					prevHash = *newHead
-					break
-				}
+			// Ensure the needed votes are actually in the mempool.
+			voteHashes := g.cfg.TxSource.VoteHashesForBlock(newHead)
+			if len(voteHashes) == 0 {
+				return nil, fmt.Errorf("no vote metadata for block %v",
+					newHead)
 			}
+			haveAllVotes := g.cfg.TxSource.HaveAllTransactions(voteHashes)
+			if !haveAllVotes {
+				continue
+			}
+
+			prevHash = *newHead
+			break
 		}
 
 		// Obtain the maximum allowed treasury expenditure.
diff --git a/internal/netsync/log.go b/internal/netsync/log.go
index cbe84d3a..8db82c2a 100644
--- a/internal/netsync/log.go
+++ b/internal/netsync/log.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 The Decred developers
+// Copyright (c) 2020-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -20,3 +20,12 @@ var log = slog.Disabled
 func UseLogger(logger slog.Logger) {
 	log = logger
 }
+
+// pickNoun returns the singular or plural form of a noun depending on the
+// provided count.
+func pickNoun(n uint64, singular, plural string) string {
+	if n == 1 {
+		return singular
+	}
+	return plural
+}
diff --git a/internal/netsync/manager.go b/internal/netsync/manager.go
index 1320e42f..2d64b42e 100644
--- a/internal/netsync/manager.go
+++ b/internal/netsync/manager.go
@@ -6,10 +6,10 @@
 package netsync
 
 import (
-	"container/list"
 	"context"
 	"errors"
 	"fmt"
+	"math"
 	"runtime/debug"
 	"sync"
 	"time"
@@ -30,13 +30,12 @@ import (
 
 const (
 	// minInFlightBlocks is the minimum number of blocks that should be
-	// in the request queue for headers-first mode before requesting
-	// more.
+	// in the request queue before requesting more.
 	minInFlightBlocks = 10
 
-	// maxOrphanBlocks is the maximum number of orphan blocks that can be
-	// queued.
-	maxOrphanBlocks = 500
+	// maxInFlightBlocks is the maximum number of blocks to allow in the sync
+	// peer request queue.
+	maxInFlightBlocks = 16
 
 	// maxRejectedTxns is the maximum number of rejected transactions
 	// hashes to store in memory.
@@ -49,6 +48,21 @@ const (
 	// maxRequestedTxns is the maximum number of requested transactions
 	// hashes to store in memory.
 	maxRequestedTxns = wire.MaxInvPerMsg
+
+	// maxExpectedHeaderAnnouncementsPerMsg is the maximum number of headers in
+	// a single message that is expected when determining when the message
+	// appears to be announcing new blocks.
+	maxExpectedHeaderAnnouncementsPerMsg = 12
+
+	// maxConsecutiveOrphanHeaders is the maximum number of consecutive header
+	// messages that contain headers which do not connect a peer can send before
+	// it is deemed to have diverged so far it is no longer useful.
+	maxConsecutiveOrphanHeaders = 10
+
+	// headerSyncStallTimeoutSecs is the number of seconds to wait for progress
+	// during the header sync process before stalling the sync and disconnecting
+	// the peer.
+	headerSyncStallTimeoutSecs = (3 + wire.MaxBlockHeadersPerMsg/1000) * 2
 )
 
 // zeroHash is the zero value hash (all zeros).  It is defined as a convenience.
@@ -128,9 +142,8 @@ type requestFromPeerResponse struct {
 // processBlockResponse is a response sent to the reply channel of a
 // processBlockMsg.
 type processBlockResponse struct {
-	forkLen  int64
-	isOrphan bool
-	err      error
+	forkLen int64
+	err     error
 }
 
 // processBlockMsg is a message type to be sent across the message channel
@@ -163,13 +176,6 @@ type processTransactionMsg struct {
 	reply         chan processTransactionResponse
 }
 
-// headerNode is used as a node in a list of headers that are linked together
-// between checkpoints.
-type headerNode struct {
-	height int64
-	hash   *chainhash.Hash
-}
-
 // syncMgrPeer extends a peer to maintain additional state maintained by the
 // sync manager.
 type syncMgrPeer struct {
@@ -178,14 +184,71 @@ type syncMgrPeer struct {
 	syncCandidate   bool
 	requestedTxns   map[chainhash.Hash]struct{}
 	requestedBlocks map[chainhash.Hash]struct{}
+
+	// numConsecutiveOrphanHeaders tracks the number of consecutive header
+	// messages sent by the peer that contain headers which do not connect.  It
+	// is used to detect peers that have either diverged so far they are no
+	// longer useful or are otherwise being malicious.
+	numConsecutiveOrphanHeaders int32
+}
+
+// headerSyncState houses the state used to track the header sync progress and
+// related stall handling.
+type headerSyncState struct {
+	// headersSynced tracks whether or not the headers are synced to a point
+	// that is recent enough to start downloading blocks.
+	headersSynced bool
+
+	// These fields are used to implement a progress stall timeout that can be
+	// reset at any time without needing to create a new one and the associated
+	// extra garbage.
+	//
+	// stallTimer is an underlying timer that is used to implement the timeout.
+	//
+	// stallChanDrained indicates whether or not the channel for the stall timer
+	// has already been read and is used when resetting the timer to ensure the
+	// channel is drained when the timer is stopped as described in the timer
+	// documentation.
+	stallTimer       *time.Timer
+	stallChanDrained bool
+}
+
+// makeHeaderSyncState returns a header sync state that is ready to use.
+func makeHeaderSyncState() headerSyncState {
+	stallTimer := time.NewTimer(math.MaxInt64)
+	stallTimer.Stop()
+	return headerSyncState{
+		stallTimer:       stallTimer,
+		stallChanDrained: true,
+	}
+}
+
+// stopStallTimeout stops the progress stall timer while ensuring to read from
+// the timer's channel in the case the timer already expired which can happen
+// due to the fact the stop happens in between channel reads.  This behavior is
+// well documented in the Timer docs.
+//
+// NOTE: This function must not be called concurrent with any other receives on
+// the timer's channel.
+func (state *headerSyncState) stopStallTimeout() {
+	t := state.stallTimer
+	if !t.Stop() && !state.stallChanDrained {
+		<-t.C
+	}
+	state.stallChanDrained = true
 }
 
-// orphanBlock represents a block for which the parent is not yet available.  It
-// is a normal block plus an expiration time to prevent caching the orphan
-// forever.
-type orphanBlock struct {
-	block      *dcrutil.Block
-	expiration time.Time
+// resetStallTimeout resets the progress stall timer while ensuring to read from
+// the timer's channel in the case the timer already expired which can happen
+// due to the fact the reset happens in between channel reads.  This behavior is
+// well documented in the Timer docs.
+//
+// NOTE: This function must not be called concurrent with any other receives on
+// the timer's channel.
+func (state *headerSyncState) resetStallTimeout() {
+	state.stopStallTimeout()
+	state.stallTimer.Reset(headerSyncStallTimeoutSecs * time.Second)
+	state.stallChanDrained = false
 }
 
 // SyncManager provides a concurrency safe sync manager for handling all
@@ -203,23 +266,14 @@ type SyncManager struct {
 	rejectedTxns    map[chainhash.Hash]struct{}
 	requestedTxns   map[chainhash.Hash]struct{}
 	requestedBlocks map[chainhash.Hash]struct{}
-	progressLogger  *progresslog.BlockLogger
+	progressLogger  *progresslog.Logger
 	syncPeer        *syncMgrPeer
 	msgChan         chan interface{}
 	peers           map[*peerpkg.Peer]*syncMgrPeer
 
-	// The following fields are used for headers-first mode.
-	headersFirstMode bool
-	headerList       *list.List
-	startHeader      *list.Element
-	nextCheckpoint   *chaincfg.Checkpoint
-
-	// These fields are related to handling of orphan blocks.  They are
-	// protected by the orphan lock.
-	orphanLock   sync.RWMutex
-	orphans      map[chainhash.Hash]*orphanBlock
-	prevOrphans  map[chainhash.Hash][]*orphanBlock
-	oldestOrphan *orphanBlock
+	// hdrSyncState houses the state used to track the initial header sync
+	// process and related stall handling.
+	hdrSyncState headerSyncState
 
 	// The following fields are used to track the height being synced to from
 	// peers.
@@ -245,55 +299,12 @@ func lookupPeer(peer *peerpkg.Peer, peers map[*peerpkg.Peer]*syncMgrPeer) *syncM
 	return sp
 }
 
-// resetHeaderState sets the headers-first mode state to values appropriate for
-// syncing from a new peer.
-func (m *SyncManager) resetHeaderState(newestHash *chainhash.Hash, newestHeight int64) {
-	m.headersFirstMode = false
-	m.headerList.Init()
-	m.startHeader = nil
-
-	// When there is a next checkpoint, add an entry for the latest known
-	// block into the header pool.  This allows the next downloaded header
-	// to prove it links to the chain properly.
-	if m.nextCheckpoint != nil {
-		node := headerNode{height: newestHeight, hash: newestHash}
-		m.headerList.PushBack(&node)
-	}
-}
-
 // SyncHeight returns latest known block being synced to.
 func (m *SyncManager) SyncHeight() int64 {
 	m.syncHeightMtx.Lock()
-	defer m.syncHeightMtx.Unlock()
-	return m.syncHeight
-}
-
-// findNextHeaderCheckpoint returns the next checkpoint after the passed height.
-// It returns nil when there is not one either because the height is already
-// later than the final checkpoint or some other reason such as disabled
-// checkpoints.
-func (m *SyncManager) findNextHeaderCheckpoint(height int64) *chaincfg.Checkpoint {
-	checkpoints := m.cfg.Chain.Checkpoints()
-	if len(checkpoints) == 0 {
-		return nil
-	}
-
-	// There is no next checkpoint if the height is already after the final
-	// checkpoint.
-	finalCheckpoint := &checkpoints[len(checkpoints)-1]
-	if height >= finalCheckpoint.Height {
-		return nil
-	}
-
-	// Find the next checkpoint.
-	nextCheckpoint := finalCheckpoint
-	for i := len(checkpoints) - 2; i >= 0; i-- {
-		if height >= checkpoints[i].Height {
-			break
-		}
-		nextCheckpoint = &checkpoints[i]
-	}
-	return nextCheckpoint
+	syncHeight := m.syncHeight
+	m.syncHeightMtx.Unlock()
+	return syncHeight
 }
 
 // chainBlockLocatorToHashes converts a block locator from chain to a slice
@@ -310,17 +321,61 @@ func chainBlockLocatorToHashes(locator blockchain.BlockLocator) []chainhash.Hash
 	return result
 }
 
+// fetchNextBlocks creates and sends a request to the provided peer for the next
+// blocks to be downloaded based on the current headers.
+func (m *SyncManager) fetchNextBlocks(peer *syncMgrPeer) {
+	// Nothing to do if the target maximum number of blocks to request from the
+	// peer at the same time are already in flight.
+	numInFlight := len(peer.requestedBlocks)
+	if numInFlight >= maxInFlightBlocks {
+		return
+	}
+
+	// Determine the next blocks to download based on the final block that has
+	// already been requested and the next blocks in the branch leading up to
+	// best known header.
+	chain := m.cfg.Chain
+	maxNeeded := uint8(maxInFlightBlocks - numInFlight)
+	neededBlocks := chain.NextNeededBlocks(maxNeeded, m.requestedBlocks)
+	if len(neededBlocks) == 0 {
+		return
+	}
+
+	// Ensure the number of needed blocks does not exceed the max inventory
+	// per message.  This should never happen because the constants above limit
+	// it to relatively small values.  However, the code below relies on this
+	// assumption, so assert it.
+	if len(neededBlocks) > wire.MaxInvPerMsg {
+		log.Warnf("%d needed blocks exceeds max allowed per message",
+			len(neededBlocks))
+		return
+	}
+
+	// Build and send a getdata request for needed blocks.
+	gdmsg := wire.NewMsgGetDataSizeHint(uint(len(neededBlocks)))
+	for i := range neededBlocks {
+		hash := neededBlocks[i]
+		iv := wire.NewInvVect(wire.InvTypeBlock, hash)
+		m.requestedBlocks[*hash] = struct{}{}
+		peer.requestedBlocks[*hash] = struct{}{}
+		gdmsg.AddInvVect(iv)
+	}
+	peer.QueueMessage(gdmsg, nil)
+
+}
+
 // startSync will choose the best peer among the available candidate peers to
 // download/sync the blockchain from.  When syncing is already running, it
 // simply returns.  It also examines the candidates for any which are no longer
 // candidates and removes them as needed.
 func (m *SyncManager) startSync() {
-	// Return now if we're already syncing.
+	// Nothing more to do when already syncing.
 	if m.syncPeer != nil {
 		return
 	}
 
-	best := m.cfg.Chain.BestSnapshot()
+	chain := m.cfg.Chain
+	best := chain.BestSnapshot()
 	var bestPeer *syncMgrPeer
 	for _, peer := range m.peers {
 		if !peer.syncCandidate {
@@ -338,7 +393,7 @@ func (m *SyncManager) startSync() {
 			continue
 		}
 
-		// the best sync candidate is the most updated peer
+		// The best sync candidate is the most updated peer.
 		if bestPeer == nil {
 			bestPeer = peer
 		}
@@ -350,77 +405,78 @@ func (m *SyncManager) startSync() {
 	// Update the state of whether or not the manager believes the chain is
 	// fully synced to whatever the chain believes when there is no candidate
 	// for a sync peer.
+	//
+	// Also, return now when there isn't a sync peer candidate as there is
+	// nothing more to do without one.
 	if bestPeer == nil {
 		m.isCurrentMtx.Lock()
-		m.isCurrent = m.cfg.Chain.IsCurrent()
+		m.isCurrent = chain.IsCurrent()
 		m.isCurrentMtx.Unlock()
+		log.Warnf("No sync peer candidates available")
+		return
 	}
 
-	// Start syncing from the best peer if one was selected.
-	if bestPeer != nil {
-		// Clear the requestedBlocks if the sync peer changes, otherwise
-		// we may ignore blocks we need that the last sync peer failed
-		// to send.
-		m.requestedBlocks = make(map[chainhash.Hash]struct{})
+	// Start syncing from the best peer.
 
-		blkLocator := m.cfg.Chain.LatestBlockLocator()
-		locator := chainBlockLocatorToHashes(blkLocator)
+	// Clear the requestedBlocks if the sync peer changes, otherwise
+	// we may ignore blocks we need that the last sync peer failed
+	// to send.
+	m.requestedBlocks = make(map[chainhash.Hash]struct{})
 
-		log.Infof("Syncing to block height %d from peer %v",
-			bestPeer.LastBlock(), bestPeer.Addr())
+	syncHeight := bestPeer.LastBlock()
 
-		// The chain is not synced whenever the current best height is less than
-		// the height to sync to.
-		if best.Height < bestPeer.LastBlock() {
-			m.isCurrentMtx.Lock()
-			m.isCurrent = false
-			m.isCurrentMtx.Unlock()
-		}
+	headersSynced := m.hdrSyncState.headersSynced
+	if !headersSynced {
+		log.Infof("Syncing headers to block height %d from peer %v", syncHeight,
+			bestPeer)
+	}
 
-		// When the current height is less than a known checkpoint we
-		// can use block headers to learn about which blocks comprise
-		// the chain up to the checkpoint and perform less validation
-		// for them.  This is possible since each header contains the
-		// hash of the previous header and a merkle root.  Therefore if
-		// we validate all of the received headers link together
-		// properly and the checkpoint hashes match, we can be sure the
-		// hashes for the blocks in between are accurate.  Further, once
-		// the full blocks are downloaded, the merkle root is computed
-		// and compared against the value in the header which proves the
-		// full block hasn't been tampered with.
-		//
-		// Once we have passed the final checkpoint, or checkpoints are
-		// disabled, use standard inv messages learn about the blocks
-		// and fully validate them.  Finally, regression test mode does
-		// not support the headers-first approach so do normal block
-		// downloads when in regression test mode.
-		if m.nextCheckpoint != nil &&
-			best.Height < m.nextCheckpoint.Height &&
-			!m.cfg.DisableCheckpoints {
-
-			err := bestPeer.PushGetHeadersMsg(locator, m.nextCheckpoint.Hash)
-			if err != nil {
-				log.Errorf("Failed to push getheadermsg for the latest "+
-					"blocks: %v", err)
-				return
-			}
-			m.headersFirstMode = true
-			log.Infof("Downloading headers for blocks %d to %d from peer %s",
-				best.Height+1, m.nextCheckpoint.Height, bestPeer.Addr())
-		} else {
-			err := bestPeer.PushGetBlocksMsg(locator, &zeroHash)
-			if err != nil {
-				log.Errorf("Failed to push getblocksmsg for the latest "+
-					"blocks: %v", err)
-				return
-			}
-		}
-		m.syncPeer = bestPeer
-		m.syncHeightMtx.Lock()
-		m.syncHeight = bestPeer.LastBlock()
-		m.syncHeightMtx.Unlock()
-	} else {
-		log.Warnf("No sync peer candidates available")
+	// The chain is not synced whenever the current best height is less than the
+	// height to sync to.
+	if best.Height < syncHeight {
+		m.isCurrentMtx.Lock()
+		m.isCurrent = false
+		m.isCurrentMtx.Unlock()
+	}
+
+	// Request headers to discover any blocks that are not already known
+	// starting from the parent of the best known header for the local chain.
+	// The parent is used as a means to accurately discover the best known block
+	// of the remote peer in the case both tips are the same where it would
+	// otherwise result in an empty response.
+	bestHeaderHash, _ := chain.BestHeader()
+	parentHash := bestHeaderHash
+	header, err := chain.HeaderByHash(&bestHeaderHash)
+	if err == nil {
+		parentHash = header.PrevBlock
+	}
+	blkLocator := chain.BlockLocatorFromHash(&parentHash)
+	locator := chainBlockLocatorToHashes(blkLocator)
+	bestPeer.PushGetHeadersMsg(locator, &zeroHash)
+
+	// Track the sync peer and update the sync height when it is higher than the
+	// currently best known value.
+	m.syncPeer = bestPeer
+	m.syncHeightMtx.Lock()
+	if syncHeight > m.syncHeight {
+		m.syncHeight = syncHeight
+	}
+	m.syncHeightMtx.Unlock()
+
+	// Start the header sync progress stall timeout when the initial headers
+	// sync is not already done.
+	if !headersSynced {
+		m.hdrSyncState.resetStallTimeout()
+	}
+
+	// Download any blocks needed to catch the local chain up to the best
+	// known header (if any) when the initial headers sync is already done.
+	//
+	// This is done in addition to the header request above to avoid waiting
+	// for the round trip when there are still blocks that are needed
+	// regardless of the headers response.
+	if headersSynced {
+		m.fetchNextBlocks(m.syncPeer)
 	}
 }
 
@@ -527,28 +583,23 @@ func (m *SyncManager) handleDonePeerMsg(p *peerpkg.Peer) {
 	delete(m.peers, p)
 
 	// Remove requested transactions from the global map so that they will
-	// be fetched from elsewhere next time we get an inv.
+	// be fetched from elsewhere.
 	for txHash := range peer.requestedTxns {
 		delete(m.requestedTxns, txHash)
 	}
 
 	// Remove requested blocks from the global map so that they will be
-	// fetched from elsewhere next time we get an inv.
+	// fetched from elsewhere.
 	// TODO(oga) we could possibly here check which peers have these blocks
 	// and request them now to speed things up a little.
 	for blockHash := range peer.requestedBlocks {
 		delete(m.requestedBlocks, blockHash)
 	}
 
-	// Attempt to find a new peer to sync from if the quitting peer is the
-	// sync peer.  Also, reset the headers-first state if in headers-first
-	// mode so
+	// Attempt to find a new peer to sync from and reset the final requested
+	// block when the quitting peer is the sync peer.
 	if m.syncPeer == peer {
 		m.syncPeer = nil
-		if m.headersFirstMode {
-			best := m.cfg.Chain.BestSnapshot()
-			m.resetHeaderState(&best.Hash, best.Height)
-		}
 		m.startSync()
 	}
 }
@@ -685,135 +736,8 @@ func (m *SyncManager) handleTxMsg(tmsg *txMsg) {
 	m.cfg.PeerNotifier.AnnounceNewTransactions(acceptedTxs)
 }
 
-// isKnownOrphan returns whether the passed hash is currently a known orphan.
-// Keep in mind that only a limited number of orphans are held onto for a
-// limited amount of time, so this function must not be used as an absolute way
-// to test if a block is an orphan block.  A full block (as opposed to just its
-// hash) must be passed to ProcessBlock for that purpose.  This function
-// provides a mechanism for a caller to intelligently detect *recent* duplicate
-// orphans and react accordingly.
-//
-// This function is safe for concurrent access.
-func (m *SyncManager) isKnownOrphan(hash *chainhash.Hash) bool {
-	// Protect concurrent access.  Using a read lock only so multiple readers
-	// can query without blocking each other.
-	m.orphanLock.RLock()
-	_, exists := m.orphans[*hash]
-	m.orphanLock.RUnlock()
-	return exists
-}
-
-// orphanRoot returns the head of the chain for the provided hash from the map
-// of orphan blocks.
-//
-// This function is safe for concurrent access.
-func (m *SyncManager) orphanRoot(hash *chainhash.Hash) *chainhash.Hash {
-	// Protect concurrent access.  Using a read lock only so multiple
-	// readers can query without blocking each other.
-	m.orphanLock.RLock()
-	defer m.orphanLock.RUnlock()
-
-	// Keep looping while the parent of each orphaned block is known and is an
-	// orphan itself.
-	orphanRoot := hash
-	prevHash := hash
-	for {
-		orphan, exists := m.orphans[*prevHash]
-		if !exists {
-			break
-		}
-		orphanRoot = prevHash
-		prevHash = &orphan.block.MsgBlock().Header.PrevBlock
-	}
-
-	return orphanRoot
-}
-
-// removeOrphanBlock removes the passed orphan block from the orphan pool and
-// previous orphan index.
-func (m *SyncManager) removeOrphanBlock(orphan *orphanBlock) {
-	// Protect concurrent access.
-	m.orphanLock.Lock()
-	defer m.orphanLock.Unlock()
-
-	// Remove the orphan block from the orphan pool.
-	orphanHash := orphan.block.Hash()
-	delete(m.orphans, *orphanHash)
-
-	// Remove the reference from the previous orphan index too.  An indexing
-	// for loop is intentionally used over a range here as range does not
-	// reevaluate the slice on each iteration nor does it adjust the index
-	// for the modified slice.
-	prevHash := &orphan.block.MsgBlock().Header.PrevBlock
-	orphans := m.prevOrphans[*prevHash]
-	for i := 0; i < len(orphans); i++ {
-		hash := orphans[i].block.Hash()
-		if hash.IsEqual(orphanHash) {
-			copy(orphans[i:], orphans[i+1:])
-			orphans[len(orphans)-1] = nil
-			orphans = orphans[:len(orphans)-1]
-			i--
-		}
-	}
-	m.prevOrphans[*prevHash] = orphans
-
-	// Remove the map entry altogether if there are no longer any orphans
-	// which depend on the parent hash.
-	if len(m.prevOrphans[*prevHash]) == 0 {
-		delete(m.prevOrphans, *prevHash)
-	}
-}
-
-// addOrphanBlock adds the passed block (which is already determined to be an
-// orphan prior calling this function) to the orphan pool.  It lazily cleans up
-// any expired blocks so a separate cleanup poller doesn't need to be run.  It
-// also imposes a maximum limit on the number of outstanding orphan blocks and
-// will remove the oldest received orphan block if the limit is exceeded.
-func (m *SyncManager) addOrphanBlock(block *dcrutil.Block) {
-	// Remove expired orphan blocks.
-	for _, oBlock := range m.orphans {
-		if time.Now().After(oBlock.expiration) {
-			m.removeOrphanBlock(oBlock)
-			continue
-		}
-
-		// Update the oldest orphan block pointer so it can be discarded
-		// in case the orphan pool fills up.
-		if m.oldestOrphan == nil ||
-			oBlock.expiration.Before(m.oldestOrphan.expiration) {
-			m.oldestOrphan = oBlock
-		}
-	}
-
-	// Limit orphan blocks to prevent memory exhaustion.
-	if len(m.orphans)+1 > maxOrphanBlocks {
-		// Remove the oldest orphan to make room for the new one.
-		m.removeOrphanBlock(m.oldestOrphan)
-		m.oldestOrphan = nil
-	}
-
-	// Protect concurrent access.  This is intentionally done here instead
-	// of near the top since removeOrphanBlock does its own locking and
-	// the range iterator is not invalidated by removing map entries.
-	m.orphanLock.Lock()
-	defer m.orphanLock.Unlock()
-
-	// Insert the block into the orphan map with an expiration time
-	// 1 hour from now.
-	expiration := time.Now().Add(time.Hour)
-	oBlock := &orphanBlock{
-		block:      block,
-		expiration: expiration,
-	}
-	m.orphans[*block.Hash()] = oBlock
-
-	// Add to previous hash lookup index for faster dependency lookups.
-	prevHash := &block.MsgBlock().Header.PrevBlock
-	m.prevOrphans[*prevHash] = append(m.prevOrphans[*prevHash], oBlock)
-}
-
-// maybeUpdateIsCurrent potentially updates the manager to signal it believes the
-// chain is considered synced.
+// maybeUpdateIsCurrent potentially updates the manager to signal it believes
+// the chain is considered synced.
 //
 // This function MUST be called with the is current mutex held (for writes).
 func (m *SyncManager) maybeUpdateIsCurrent() {
@@ -831,103 +755,24 @@ func (m *SyncManager) maybeUpdateIsCurrent() {
 	}
 }
 
-// processOrphans determines if there are any orphans which depend on the passed
-// block hash (they are no longer orphans if true) and potentially accepts them.
-// It repeats the process for the newly accepted blocks (to detect further
-// orphans which may no longer be orphans) until there are no more.
-//
-// The flags do not modify the behavior of this function directly, however they
-// are needed to pass along to maybeAcceptBlock.
-func (m *SyncManager) processOrphans(hash *chainhash.Hash, flags blockchain.BehaviorFlags) error {
-	// Start with processing at least the passed hash.  Leave a little room for
-	// additional orphan blocks that need to be processed without needing to
-	// grow the array in the common case.
-	processHashes := make([]*chainhash.Hash, 0, 10)
-	processHashes = append(processHashes, hash)
-	for len(processHashes) > 0 {
-		// Pop the first hash to process from the slice.
-		processHash := processHashes[0]
-		processHashes[0] = nil // Prevent GC leak.
-		processHashes = processHashes[1:]
-
-		// Look up all orphans that are parented by the block we just accepted.
-		// This will typically only be one, but it could be multiple if multiple
-		// blocks are mined and broadcast around the same time.  The one with
-		// the most proof of work will eventually win out.  An indexing for loop
-		// is intentionally used over a range here as range does not reevaluate
-		// the slice on each iteration nor does it adjust the index for the
-		// modified slice.
-		for i := 0; i < len(m.prevOrphans[*processHash]); i++ {
-			orphan := m.prevOrphans[*processHash][i]
-			if orphan == nil {
-				log.Warnf("Found a nil entry at index %d in the orphan "+
-					"dependency list for block %v", i, processHash)
-				continue
-			}
-
-			// Remove the orphan from the orphan pool.
-			orphanHash := orphan.block.Hash()
-			m.removeOrphanBlock(orphan)
-			i--
-
-			// Potentially accept the block into the block chain.
-			_, err := m.cfg.Chain.ProcessBlock(orphan.block, flags)
-			if err != nil {
-				return err
-			}
-			m.isCurrentMtx.Lock()
-			m.maybeUpdateIsCurrent()
-			m.isCurrentMtx.Unlock()
-
-			// Add this block to the list of blocks to process so any orphan
-			// blocks that depend on this block are handled too.
-			processHashes = append(processHashes, orphanHash)
-		}
-	}
-	return nil
-}
-
-// processBlockAndOrphans processes the provided block using the internal chain
-// instance while keeping track of orphan blocks and also processing any orphans
-// that depend on the passed block to potentially accept as well.
+// processBlock processes the provided block using the internal chain instance.
 //
 // When no errors occurred during processing, the first return value indicates
 // the length of the fork the block extended.  In the case it either extended
 // the best chain or is now the tip of the best chain due to causing a
-// reorganize, the fork length will be 0.  The second return value indicates
-// whether or not the block is an orphan, in which case the fork length will
-// also be zero as expected, because it, by definition, does not connect to the
-// best chain.
-func (m *SyncManager) processBlockAndOrphans(block *dcrutil.Block, flags blockchain.BehaviorFlags) (int64, bool, error) {
+// reorganize, the fork length will be 0.  Orphans are rejected and can be
+// detected by checking if the error is blockchain.ErrMissingParent.
+func (m *SyncManager) processBlock(block *dcrutil.Block, flags blockchain.BehaviorFlags) (int64, error) {
 	// Process the block to include validation, best chain selection, etc.
-	//
-	// Also, keep track of orphan blocks in the sync manager when the error
-	// returned indicates the block is an orphan.
-	blockHash := block.Hash()
 	forkLen, err := m.cfg.Chain.ProcessBlock(block, flags)
-	if errors.Is(err, blockchain.ErrMissingParent) {
-		log.Infof("Adding orphan block %v with parent %v", blockHash,
-			block.MsgBlock().Header.PrevBlock)
-		m.addOrphanBlock(block)
-
-		// The fork length of orphans is unknown since they, by definition, do
-		// not connect to the best chain.
-		return 0, true, nil
-	}
 	if err != nil {
-		return 0, false, err
+		return 0, err
 	}
 	m.isCurrentMtx.Lock()
 	m.maybeUpdateIsCurrent()
 	m.isCurrentMtx.Unlock()
 
-	// Accept any orphan blocks that depend on this block (they are no longer
-	// orphans) and repeat for those accepted blocks until there are no more.
-	if err := m.processOrphans(blockHash, flags); err != nil {
-		return 0, false, err
-	}
-
-	return forkLen, false, nil
+	return forkLen, nil
 }
 
 // handleBlockMsg handles block messages from all peers.
@@ -941,49 +786,38 @@ func (m *SyncManager) handleBlockMsg(bmsg *blockMsg) {
 	blockHash := bmsg.block.Hash()
 	if _, exists := peer.requestedBlocks[*blockHash]; !exists {
 		log.Warnf("Got unrequested block %v from %s -- disconnecting",
-			blockHash, peer.Addr())
+			blockHash, peer)
 		peer.Disconnect()
 		return
 	}
 
-	// When in headers-first mode, if the block matches the hash of the
-	// first header in the list of headers that are being fetched, it's
-	// eligible for less validation since the headers have already been
-	// verified to link together and are valid up to the next checkpoint.
-	// Also, remove the list entry for all blocks except the checkpoint
-	// since it is needed to verify the next round of headers links
-	// properly.
-	isCheckpointBlock := false
-	behaviorFlags := blockchain.BFNone
-	if m.headersFirstMode {
-		firstNodeEl := m.headerList.Front()
-		if firstNodeEl != nil {
-			firstNode := firstNodeEl.Value.(*headerNode)
-			if blockHash.IsEqual(firstNode.hash) {
-				behaviorFlags |= blockchain.BFFastAdd
-				if firstNode.hash.IsEqual(m.nextCheckpoint.Hash) {
-					isCheckpointBlock = true
-				} else {
-					m.headerList.Remove(firstNodeEl)
-				}
-			}
-		}
-	}
+	// Save whether or not the chain believes it is current prior to processing
+	// the block for use below in determining logging behavior.
+	chain := m.cfg.Chain
+	wasChainCurrent := chain.IsCurrent()
 
-	// Remove block from request maps. Either chain will know about it and
-	// so we shouldn't have any more instances of trying to fetch it, or we
-	// will fail the insert and thus we'll retry next time we get an inv.
+	// Process the block to include validation, best chain selection, etc.
+	//
+	// Also, remove the block from the request maps once it has been processed.
+	// This ensures chain is aware of the block before it is removed from the
+	// maps in order to help prevent duplicate requests.
+	forkLen, err := m.processBlock(bmsg.block, blockchain.BFNone)
 	delete(peer.requestedBlocks, *blockHash)
 	delete(m.requestedBlocks, *blockHash)
-
-	// Process the block to include validation, best chain selection, orphan
-	// handling, etc.
-	forkLen, isOrphan, err := m.processBlockAndOrphans(bmsg.block, behaviorFlags)
 	if err != nil {
+		// Ideally there should never be any requests for duplicate blocks, but
+		// ignore any that manage to make it through.
+		if errors.Is(err, blockchain.ErrDuplicateBlock) {
+			return
+		}
+
 		// When the error is a rule error, it means the block was simply
-		// rejected as opposed to something actually going wrong, so log
-		// it as such.  Otherwise, something really did go wrong, so log
-		// it as an actual error.
+		// rejected as opposed to something actually going wrong, so log it as
+		// such.  Otherwise, something really did go wrong, so log it as an
+		// actual error.
+		//
+		// Note that orphan blocks are never requested so there is no need to
+		// test for that rule error separately.
 		var rErr blockchain.RuleError
 		if errors.As(err, &rErr) {
 			log.Infof("Rejected block %v from %s: %v", blockHash, peer, err)
@@ -994,51 +828,69 @@ func (m *SyncManager) handleBlockMsg(bmsg *blockMsg) {
 			log.Errorf("Critical failure: %v", err)
 		}
 
-		// Convert the error into an appropriate reject message and
-		// send it.
+		// Convert the error into an appropriate reject message and send it.
 		code, reason := errToWireRejectCode(err)
 		peer.PushRejectMsg(wire.CmdBlock, code, reason, blockHash, false)
 		return
 	}
 
-	// Request the parents for the orphan block from the peer that sent it.
-	onMainChain := !isOrphan && forkLen == 0
-	if isOrphan {
-		orphanRoot := m.orphanRoot(blockHash)
-		blkLocator := m.cfg.Chain.LatestBlockLocator()
-		locator := chainBlockLocatorToHashes(blkLocator)
-		err := peer.PushGetBlocksMsg(locator, orphanRoot)
-		if err != nil {
-			log.Warnf("Failed to push getblocksmsg for the latest block: %v",
-				err)
+	// Log information about the block.  Use the progress logger when the chain
+	// was not already current prior to processing the block to provide nicer
+	// periodic logging with a progress percentage.  Otherwise, log the block
+	// individually along with some stats.
+	msgBlock := bmsg.block.MsgBlock()
+	header := &msgBlock.Header
+	if !wasChainCurrent {
+		forceLog := int64(header.Height) >= m.SyncHeight()
+		m.progressLogger.LogProgress(msgBlock, forceLog, chain.VerifyProgress)
+		if chain.IsCurrent() {
+			best := chain.BestSnapshot()
+			log.Infof("Initial chain sync complete (hash %s, height %d)",
+				best.Hash, best.Height)
 		}
 	} else {
-		// When the block is not an orphan, log information about it and
-		// update the chain state.
-		m.progressLogger.LogBlockHeight(bmsg.block.MsgBlock(), m.SyncHeight())
-
-		if onMainChain {
-			// Notify stake difficulty subscribers and prune invalidated
-			// transactions.
-			best := m.cfg.Chain.BestSnapshot()
-			m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff, best.Height)
-			m.cfg.TxMemPool.PruneExpiredTx()
-
-			// Clear the rejected transactions.
-			m.rejectedTxns = make(map[chainhash.Hash]struct{})
+		var interval string
+		prevBlockHeader, err := chain.HeaderByHash(&header.PrevBlock)
+		if err == nil {
+			diff := header.Timestamp.Sub(prevBlockHeader.Timestamp)
+			interval = ", interval " + diff.Round(time.Second).String()
 		}
+
+		numTxns := uint64(len(msgBlock.Transactions))
+		numTickets := uint64(header.FreshStake)
+		numVotes := uint64(header.Voters)
+		numRevokes := uint64(header.Revocations)
+		log.Infof("New block %s (%d %s, %d %s, %d %s, %d %s, height %d%s)",
+			blockHash, numTxns, pickNoun(numTxns, "transaction", "transactions"),
+			numTickets, pickNoun(numTickets, "ticket", "tickets"),
+			numVotes, pickNoun(numVotes, "vote", "votes"),
+			numRevokes, pickNoun(numRevokes, "revocation", "revocations"),
+			header.Height, interval)
+	}
+
+	// Perform some additional processing when the block extended the main
+	// chain.
+	onMainChain := forkLen == 0
+	if onMainChain {
+		// Prune invalidated transactions.
+		best := chain.BestSnapshot()
+		m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff, best.Height)
+		m.cfg.TxMemPool.PruneExpiredTx()
+
+		// Clear the rejected transactions.
+		m.rejectedTxns = make(map[chainhash.Hash]struct{})
 	}
 
 	// Update the latest block height for the peer to avoid stale heights when
 	// looking for future potential sync node candidacy.
 	//
-	// Also, when the block is an orphan or the chain is considered current and
-	// the block was accepted to the main chain, update the heights of other
-	// peers whose invs may have been ignored when actively syncing while the
-	// chain was not yet current or lost the lock announcement race.
-	blockHeight := int64(bmsg.block.MsgBlock().Header.Height)
+	// Also, when the chain is considered current and the block was accepted to
+	// the main chain, update the heights of other peers whose invs may have
+	// been ignored when actively syncing while the chain was not yet current or
+	// lost the lock announcement race.
+	blockHeight := int64(header.Height)
 	peer.UpdateLastBlockHeight(blockHeight)
-	if isOrphan || (onMainChain && m.IsCurrent()) {
+	if onMainChain && m.IsCurrent() {
 		for _, p := range m.peers {
 			// The height for the sending peer is already updated.
 			if p == peer {
@@ -1053,97 +905,70 @@ func (m *SyncManager) handleBlockMsg(bmsg *blockMsg) {
 		}
 	}
 
-	// Nothing more to do if we aren't in headers-first mode.
-	if !m.headersFirstMode {
-		return
-	}
-
-	// This is headers-first mode, so if the block is not a checkpoint
-	// request more blocks using the header list when the request queue is
-	// getting short.
-	if !isCheckpointBlock {
-		if m.startHeader != nil &&
-			len(peer.requestedBlocks) < minInFlightBlocks {
-			m.fetchHeaderBlocks()
-		}
-		return
+	// Request more blocks using the headers when the request queue is getting
+	// short.
+	if peer == m.syncPeer && len(peer.requestedBlocks) < minInFlightBlocks {
+		m.fetchNextBlocks(peer)
 	}
+}
 
-	// This is headers-first mode and the block is a checkpoint.  When
-	// there is a next checkpoint, get the next round of headers by asking
-	// for headers starting from the block after this one up to the next
-	// checkpoint.
-	prevHeight := m.nextCheckpoint.Height
-	prevHash := m.nextCheckpoint.Hash
-	m.nextCheckpoint = m.findNextHeaderCheckpoint(prevHeight)
-	if m.nextCheckpoint != nil {
-		locator := []chainhash.Hash{*prevHash}
-		err := peer.PushGetHeadersMsg(locator, m.nextCheckpoint.Hash)
-		if err != nil {
-			log.Warnf("Failed to send getheaders message to peer %s: %v",
-				peer.Addr(), err)
-			return
-		}
-		log.Infof("Downloading headers for blocks %d to %d from peer %s",
-			prevHeight+1, m.nextCheckpoint.Height, m.syncPeer.Addr())
-		return
-	}
+// guessHeaderSyncProgress returns a percentage that is a guess of the progress
+// of the header sync progress for the given currently best known header based
+// on an algorithm that considers the total number of expected headers based on
+// the target time per block of the network.  It should only be used for the
+// main and test networks because it relies on relatively consistent mining
+// which is not the case for other network such as the simulation test network.
+//
+// This function is safe for concurrent access.
+func (m *SyncManager) guessHeaderSyncProgress(header *wire.BlockHeader) float64 {
+	// Calculate the expected total number of blocks to reach the current time
+	// by considering the number there already are plus the expected number of
+	// remaining ones there should be in the time interval since the provided
+	// best known header and the current time given the target block time.
+	//
+	// This approach is used as opposed to calculating the total expected since
+	// the genesis block since it gets more accurate as more headers are
+	// processed and thus provide more information.  It is also more robust
+	// against networks with dynamic difficulty readjustment such as the test
+	// network.
+	curTimestamp := m.cfg.TimeSource.AdjustedTime().Unix()
+	targetSecsPerBlock := int64(m.cfg.ChainParams.TargetTimePerBlock.Seconds())
+	remaining := (curTimestamp - header.Timestamp.Unix()) / targetSecsPerBlock
+	expectedTotal := int64(header.Height) + remaining
+
+	// Finally the progress guess is simply the ratio of the current number of
+	// known headers to the total expected number of headers.
+	return math.Min(float64(header.Height)/float64(expectedTotal), 1.0) * 100
+}
 
-	// This is headers-first mode, the block is a checkpoint, and there are
-	// no more checkpoints, so switch to normal mode by requesting blocks
-	// from the block after this one up to the end of the chain (zero hash).
-	m.headersFirstMode = false
-	m.headerList.Init()
-	log.Infof("Reached the final checkpoint -- switching to normal mode")
-	locator := []chainhash.Hash{*blockHash}
-	err = peer.PushGetBlocksMsg(locator, &zeroHash)
+// headerSyncProgress returns a percentage that is a guess of the progress of
+// of the header sync process.
+//
+// This function is safe for concurrent access.
+func (m *SyncManager) headerSyncProgress() float64 {
+	hash, _ := m.cfg.Chain.BestHeader()
+	header, err := m.cfg.Chain.HeaderByHash(&hash)
 	if err != nil {
-		log.Warnf("Failed to send getblocks message to peer %s: %v",
-			peer.Addr(), err)
-		return
+		return 0.0
 	}
-}
 
-// fetchHeaderBlocks creates and sends a request to the syncPeer for the next
-// list of blocks to be downloaded based on the current list of headers.
-func (m *SyncManager) fetchHeaderBlocks() {
-	// Nothing to do if there is no start header.
-	if m.startHeader == nil {
-		log.Warnf("fetchHeaderBlocks called with no start header")
-		return
+	// Use an algorithm that considers the total number of expected headers
+	// based on the target time per block of the network for the main and test
+	// networks.  This is the preferred approach because, unlike the sync height
+	// reported by remote peers, it is difficult to game since it is based on
+	// the target proof of work, but it assumes consistent mining, which is not
+	// the case on all networks, so limit it to the two where that applies.
+	net := m.cfg.ChainParams.Net
+	if net == wire.MainNet || net == wire.TestNet3 {
+		return m.guessHeaderSyncProgress(&header)
 	}
 
-	// Build up a getdata request for the list of blocks the headers
-	// describe.  The size hint will be limited to wire.MaxInvPerMsg by
-	// the function, so no need to double check it here.
-	gdmsg := wire.NewMsgGetDataSizeHint(uint(m.headerList.Len()))
-	numRequested := 0
-	for e := m.startHeader; e != nil; e = e.Next() {
-		node, ok := e.Value.(*headerNode)
-		if !ok {
-			log.Warn("Header list node type is not a headerNode")
-			continue
-		}
-
-		if m.needBlock(node.hash) {
-			iv := wire.NewInvVect(wire.InvTypeBlock, node.hash)
-			m.requestedBlocks[*node.hash] = struct{}{}
-			m.syncPeer.requestedBlocks[*node.hash] = struct{}{}
-			err := gdmsg.AddInvVect(iv)
-			if err != nil {
-				log.Warnf("Failed to add invvect while fetching block "+
-					"headers: %v", err)
-			}
-			numRequested++
-		}
-		m.startHeader = e.Next()
-		if numRequested >= wire.MaxInvPerMsg {
-			break
-		}
-	}
-	if len(gdmsg.InvList) > 0 {
-		m.syncPeer.QueueMessage(gdmsg, nil)
+	// Fall back to using the sync height reported by the remote peer otherwise.
+	syncHeight := m.SyncHeight()
+	if syncHeight == 0 {
+		return 0.0
 	}
+	return math.Min(float64(header.Height)/float64(syncHeight), 1.0) * 100
 }
 
 // handleHeadersMsg handles headers messages from all peers.
@@ -1153,97 +978,226 @@ func (m *SyncManager) handleHeadersMsg(hmsg *headersMsg) {
 		return
 	}
 
-	// The remote peer is misbehaving if we didn't request headers.
-	msg := hmsg.headers
-	numHeaders := len(msg.Headers)
-	if !m.headersFirstMode {
-		log.Warnf("Got %d unrequested headers from %s -- disconnecting",
-			numHeaders, peer.Addr())
-		peer.Disconnect()
+	// Nothing to do for an empty headers message as it means the sending peer
+	// does not have any additional headers for the requested block locator.
+	headers := hmsg.headers.Headers
+	numHeaders := len(headers)
+	if numHeaders == 0 {
 		return
 	}
 
-	// Nothing to do for an empty headers message.
-	if numHeaders == 0 {
+	// Handle the case where the first header does not connect to any known
+	// headers specially.
+	chain := m.cfg.Chain
+	firstHeader := headers[0]
+	firstHeaderHash := firstHeader.BlockHash()
+	firstHeaderConnects := chain.HaveHeader(&firstHeader.PrevBlock)
+	headersSynced := m.hdrSyncState.headersSynced
+	if !firstHeaderConnects {
+		// Ignore headers that do not connect to any known headers when the
+		// initial headers sync is taking place.  It is expected that headers
+		// will be announced that are not yet known.
+		if !headersSynced {
+			return
+		}
+
+		// Attempt to detect block announcements which do not connect to any
+		// known headers and request any headers starting from the best header
+		// the local chain knows in order to (hopefully) discover the missing
+		// headers.
+		//
+		// Meanwhile, also keep track of how many times the peer has
+		// consecutively sent a headers message that does not connect and
+		// disconnect it once the max allowed threshold has been reached.
+		if numHeaders < maxExpectedHeaderAnnouncementsPerMsg {
+			peer.numConsecutiveOrphanHeaders++
+			if peer.numConsecutiveOrphanHeaders >= maxConsecutiveOrphanHeaders {
+				log.Debugf("Received %d consecutive headers messages that do "+
+					"not connect from peer %s -- disconnecting",
+					peer.numConsecutiveOrphanHeaders, peer)
+				peer.Disconnect()
+			}
+
+			log.Debugf("Requesting missing parents for header %s (height %d) "+
+				"received from peer %s", firstHeaderHash, firstHeader.Height,
+				peer)
+			bestHeaderHash, _ := chain.BestHeader()
+			blkLocator := chain.BlockLocatorFromHash(&bestHeaderHash)
+			locator := chainBlockLocatorToHashes(blkLocator)
+			peer.PushGetHeadersMsg(locator, &zeroHash)
+
+			return
+		}
+
+		// The initial headers sync process is done and this does not appear to
+		// be a block announcement, so disconnect the peer.
+		log.Debugf("Received orphan header from peer %s -- disconnecting", peer)
+		peer.Disconnect()
 		return
 	}
 
-	// Process all of the received headers ensuring each one connects to the
-	// previous and that checkpoints match.
-	receivedCheckpoint := false
-	var finalHash *chainhash.Hash
-	for _, blockHeader := range msg.Headers {
-		blockHash := blockHeader.BlockHash()
-		finalHash = &blockHash
-
-		// Ensure there is a previous header to compare against.
-		prevNodeEl := m.headerList.Back()
-		if prevNodeEl == nil {
-			log.Warnf("Header list does not contain a previous element as " +
-				"expected -- disconnecting peer")
+	// Ensure all of the received headers connect the previous one before
+	// attempting to perform any further processing on any of them.
+	headerHashes := make([]chainhash.Hash, 0, len(headers))
+	headerHashes = append(headerHashes, firstHeaderHash)
+	for prevIdx, header := range headers[1:] {
+		prevHash := &headerHashes[prevIdx]
+		prevHeight := headers[prevIdx].Height
+		if header.PrevBlock != *prevHash || header.Height != prevHeight+1 {
+			log.Debugf("Received block header that does not properly connect "+
+				"to previous one from peer %s -- disconnecting", peer)
 			peer.Disconnect()
 			return
 		}
+		headerHashes = append(headerHashes, header.BlockHash())
+	}
 
-		// Ensure the header properly connects to the previous one and
-		// add it to the list of headers.
-		node := headerNode{hash: &blockHash}
-		prevNode := prevNodeEl.Value.(*headerNode)
-		if prevNode.hash.IsEqual(&blockHeader.PrevBlock) {
-			node.height = prevNode.height + 1
-			e := m.headerList.PushBack(&node)
-			if m.startHeader == nil {
-				m.startHeader = e
-			}
-		} else {
-			log.Warnf("Received block header that does not properly connect "+
-				"to the chain from peer %s -- disconnecting", peer.Addr())
+	// Save the current best known header height prior to processing the headers
+	// so the code later is able to determine if any new useful headers were
+	// provided.
+	_, prevBestHeaderHeight := chain.BestHeader()
+
+	// Process all of the received headers.
+	for _, header := range headers {
+		err := chain.ProcessBlockHeader(header, blockchain.BFNone)
+		if err != nil {
+			// Note that there is no need to check for an orphan header here
+			// because they were already verified to connect above.
+
+			log.Debugf("Failed to process block header %s from peer %s: %v -- "+
+				"disconnecting", header.BlockHash(), peer, err)
 			peer.Disconnect()
 			return
 		}
+	}
+
+	// All of the headers were either accepted or already known valid at this
+	// point.
 
-		// Verify the header at the next checkpoint height matches.
-		if node.height == m.nextCheckpoint.Height {
-			if node.hash.IsEqual(m.nextCheckpoint.Hash) {
-				receivedCheckpoint = true
-				log.Infof("Verified downloaded block header against "+
-					"checkpoint at height %d/hash %s", node.height, node.hash)
-			} else {
-				log.Warnf("Block header at height %d/hash %s from peer %s "+
-					"does NOT match expected checkpoint hash of %s -- "+
-					"disconnecting", node.height, node.hash, peer.Addr(),
-					m.nextCheckpoint.Hash)
+	// Reset the header sync progress stall timeout when the headers are not
+	// already synced and progress was made.
+	newBestHeaderHash, newBestHeaderHeight := chain.BestHeader()
+	if peer == m.syncPeer && !headersSynced {
+		if newBestHeaderHeight > prevBestHeaderHeight {
+			m.hdrSyncState.resetStallTimeout()
+		}
+	}
+
+	// Reset the count of consecutive headers messages that contained headers
+	// which do not connect.  Note that this is intentionally only done when all
+	// of the provided headers are successfully processed above.
+	peer.numConsecutiveOrphanHeaders = 0
+
+	// Update the last announced block to the final one in the announced headers
+	// above and update the height for the peer too.
+	finalHeader := headers[len(headers)-1]
+	finalReceivedHash := &headerHashes[len(headerHashes)-1]
+	peer.UpdateLastAnnouncedBlock(finalReceivedHash)
+	peer.UpdateLastBlockHeight(int64(finalHeader.Height))
+
+	// Update the sync height if the new best known header height exceeds it.
+	syncHeight := m.SyncHeight()
+	if newBestHeaderHeight > syncHeight {
+		syncHeight = newBestHeaderHeight
+		m.syncHeightMtx.Lock()
+		m.syncHeight = syncHeight
+		m.syncHeightMtx.Unlock()
+	}
+
+	// Disconnect outbound peers that have less cumulative work than the minimum
+	// value already known to have been achieved on the network a priori while
+	// the initial sync is still underway.  This is determined by noting that a
+	// peer only sends fewer than the maximum number of headers per message when
+	// it has reached its best known header.
+	isChainCurrent := chain.IsCurrent()
+	receivedMaxHeaders := len(headers) == wire.MaxBlockHeadersPerMsg
+	if !isChainCurrent && !peer.Inbound() && !receivedMaxHeaders {
+		minKnownWork := m.cfg.ChainParams.MinKnownChainWork
+		if minKnownWork != nil {
+			workSum, err := chain.ChainWork(finalReceivedHash)
+			if err == nil && workSum.Cmp(minKnownWork) < 0 {
+				log.Debugf("Best known chain for peer %s has too little "+
+					"cumulative work -- disconnecting", peer)
 				peer.Disconnect()
 				return
 			}
-			break
 		}
 	}
 
-	// When this header is a checkpoint, switch to fetching the blocks for
-	// all of the headers since the last checkpoint.
-	if receivedCheckpoint {
-		// Since the first entry of the list is always the final block
-		// that is already in the database and is only used to ensure
-		// the next header links properly, it must be removed before
-		// fetching the blocks.
-		m.headerList.Remove(m.headerList.Front())
-		log.Infof("Received %v block headers: Fetching blocks",
-			m.headerList.Len())
-		m.progressLogger.SetLastLogTime(time.Now())
-		m.fetchHeaderBlocks()
-		return
+	// Request more headers when the peer announced the maximum number of
+	// headers that can be sent in a single message since it probably has more.
+	if receivedMaxHeaders {
+		blkLocator := chain.BlockLocatorFromHash(finalReceivedHash)
+		locator := chainBlockLocatorToHashes(blkLocator)
+		peer.PushGetHeadersMsg(locator, &zeroHash)
+
+		m.progressLogger.LogHeaderProgress(uint64(len(headers)), headersSynced,
+			m.headerSyncProgress)
+	}
+
+	// Consider the headers synced once the sync peer sends a message with a
+	// final header that is within a few blocks of the sync height.
+	if !headersSynced && peer == m.syncPeer {
+		const syncHeightFetchOffset = 6
+		if int64(finalHeader.Height)+syncHeightFetchOffset > syncHeight {
+			headersSynced = true
+			m.hdrSyncState.headersSynced = headersSynced
+			m.hdrSyncState.stopStallTimeout()
+
+			m.progressLogger.LogHeaderProgress(uint64(len(headers)),
+				headersSynced, m.headerSyncProgress)
+			log.Infof("Initial headers sync complete (best header hash %s, "+
+				"height %d)", newBestHeaderHash, newBestHeaderHeight)
+			log.Info("Syncing chain")
+			m.progressLogger.SetLastLogTime(time.Now())
+
+			// Potentially update whether the chain believes it is current now
+			// that the headers are synced.
+			chain.MaybeUpdateIsCurrent()
+			isChainCurrent = chain.IsCurrent()
+			if isChainCurrent {
+				best := chain.BestSnapshot()
+				log.Infof("Initial chain sync complete (hash %s, height %d)",
+					best.Hash, best.Height)
+			}
+		}
 	}
 
-	// This header is not a checkpoint, so request the next batch of
-	// headers starting from the latest known header and ending with the
-	// next checkpoint.
-	locator := []chainhash.Hash{*finalHash}
-	err := peer.PushGetHeadersMsg(locator, m.nextCheckpoint.Hash)
-	if err != nil {
-		log.Warnf("Failed to send getheaders message to peer %s: %v",
-			peer.Addr(), err)
-		return
+	// Immediately download blocks associated with the announced headers once
+	// the chain is current.  This allows downloading from whichever peer
+	// announces it first and also ensures any side chain blocks are downloaded
+	// for vote consideration.
+	//
+	// Ultimately, it would likely be better for this functionality to be moved
+	// to the code which determines the next blocks to request based on the
+	// available headers once that code supports downloading from multiple peers
+	// and associated infrastructure to efficiently determinine which peers have
+	// the associated block(s).
+	if isChainCurrent {
+		gdmsg := wire.NewMsgGetDataSizeHint(uint(len(headers)))
+		for i := range headerHashes {
+			// Skip the block when it has already been requested or is otherwise
+			// already known.
+			hash := &headerHashes[i]
+			_, isRequestedBlock := m.requestedBlocks[*hash]
+			if isRequestedBlock || chain.HaveBlock(hash) {
+				continue
+			}
+
+			iv := wire.NewInvVect(wire.InvTypeBlock, hash)
+			limitAdd(m.requestedBlocks, *hash, maxRequestedBlocks)
+			limitAdd(peer.requestedBlocks, *hash, maxRequestedBlocks)
+			gdmsg.AddInvVect(iv)
+		}
+		if len(gdmsg.InvList) > 0 {
+			peer.QueueMessage(gdmsg, nil)
+		}
+	}
+
+	// Download any blocks needed to catch the local chain up to the best known
+	// header (if any) once the initial headers sync is done.
+	if headersSynced && m.syncPeer != nil {
+		m.fetchNextBlocks(m.syncPeer)
 	}
 }
 
@@ -1272,12 +1226,6 @@ func (m *SyncManager) handleNotFoundMsg(nfmsg *notFoundMsg) {
 	}
 }
 
-// needBlock returns whether or not the block needs to be downloaded.  For
-// example, it does not need to be downloaded when it is already known.
-func (m *SyncManager) needBlock(hash *chainhash.Hash) bool {
-	return !m.isKnownOrphan(hash) && !m.cfg.Chain.HaveBlock(hash)
-}
-
 // needTx returns whether or not the transaction needs to be downloaded.  For
 // example, it does not need to be downloaded when it is already known.
 func (m *SyncManager) needTx(hash *chainhash.Hash) bool {
@@ -1300,14 +1248,14 @@ func (m *SyncManager) needTx(hash *chainhash.Hash) bool {
 	return false
 }
 
-// handleInvMsg handles inv messages from all peers.
-// We examine the inventory advertised by the remote peer and act accordingly.
+// handleInvMsg handles inv messages from all peers.  This entails examining the
+// inventory advertised by the remote peer for block and transaction
+// announcements and acting accordingly.
 func (m *SyncManager) handleInvMsg(imsg *invMsg) {
 	peer := lookupPeer(imsg.peer, m.peers)
 	if peer == nil {
 		return
 	}
-	fromSyncPeer := peer == m.syncPeer
 	isCurrent := m.IsCurrent()
 
 	// Update state information regarding per-peer known inventory and determine
@@ -1321,6 +1269,14 @@ func (m *SyncManager) handleInvMsg(imsg *invMsg) {
 	for _, iv := range imsg.inv.InvList {
 		switch iv.Type {
 		case wire.InvTypeBlock:
+			// NOTE: All block announcements are now made via headers and the
+			// decisions regarding which blocks to download are based on those
+			// headers.  Therefore, there is no need to request anything here.
+			//
+			// Also, there realistically should not typically be any inv
+			// messages with a type of block for the same reason.  However, it
+			// doesn't hurt to update the state accordingly just in case.
+
 			// Add the block to the cache of known inventory for the peer.  This
 			// helps avoid sending blocks to the peer that it is already known
 			// to have.
@@ -1329,22 +1285,6 @@ func (m *SyncManager) handleInvMsg(imsg *invMsg) {
 			// Update the last block in the announced inventory.
 			lastBlock = iv
 
-			// Ignore block announcements for blocks that are from peers other
-			// than the sync peer before the chain is current or are otherwise
-			// not needed, such as when they're already available.
-			//
-			// This helps prevent fetching a mass of orphans.
-			if (!fromSyncPeer && !isCurrent) || !m.needBlock(&iv.Hash) {
-				continue
-			}
-
-			// Request the block if there is not one already pending.
-			if _, exists := m.requestedBlocks[iv.Hash]; !exists {
-				limitAdd(m.requestedBlocks, iv.Hash, maxRequestedBlocks)
-				limitAdd(peer.requestedBlocks, iv.Hash, maxRequestedBlocks)
-				requestQueue = append(requestQueue, iv)
-			}
-
 		case wire.InvTypeTx:
 			// Add the tx to the cache of known inventory for the peer.  This
 			// helps avoid sending transactions to the peer that it is already
@@ -1372,26 +1312,6 @@ func (m *SyncManager) handleInvMsg(imsg *invMsg) {
 	}
 
 	if lastBlock != nil {
-		// When the block is an orphan that we already have, the missing parent
-		// blocks were requested when the orphan was processed.  In that case,
-		// there were more blocks missing than are allowed into a single
-		// inventory message.  As a result, once this peer requested the final
-		// advertised block, the remote peer noticed and is now resending the
-		// orphan block as an available block to signal there are more missing
-		// blocks that need to be requested.
-		if m.isKnownOrphan(&lastBlock.Hash) {
-			// Request blocks starting at the latest known up to the root of the
-			// orphan that just came in.
-			orphanRoot := m.orphanRoot(&lastBlock.Hash)
-			blkLocator := m.cfg.Chain.LatestBlockLocator()
-			locator := chainBlockLocatorToHashes(blkLocator)
-			err := peer.PushGetBlocksMsg(locator, orphanRoot)
-			if err != nil {
-				log.Errorf("Failed to push getblocksmsg for orphan chain: %v",
-					err)
-			}
-		}
-
 		// Update the last announced block to the final one in the announced
 		// inventory above (if any).  In the case the header for that block is
 		// already known, use that information to update the height for the peer
@@ -1403,15 +1323,6 @@ func (m *SyncManager) handleInvMsg(imsg *invMsg) {
 				peer.UpdateLastBlockHeight(int64(header.Height))
 			}
 		}
-
-		// Request blocks after the last one advertised up to the final one the
-		// remote peer knows about (zero stop hash).
-		blkLocator := m.cfg.Chain.BlockLocatorFromHash(&lastBlock.Hash)
-		locator := chainBlockLocatorToHashes(blkLocator)
-		err := peer.PushGetBlocksMsg(locator, &zeroHash)
-		if err != nil {
-			log.Errorf("Failed to push getblocksmsg: %v", err)
-		}
 	}
 
 	// Request as much as possible at once.
@@ -1512,18 +1423,16 @@ out:
 				}
 
 			case processBlockMsg:
-				forkLen, isOrphan, err := m.processBlockAndOrphans(msg.block,
-					msg.flags)
+				forkLen, err := m.processBlock(msg.block, msg.flags)
 				if err != nil {
 					msg.reply <- processBlockResponse{
-						forkLen:  forkLen,
-						isOrphan: isOrphan,
-						err:      err,
+						forkLen: forkLen,
+						err:     err,
 					}
 					continue
 				}
 
-				onMainChain := !isOrphan && forkLen == 0
+				onMainChain := forkLen == 0
 				if onMainChain {
 					// Prune invalidated transactions.
 					best := m.cfg.Chain.BestSnapshot()
@@ -1533,8 +1442,7 @@ out:
 				}
 
 				msg.reply <- processBlockResponse{
-					isOrphan: isOrphan,
-					err:      nil,
+					err: nil,
 				}
 
 			case processTransactionMsg:
@@ -1549,6 +1457,18 @@ out:
 				log.Warnf("Invalid message type in event handler: %T", msg)
 			}
 
+		case <-m.hdrSyncState.stallTimer.C:
+			// Mark the timer's channel as having been drained so the timer can
+			// safely be reset.
+			m.hdrSyncState.stallChanDrained = true
+
+			// Disconnect the sync peer due to stalling the header sync process.
+			if m.syncPeer != nil {
+				log.Debugf("Header sync progress stalled from peer %s -- "+
+					"disconnecting", m.syncPeer)
+				m.syncPeer.Disconnect()
+			}
+
 		case <-ctx.Done():
 			break out
 		}
@@ -1643,9 +1563,15 @@ func (m *SyncManager) RequestFromPeer(p *peerpkg.Peer, blocks, voteHashes,
 	tSpendHashes []*chainhash.Hash) error {
 
 	reply := make(chan requestFromPeerResponse, 1)
+	request := requestFromPeerMsg{
+		peer:         p,
+		blocks:       blocks,
+		voteHashes:   voteHashes,
+		tSpendHashes: tSpendHashes,
+		reply:        reply,
+	}
 	select {
-	case m.msgChan <- requestFromPeerMsg{peer: p, blocks: blocks,
-		voteHashes: voteHashes, tSpendHashes: tSpendHashes, reply: reply}:
+	case m.msgChan <- request:
 	case <-m.quit:
 	}
 
@@ -1677,7 +1603,7 @@ func (m *SyncManager) requestFromPeer(p *peerpkg.Peer, blocks, voteHashes,
 		}
 
 		// Skip the block when it is already known.
-		if m.isKnownOrphan(bh) || m.cfg.Chain.HaveBlock(bh) {
+		if m.cfg.Chain.HaveBlock(bh) {
 			continue
 		}
 
@@ -1778,7 +1704,7 @@ func (m *SyncManager) requestFromPeer(p *peerpkg.Peer, blocks, voteHashes,
 // ProcessBlock makes use of ProcessBlock on an internal instance of a block
 // chain.  It is funneled through the sync manager since blockchain is not safe
 // for concurrent access.
-func (m *SyncManager) ProcessBlock(block *dcrutil.Block, flags blockchain.BehaviorFlags) (bool, error) {
+func (m *SyncManager) ProcessBlock(block *dcrutil.Block, flags blockchain.BehaviorFlags) error {
 	reply := make(chan processBlockResponse, 1)
 	select {
 	case m.msgChan <- processBlockMsg{block: block, flags: flags, reply: reply}:
@@ -1787,9 +1713,9 @@ func (m *SyncManager) ProcessBlock(block *dcrutil.Block, flags blockchain.Behavi
 
 	select {
 	case response := <-reply:
-		return response.isOrphan, response.err
+		return response.err
 	case <-m.quit:
-		return false, fmt.Errorf("sync manager stopped")
+		return fmt.Errorf("sync manager stopped")
 	}
 }
 
@@ -1862,6 +1788,10 @@ type Config struct {
 	// transactions.
 	Chain *blockchain.BlockChain
 
+	// TimeSource defines the median time source which is used to retrieve the
+	// current time adjusted by the median time offset.
+	TimeSource blockchain.MedianTimeSource
+
 	// TxMemPool specifies the mempool to use for processing transactions.
 	TxMemPool *mempool.TxPool
 
@@ -1869,10 +1799,6 @@ type Config struct {
 	// It may return nil if there is no active RPC server.
 	RpcServer func() *rpcserver.Server
 
-	// DisableCheckpoints indicates whether or not the sync manager should make
-	// use of checkpoints.
-	DisableCheckpoints bool
-
 	// NoMiningStateSync indicates whether or not the sync manager should
 	// perform an initial mining state synchronization with peers once they are
 	// believed to be fully synced.
@@ -1896,35 +1822,17 @@ type Config struct {
 // New returns a new network chain synchronization manager.  Use Run to begin
 // processing asynchronous events.
 func New(config *Config) *SyncManager {
-	m := SyncManager{
+	return &SyncManager{
 		cfg:             *config,
 		rejectedTxns:    make(map[chainhash.Hash]struct{}),
 		requestedTxns:   make(map[chainhash.Hash]struct{}),
 		requestedBlocks: make(map[chainhash.Hash]struct{}),
 		peers:           make(map[*peerpkg.Peer]*syncMgrPeer),
+		hdrSyncState:    makeHeaderSyncState(),
 		progressLogger:  progresslog.New("Processed", log),
 		msgChan:         make(chan interface{}, config.MaxPeers*3),
-		headerList:      list.New(),
 		quit:            make(chan struct{}),
-		orphans:         make(map[chainhash.Hash]*orphanBlock),
-		prevOrphans:     make(map[chainhash.Hash][]*orphanBlock),
+		syncHeight:      config.Chain.BestSnapshot().Height,
 		isCurrent:       config.Chain.IsCurrent(),
 	}
-
-	best := m.cfg.Chain.BestSnapshot()
-	if !m.cfg.DisableCheckpoints {
-		// Initialize the next checkpoint based on the current height.
-		m.nextCheckpoint = m.findNextHeaderCheckpoint(best.Height)
-		if m.nextCheckpoint != nil {
-			m.resetHeaderState(&best.Hash, best.Height)
-		}
-	} else {
-		log.Info("Checkpoints are disabled")
-	}
-
-	m.syncHeightMtx.Lock()
-	m.syncHeight = best.Height
-	m.syncHeightMtx.Unlock()
-
-	return &m
 }
diff --git a/internal/progresslog/blocklogger.go b/internal/progresslog/blocklogger.go
deleted file mode 100644
index d0274880..00000000
--- a/internal/progresslog/blocklogger.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// 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 progresslog
-
-import (
-	"sync"
-	"time"
-
-	"github.com/decred/dcrd/wire"
-	"github.com/decred/slog"
-)
-
-// BlockLogger provides periodic logging for other services in order to show
-// users progress of certain "actions" involving some or all current blocks.
-// For example, syncing to best chain, indexing all blocks, etc.
-type BlockLogger struct {
-	sync.Mutex
-	subsystemLogger slog.Logger
-	progressAction  string
-
-	receivedLogBlocks      int64
-	receivedLogTx          int64
-	receivedLogVotes       int64
-	receivedLogRevocations int64
-	receivedLogTickets     int64
-	lastBlockLogTime       time.Time
-}
-
-// New returns a new block progress logger.
-//
-// The progress message is templated as follows:
-//  {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
-//  ({numTxs} {transactions|transaction}, {numTickets} {tickets|ticket},
-//  {numVotes} {votes|vote}, {numRevocations} {revocations|revocation},
-//  height {lastBlockHeight}, {lastBlockTimeStamp})
-func New(progressMessage string, logger slog.Logger) *BlockLogger {
-	return &BlockLogger{
-		lastBlockLogTime: time.Now(),
-		progressAction:   progressMessage,
-		subsystemLogger:  logger,
-	}
-}
-
-// LogBlockHeight logs a new block height as an information message to show
-// progress to the user.  In order to prevent spam, it limits logging to one
-// message every 10 seconds with duration and totals included.
-func (b *BlockLogger) LogBlockHeight(block *wire.MsgBlock, syncHeight int64) {
-	b.Lock()
-	defer b.Unlock()
-
-	header := &block.Header
-	b.receivedLogBlocks++
-	b.receivedLogTx += int64(len(block.Transactions))
-	b.receivedLogVotes += int64(header.Voters)
-	b.receivedLogRevocations += int64(header.Revocations)
-	b.receivedLogTickets += int64(header.FreshStake)
-	now := time.Now()
-	duration := now.Sub(b.lastBlockLogTime)
-	if int64(header.Height) < syncHeight && duration < time.Second*10 {
-		return
-	}
-
-	// Truncate the duration to 10s of milliseconds.
-	durationMillis := int64(duration / time.Millisecond)
-	tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
-
-	// Log information about new block height.
-	blockStr := "blocks"
-	if b.receivedLogBlocks == 1 {
-		blockStr = "block"
-	}
-	txStr := "transactions"
-	if b.receivedLogTx == 1 {
-		txStr = "transaction"
-	}
-	ticketStr := "tickets"
-	if b.receivedLogTickets == 1 {
-		ticketStr = "ticket"
-	}
-	revocationStr := "revocations"
-	if b.receivedLogRevocations == 1 {
-		revocationStr = "revocation"
-	}
-	voteStr := "votes"
-	if b.receivedLogVotes == 1 {
-		voteStr = "vote"
-	}
-	b.subsystemLogger.Infof("%s %d %s in the last %s (%d %s, %d %s, %d %s, "+
-		"%d %s, height %d, %s)", b.progressAction, b.receivedLogBlocks,
-		blockStr, tDuration, b.receivedLogTx, txStr, b.receivedLogTickets,
-		ticketStr, b.receivedLogVotes, voteStr, b.receivedLogRevocations,
-		revocationStr, header.Height, header.Timestamp)
-
-	b.receivedLogBlocks = 0
-	b.receivedLogTx = 0
-	b.receivedLogVotes = 0
-	b.receivedLogTickets = 0
-	b.receivedLogRevocations = 0
-	b.lastBlockLogTime = now
-}
-
-// SetLastLogTime updates the last time data was logged to the provided time.
-func (b *BlockLogger) SetLastLogTime(time time.Time) {
-	b.Lock()
-	b.lastBlockLogTime = time
-	b.Unlock()
-}
diff --git a/internal/progresslog/doc.go b/internal/progresslog/doc.go
index 0758a3b6..0a4a10d0 100644
--- a/internal/progresslog/doc.go
+++ b/internal/progresslog/doc.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 The Decred developers
+// Copyright (c) 2020-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -16,6 +16,6 @@ Tests are included to ensure proper functionality.
   - Total number of tickets
   - Total number of revocations
 - Logs all cumulative data every 10 seconds
-- Immediately logs any outstanding data when the provided sync height is reached
+- Immediately logs any outstanding data when requested by the caller
 */
 package progresslog
diff --git a/internal/progresslog/logger.go b/internal/progresslog/logger.go
new file mode 100644
index 00000000..e3017824
--- /dev/null
+++ b/internal/progresslog/logger.go
@@ -0,0 +1,135 @@
+// Copyright (c) 2015-2021 The Decred developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package progresslog
+
+import (
+	"sync"
+	"time"
+
+	"github.com/decred/dcrd/wire"
+	"github.com/decred/slog"
+)
+
+// pickNoun returns the singular or plural form of a noun depending on the
+// provided count.
+func pickNoun(n uint64, singular, plural string) string {
+	if n == 1 {
+		return singular
+	}
+	return plural
+}
+
+// Logger provides periodic logging of progress towards some action such as
+// syncing the chain.
+type Logger struct {
+	sync.Mutex
+	subsystemLogger slog.Logger
+	progressAction  string
+
+	// lastLogTime tracks the last time a log statement was shown.
+	lastLogTime time.Time
+
+	// These fields accumulate information about blocks between log statements.
+	receivedBlocks  uint64
+	receivedTxns    uint64
+	receivedVotes   uint64
+	receivedRevokes uint64
+	receivedTickets uint64
+
+	// These fields accumulate information about headers between log statements.
+	receivedHeaders uint64
+}
+
+// New returns a new block progress logger.
+func New(progressAction string, logger slog.Logger) *Logger {
+	return &Logger{
+		lastLogTime:     time.Now(),
+		progressAction:  progressAction,
+		subsystemLogger: logger,
+	}
+}
+
+// LogProgress accumulates details for the provided block and periodically
+// (every 10 seconds) logs an information message to show progress to the user
+// along with duration and totals included.
+//
+// The force flag may be used to force a log message to be shown regardless of
+// the time the last one was shown.
+//
+// The progress message is templated as follows:
+//  {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
+//  ({numTxs} {transactions|transaction}, {numTickets} {tickets|ticket},
+//  {numVotes} {votes|vote}, {numRevocations} {revocations|revocation},
+//  height {lastBlockHeight}, progress {progress}%)
+func (l *Logger) LogProgress(block *wire.MsgBlock, forceLog bool, progressFn func() float64) {
+	l.Lock()
+	defer l.Unlock()
+
+	header := &block.Header
+	l.receivedBlocks++
+	l.receivedTxns += uint64(len(block.Transactions))
+	l.receivedVotes += uint64(header.Voters)
+	l.receivedRevokes += uint64(header.Revocations)
+	l.receivedTickets += uint64(header.FreshStake)
+	now := time.Now()
+	duration := now.Sub(l.lastLogTime)
+	if !forceLog && duration < time.Second*10 {
+		return
+	}
+
+	// Log information about chain progress.
+	l.subsystemLogger.Infof("%s %d %s in the last %0.2fs (%d %s, %d %s, %d %s, "+
+		"%d %s, height %d, progress %0.2f%%)", l.progressAction,
+		l.receivedBlocks, pickNoun(l.receivedBlocks, "block", "blocks"),
+		duration.Seconds(),
+		l.receivedTxns, pickNoun(l.receivedTxns, "transaction", "transactions"),
+		l.receivedTickets, pickNoun(l.receivedTickets, "ticket", "tickets"),
+		l.receivedVotes, pickNoun(l.receivedVotes, "vote", "votes"),
+		l.receivedRevokes, pickNoun(l.receivedRevokes, "revocation", "revocations"),
+		header.Height, progressFn())
+
+	l.receivedBlocks = 0
+	l.receivedTxns = 0
+	l.receivedVotes = 0
+	l.receivedTickets = 0
+	l.receivedRevokes = 0
+	l.lastLogTime = now
+}
+
+// LogHeaderProgress accumulates the provided number of processed headers and
+// periodically (every 10 seconds) logs an information message to show the
+// header sync progress to the user along with duration and totals included.
+//
+// The force flag may be used to force a log message to be shown regardless of
+// the time the last one was shown.
+//
+// The progress message is templated as follows:
+//  {progressAction} {numProcessed} {headers|header} in the last {timePeriod}
+//  (progress {progress}%)
+func (l *Logger) LogHeaderProgress(processedHeaders uint64, forceLog bool, progressFn func() float64) {
+	l.receivedHeaders += processedHeaders
+
+	now := time.Now()
+	duration := now.Sub(l.lastLogTime)
+	if !forceLog && duration < time.Second*10 {
+		return
+	}
+
+	// Log information about header progress.
+	l.subsystemLogger.Infof("%s %d %s in the last %0.2fs (progress %0.2f%%)",
+		l.progressAction, l.receivedHeaders,
+		pickNoun(l.receivedHeaders, "header", "headers"),
+		duration.Seconds(), progressFn())
+
+	l.receivedHeaders = 0
+	l.lastLogTime = now
+}
+
+// SetLastLogTime updates the last time data was logged to the provided time.
+func (l *Logger) SetLastLogTime(time time.Time) {
+	l.Lock()
+	l.lastLogTime = time
+	l.Unlock()
+}
diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go
index dc3ebc24..cc952802 100644
--- a/internal/rpcserver/interface.go
+++ b/internal/rpcserver/interface.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 The Decred developers
+// Copyright (c) 2019-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -148,7 +148,7 @@ type SyncManager interface {
 
 	// SubmitBlock submits the provided block to the network after
 	// processing it locally.
-	SubmitBlock(block *dcrutil.Block, flags blockchain.BehaviorFlags) (bool, error)
+	SubmitBlock(block *dcrutil.Block) error
 
 	// SyncPeerID returns the id of the current peer being synced with.
 	SyncPeerID() int32
@@ -578,8 +578,8 @@ type TxMempooler interface {
 	Count() int
 
 	// FetchTransaction returns the requested transaction from the
-	// transaction pool. This only fetches from the main transaction pool
-	// and does not include orphans.
+	// transaction pool. This only fetches from the main and stage transaction
+	// pools and does not include orphans.
 	FetchTransaction(txHash *chainhash.Hash) (*dcrutil.Tx, error)
 
 	// TSpendHashes returns the hashes of the treasury spend transactions
diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go
index ec49a955..292e8dfc 100644
--- a/internal/rpcserver/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2013-2016 The btcsuite developers
-// Copyright (c) 2015-2020 The Decred developers
+// Copyright (c) 2015-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -2035,10 +2035,10 @@ func handleGetBlockchainInfo(_ context.Context, s *Server, cmd interface{}) (int
 	}
 
 	// Estimate the verification progress of the node.
-	syncHeight := s.cfg.SyncMgr.SyncHeight()
 	var verifyProgress float64
-	if syncHeight > 0 {
-		verifyProgress = math.Min(float64(best.Height)/float64(syncHeight), 1.0)
+	if bestHeaderHeight > 0 {
+		progress := float64(best.Height) / float64(bestHeaderHeight)
+		verifyProgress = math.Min(progress, 1.0)
 	}
 
 	// Fetch the maximum allowed block size for all blocks other than the
@@ -2101,7 +2101,7 @@ func handleGetBlockchainInfo(_ context.Context, s *Server, cmd interface{}) (int
 		Chain:                params.Name,
 		Blocks:               best.Height,
 		Headers:              bestHeaderHeight,
-		SyncHeight:           syncHeight,
+		SyncHeight:           s.cfg.SyncMgr.SyncHeight(),
 		ChainWork:            fmt.Sprintf("%064x", chainWork),
 		InitialBlockDownload: !chain.IsCurrent(),
 		VerificationProgress: verifyProgress,
@@ -3210,11 +3210,11 @@ func handleGetTreasurySpendVotes(_ context.Context, s *Server, cmd interface{})
 				// Given this tspend was mined in the main
 				// chain, it doesn't make sense to count votes
 				// after it was mined. So stop early if the
-				// target block height is higher than the
-				// tspend's mined height. We need to count
-				// votes only up to the block _before_ the
-				// tspend was mined.
-				if blockHeight > int64(hdr.Height) {
+				// target block height is greater than or equal
+				// to the tspend's mined height. We need to
+				// count votes only up to the block _before_
+				// the tspend was mined.
+				if blockHeight >= int64(hdr.Height) {
 					endBlocks[hash] = hdr.PrevBlock
 				}
 
@@ -3783,8 +3783,14 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte
 
 	// Process this block using the same rules as blocks coming from other
 	// nodes.  This will in turn relay it to the network like normal.
-	isOrphan, err := s.cfg.SyncMgr.SubmitBlock(block, blockchain.BFNone)
+	err = s.cfg.SyncMgr.SubmitBlock(block)
 	if err != nil {
+		if errors.Is(err, blockchain.ErrMissingParent) {
+			log.Infof("Block submitted via getwork rejected: orphan building "+
+				"on parent %v", block.MsgBlock().Header.PrevBlock)
+			return false, nil
+		}
+
 		// Anything other than a rule violation is an unexpected error,
 		// so return that error as an internal error.
 		var rErr blockchain.RuleError
@@ -3797,12 +3803,6 @@ func handleGetWorkSubmission(_ context.Context, s *Server, hexData string) (inte
 		return false, nil
 	}
 
-	if isOrphan {
-		log.Infof("Block submitted via getwork rejected: an orphan "+
-			"building on parent %v", block.MsgBlock().Header.PrevBlock)
-		return false, nil
-	}
-
 	// The block was accepted.
 	log.Infof("Block submitted via getwork accepted: %s (height %d)",
 		block.Hash(), msgBlock.Header.Height)
@@ -4745,7 +4745,7 @@ func handleSubmitBlock(_ context.Context, s *Server, cmd interface{}) (interface
 		return nil, rpcInternalError(err.Error(), "Block decode")
 	}
 
-	_, err = s.cfg.SyncMgr.SubmitBlock(block, blockchain.BFNone)
+	err = s.cfg.SyncMgr.SubmitBlock(block)
 	if err != nil {
 		return fmt.Sprintf("rejected: %v", err), nil
 	}
diff --git a/lru/README.md b/lru/README.md
index 3360f76d..65517624 100644
--- a/lru/README.md
+++ b/lru/README.md
@@ -25,9 +25,8 @@ lookups, inserts, and deletions.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/lru
-```
+This package is part of the `github.com/decred/dcrd/lru` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/peer/README.md b/peer/README.md
index 77181519..82631da5 100644
--- a/peer/README.md
+++ b/peer/README.md
@@ -53,9 +53,8 @@ A quick overview of the major features peer provides are as follows:
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/peer
-```
+This package is part of the `github.com/decred/dcrd/peer/v2` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/peer/log.go b/peer/log.go
index 30feeb35..6c7233b7 100644
--- a/peer/log.go
+++ b/peer/log.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2015-2016 The btcsuite developers
-// Copyright (c) 2016-2020 The Decred developers
+// Copyright (c) 2016-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -188,7 +188,13 @@ func messageSummary(msg wire.Message) string {
 		return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop)
 
 	case *wire.MsgHeaders:
-		return fmt.Sprintf("num %d", len(msg.Headers))
+		summary := fmt.Sprintf("num %d", len(msg.Headers))
+		if len(msg.Headers) > 0 {
+			finalHeader := msg.Headers[len(msg.Headers)-1]
+			summary = fmt.Sprintf("%s, final hash %s, height %d", summary,
+				finalHeader.BlockHash(), finalHeader.Height)
+		}
+		return summary
 
 	case *wire.MsgReject:
 		// Ensure the variable length strings don't contain any
diff --git a/peer/peer.go b/peer/peer.go
index d7f2cd77..5caa65be 100644
--- a/peer/peer.go
+++ b/peer/peer.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2013-2016 The btcsuite developers
-// Copyright (c) 2016-2020 The Decred developers
+// Copyright (c) 2016-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -1090,40 +1090,45 @@ func (p *Peer) maybeAddDeadline(pendingResponses map[string]time.Time, msgCmd st
 	// sent asynchronously and as a result of a long backlog of messages,
 	// such as is typical in the case of initial block download, the
 	// response won't be received in time.
-	log.Debugf("Adding deadline for command %s for peer %s", msgCmd, p.addr)
-
+	//
+	// Also, getheaders is intentionally ignored since there is no guaranteed
+	// response if the remote peer does not have any headers for the locator.
+	var addedDeadline bool
 	deadline := time.Now().Add(stallResponseTimeout)
 	switch msgCmd {
 	case wire.CmdVersion:
 		// Expects a verack message.
 		pendingResponses[wire.CmdVerAck] = deadline
+		addedDeadline = true
 
 	case wire.CmdMemPool:
 		// Expects an inv message.
 		pendingResponses[wire.CmdInv] = deadline
+		addedDeadline = true
 
 	case wire.CmdGetBlocks:
 		// Expects an inv message.
 		pendingResponses[wire.CmdInv] = deadline
+		addedDeadline = true
 
 	case wire.CmdGetData:
 		// Expects a block, tx, or notfound message.
 		pendingResponses[wire.CmdBlock] = deadline
 		pendingResponses[wire.CmdTx] = deadline
 		pendingResponses[wire.CmdNotFound] = deadline
-
-	case wire.CmdGetHeaders:
-		// Expects a headers message.  Use a longer deadline since it
-		// can take a while for the remote peer to load all of the
-		// headers.
-		deadline = time.Now().Add(stallResponseTimeout * 3)
-		pendingResponses[wire.CmdHeaders] = deadline
+		addedDeadline = true
 
 	case wire.CmdGetMiningState:
 		pendingResponses[wire.CmdMiningState] = deadline
+		addedDeadline = true
 
 	case wire.CmdGetInitState:
 		pendingResponses[wire.CmdInitState] = deadline
+		addedDeadline = true
+	}
+
+	if addedDeadline {
+		log.Debugf("Adding deadline for command %s for peer %s", msgCmd, p.addr)
 	}
 }
 
diff --git a/rpc/jsonrpc/types/README.md b/rpc/jsonrpc/types/README.md
index a434c363..68dce113 100644
--- a/rpc/jsonrpc/types/README.md
+++ b/rpc/jsonrpc/types/README.md
@@ -17,9 +17,8 @@ projects needing to marshal to and from dcrd JSON-RPC requests and responses.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/rpc/jsonrpc/types
-```
+This package is part of the `github.com/decred/dcrd/rpc/jsonrpc/types/v2`
+module.  Use the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/rpcadaptors.go b/rpcadaptors.go
index 3f89c618..b7f59c24 100644
--- a/rpcadaptors.go
+++ b/rpcadaptors.go
@@ -1,5 +1,5 @@
 // Copyright (c) 2017 The btcsuite developers
-// Copyright (c) 2015-2020 The Decred developers
+// Copyright (c) 2015-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -330,8 +330,8 @@ func (b *rpcSyncMgr) IsCurrent() bool {
 //
 // This function is safe for concurrent access and is part of the
 // rpcserver.SyncManager interface implementation.
-func (b *rpcSyncMgr) SubmitBlock(block *dcrutil.Block, flags blockchain.BehaviorFlags) (bool, error) {
-	return b.syncMgr.ProcessBlock(block, flags)
+func (b *rpcSyncMgr) SubmitBlock(block *dcrutil.Block) error {
+	return b.syncMgr.ProcessBlock(block, blockchain.BFNone)
 }
 
 // SyncPeer returns the id of the current peer being synced with.
diff --git a/rpcclient/README.md b/rpcclient/README.md
index 284856c9..83afdd97 100644
--- a/rpcclient/README.md
+++ b/rpcclient/README.md
@@ -36,11 +36,10 @@ implement and the API is not stable yet.
   * Registered notifications are automatically reregistered
   * Back-off support on reconnect attempts
 
-## Installation
+## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/rpcclient
-```
+This package is part of the `github.com/decred/dcrd/rpcclient/v6` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/rpcclient/examples/dcrdwebsockets/README.md b/rpcclient/examples/dcrdwebsockets/README.md
index 16d50796..b485f255 100644
--- a/rpcclient/examples/dcrdwebsockets/README.md
+++ b/rpcclient/examples/dcrdwebsockets/README.md
@@ -10,24 +10,17 @@ demonstrate clean shutdown.
 
 ## Running the Example
 
-The first step is to use `go get` to download and install the rpcclient package:
-
-```bash
-$ go get -u github.com/decred/dcrd/rpcclient
-```
-
-Next, modify the `main.go` source to specify the correct RPC username and
-password for the RPC server:
+After obtaining the source code, modify the `main.go` source to specify the
+correct RPC username and password for the RPC server:
 
 ```Go
 	User: "yourrpcuser",
 	Pass: "yourrpcpass",
 ```
 
-Finally, navigate to the example's directory and run it with:
+Then, navigate to the example's directory and run it with:
 
 ```bash
-$ cd $GOPATH/src/github.com/decred/dcrd/rpcclient/examples/dcrdwebsockets
 $ go run *.go
 ```
 
diff --git a/sampleconfig/README.md b/sampleconfig/README.md
index 95bf47a0..39b499eb 100644
--- a/sampleconfig/README.md
+++ b/sampleconfig/README.md
@@ -13,9 +13,8 @@ samples of other configuration options.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/sampleconfig
-```
+This package is part of the `github.com/decred/dcrd` module.  Use the standard
+go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/server.go b/server.go
index 6db83815..80267a64 100644
--- a/server.go
+++ b/server.go
@@ -673,7 +673,7 @@ func (sp *serverPeer) OnVersion(p *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej
 
 	// Ignore peers that have a protocol version that is too old.  The peer
 	// negotiation logic will disconnect it after this callback returns.
-	if msg.ProtocolVersion < int32(wire.InitialProcotolVersion) {
+	if msg.ProtocolVersion < int32(wire.SendHeadersVersion) {
 		return nil
 	}
 
@@ -737,6 +737,13 @@ func (sp *serverPeer) OnVersion(p *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej
 	return nil
 }
 
+// OnVerAck is invoked when a peer receives a verack wire message.  It creates
+// and sends a sendheaders message to request all block annoucements are made
+// via full headers instead of the inv message.
+func (sp *serverPeer) OnVerAck(_ *peer.Peer, msg *wire.MsgVerAck) {
+	sp.QueueMessage(wire.NewMsgSendHeaders(), nil)
+}
+
 // OnMemPool is invoked when a peer receives a mempool wire message.  It creates
 // and sends an inventory message with the contents of the memory pool up to the
 // maximum inventory allowed per message.
@@ -1067,12 +1074,6 @@ func (sp *serverPeer) OnInv(p *peer.Peer, msg *wire.MsgInv) {
 // OnHeaders is invoked when a peer receives a headers wire message.  The
 // message is passed down to the net sync manager.
 func (sp *serverPeer) OnHeaders(_ *peer.Peer, msg *wire.MsgHeaders) {
-	// Ban peers sending empty headers requests.
-	if len(msg.Headers) == 0 {
-		sp.server.BanPeer(sp)
-		return
-	}
-
 	sp.server.syncManager.QueueHeaders(msg, sp.Peer)
 }
 
@@ -2068,6 +2069,7 @@ func newPeerConfig(sp *serverPeer) *peer.Config {
 	return &peer.Config{
 		Listeners: peer.MessageListeners{
 			OnVersion:        sp.OnVersion,
+			OnVerAck:         sp.OnVerAck,
 			OnMemPool:        sp.OnMemPool,
 			OnGetMiningState: sp.OnGetMiningState,
 			OnMiningState:    sp.OnMiningState,
@@ -2271,8 +2273,6 @@ func (s *server) RelayInventory(invVect *wire.InvVect, data interface{}, immedia
 // BroadcastMessage sends msg to all peers currently connected to the server
 // except those in the passed peers to exclude.
 func (s *server) BroadcastMessage(msg wire.Message, exclPeers ...*serverPeer) {
-	// XXX: Need to determine if this is an alert that has already been
-	// broadcast and refrain from broadcasting again.
 	bmsg := broadcastMsg{message: msg, excludePeers: exclPeers}
 	s.broadcast <- bmsg
 }
@@ -2280,9 +2280,7 @@ func (s *server) BroadcastMessage(msg wire.Message, exclPeers ...*serverPeer) {
 // ConnectedCount returns the number of currently connected peers.
 func (s *server) ConnectedCount() int32 {
 	replyChan := make(chan int32)
-
 	s.query <- getConnCountMsg{reply: replyChan}
-
 	return <-replyChan
 }
 
@@ -2302,98 +2300,6 @@ func (s *server) AddedNodeInfo() []*serverPeer {
 	return <-replyChan
 }
 
-// Peers returns an array of all connected peers.
-func (s *server) Peers() []*serverPeer {
-	replyChan := make(chan []*serverPeer)
-
-	s.query <- getPeersMsg{reply: replyChan}
-
-	return <-replyChan
-}
-
-// DisconnectNodeByAddr disconnects a peer by target address. Both outbound and
-// inbound nodes will be searched for the target node. An error message will
-// be returned if the peer was not found.
-func (s *server) DisconnectNodeByAddr(addr string) error {
-	replyChan := make(chan error)
-
-	s.query <- disconnectNodeMsg{
-		cmp:   func(sp *serverPeer) bool { return sp.Addr() == addr },
-		reply: replyChan,
-	}
-
-	return <-replyChan
-}
-
-// DisconnectNodeByID disconnects a peer by target node id. Both outbound and
-// inbound nodes will be searched for the target node. An error message will be
-// returned if the peer was not found.
-func (s *server) DisconnectNodeByID(id int32) error {
-	replyChan := make(chan error)
-
-	s.query <- disconnectNodeMsg{
-		cmp:   func(sp *serverPeer) bool { return sp.ID() == id },
-		reply: replyChan,
-	}
-
-	return <-replyChan
-}
-
-// RemoveNodeByAddr removes a peer from the list of persistent peers if
-// present. An error will be returned if the peer was not found.
-func (s *server) RemoveNodeByAddr(addr string) error {
-	replyChan := make(chan error)
-
-	s.query <- removeNodeMsg{
-		cmp:   func(sp *serverPeer) bool { return sp.Addr() == addr },
-		reply: replyChan,
-	}
-
-	err := <-replyChan
-	if err != nil {
-		// This connection may still be pending, cancel it.
-		return s.cancelPendingConnection(addr)
-	}
-	return nil
-}
-
-// cancelPendingConnection removes an address from the list of
-// pending connections.
-func (s *server) cancelPendingConnection(addr string) error {
-	replyChan := make(chan error)
-
-	s.query <- cancelPendingMsg{
-		addr:  addr,
-		reply: replyChan,
-	}
-
-	return <-replyChan
-}
-
-// RemoveNodeByID removes a peer by node ID from the list of persistent peers
-// if present. An error will be returned if the peer was not found.
-func (s *server) RemoveNodeByID(id int32) error {
-	replyChan := make(chan error)
-
-	s.query <- removeNodeMsg{
-		cmp:   func(sp *serverPeer) bool { return sp.ID() == id },
-		reply: replyChan,
-	}
-
-	return <-replyChan
-}
-
-// ConnectNode adds `addr' as a new outbound peer. If permanent is true then the
-// peer will be persistent and reconnect if the connection is lost.
-// It is an error to call this with an already existing peer.
-func (s *server) ConnectNode(addr string, permanent bool) error {
-	replyChan := make(chan error)
-
-	s.query <- connectNodeMsg{addr: addr, permanent: permanent, reply: replyChan}
-
-	return <-replyChan
-}
-
 // AddBytesSent adds the passed number of bytes to the total bytes sent counter
 // for the server.  It is safe for concurrent access.
 func (s *server) AddBytesSent(bytesSent uint64) {
@@ -3508,15 +3414,20 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 		},
 	}
 	s.txMemPool = mempool.New(&txC)
+
+	// Create a new sync manager instance with the appropriate configuration.
+	if cfg.DisableCheckpoints {
+		srvrLog.Info("Checkpoints are disabled")
+	}
 	s.syncManager = netsync.New(&netsync.Config{
 		PeerNotifier: &s,
 		Chain:        s.chain,
 		ChainParams:  s.chainParams,
+		TimeSource:   s.timeSource,
 		TxMemPool:    s.txMemPool,
 		RpcServer: func() *rpcserver.Server {
 			return s.rpcServer
 		},
-		DisableCheckpoints:    cfg.DisableCheckpoints,
 		NoMiningStateSync:     cfg.NoMiningStateSync,
 		MaxPeers:              cfg.MaxPeers,
 		MaxOrphanTxs:          cfg.MaxOrphanTxs,
diff --git a/txscript/README.md b/txscript/README.md
index f9c28b5d..cfe45245 100644
--- a/txscript/README.md
+++ b/txscript/README.md
@@ -13,15 +13,14 @@ package for any projects needing to use or validate Decred transaction scripts.
 
 ## Decred Scripts
 
-Decred provides a stack-based, FORTH-like language for the scripts in
-the Decred transactions.  This language is not turing complete
-although it is still fairly powerful.
+Decred provides a stack-based, FORTH-like language for the scripts in the Decred
+transactions.  This language is not Turing complete although it is still fairly
+powerful.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/txscript
-```
+This package is part of the `github.com/decred/dcrd/txscript/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## Examples
 
diff --git a/upnp.go b/upnp.go
index 03d284b8..a1291e48 100644
--- a/upnp.go
+++ b/upnp.go
@@ -40,7 +40,6 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"os"
 	"strconv"
 	"strings"
 	"time"
@@ -200,11 +199,14 @@ func getChildService(d *device, serviceType string) *service {
 
 // getOurIP returns a best guess at what the local IP is.
 func getOurIP() (ip string, err error) {
-	hostname, err := os.Hostname()
-	if err != nil {
-		return
+	addrs, err := net.InterfaceAddrs()
+	for _, addr := range addrs {
+		ip, _, _ := net.ParseCIDR(addr.String())
+		if !ip.IsLoopback() {
+			return ip.String(), nil
+		}
 	}
-	return net.LookupCNAME(hostname)
+	return
 }
 
 // getServiceURL parses the xml description at the given root url to find the
diff --git a/wire/README.md b/wire/README.md
index c3851d20..a3d70e91 100644
--- a/wire/README.md
+++ b/wire/README.md
@@ -14,9 +14,8 @@ protocol level.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/wire
-```
+This package is part of the `github.com/decred/dcrd/wire` module.  Use the
+standard go tooling for working with modules to incorporate it.
 
 ## Decred Message Overview
 


Test patch:
diff --git a/blockchain/fullblocktests/README.md b/blockchain/fullblocktests/README.md
index 5a0419b0..18d30cb1 100644
--- a/blockchain/fullblocktests/README.md
+++ b/blockchain/fullblocktests/README.md
@@ -19,9 +19,8 @@ of blocks that exercise the consensus validation rules.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/blockchain/fullblocktests
-```
+This package is part of the `github.com/decred/dcrd/blockchain/v3` module.  Use
+the standard go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/chaincfg/testnetparams.go b/chaincfg/testnetparams.go
index e49f2159..517c89a5 100644
--- a/chaincfg/testnetparams.go
+++ b/chaincfg/testnetparams.go
@@ -308,7 +308,7 @@ func TestNet3Params() *Params {
 			"testnet-seed-2.decred.org",
 			"testnet-seed.planetdecred.org",
 			"testnet-seed.dcrdata.org",
-			"testnet-seed.jamieholdstock.com",
+			"testnet-seed.jholdstock.uk",
 		},
 	}
 }
diff --git a/internal/mempool/mempool_test.go b/internal/mempool/mempool_test.go
index a1ef4ace..aed020e8 100644
--- a/internal/mempool/mempool_test.go
+++ b/internal/mempool/mempool_test.go
@@ -2686,3 +2686,70 @@ func (p *poolHarness) countTotalSigOps(tx *dcrutil.Tx, txType stake.TxType) (int
 
 	return sigOps + p2shSigOps, nil
 }
+
+// TestStagedTransactionHeight verifies that the height of a transaction
+// that moves from the stage pool into the main pool is set to the height it
+// was initially added to the mempool, rather than the height it was unstaged.
+func TestStagedTransactionHeight(t *testing.T) {
+	harness, spendableOuts, err := newPoolHarness(chaincfg.MainNetParams())
+	if err != nil {
+		t.Fatalf("unable to create test pool: %v", err)
+	}
+
+	txA, _ := harness.CreateSignedTx([]spendableOutput{
+		spendableOuts[0],
+	}, 1)
+
+	ticket, err := harness.CreateTicketPurchase(txA, 40000)
+	if err != nil {
+		t.Fatalf("unable to create ticket purchase transaction %v", err)
+	}
+
+	allTxns := []*dcrutil.Tx{txA, ticket}
+	for index, tx := range allTxns {
+		_, err := harness.txPool.ProcessTransaction(tx,
+			true, false, true, 0)
+		if err != nil {
+			t.Fatalf("ProcessTransaction: failed to accept valid "+
+				"transaction at index %d: %v", index, err)
+		}
+	}
+
+	initialBlockHeight := harness.chain.BestHeight()
+	poolTxDescs := harness.txPool.TxDescs()
+	if len(poolTxDescs) != 1 {
+		t.Fatalf("expected to find exactly one transaction in the mempool but "+
+			"got %v", len(poolTxDescs))
+	}
+
+	poolTxA := poolTxDescs[0]
+	if poolTxA.Height != initialBlockHeight {
+		t.Fatalf("expected txA mempool height to be %v but got %v",
+			initialBlockHeight, poolTxA.Height)
+	}
+
+	// Remove txA, which should bring the ticket out of the stage pool and
+	// into the main pool.
+	newBlockHeight := initialBlockHeight + 1
+	harness.AddFakeUTXO(txA, newBlockHeight)
+	harness.chain.SetHeight(newBlockHeight)
+	harness.txPool.RemoveTransaction(txA, false, noTreasury)
+	harness.txPool.MaybeAcceptDependents(txA, noTreasury)
+
+	poolTxDescs = harness.txPool.TxDescs()
+	if len(poolTxDescs) != 1 {
+		t.Fatalf("expected to find exactly one transaction in the mempool but "+
+			"got %v", len(poolTxDescs))
+	}
+
+	poolTransaction := poolTxDescs[0]
+	if *poolTransaction.Tx.Hash() != *ticket.Hash() {
+		t.Fatalf("expected to find ticket %v in the mempool but got %v",
+			ticket.Hash(), poolTransaction.Tx.Hash())
+	}
+
+	if poolTransaction.Height != initialBlockHeight {
+		t.Fatalf("expected ticket mempool height to be %v but got %v",
+			initialBlockHeight, poolTransaction.Height)
+	}
+}
diff --git a/internal/progresslog/blocklogger_test.go b/internal/progresslog/blocklogger_test.go
deleted file mode 100644
index e6401342..00000000
--- a/internal/progresslog/blocklogger_test.go
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2020 The Decred developers
-// Use of this source code is governed by an ISC
-// license that can be found in the LICENSE file.
-
-package progresslog
-
-import (
-	"io/ioutil"
-	"reflect"
-	"testing"
-	"time"
-
-	"github.com/decred/dcrd/wire"
-	"github.com/decred/slog"
-)
-
-var (
-	backendLog = slog.NewBackend(ioutil.Discard)
-	testLog    = backendLog.Logger("TEST")
-)
-
-// TestLogBlockHeight ensures the logging functionality works as expected via
-// a test logger.
-func TestLogBlockHeight(t *testing.T) {
-	testBlocks := []wire.MsgBlock{{
-		Header: wire.BlockHeader{
-			Version:     1,
-			Height:      100000,
-			Timestamp:   time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC
-			Voters:      2,
-			Revocations: 1,
-			FreshStake:  4,
-		},
-		Transactions:  make([]*wire.MsgTx, 4),
-		STransactions: nil,
-	}, {
-		Header: wire.BlockHeader{
-			Version:     1,
-			Height:      100001,
-			Timestamp:   time.Unix(1293624163, 0), // 2010-12-29 12:02:43 +0000 UTC
-			Voters:      3,
-			Revocations: 2,
-			FreshStake:  2,
-		},
-		Transactions:  make([]*wire.MsgTx, 2),
-		STransactions: nil,
-	}, {
-		Header: wire.BlockHeader{
-			Version:     1,
-			Height:      100002,
-			Timestamp:   time.Unix(1293624463, 0), // 2010-12-29 12:07:43 +0000 UTC
-			Voters:      1,
-			Revocations: 3,
-			FreshStake:  1,
-		},
-		Transactions:  make([]*wire.MsgTx, 3),
-		STransactions: nil,
-	}}
-
-	tests := []struct {
-		name                       string
-		reset                      bool
-		inputBlock                 *wire.MsgBlock
-		inputSyncHeight            int64
-		inputLastLogTime           time.Time
-		wantReceivedLogBlocks      int64
-		wantReceivedLogTx          int64
-		wantReceivedLogVotes       int64
-		wantReceivedLogRevocations int64
-		wantReceivedLogTickets     int64
-	}{{
-		name:                       "round 1, block 0, last log time < 10 secs ago, < sync height",
-		inputBlock:                 &testBlocks[0],
-		inputSyncHeight:            100002,
-		inputLastLogTime:           time.Now(),
-		wantReceivedLogBlocks:      1,
-		wantReceivedLogTx:          4,
-		wantReceivedLogVotes:       2,
-		wantReceivedLogRevocations: 1,
-		wantReceivedLogTickets:     4,
-	}, {
-		name:                       "round 1, block 1, last log time < 10 secs ago, < sync height",
-		inputBlock:                 &testBlocks[1],
-		inputSyncHeight:            100002,
-		inputLastLogTime:           time.Now(),
-		wantReceivedLogBlocks:      2,
-		wantReceivedLogTx:          6,
-		wantReceivedLogVotes:       5,
-		wantReceivedLogRevocations: 3,
-		wantReceivedLogTickets:     6,
-	}, {
-		name:                       "round 1, block 2, last log time < 10 secs ago, < sync height",
-		inputBlock:                 &testBlocks[2],
-		inputSyncHeight:            100002,
-		inputLastLogTime:           time.Now(),
-		wantReceivedLogBlocks:      0,
-		wantReceivedLogTx:          0,
-		wantReceivedLogVotes:       0,
-		wantReceivedLogRevocations: 0,
-		wantReceivedLogTickets:     0,
-	}, {
-		name:                       "round 2, block 0, last log time < 10 secs ago, < sync height",
-		reset:                      true,
-		inputBlock:                 &testBlocks[0],
-		inputSyncHeight:            100002,
-		inputLastLogTime:           time.Now(),
-		wantReceivedLogBlocks:      1,
-		wantReceivedLogTx:          4,
-		wantReceivedLogVotes:       2,
-		wantReceivedLogRevocations: 1,
-		wantReceivedLogTickets:     4,
-	}, {
-		name:                       "round 2, block 1, last log time > 10 secs ago, < sync height",
-		inputBlock:                 &testBlocks[1],
-		inputSyncHeight:            100002,
-		inputLastLogTime:           time.Now().Add(-11 * time.Second),
-		wantReceivedLogBlocks:      0,
-		wantReceivedLogTx:          0,
-		wantReceivedLogVotes:       0,
-		wantReceivedLogRevocations: 0,
-		wantReceivedLogTickets:     0,
-	}, {
-		name:                       "round 2, block 2, last log time > 10 secs ago, == sync height",
-		inputBlock:                 &testBlocks[2],
-		inputSyncHeight:            100002,
-		inputLastLogTime:           time.Now().Add(-11 * time.Second),
-		wantReceivedLogBlocks:      0,
-		wantReceivedLogTx:          0,
-		wantReceivedLogVotes:       0,
-		wantReceivedLogRevocations: 0,
-		wantReceivedLogTickets:     0,
-	}}
-
-	progressLogger := New("Wrote", testLog)
-	for _, test := range tests {
-		if test.reset {
-			progressLogger = New("Wrote", testLog)
-		}
-		progressLogger.SetLastLogTime(test.inputLastLogTime)
-		progressLogger.LogBlockHeight(test.inputBlock, test.inputSyncHeight)
-		wantBlockProgressLogger := &BlockLogger{
-			receivedLogBlocks:      test.wantReceivedLogBlocks,
-			receivedLogTx:          test.wantReceivedLogTx,
-			receivedLogVotes:       test.wantReceivedLogVotes,
-			receivedLogRevocations: test.wantReceivedLogRevocations,
-			receivedLogTickets:     test.wantReceivedLogTickets,
-			lastBlockLogTime:       progressLogger.lastBlockLogTime,
-			progressAction:         progressLogger.progressAction,
-			subsystemLogger:        progressLogger.subsystemLogger,
-		}
-		if !reflect.DeepEqual(progressLogger, wantBlockProgressLogger) {
-			t.Errorf("%s:\nwant: %+v\ngot: %+v\n", test.name,
-				wantBlockProgressLogger, progressLogger)
-		}
-	}
-}
diff --git a/internal/progresslog/logger_test.go b/internal/progresslog/logger_test.go
new file mode 100644
index 00000000..37add29e
--- /dev/null
+++ b/internal/progresslog/logger_test.go
@@ -0,0 +1,228 @@
+// Copyright (c) 2021 The Decred developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package progresslog
+
+import (
+	"io/ioutil"
+	"reflect"
+	"testing"
+	"time"
+
+	"github.com/decred/dcrd/wire"
+	"github.com/decred/slog"
+)
+
+var (
+	backendLog = slog.NewBackend(ioutil.Discard)
+	testLog    = backendLog.Logger("TEST")
+)
+
+// TestLogProgress ensures the logging functionality works as expected via a
+// test logger.
+func TestLogProgress(t *testing.T) {
+	testBlocks := []wire.MsgBlock{{
+		Header: wire.BlockHeader{
+			Version:     1,
+			Height:      100000,
+			Timestamp:   time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC
+			Voters:      2,
+			Revocations: 1,
+			FreshStake:  4,
+		},
+		Transactions:  make([]*wire.MsgTx, 4),
+		STransactions: nil,
+	}, {
+		Header: wire.BlockHeader{
+			Version:     1,
+			Height:      100001,
+			Timestamp:   time.Unix(1293624163, 0), // 2010-12-29 12:02:43 +0000 UTC
+			Voters:      3,
+			Revocations: 2,
+			FreshStake:  2,
+		},
+		Transactions:  make([]*wire.MsgTx, 2),
+		STransactions: nil,
+	}, {
+		Header: wire.BlockHeader{
+			Version:     1,
+			Height:      100002,
+			Timestamp:   time.Unix(1293624463, 0), // 2010-12-29 12:07:43 +0000 UTC
+			Voters:      1,
+			Revocations: 3,
+			FreshStake:  1,
+		},
+		Transactions:  make([]*wire.MsgTx, 3),
+		STransactions: nil,
+	}}
+
+	tests := []struct {
+		name                string
+		reset               bool
+		inputBlock          *wire.MsgBlock
+		forceLog            bool
+		inputLastLogTime    time.Time
+		wantReceivedBlocks  uint64
+		wantReceivedTxns    uint64
+		wantReceivedVotes   uint64
+		wantReceivedRevokes uint64
+		wantReceivedTickets uint64
+	}{{
+		name:                "round 1, block 0, last log time < 10 secs ago, not forced",
+		inputBlock:          &testBlocks[0],
+		forceLog:            false,
+		inputLastLogTime:    time.Now(),
+		wantReceivedBlocks:  1,
+		wantReceivedTxns:    4,
+		wantReceivedVotes:   2,
+		wantReceivedRevokes: 1,
+		wantReceivedTickets: 4,
+	}, {
+		name:                "round 1, block 1, last log time < 10 secs ago, not forced",
+		inputBlock:          &testBlocks[1],
+		forceLog:            false,
+		inputLastLogTime:    time.Now(),
+		wantReceivedBlocks:  2,
+		wantReceivedTxns:    6,
+		wantReceivedVotes:   5,
+		wantReceivedRevokes: 3,
+		wantReceivedTickets: 6,
+	}, {
+		name:                "round 1, block 2, last log time < 10 secs ago, forced",
+		inputBlock:          &testBlocks[2],
+		forceLog:            true,
+		inputLastLogTime:    time.Now(),
+		wantReceivedBlocks:  0,
+		wantReceivedTxns:    0,
+		wantReceivedVotes:   0,
+		wantReceivedRevokes: 0,
+		wantReceivedTickets: 0,
+	}, {
+		name:                "round 2, block 0, last log time < 10 secs ago, not forced",
+		reset:               true,
+		inputBlock:          &testBlocks[0],
+		forceLog:            false,
+		inputLastLogTime:    time.Now(),
+		wantReceivedBlocks:  1,
+		wantReceivedTxns:    4,
+		wantReceivedVotes:   2,
+		wantReceivedRevokes: 1,
+		wantReceivedTickets: 4,
+	}, {
+		name:                "round 2, block 1, last log time > 10 secs ago, not forced",
+		inputBlock:          &testBlocks[1],
+		forceLog:            false,
+		inputLastLogTime:    time.Now().Add(-11 * time.Second),
+		wantReceivedBlocks:  0,
+		wantReceivedTxns:    0,
+		wantReceivedVotes:   0,
+		wantReceivedRevokes: 0,
+		wantReceivedTickets: 0,
+	}, {
+		name:                "round 2, block 2, last log time > 10 secs ago, forced",
+		inputBlock:          &testBlocks[2],
+		forceLog:            true,
+		inputLastLogTime:    time.Now().Add(-11 * time.Second),
+		wantReceivedBlocks:  0,
+		wantReceivedTxns:    0,
+		wantReceivedVotes:   0,
+		wantReceivedRevokes: 0,
+		wantReceivedTickets: 0,
+	}}
+
+	progressFn := func() float64 { return 0.0 }
+	progressLogger := New("Wrote", testLog)
+	for _, test := range tests {
+		if test.reset {
+			progressLogger = New("Wrote", testLog)
+		}
+		progressLogger.SetLastLogTime(test.inputLastLogTime)
+		progressLogger.LogProgress(test.inputBlock, test.forceLog, progressFn)
+		wantBlockProgressLogger := &Logger{
+			receivedBlocks:  test.wantReceivedBlocks,
+			receivedTxns:    test.wantReceivedTxns,
+			receivedVotes:   test.wantReceivedVotes,
+			receivedRevokes: test.wantReceivedRevokes,
+			receivedTickets: test.wantReceivedTickets,
+			lastLogTime:     progressLogger.lastLogTime,
+			progressAction:  progressLogger.progressAction,
+			subsystemLogger: progressLogger.subsystemLogger,
+		}
+		if !reflect.DeepEqual(progressLogger, wantBlockProgressLogger) {
+			t.Errorf("%s:\nwant: %+v\ngot: %+v\n", test.name,
+				wantBlockProgressLogger, progressLogger)
+		}
+	}
+}
+
+// TestLogHeaderProgress ensures the logging functionality for headers works as
+// expected via a test logger.
+func TestLogHeaderProgress(t *testing.T) {
+	tests := []struct {
+		name                string
+		reset               bool
+		numHeaders          uint64
+		forceLog            bool
+		lastLogTime         time.Time
+		wantReceivedHeaders uint64
+	}{{
+		name:                "round 1, batch 1, last log time < 10 secs ago, not forced",
+		numHeaders:          35500,
+		forceLog:            false,
+		lastLogTime:         time.Now(),
+		wantReceivedHeaders: 35500,
+	}, {
+		name:                "round 1, batch 2, last log time < 10 secs ago, not forced",
+		numHeaders:          40000,
+		forceLog:            false,
+		lastLogTime:         time.Now(),
+		wantReceivedHeaders: 75500,
+	}, {
+		name:                "round 1, batch 3, last log time < 10 secs ago, not forced",
+		numHeaders:          66500,
+		forceLog:            false,
+		lastLogTime:         time.Now(),
+		wantReceivedHeaders: 142000,
+	}, {
+		name:                "round 2, batch 1, last log time < 10 secs ago, not forced",
+		reset:               true,
+		numHeaders:          40000,
+		forceLog:            false,
+		lastLogTime:         time.Now(),
+		wantReceivedHeaders: 40000,
+	}, {
+		name:                "round 2, batch 2, last log time > 10 secs ago, not forced",
+		numHeaders:          66500,
+		forceLog:            false,
+		lastLogTime:         time.Now().Add(-11 * time.Second),
+		wantReceivedHeaders: 0,
+	}, {
+		name:                "round 2, batch 1, last log time < 10 secs ago, forced",
+		reset:               true,
+		numHeaders:          54330,
+		forceLog:            true,
+		lastLogTime:         time.Now(),
+		wantReceivedHeaders: 0,
+	}}
+
+	progressFn := func() float64 { return 0.0 }
+	logger := New("Wrote", testLog)
+	for _, test := range tests {
+		if test.reset {
+			logger = New("Wrote", testLog)
+		}
+		logger.SetLastLogTime(test.lastLogTime)
+		logger.LogHeaderProgress(test.numHeaders, test.forceLog, progressFn)
+
+		want := &Logger{
+			subsystemLogger: logger.subsystemLogger,
+			progressAction:  logger.progressAction,
+			lastLogTime:     logger.lastLogTime,
+			receivedHeaders: test.wantReceivedHeaders,
+		}
+		if !reflect.DeepEqual(logger, want) {
+			t.Errorf("%s:\nwant: %+v\ngot: %+v\n", test.name, want, logger)
+		}
+	}
+}
diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
index eedb74a9..53267ad5 100644
--- a/internal/rpcserver/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2020 The Decred developers
+// Copyright (c) 2020-2021 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
@@ -500,7 +500,6 @@ func (c *testAddrManager) LocalAddresses() []addrmgr.LocalAddr {
 // SyncManager interface.
 type testSyncManager struct {
 	isCurrent             bool
-	isOrphan              bool
 	submitBlockErr        error
 	syncPeerID            int32
 	syncHeight            int64
@@ -516,8 +515,8 @@ func (s *testSyncManager) IsCurrent() bool {
 
 // SubmitBlock provides a mock implementation for submitting the provided block
 // to the network after processing it locally.
-func (s *testSyncManager) SubmitBlock(block *dcrutil.Block, flags blockchain.BehaviorFlags) (bool, error) {
-	return s.isOrphan, s.submitBlockErr
+func (s *testSyncManager) SubmitBlock(block *dcrutil.Block) error {
+	return s.submitBlockErr
 }
 
 // SyncPeer returns a mocked id of the current peer being synced with.
@@ -1584,7 +1583,6 @@ func defaultMockDB() *testDB {
 // *testSyncManager.
 func defaultMockSyncManager() *testSyncManager {
 	return &testSyncManager{
-		isOrphan:   false,
 		syncHeight: 463074,
 	}
 }
@@ -3521,7 +3519,7 @@ func TestHandleGetBlockchainInfo(t *testing.T) {
 			SyncHeight:           int64(463074),
 			ChainWork:            "000000000000000000000000000000000000000000115d2833849090b0026506",
 			InitialBlockDownload: true,
-			VerificationProgress: float64(0.9999978405179302),
+			VerificationProgress: float64(1),
 			BestBlockHash:        "00000000000000001e6ec1501c858506de1de4703d1be8bab4061126e8f61480",
 			Difficulty:           uint32(404696953),
 			DifficultyRatio:      float64(35256672611.3862),
@@ -5929,9 +5927,14 @@ func TestHandleGetWork(t *testing.T) {
 	hex.Encode(submissionB, data)
 
 	submission := string(submissionB)
-	truncatedSubmission := submission[1:]
 	lessThanGetWorkDataLen := submission[10:]
 
+	// Create an orphan block by mutating the prevblock field of the data.
+	orphanData := make([]byte, len(data))
+	copy(orphanData, data)
+	orphanData[4] ^= 0x55
+	orphanSubmission := hex.EncodeToString(orphanData)
+
 	buf = &bytes.Buffer{}
 	buf.Write(submissionB[:10])
 	buf.WriteRune('g')
@@ -6086,12 +6089,11 @@ func TestHandleGetWork(t *testing.T) {
 		name:    "handleGetWork: submission is an orphan",
 		handler: handleGetWork,
 		cmd: &types.GetWorkCmd{
-			Data: &truncatedSubmission,
+			Data: &orphanSubmission,
 		},
 		mockMiningState: mine(),
 		mockSyncManager: func() *testSyncManager {
 			syncManager := defaultMockSyncManager()
-			syncManager.isOrphan = true
 			return syncManager
 		}(),
 		result: false,
diff --git a/rpctest/README.md b/rpctest/README.md
index f17d9bb0..8b40e20a 100644
--- a/rpctest/README.md
+++ b/rpctest/README.md
@@ -19,9 +19,8 @@ systems/integration tests.
 
 ## Installation and Updating
 
-```bash
-$ go get -u github.com/decred/dcrd/rpctest
-```
+This package is part of the `github.com/decred/dcrd` module.  Use the standard
+go tooling for working with modules to incorporate it.
 
 ## License
 
diff --git a/rpctest/node.go b/rpctest/node.go
index 922fee71..ebd0a783 100644
--- a/rpctest/node.go
+++ b/rpctest/node.go
@@ -40,7 +40,7 @@ type nodeConfig struct {
 	extra      []string
 	prefix     string
 
-	exe          string
+	pathToDCRD   string
 	endpoint     string
 	certFile     string
 	keyFile      string
@@ -57,7 +57,6 @@ func newConfig(prefix, certFile, keyFile string, extra []string) (*nodeConfig, e
 		extra:     extra,
 		prefix:    prefix,
 
-		exe:      "dcrd",
 		endpoint: "ws",
 		certFile: certFile,
 		keyFile:  keyFile,
@@ -138,7 +137,7 @@ func (n *nodeConfig) arguments() []string {
 
 // command returns the exec.Cmd which will be used to start the dcrd process.
 func (n *nodeConfig) command() *exec.Cmd {
-	return exec.Command(n.exe, n.arguments()...)
+	return exec.Command(n.pathToDCRD, n.arguments()...)
 }
 
 // rpcConnConfig returns the rpc connection config that can be used to connect
@@ -202,16 +201,55 @@ func (n *node) debugf(format string, args ...interface{}) {
 	debugf(n.t, pid+format, args...)
 }
 
+// buildNode creates a new temporary directory and node and saves the location
+// to a package level variable where it is used for all tests. pathToDCRDMtx
+// must be held for writes.
+func buildNode(t *testing.T) error {
+	testNodeDir, err := ioutil.TempDir("", "rpctestdcrdnode")
+	if err != nil {
+		return err
+	}
+	pathToDCRD = filepath.Join(testNodeDir, "dcrd")
+	if runtime.GOOS == "windows" {
+		pathToDCRD += ".exe"
+	}
+	debugf(t, "test node located at: %v\n", pathToDCRD)
+	// Determine import path of this package.
+	_, rpctestDir, _, ok := runtime.Caller(1)
+	if !ok {
+		return fmt.Errorf("cannot get path to dcrd source code")
+	}
+	dcrdPkgPath := filepath.Join(rpctestDir, "..", "..")
+	// Build dcrd and output an executable in a static temp path.
+	cmd := exec.Command("go", "build", "-o", pathToDCRD, dcrdPkgPath)
+	err = cmd.Run()
+	if err != nil {
+		return fmt.Errorf("failed to build dcrd: %v", err)
+	}
+	return nil
+}
+
 // newNode creates a new node instance according to the passed config. dataDir
 // will be used to hold a file recording the pid of the launched process, and
-// as the base for the log and data directories for dcrd.
-func newNode(t *testing.T, config *nodeConfig, dataDir string) *node {
+// as the base for the log and data directories for dcrd. If pathToDCRD has a
+// non-zero value, the executable located there is used.
+func newNode(t *testing.T, config *nodeConfig, dataDir string) (*node, error) {
+	// Create the dcrd node used for tests if not created yet.
+	pathToDCRDMtx.Lock()
+	if pathToDCRD == "" {
+		if err := buildNode(t); err != nil {
+			pathToDCRDMtx.Unlock()
+			return nil, err
+		}
+	}
+	config.pathToDCRD = pathToDCRD
+	pathToDCRDMtx.Unlock()
 	return &node{
 		config:  config,
 		dataDir: dataDir,
 		cmd:     config.command(),
 		t:       t,
-	}
+	}, nil
 }
 
 // start creates a new dcrd process, and writes its pid in a file reserved for
diff --git a/rpctest/rpc_harness.go b/rpctest/rpc_harness.go
index 996c2381..4e356db7 100644
--- a/rpctest/rpc_harness.go
+++ b/rpctest/rpc_harness.go
@@ -59,6 +59,12 @@ var (
 
 	// Used to protest concurrent access to above declared variables.
 	harnessStateMtx sync.RWMutex
+
+	// pathToDCRD points to the test node. It is supplied through
+	// NewWithDCRD or created on the first call to newNode and used
+	// throughout the life of this package.
+	pathToDCRD    string
+	pathToDCRDMtx sync.RWMutex
 )
 
 const (
@@ -100,12 +106,32 @@ type Harness struct {
 	sync.Mutex
 }
 
+// SetPathToDCRD sets the package level dcrd executable. All calls to New will
+// use the dcrd located there throughout their life. If not set upon the first
+// call to New, a dcrd will be created in a temporary directory and pathToDCRD
+// set automatically.
+//
+// NOTE: This function is safe for concurrent access, but care must be taken
+// when setting different paths and using New, as whatever is at pathToDCRD at
+// the time will be identified with that node.
+func SetPathToDCRD(fnScopePathToDCRD string) {
+	pathToDCRDMtx.Lock()
+	pathToDCRD = fnScopePathToDCRD
+	pathToDCRDMtx.Unlock()
+}
+
 // New creates and initializes new instance of the rpc test harness.
 // Optionally, websocket handlers and a specified configuration may be passed.
 // In the case that a nil config is passed, a default configuration will be
-// used.
+// used. If pathToDCRD has not been set and working within the dcrd repository,
+// a dcrd executable created from the directory at rpctest/../ (dcrd repo's
+// root directory) will be created in a temporary directory. pathToDCRD will be
+// set as that file's location. If pathToDCRD has already been set, the
+// executable at that location will be used.
 //
-// NOTE: This function is safe for concurrent access.
+// NOTE: This function is safe for concurrent access, but care must be taken
+// when calling New with different dcrd executables, as whatever is at
+// pathToDCRD at the time will be identified with that node.
 func New(t *testing.T, activeNet *chaincfg.Params, handlers *rpcclient.NotificationHandlers, extraArgs []string) (*Harness, error) {
 	harnessStateMtx.Lock()
 	defer harnessStateMtx.Unlock()
@@ -159,7 +185,10 @@ func New(t *testing.T, activeNet *chaincfg.Params, handlers *rpcclient.Notificat
 	config.listen, config.rpcListen = generateListeningAddresses()
 
 	// Create the testing node bounded to the simnet.
-	node := newNode(t, config, nodeTestData)
+	node, err := newNode(t, config, nodeTestData)
+	if err != nil {
+		return nil, err
+	}
 	nodeNum := numTestInstances
 	numTestInstances++ // XXX this really should be the length of the harness map.
 
diff --git a/run_tests.sh b/run_tests.sh
index de7ef7a0..83623f08 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -14,14 +14,8 @@ set -ex
 # golangci-lint (github.com/golangci/golangci-lint) is used to run each
 # static checker.
 
-REPO=dcrd
-
 go version
 
-# binary needed for RPC tests
-env CC=gcc go build
-cp "$REPO" "$(go env GOPATH)/bin/"
-
 # run tests on all modules
 ROOTPATH=$(go list -m)
 ROOTPATHPATTERN=$(echo $ROOTPATH | sed 's/\\/\\\\/g' | sed 's/\//\\\//g')