diff --git a/src/init.cpp b/src/init.cpp
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1865,10 +1865,6 @@
         LogPrintf("Skipping checkpoint verification.\n");
     }
 
-    hashAssumeValid = BlockHash::fromHex(
-        args.GetArg("-assumevalid",
-                    chainparams.GetConsensus().defaultAssumeValid.GetHex()));
-
     if (args.IsArgSet("-minimumchainwork")) {
         const std::string minChainWorkStr =
             args.GetArg("-minimumchainwork", "");
diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h
--- a/src/kernel/chainstatemanager_opts.h
+++ b/src/kernel/chainstatemanager_opts.h
@@ -5,10 +5,12 @@
 #ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H
 #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H
 
+#include <primitives/blockhash.h>
 #include <util/time.h>
 
 #include <cstdint>
 #include <functional>
+#include <optional>
 
 class Config;
 
@@ -25,6 +27,9 @@
     const Config &config;
     const std::function<NodeClock::time_point()> adjusted_time_callback{
         nullptr};
+    //! If set, it will override the block hash whose ancestors we will assume
+    //! to have valid scripts without checking them.
+    std::optional<BlockHash> assumed_valid_block{};
     //! If the tip is older than this, the node is considered to be in initial
     //! block download.
     std::chrono::seconds max_tip_age{DEFAULT_MAX_TIP_AGE};
diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp
--- a/src/node/chainstate.cpp
+++ b/src/node/chainstate.cpp
@@ -163,9 +163,9 @@
 ChainstateLoadResult LoadChainstate(ChainstateManager &chainman,
                                     const CacheSizes &cache_sizes,
                                     const ChainstateLoadOptions &options) {
-    if (!hashAssumeValid.IsNull()) {
+    if (!chainman.AssumedValidBlock().IsNull()) {
         LogPrintf("Assuming ancestors of block %s have valid signatures.\n",
-                  hashAssumeValid.GetHex());
+                  chainman.AssumedValidBlock().GetHex());
     } else {
         LogPrintf("Validating signatures for all blocks.\n");
     }
diff --git a/src/node/chainstatemanager_args.cpp b/src/node/chainstatemanager_args.cpp
--- a/src/node/chainstatemanager_args.cpp
+++ b/src/node/chainstatemanager_args.cpp
@@ -12,6 +12,10 @@
 namespace node {
 void ApplyArgsManOptions(const ArgsManager &args,
                          ChainstateManager::Options &opts) {
+    if (auto value{args.GetArg("-assumevalid")}) {
+        opts.assumed_valid_block = BlockHash::fromHex(*value);
+    }
+
     if (auto value{args.GetIntArg("-maxtipage")}) {
         opts.max_tip_age = std::chrono::seconds{*value};
     }
diff --git a/src/validation.h b/src/validation.h
--- a/src/validation.h
+++ b/src/validation.h
@@ -120,12 +120,6 @@
 extern bool fCheckBlockIndex;
 extern bool fCheckpointsEnabled;
 
-/**
- * Block hash whose ancestors we will assume to have valid scripts without
- * checking them.
- */
-extern BlockHash hashAssumeValid;
-
 /**
  * Minimum work we will assume exists on some valid chain.
  */
@@ -1235,10 +1229,7 @@
 public:
     using Options = kernel::ChainstateManagerOpts;
 
-    explicit ChainstateManager(Options options)
-        : m_options{std::move(options)} {
-        Assert(m_options.adjusted_time_callback);
-    }
+    explicit ChainstateManager(Options options);
 
     const Config &GetConfig() const { return m_options.config; }
 
@@ -1248,6 +1239,9 @@
     const Consensus::Params &GetConsensus() const {
         return m_options.config.GetChainParams().GetConsensus();
     }
+    const BlockHash &AssumedValidBlock() const {
+        return *Assert(m_options.assumed_valid_block);
+    }
 
     /**
      * Alias for ::cs_main.
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -112,7 +112,6 @@
 bool fCheckBlockIndex = false;
 bool fCheckpointsEnabled = DEFAULT_CHECKPOINTS_ENABLED;
 
-BlockHash hashAssumeValid;
 arith_uint256 nMinimumChainWork;
 
 BlockValidationOptions::BlockValidationOptions(const Config &config)
@@ -1754,7 +1753,7 @@
     }
 
     bool fScriptChecks = true;
-    if (!hashAssumeValid.IsNull()) {
+    if (!m_chainman.AssumedValidBlock().IsNull()) {
         // We've been configured with the hash of a block which has been
         // externally verified to have a valid history. A suitable default value
         // is included with the software and updated from time to time. Because
@@ -1762,8 +1761,8 @@
         // defaults can be easily reviewed. This setting doesn't force the
         // selection of any particular chain but makes validating some faster by
         // effectively caching the result of part of the verification.
-        BlockMap::const_iterator it =
-            m_blockman.m_block_index.find(hashAssumeValid);
+        BlockMap::const_iterator it{
+            m_blockman.m_block_index.find(m_chainman.AssumedValidBlock())};
         if (it != m_blockman.m_block_index.end()) {
             if (it->second.GetAncestor(pindex->nHeight) == pindex &&
                 m_chainman.m_best_header->GetAncestor(pindex->nHeight) ==
@@ -6363,6 +6362,23 @@
     m_active_chainstate = nullptr;
 }
 
+/**
+ * Apply default chain params to nullopt members.
+ * This helps to avoid coding errors around the accidental use of the compare
+ * operators that accept nullopt, thus ignoring the intended default value.
+ */
+static ChainstateManager::Options &&Flatten(ChainstateManager::Options &&opts) {
+    if (!opts.assumed_valid_block.has_value()) {
+        opts.assumed_valid_block =
+            opts.config.GetChainParams().GetConsensus().defaultAssumeValid;
+    }
+    Assert(opts.adjusted_time_callback);
+    return std::move(opts);
+}
+
+ChainstateManager::ChainstateManager(Options options)
+    : m_options{Flatten(std::move(options))} {}
+
 bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool *mempool) {
     assert(!m_snapshot_chainstate);
     std::optional<fs::path> path = node::FindSnapshotChainstateDir();