Repository: decred/dcrd
Pull request #2532 (2020-12-28 11:09:10)

chaincfg: Add extra seeders.
Adds additional seeders for both mainnet and testnet. This will help to
reduce dependency on the decred.org domain, and gives users some
alternatives if they don't want to use the decred.org seeders for whatever
reason.

FYI: @raedah @chappjc 


Patch:
diff --git a/blockchain/validate.go b/blockchain/validate.go
index 5b43e16c..fa979c24 100644
--- a/blockchain/validate.go
+++ b/blockchain/validate.go
@@ -100,6 +100,19 @@ const (
 	checkForDuplicateHashes = false
 )
 
+// mustParseHash converts the passed big-endian hex string into a
+// chainhash.Hash and will panic if there is an error.  It only differs from the
+// one available in chainhash in that it will panic so errors in the source code
+// be detected.  It will only (and must only) be called with hard-coded, and
+// therefore known good, hashes.
+func mustParseHash(s string) *chainhash.Hash {
+	hash, err := chainhash.NewHashFromStr(s)
+	if err != nil {
+		panic("invalid hash in source file: " + s)
+	}
+	return hash
+}
+
 var (
 	// zeroHash is the zero value for a chainhash.Hash and is defined as a
 	// package level variable to avoid the need to create a new instance
@@ -109,6 +122,17 @@ var (
 	// earlyFinalState is the only value of the final state allowed in a
 	// block header before stake validation height.
 	earlyFinalState = [6]byte{0x00}
+
+	// The following blocks violate DCP0005 as they were submitted with an old
+	// version after the majority of the network had upgraded.  See
+	// isDCP0005Violation for more details.  They are defined as package level
+	// variables to avoid the need to create new instances every time a check is
+	// needed.
+	block413762Hash = mustParseHash("00000000000000002086ed61f62a546f3bfa8f3e567b003f097efa982008c47b")
+	block414036Hash = mustParseHash("0000000000000000194fa59310b9e988ecb23de0c716d6e8f1b2aa31d9592387")
+	block424011Hash = mustParseHash("0000000000000000317fc6c7a8a6578be7dfa9c96eb81d620050a3732b02d572")
+	block428809Hash = mustParseHash("00000000000000003147798ccffcecaa420fb1c7934d8f4e33809a871ee34aaa")
+	block430191Hash = mustParseHash("00000000000000002127ad6d4cb30cc16f6344589b417e42650388bb0690a88e")
 )
 
 // voteBitsApproveParent returns whether or not the passed vote bits indicate
@@ -821,6 +845,44 @@ func CheckBlockSanity(block *dcrutil.Block, timeSource MedianTimeSource, chainPa
 	return checkBlockSanity(block, timeSource, BFNone, chainParams)
 }
 
+// isDCP0005Violation returns whether or not the block is known to violate
+// DCP0005.  Due to a bug that has since been fixed, some blocks were accepted
+// that violated DCP0005 by still being submitted on an old block version
+// (version 6 on mainnet) when the majority of the network had already upgraded
+// to the new version specified by DCP0005 (version 7 on mainnet).
+//
+// The blocks that violated DCP0005 due to specifying the old version were
+// otherwise valid and are whitelisted by this function so that they will be
+// accepted when fully syncing the chain without checkpoints.
+func isDCP0005Violation(network wire.CurrencyNet, header *wire.BlockHeader, blockHash *chainhash.Hash) bool {
+	switch network {
+	case wire.MainNet:
+		// 430191 is the height of the last block on mainnet that violated
+		// DCP0005, so return false immediately if the height is greater than
+		// that.
+		if header.Height > 430191 {
+			return false
+		}
+
+		// Return whether or not the block is any of the following 5 mainnet
+		// blocks that are known to violate DCP0005.
+		return header.Height == 413762 && *blockHash == *block413762Hash ||
+			header.Height == 414036 && *blockHash == *block414036Hash ||
+			header.Height == 424011 && *blockHash == *block424011Hash ||
+			header.Height == 428809 && *blockHash == *block428809Hash ||
+			header.Height == 430191 && *blockHash == *block430191Hash
+
+	case wire.TestNet3:
+		// 323282 is the height of the last block on testnet that violated
+		// DCP0005.  Return true if the height is less than or equal to 323282
+		// so that the DCP0005 version check is not enforced until after that
+		// point.
+		return header.Height <= 323282
+	}
+
+	return false
+}
+
 // checkBlockHeaderPositional performs several validation checks on the block
 // header which depend on its position within the block chain and having the
 // headers of all ancestors available.  These checks do not, and must not, rely
@@ -900,10 +962,20 @@ func (b *BlockChain) checkBlockHeaderPositional(header *wire.BlockHeader, prevNo
 		// Note that the latest block version for all networks other than the
 		// main network is one higher.
 		latestBlockVersion := int32(8)
+		dcp0005Version := int32(7)
 		if b.chainParams.Net != wire.MainNet {
 			latestBlockVersion++
+			dcp0005Version++
 		}
 		for version := latestBlockVersion; version > 1; version-- {
+			// Skip the version check for blocks that are known to violate DCP0005.
+			// See isDCP0005Violation for more details.
+			if version == dcp0005Version &&
+				isDCP0005Violation(b.chainParams.Net, header, &blockHash) {
+
+				continue
+			}
+
 			if header.Version < version && b.isMajorityVersion(version,
 				prevNode, b.chainParams.BlockRejectNumRequired) {
 
diff --git a/chaincfg/mainnetparams.go b/chaincfg/mainnetparams.go
index 55f2965a..93571ab1 100644
--- a/chaincfg/mainnetparams.go
+++ b/chaincfg/mainnetparams.go
@@ -418,6 +418,9 @@ func MainNetParams() *Params {
 		seeders: []string{
 			"mainnet-seed-1.decred.org",
 			"mainnet-seed-2.decred.org",
+			"mainnet-seed.planetdecred.org",
+			"mainnet-seed.dcrdata.org",
+			"mainnet-seed.jamieholdstock.com",
 		},
 	}
 }
diff --git a/docs/json_rpc_api.mediawiki b/docs/json_rpc_api.mediawiki
index 7bfce099..69b67e87 100644
--- a/docs/json_rpc_api.mediawiki
+++ b/docs/json_rpc_api.mediawiki
@@ -152,7 +152,7 @@ the method name for further details such as parameter and return information.
 |-
 |[[#estimatesmartfee|estimatesmartfee]]
 |Y
-|Returns the estimated fee using the historical fee data in dcr/kb.
+|Returns the estimated fee using the historical fee data in dcr/kb and the block number where the estimate was found.
 |-
 |[[#estimatestakediff|estimatestakediff]]
 |Y
@@ -737,10 +737,13 @@ the method name for further details such as parameter and return information.
 # <code>mode</code>: <code>(string)</code> The only supported mode for the moment is 'conservative'.
 |-
 !Description
-|Returns the estimated fee using the historical fee data in dcr/kb.
+|Returns the estimated fee using the historical fee data in dcr/kb and the block number where the estimate was found.
 |-
 !Returns
-|<code>numeric</code>
+|<code>{json object}</code>
+: <code>fee</code>: <code>(numeric)</code> The estimated fee.
+: <code>errors</code>: <code>(json array)</code> Unused.
+: <code>blocks</code>: <code>(numeric)</code> The block number where the estimate was found.
 |-
 !Example Return
 |
@@ -2177,17 +2180,15 @@ of the best block.
 |
 <code>(json object)</code>
 : <code>data</code>: <code>(string)</code> hex-encoded block data
-: <code>hash1</code>: <code>(string)</code> (DEPRECATED) hex-encoded formatted hash buffer
-: <code>midstate</code>: <code>(string)</code> (DEPRECATED) hex-encoded precomputed hash state after hashing first half of the data
 : <code>target</code>: <code>(string)</code> the hex-encoded little-endian hash target
 
-<code>{"data": "hex", "hash1": "hex", "midstate": "hex", "target": "hex"}</code>
+<code>{"data": "hex", "target": "hex"}</code>
 |-
 !Returns (data specified)
 |<code>true</code> or <code>false</code> (boolean)
 |-
 !Example Return (data not specified)
-|<code>{"data": "00000002c39b5d2b7a1e8f7356a1efce26b24bd15d7d906e85341ef9cec99b6a000000006474f...", "hash1": "00000000000000000000000000000000000000000000000000000000000000000000008000000...", "midstate": "ae4a80fc51476e452de855b4e20d5f33418c50fc7cae3b1ecd5badb819b8a584", "target": "0000000000000000000000000000000000000000000000008c96010000000000"}</code>
+|<code>{"data": "00000002c39b5d2b7a1e8f7356a1efce26b24bd15d7d906e85341ef9cec99b6a000000006474f...", "target": "0000000000000000000000000000000000000000000000008c96010000000000"}</code>
 |-
 !Example Return (data specified)
 |<code>true</code>
@@ -2743,10 +2744,6 @@ user.  Click the method name for further details such as parameter and return in
 |Send notifications for all new tickets that have matured.
 |[[#newtickets|newtickets]]
 |-
-|[[#notifystakedifficulty|notifystakedifficulty]]
-|Send notifications whenever the stake difficulty is updated.
-|[[#stakedifficulty|stakedifficulty]]
-|-
 |[[#session|session]]
 |Return details regarding a websocket client's current connection.
 |None
@@ -3183,26 +3180,6 @@ NOTE: This is only required if an HTTP Authorization header is not being used.
 
 ----
 
-====notifystakedifficulty====
-{|
-!Method
-|notifystakedifficulty
-|-
-!Notifications
-|[[#stakedifficulty|stakedifficulty]]
-|-
-!Parameters
-|None
-|-
-!Description
-|Send a stakedifficulty notification when the stake difficulty is updated.
-|-
-!Returns
-|Nothing
-|}
-
-----
-
 ====session====
 {|
 !Method
@@ -3285,10 +3262,6 @@ The following is an overview of the JSON-RPC notifications used for Websocket co
 |New tickets matured.
 |[[#notifynewtickets|notifynewtickets]]
 |-
-|[[#stakedifficulty|stakedifficulty]]
-|The stake difficulty was updated.
-|[[#notifystakedifficulty|notifystakedifficulty]]
-|-
 |[[#rescanprogress|rescanprogress]]
 |A rescan operation that is underway has made progress.
 |[[#rescan|rescan]]
@@ -3580,29 +3553,6 @@ The redeemingtx notification for the same txout, after the spending transaction
 
 ----
 
-====stakedifficulty====
-{|
-!Method
-|stakedifficulty
-|-
-!Request
-|[[#notifystakedifficulty|notifystakedifficulty]]
-|-
-!Parameters
-|
-# <code>BlockHash</code>: <code>(string)</code> the hash of the block.
-# <code>BlockHeight</code>: <code>(numeric)</code> the height of the block.
-# <code>StakeDiff</code>: <code>(numeric)</code> the stake difficulty of the block.
-|-
-!Description
-|Notifies a client when the stake difficulty has been updated.
-|-
-!Example
-|Example stakedifficulty notification for block 479903 on testnet:
-: <code>{"jsonrpc": "1.0", "method": "stakedifficulty", "params": ["00000044a6c0e2fb8f4feae2ac1133443859407abcf27d5d3a29d7d16eda8bc4", 479903, 9003800525], "id": null}</code>
-|}
-----
-
 ====rescanprogress====
 {|
 !Method
diff --git a/internal/fees/log.go b/internal/fees/log.go
index 08a738d5..08793d69 100644
--- a/internal/fees/log.go
+++ b/internal/fees/log.go
@@ -13,14 +13,6 @@ import (
 // The default amount of logging is none.
 var log = slog.Disabled
 
-// DisableLog disables all library log output.  Logging output is disabled
-// by default until UseLogger is called.
-//
-// Deprecated: Use UseLogger(slog.Disabled) instead.
-func DisableLog() {
-	log = slog.Disabled
-}
-
 // UseLogger uses a specified Logger to output fee estimator logging info. This
 // should be used in preference to SetLogWriter if the caller is also using
 // slog.
diff --git a/internal/mining/mining.go b/internal/mining/mining.go
index 0482b0c6..7d2c5e58 100644
--- a/internal/mining/mining.go
+++ b/internal/mining/mining.go
@@ -132,8 +132,8 @@ type Config struct {
 	FetchUtxoViewParentTemplate func(block *wire.MsgBlock) (*blockchain.UtxoViewpoint, error)
 
 	// ForceHeadReorganization defines the function to use to force a
-	// reorganization of the block chain to the block hash requested, so long as it
-	// matches up with the current organization of the best chain.
+	// reorganization of the block chain to the block hash requested, so long as
+	// it matches up with the current organization of the best chain.
 	ForceHeadReorganization func(formerBest chainhash.Hash, newBest chainhash.Hash) error
 
 	// IsFinalizedTransaction defines the function to use to determine whether or
@@ -166,11 +166,6 @@ type Config struct {
 	// ValidateTransactionScripts defines the function to use to validate the
 	// scripts for the passed transaction.
 	ValidateTransactionScripts func(tx *dcrutil.Tx, utxoView *blockchain.UtxoViewpoint, flags txscript.ScriptFlags) error
-
-	// ForceReorganization forces a reorganization of the block chain to the
-	// block hash requested, so long as it matches up with the current
-	// organization of the best chain.
-	ForceReorganization func(formerBest, newBest chainhash.Hash) error
 }
 
 // TxDesc is a descriptor about a transaction in a transaction source along with
@@ -1096,7 +1091,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress dcrutil.Address) (*Bloc
 		if eligibleParents[0] != prevHash {
 			for i := range eligibleParents {
 				newHead := &eligibleParents[i]
-				err := g.cfg.ForceReorganization(prevHash, *newHead)
+				err := g.cfg.ForceHeadReorganization(prevHash, *newHead)
 				if err != nil {
 					log.Errorf("failed to reorganize to new parent: %v", err)
 					continue
diff --git a/internal/netsync/manager.go b/internal/netsync/manager.go
index 6f588631..7628766e 100644
--- a/internal/netsync/manager.go
+++ b/internal/netsync/manager.go
@@ -136,21 +136,6 @@ type tipGenerationMsg struct {
 	reply chan tipGenerationResponse
 }
 
-// forceReorganizationResponse is a response sent to the reply channel of a
-// forceReorganizationMsg query.
-type forceReorganizationResponse struct {
-	err error
-}
-
-// forceReorganizationMsg is a message type to be sent across the message
-// channel for requesting that the block on head be reorganized to one of its
-// adjacent orphans.
-type forceReorganizationMsg struct {
-	formerBest chainhash.Hash
-	newBest    chainhash.Hash
-	reply      chan forceReorganizationResponse
-}
-
 // processBlockResponse is a response sent to the reply channel of a
 // processBlockMsg.
 type processBlockResponse struct {
@@ -1042,16 +1027,6 @@ func (m *SyncManager) handleBlockMsg(bmsg *blockMsg) {
 			// Notify stake difficulty subscribers and prune invalidated
 			// transactions.
 			best := m.cfg.Chain.BestSnapshot()
-			if r := m.cfg.RpcServer(); r != nil {
-				// Update registered websocket clients on the
-				// current stake difficulty.
-				r.NotifyStakeDifficulty(
-					&rpcserver.StakeDifficultyNtfnData{
-						BlockHash:       best.Hash,
-						BlockHeight:     best.Height,
-						StakeDifficulty: best.NextStakeDiff,
-					})
-			}
 			m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff, best.Height)
 			m.cfg.TxMemPool.PruneExpiredTx()
 
@@ -1608,31 +1583,6 @@ out:
 					err: err,
 				}
 
-			case forceReorganizationMsg:
-				err := m.cfg.Chain.ForceHeadReorganization(
-					msg.formerBest, msg.newBest)
-
-				if err == nil {
-					// Notify stake difficulty subscribers and prune
-					// invalidated transactions.
-					best := m.cfg.Chain.BestSnapshot()
-					if r := m.cfg.RpcServer(); r != nil {
-						r.NotifyStakeDifficulty(
-							&rpcserver.StakeDifficultyNtfnData{
-								BlockHash:       best.Hash,
-								BlockHeight:     best.Height,
-								StakeDifficulty: best.NextStakeDiff,
-							})
-					}
-					m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff,
-						best.Height)
-					m.cfg.TxMemPool.PruneExpiredTx()
-				}
-
-				msg.reply <- forceReorganizationResponse{
-					err: err,
-				}
-
 			case tipGenerationMsg:
 				g, err := m.cfg.Chain.TipGeneration()
 				msg.reply <- tipGenerationResponse{
@@ -1654,17 +1604,8 @@ out:
 
 				onMainChain := !isOrphan && forkLen == 0
 				if onMainChain {
-					// Notify stake difficulty subscribers and prune
-					// invalidated transactions.
+					// Prune invalidated transactions.
 					best := m.cfg.Chain.BestSnapshot()
-					if r := m.cfg.RpcServer(); r != nil {
-						r.NotifyStakeDifficulty(
-							&rpcserver.StakeDifficultyNtfnData{
-								BlockHash:       best.Hash,
-								BlockHeight:     best.Height,
-								StakeDifficulty: best.NextStakeDiff,
-							})
-					}
 					m.cfg.TxMemPool.PruneStakeTx(best.NextStakeDiff,
 						best.Height)
 					m.cfg.TxMemPool.PruneExpiredTx()
@@ -1896,20 +1837,6 @@ func (m *SyncManager) requestFromPeer(p *peerpkg.Peer, blocks, txs []*chainhash.
 	return nil
 }
 
-// ForceReorganization forces a reorganization of the block chain to the block
-// hash requested, so long as it matches up with the current organization of the
-// best chain.  It is funneled through the sync manager since blockchain is not
-// safe for concurrent access.
-func (m *SyncManager) ForceReorganization(formerBest, newBest chainhash.Hash) error {
-	reply := make(chan forceReorganizationResponse)
-	m.msgChan <- forceReorganizationMsg{
-		formerBest: formerBest,
-		newBest:    newBest,
-		reply:      reply}
-	response := <-reply
-	return response.err
-}
-
 // TipGeneration returns the hashes of all the children of the current best
 // chain tip.  It is funneled through the sync manager since blockchain is not
 // safe for concurrent access.
diff --git a/internal/rpcserver/interface.go b/internal/rpcserver/interface.go
index 16b832fa..fbfe0626 100644
--- a/internal/rpcserver/interface.go
+++ b/internal/rpcserver/interface.go
@@ -687,10 +687,6 @@ type NtfnManager interface {
 	// manager for processing.
 	NotifyNewTickets(tnd *blockchain.TicketNotificationsData)
 
-	// NotifyStakeDifficulty passes a new stake difficulty notification to the
-	// manager for processing.
-	NotifyStakeDifficulty(stnd *StakeDifficultyNtfnData)
-
 	// NotifyMempoolTx passes a transaction accepted by mempool to the
 	// manager for processing.
 	NotifyMempoolTx(tx *dcrutil.Tx, isNew bool)
@@ -746,14 +742,6 @@ type NtfnManager interface {
 	// the passed websocket client.
 	UnregisterNewTickets(wsc *wsClient)
 
-	// RegisterStakeDifficulty requests stake difficulty notifications
-	// to the passed websocket client.
-	RegisterStakeDifficulty(wsc *wsClient)
-
-	// UnregisterStakeDifficulty removes stake difficulty notifications for
-	// the passed websocket client.
-	UnregisterStakeDifficulty(wsc *wsClient)
-
 	// RegisterNewMempoolTxsUpdates requests notifications to the passed websocket
 	// client when new transactions are added to the memory pool.
 	RegisterNewMempoolTxsUpdates(wsc *wsClient)
diff --git a/internal/rpcserver/rpcserver.go b/internal/rpcserver/rpcserver.go
index a2b4445f..204054f8 100644
--- a/internal/rpcserver/rpcserver.go
+++ b/internal/rpcserver/rpcserver.go
@@ -1420,7 +1420,10 @@ func handleEstimateSmartFee(_ context.Context, s *Server, cmd interface{}) (inte
 		return nil, rpcInternalError(err.Error(), "Could not estimate fee")
 	}
 
-	return fee.ToCoin(), nil
+	return &types.EstimateSmartFeeResult{
+		FeeRate: fee.ToCoin(),
+		Blocks:  c.Confirmations,
+	}, nil
 }
 
 // handleEstimateStakeDiff implements the estimatestakediff command.
@@ -5530,12 +5533,6 @@ func (s *Server) NotifyTSpend(tx *dcrutil.Tx) {
 	s.ntfnMgr.NotifyTSpend(tx)
 }
 
-// NotifyStakeDifficulty notifies websocket clients that have registered for
-// stake difficulty updates.
-func (s *Server) NotifyStakeDifficulty(stnd *StakeDifficultyNtfnData) {
-	s.ntfnMgr.NotifyStakeDifficulty(stnd)
-}
-
 // NotifyNewTickets notifies websocket clients that have registered for maturing
 // ticket updates.
 func (s *Server) NotifyNewTickets(tnd *blockchain.TicketNotificationsData) {
diff --git a/internal/rpcserver/rpcserverhelp.go b/internal/rpcserver/rpcserverhelp.go
index 155ff36a..75278854 100644
--- a/internal/rpcserver/rpcserverhelp.go
+++ b/internal/rpcserver/rpcserverhelp.go
@@ -828,9 +828,6 @@ var helpDescsEnUS = map[string]string{
 	// NotifyNewTicketsCmd help
 	"notifynewtickets--synopsis": "Request notifications for whenever new tickets are found.",
 
-	// NotifyStakeDifficultyCmd help
-	"notifystakedifficulty--synopsis": "Request notifications for whenever stake difficulty goes up.",
-
 	// NotifyWinningTicketsCmd help
 	"notifywinningtickets--synopsis": "Request notifications for whenever any tickets are chosen to vote.",
 
@@ -885,7 +882,9 @@ var helpDescsEnUS = map[string]string{
 	"estimatesmartfee--synopsis":     "Returns the estimated fee using the historical fee data in dcr/kb.",
 	"estimatesmartfee-confirmations": "Estimate the fee rate a transaction requires so that it is mined in up to this number of blocks.",
 	"estimatesmartfee-mode":          "The only supported mode for the moment is 'conservative'.",
-	"estimatesmartfee--result0":      "Estimated fee rate (in DCR/KB).",
+	"estimatesmartfeeresult-feerate": "The Estimated fee rate (in DCR/KB).",
+	"estimatesmartfeeresult-errors":  "Unused.",
+	"estimatesmartfeeresult-blocks":  "The block number where the estimate was found.",
 
 	// EstimateStakeDiff help.
 	"estimatestakediff--synopsis":      "Estimate the next minimum, maximum, expected, and user-specified stake difficulty",
@@ -1000,7 +999,7 @@ var rpcResultTypes = map[types.Method][]interface{}{
 	"decoderawtransaction":  {(*types.TxRawDecodeResult)(nil)},
 	"decodescript":          {(*types.DecodeScriptResult)(nil)},
 	"estimatefee":           {(*float64)(nil)},
-	"estimatesmartfee":      {(*float64)(nil)},
+	"estimatesmartfee":      {(*types.EstimateSmartFeeResult)(nil)},
 	"estimatestakediff":     {(*types.EstimateStakeDiffResult)(nil)},
 	"existsaddress":         {(*bool)(nil)},
 	"existsaddresses":       {(*string)(nil)},
@@ -1074,7 +1073,6 @@ var rpcResultTypes = map[types.Method][]interface{}{
 	"notifywinningtickets":        nil,
 	"notifyspentandmissedtickets": nil,
 	"notifynewtickets":            nil,
-	"notifystakedifficulty":       nil,
 	"notifyblocks":                nil,
 	"notifywork":                  nil,
 	"notifytspend":                nil,
diff --git a/internal/rpcserver/rpcwebsocket.go b/internal/rpcserver/rpcwebsocket.go
index 9e182e36..58e4a021 100644
--- a/internal/rpcserver/rpcwebsocket.go
+++ b/internal/rpcserver/rpcwebsocket.go
@@ -75,7 +75,6 @@ var wsHandlersBeforeInit = map[types.Method]wsCommandHandler{
 	"notifywinningtickets":        handleWinningTickets,
 	"notifyspentandmissedtickets": handleSpentAndMissedTickets,
 	"notifynewtickets":            handleNewTickets,
-	"notifystakedifficulty":       handleStakeDifficulty,
 	"notifynewtransactions":       handleNotifyNewTransactions,
 	"rebroadcastmissed":           handleRebroadcastMissed,
 	"rebroadcastwinners":          handleRebroadcastWinners,
@@ -274,15 +273,6 @@ func (m *wsNotificationManager) NotifyNewTickets(tnd *blockchain.TicketNotificat
 	}
 }
 
-// NotifyStakeDifficulty notifies websocket clients that have registered for
-// stake difficulty updates.
-func (m *wsNotificationManager) NotifyStakeDifficulty(stnd *StakeDifficultyNtfnData) {
-	select {
-	case m.queueNotification <- (*notificationStakeDifficulty)(stnd):
-	case <-m.quit:
-	}
-}
-
 // NotifyMempoolTx passes a transaction accepted by mempool to the
 // notification manager for transaction notification processing.  If
 // isNew is true, the tx is a new transaction, rather than one
@@ -308,14 +298,6 @@ type WinningTicketsNtfnData struct {
 	Tickets     []chainhash.Hash
 }
 
-// StakeDifficultyNtfnData is the data that is used to generate
-// stake difficulty notifications.
-type StakeDifficultyNtfnData struct {
-	BlockHash       chainhash.Hash
-	BlockHeight     int64
-	StakeDifficulty int64
-}
-
 type wsClientFilter struct {
 	mu sync.Mutex
 
@@ -448,7 +430,6 @@ type notificationReorganization blockchain.ReorganizationNtfnsData
 type notificationWinningTickets WinningTicketsNtfnData
 type notificationSpentAndMissedTickets blockchain.TicketNotificationsData
 type notificationNewTickets blockchain.TicketNotificationsData
-type notificationStakeDifficulty StakeDifficultyNtfnData
 type notificationTxAcceptedByMempool struct {
 	isNew bool
 	tx    *dcrutil.Tx
@@ -469,8 +450,6 @@ type notificationRegisterSpentAndMissedTickets wsClient
 type notificationUnregisterSpentAndMissedTickets wsClient
 type notificationRegisterNewTickets wsClient
 type notificationUnregisterNewTickets wsClient
-type notificationRegisterStakeDifficulty wsClient
-type notificationUnregisterStakeDifficulty wsClient
 type notificationRegisterNewMempoolTxs wsClient
 type notificationUnregisterNewMempoolTxs wsClient
 
@@ -493,7 +472,6 @@ func (m *wsNotificationManager) notificationHandler(ctx context.Context) {
 	winningTicketNotifications := make(map[chan struct{}]*wsClient)
 	ticketSMNotifications := make(map[chan struct{}]*wsClient)
 	ticketNewNotifications := make(map[chan struct{}]*wsClient)
-	stakeDifficultyNotifications := make(map[chan struct{}]*wsClient)
 	txNotifications := make(map[chan struct{}]*wsClient)
 
 out:
@@ -546,10 +524,6 @@ out:
 				m.notifyNewTickets(ticketNewNotifications,
 					(*blockchain.TicketNotificationsData)(n))
 
-			case *notificationStakeDifficulty:
-				m.notifyStakeDifficulty(stakeDifficultyNotifications,
-					(*StakeDifficultyNtfnData)(n))
-
 			case *notificationTxAcceptedByMempool:
 				if n.isNew && len(txNotifications) != 0 {
 					m.notifyForNewTx(txNotifications, n.tx)
@@ -604,14 +578,6 @@ out:
 				wsc := (*wsClient)(n)
 				delete(ticketNewNotifications, wsc.quit)
 
-			case *notificationRegisterStakeDifficulty:
-				wsc := (*wsClient)(n)
-				stakeDifficultyNotifications[wsc.quit] = wsc
-
-			case *notificationUnregisterStakeDifficulty:
-				wsc := (*wsClient)(n)
-				delete(stakeDifficultyNotifications, wsc.quit)
-
 			case *notificationRegisterClient:
 				wsc := (*wsClient)(n)
 				clients[wsc.quit] = wsc
@@ -627,7 +593,6 @@ out:
 				delete(winningTicketNotifications, wsc.quit)
 				delete(ticketSMNotifications, wsc.quit)
 				delete(ticketNewNotifications, wsc.quit)
-				delete(stakeDifficultyNotifications, wsc.quit)
 				delete(clients, wsc.quit)
 
 			case *notificationRegisterNewMempoolTxs:
@@ -1107,18 +1072,6 @@ func (m *wsNotificationManager) UnregisterNewTickets(wsc *wsClient) {
 	m.queueNotification <- (*notificationUnregisterNewTickets)(wsc)
 }
 
-// RegisterStakeDifficulty requests stake difficulty notifications
-// to the passed websocket client.
-func (m *wsNotificationManager) RegisterStakeDifficulty(wsc *wsClient) {
-	m.queueNotification <- (*notificationRegisterStakeDifficulty)(wsc)
-}
-
-// UnregisterStakeDifficulty removes stake difficulty notifications for
-// the passed websocket client.
-func (m *wsNotificationManager) UnregisterStakeDifficulty(wsc *wsClient) {
-	m.queueNotification <- (*notificationUnregisterStakeDifficulty)(wsc)
-}
-
 // notifyNewTickets notifies websocket clients that have registered for
 // maturing ticket updates.
 func (*wsNotificationManager) notifyNewTickets(clients map[chan struct{}]*wsClient, tnd *blockchain.TicketNotificationsData) {
@@ -1143,25 +1096,6 @@ func (*wsNotificationManager) notifyNewTickets(clients map[chan struct{}]*wsClie
 	}
 }
 
-// notifyStakeDifficulty notifies websocket clients that have registered for
-// maturing ticket updates.
-func (*wsNotificationManager) notifyStakeDifficulty(clients map[chan struct{}]*wsClient, sdnd *StakeDifficultyNtfnData) {
-	// Notify interested websocket clients about the connected block.
-	ntfn := types.NewStakeDifficultyNtfn(sdnd.BlockHash.String(),
-		int32(sdnd.BlockHeight),
-		sdnd.StakeDifficulty)
-
-	marshalledJSON, err := dcrjson.MarshalCmd("1.0", nil, ntfn)
-	if err != nil {
-		log.Errorf("Failed to marshal stake difficulty notification: "+
-			"%v", err)
-		return
-	}
-	for _, wsc := range clients {
-		wsc.QueueNotification(marshalledJSON)
-	}
-}
-
 // RegisterNewMempoolTxsUpdates requests notifications to the passed websocket
 // client when new transactions are added to the memory pool.
 func (m *wsNotificationManager) RegisterNewMempoolTxsUpdates(wsc *wsClient) {
@@ -2310,13 +2244,6 @@ func handleNewTickets(wsc *wsClient, icmd interface{}) (interface{}, error) {
 	return nil, nil
 }
 
-// handleStakeDifficulty implements the notifystakedifficulty command extension
-// for websocket connections.
-func handleStakeDifficulty(wsc *wsClient, icmd interface{}) (interface{}, error) {
-	wsc.rpcServer.ntfnMgr.RegisterStakeDifficulty(wsc)
-	return nil, nil
-}
-
 // handleStopNotifyBlocks implements the stopnotifyblocks command extension for
 // websocket connections.
 func handleStopNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) {
diff --git a/rpc/jsonrpc/types/chainsvrresults.go b/rpc/jsonrpc/types/chainsvrresults.go
index c9d6cd90..f4566b7e 100644
--- a/rpc/jsonrpc/types/chainsvrresults.go
+++ b/rpc/jsonrpc/types/chainsvrresults.go
@@ -28,14 +28,9 @@ type DecodeScriptResult struct {
 
 // EstimateSmartFeeResult models the data returned from the estimatesmartfee
 // command.
-//
-// NOTE: EstimateSmartFee currently returns a float. This result type is
-// unused.
-//
-// TODO: Rework EstimateSmartFee to return this result type.
 type EstimateSmartFeeResult struct {
 	FeeRate float64  `json:"feerate"`
-	Errors  []string `json:"errors"`
+	Errors  []string `json:"errors,omitempty"`
 	Blocks  int64    `json:"blocks"`
 }
 
diff --git a/rpc/jsonrpc/types/chainsvrwscmds.go b/rpc/jsonrpc/types/chainsvrwscmds.go
index a1c81d69..24d78b75 100644
--- a/rpc/jsonrpc/types/chainsvrwscmds.go
+++ b/rpc/jsonrpc/types/chainsvrwscmds.go
@@ -111,17 +111,6 @@ func NewNotifyNewTicketsCmd() *NotifyNewTicketsCmd {
 	return &NotifyNewTicketsCmd{}
 }
 
-// NotifyStakeDifficultyCmd is a type handling custom marshaling and
-// unmarshaling of notifystakedifficulty JSON websocket extension
-// commands.
-type NotifyStakeDifficultyCmd struct {
-}
-
-// NewNotifyStakeDifficultyCmd creates a new NotifyStakeDifficultyCmd.
-func NewNotifyStakeDifficultyCmd() *NotifyStakeDifficultyCmd {
-	return &NotifyStakeDifficultyCmd{}
-}
-
 // RebroadcastMissedCmd is a type handling custom marshaling and
 // unmarshaling of rebroadcastmissed JSON RPC commands.
 type RebroadcastMissedCmd struct{}
@@ -228,12 +217,8 @@ func init() {
 	dcrjson.MustRegister(Method("notifytspend"), (*NotifyTSpendCmd)(nil), flags)
 	dcrjson.MustRegister(Method("notifynewtransactions"), (*NotifyNewTransactionsCmd)(nil), flags)
 	dcrjson.MustRegister(Method("notifynewtickets"), (*NotifyNewTicketsCmd)(nil), flags)
-	dcrjson.MustRegister(Method("notifyspentandmissedtickets"),
-		(*NotifySpentAndMissedTicketsCmd)(nil), flags)
-	dcrjson.MustRegister(Method("notifystakedifficulty"),
-		(*NotifyStakeDifficultyCmd)(nil), flags)
-	dcrjson.MustRegister(Method("notifywinningtickets"),
-		(*NotifyWinningTicketsCmd)(nil), flags)
+	dcrjson.MustRegister(Method("notifyspentandmissedtickets"), (*NotifySpentAndMissedTicketsCmd)(nil), flags)
+	dcrjson.MustRegister(Method("notifywinningtickets"), (*NotifyWinningTicketsCmd)(nil), flags)
 	dcrjson.MustRegister(Method("rebroadcastmissed"), (*RebroadcastMissedCmd)(nil), flags)
 	dcrjson.MustRegister(Method("rebroadcastwinners"), (*RebroadcastWinnersCmd)(nil), flags)
 	dcrjson.MustRegister(Method("session"), (*SessionCmd)(nil), flags)
diff --git a/rpc/jsonrpc/types/chainsvrwsntfns.go b/rpc/jsonrpc/types/chainsvrwsntfns.go
index ffceed86..074be7f3 100644
--- a/rpc/jsonrpc/types/chainsvrwsntfns.go
+++ b/rpc/jsonrpc/types/chainsvrwsntfns.go
@@ -53,10 +53,6 @@ const (
 	// spentandmissedtickets notification.
 	SpentAndMissedTicketsNtfnMethod Method = "spentandmissedtickets"
 
-	// StakeDifficultyNtfnMethod is the method of the daemon stakedifficulty
-	// notification.
-	StakeDifficultyNtfnMethod Method = "stakedifficulty"
-
 	// WinningTicketsNtfnMethod is the method of the daemon winningtickets
 	// notification.
 	WinningTicketsNtfnMethod Method = "winningtickets"
@@ -177,23 +173,6 @@ func NewSpentAndMissedTicketsNtfn(hash string, height int32, stakeDiff int64, ti
 	}
 }
 
-// StakeDifficultyNtfn is a type handling custom marshaling and
-// unmarshaling of stakedifficulty JSON websocket notifications.
-type StakeDifficultyNtfn struct {
-	BlockHash   string
-	BlockHeight int32
-	StakeDiff   int64
-}
-
-// NewStakeDifficultyNtfn creates a new StakeDifficultyNtfn.
-func NewStakeDifficultyNtfn(hash string, height int32, stakeDiff int64) *StakeDifficultyNtfn {
-	return &StakeDifficultyNtfn{
-		BlockHash:   hash,
-		BlockHeight: height,
-		StakeDiff:   stakeDiff,
-	}
-}
-
 // TxAcceptedNtfn defines the txaccepted JSON-RPC notification.
 type TxAcceptedNtfn struct {
 	TxID   string  `json:"txid"`
@@ -266,6 +245,5 @@ func init() {
 	dcrjson.MustRegister(TxAcceptedVerboseNtfnMethod, (*TxAcceptedVerboseNtfn)(nil), flags)
 	dcrjson.MustRegister(RelevantTxAcceptedNtfnMethod, (*RelevantTxAcceptedNtfn)(nil), flags)
 	dcrjson.MustRegister(SpentAndMissedTicketsNtfnMethod, (*SpentAndMissedTicketsNtfn)(nil), flags)
-	dcrjson.MustRegister(StakeDifficultyNtfnMethod, (*StakeDifficultyNtfn)(nil), flags)
 	dcrjson.MustRegister(WinningTicketsNtfnMethod, (*WinningTicketsNtfn)(nil), flags)
 }
diff --git a/rpcclient/infrastructure.go b/rpcclient/infrastructure.go
index 9c083e23..e7e8e080 100644
--- a/rpcclient/infrastructure.go
+++ b/rpcclient/infrastructure.go
@@ -282,9 +282,6 @@ func (c *Client) trackRegisteredNtfns(cmd interface{}) {
 	case *chainjson.NotifyNewTicketsCmd:
 		c.ntfnState.notifyNewTickets = true
 
-	case *chainjson.NotifyStakeDifficultyCmd:
-		c.ntfnState.notifyStakeDifficulty = true
-
 	case *chainjson.NotifyBlocksCmd:
 		c.ntfnState.notifyBlocks = true
 
@@ -602,14 +599,6 @@ func (c *Client) reregisterNtfns(ctx context.Context) error {
 		}
 	}
 
-	// Reregister notifystakedifficulty if needed.
-	if stateCopy.notifyStakeDifficulty {
-		log.Debugf("Reregistering [notifystakedifficulty]")
-		if err := c.NotifyStakeDifficulty(ctx); err != nil {
-			return err
-		}
-	}
-
 	// Reregister notifynewtransactions if needed.
 	if stateCopy.notifyNewTx || stateCopy.notifyNewTxVerbose {
 		log.Debugf("Reregistering [notifynewtransactions] (verbose=%v)",
diff --git a/rpcclient/log.go b/rpcclient/log.go
index 6dd5d28f..db331ea1 100644
--- a/rpcclient/log.go
+++ b/rpcclient/log.go
@@ -15,14 +15,6 @@ import (
 // The default amount of logging is none.
 var log = slog.Disabled
 
-// DisableLog disables all library log output.  Logging output is disabled
-// by default until UseLogger is called.
-//
-// Deprecated: Use UseLogger(slog.Disabled) instead.
-func DisableLog() {
-	log = slog.Disabled
-}
-
 // UseLogger uses a specified Logger to output package logging info.
 func UseLogger(logger slog.Logger) {
 	log = logger
diff --git a/rpcclient/notify.go b/rpcclient/notify.go
index 86239cfb..974b3581 100644
--- a/rpcclient/notify.go
+++ b/rpcclient/notify.go
@@ -38,7 +38,6 @@ type notificationState struct {
 	notifyWinningTickets        bool
 	notifySpentAndMissedTickets bool
 	notifyNewTickets            bool
-	notifyStakeDifficulty       bool
 	notifyNewTx                 bool
 	notifyNewTxVerbose          bool
 }
@@ -52,7 +51,6 @@ func (s *notificationState) Copy() *notificationState {
 	stateCopy.notifyWinningTickets = s.notifyWinningTickets
 	stateCopy.notifySpentAndMissedTickets = s.notifySpentAndMissedTickets
 	stateCopy.notifyNewTickets = s.notifyNewTickets
-	stateCopy.notifyStakeDifficulty = s.notifyStakeDifficulty
 	stateCopy.notifyNewTx = s.notifyNewTx
 	stateCopy.notifyNewTxVerbose = s.notifyNewTxVerbose
 
@@ -147,14 +145,6 @@ type NotificationHandlers struct {
 		stakeDiff int64,
 		tickets []*chainhash.Hash)
 
-	// OnStakeDifficulty is invoked when a block is connected to the longest
-	// (best) chain and a new stake difficulty is calculated.  It will only
-	// be invoked if a preceding call to NotifyStakeDifficulty has been
-	// made to register for the notification and the function is non-nil.
-	OnStakeDifficulty func(hash *chainhash.Hash,
-		height int64,
-		stakeDiff int64)
-
 	// OnTxAccepted is invoked when a transaction is accepted into the
 	// memory pool.  It will only be invoked if a preceding call to
 	// NotifyNewTransactions with the verbose flag set to false has been
@@ -349,26 +339,6 @@ func (c *Client) handleNotification(ntfn *rawNotification) {
 			stakeDifficulty,
 			tickets)
 
-	// OnStakeDifficulty
-	case chainjson.StakeDifficultyNtfnMethod:
-		// Ignore the notification if the client is not interested in
-		// it.
-		if c.ntfnHandlers.OnStakeDifficulty == nil {
-			return
-		}
-
-		blockSha, blockHeight, stakeDiff,
-			err := parseStakeDifficultyNtfnParams(ntfn.Params)
-		if err != nil {
-			log.Warnf("Received invalid stake difficulty "+
-				"notification: %v", err)
-			return
-		}
-
-		c.ntfnHandlers.OnStakeDifficulty(blockSha,
-			blockHeight,
-			stakeDiff)
-
 	// OnTxAccepted
 	case chainjson.TxAcceptedNtfnMethod:
 		// Ignore the notification if the client is not interested in
@@ -757,49 +727,6 @@ func parseNewTicketsNtfnParams(params []json.RawMessage) (*chainhash.Hash, int64
 	return sha, bh, stakeDiff, t, nil
 }
 
-// parseStakeDifficultyNtfnParams parses out the list of block hash, block
-// height, and stake difficulty from a WinningTickets notification.
-func parseStakeDifficultyNtfnParams(params []json.RawMessage) (
-	*chainhash.Hash,
-	int64,
-	int64,
-	error) {
-
-	if len(params) != 3 {
-		return nil, 0, 0, wrongNumParams(len(params))
-	}
-
-	// Unmarshal first parameter as a string.
-	var blockHashStr string
-	err := json.Unmarshal(params[0], &blockHashStr)
-	if err != nil {
-		return nil, 0, 0, err
-	}
-
-	// Create ShaHash from block sha string.
-	bHash, err := chainhash.NewHashFromStr(blockHashStr)
-	if err != nil {
-		return nil, 0, 0, err
-	}
-
-	// Unmarshal second parameter as an integer.
-	var blockHeight int32
-	err = json.Unmarshal(params[1], &blockHeight)
-	if err != nil {
-		return nil, 0, 0, err
-	}
-	bHeight := int64(blockHeight)
-
-	// Unmarshal third parameter as an integer.
-	var stakeDiff int64
-	err = json.Unmarshal(params[2], &stakeDiff)
-	if err != nil {
-		return nil, 0, 0, err
-	}
-
-	return bHash, bHeight, stakeDiff, nil
-}
-
 // parseTxAcceptedNtfnParams parses out the transaction hash and total amount
 // from the parameters of a txaccepted notification.
 func parseTxAcceptedNtfnParams(params []json.RawMessage) (*chainhash.Hash,
@@ -1145,56 +1072,6 @@ func (c *Client) NotifyNewTickets(ctx context.Context) error {
 	return c.NotifyNewTicketsAsync(ctx).Receive()
 }
 
-// FutureNotifyStakeDifficultyResult is a future promise to deliver the result of a
-// NotifyStakeDifficultyAsync RPC invocation (or an applicable error).
-type FutureNotifyStakeDifficultyResult cmdRes
-
-// Receive waits for the response promised by the future and returns an error
-// if the registration was not successful.
-func (r *FutureNotifyStakeDifficultyResult) Receive() error {
-	_, err := receiveFuture(r.ctx, r.c)
-	return err
-}
-
-// NotifyStakeDifficultyAsync returns an instance of a type that can be used to get the
-// result of the RPC at some future time by invoking the Receive function on
-// the returned instance.
-//
-// See NotifyStakeDifficulty for the blocking version and more details.
-//
-// NOTE: This is a dcrd extension and requires a websocket connection.
-func (c *Client) NotifyStakeDifficultyAsync(ctx context.Context) *FutureNotifyStakeDifficultyResult {
-	// Not supported in HTTP POST mode.
-	if c.config.HTTPPostMode {
-		return (*FutureNotifyStakeDifficultyResult)(newFutureError(ctx, ErrWebsocketsRequired))
-	}
-
-	// Ignore the notification if the client is not interested in
-	// notifications.
-	if c.ntfnHandlers == nil {
-		return (*FutureNotifyStakeDifficultyResult)(newNilFutureResult(ctx))
-	}
-
-	cmd := chainjson.NewNotifyStakeDifficultyCmd()
-
-	return (*FutureNotifyStakeDifficultyResult)(c.sendCmd(ctx, cmd))
-}
-
-// NotifyStakeDifficulty registers the client to receive notifications when
-// blocks are connected to the main chain and stake difficulty is updated.  The
-// notifications are delivered to the notification handlers associated with the
-// client.  Calling this function has no effect if there are no notification
-// handlers and will result in an error if the client is configured to run in
-// HTTP POST mode.
-//
-// The notifications delivered as a result of this call will be via
-// OnStakeDifficulty.
-//
-// NOTE: This is a dcrd extension and requires a websocket connection.
-func (c *Client) NotifyStakeDifficulty(ctx context.Context) error {
-	return c.NotifyStakeDifficultyAsync(ctx).Receive()
-}
-
 // FutureNotifyNewTransactionsResult is a future promise to deliver the result
 // of a NotifyNewTransactionsAsync RPC invocation (or an applicable error).
 type FutureNotifyNewTransactionsResult cmdRes
diff --git a/server.go b/server.go
index 1a19d4ac..a19560ce 100644
--- a/server.go
+++ b/server.go
@@ -3731,7 +3731,6 @@ func newServer(ctx context.Context, listenAddrs []string, db database.DB, chainP
 				return blockchain.ValidateTransactionScripts(tx, utxoView, flags,
 					s.sigCache)
 			},
-			ForceReorganization: s.syncManager.ForceReorganization,
 		})
 
 		s.bg = mining.NewBgBlkTmplGenerator(&mining.BgBlkTmplConfig{


Test patch:
diff --git a/blockchain/blockindex_test.go b/blockchain/blockindex_test.go
index a657129d..38d0f7b6 100644
--- a/blockchain/blockindex_test.go
+++ b/blockchain/blockindex_test.go
@@ -10,24 +10,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/decred/dcrd/chaincfg/chainhash"
 	"github.com/decred/dcrd/chaincfg/v3"
 	"github.com/decred/dcrd/wire"
 )
 
-// mustParseHash converts the passed big-endian hex string into a
-// chainhash.Hash and will panic if there is an error.  It only differs from the
-// one available in chainhash in that it will panic so errors in the source code
-// be detected.  It will only (and must only) be called with hard-coded, and
-// therefore known good, hashes.
-func mustParseHash(s string) *chainhash.Hash {
-	hash, err := chainhash.NewHashFromStr(s)
-	if err != nil {
-		panic("invalid hash in source file: " + s)
-	}
-	return hash
-}
-
 // TestBlockNodeHeader ensures that block nodes reconstruct the correct header
 // and fetching the header from the chain reconstructs it from memory.
 func TestBlockNodeHeader(t *testing.T) {
diff --git a/blockchain/chainio_test.go b/blockchain/chainio_test.go
index c3144de6..0ff57e60 100644
--- a/blockchain/chainio_test.go
+++ b/blockchain/chainio_test.go
@@ -21,15 +21,6 @@ import (
 	"github.com/decred/dcrd/wire"
 )
 
-// newHashFromStr converts the passed big-endian hex string into a
-// chainhash.Hash.  It only differs from the one available in chainhash in that
-// it ignores the error since it will only (and must only) be called with
-// hard-coded, and therefore known good, hashes.
-func newHashFromStr(hexStr string) *chainhash.Hash {
-	hash, _ := chainhash.NewHashFromStr(hexStr)
-	return hash
-}
-
 // hexToFinalState converts the passed hex string into an array of 6 bytes and
 // will panic if there is an error.  This is only provided for the hard-coded
 // constants so errors in the source code can be detected. It will only (and
@@ -104,9 +95,9 @@ func TestBlockIndexSerialization(t *testing.T) {
 	// for the various tests below.
 	baseHeader := wire.BlockHeader{
 		Version:      4,
-		PrevBlock:    *newHashFromStr("000000000000016916671ae225343a5ee131c999d5cadb6348805db25737731f"),
-		MerkleRoot:   *newHashFromStr("5ef2bb79795d7503c0ccc5cb6e0d4731992fc8c8c5b332c1c0e2c687d864c666"),
-		StakeRoot:    *newHashFromStr("022965059b7527dc2bc18daaa533f806eda1f96fd0b04bbda2381f5552d7c2de"),
+		PrevBlock:    *mustParseHash("000000000000016916671ae225343a5ee131c999d5cadb6348805db25737731f"),
+		MerkleRoot:   *mustParseHash("5ef2bb79795d7503c0ccc5cb6e0d4731992fc8c8c5b332c1c0e2c687d864c666"),
+		StakeRoot:    *mustParseHash("022965059b7527dc2bc18daaa533f806eda1f96fd0b04bbda2381f5552d7c2de"),
 		VoteBits:     0x0001,
 		FinalState:   hexToFinalState("313e16e64c0b"),
 		Voters:       4,
@@ -597,7 +588,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 				Version: 1,
 				TxIn: []*wire.TxIn{{
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
+						Hash:  *mustParseHash("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
 						Index: 0,
 						Tree:  0,
 					},
@@ -650,7 +641,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 				Version: 1,
 				TxIn: []*wire.TxIn{{
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
+						Hash:  *mustParseHash("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
 						Index: 1,
 						Tree:  0,
 					},
@@ -676,7 +667,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 				Version: 1,
 				TxIn: []*wire.TxIn{{
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("92fbe1d4be82f765dfabc9559d4620864b05cc897c4db0e29adac92d294e52b7"),
+						Hash:  *mustParseHash("92fbe1d4be82f765dfabc9559d4620864b05cc897c4db0e29adac92d294e52b7"),
 						Index: 0,
 						Tree:  0,
 					},
@@ -699,7 +690,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 				Expiry:   0,
 			}},
 			utxoView: &UtxoViewpoint{entries: map[chainhash.Hash]*UtxoEntry{
-				*newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
+				*mustParseHash("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
 					txVersion:  1,
 					isCoinBase: false,
 					hasExpiry:  false,
@@ -739,7 +730,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 				Version: 1,
 				TxIn: []*wire.TxIn{{
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
+						Hash:  *mustParseHash("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
 						Index: 1,
 					},
 					SignatureScript: hexToBytes(""),
@@ -749,7 +740,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 					ValueIn:         159747816,
 				}, {
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
+						Hash:  *mustParseHash("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
 						Index: 2,
 					},
 					SignatureScript: hexToBytes(""),
@@ -771,7 +762,7 @@ func TestSpendJournalSerialization(t *testing.T) {
 				Expiry:   0,
 			}},
 			utxoView: &UtxoViewpoint{entries: map[chainhash.Hash]*UtxoEntry{
-				*newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
+				*mustParseHash("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): {
 					txVersion:  1,
 					isCoinBase: false,
 					hasExpiry:  false,
@@ -844,7 +835,7 @@ func TestSpendJournalErrors(t *testing.T) {
 				Version: 1,
 				TxIn: []*wire.TxIn{{
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
+						Hash:  *mustParseHash("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
 						Index: 0,
 					},
 					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
@@ -862,7 +853,7 @@ func TestSpendJournalErrors(t *testing.T) {
 				Version: 1,
 				TxIn: []*wire.TxIn{{
 					PreviousOutPoint: wire.OutPoint{
-						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
+						Hash:  *mustParseHash("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
 						Index: 0,
 					},
 					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
@@ -1373,7 +1364,7 @@ func TestBestChainStateSerialization(t *testing.T) {
 		{
 			name: "genesis",
 			state: bestChainState{
-				hash:         *newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
+				hash:         *mustParseHash("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
 				height:       0,
 				totalTxns:    1,
 				totalSubsidy: 0,
@@ -1387,7 +1378,7 @@ func TestBestChainStateSerialization(t *testing.T) {
 		{
 			name: "block 1",
 			state: bestChainState{
-				hash:         *newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"),
+				hash:         *mustParseHash("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"),
 				height:       1,
 				totalTxns:    2,
 				totalSubsidy: 123456789,
diff --git a/blockchain/upgrade_test.go b/blockchain/upgrade_test.go
index a2f504a4..7e0bac93 100644
--- a/blockchain/upgrade_test.go
+++ b/blockchain/upgrade_test.go
@@ -169,9 +169,9 @@ func TestBlockIndexSerializationV2(t *testing.T) {
 	// for the various tests below.
 	baseHeader := wire.BlockHeader{
 		Version:      4,
-		PrevBlock:    *newHashFromStr("000000000000016916671ae225343a5ee131c999d5cadb6348805db25737731f"),
-		MerkleRoot:   *newHashFromStr("5ef2bb79795d7503c0ccc5cb6e0d4731992fc8c8c5b332c1c0e2c687d864c666"),
-		StakeRoot:    *newHashFromStr("022965059b7527dc2bc18daaa533f806eda1f96fd0b04bbda2381f5552d7c2de"),
+		PrevBlock:    *mustParseHash("000000000000016916671ae225343a5ee131c999d5cadb6348805db25737731f"),
+		MerkleRoot:   *mustParseHash("5ef2bb79795d7503c0ccc5cb6e0d4731992fc8c8c5b332c1c0e2c687d864c666"),
+		StakeRoot:    *mustParseHash("022965059b7527dc2bc18daaa533f806eda1f96fd0b04bbda2381f5552d7c2de"),
 		VoteBits:     0x0001,
 		FinalState:   hexToFinalState("313e16e64c0b"),
 		Voters:       4,
@@ -188,14 +188,14 @@ func TestBlockIndexSerializationV2(t *testing.T) {
 		StakeVersion: 4,
 	}
 	baseTicketsVoted := []chainhash.Hash{
-		*newHashFromStr("8b62a877544753ea80a822142a48ec066170e9381d21a9e8a84bc7373f0f9b2e"),
-		*newHashFromStr("4427a003a7aceb1404ffd9072e9aff1e128a24333a543332030e91668a389db7"),
-		*newHashFromStr("4415b88ac74881d7b6b15d41df465257cd1cc92d55e95f1b648434aef3a2110b"),
-		*newHashFromStr("9d2621b57352088809d3a069b04b76c832f30a76da14e56aece72208b3e5b87a"),
+		*mustParseHash("8b62a877544753ea80a822142a48ec066170e9381d21a9e8a84bc7373f0f9b2e"),
+		*mustParseHash("4427a003a7aceb1404ffd9072e9aff1e128a24333a543332030e91668a389db7"),
+		*mustParseHash("4415b88ac74881d7b6b15d41df465257cd1cc92d55e95f1b648434aef3a2110b"),
+		*mustParseHash("9d2621b57352088809d3a069b04b76c832f30a76da14e56aece72208b3e5b87a"),
 	}
 	baseTicketsRevoked := []chainhash.Hash{
-		*newHashFromStr("8146f01b8ffca8008ebc80293d2978d63b1dffa5c456a73e7b39a9b1e695e8eb"),
-		*newHashFromStr("2292ff2461e725c58cc6e2051eac2a10e6ee6d1f62327ed676b7a196fb94be0c"),
+		*mustParseHash("8146f01b8ffca8008ebc80293d2978d63b1dffa5c456a73e7b39a9b1e695e8eb"),
+		*mustParseHash("2292ff2461e725c58cc6e2051eac2a10e6ee6d1f62327ed676b7a196fb94be0c"),
 	}
 	baseVoteInfo := []stake.VoteVersionTuple{
 		{Version: 4, Bits: 0x0001},
diff --git a/blockchain/validate_test.go b/blockchain/validate_test.go
index 52674b89..27517d95 100644
--- a/blockchain/validate_test.go
+++ b/blockchain/validate_test.go
@@ -613,7 +613,7 @@ func TestTxValidationErrors(t *testing.T) {
 var badBlock = wire.MsgBlock{
 	Header: wire.BlockHeader{
 		Version:      1,
-		MerkleRoot:   *newHashFromStr("66aa7491b9adce110585ccab7e3fb5fe280de174530cca10eba2c6c3df01c10d"),
+		MerkleRoot:   *mustParseHash("66aa7491b9adce110585ccab7e3fb5fe280de174530cca10eba2c6c3df01c10d"),
 		VoteBits:     uint16(0x0000),
 		FinalState:   [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
 		Voters:       uint16(0x0000),
diff --git a/chaincfg/testnetparams.go b/chaincfg/testnetparams.go
index 74fae2c0..e49f2159 100644
--- a/chaincfg/testnetparams.go
+++ b/chaincfg/testnetparams.go
@@ -306,6 +306,9 @@ func TestNet3Params() *Params {
 		seeders: []string{
 			"testnet-seed-1.decred.org",
 			"testnet-seed-2.decred.org",
+			"testnet-seed.planetdecred.org",
+			"testnet-seed.dcrdata.org",
+			"testnet-seed.jamieholdstock.com",
 		},
 	}
 }
diff --git a/internal/mining/mining_harness_test.go b/internal/mining/mining_harness_test.go
index 9a194b29..10156e94 100644
--- a/internal/mining/mining_harness_test.go
+++ b/internal/mining/mining_harness_test.go
@@ -1399,7 +1399,6 @@ func newMiningHarness(chainParams *chaincfg.Params) (*miningHarness, []spendable
 
 				return blockchain.ValidateTransactionScripts(tx, utxoView, flags, sigCache)
 			},
-			ForceReorganization: chain.ForceHeadReorganization,
 		}),
 	}
 
diff --git a/internal/rpcserver/rpcserverhandlers_test.go b/internal/rpcserver/rpcserverhandlers_test.go
index c7c8c46e..8178e807 100644
--- a/internal/rpcserver/rpcserverhandlers_test.go
+++ b/internal/rpcserver/rpcserverhandlers_test.go
@@ -1122,10 +1122,6 @@ func (mgr *testNtfnManager) NotifySpentAndMissedTickets(tnd *blockchain.TicketNo
 // manager for processing.
 func (mgr *testNtfnManager) NotifyNewTickets(tnd *blockchain.TicketNotificationsData) {}
 
-// NotifyStakeDifficulty passes a new stake difficulty notification to the
-// manager for processing.
-func (mgr *testNtfnManager) NotifyStakeDifficulty(stnd *StakeDifficultyNtfnData) {}
-
 // NotifyMempoolTx passes a transaction accepted by mempool to the
 // manager for processing.
 func (mgr *testNtfnManager) NotifyMempoolTx(tx *dcrutil.Tx, isNew bool) {}
@@ -2708,7 +2704,9 @@ func TestHandleEstimateSmartFee(t *testing.T) {
 	economical := types.EstimateSmartFeeEconomical
 	validFeeEstimator := defaultMockFeeEstimator()
 	validFeeEstimator.estimateFeeAmt = 123456789
-	validFee := float64(1.23456789)
+	result := &types.EstimateSmartFeeResult{
+		FeeRate: float64(1.23456789),
+	}
 	testRPCServerHandler(t, []rpcTest{{
 		name:    "handleEstimateSmartFee: ok with mode",
 		handler: handleEstimateSmartFee,
@@ -2717,13 +2715,13 @@ func TestHandleEstimateSmartFee(t *testing.T) {
 			Mode:          &conservative,
 		},
 		mockFeeEstimator: validFeeEstimator,
-		result:           validFee,
+		result:           result,
 	}, {
 		name:             "handleEstimateSmartFee: ok no mode",
 		handler:          handleEstimateSmartFee,
 		cmd:              &types.EstimateSmartFeeCmd{},
 		mockFeeEstimator: validFeeEstimator,
-		result:           validFee,
+		result:           result,
 	}, {
 		name:    "handleEstimateSmartFee: not conservative mode",
 		handler: handleEstimateSmartFee,
diff --git a/rpc/jsonrpc/types/chainsvrwscmds_test.go b/rpc/jsonrpc/types/chainsvrwscmds_test.go
index 957adfb9..0ad900ff 100644
--- a/rpc/jsonrpc/types/chainsvrwscmds_test.go
+++ b/rpc/jsonrpc/types/chainsvrwscmds_test.go
@@ -74,17 +74,6 @@ func TestChainSvrWsCmds(t *testing.T) {
 			marshalled:   `{"jsonrpc":"1.0","method":"notifynewtickets","params":[],"id":1}`,
 			unmarshalled: &NotifyNewTicketsCmd{},
 		},
-		{
-			name: "notifystakedifficulty",
-			newCmd: func() (interface{}, error) {
-				return dcrjson.NewCmd(Method("notifystakedifficulty"))
-			},
-			staticCmd: func() interface{} {
-				return NewNotifyStakeDifficultyCmd()
-			},
-			marshalled:   `{"jsonrpc":"1.0","method":"notifystakedifficulty","params":[],"id":1}`,
-			unmarshalled: &NotifyStakeDifficultyCmd{},
-		},
 		{
 			name: "notifyblocks",
 			newCmd: func() (interface{}, error) {