Repository: decred/dcrd
Pull request #2278 (2020-07-20 12:18:38)

multi: update blockchain and mempool error types.
This updates the blockchain and mempool error types to leverage go 1.13 errors.Is/As functionality as well as confirm to
the error infrastructure best practices.


Patch:
diff --git a/blockchain/chain.go b/blockchain/chain.go
index 477e7ad8..3c157851 100644
--- a/blockchain/chain.go
+++ b/blockchain/chain.go
@@ -1260,21 +1260,22 @@ func (b *BlockChain) forceHeadReorganization(formerBest chainhash.Hash, newBest
 	// We can't reorganize the chain unless our head block matches up with
 	// b.bestChain.
 	if !formerBestNode.hash.IsEqual(&formerBest) {
-		return ruleError(ErrForceReorgWrongChain, "tried to force reorg "+
-			"on wrong chain")
+		str := "tried to force reorg on wrong chain"
+		return ruleError(ErrForceReorgWrongChain, str)
 	}
 
 	// Child to reorganize to is missing.
 	newBestNode := b.index.LookupNode(&newBest)
 	if newBestNode == nil || newBestNode.parent != formerBestNode.parent {
-		return ruleError(ErrForceReorgMissingChild, "missing child of "+
-			"common parent for forced reorg")
+		str := "missing child of common parent for forced reorg"
+		return ruleError(ErrForceReorgMissingChild, str)
 	}
 
 	// Don't allow a reorganize to a known invalid chain.
 	newBestNodeStatus := b.index.NodeStatus(newBestNode)
 	if newBestNodeStatus.KnownInvalid() {
-		return ruleError(ErrKnownInvalidBlock, "block is known to be invalid")
+		str := "block is known to be invalid"
+		return ruleError(ErrKnownInvalidBlock, str)
 	}
 
 	// Reorganize the chain and flush any potential unsaved changes to the
diff --git a/blockchain/error.go b/blockchain/error.go
index 5edce24a..dd2de481 100644
--- a/blockchain/error.go
+++ b/blockchain/error.go
@@ -6,7 +6,6 @@
 package blockchain
 
 import (
-	"errors"
 	"fmt"
 
 	"github.com/decred/dcrd/chaincfg/chainhash"
@@ -93,703 +92,563 @@ func (e AssertError) Error() string {
 	return "assertion failed: " + string(e)
 }
 
-// ErrorCode identifies a kind of error.
-type ErrorCode int
+// ErrorKind identifies a kind of error.  It has full support for errors.Is and
+// errors.As, so the caller can directly check against an error kind when
+// determining the reason for an error.
+type ErrorKind string
 
 // These constants are used to identify a specific RuleError.
 const (
 	// ErrDuplicateBlock indicates a block with the same hash already
 	// exists.
-	ErrDuplicateBlock ErrorCode = iota
+	ErrDuplicateBlock = ErrorKind("ErrDuplicateBlock")
 
 	// ErrMissingParent indicates that the block was an orphan.
-	ErrMissingParent
+	ErrMissingParent = ErrorKind("ErrMissingParent")
 
 	// ErrBlockTooBig indicates the serialized block size exceeds the
 	// maximum allowed size.
-	ErrBlockTooBig
+	ErrBlockTooBig = ErrorKind("ErrBlockTooBig")
 
 	// ErrWrongBlockSize indicates that the block size in the header is not
 	// the actual serialized size of the block.
-	ErrWrongBlockSize
+	ErrWrongBlockSize = ErrorKind("ErrWrongBlockSize")
 
 	// ErrBlockVersionTooOld indicates the block version is too old and is
 	// no longer accepted since the majority of the network has upgraded
 	// to a newer version.
-	ErrBlockVersionTooOld
+	ErrBlockVersionTooOld = ErrorKind("ErrBlockVersionTooOld")
 
 	// ErrBadStakeVersionindicates the block version is too old and is no
 	// longer accepted since the majority of the network has upgraded to a
 	// newer version.
-	ErrBadStakeVersion
+	ErrBadStakeVersion = ErrorKind("ErrBadStakeVersion")
 
 	// ErrInvalidTime indicates the time in the passed block has a precision
 	// that is more than one second.  The chain consensus rules require
 	// timestamps to have a maximum precision of one second.
-	ErrInvalidTime
+	ErrInvalidTime = ErrorKind("ErrInvalidTime")
 
 	// ErrTimeTooOld indicates the time is either before the median time of
 	// the last several blocks per the chain consensus rules or prior to the
 	// most recent checkpoint.
-	ErrTimeTooOld
+	ErrTimeTooOld = ErrorKind("ErrTimeTooOld")
 
 	// ErrTimeTooNew indicates the time is too far in the future as compared
 	// the current time.
-	ErrTimeTooNew
+	ErrTimeTooNew = ErrorKind("ErrTimeTooNew")
 
 	// ErrDifficultyTooLow indicates the difficulty for the block is lower
 	// than the difficulty required by the most recent checkpoint.
-	ErrDifficultyTooLow
+	ErrDifficultyTooLow = ErrorKind("ErrDifficultyTooLow")
 
 	// ErrUnexpectedDifficulty indicates specified bits do not align with
 	// the expected value either because it doesn't match the calculated
 	// value based on difficulty regarding the rules or it is out of the
 	// valid range.
-	ErrUnexpectedDifficulty
+	ErrUnexpectedDifficulty = ErrorKind("ErrUnexpectedDifficulty")
 
 	// ErrHighHash indicates the block does not hash to a value which is
 	// lower than the required target difficultly.
-	ErrHighHash
+	ErrHighHash = ErrorKind("ErrHighHash")
 
 	// ErrBadMerkleRoot indicates the calculated merkle root does not match
 	// the expected value.
-	ErrBadMerkleRoot
+	ErrBadMerkleRoot = ErrorKind("ErrBadMerkleRoot")
 
 	// ErrBadCommitmentRoot indicates the calculated commitment root does
 	// not match the expected value.
-	ErrBadCommitmentRoot
+	ErrBadCommitmentRoot = ErrorKind("ErrBadCommitmentRoot")
 
 	// ErrBadCheckpoint indicates a block that is expected to be at a
 	// checkpoint height does not match the expected one.
-	ErrBadCheckpoint
+	ErrBadCheckpoint = ErrorKind("ErrBadCheckpoint")
 
 	// ErrForkTooOld indicates a block is attempting to fork the block chain
 	// before the most recent checkpoint.
-	ErrForkTooOld
+	ErrForkTooOld = ErrorKind("ErrForkTooOld")
 
 	// ErrCheckpointTimeTooOld indicates a block has a timestamp before the
 	// most recent checkpoint.
-	ErrCheckpointTimeTooOld
+	ErrCheckpointTimeTooOld = ErrorKind("ErrCheckpointTimeTooOld")
 
 	// ErrNoTransactions indicates the block does not have a least one
 	// transaction.  A valid block must have at least the coinbase
 	// transaction.
-	ErrNoTransactions
+	ErrNoTransactions = ErrorKind("ErrNoTransactions")
 
 	// ErrTooManyTransactions indicates the block has more transactions than
 	// are allowed.
-	ErrTooManyTransactions
+	ErrTooManyTransactions = ErrorKind("ErrTooManyTransactions")
 
 	// ErrNoTxInputs indicates a transaction does not have any inputs.  A
 	// valid transaction must have at least one input.
-	ErrNoTxInputs
+	ErrNoTxInputs = ErrorKind("ErrNoTxInputs")
 
 	// ErrNoTxOutputs indicates a transaction does not have any outputs.  A
 	// valid transaction must have at least one output.
-	ErrNoTxOutputs
+	ErrNoTxOutputs = ErrorKind("ErrNoTxOutputs")
 
 	// ErrInvalidTxOutputs indicates a transaction does not have the exact
 	// number of outputs.
-	ErrInvalidTxOutputs
+	ErrInvalidTxOutputs = ErrorKind("ErrInvalidTxOutputs")
 
 	// ErrTxTooBig indicates a transaction exceeds the maximum allowed size
 	// when serialized.
-	ErrTxTooBig
+	ErrTxTooBig = ErrorKind("ErrTxTooBig")
 
 	// ErrBadTxOutValue indicates an output value for a transaction is
 	// invalid in some way such as being out of range.
-	ErrBadTxOutValue
+	ErrBadTxOutValue = ErrorKind("ErrBadTxOutValue")
 
 	// ErrDuplicateTxInputs indicates a transaction references the same
 	// input more than once.
-	ErrDuplicateTxInputs
+	ErrDuplicateTxInputs = ErrorKind("ErrDuplicateTxInputs")
 
 	// ErrBadTxInput indicates a transaction input is invalid in some way
 	// such as referencing a previous transaction outpoint which is out of
 	// range or not referencing one at all.
-	ErrBadTxInput
+	ErrBadTxInput = ErrorKind("ErrBadTxInput")
 
 	// ErrMissingTxOut indicates a transaction output referenced by an input
 	// either does not exist or has already been spent.
-	ErrMissingTxOut
+	ErrMissingTxOut = ErrorKind("ErrMissingTxOut")
 
 	// ErrUnfinalizedTx indicates a transaction has not been finalized.
 	// A valid block may only contain finalized transactions.
-	ErrUnfinalizedTx
+	ErrUnfinalizedTx = ErrorKind("ErrUnfinalizedTx")
 
 	// ErrDuplicateTx indicates a block contains an identical transaction
 	// (or at least two transactions which hash to the same value).  A
 	// valid block may only contain unique transactions.
-	ErrDuplicateTx
+	ErrDuplicateTx = ErrorKind("ErrDuplicateTx")
 
 	// ErrOverwriteTx indicates a block contains a transaction that has
 	// the same hash as a previous transaction which has not been fully
 	// spent.
-	ErrOverwriteTx
+	ErrOverwriteTx = ErrorKind("ErrOverwriteTx")
 
 	// ErrImmatureSpend indicates a transaction is attempting to spend a
 	// coinbase that has not yet reached the required maturity.
-	ErrImmatureSpend
+	ErrImmatureSpend = ErrorKind("ErrImmatureSpend")
 
 	// ErrSpendTooHigh indicates a transaction is attempting to spend more
 	// value than the sum of all of its inputs.
-	ErrSpendTooHigh
+	ErrSpendTooHigh = ErrorKind("ErrSpendTooHigh")
 
 	// ErrBadFees indicates the total fees for a block are invalid due to
 	// exceeding the maximum possible value.
-	ErrBadFees
+	ErrBadFees = ErrorKind("ErrBadFees")
 
 	// ErrTooManySigOps indicates the total number of signature operations
 	// for a transaction or block exceed the maximum allowed limits.
-	ErrTooManySigOps
+	ErrTooManySigOps = ErrorKind("ErrTooManySigOps")
 
 	// ErrFirstTxNotCoinbase indicates the first transaction in a block
 	// is not a coinbase transaction.
-	ErrFirstTxNotCoinbase
+	ErrFirstTxNotCoinbase = ErrorKind("ErrFirstTxNotCoinbase")
 
 	// ErrFirstTxNotOpReturn indicates the first transaction in a block
 	// is not an OP_RETURN.
-	ErrFirstTxNotOpReturn
+	ErrFirstTxNotOpReturn = ErrorKind("ErrFirstTxNotOpReturn")
 
 	// ErrCoinbaseHeight indicates that the encoded height in the coinbase
 	// is incorrect.
-	ErrCoinbaseHeight
+	ErrCoinbaseHeight = ErrorKind("ErrCoinbaseHeight")
 
 	// ErrMultipleCoinbases indicates a block contains more than one
 	// coinbase transaction.
-	ErrMultipleCoinbases
+	ErrMultipleCoinbases = ErrorKind("ErrMultipleCoinbases")
 
 	// ErrStakeTxInRegularTree indicates a stake transaction was found in
 	// the regular transaction tree.
-	ErrStakeTxInRegularTree
+	ErrStakeTxInRegularTree = ErrorKind("ErrStakeTxInRegularTree")
 
 	// ErrRegTxInStakeTree indicates that a regular transaction was found in
 	// the stake transaction tree.
-	ErrRegTxInStakeTree
+	ErrRegTxInStakeTree = ErrorKind("ErrRegTxInStakeTree")
 
 	// ErrBadCoinbaseScriptLen indicates the length of the signature script
 	// for a coinbase transaction is not within the valid range.
-	ErrBadCoinbaseScriptLen
+	ErrBadCoinbaseScriptLen = ErrorKind("ErrBadCoinbaseScriptLen")
 
 	// ErrBadCoinbaseValue indicates the amount of a coinbase value does
 	// not match the expected value of the subsidy plus the sum of all fees.
-	ErrBadCoinbaseValue
+	ErrBadCoinbaseValue = ErrorKind("ErrBadCoinbaseValue")
 
 	// ErrBadCoinbaseOutpoint indicates that the outpoint used by a coinbase
 	// as input was non-null.
-	ErrBadCoinbaseOutpoint
+	ErrBadCoinbaseOutpoint = ErrorKind("ErrBadCoinbaseOutpoint")
 
 	// ErrBadCoinbaseFraudProof indicates that the fraud proof for a coinbase
 	// input was non-null.
-	ErrBadCoinbaseFraudProof
+	ErrBadCoinbaseFraudProof = ErrorKind("ErrBadCoinbaseFraudProof")
 
 	// ErrBadCoinbaseAmountIn indicates that the AmountIn (=subsidy) for a
 	// coinbase input was incorrect.
-	ErrBadCoinbaseAmountIn
+	ErrBadCoinbaseAmountIn = ErrorKind("ErrBadCoinbaseAmountIn")
 
 	// ErrBadStakebaseAmountIn indicates that the AmountIn (=subsidy) for a
 	// stakebase input was incorrect.
-	ErrBadStakebaseAmountIn
+	ErrBadStakebaseAmountIn = ErrorKind("ErrBadStakebaseAmountIn")
 
 	// ErrBadStakebaseScriptLen indicates the length of the signature script
 	// for a stakebase transaction is not within the valid range.
-	ErrBadStakebaseScriptLen
+	ErrBadStakebaseScriptLen = ErrorKind("ErrBadStakebaseScriptLen")
 
 	// ErrBadStakebaseScrVal indicates the signature script for a stakebase
 	// transaction was not set to the network consensus value.
-	ErrBadStakebaseScrVal
+	ErrBadStakebaseScrVal = ErrorKind("ErrBadStakebaseScrVal")
 
 	// ErrScriptMalformed indicates a transaction script is malformed in
 	// some way.  For example, it might be longer than the maximum allowed
 	// length or fail to parse.
-	ErrScriptMalformed
+	ErrScriptMalformed = ErrorKind("ErrScriptMalformed")
 
 	// ErrScriptValidation indicates the result of executing transaction
 	// script failed.  The error covers any failure when executing scripts
 	// such signature verification failures and execution past the end of
 	// the stack.
-	ErrScriptValidation
+	ErrScriptValidation = ErrorKind("ErrScriptValidation")
 
 	// ErrNotEnoughStake indicates that there was for some SStx in a given block,
 	// the given SStx did not have enough stake to meet the network target.
-	ErrNotEnoughStake
+	ErrNotEnoughStake = ErrorKind("ErrNotEnoughStake")
 
 	// ErrStakeBelowMinimum indicates that for some SStx in a given block,
 	// the given SStx had an amount of stake below the minimum network target.
-	ErrStakeBelowMinimum
+	ErrStakeBelowMinimum = ErrorKind("ErrStakeBelowMinimum")
 
 	// ErrNonstandardStakeTx indicates that a block contained a stake tx that
 	// was not one of the allowed types of a stake transactions.
-	ErrNonstandardStakeTx
+	ErrNonstandardStakeTx = ErrorKind("ErrNonstandardStakeTx")
 
 	// ErrNotEnoughVotes indicates that a block contained less than a majority
 	// of voters.
-	ErrNotEnoughVotes
+	ErrNotEnoughVotes = ErrorKind("ErrNotEnoughVotes")
 
 	// ErrTooManyVotes indicates that a block contained more than the maximum
 	// allowable number of votes.
-	ErrTooManyVotes
+	ErrTooManyVotes = ErrorKind("ErrTooManyVotes")
 
 	// ErrFreshStakeMismatch indicates that a block's header contained a different
 	// number of SStx as compared to what was found in the block.
-	ErrFreshStakeMismatch
+	ErrFreshStakeMismatch = ErrorKind("ErrFreshStakeMismatch")
 
 	// ErrTooManySStxs indicates that more than the allowed number of SStx was
 	// found in a block.
-	ErrTooManySStxs
+	ErrTooManySStxs = ErrorKind("ErrTooManySStxs")
 
 	// ErrInvalidEarlyStakeTx indicates that a tx type other than SStx was found
 	// in the stake tx tree before the period when stake validation begins, or
 	// before the stake tx type could possibly be included in the block.
-	ErrInvalidEarlyStakeTx
+	ErrInvalidEarlyStakeTx = ErrorKind("ErrInvalidEarlyStakeTx")
 
 	// ErrTicketUnavailable indicates that a vote in the block spent a ticket
 	// that could not be found.
-	ErrTicketUnavailable
+	ErrTicketUnavailable = ErrorKind("ErrTicketUnavailable")
 
 	// ErrVotesOnWrongBlock indicates that an SSGen voted on a block not the
 	// block's parent, and so was ineligible for inclusion into that block.
-	ErrVotesOnWrongBlock
+	ErrVotesOnWrongBlock = ErrorKind("ErrVotesOnWrongBlock")
 
 	// ErrVotesMismatch indicates that the number of SSGen in the block was not
 	// equivalent to the number of votes provided in the block header.
-	ErrVotesMismatch
+	ErrVotesMismatch = ErrorKind("ErrVotesMismatch")
 
 	// ErrIncongruentVotebit indicates that the first votebit in votebits was not
 	// the same as that determined by the majority of voters in the SSGen tx
 	// included in the block.
-	ErrIncongruentVotebit
+	ErrIncongruentVotebit = ErrorKind("ErrIncongruentVotebit")
 
 	// ErrInvalidSSRtx indicates than an SSRtx in a block could not be found to
 	// have a valid missed sstx input as per the stake ticket database.
-	ErrInvalidSSRtx
+	ErrInvalidSSRtx = ErrorKind("ErrInvalidSSRtx")
 
 	// ErrInvalidRevNum indicates that the number of revocations from the
 	// header was not the same as the number of SSRtx included in the block.
-	ErrRevocationsMismatch
+	ErrRevocationsMismatch = ErrorKind("ErrRevocationsMismatch")
 
 	// ErrTooManyRevocations indicates more revocations were found in a block
 	// than were allowed.
-	ErrTooManyRevocations
+	ErrTooManyRevocations = ErrorKind("ErrTooManyRevocations")
 
 	// ErrTicketCommitment indicates that a ticket commitment contains an amount
 	// that does not coincide with the associated ticket input amount.
-	ErrTicketCommitment
+	ErrTicketCommitment = ErrorKind("ErrTicketCommitment")
 
 	// ErrInvalidVoteInput indicates that an input to a vote transaction is
 	// either not a stake ticket submission or is not a supported version.
-	ErrInvalidVoteInput
+	ErrInvalidVoteInput = ErrorKind("ErrInvalidVoteInput")
 
 	// ErrBadNumPayees indicates that either a vote or revocation transaction
 	// does not make the correct number of payments per the associated ticket
 	// commitments.
-	ErrBadNumPayees
+	ErrBadNumPayees = ErrorKind("ErrBadNumPayees")
 
 	// ErrBadPayeeScriptVersion indicates that either a vote or revocation
 	// transaction output that corresponds to a ticket commitment does not use
 	// a supported script version.
-	ErrBadPayeeScriptVersion
+	ErrBadPayeeScriptVersion = ErrorKind("ErrBadPayeeScriptVersion")
 
 	// ErrBadPayeeScriptType indicates that either a vote or revocation
 	// transaction output that corresponds to a ticket commitment does not pay
 	// to the same script type required by the commitment.
-	ErrBadPayeeScriptType
+	ErrBadPayeeScriptType = ErrorKind("ErrBadPayeeScriptType")
 
 	// ErrBadPayeeScriptType indicates that either a vote or revocation
 	// transaction output that corresponds to a ticket commitment does not pay
 	// to the hash required by the commitment.
-	ErrMismatchedPayeeHash
+	ErrMismatchedPayeeHash = ErrorKind("ErrMismatchedPayeeHash")
 
 	// ErrBadPayeeValue indicates that either a vote or revocation transaction
 	// output that corresponds to a ticket commitment does not pay the expected
 	// amount required by the commitment.
-	ErrBadPayeeValue
+	ErrBadPayeeValue = ErrorKind("ErrBadPayeeValue")
 
 	// ErrSSGenSubsidy indicates that there was an error in the amount of subsidy
 	// generated in the vote.
-	ErrSSGenSubsidy
+	ErrSSGenSubsidy = ErrorKind("ErrSSGenSubsidy")
 
 	// ErrImmatureTicketSpend indicates that a vote or revocation is attempting
 	// to spend a ticket submission output that has not yet reached the required
 	// maturity.
-	ErrImmatureTicketSpend
+	ErrImmatureTicketSpend = ErrorKind("ErrImmatureTicketSpend")
 
 	// ErrTicketInputScript indicates that a ticket input is not one of the
 	// supported script forms or versions.
-	ErrTicketInputScript
+	ErrTicketInputScript = ErrorKind("ErrTicketInputScript")
 
 	// ErrInvalidRevokeInput indicates that an input to a revocation transaction
 	// is either not a stake ticket submission or is not a supported version.
-	ErrInvalidRevokeInput
+	ErrInvalidRevokeInput = ErrorKind("ErrInvalidRevokeInput")
 
 	// ErrSSRtxPayees indicates that the SSRtx failed to pay out to the committed
 	// addresses or amounts from the originating SStx.
-	ErrSSRtxPayees
+	ErrSSRtxPayees = ErrorKind("ErrSSRtxPayees")
 
 	// ErrTxSStxOutSpend indicates that a non SSGen or SSRtx tx attempted to spend
 	// an OP_SSTX tagged output from an SStx.
-	ErrTxSStxOutSpend
+	ErrTxSStxOutSpend = ErrorKind("ErrTxSStxOutSpend")
 
 	// ErrRegTxCreateStakeOut indicates that a regular tx attempted to create
 	// a stake tagged output.
-	ErrRegTxCreateStakeOut
+	ErrRegTxCreateStakeOut = ErrorKind("ErrRegTxCreateStakeOut")
 
 	// ErrInvalidFinalState indicates that the final state of the PRNG included
 	// in the block differed from the calculated final state.
-	ErrInvalidFinalState
+	ErrInvalidFinalState = ErrorKind("ErrInvalidFinalState")
 
 	// ErrPoolSize indicates an error in the ticket pool size for this block.
-	ErrPoolSize
+	ErrPoolSize = ErrorKind("ErrPoolSize")
 
 	// ErrForceReorgWrongChain indicates that a reroganization was attempted
 	// to be forced, but the chain indicated was not mirrored by b.bestChain.
-	ErrForceReorgWrongChain
+	ErrForceReorgWrongChain = ErrorKind("ErrForceReorgWrongChain")
 
 	// ErrForceReorgMissingChild indicates that a reroganization was attempted
 	// to be forced, but the child node to reorganize to could not be found.
-	ErrForceReorgMissingChild
+	ErrForceReorgMissingChild = ErrorKind("ErrForceReorgMissingChild")
 
 	// ErrBadStakebaseValue indicates that a block's stake tx tree has spent
 	// more than it is allowed.
-	ErrBadStakebaseValue
+	ErrBadStakebaseValue = ErrorKind("ErrBadStakebaseValue")
 
 	// ErrDiscordantTxTree specifies that a given origin tx's content
 	// indicated that it should exist in a different tx tree than the
 	// one given in the TxIn outpoint.
-	ErrDiscordantTxTree
+	ErrDiscordantTxTree = ErrorKind("ErrDiscordantTxTree")
 
 	// ErrStakeFees indicates an error with the fees found in the stake
 	// transaction tree.
-	ErrStakeFees
+	ErrStakeFees = ErrorKind("ErrStakeFees")
 
 	// ErrNoStakeTx indicates there were no stake transactions found in a
 	// block after stake validation height.
-	ErrNoStakeTx
+	ErrNoStakeTx = ErrorKind("ErrNoStakeTx")
 
 	// ErrBadBlockHeight indicates that a block header's embedded block height
 	// was different from where it was actually embedded in the block chain.
-	ErrBadBlockHeight
+	ErrBadBlockHeight = ErrorKind("ErrBadBlockHeight")
 
 	// ErrBlockOneTx indicates that block height 1 failed to correct generate
 	// the block one initial payout transaction.
-	ErrBlockOneTx
+	ErrBlockOneTx = ErrorKind("ErrBlockOneTx")
 
 	// ErrBlockOneTx indicates that block height 1 coinbase transaction in
 	// zero was incorrect in some way.
-	ErrBlockOneInputs
+	ErrBlockOneInputs = ErrorKind("ErrBlockOneInputs")
 
 	// ErrBlockOneOutputs indicates that block height 1 failed to incorporate
 	// the ledger addresses correctly into the transaction's outputs.
-	ErrBlockOneOutputs
+	ErrBlockOneOutputs = ErrorKind("ErrBlockOneOutputs")
 
 	// ErrNoTax indicates that there was no tax present in the coinbase of a
 	// block after height 1.
-	ErrNoTax
+	ErrNoTax = ErrorKind("ErrNoTax")
 
 	// ErrExpiredTx indicates that the transaction is currently expired.
-	ErrExpiredTx
+	ErrExpiredTx = ErrorKind("ErrExpiredTx")
 
 	// ErrExpiryTxSpentEarly indicates that an output from a transaction
 	// that included an expiry field was spent before coinbase maturity
 	// many blocks had passed in the blockchain.
-	ErrExpiryTxSpentEarly
+	ErrExpiryTxSpentEarly = ErrorKind("ErrExpiryTxSpentEarly")
 
 	// ErrFraudAmountIn indicates the witness amount given was fraudulent.
-	ErrFraudAmountIn
+	ErrFraudAmountIn = ErrorKind("ErrFraudAmountIn")
 
 	// ErrFraudBlockHeight indicates the witness block height given was
 	// fraudulent.
-	ErrFraudBlockHeight
+	ErrFraudBlockHeight = ErrorKind("ErrFraudBlockHeight")
 
 	// ErrFraudBlockIndex indicates the witness block index given was
 	// fraudulent.
-	ErrFraudBlockIndex
+	ErrFraudBlockIndex = ErrorKind("ErrFraudBlockIndex")
 
 	// ErrZeroValueOutputSpend indicates that a transaction attempted to spend a
 	// zero value output.
-	ErrZeroValueOutputSpend
+	ErrZeroValueOutputSpend = ErrorKind("ErrZeroValueOutputSpend")
 
 	// ErrInvalidEarlyVoteBits indicates that a block before stake validation
 	// height had an unallowed vote bits value.
-	ErrInvalidEarlyVoteBits
+	ErrInvalidEarlyVoteBits = ErrorKind("ErrInvalidEarlyVoteBits")
 
 	// ErrInvalidEarlyFinalState indicates that a block before stake validation
 	// height had a non-zero final state.
-	ErrInvalidEarlyFinalState
+	ErrInvalidEarlyFinalState = ErrorKind("ErrInvalidEarlyFinalState")
 
 	// ErrKnownInvalidBlock indicates that this block has previously failed
 	// validation.
-	ErrKnownInvalidBlock
+	ErrKnownInvalidBlock = ErrorKind("ErrKnownInvalidBlock")
 
 	// ErrInvalidAncestorBlock indicates that an ancestor of this block has
 	// failed validation.
-	ErrInvalidAncestorBlock
+	ErrInvalidAncestorBlock = ErrorKind("ErrInvalidAncestorBlock")
 
 	// ErrInvalidTemplateParent indicates that a block template builds on a
 	// block that is either not the current best chain tip or its parent.
-	ErrInvalidTemplateParent
+	ErrInvalidTemplateParent = ErrorKind("ErrInvalidTemplateParent")
 
 	// ErrUnknownPiKey indicates that the provided public Pi Key is not
 	// a well known key.
-	ErrUnknownPiKey
+	ErrUnknownPiKey = ErrorKind("ErrUnknownPiKey")
 
 	// ErrInvalidPiSignature indicates that a treasury spend transaction
 	// was not properly signed.
-	ErrInvalidPiSignature
+	ErrInvalidPiSignature = ErrorKind("ErrInvalidPiSignature")
 
 	// ErrInvalidTVoteWindow indicates that a treasury spend transaction
 	// appeared in a block that is prior to a valid treasury vote window.
-	ErrInvalidTVoteWindow
+	ErrInvalidTVoteWindow = ErrorKind("ErrInvalidTVoteWindow")
 
 	// ErrNotTVI indicates that a treasury spend transaction appeared in a
 	// block that is not at a TVI interval.
-	ErrNotTVI
+	ErrNotTVI = ErrorKind("ErrNotTVI")
 
 	// ErrInvalidTSpendWindow indicates that this treasury spend
 	// transaction is outside of the allowed window.
-	ErrInvalidTSpendWindow
+	ErrInvalidTSpendWindow = ErrorKind("ErrInvalidTSpendWindow")
 
 	// ErrNotEnoughTSpendVotes indicates that a treasury spend transaction
 	// does not have enough votes to be included in block.
-	ErrNotEnoughTSpendVotes
+	ErrNotEnoughTSpendVotes = ErrorKind("ErrNotEnoughTSpendVotes")
 
 	// ErrInvalidTSpendValueIn indicates that a treasury spend transaction
 	// ValueIn does not match the encoded copy in the first TxOut.
-	ErrInvalidTSpendValueIn
+	ErrInvalidTSpendValueIn = ErrorKind("ErrInvalidTSpendValueIn")
 
 	// ErrTSpendExists indicates that a duplicate treasury spend
 	// transaction has been mined on a TVI in the current best chain.
-	ErrTSpendExists
+	ErrTSpendExists = ErrorKind("ErrTSpendExists")
 
 	// ErrInvalidExpenditure indicates that a treasury spend transaction
 	// expenditure is out of range.
-	ErrInvalidExpenditure
+	ErrInvalidExpenditure = ErrorKind("ErrInvalidExpenditure")
 
 	// ErrFirstTxNotTreasurybase indicates the first transaction in a block
 	// is not a treasurybase transaction.
-	ErrFirstTxNotTreasurybase
+	ErrFirstTxNotTreasurybase = ErrorKind("ErrFirstTxNotTreasurybase")
 
 	// ErrBadTreasurybaseOutpoint indicates that the outpoint used by a
 	// treasurybase as input was non-null.
-	ErrBadTreasurybaseOutpoint
+	ErrBadTreasurybaseOutpoint = ErrorKind("ErrBadTreasurybaseOutpoint")
 
 	// ErrBadTreasurybaseFraudProof indicates that the fraud proof for a
 	// treasurybase input was non-null.
-	ErrBadTreasurybaseFraudProof
+	ErrBadTreasurybaseFraudProof = ErrorKind("ErrBadTreasurybaseFraudProof")
 
 	// ErrBadTreasurybaseScriptLen indicates the length of the signature script
 	// for a treasurybase transaction is not within the valid range.
-	ErrBadTreasurybaseScriptLen
+	ErrBadTreasurybaseScriptLen = ErrorKind("ErrBadTreasurybaseScriptLen")
 
 	// ErrTreasurybaseTxNotOpReturn indicates the second ouptut of a
 	// treasury base transaction is not an OP_RETURN.
-	ErrTreasurybaseTxNotOpReturn
+	ErrTreasurybaseTxNotOpReturn = ErrorKind("ErrTreasurybaseTxNotOpReturn")
 
 	// ErrTreasurybaseHeight indicates that the encoded height in the
 	// treasurybase is incorrect.
-	ErrTreasurybaseHeight
+	ErrTreasurybaseHeight = ErrorKind("ErrTreasurybaseHeight")
 
 	// ErrInvalidTreasurybaseTxOutputs indicates that the transaction does
 	// not have the correct number of outputs.
-	ErrInvalidTreasurybaseTxOutputs
+	ErrInvalidTreasurybaseTxOutputs = ErrorKind("ErrInvalidTreasurybaseTxOutputs")
 
 	// ErrInvalidTreasurybaseVersion indicates that the transaction output
 	// has the wrong version.
-	ErrInvalidTreasurybaseVersion
+	ErrInvalidTreasurybaseVersion = ErrorKind("ErrInvalidTreasurybaseVersion")
 
 	// ErrInvalidTreasurybaseScript indicates that the transaction output
 	// script is invalid.
-	ErrInvalidTreasurybaseScript
+	ErrInvalidTreasurybaseScript = ErrorKind("ErrInvalidTreasurybaseScript")
 
 	// ErrTreasurybaseOutValue ensures that the OP_TADD value of a
 	// treasurybase is not the expected amount.
-	ErrTreasurybaseOutValue
+	ErrTreasurybaseOutValue = ErrorKind("ErrTreasurybaseOutValue")
 
 	// ErrMultipleTreasurybases indicates a block contains more than one
 	// treasurybase transaction.
-	ErrMultipleTreasurybases
+	ErrMultipleTreasurybases = ErrorKind("ErrMultipleTreasurybases")
 
 	// ErrBadTreasurybaseAmountIn indicates that a block contains an
 	// invalid treasury contribution.
-	ErrBadTreasurybaseAmountIn
+	ErrBadTreasurybaseAmountIn = ErrorKind("ErrBadTreasurybaseAmountIn")
 
 	// ErrBadTSpendOutpoint indicates that the outpoint used by a
 	// treasury spend as input was non-null.
-	ErrBadTSpendOutpoint
+	ErrBadTSpendOutpoint = ErrorKind("ErrBadTSpendOutpoint")
 
 	// ErrBadTSpendFraudProof indicates that the fraud proof for a treasury
 	// spend transaction input was non-null.
-	ErrBadTSpendFraudProof
+	ErrBadTSpendFraudProof = ErrorKind("ErrBadTSpendFraudProof")
 
 	// ErrBadTSpendScriptLen indicates the length of the signature script
 	// for a treasury spend transaction is not within the valid range.
-	ErrBadTSpendScriptLen
+	ErrBadTSpendScriptLen = ErrorKind("ErrBadTSpendScriptLen")
 
 	// ErrInvalidTAddChange indicates the change output of a TAdd is zero.
-	ErrInvalidTAddChange
+	ErrInvalidTAddChange = ErrorKind("ErrInvalidTAddChange")
 
 	// ErrTooManyTAdds indicates the number of treasury adds in a given
 	// block is larger than the maximum allowed.
-	ErrTooManyTAdds
+	ErrTooManyTAdds = ErrorKind("ErrTooManyTAdds")
 
 	// ErrTicketExhaustion indicates extending a given block with another one
 	// would result in an unrecoverable chain due to ticket exhaustion.
-	ErrTicketExhaustion
-
-	// numErrorCodes is the maximum error code number used in tests.
-	numErrorCodes
+	ErrTicketExhaustion = ErrorKind("ErrTicketExhaustion")
 )
 
-// Map of ErrorCode values back to their constant names for pretty printing.
-var errorCodeStrings = map[ErrorCode]string{
-	ErrDuplicateBlock:               "ErrDuplicateBlock",
-	ErrMissingParent:                "ErrMissingParent",
-	ErrBlockTooBig:                  "ErrBlockTooBig",
-	ErrWrongBlockSize:               "ErrWrongBlockSize",
-	ErrBlockVersionTooOld:           "ErrBlockVersionTooOld",
-	ErrBadStakeVersion:              "ErrBadStakeVersion",
-	ErrInvalidTime:                  "ErrInvalidTime",
-	ErrTimeTooOld:                   "ErrTimeTooOld",
-	ErrTimeTooNew:                   "ErrTimeTooNew",
-	ErrDifficultyTooLow:             "ErrDifficultyTooLow",
-	ErrUnexpectedDifficulty:         "ErrUnexpectedDifficulty",
-	ErrHighHash:                     "ErrHighHash",
-	ErrBadMerkleRoot:                "ErrBadMerkleRoot",
-	ErrBadCommitmentRoot:            "ErrBadCommitmentRoot",
-	ErrBadCheckpoint:                "ErrBadCheckpoint",
-	ErrForkTooOld:                   "ErrForkTooOld",
-	ErrCheckpointTimeTooOld:         "ErrCheckpointTimeTooOld",
-	ErrNoTransactions:               "ErrNoTransactions",
-	ErrTooManyTransactions:          "ErrTooManyTransactions",
-	ErrNoTxInputs:                   "ErrNoTxInputs",
-	ErrNoTxOutputs:                  "ErrNoTxOutputs",
-	ErrInvalidTxOutputs:             "ErrInvalidTxOutputs",
-	ErrTxTooBig:                     "ErrTxTooBig",
-	ErrBadTxOutValue:                "ErrBadTxOutValue",
-	ErrDuplicateTxInputs:            "ErrDuplicateTxInputs",
-	ErrBadTxInput:                   "ErrBadTxInput",
-	ErrMissingTxOut:                 "ErrMissingTxOut",
-	ErrUnfinalizedTx:                "ErrUnfinalizedTx",
-	ErrDuplicateTx:                  "ErrDuplicateTx",
-	ErrOverwriteTx:                  "ErrOverwriteTx",
-	ErrImmatureSpend:                "ErrImmatureSpend",
-	ErrSpendTooHigh:                 "ErrSpendTooHigh",
-	ErrBadFees:                      "ErrBadFees",
-	ErrTooManySigOps:                "ErrTooManySigOps",
-	ErrFirstTxNotCoinbase:           "ErrFirstTxNotCoinbase",
-	ErrFirstTxNotOpReturn:           "ErrFirstTxNotOpReturn",
-	ErrCoinbaseHeight:               "ErrCoinbaseHeight",
-	ErrMultipleCoinbases:            "ErrMultipleCoinbases",
-	ErrStakeTxInRegularTree:         "ErrStakeTxInRegularTree",
-	ErrRegTxInStakeTree:             "ErrRegTxInStakeTree",
-	ErrBadCoinbaseScriptLen:         "ErrBadCoinbaseScriptLen",
-	ErrBadCoinbaseValue:             "ErrBadCoinbaseValue",
-	ErrBadCoinbaseOutpoint:          "ErrBadCoinbaseOutpoint",
-	ErrBadCoinbaseFraudProof:        "ErrBadCoinbaseFraudProof",
-	ErrBadCoinbaseAmountIn:          "ErrBadCoinbaseAmountIn",
-	ErrBadStakebaseAmountIn:         "ErrBadStakebaseAmountIn",
-	ErrBadStakebaseScriptLen:        "ErrBadStakebaseScriptLen",
-	ErrBadStakebaseScrVal:           "ErrBadStakebaseScrVal",
-	ErrScriptMalformed:              "ErrScriptMalformed",
-	ErrScriptValidation:             "ErrScriptValidation",
-	ErrNotEnoughStake:               "ErrNotEnoughStake",
-	ErrStakeBelowMinimum:            "ErrStakeBelowMinimum",
-	ErrNonstandardStakeTx:           "ErrNonstandardStakeTx",
-	ErrNotEnoughVotes:               "ErrNotEnoughVotes",
-	ErrTooManyVotes:                 "ErrTooManyVotes",
-	ErrFreshStakeMismatch:           "ErrFreshStakeMismatch",
-	ErrTooManySStxs:                 "ErrTooManySStxs",
-	ErrInvalidEarlyStakeTx:          "ErrInvalidEarlyStakeTx",
-	ErrTicketUnavailable:            "ErrTicketUnavailable",
-	ErrVotesOnWrongBlock:            "ErrVotesOnWrongBlock",
-	ErrVotesMismatch:                "ErrVotesMismatch",
-	ErrIncongruentVotebit:           "ErrIncongruentVotebit",
-	ErrInvalidSSRtx:                 "ErrInvalidSSRtx",
-	ErrRevocationsMismatch:          "ErrRevocationsMismatch",
-	ErrTooManyRevocations:           "ErrTooManyRevocations",
-	ErrTicketCommitment:             "ErrTicketCommitment",
-	ErrInvalidVoteInput:             "ErrInvalidVoteInput",
-	ErrBadNumPayees:                 "ErrBadNumPayees",
-	ErrBadPayeeScriptVersion:        "ErrBadPayeeScriptVersion",
-	ErrBadPayeeScriptType:           "ErrBadPayeeScriptType",
-	ErrMismatchedPayeeHash:          "ErrMismatchedPayeeHash",
-	ErrBadPayeeValue:                "ErrBadPayeeValue",
-	ErrSSGenSubsidy:                 "ErrSSGenSubsidy",
-	ErrImmatureTicketSpend:          "ErrImmatureTicketSpend",
-	ErrTicketInputScript:            "ErrTicketInputScript",
-	ErrInvalidRevokeInput:           "ErrInvalidRevokeInput",
-	ErrSSRtxPayees:                  "ErrSSRtxPayees",
-	ErrTxSStxOutSpend:               "ErrTxSStxOutSpend",
-	ErrRegTxCreateStakeOut:          "ErrRegTxCreateStakeOut",
-	ErrInvalidFinalState:            "ErrInvalidFinalState",
-	ErrPoolSize:                     "ErrPoolSize",
-	ErrForceReorgWrongChain:         "ErrForceReorgWrongChain",
-	ErrForceReorgMissingChild:       "ErrForceReorgMissingChild",
-	ErrBadStakebaseValue:            "ErrBadStakebaseValue",
-	ErrDiscordantTxTree:             "ErrDiscordantTxTree",
-	ErrStakeFees:                    "ErrStakeFees",
-	ErrNoStakeTx:                    "ErrNoStakeTx",
-	ErrBadBlockHeight:               "ErrBadBlockHeight",
-	ErrBlockOneTx:                   "ErrBlockOneTx",
-	ErrBlockOneInputs:               "ErrBlockOneInputs",
-	ErrBlockOneOutputs:              "ErrBlockOneOutputs",
-	ErrNoTax:                        "ErrNoTax",
-	ErrExpiredTx:                    "ErrExpiredTx",
-	ErrExpiryTxSpentEarly:           "ErrExpiryTxSpentEarly",
-	ErrFraudAmountIn:                "ErrFraudAmountIn",
-	ErrFraudBlockHeight:             "ErrFraudBlockHeight",
-	ErrFraudBlockIndex:              "ErrFraudBlockIndex",
-	ErrZeroValueOutputSpend:         "ErrZeroValueOutputSpend",
-	ErrInvalidEarlyVoteBits:         "ErrInvalidEarlyVoteBits",
-	ErrInvalidEarlyFinalState:       "ErrInvalidEarlyFinalState",
-	ErrKnownInvalidBlock:            "ErrKnownInvalidBlock",
-	ErrInvalidAncestorBlock:         "ErrInvalidAncestorBlock",
-	ErrUnknownPiKey:                 "ErrUnknownPiKey",
-	ErrInvalidPiSignature:           "ErrInvalidPiSignature",
-	ErrInvalidTVoteWindow:           "ErrInvalidTVoteWindow",
-	ErrNotTVI:                       "ErrNotTVI",
-	ErrInvalidTSpendWindow:          "ErrInvalidTSpendWindow",
-	ErrNotEnoughTSpendVotes:         "ErrNotEnoughTSpendVotes",
-	ErrInvalidTSpendValueIn:         "ErrInvalidTSpendValueIn",
-	ErrTSpendExists:                 "ErrTSpendExists",
-	ErrInvalidExpenditure:           "ErrInvalidExpenditure",
-	ErrFirstTxNotTreasurybase:       "ErrFirstTxNotTreasurybase",
-	ErrBadTreasurybaseOutpoint:      "ErrBadTreasurybaseOutpoint",
-	ErrBadTreasurybaseFraudProof:    "ErrBadTreasurybaseFraudProof",
-	ErrBadTreasurybaseScriptLen:     "ErrBadTreasurybaseScriptLen",
-	ErrTreasurybaseTxNotOpReturn:    "ErrTreasurybaseTxNotOpReturn",
-	ErrTreasurybaseHeight:           "ErrTreasurybaseHeight",
-	ErrTreasurybaseOutValue:         "ErrTreasurybaseOutValue",
-	ErrInvalidTreasurybaseTxOutputs: "ErrInvalidTreasurybaseTxOutputs",
-	ErrInvalidTreasurybaseVersion:   "ErrInvalidTreasurybaseVersion",
-	ErrInvalidTreasurybaseScript:    "ErrInvalidTreasurybaseScript",
-	ErrMultipleTreasurybases:        "ErrMultipleTreasurybases",
-	ErrBadTreasurybaseAmountIn:      "ErrBadTreasurybaseAmountIn",
-	ErrBadTSpendOutpoint:            "ErrBadTSpendOutpoint",
-	ErrBadTSpendFraudProof:          "ErrBadTSpendFraudProof",
-	ErrBadTSpendScriptLen:           "ErrBadTSpendScriptLen",
-	ErrInvalidTemplateParent:        "ErrInvalidTemplateParent",
-	ErrInvalidTAddChange:            "ErrInvalidTAddChange",
-	ErrTooManyTAdds:                 "ErrTooManyTAdds",
-	ErrTicketExhaustion:             "ErrTicketExhaustion",
-}
-
-// String returns the ErrorCode as a human-readable name.
-func (e ErrorCode) String() string {
-	if s := errorCodeStrings[e]; s != "" {
-		return s
-	}
-	return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
+// Error satisfies the error interface and prints human-readable errors.
+func (e ErrorKind) Error() string {
+	return string(e)
 }
 
-// RuleError identifies a rule violation.  It is used to indicate that
-// processing of a block or transaction failed due to one of the many validation
-// rules.  The caller can use type assertions to determine if a failure was
-// specifically due to a rule violation and access the ErrorCode field to
-// ascertain the specific reason for the rule violation.
+// RuleError identifies a rule violation related to blocks. It has
+// full support for errors.Is and errors.As, so the caller can ascertain the
+// specific reason for the error by checking the underlying error.
 type RuleError struct {
-	ErrorCode   ErrorCode // Describes the kind of error
-	Description string    // Human readable description of the issue
+	Err         error
+	Description string
 }
 
 // Error satisfies the error interface and prints human-readable errors.
@@ -797,14 +656,12 @@ func (e RuleError) Error() string {
 	return e.Description
 }
 
-// ruleError creates a RuleError given a set of arguments.
-func ruleError(c ErrorCode, desc string) RuleError {
-	return RuleError{ErrorCode: c, Description: desc}
+// Unwrap returns the underlying wrapped error.
+func (e RuleError) Unwrap() error {
+	return e.Err
 }
 
-// IsErrorCode returns whether or not the provided error is a rule error with
-// the provided error code.
-func IsErrorCode(err error, c ErrorCode) bool {
-	var e RuleError
-	return errors.As(err, &e) && e.ErrorCode == c
+// ruleError creates a RuleError given a set of arguments.
+func ruleError(kind ErrorKind, desc string) RuleError {
+	return RuleError{Err: kind, Description: desc}
 }
diff --git a/blockmanager.go b/blockmanager.go
index 017ac44b..c7233467 100644
--- a/blockmanager.go
+++ b/blockmanager.go
@@ -662,76 +662,63 @@ func (b *blockManager) handleDonePeerMsg(peer *peerpkg.Peer) {
 // given error. This function can convert some select blockchain and mempool
 // error types to the historical rejection codes used on the p2p wire protocol.
 func errToWireRejectCode(err error) (wire.RejectCode, string) {
-	// Unwrap mempool errors.
-	var rerr mempool.RuleError
-	if errors.As(err, &rerr) {
-		err = rerr.Err
-	}
-
 	// The default reason to reject a transaction/block is due to it being
 	// invalid somehow.
 	code := wire.RejectInvalid
 	var reason string
 
-	var berr blockchain.RuleError
-	var terr mempool.TxRuleError
+	// Convert recognized errors to a reject code.
 	switch {
-	case errors.As(err, &berr):
-		// Convert the chain error to a reject code.
-		switch berr.ErrorCode {
-		// Rejected due to duplicate.
-		case blockchain.ErrDuplicateBlock:
-			code = wire.RejectDuplicate
-
-		// Rejected due to obsolete version.
-		case blockchain.ErrBlockVersionTooOld:
-			code = wire.RejectObsolete
-
-		// Rejected due to checkpoint.
-		case blockchain.ErrCheckpointTimeTooOld,
-			blockchain.ErrDifficultyTooLow,
-			blockchain.ErrBadCheckpoint,
-			blockchain.ErrForkTooOld:
-
-			code = wire.RejectCheckpoint
-		}
-
-		reason = berr.Error()
-	case errors.As(err, &terr):
-		switch terr.ErrorCode {
-		// Error codes which map to a duplicate transaction already
-		// mined or in the mempool.
-		case mempool.ErrMempoolDoubleSpend,
-			mempool.ErrAlreadyVoted,
-			mempool.ErrDuplicate,
-			mempool.ErrTooManyVotes,
-			mempool.ErrDuplicateRevocation,
-			mempool.ErrAlreadyExists,
-			mempool.ErrOrphan:
-
-			code = wire.RejectDuplicate
-
-		// Error codes which map to a non-standard transaction being
-		// relayed.
-		case mempool.ErrOrphanPolicyViolation,
-			mempool.ErrOldVote,
-			mempool.ErrSeqLockUnmet,
-			mempool.ErrNonStandard:
-
-			code = wire.RejectNonstandard
-
-		// Error codes which map to an insufficient fee being paid.
-		case mempool.ErrInsufficientFee,
-			mempool.ErrInsufficientPriority:
-
-			code = wire.RejectInsufficientFee
-
-		// Error codes which map to an attempt to create dust outputs.
-		case mempool.ErrDustOutput:
-			code = wire.RejectDust
-		}
-
-		reason = terr.Error()
+	// Rejected due to duplicate.
+	case errors.Is(err, blockchain.ErrDuplicateBlock):
+		code = wire.RejectDuplicate
+		reason = err.Error()
+
+	// Rejected due to obsolete version.
+	case errors.Is(err, blockchain.ErrBlockVersionTooOld):
+		code = wire.RejectObsolete
+		reason = err.Error()
+
+	// Rejected due to checkpoint.
+	case errors.Is(err, blockchain.ErrCheckpointTimeTooOld),
+		errors.Is(err, blockchain.ErrDifficultyTooLow),
+		errors.Is(err, blockchain.ErrBadCheckpoint),
+		errors.Is(err, blockchain.ErrForkTooOld):
+		code = wire.RejectCheckpoint
+		reason = err.Error()
+
+	// Error codes which map to a duplicate transaction already
+	// mined or in the mempool.
+	case errors.Is(err, mempool.ErrMempoolDoubleSpend),
+		errors.Is(err, mempool.ErrAlreadyVoted),
+		errors.Is(err, mempool.ErrDuplicate),
+		errors.Is(err, mempool.ErrTooManyVotes),
+		errors.Is(err, mempool.ErrDuplicateRevocation),
+		errors.Is(err, mempool.ErrAlreadyExists),
+		errors.Is(err, mempool.ErrOrphan):
+		code = wire.RejectDuplicate
+		reason = err.Error()
+
+	// Error codes which map to a non-standard transaction being
+	// relayed.
+	case errors.Is(err, mempool.ErrOrphanPolicyViolation),
+		errors.Is(err, mempool.ErrOldVote),
+		errors.Is(err, mempool.ErrSeqLockUnmet),
+		errors.Is(err, mempool.ErrNonStandard):
+		code = wire.RejectNonstandard
+		reason = err.Error()
+
+	// Error codes which map to an insufficient fee being paid.
+	case errors.Is(err, mempool.ErrInsufficientFee),
+		errors.Is(err, mempool.ErrInsufficientPriority):
+		code = wire.RejectInsufficientFee
+		reason = err.Error()
+
+	// Error codes which map to an attempt to create dust outputs.
+	case errors.Is(err, mempool.ErrDustOutput):
+		code = wire.RejectDust
+		reason = err.Error()
+
 	default:
 		reason = fmt.Sprintf("rejected: %v", err)
 	}
@@ -1005,7 +992,7 @@ func (b *blockManager) processBlockAndOrphans(block *dcrutil.Block, flags blockc
 	// returned indicates the block is an orphan.
 	blockHash := block.Hash()
 	forkLen, err := b.cfg.Chain.ProcessBlock(block, flags)
-	if blockchain.IsErrorCode(err, blockchain.ErrMissingParent) {
+	if errors.Is(err, blockchain.ErrMissingParent) {
 		bmgrLog.Infof("Adding orphan block %v with parent %v", blockHash,
 			block.MsgBlock().Header.PrevBlock)
 		b.addOrphanBlock(block)
@@ -1846,26 +1833,16 @@ func headerApprovesParent(header *wire.BlockHeader) bool {
 // is expected to have come from mempool, indicates a transaction was rejected
 // either due to containing a double spend or already existing in the pool.
 func isDoubleSpendOrDuplicateError(err error) bool {
-	var merr mempool.RuleError
-	if !errors.As(err, &merr) {
-		return false
-	}
-
-	var rerr mempool.TxRuleError
-	if errors.As(merr.Err, &rerr) {
-		switch rerr.ErrorCode {
-		case mempool.ErrDuplicate:
-			return true
-		case mempool.ErrAlreadyExists:
-			return true
-		default:
-			return false
-		}
+	switch {
+	case errors.Is(err, mempool.ErrDuplicate):
+		return true
+	case errors.Is(err, mempool.ErrAlreadyExists):
+		return true
+	case errors.Is(err, blockchain.ErrMissingTxOut):
+		return true
 	}
 
-	var cerr blockchain.RuleError
-	return errors.As(merr.Err, &cerr) &&
-		cerr.ErrorCode == blockchain.ErrMissingTxOut
+	return false
 }
 
 // handleBlockchainNotification handles notifications from blockchain.  It does
diff --git a/cmd/addblock/import.go b/cmd/addblock/import.go
index fa5c4cb5..74ac43ab 100644
--- a/cmd/addblock/import.go
+++ b/cmd/addblock/import.go
@@ -8,6 +8,7 @@ package main
 import (
 	"context"
 	"encoding/binary"
+	"errors"
 	"fmt"
 	"io"
 	"sync"
@@ -129,7 +130,7 @@ func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
 	// known checkpoints.
 	forkLen, err := bi.chain.ProcessBlock(block, blockchain.BFFastAdd)
 	if err != nil {
-		if blockchain.IsErrorCode(err, blockchain.ErrMissingParent) {
+		if errors.Is(err, blockchain.ErrMissingParent) {
 			return false, fmt.Errorf("import file contains an orphan block: %v",
 				blockHash)
 		}
diff --git a/internal/mempool/error.go b/internal/mempool/error.go
index 114df6a7..11186eab 100644
--- a/internal/mempool/error.go
+++ b/internal/mempool/error.go
@@ -12,116 +12,132 @@ import (
 	"github.com/decred/dcrd/blockchain/v3"
 )
 
-// RuleError identifies a rule violation.  It is used to indicate that
-// processing of a transaction failed due to one of the many validation
-// rules.  The caller can use type assertions to determine if a failure was
-// specifically due to a rule violation and use the Err field to access the
-// underlying error, which will be either a TxRuleError or a
-// blockchain.RuleError.
-type RuleError struct {
-	Err error
-}
+// ErrorKind identifies a kind of error.  It has full support for errors.Is and
+// errors.As, so the caller can directly check against an error kind when
+// determining the reason for an error.
+type ErrorKind string
 
-// Error satisfies the error interface and prints human-readable errors.
-func (e RuleError) Error() string {
-	if e.Err == nil {
-		return "<nil>"
-	}
-	return e.Err.Error()
-}
+const (
+	// ErrorInvalid indicates a mempool transaction is invalid per consensus.
+	ErrInvalid = ErrorKind("ErrInvalid")
 
-// ErrorCode identifies the kind of error.
-type ErrorCode int
+	// ErrorOrphanPolicyViolation indicates that an orphan block violates the
+	// prevailing orphan policy.
+	ErrOrphanPolicyViolation = ErrorKind("ErrOrphanPolicyViolation")
 
-const (
-	ErrOther ErrorCode = iota
-	ErrInvalid
-	ErrOrphanPolicyViolation
-	ErrMempoolDoubleSpend
-	ErrAlreadyVoted
-	ErrDuplicate
-	ErrCoinbase
-	ErrExpired
-	ErrNonStandard
-	ErrDustOutput
-	ErrInsufficientFee
-	ErrTooManyVotes
-	ErrDuplicateRevocation
-	ErrOldVote
-	ErrAlreadyExists
-	ErrSeqLockUnmet
-	ErrInsufficientPriority
-	ErrFeeTooHigh
-	ErrOrphan
-	ErrTooManyTSpends
-	ErrTSpendMinedOnAncestor
-	ErrTSpendInvalidExpiry
+	// ErrMempoolDoubleSpend indicates a transaction that attempts to to spend
+	// coins already spent by other transactions in the pool.
+	ErrMempoolDoubleSpend = ErrorKind("ErrMempoolDoubleSpend")
+
+	// ErrAlreadyVoted indicates a ticket already voted.
+	ErrAlreadyVoted = ErrorKind("ErrorAlreadyVoted")
+
+	// ErrDuplicate indicates a transaction already exists in the mempool.
+	ErrDuplicate = ErrorKind("ErrDuplicate")
+
+	// ErrCoinbase indicates a transaction is a standalone coinbase transaction.
+	ErrCoinbase = ErrorKind("ErrCoinbase")
+
+	// ErrExpired indicates a transaction will be expired as of the next block.
+	ErrExpired = ErrorKind("ErrExpired")
+
+	// ErrNonStandard indicates a non-standard transaction.
+	ErrNonStandard = ErrorKind("ErrNonStandard")
+
+	// ErrDustOutput indicates a transaction has one or more dust outputs.
+	ErrDustOutput = ErrorKind("ErrDustOutput")
+
+	// ErrInsufficientFee indicates a transaction cannot does not pay the minimum
+	// fee required by the active policy.
+	ErrInsufficientFee = ErrorKind("ErrInsufficientFee")
+
+	// ErrTooManyVotes indicates the number of vote double spends exceeds the
+	// maximum allowed.
+	ErrTooManyVotes = ErrorKind("ErrTooManyVotes")
+
+	// ErrDuplicateRevocation indicates a revocation already exists in the
+	// mempool.
+	ErrDuplicateRevocation = ErrorKind("ErrDuplicateRevocation")
+
+	// ErrOldVote indicates a ticket votes on a block height lower than
+	// the minimum allowed by the mempool.
+	ErrOldVote = ErrorKind("ErrOldVote")
+
+	// ErrAlreadyExists indicates a transaction already exists on the
+	// main chain and is not fully spent.
+	ErrAlreadyExists = ErrorKind("ErrAlreadyExists")
+
+	// ErrSeqLockUnmet indicates a transaction sequence locks are not active.
+	ErrSeqLockUnmet = ErrorKind("ErrSeqLockUnmet")
+
+	// ErrInsufficientPriority indicates a non-stake transaction has a
+	// priority lower than the minimum allowed.
+	ErrInsufficientPriority = ErrorKind("ErrInsufficientPriority")
+
+	// ErrFeeTooHigh indicates a transaction pays fees above the maximum
+	// allowed by the active policy.
+	ErrFeeTooHigh = ErrorKind("ErrFeeTooHigh")
+
+	// ErrOrphan indicates a transaction is an orphan.
+	ErrOrphan = ErrorKind("ErrOrphan")
+
+	// ErrTooManyTSpends indicates the number of treasury spend hashes exceeds
+	// the maximum allowed.
+	ErrTooManyTSpends = ErrorKind("ErrTooManyTSpends")
+
+	// ErrTSpendMinedOnAncestor indicates a referenced treasury spend was
+	// already mined on an ancestor block.
+	ErrTSpendMinedOnAncestor = ErrorKind("ErrTSpendMinedOnAncestor")
+
+	// ErrTSpendInvalidExpiry indicates a treasury spend expiry is invalid.
+	ErrTSpendInvalidExpiry = ErrorKind("ErrTSpendInvalidExpiry")
 )
 
-// TxRuleError identifies a rule violation.  It is used to indicate that
+// Error satisfies the error interface and prints human-readable errors.
+func (e ErrorKind) Error() string {
+	return string(e)
+}
+
+// RuleError identifies a rule violation.  It is used to indicate that
 // processing of a transaction failed due to one of the many validation
-// rules.  The caller can use type assertions to determine if a failure was
-// specifically due to a rule violation and access the ErrorCode field to
-// ascertain the specific reason for the rule violation.
-type TxRuleError struct {
-	// ErrorCode is the mempool package error code ID.
-	ErrorCode ErrorCode
-
-	// Description is an additional human readable description of the
-	// error.
+// rules.  It has full support for errors.Is and errors.As, so the caller
+// can ascertain the specific reason for the error by checking the
+// underlying error, which will be either an ErrorKind or blockchain.RuleError.
+type RuleError struct {
+	Err         error
 	Description string
 }
 
 // Error satisfies the error interface and prints human-readable errors.
-func (e TxRuleError) Error() string {
+func (e RuleError) Error() string {
 	return e.Description
 }
 
-// txRuleError creates an underlying TxRuleError with the given a set of
-// arguments and returns a RuleError that encapsulates it.
-func txRuleError(code ErrorCode, desc string) RuleError {
-	return RuleError{
-		Err: TxRuleError{ErrorCode: code, Description: desc},
-	}
+// Unwrap returns the underlying wrapped error.
+func (e RuleError) Unwrap() error {
+	return e.Err
 }
 
 // chainRuleError returns a RuleError that encapsulates the given
 // blockchain.RuleError.
 func chainRuleError(chainErr blockchain.RuleError) RuleError {
-	return RuleError{
-		Err: chainErr,
-	}
+	return RuleError{Err: chainErr, Description: chainErr.Description}
 }
 
-// IsErrorCode returns true if the passed error encodes a TxRuleError with the
-// given ErrorCode, either directly or embedded in an outer RuleError.
-func IsErrorCode(err error, code ErrorCode) bool {
-	// Unwrap RuleError if necessary.
-	var rerr RuleError
-	if errors.As(err, &rerr) {
-		err = rerr.Err
-	}
-
-	var trerr TxRuleError
-	return errors.As(err, &trerr) &&
-		trerr.ErrorCode == code
+// txRuleError creates a RuleError given a set of arguments.
+func txRuleError(kind ErrorKind, desc string) RuleError {
+	return RuleError{Err: kind, Description: desc}
 }
 
-// wrapTxRuleError returns a new RuleError with an underlying TxRuleError,
+// wrapTxRuleError returns a new RuleError with an underlying ErrorKind,
 // replacing the description with the provided one while retaining the error
-// code from the original error if it can be determined.
-func wrapTxRuleError(errorCode ErrorCode, desc string, err error) error {
-	// Unwrap the underlying error if err is a RuleError
-	var rerr RuleError
-	if errors.As(err, &rerr) {
-		err = rerr.Err
-	}
-
-	// Override the passed error code with the ones from the error if it is a
-	// TxRuleError.
-	var txerr TxRuleError
-	if errors.As(err, &txerr) {
-		errorCode = txerr.ErrorCode
+// kind from the original error if it can be determined.
+func wrapTxRuleError(kind ErrorKind, desc string, err error) error {
+	// Override the passed error kind with the one from the error if it is an
+	// ErrorKind.
+	var kerr ErrorKind
+	if errors.As(err, &kerr) {
+		kind = kerr
 	}
 
 	// Fill a default error description if empty.
@@ -129,5 +145,5 @@ func wrapTxRuleError(errorCode ErrorCode, desc string, err error) error {
 		desc = fmt.Sprintf("rejected: %v", err)
 	}
 
-	return txRuleError(errorCode, desc)
+	return txRuleError(kind, desc)
 }
diff --git a/internal/mining/cpuminer/cpuminer.go b/internal/mining/cpuminer/cpuminer.go
index 4bcc6cb3..9a251f7e 100644
--- a/internal/mining/cpuminer/cpuminer.go
+++ b/internal/mining/cpuminer/cpuminer.go
@@ -218,7 +218,7 @@ func (m *CPUMiner) submitBlock(block *dcrutil.Block) bool {
 		// debug since it is expected to happen from time to time and not really
 		// an error.
 		if m.cfg.ChainParams.ReduceMinDifficulty &&
-			rErr.ErrorCode == blockchain.ErrHighHash {
+			errors.Is(rErr, blockchain.ErrHighHash) {
 			log.Debugf("Block submitted via CPU miner rejected because of "+
 				"ReduceMinDifficulty time sync failure: %v", err)
 			return false
diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go
index 6d2b4806..a42f5c5a 100644
--- a/internal/rpcserver/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -4536,7 +4536,7 @@ func handleSendRawTransaction(_ context.Context, s *Server, cmd interface{}) (in
 			err = fmt.Errorf("rejected transaction %v: %v", tx.Hash(),
 				err)
 			log.Debugf("%v", err)
-			if mempool.IsErrorCode(rErr, mempool.ErrDuplicate) {
+			if errors.Is(rErr, mempool.ErrDuplicate) {
 				// This is an actual exact duplicate tx, so
 				// return the specific duplicate tx error.
 				return nil, rpcDuplicateTxError("%v", err)


Test patch:
diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go
index 3ac5300c..663f1105 100644
--- a/blockchain/chain_test.go
+++ b/blockchain/chain_test.go
@@ -157,8 +157,8 @@ func TestForceHeadReorg(t *testing.T) {
 	//
 	// rejectForceTipReorg forces the chain instance to reorganize the
 	// current tip of the main chain from the given block to the given
-	// block and expected it to be rejected with the provided error code.
-	rejectForceTipReorg := func(fromTipName, toTipName string, code ErrorCode) {
+	// block and expected it to be rejected with the provided error kind.
+	rejectForceTipReorg := func(fromTipName, toTipName string, kind ErrorKind) {
 		from := g.BlockByName(fromTipName)
 		to := g.BlockByName(toTipName)
 		t.Logf("Testing forced reorg from %s (hash %s, height %d) "+
@@ -175,25 +175,15 @@ func TestForceHeadReorg(t *testing.T) {
 				to.Header.Height)
 		}
 
-		// Ensure the error code is of the expected type and the reject
-		// code matches the value specified in the test instance.
-		var rerr RuleError
-		if !errors.As(err, &rerr) {
-			t.Fatalf("forced header reorg from block %q (hash %s, "+
-				"height %d) to block %q (hash %s, height %d) "+
-				"returned unexpected error type -- got %T, "+
-				"want blockchain.RuleError", fromTipName,
-				from.BlockHash(), from.Header.Height, toTipName,
-				to.BlockHash(), to.Header.Height, err)
-		}
-		if rerr.ErrorCode != code {
+		// Ensure the error kind is of the expected type and matches
+		// the value specified in the test instance.
+		if !errors.Is(err, kind) {
 			t.Fatalf("forced header reorg from block %q (hash %s, "+
 				"height %d) to block %q (hash %s, height %d) "+
 				"does not have expected reject code -- got %v, "+
 				"want %v", fromTipName,
 				from.BlockHash(), from.Header.Height, toTipName,
-				to.BlockHash(), to.Header.Height, rerr.ErrorCode,
-				code)
+				to.BlockHash(), to.Header.Height, err, kind)
 		}
 	}
 
diff --git a/blockchain/common_test.go b/blockchain/common_test.go
index e5fbc8a2..67843b78 100644
--- a/blockchain/common_test.go
+++ b/blockchain/common_test.go
@@ -532,8 +532,8 @@ func (g *chaingenHarness) AcceptTipBlock() {
 }
 
 // RejectBlock expects the block associated with the given name in the harness
-// generator to be rejected with the provided error code.
-func (g *chaingenHarness) RejectBlock(blockName string, code ErrorCode) {
+// generator to be rejected with the provided error kind.
+func (g *chaingenHarness) RejectBlock(blockName string, kind ErrorKind) {
 	g.t.Helper()
 
 	msgBlock := g.BlockByName(blockName)
@@ -548,27 +548,21 @@ func (g *chaingenHarness) RejectBlock(blockName string, code ErrorCode) {
 			blockName, block.Hash(), blockHeight)
 	}
 
-	// Ensure the error code is of the expected type and the reject code matches
+	// Ensure the error kind is of the expected type and matches
 	// the value specified in the test instance.
-	var rerr RuleError
-	if !errors.As(err, &rerr) {
-		g.t.Fatalf("block %q (hash %s, height %d) returned unexpected error "+
-			"type -- got %T, want blockchain.RuleError", blockName,
-			block.Hash(), blockHeight, err)
-	}
-	if rerr.ErrorCode != code {
+	if !errors.Is(err, kind) {
 		g.t.Fatalf("block %q (hash %s, height %d) does not have expected reject "+
 			"code -- got %v, want %v", blockName, block.Hash(), blockHeight,
-			rerr.ErrorCode, code)
+			err, kind)
 	}
 }
 
 // RejectTipBlock expects the current tip block associated with the harness
 // generator to be rejected with the provided error code.
-func (g *chaingenHarness) RejectTipBlock(code ErrorCode) {
+func (g *chaingenHarness) RejectTipBlock(kind ErrorKind) {
 	g.t.Helper()
 
-	g.RejectBlock(g.TipName(), code)
+	g.RejectBlock(g.TipName(), kind)
 }
 
 // ExpectTip expects the provided block to be the current tip of the main chain
diff --git a/blockchain/error_test.go b/blockchain/error_test.go
index c9b6993e..8100da2a 100644
--- a/blockchain/error_test.go
+++ b/blockchain/error_test.go
@@ -1,18 +1,20 @@
 // Copyright (c) 2014 The btcsuite developers
-// Copyright (c) 2015-2018 The Decred developers
+// Copyright (c) 2015-2020 The Decred developers
 // Use of this source code is governed by an ISC
 // license that can be found in the LICENSE file.
 
 package blockchain
 
 import (
+	"errors"
+	"io"
 	"testing"
 )
 
-// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
-func TestErrorCodeStringer(t *testing.T) {
+// TestErrorKindStringer tests the stringized output for the ErrorKind type.
+func TestErrorKindStringer(t *testing.T) {
 	tests := []struct {
-		in   ErrorCode
+		in   ErrorKind
 		want string
 	}{
 		{ErrDuplicateBlock, "ErrDuplicateBlock"},
@@ -145,20 +147,13 @@ func TestErrorCodeStringer(t *testing.T) {
 		{ErrInvalidTAddChange, "ErrInvalidTAddChange"},
 		{ErrTooManyTAdds, "ErrTooManyTAdds"},
 		{ErrTicketExhaustion, "ErrTicketExhaustion"},
-		{0xffff, "Unknown ErrorCode (65535)"},
-	}
-
-	// Detect additional error codes that don't have the stringer added.
-	if len(tests)-1 != int(numErrorCodes) {
-		t.Errorf("It appears an error code was added without adding an " +
-			"associated stringer test")
 	}
 
 	t.Logf("Running %d tests", len(tests))
 	for i, test := range tests {
-		result := test.in.String()
+		result := test.in.Error()
 		if result != test.want {
-			t.Errorf("String #%d\n got: %s want: %s", i, result,
+			t.Errorf("#%d: got: %s want: %s", i, result,
 				test.want)
 			continue
 		}
@@ -185,51 +180,91 @@ func TestRuleError(t *testing.T) {
 	for i, test := range tests {
 		result := test.in.Error()
 		if result != test.want {
-			t.Errorf("Error #%d\n got: %s want: %s", i, result,
+			t.Errorf("Error #%d: got: %s want: %s", i, result,
 				test.want)
 			continue
 		}
 	}
 }
 
-// TestIsErrorCode ensures IsErrorCode works as intended.
-func TestIsErrorCode(t *testing.T) {
+// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being
+// a specific error kind via errors.Is and unwrapped via errors.As.
+func TestErrorKindIsAs(t *testing.T) {
 	tests := []struct {
-		name string
-		err  error
-		code ErrorCode
-		want bool
+		name      string
+		err       error
+		target    error
+		wantMatch bool
+		wantAs    ErrorKind
 	}{{
-		name: "ErrUnexpectedDifficulty testing for ErrUnexpectedDifficulty",
-		err:  ruleError(ErrUnexpectedDifficulty, ""),
-		code: ErrUnexpectedDifficulty,
-		want: true,
+		name:      "ErrDuplicateBlock == ErrDuplicateBlock",
+		err:       ErrDuplicateBlock,
+		target:    ErrDuplicateBlock,
+		wantMatch: true,
+		wantAs:    ErrDuplicateBlock,
+	}, {
+		name:      "RuleError.ErrDuplicateBlock == ErrDuplicateBlock",
+		err:       ruleError(ErrDuplicateBlock, ""),
+		target:    ErrDuplicateBlock,
+		wantMatch: true,
+		wantAs:    ErrDuplicateBlock,
 	}, {
-		name: "ErrHighHash testing for ErrHighHash",
-		err:  ruleError(ErrHighHash, ""),
-		code: ErrHighHash,
-		want: true,
+		name:      "RuleError.ErrDuplicateBlock == RuleError.ErrDuplicateBlock",
+		err:       ruleError(ErrDuplicateBlock, ""),
+		target:    ruleError(ErrDuplicateBlock, ""),
+		wantMatch: true,
+		wantAs:    ErrDuplicateBlock,
 	}, {
-		name: "ErrHighHash error testing for ErrUnexpectedDifficulty",
-		err:  ruleError(ErrHighHash, ""),
-		code: ErrUnexpectedDifficulty,
-		want: false,
+		name:      "ErrDuplicateBlock != ErrMissingParent",
+		err:       ErrDuplicateBlock,
+		target:    ErrMissingParent,
+		wantMatch: false,
+		wantAs:    ErrDuplicateBlock,
 	}, {
-		name: "ErrHighHash error testing for unknown error code",
-		err:  ruleError(ErrHighHash, ""),
-		code: 0xffff,
-		want: false,
+		name:      "RuleError.ErrDuplicateBlock != ErrMissingParent",
+		err:       ruleError(ErrDuplicateBlock, ""),
+		target:    ErrMissingParent,
+		wantMatch: false,
+		wantAs:    ErrDuplicateBlock,
 	}, {
-		name: "nil error testing for ErrUnexpectedDifficulty",
-		err:  nil,
-		code: ErrUnexpectedDifficulty,
-		want: false,
+		name:      "ErrDuplicateBlock != RuleError.ErrMissingParent",
+		err:       ErrDuplicateBlock,
+		target:    ruleError(ErrMissingParent, ""),
+		wantMatch: false,
+		wantAs:    ErrDuplicateBlock,
+	}, {
+		name:      "RuleError.ErrDuplicateBlock != RuleError.ErrMissingParent",
+		err:       ruleError(ErrDuplicateBlock, ""),
+		target:    ruleError(ErrMissingParent, ""),
+		wantMatch: false,
+		wantAs:    ErrDuplicateBlock,
+	}, {
+		name:      "RuleError.ErrDuplicateBlock != io.EOF",
+		err:       ruleError(ErrDuplicateBlock, ""),
+		target:    io.EOF,
+		wantMatch: false,
+		wantAs:    ErrDuplicateBlock,
 	}}
+
 	for _, test := range tests {
-		result := IsErrorCode(test.err, test.code)
-		if result != test.want {
-			t.Errorf("%s: unexpected result -- got: %v want: %v", test.name,
-				result, test.want)
+		// Ensure the error matches or not depending on the expected result.
+		result := errors.Is(test.err, test.target)
+		if result != test.wantMatch {
+			t.Errorf("%s: incorrect error identification -- got %v, want %v",
+				test.name, result, test.wantMatch)
+			continue
+		}
+
+		// Ensure the underlying error kind can be unwrapped is and is the
+		// expected kind.
+		var kind ErrorKind
+		if !errors.As(test.err, &kind) {
+			t.Errorf("%s: unable to unwrap to error kind", test.name)
+			continue
+		}
+		if kind != test.wantAs {
+			t.Errorf("%s: unexpected unwrapped error kind -- got %v, want %v",
+				test.name, kind, test.wantAs)
 			continue
 		}
 	}
diff --git a/blockchain/fullblocks_test.go b/blockchain/fullblocks_test.go
index 5a422a13..18890971 100644
--- a/blockchain/fullblocks_test.go
+++ b/blockchain/fullblocks_test.go
@@ -149,7 +149,7 @@ func TestFullBlocks(t *testing.T) {
 
 		var isOrphan bool
 		forkLen, err := chain.ProcessBlock(block, blockchain.BFNone)
-		if blockchain.IsErrorCode(err, blockchain.ErrMissingParent) {
+		if errors.Is(err, blockchain.ErrMissingParent) {
 			isOrphan = true
 			err = nil
 		}
@@ -194,18 +194,10 @@ func TestFullBlocks(t *testing.T) {
 
 		// Ensure the error code is of the expected type and the reject
 		// code matches the value specified in the test instance.
-		var rerr blockchain.RuleError
-		if !errors.As(err, &rerr) {
-			t.Fatalf("block %q (hash %s, height %d) returned "+
-				"unexpected error type -- got %T, want "+
-				"blockchain.RuleError", item.Name, block.Hash(),
-				blockHeight, err)
-		}
-		if rerr.ErrorCode != item.RejectCode {
+		if !errors.Is(err, item.RejectKind) {
 			t.Fatalf("block %q (hash %s, height %d) does not have "+
 				"expected reject code -- got %v, want %v",
-				item.Name, block.Hash(), blockHeight,
-				rerr.ErrorCode, item.RejectCode)
+				item.Name, block.Hash(), blockHeight, err, item.RejectKind)
 		}
 	}
 
diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go
index 1f50cfb6..88ad3819 100644
--- a/blockchain/fullblocktests/generate.go
+++ b/blockchain/fullblocktests/generate.go
@@ -93,7 +93,7 @@ func (b AcceptedBlock) FullBlockTestInstance() {}
 type RejectedBlock struct {
 	Name       string
 	Block      *wire.MsgBlock
-	RejectCode blockchain.ErrorCode
+	RejectKind blockchain.ErrorKind
 }
 
 // Ensure RejectedBlock implements the TestInstance interface.
@@ -402,8 +402,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
 	acceptBlock := func(blockName string, block *wire.MsgBlock, isMainChain, isOrphan bool) TestInstance {
 		return AcceptedBlock{blockName, block, isMainChain, isOrphan}
 	}
-	rejectBlock := func(blockName string, block *wire.MsgBlock, code blockchain.ErrorCode) TestInstance {
-		return RejectedBlock{blockName, block, code}
+	rejectBlock := func(blockName string, block *wire.MsgBlock, kind blockchain.ErrorKind) TestInstance {
+		return RejectedBlock{blockName, block, kind}
 	}
 	rejectNonCanonicalBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
 		blockHeight := int32(block.Header.Height)
@@ -449,9 +449,9 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
 			expectTipBlock(tipName, g.BlockByName(tipName)),
 		})
 	}
-	rejected := func(code blockchain.ErrorCode) {
+	rejected := func(kind blockchain.ErrorKind) {
 		tests = append(tests, []TestInstance{
-			rejectBlock(g.TipName(), g.Tip(), code),
+			rejectBlock(g.TipName(), g.Tip(), kind),
 		})
 	}
 	rejectedNonCanonical := func() {
diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go
index 90fb6f44..9bc26f8c 100644
--- a/blockchain/validate_test.go
+++ b/blockchain/validate_test.go
@@ -602,15 +602,10 @@ func TestTxValidationErrors(t *testing.T) {
 
 	// Ensure transaction is rejected due to being too large.
 	err := CheckTransactionSanity(tx, chaincfg.MainNetParams(), noTreasury)
-	var rerr RuleError
-	if !errors.As(err, &rerr) {
-		t.Fatalf("CheckTransactionSanity: unexpected error type for "+
-			"transaction that is too large -- got %T", err)
-	}
-	if rerr.ErrorCode != ErrTxTooBig {
+	if !errors.Is(err, ErrTxTooBig) {
 		t.Fatalf("CheckTransactionSanity: unexpected error code for "+
 			"transaction that is too large -- got %v, want %v",
-			rerr.ErrorCode, ErrTxTooBig)
+			err, ErrTxTooBig)
 	}
 }
 
@@ -667,7 +662,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
 				block.Hash(), blockHeight, err)
 		}
 	}
-	rejectedBlockTemplate := func(code ErrorCode) {
+	rejectedBlockTemplate := func(kind ErrorKind) {
 		msgBlock := g.Tip()
 		blockHeight := msgBlock.Header.Height
 		block := dcrutil.NewBlock(msgBlock)
@@ -683,18 +678,10 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
 
 		// Ensure the error code is of the expected type and the reject
 		// code matches the value specified in the test instance.
-		var rerr RuleError
-		if !errors.As(err, &rerr) {
-			t.Fatalf("block template %q (hash %s, height %d) "+
-				"returned unexpected error type -- got %T, want "+
-				"blockchain.RuleError", g.TipName(),
-				block.Hash(), blockHeight, err)
-		}
-		if rerr.ErrorCode != code {
+		if !errors.Is(err, kind) {
 			t.Fatalf("block template %q (hash %s, height %d) does "+
 				"not have expected reject code -- got %v, want %v",
-				g.TipName(), block.Hash(), blockHeight,
-				rerr.ErrorCode, code)
+				g.TipName(), block.Hash(), blockHeight, err, kind)
 		}
 	}
 
@@ -1054,16 +1041,6 @@ func TestCheckTicketExhaustion(t *testing.T) {
 	params.StakeEnabledHeight = stakeEnabledHeight
 	params.StakeValidationHeight = stakeValidationHeight
 
-	// isErr is a convenience func which acts as a limited version of errors.Is
-	// until the package is converted to support it at which point this can be
-	// removed.
-	isErr := func(err error, target error) bool {
-		if (err == nil) != (target == nil) {
-			return false
-		}
-		return target == nil || IsErrorCode(err, target.(RuleError).ErrorCode)
-	}
-
 	// ticketInfo is used to control the tests by specifying the details about
 	// how many fake blocks to create with the specified number of tickets.
 	type ticketInfo struct {
@@ -1085,7 +1062,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 			{126, 0}, // height: 126, 0 live, 0 immature
 		},
 		newBlockTix: 0, // extending height: 126, 0 live, 0 immature
-		err:         ruleError(ErrTicketExhaustion, ""),
+		err:         ErrTicketExhaustion,
 	}, {
 		// Reach inevitable ticket exhaustion by not including any ticket
 		// purchases up to just before the final possible block prior to svh
@@ -1096,7 +1073,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 			{126, 0}, // height: 126, 0 live, 0 immature
 		},
 		newBlockTix: 4, // extending height: 126, 0 live, 4 immature
-		err:         ruleError(ErrTicketExhaustion, ""),
+		err:         ErrTicketExhaustion,
 	}, {
 		// Construct chain such that there are no ticket purchases up to just
 		// before the final possible block prior to svh that can prevent ticket
@@ -1119,7 +1096,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 			{109, 0}, // height: 126, 4 live, 0 immature
 		},
 		newBlockTix: 0, // extending height: 126, 4 live, 0 immature
-		err:         ruleError(ErrTicketExhaustion, ""),
+		err:         ErrTicketExhaustion,
 	}, {
 		name: "just enough live tickets at 1st possible exhaustion",
 		ticketInfo: []ticketInfo{
@@ -1140,7 +1117,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 			{110, 0}, // height: 127, 5 live, 0 immature
 		},
 		newBlockTix: 0, // extending height: 127, 5 live, 0 immature
-		err:         ruleError(ErrTicketExhaustion, ""),
+		err:         ErrTicketExhaustion,
 	}, {
 		// Reach inevitable ticket exhaustion in the second possible block that
 		// it can happen with one live ticket less than needed to prevent it.
@@ -1151,7 +1128,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 			{110, 0}, // height: 127, 9 live, 0 immature
 		},
 		newBlockTix: 0, // extending height: 127, 9 live, 0 immature
-		err:         ruleError(ErrTicketExhaustion, ""),
+		err:         ErrTicketExhaustion,
 	}, {
 		// Construct chain to one block before svh such that there are exactly
 		// enough live tickets to prevent exhaustion.
@@ -1212,7 +1189,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 			{75, 0},  // height: 226, 85 live, 0 immature
 		},
 		newBlockTix: 0, // extending height: 226, 85 live, 0 immature
-		err:         ruleError(ErrTicketExhaustion, ""),
+		err:         ErrTicketExhaustion,
 	}}
 
 	for _, test := range tests {
@@ -1261,7 +1238,7 @@ func TestCheckTicketExhaustion(t *testing.T) {
 
 		// Ensure the expected result is returned from ticket exhaustion check.
 		err := bc.CheckTicketExhaustion(&node.hash, test.newBlockTix)
-		if !isErr(err, test.err) {
+		if !errors.Is(err, test.err) {
 			t.Errorf("%q: mismatched err -- got %v, want %v", test.name, err,
 				test.err)
 			continue
diff --git a/internal/mempool/error_test.go b/internal/mempool/error_test.go
new file mode 100644
index 00000000..c4282042
--- /dev/null
+++ b/internal/mempool/error_test.go
@@ -0,0 +1,160 @@
+// 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 mempool
+
+import (
+	"errors"
+	"io"
+	"testing"
+)
+
+// TestErrorKindStringer tests the stringized output for the ErrorKind type.
+func TestErrorKindStringer(t *testing.T) {
+	tests := []struct {
+		in   ErrorKind
+		want string
+	}{
+		{ErrInvalid, "ErrInvalid"},
+		{ErrOrphanPolicyViolation, "ErrOrphanPolicyViolation"},
+		{ErrMempoolDoubleSpend, "ErrMempoolDoubleSpend"},
+		{ErrAlreadyVoted, "ErrorAlreadyVoted"},
+		{ErrDuplicate, "ErrDuplicate"},
+		{ErrCoinbase, "ErrCoinbase"},
+		{ErrExpired, "ErrExpired"},
+		{ErrNonStandard, "ErrNonStandard"},
+		{ErrDustOutput, "ErrDustOutput"},
+		{ErrInsufficientFee, "ErrInsufficientFee"},
+		{ErrTooManyVotes, "ErrTooManyVotes"},
+		{ErrDuplicateRevocation, "ErrDuplicateRevocation"},
+		{ErrOldVote, "ErrOldVote"},
+		{ErrAlreadyExists, "ErrAlreadyExists"},
+		{ErrSeqLockUnmet, "ErrSeqLockUnmet"},
+		{ErrInsufficientPriority, "ErrInsufficientPriority"},
+		{ErrFeeTooHigh, "ErrFeeTooHigh"},
+		{ErrOrphan, "ErrOrphan"},
+		{ErrTooManyTSpends, "ErrTooManyTSpends"},
+		{ErrTSpendMinedOnAncestor, "ErrTSpendMinedOnAncestor"},
+		{ErrTSpendInvalidExpiry, "ErrTSpendInvalidExpiry"},
+	}
+
+	t.Logf("Running %d tests", len(tests))
+	for i, test := range tests {
+		result := test.in.Error()
+		if result != test.want {
+			t.Errorf("#%d\n got: %s want: %s", i, result,
+				test.want)
+			continue
+		}
+	}
+}
+
+// TestRuleError tests the error output for the RuleError type.
+func TestRuleError(t *testing.T) {
+	tests := []struct {
+		in   RuleError
+		want string
+	}{
+		{
+			RuleError{Description: "duplicate block"},
+			"duplicate block",
+		},
+		{
+			RuleError{Description: "human-readable error"},
+			"human-readable error",
+		},
+	}
+
+	t.Logf("Running %d tests", len(tests))
+	for i, test := range tests {
+		result := test.in.Error()
+		if result != test.want {
+			t.Errorf("#%d\n got: %s want: %s", i, result, test.want)
+			continue
+		}
+	}
+}
+
+// TestErrorKindIsAs ensures both ErrorKind and Error can be identified as being
+// a specific error kind via errors.Is and unwrapped via errors.As.
+func TestErrorKindIsAs(t *testing.T) {
+	tests := []struct {
+		name      string
+		err       error
+		target    error
+		wantMatch bool
+		wantAs    ErrorKind
+	}{{
+		name:      "ErrOrphanPolicyViolation == ErrOrphanPolicyViolation",
+		err:       ErrOrphanPolicyViolation,
+		target:    ErrOrphanPolicyViolation,
+		wantMatch: true,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "RuleError.ErrOrphanPolicyViolation == ErrOrphanPolicyViolation",
+		err:       txRuleError(ErrOrphanPolicyViolation, ""),
+		target:    ErrOrphanPolicyViolation,
+		wantMatch: true,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "RuleError.ErrOrphanPolicyViolation == RuleError.ErrOrphanPolicyViolation",
+		err:       txRuleError(ErrOrphanPolicyViolation, ""),
+		target:    txRuleError(ErrOrphanPolicyViolation, ""),
+		wantMatch: true,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "ErrOrphanPolicyViolation != ErrMempoolDoubleSpend",
+		err:       ErrOrphanPolicyViolation,
+		target:    ErrMempoolDoubleSpend,
+		wantMatch: false,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "RuleError.ErrOrphanPolicyViolation != ErrMempoolDoubleSpend",
+		err:       txRuleError(ErrOrphanPolicyViolation, ""),
+		target:    ErrMempoolDoubleSpend,
+		wantMatch: false,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "ErrOrphanPolicyViolation != RuleError.ErrMempoolDoubleSpend",
+		err:       ErrOrphanPolicyViolation,
+		target:    txRuleError(ErrMempoolDoubleSpend, ""),
+		wantMatch: false,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "RuleError.ErrOrphanPolicyViolation != RuleError.ErrMempoolDoubleSpend",
+		err:       txRuleError(ErrOrphanPolicyViolation, ""),
+		target:    txRuleError(ErrMempoolDoubleSpend, ""),
+		wantMatch: false,
+		wantAs:    ErrOrphanPolicyViolation,
+	}, {
+		name:      "RuleError.ErrOrphanPolicyViolation != io.EOF",
+		err:       txRuleError(ErrOrphanPolicyViolation, ""),
+		target:    io.EOF,
+		wantMatch: false,
+		wantAs:    ErrOrphanPolicyViolation,
+	}}
+
+	for _, test := range tests {
+		// Ensure the error matches or not depending on the expected result.
+		result := errors.Is(test.err, test.target)
+		if result != test.wantMatch {
+			t.Errorf("%s: incorrect error identification -- got %v, want %v",
+				test.name, result, test.wantMatch)
+			continue
+		}
+
+		// Ensure the underlying error kind can be unwrapped is and is the
+		// expected kind.
+		var kind ErrorKind
+		if !errors.As(test.err, &kind) {
+			t.Errorf("%s: unable to unwrap to error kind", test.name)
+			continue
+		}
+		if kind != test.wantAs {
+			t.Errorf("%s: unexpected unwrapped error kind -- got %v, want %v",
+				test.name, kind, test.wantAs)
+			continue
+		}
+	}
+}
diff --git a/internal/mempool/mempool_test.go b/internal/mempool/mempool_test.go
index 3d0e83d3..67f49c16 100644
--- a/internal/mempool/mempool_test.go
+++ b/internal/mempool/mempool_test.go
@@ -200,7 +200,7 @@ func (s *fakeChain) CalcSequenceLock(tx *dcrutil.Tx, view *blockchain.UtxoViewpo
 				"either does not exist or has already been spent",
 				txIn.PreviousOutPoint, tx.Hash(), txInIndex)
 			return nil, blockchain.RuleError{
-				ErrorCode:   blockchain.ErrMissingTxOut,
+				Err:         blockchain.ErrMissingTxOut,
 				Description: str,
 			}
 		}
@@ -1065,7 +1065,7 @@ func TestVoteOrphan(t *testing.T) {
 
 	// Ensure the vote is rejected because it is an orphan.
 	_, err = harness.txPool.ProcessTransaction(vote, false, false, true, 0)
-	if !IsErrorCode(err, ErrOrphan) {
+	if !errors.Is(err, ErrOrphan) {
 		t.Fatalf("Process Transaction: did not get expected ErrOrphan")
 	}
 	testPoolMembership(tc, vote, false, false)
@@ -1136,7 +1136,7 @@ func TestRevocationOrphan(t *testing.T) {
 	// Ensure the vote is rejected because it is an orphan.
 	_, err = harness.txPool.ProcessTransaction(revocation, false, false, true,
 		0)
-	if !IsErrorCode(err, ErrOrphan) {
+	if !errors.Is(err, ErrOrphan) {
 		t.Fatalf("Process Transaction: did not get expected " +
 			"ErrTooManyVotes error code")
 	}
@@ -1201,7 +1201,7 @@ func TestOrphanReject(t *testing.T) {
 			t.Fatalf("ProcessTransaction: did not fail on orphan "+
 				"%v when allow orphans flag is false", tx.Hash())
 		}
-		if !IsErrorCode(err, ErrOrphan) {
+		if !errors.Is(err, ErrOrphan) {
 			t.Fatalf("ProcessTransaction: unexpected error -- got %v, want %v",
 				err, ErrOrphan)
 		}
@@ -1860,7 +1860,7 @@ func TestSequenceLockAcceptance(t *testing.T) {
 			case acceptSeqLocks && !test.valid && err == nil:
 				t.Fatalf("%s: did not reject tx", test.name)
 
-			case acceptSeqLocks && !test.valid && !IsErrorCode(err, ErrSeqLockUnmet):
+			case acceptSeqLocks && !test.valid && !errors.Is(err, ErrSeqLockUnmet):
 				t.Fatalf("%s: did not get expected ErrSeqLockUnmet",
 					test.name)
 			}
@@ -1977,7 +1977,7 @@ func TestMaxVoteDoubleSpendRejection(t *testing.T) {
 			t.Fatalf("ProcessTransaction: accepted double-spending vote with " +
 				"more than max allowed")
 		}
-		if !IsErrorCode(err, ErrTooManyVotes) {
+		if !errors.Is(err, ErrTooManyVotes) {
 			t.Fatalf("Process Transaction: did not get expected " +
 				"ErrTooManyVotes error code")
 		}
@@ -2015,7 +2015,7 @@ func TestMaxVoteDoubleSpendRejection(t *testing.T) {
 	// in the transaction pool, and not reported as available.
 	vote = votes[maxVoteDoubleSpends+1]
 	_, err = harness.txPool.ProcessTransaction(vote, false, false, true, 0)
-	if !IsErrorCode(err, ErrTooManyVotes) {
+	if !errors.Is(err, ErrTooManyVotes) {
 		t.Fatalf("Process Transaction: did not get expected " +
 			"ErrTooManyVotes error code")
 	}
@@ -2088,7 +2088,7 @@ func TestDuplicateVoteRejection(t *testing.T) {
 	// ensure it is not in the orphan pool, not in the transaction pool, and not
 	// reported as available.
 	_, err = harness.txPool.ProcessTransaction(dupVote, false, false, true, 0)
-	if !IsErrorCode(err, ErrAlreadyVoted) {
+	if !errors.Is(err, ErrAlreadyVoted) {
 		t.Fatalf("Process Transaction: did not get expected " +
 			"ErrTooManyVotes error code")
 	}
@@ -2138,7 +2138,7 @@ func TestDuplicateTxError(t *testing.T) {
 	// Ensure a second attempt to process the tx is rejected with the
 	// correct error code and that the transaction remains in the pool.
 	_, err = harness.txPool.ProcessTransaction(tx, true, false, true, 0)
-	if !IsErrorCode(err, ErrDuplicate) {
+	if !errors.Is(err, ErrDuplicate) {
 		t.Fatalf("ProcessTransaction: did get the expected ErrDuplicate")
 	}
 	testPoolMembership(tc, tx, false, true)
@@ -2162,7 +2162,7 @@ func TestDuplicateTxError(t *testing.T) {
 
 	// The second call should fail with the expected ErrDuplicate error.
 	_, err = harness.txPool.ProcessTransaction(orphan, true, false, true, 0)
-	if !IsErrorCode(err, ErrDuplicate) {
+	if !errors.Is(err, ErrDuplicate) {
 		t.Fatalf("ProcessTransaction: did not get expected ErrDuplicate")
 	}
 	testPoolMembership(tc, orphan, true, false)
@@ -2207,7 +2207,7 @@ func TestMempoolDoubleSpend(t *testing.T) {
 	// pool and the double spend is not added to the pool.
 	_, err = harness.txPool.ProcessTransaction(doubleSpendTx, true, false, true,
 		0)
-	if !IsErrorCode(err, ErrMempoolDoubleSpend) {
+	if !errors.Is(err, ErrMempoolDoubleSpend) {
 		t.Fatalf("ProcessTransaction: did not get expected ErrMempoolDoubleSpend")
 	}
 	testPoolMembership(tc, tx, false, true)
@@ -2451,13 +2451,13 @@ func TestHandlesTSpends(t *testing.T) {
 
 	// Helper that attempts to add and asserts the given tspend is rejected
 	// with the given error code.
-	rejectTSpend := func(tx *dcrutil.Tx, errCode ErrorCode) {
+	rejectTSpend := func(tx *dcrutil.Tx, kind ErrorKind) {
 		t.Helper()
 		_, err = harness.txPool.ProcessTransaction(tx, true,
 			false, true, 0)
-		if !IsErrorCode(err, errCode) {
+		if !errors.Is(err, kind) {
 			t.Fatalf("Unexpected error while processing rejected tspend. "+
-				"want=%v got=%#v", errCode, err)
+				"want=%v got=%#v", kind, err)
 		}
 		testPoolMembership(tc, tx, false, false)
 	}
@@ -2467,17 +2467,13 @@ func TestHandlesTSpends(t *testing.T) {
 	//
 	// TODO: unify with the above after mempool errors correctly handle
 	// errors.Is/As.
-	rejectTSpendChainError := func(tx *dcrutil.Tx, errCode blockchain.ErrorCode) {
+	rejectTSpendChainError := func(tx *dcrutil.Tx, kind blockchain.ErrorKind) {
 		t.Helper()
 		_, err = harness.txPool.ProcessTransaction(tx, true,
 			false, true, 0)
-		rerr, ok := err.(RuleError)
-		if !ok {
-			t.Fatalf("Returned error is not a rule error: %#v", err)
-		}
-		if !blockchain.IsErrorCode(rerr.Err, errCode) {
+		if !errors.Is(err, kind) {
 			t.Fatalf("Unexpected error while processing rejected tspend. "+
-				"want=%v got=%#v", errCode, err)
+				"want=%v got=%#v", kind, err)
 		}
 		testPoolMembership(tc, tx, false, false)
 	}
diff --git a/internal/mempool/policy_test.go b/internal/mempool/policy_test.go
index 7a853248..32a3d187 100644
--- a/internal/mempool/policy_test.go
+++ b/internal/mempool/policy_test.go
@@ -7,6 +7,7 @@ package mempool
 
 import (
 	"bytes"
+	"errors"
 	"testing"
 	"time"
 
@@ -345,7 +346,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 		tx         wire.MsgTx
 		height     int64
 		isStandard bool
-		code       ErrorCode
+		err        error
 	}{
 		{
 			name: "Typical pay-to-pubkey-hash transaction",
@@ -370,7 +371,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Transaction version too high",
@@ -383,7 +384,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Transaction is not finalized",
@@ -400,7 +401,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Transaction size is too large",
@@ -417,7 +418,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Signature script size is too large",
@@ -435,7 +436,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Signature script that does more than push data",
@@ -453,7 +454,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Valid but non standard public key script",
@@ -469,7 +470,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "More than four nulldata outputs",
@@ -497,7 +498,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrNonStandard,
+			err:        ErrNonStandard,
 		},
 		{
 			name: "Dust output",
@@ -513,7 +514,7 @@ func TestCheckTransactionStandard(t *testing.T) {
 			},
 			height:     300000,
 			isStandard: false,
-			code:       ErrDustOutput,
+			err:        ErrDustOutput,
 		},
 		{
 			name: "One nulldata output with 0 amount (standard)",
@@ -556,9 +557,9 @@ func TestCheckTransactionStandard(t *testing.T) {
 		}
 
 		// Ensure the error code is the expected one.
-		if !IsErrorCode(err, test.code) {
+		if !errors.Is(err, test.err) {
 			t.Errorf("checkTransactionStandard (%s): unexpected error -- got "+
-				"%v, want %v", test.name, err, test.code)
+				"%v, want %v", test.name, err, test.err)
 			continue
 		}
 	}
diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
index 10e4e382..a369c8e2 100644
--- a/internal/rpcserver/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -5693,7 +5693,7 @@ func TestHandleGetWork(t *testing.T) {
 		mockSyncManager: func() *testSyncManager {
 			syncManager := defaultMockSyncManager()
 			syncManager.submitBlockErr = blockchain.RuleError{
-				ErrorCode:   blockchain.ErrDuplicateBlock,
+				Err:         blockchain.ErrDuplicateBlock,
 				Description: "Duplicate Block",
 			}
 			return syncManager