diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index 52bf31e94..f7a5e5197 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -1,259 +1,277 @@
 // Copyright (c) 2018 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #ifndef BITCOIN_INTERFACES_NODE_H
 #define BITCOIN_INTERFACES_NODE_H
 
 #include <consensus/amount.h>
 #include <net.h>        // For CConnman::NumConnections
 #include <net_types.h>  // For banmap_t
 #include <netaddress.h> // For Network
 
 #include <support/allocators/secure.h> // For SecureString
+#include <util/settings.h>             // For util::SettingsValue
 #include <util/translation.h>
 
 #include <cstddef>
 #include <cstdint>
 #include <functional>
 #include <memory>
 #include <string>
 #include <tuple>
 #include <vector>
 
 class BanMan;
 class CCoinControl;
 class CFeeRate;
 struct CNodeStateStats;
 struct CNodeStats;
 class Coin;
 class Config;
 class HTTPRPCRequestProcessor;
 class proxyType;
 class RPCServer;
 class RPCTimerInterface;
 enum class SynchronizationState;
 class UniValue;
 struct bilingual_str;
 namespace node {
 struct NodeContext;
 } // namespace node
 
 namespace interfaces {
 class Handler;
 class WalletClient;
 struct BlockTip;
 
 //! Block and header tip information
 struct BlockAndHeaderTipInfo {
     int block_height;
     int64_t block_time;
     int header_height;
     int64_t header_time;
     double verification_progress;
 };
 
 //! Top-level interface for a bitcoin node (bitcoind process).
 class Node {
 public:
     virtual ~Node() {}
 
     //! Init logging.
     virtual void initLogging() = 0;
 
     //! Init parameter interaction.
     virtual void initParameterInteraction() = 0;
 
     //! Get warnings.
     virtual bilingual_str getWarnings() = 0;
 
     //! Initialize app dependencies.
     virtual bool baseInitialize(Config &config) = 0;
 
     //! Start node.
     virtual bool
     appInitMain(Config &config, RPCServer &rpcServer,
                 HTTPRPCRequestProcessor &httpRPCRequestProcessor,
                 interfaces::BlockAndHeaderTipInfo *tip_info = nullptr) = 0;
 
     //! Stop node.
     virtual void appShutdown() = 0;
 
     //! Start shutdown.
     virtual void startShutdown() = 0;
 
     //! Return whether shutdown was requested.
     virtual bool shutdownRequested() = 0;
 
+    //! Return whether a particular setting in <datadir>/settings.json
+    //! would be ignored because it is also specified in the command line.
+    virtual bool isPersistentSettingIgnored(const std::string &name) = 0;
+
+    //! Return setting value from <datadir>/settings.json or bitcoin.conf.
+    virtual util::SettingsValue
+    getPersistentSetting(const std::string &name) = 0;
+
+    //! Update a setting in <datadir>/settings.json.
+    virtual void updateRwSetting(const std::string &name,
+                                 const util::SettingsValue &value) = 0;
+
+    //! Force a setting value to be applied, overriding any other configuration
+    //! source, but not being persisted.
+    virtual void forceSetting(const std::string &name,
+                              const util::SettingsValue &value) = 0;
+
     //! Map port.
     virtual void mapPort(bool use_upnp, bool use_natpmp) = 0;
 
     //! Get proxy.
     virtual bool getProxy(Network net, proxyType &proxy_info) = 0;
 
     //! Get number of connections.
     virtual size_t getNodeCount(CConnman::NumConnections flags) = 0;
 
     //! Get stats for connected nodes.
     using NodesStats =
         std::vector<std::tuple<CNodeStats, bool, CNodeStateStats>>;
     virtual bool getNodesStats(NodesStats &stats) = 0;
 
     //! Get ban map entries.
     virtual bool getBanned(banmap_t &banmap) = 0;
 
     //! Ban node.
     virtual bool ban(const CNetAddr &net_addr, int64_t ban_time_offset) = 0;
 
     //! Unban node.
     virtual bool unban(const CSubNet &ip) = 0;
 
     //! Disconnect node by address.
     virtual bool disconnectByAddress(const CNetAddr &net_addr) = 0;
 
     //! Disconnect node by id.
     virtual bool disconnectById(NodeId id) = 0;
 
     //! Get total bytes recv.
     virtual int64_t getTotalBytesRecv() = 0;
 
     //! Get total bytes sent.
     virtual int64_t getTotalBytesSent() = 0;
 
     //! Get mempool size.
     virtual size_t getMempoolSize() = 0;
 
     //! Get mempool dynamic usage.
     virtual size_t getMempoolDynamicUsage() = 0;
 
     //! Get header tip height and time.
     virtual bool getHeaderTip(int &height, int64_t &block_time) = 0;
 
     //! Get num blocks.
     virtual int getNumBlocks() = 0;
 
     //! Get best block hash.
     virtual BlockHash getBestBlockHash() = 0;
 
     //! Get last block time.
     virtual int64_t getLastBlockTime() = 0;
 
     //! Get verification progress.
     virtual double getVerificationProgress() = 0;
 
     //! Is initial block download.
     virtual bool isInitialBlockDownload() = 0;
 
     //! Get reindex.
     virtual bool getReindex() = 0;
 
     //! Get importing.
     virtual bool getImporting() = 0;
 
     //! Set network active.
     virtual void setNetworkActive(bool active) = 0;
 
     //! Get network active.
     virtual bool getNetworkActive() = 0;
 
     //! Get dust relay fee.
     virtual CFeeRate getDustRelayFee() = 0;
 
     //! Execute rpc command.
     virtual UniValue executeRpc(const Config &config,
                                 const std::string &command,
                                 const UniValue &params,
                                 const std::string &uri) = 0;
 
     //! List rpc commands.
     virtual std::vector<std::string> listRpcCommands() = 0;
 
     //! Set RPC timer interface if unset.
     virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) = 0;
 
     //! Unset RPC timer interface.
     virtual void rpcUnsetTimerInterface(RPCTimerInterface *iface) = 0;
 
     //! Get unspent outputs associated with a transaction.
     virtual bool getUnspentOutput(const COutPoint &output, Coin &coin) = 0;
 
     //! Get wallet client.
     virtual WalletClient &walletClient() = 0;
 
     //! Register handler for init messages.
     using InitMessageFn = std::function<void(const std::string &message)>;
     virtual std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) = 0;
 
     //! Register handler for message box messages.
     using MessageBoxFn =
         std::function<bool(const bilingual_str &message,
                            const std::string &caption, unsigned int style)>;
     virtual std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) = 0;
 
     //! Register handler for question messages.
     using QuestionFn =
         std::function<bool(const bilingual_str &message,
                            const std::string &non_interactive_message,
                            const std::string &caption, unsigned int style)>;
     virtual std::unique_ptr<Handler> handleQuestion(QuestionFn fn) = 0;
 
     //! Register handler for progress messages.
     using ShowProgressFn = std::function<void(
         const std::string &title, int progress, bool resume_possible)>;
     virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0;
 
     //! Register handler for number of connections changed messages.
     using NotifyNumConnectionsChangedFn =
         std::function<void(int new_num_connections)>;
     virtual std::unique_ptr<Handler>
     handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) = 0;
 
     //! Register handler for network active messages.
     using NotifyNetworkActiveChangedFn =
         std::function<void(bool network_active)>;
     virtual std::unique_ptr<Handler>
     handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) = 0;
 
     //! Register handler for notify alert messages.
     using NotifyAlertChangedFn = std::function<void()>;
     virtual std::unique_ptr<Handler>
     handleNotifyAlertChanged(NotifyAlertChangedFn fn) = 0;
 
     //! Register handler for ban list messages.
     using BannedListChangedFn = std::function<void()>;
     virtual std::unique_ptr<Handler>
     handleBannedListChanged(BannedListChangedFn fn) = 0;
 
     //! Register handler for block tip messages.
     using NotifyBlockTipFn =
         std::function<void(SynchronizationState, interfaces::BlockTip tip,
                            double verification_progress)>;
     virtual std::unique_ptr<Handler>
     handleNotifyBlockTip(NotifyBlockTipFn fn) = 0;
 
     //! Register handler for header tip messages.
     using NotifyHeaderTipFn =
         std::function<void(SynchronizationState, interfaces::BlockTip tip,
                            double verification_progress)>;
     virtual std::unique_ptr<Handler>
     handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0;
 
     //! Get and set internal node context. Useful for testing, but not
     //! accessible across processes.
     virtual node::NodeContext *context() { return nullptr; }
     virtual void setContext(node::NodeContext *context) {}
 };
 
 //! Return implementation of Node interface.
 std::unique_ptr<Node> MakeNode(node::NodeContext *context = nullptr);
 
 //! Block tip (could be a header or not, depends on the subscribed signal).
 struct BlockTip {
     int block_height;
     int64_t block_time;
     BlockHash block_hash;
 };
 
 } // namespace interfaces
 
 #endif // BITCOIN_INTERFACES_NODE_H
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index ab6e2f0c0..3b257708b 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -1,728 +1,763 @@
 // Copyright (c) 2018 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <addrdb.h>
 #include <banman.h>
 #include <chain.h>
 #include <chainparams.h>
 #include <config.h>
 #include <init.h>
 #include <interfaces/chain.h>
 #include <interfaces/handler.h>
 #include <interfaces/node.h>
 #include <interfaces/wallet.h>
 #include <mapport.h>
 #include <net.h>
 #include <net_processing.h>
 #include <netaddress.h>
 #include <netbase.h>
 #include <node/blockstorage.h>
 #include <node/coin.h>
 #include <node/context.h>
 #include <node/transaction.h>
 #include <node/ui_interface.h>
 #include <policy/settings.h>
 #include <primitives/block.h>
 #include <primitives/transaction.h>
 #include <rpc/protocol.h>
 #include <rpc/server.h>
 #include <shutdown.h>
 #include <sync.h>
 #include <timedata.h>
 #include <txmempool.h>
 #include <uint256.h>
 #include <util/check.h>
 #include <util/system.h>
 #include <util/translation.h>
 #include <validation.h>
 #include <validationinterface.h>
 #include <warnings.h>
 
 #if defined(HAVE_CONFIG_H)
 #include <config/bitcoin-config.h>
 #endif
 
 #include <univalue.h>
 
 #include <boost/signals2/signal.hpp>
 
 #include <memory>
 #include <utility>
 
 class HTTPRPCRequestProcessor;
 
 using interfaces::BlockTip;
 using interfaces::Chain;
 using interfaces::FoundBlock;
 using interfaces::Handler;
 using interfaces::MakeHandler;
 using interfaces::Node;
 using interfaces::WalletClient;
 
 namespace node {
 namespace {
 
     class NodeImpl : public Node {
     private:
         ChainstateManager &chainman() { return *Assert(m_context->chainman); }
 
     public:
         explicit NodeImpl(NodeContext *context) { setContext(context); }
         void initLogging() override { InitLogging(*Assert(m_context->args)); }
         void initParameterInteraction() override {
             InitParameterInteraction(*Assert(m_context->args));
         }
         bilingual_str getWarnings() override { return GetWarnings(true); }
         bool baseInitialize(Config &config) override {
             return AppInitBasicSetup(gArgs) &&
                    AppInitParameterInteraction(config, gArgs) &&
                    AppInitSanityChecks() && AppInitLockDataDirectory() &&
                    AppInitInterfaces(*m_context);
         }
         bool appInitMain(Config &config, RPCServer &rpcServer,
                          HTTPRPCRequestProcessor &httpRPCRequestProcessor,
                          interfaces::BlockAndHeaderTipInfo *tip_info) override {
             return AppInitMain(config, rpcServer, httpRPCRequestProcessor,
                                *m_context, tip_info);
         }
         void appShutdown() override {
             Interrupt(*m_context);
             Shutdown(*m_context);
         }
         void startShutdown() override {
             StartShutdown();
             // Stop RPC for clean shutdown if any of waitfor* commands is
             // executed.
             if (gArgs.GetBoolArg("-server", false)) {
                 InterruptRPC();
                 StopRPC();
             }
         }
         bool shutdownRequested() override { return ShutdownRequested(); }
+        bool isPersistentSettingIgnored(const std::string &name) override {
+            bool ignored = false;
+            gArgs.LockSettings([&](util::Settings &settings) {
+                if (auto *options =
+                        util::FindKey(settings.command_line_options, name)) {
+                    ignored = !options->empty();
+                }
+            });
+            return ignored;
+        }
+        util::SettingsValue
+        getPersistentSetting(const std::string &name) override {
+            return gArgs.GetPersistentSetting(name);
+        }
+        void updateRwSetting(const std::string &name,
+                             const util::SettingsValue &value) override {
+            gArgs.LockSettings([&](util::Settings &settings) {
+                if (value.isNull()) {
+                    settings.rw_settings.erase(name);
+                } else {
+                    settings.rw_settings[name] = value;
+                }
+            });
+            gArgs.WriteSettingsFile();
+        }
+        void forceSetting(const std::string &name,
+                          const util::SettingsValue &value) override {
+            gArgs.LockSettings([&](util::Settings &settings) {
+                if (value.isNull()) {
+                    settings.forced_settings.erase(name);
+                } else {
+                    settings.forced_settings[name] = value;
+                }
+            });
+        }
         void mapPort(bool use_upnp, bool use_natpmp) override {
             StartMapPort(use_upnp, use_natpmp);
         }
         bool getProxy(Network net, proxyType &proxy_info) override {
             return GetProxy(net, proxy_info);
         }
         size_t getNodeCount(CConnman::NumConnections flags) override {
             return m_context->connman ? m_context->connman->GetNodeCount(flags)
                                       : 0;
         }
         bool getNodesStats(NodesStats &stats) override {
             stats.clear();
 
             if (m_context->connman) {
                 std::vector<CNodeStats> stats_temp;
                 m_context->connman->GetNodeStats(stats_temp);
 
                 stats.reserve(stats_temp.size());
                 for (auto &node_stats_temp : stats_temp) {
                     stats.emplace_back(std::move(node_stats_temp), false,
                                        CNodeStateStats());
                 }
 
                 // Try to retrieve the CNodeStateStats for each node.
                 if (m_context->peerman) {
                     TRY_LOCK(::cs_main, lockMain);
                     if (lockMain) {
                         for (auto &node_stats : stats) {
                             std::get<1>(node_stats) =
                                 m_context->peerman->GetNodeStateStats(
                                     std::get<0>(node_stats).nodeid,
                                     std::get<2>(node_stats));
                         }
                     }
                 }
                 return true;
             }
             return false;
         }
         bool getBanned(banmap_t &banmap) override {
             if (m_context->banman) {
                 m_context->banman->GetBanned(banmap);
                 return true;
             }
             return false;
         }
         bool ban(const CNetAddr &net_addr, int64_t ban_time_offset) override {
             if (m_context->banman) {
                 m_context->banman->Ban(net_addr, ban_time_offset);
                 return true;
             }
             return false;
         }
         bool unban(const CSubNet &ip) override {
             if (m_context->banman) {
                 m_context->banman->Unban(ip);
                 return true;
             }
             return false;
         }
         bool disconnectByAddress(const CNetAddr &net_addr) override {
             if (m_context->connman) {
                 return m_context->connman->DisconnectNode(net_addr);
             }
             return false;
         }
         bool disconnectById(NodeId id) override {
             if (m_context->connman) {
                 return m_context->connman->DisconnectNode(id);
             }
             return false;
         }
         int64_t getTotalBytesRecv() override {
             return m_context->connman ? m_context->connman->GetTotalBytesRecv()
                                       : 0;
         }
         int64_t getTotalBytesSent() override {
             return m_context->connman ? m_context->connman->GetTotalBytesSent()
                                       : 0;
         }
         size_t getMempoolSize() override {
             return m_context->mempool ? m_context->mempool->size() : 0;
         }
         size_t getMempoolDynamicUsage() override {
             return m_context->mempool ? m_context->mempool->DynamicMemoryUsage()
                                       : 0;
         }
         bool getHeaderTip(int &height, int64_t &block_time) override {
             LOCK(::cs_main);
             auto best_header = chainman().m_best_header;
             if (best_header) {
                 height = best_header->nHeight;
                 block_time = best_header->GetBlockTime();
                 return true;
             }
             return false;
         }
         int getNumBlocks() override {
             LOCK(::cs_main);
             return chainman().ActiveChain().Height();
         }
         BlockHash getBestBlockHash() override {
             const CBlockIndex *tip =
                 WITH_LOCK(::cs_main, return chainman().ActiveTip());
             return tip ? tip->GetBlockHash()
                        : chainman().GetParams().GenesisBlock().GetHash();
         }
         int64_t getLastBlockTime() override {
             LOCK(::cs_main);
             if (chainman().ActiveChain().Tip()) {
                 return chainman().ActiveChain().Tip()->GetBlockTime();
             }
             // Genesis block's time of current network
             return chainman().GetParams().GenesisBlock().GetBlockTime();
         }
         double getVerificationProgress() override {
             const CBlockIndex *tip;
             {
                 LOCK(::cs_main);
                 tip = chainman().ActiveChain().Tip();
             }
             return GuessVerificationProgress(chainman().GetParams().TxData(),
                                              tip);
         }
         bool isInitialBlockDownload() override {
             return chainman().ActiveChainstate().IsInitialBlockDownload();
         }
         bool getReindex() override { return node::fReindex; }
         bool getImporting() override { return node::fImporting; }
         void setNetworkActive(bool active) override {
             if (m_context->connman) {
                 m_context->connman->SetNetworkActive(active);
             }
         }
         bool getNetworkActive() override {
             return m_context->connman && m_context->connman->GetNetworkActive();
         }
         CFeeRate getDustRelayFee() override { return ::dustRelayFee; }
         UniValue executeRpc(const Config &config, const std::string &command,
                             const UniValue &params,
                             const std::string &uri) override {
             JSONRPCRequest req;
             req.context = m_context;
             req.params = params;
             req.strMethod = command;
             req.URI = uri;
             return ::tableRPC.execute(config, req);
         }
         std::vector<std::string> listRpcCommands() override {
             return ::tableRPC.listCommands();
         }
         void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) override {
             RPCSetTimerInterfaceIfUnset(iface);
         }
         void rpcUnsetTimerInterface(RPCTimerInterface *iface) override {
             RPCUnsetTimerInterface(iface);
         }
         bool getUnspentOutput(const COutPoint &output, Coin &coin) override {
             LOCK(::cs_main);
             return chainman().ActiveChainstate().CoinsTip().GetCoin(output,
                                                                     coin);
         }
         WalletClient &walletClient() override {
             return *Assert(m_context->wallet_client);
         }
         std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override {
             return MakeHandler(::uiInterface.InitMessage_connect(fn));
         }
         std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) override {
             return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn));
         }
         std::unique_ptr<Handler> handleQuestion(QuestionFn fn) override {
             return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn));
         }
         std::unique_ptr<Handler>
         handleShowProgress(ShowProgressFn fn) override {
             return MakeHandler(::uiInterface.ShowProgress_connect(fn));
         }
         std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(
             NotifyNumConnectionsChangedFn fn) override {
             return MakeHandler(
                 ::uiInterface.NotifyNumConnectionsChanged_connect(fn));
         }
         std::unique_ptr<Handler> handleNotifyNetworkActiveChanged(
             NotifyNetworkActiveChangedFn fn) override {
             return MakeHandler(
                 ::uiInterface.NotifyNetworkActiveChanged_connect(fn));
         }
         std::unique_ptr<Handler>
         handleNotifyAlertChanged(NotifyAlertChangedFn fn) override {
             return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn));
         }
         std::unique_ptr<Handler>
         handleBannedListChanged(BannedListChangedFn fn) override {
             return MakeHandler(::uiInterface.BannedListChanged_connect(fn));
         }
         std::unique_ptr<Handler>
         handleNotifyBlockTip(NotifyBlockTipFn fn) override {
             return MakeHandler(::uiInterface.NotifyBlockTip_connect(
                 [fn](SynchronizationState sync_state,
                      const CBlockIndex *block) {
                     fn(sync_state,
                        BlockTip{block->nHeight, block->GetBlockTime(),
                                 block->GetBlockHash()},
                        GuessVerificationProgress(Params().TxData(), block));
                 }));
         }
         std::unique_ptr<Handler>
         handleNotifyHeaderTip(NotifyHeaderTipFn fn) override {
             /* verification progress is unused when a header was received */
             return MakeHandler(::uiInterface.NotifyHeaderTip_connect(
                 [fn](SynchronizationState sync_state,
                      const CBlockIndex *block) {
                     fn(sync_state,
                        BlockTip{block->nHeight, block->GetBlockTime(),
                                 block->GetBlockHash()},
                        0);
                 }));
         }
         NodeContext *context() override { return m_context; }
         void setContext(NodeContext *context) override { m_context = context; }
         NodeContext *m_context{nullptr};
     };
 
     bool FillBlock(const CBlockIndex *index, const FoundBlock &block,
                    UniqueLock<RecursiveMutex> &lock, const CChain &active) {
         if (!index) {
             return false;
         }
         if (block.m_hash) {
             *block.m_hash = index->GetBlockHash();
         }
         if (block.m_height) {
             *block.m_height = index->nHeight;
         }
         if (block.m_time) {
             *block.m_time = index->GetBlockTime();
         }
         if (block.m_max_time) {
             *block.m_max_time = index->GetBlockTimeMax();
         }
         if (block.m_mtp_time) {
             *block.m_mtp_time = index->GetMedianTimePast();
         }
         if (block.m_in_active_chain) {
             *block.m_in_active_chain = active[index->nHeight] == index;
         }
         if (block.m_next_block) {
             FillBlock(active[index->nHeight] == index
                           ? active[index->nHeight + 1]
                           : nullptr,
                       *block.m_next_block, lock, active);
         }
         if (block.m_data) {
             REVERSE_LOCK(lock);
             if (!ReadBlockFromDisk(*block.m_data, index,
                                    Params().GetConsensus())) {
                 block.m_data->SetNull();
             }
         }
         return true;
     }
 
     class NotificationsProxy : public CValidationInterface {
     public:
         explicit NotificationsProxy(
             std::shared_ptr<Chain::Notifications> notifications)
             : m_notifications(std::move(notifications)) {}
         virtual ~NotificationsProxy() = default;
         void TransactionAddedToMempool(const CTransactionRef &tx,
                                        std::shared_ptr<const std::vector<Coin>>,
                                        uint64_t mempool_sequence) override {
             m_notifications->transactionAddedToMempool(tx, mempool_sequence);
         }
         void TransactionRemovedFromMempool(const CTransactionRef &tx,
                                            MemPoolRemovalReason reason,
                                            uint64_t mempool_sequence) override {
             m_notifications->transactionRemovedFromMempool(tx, reason,
                                                            mempool_sequence);
         }
         void BlockConnected(const std::shared_ptr<const CBlock> &block,
                             const CBlockIndex *index) override {
             m_notifications->blockConnected(*block, index->nHeight);
         }
         void BlockDisconnected(const std::shared_ptr<const CBlock> &block,
                                const CBlockIndex *index) override {
             m_notifications->blockDisconnected(*block, index->nHeight);
         }
         void UpdatedBlockTip(const CBlockIndex *index,
                              const CBlockIndex *fork_index,
                              bool is_ibd) override {
             m_notifications->updatedBlockTip();
         }
         void ChainStateFlushed(const CBlockLocator &locator) override {
             m_notifications->chainStateFlushed(locator);
         }
         std::shared_ptr<Chain::Notifications> m_notifications;
     };
 
     class NotificationsHandlerImpl : public Handler {
     public:
         explicit NotificationsHandlerImpl(
             std::shared_ptr<Chain::Notifications> notifications)
             : m_proxy(std::make_shared<NotificationsProxy>(
                   std::move(notifications))) {
             RegisterSharedValidationInterface(m_proxy);
         }
         ~NotificationsHandlerImpl() override { disconnect(); }
         void disconnect() override {
             if (m_proxy) {
                 UnregisterSharedValidationInterface(m_proxy);
                 m_proxy.reset();
             }
         }
         std::shared_ptr<NotificationsProxy> m_proxy;
     };
 
     class RpcHandlerImpl : public Handler {
     public:
         explicit RpcHandlerImpl(const CRPCCommand &command)
             : m_command(command), m_wrapped_command(&command) {
             m_command.actor = [this](const Config &config,
                                      const JSONRPCRequest &request,
                                      UniValue &result, bool last_handler) {
                 if (!m_wrapped_command) {
                     return false;
                 }
                 try {
                     return m_wrapped_command->actor(config, request, result,
                                                     last_handler);
                 } catch (const UniValue &e) {
                     // If this is not the last handler and a wallet not found
                     // exception was thrown, return false so the next handler
                     // can try to handle the request. Otherwise, reraise the
                     // exception.
                     if (!last_handler) {
                         const UniValue &code = e["code"];
                         if (code.isNum() &&
                             code.get_int() == RPC_WALLET_NOT_FOUND) {
                             return false;
                         }
                     }
                     throw;
                 }
             };
             ::tableRPC.appendCommand(m_command.name, &m_command);
         }
 
         void disconnect() final {
             if (m_wrapped_command) {
                 m_wrapped_command = nullptr;
                 ::tableRPC.removeCommand(m_command.name, &m_command);
             }
         }
 
         ~RpcHandlerImpl() override { disconnect(); }
 
         CRPCCommand m_command;
         const CRPCCommand *m_wrapped_command;
     };
 
     class ChainImpl : public Chain {
     private:
         ChainstateManager &chainman() { return *Assert(m_node.chainman); }
 
     public:
         explicit ChainImpl(NodeContext &node, const CChainParams &params)
             : m_node(node), m_params(params) {}
         std::optional<int> getHeight() override {
             LOCK(::cs_main);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             int height = active.Height();
             if (height >= 0) {
                 return height;
             }
             return std::nullopt;
         }
         BlockHash getBlockHash(int height) override {
             LOCK(::cs_main);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             CBlockIndex *block = active[height];
             assert(block);
             return block->GetBlockHash();
         }
         bool haveBlockOnDisk(int height) override {
             LOCK(cs_main);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             CBlockIndex *block = active[height];
             return block && (block->nStatus.hasData() != 0) && block->nTx > 0;
         }
         CBlockLocator getTipLocator() override {
             LOCK(cs_main);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             return active.GetLocator();
         }
         std::optional<int>
         findLocatorFork(const CBlockLocator &locator) override {
             LOCK(cs_main);
             const Chainstate &active =
                 Assert(m_node.chainman)->ActiveChainstate();
             if (const CBlockIndex *fork =
                     active.FindForkInGlobalIndex(locator)) {
                 return fork->nHeight;
             }
             return std::nullopt;
         }
         bool findBlock(const BlockHash &hash,
                        const FoundBlock &block) override {
             WAIT_LOCK(cs_main, lock);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash),
                              block, lock, active);
         }
         bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height,
                                              const FoundBlock &block) override {
             WAIT_LOCK(cs_main, lock);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             return FillBlock(active.FindEarliestAtLeast(min_time, min_height),
                              block, lock, active);
         }
         bool findAncestorByHeight(const BlockHash &block_hash,
                                   int ancestor_height,
                                   const FoundBlock &ancestor_out) override {
             WAIT_LOCK(cs_main, lock);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             if (const CBlockIndex *block =
                     m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) {
                 if (const CBlockIndex *ancestor =
                         block->GetAncestor(ancestor_height)) {
                     return FillBlock(ancestor, ancestor_out, lock, active);
                 }
             }
             return FillBlock(nullptr, ancestor_out, lock, active);
         }
         bool findAncestorByHash(const BlockHash &block_hash,
                                 const BlockHash &ancestor_hash,
                                 const FoundBlock &ancestor_out) override {
             WAIT_LOCK(cs_main, lock);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             const CBlockIndex *block =
                 m_node.chainman->m_blockman.LookupBlockIndex(block_hash);
             const CBlockIndex *ancestor =
                 m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash);
             if (block && ancestor &&
                 block->GetAncestor(ancestor->nHeight) != ancestor) {
                 ancestor = nullptr;
             }
             return FillBlock(ancestor, ancestor_out, lock, active);
         }
         bool findCommonAncestor(const BlockHash &block_hash1,
                                 const BlockHash &block_hash2,
                                 const FoundBlock &ancestor_out,
                                 const FoundBlock &block1_out,
                                 const FoundBlock &block2_out) override {
             WAIT_LOCK(cs_main, lock);
             const CChain &active = Assert(m_node.chainman)->ActiveChain();
             const CBlockIndex *block1 =
                 m_node.chainman->m_blockman.LookupBlockIndex(block_hash1);
             const CBlockIndex *block2 =
                 m_node.chainman->m_blockman.LookupBlockIndex(block_hash2);
             const CBlockIndex *ancestor =
                 block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr;
             // Using & instead of && below to avoid short circuiting and leaving
             // output uninitialized. Cast bool to int to avoid
             // -Wbitwise-instead-of-logical compiler warnings.
             return int{FillBlock(ancestor, ancestor_out, lock, active)} &
                    int{FillBlock(block1, block1_out, lock, active)} &
                    int{FillBlock(block2, block2_out, lock, active)};
         }
         void findCoins(std::map<COutPoint, Coin> &coins) override {
             return FindCoins(m_node, coins);
         }
         double guessVerificationProgress(const BlockHash &block_hash) override {
             LOCK(cs_main);
             return GuessVerificationProgress(
                 chainman().GetParams().TxData(),
                 chainman().m_blockman.LookupBlockIndex(block_hash));
         }
         bool hasBlocks(const BlockHash &block_hash, int min_height,
                        std::optional<int> max_height) override {
             // hasBlocks returns true if all ancestors of block_hash in
             // specified range have block data (are not pruned), false if any
             // ancestors in specified range are missing data.
             //
             // For simplicity and robustness, min_height and max_height are only
             // used to limit the range, and passing min_height that's too low or
             // max_height that's too high will not crash or change the result.
             LOCK(::cs_main);
             if (const CBlockIndex *block =
                     chainman().m_blockman.LookupBlockIndex(block_hash)) {
                 if (max_height && block->nHeight >= *max_height) {
                     block = block->GetAncestor(*max_height);
                 }
                 for (; block->nStatus.hasData(); block = block->pprev) {
                     // Check pprev to not segfault if min_height is too low
                     if (block->nHeight <= min_height || !block->pprev) {
                         return true;
                     }
                 }
             }
             return false;
         }
         bool broadcastTransaction(const Config &config,
                                   const CTransactionRef &tx,
                                   const Amount &max_tx_fee, bool relay,
                                   std::string &err_string) override {
             const TransactionError err =
                 BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay,
                                      /*wait_callback=*/false);
             // Chain clients only care about failures to accept the tx to the
             // mempool. Disregard non-mempool related failures. Note: this will
             // need to be updated if BroadcastTransactions() is updated to
             // return other non-mempool failures that Chain clients do not need
             // to know about.
             return err == TransactionError::OK;
         }
         CFeeRate estimateFee() const override {
             if (!m_node.mempool) {
                 return {};
             }
             return m_node.mempool->estimateFee();
         }
         CFeeRate relayMinFee() override { return ::minRelayTxFee; }
         CFeeRate relayDustFee() override { return ::dustRelayFee; }
         bool havePruned() override {
             LOCK(cs_main);
             return m_node.chainman->m_blockman.m_have_pruned;
         }
         bool isReadyToBroadcast() override {
             return !chainman().m_blockman.LoadingBlocks() &&
                    !isInitialBlockDownload();
         }
         bool isInitialBlockDownload() override {
             return chainman().ActiveChainstate().IsInitialBlockDownload();
         }
         bool shutdownRequested() override { return ShutdownRequested(); }
         int64_t getAdjustedTime() override { return GetAdjustedTime(); }
         void initMessage(const std::string &message) override {
             ::uiInterface.InitMessage(message);
         }
         void initWarning(const bilingual_str &message) override {
             InitWarning(message);
         }
         void initError(const bilingual_str &message) override {
             InitError(message);
         }
         void showProgress(const std::string &title, int progress,
                           bool resume_possible) override {
             ::uiInterface.ShowProgress(title, progress, resume_possible);
         }
         std::unique_ptr<Handler> handleNotifications(
             std::shared_ptr<Notifications> notifications) override {
             return std::make_unique<NotificationsHandlerImpl>(
                 std::move(notifications));
         }
         void
         waitForNotificationsIfTipChanged(const BlockHash &old_tip) override {
             if (!old_tip.IsNull()) {
                 LOCK(::cs_main);
                 const CChain &active = Assert(m_node.chainman)->ActiveChain();
                 if (old_tip == active.Tip()->GetBlockHash()) {
                     return;
                 }
             }
             SyncWithValidationInterfaceQueue();
         }
 
         std::unique_ptr<Handler>
         handleRpc(const CRPCCommand &command) override {
             return std::make_unique<RpcHandlerImpl>(command);
         }
         bool rpcEnableDeprecated(const std::string &method) override {
             return IsDeprecatedRPCEnabled(gArgs, method);
         }
         void rpcRunLater(const std::string &name, std::function<void()> fn,
                          int64_t seconds) override {
             RPCRunLater(name, std::move(fn), seconds);
         }
         int rpcSerializationFlags() override { return RPCSerializationFlags(); }
         util::SettingsValue getRwSetting(const std::string &name) override {
             util::SettingsValue result;
             gArgs.LockSettings([&](const util::Settings &settings) {
                 if (const util::SettingsValue *value =
                         util::FindKey(settings.rw_settings, name)) {
                     result = *value;
                 }
             });
             return result;
         }
         bool updateRwSetting(const std::string &name,
                              const util::SettingsValue &value) override {
             gArgs.LockSettings([&](util::Settings &settings) {
                 if (value.isNull()) {
                     settings.rw_settings.erase(name);
                 } else {
                     settings.rw_settings[name] = value;
                 }
             });
             return gArgs.WriteSettingsFile();
         }
         void requestMempoolTransactions(Notifications &notifications) override {
             if (!m_node.mempool) {
                 return;
             }
             LOCK2(::cs_main, m_node.mempool->cs);
             for (const CTxMemPoolEntry &entry : m_node.mempool->mapTx) {
                 notifications.transactionAddedToMempool(entry.GetSharedTx(),
                                                         /*mempool_sequence=*/0);
             }
         }
         const CChainParams &params() const override { return m_params; }
         NodeContext &m_node;
         const CChainParams &m_params;
     };
 } // namespace
 } // namespace node
 
 namespace interfaces {
 std::unique_ptr<Node> MakeNode(node::NodeContext *context) {
     return std::make_unique<node::NodeImpl>(context);
 }
 std::unique_ptr<Chain> MakeChain(node::NodeContext &node,
                                  const CChainParams &params) {
     return std::make_unique<node::ChainImpl>(node, params);
 }
 } // namespace interfaces
diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp
index c2cecb23f..b4fbb6c07 100644
--- a/src/test/settings_tests.cpp
+++ b/src/test/settings_tests.cpp
@@ -1,300 +1,310 @@
 // Copyright (c) 2011-2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <util/settings.h>
 
 #include <fs.h>
 #include <test/util/setup_common.h>
 #include <test/util/str.h>
 #include <util/strencodings.h>
 #include <util/string.h>
 #include <util/system.h>
 
 #include <univalue.h>
 
 #include <boost/test/unit_test.hpp>
 
 #include <fstream>
 #include <map>
 #include <string>
 #include <system_error>
 #include <vector>
 
 inline bool operator==(const util::SettingsValue &a,
                        const util::SettingsValue &b) {
     return a.write() == b.write();
 }
 
 inline std::ostream &operator<<(std::ostream &os,
                                 const util::SettingsValue &value) {
     os << value.write();
     return os;
 }
 
 inline std::ostream &
 operator<<(std::ostream &os,
            const std::pair<std::string, util::SettingsValue> &kv) {
     util::SettingsValue out(util::SettingsValue::VOBJ);
     out.__pushKV(kv.first, kv.second);
     os << out.write();
     return os;
 }
 
 inline void WriteText(const fs::path &path, const std::string &text) {
     std::ofstream file;
     file.open(path);
     file << text;
 }
 
 BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup)
 
 BOOST_AUTO_TEST_CASE(ReadWrite) {
     fs::path path = m_args.GetDataDirBase() / "settings.json";
 
     WriteText(path, R"({
         "string": "string",
         "num": 5,
         "bool": true,
         "null": null
     })");
 
     std::map<std::string, util::SettingsValue> expected{
         {"string", "string"},
         {"num", 5},
         {"bool", true},
         {"null", {}},
     };
 
     // Check file read.
     std::map<std::string, util::SettingsValue> values;
     std::vector<std::string> errors;
     BOOST_CHECK(util::ReadSettings(path, values, errors));
     BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(),
                                   expected.begin(), expected.end());
     BOOST_CHECK(errors.empty());
 
     // Check no errors if file doesn't exist.
     fs::remove(path);
     BOOST_CHECK(util::ReadSettings(path, values, errors));
     BOOST_CHECK(values.empty());
     BOOST_CHECK(errors.empty());
 
     // Check duplicate keys not allowed
     WriteText(path, R"({
         "dupe": "string",
         "dupe": "dupe"
     })");
     BOOST_CHECK(!util::ReadSettings(path, values, errors));
     std::vector<std::string> dup_keys = {
         strprintf("Found duplicate key dupe in settings file %s",
                   fs::PathToString(path))};
     BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(),
                                   dup_keys.begin(), dup_keys.end());
 
     // Check non-kv json files not allowed
     WriteText(path, R"("non-kv")");
     BOOST_CHECK(!util::ReadSettings(path, values, errors));
     std::vector<std::string> non_kv = {
         strprintf("Found non-object value \"non-kv\" in settings file %s",
                   fs::PathToString(path))};
     BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), non_kv.begin(),
                                   non_kv.end());
 
     // Check invalid json not allowed
     WriteText(path, R"(invalid json)");
     BOOST_CHECK(!util::ReadSettings(path, values, errors));
     std::vector<std::string> fail_parse = {
         strprintf("Unable to parse settings file %s", fs::PathToString(path))};
     BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(),
                                   fail_parse.begin(), fail_parse.end());
 }
 
 //! Check settings struct contents against expected json strings.
 static void CheckValues(const util::Settings &settings,
                         const std::string &single_val,
                         const std::string &list_val) {
-    util::SettingsValue single_value =
-        GetSetting(settings, "section", "name", false, false);
+    util::SettingsValue single_value = GetSetting(
+        settings, "section", "name", /*ignore_default_section_config=*/false,
+        /*ignore_nonpersistent=*/false, /*get_chain_name=*/false);
     util::SettingsValue list_value(util::SettingsValue::VARR);
     for (const auto &item :
-         GetSettingsList(settings, "section", "name", false)) {
+         GetSettingsList(settings, "section", "name",
+                         /*ignore_default_section_config=*/false)) {
         list_value.push_back(item);
     }
     BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val);
     BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val);
 };
 
 // Simple settings merge test case.
 BOOST_AUTO_TEST_CASE(Simple) {
     util::Settings settings;
     settings.command_line_options["name"].push_back("val1");
     settings.command_line_options["name"].push_back("val2");
     settings.ro_config["section"]["name"].push_back(2);
 
     // The last given arg takes precedence when specified via commandline.
     CheckValues(settings, R"("val2")", R"(["val1","val2",2])");
 
     util::Settings settings2;
     settings2.ro_config["section"]["name"].push_back("val2");
     settings2.ro_config["section"]["name"].push_back("val3");
 
     // The first given arg takes precedence when specified via config file.
     CheckValues(settings2, R"("val2")", R"(["val2","val3"])");
 }
 
 // Confirm that a high priority setting overrides a lower priority setting even
 // if the high priority setting is null. This behavior is useful for a high
 // priority setting source to be able to effectively reset any setting back to
 // its default value.
 BOOST_AUTO_TEST_CASE(NullOverride) {
     util::Settings settings;
     settings.command_line_options["name"].push_back("value");
-    BOOST_CHECK_EQUAL(
-        R"("value")",
-        GetSetting(settings, "section", "name", false, false).write().c_str());
+    BOOST_CHECK_EQUAL(R"("value")",
+                      GetSetting(settings, "section", "name",
+                                 /*ignore_default_section_config=*/false,
+                                 /*ignore_nonpersistent=*/false,
+                                 /*get_chain_name=*/false)
+                          .write()
+                          .c_str());
     settings.forced_settings["name"] = {};
-    BOOST_CHECK_EQUAL(
-        R"(null)",
-        GetSetting(settings, "section", "name", false, false).write().c_str());
+    BOOST_CHECK_EQUAL(R"(null)",
+                      GetSetting(settings, "section", "name",
+                                 /*ignore_default_section_config=*/false,
+                                 /*ignore_nonpersistent=*/false,
+                                 /*get_chain_name=*/false)
+                          .write()
+                          .c_str());
 }
 
 // Test different ways settings can be merged, and verify results. This test can
 // be used to confirm that updates to settings code don't change behavior
 // unintentionally.
 struct MergeTestingSetup : public BasicTestingSetup {
     //! Max number of actions to sequence together. Can decrease this when
     //! debugging to make test results easier to understand.
     static constexpr int MAX_ACTIONS = 3;
 
     enum Action { END, SET, NEGATE, SECTION_SET, SECTION_NEGATE };
     using ActionList = Action[MAX_ACTIONS];
 
     //! Enumerate all possible test configurations.
     template <typename Fn> void ForEachMergeSetup(Fn &&fn) {
         ActionList arg_actions = {};
         // command_line_options do not have sections. Only iterate over SET and
         // NEGATE
         ForEachNoDup(arg_actions, SET, NEGATE, [&] {
             ActionList conf_actions = {};
             ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] {
                 for (bool force_set : {false, true}) {
                     for (bool ignore_default_section_config : {false, true}) {
                         fn(arg_actions, conf_actions, force_set,
                            ignore_default_section_config);
                     }
                 }
             });
         });
     }
 };
 
 // Regression test covering different ways config settings can be merged. The
 // test parses and merges settings, representing the results as strings that get
 // compared against an expected hash. To debug, the result strings can be dumped
 // to a file (see comments below).
 BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup) {
     CHash256 out_sha;
     FILE *out_file = nullptr;
     if (const char *out_path = getenv("SETTINGS_MERGE_TEST_OUT")) {
         out_file = fsbridge::fopen(out_path, "w");
         if (!out_file) {
             throw std::system_error(errno, std::generic_category(),
                                     "fopen failed");
         }
     }
 
     const std::string &network = CBaseChainParams::MAIN;
     ForEachMergeSetup([&](const ActionList &arg_actions,
                           const ActionList &conf_actions, bool force_set,
                           bool ignore_default_section_config) {
         std::string desc;
         int value_suffix = 0;
         util::Settings settings;
 
         const std::string &name =
             ignore_default_section_config ? "wallet" : "server";
         auto push_values = [&](Action action, const char *value_prefix,
                                const std::string &name_prefix,
                                std::vector<util::SettingsValue> &dest) {
             if (action == SET || action == SECTION_SET) {
                 for (int i = 0; i < 2; ++i) {
                     dest.push_back(value_prefix + ToString(++value_suffix));
                     desc +=
                         " " + name_prefix + name + "=" + dest.back().get_str();
                 }
             } else if (action == NEGATE || action == SECTION_NEGATE) {
                 dest.push_back(false);
                 desc += " " + name_prefix + "no" + name;
             }
         };
 
         if (force_set) {
             settings.forced_settings[name] = "forced";
             desc += " " + name + "=forced";
         }
         for (Action arg_action : arg_actions) {
             push_values(arg_action, "a", "-",
                         settings.command_line_options[name]);
         }
         for (Action conf_action : conf_actions) {
             bool use_section =
                 conf_action == SECTION_SET || conf_action == SECTION_NEGATE;
             push_values(conf_action, "c", use_section ? network + "." : "",
                         settings.ro_config[use_section ? network : ""][name]);
         }
 
         desc += " || ";
         desc +=
             GetSetting(settings, network, name, ignore_default_section_config,
-                       /* get_chain_name= */ false)
+                       /*ignore_nonpersistent=*/false, /*get_chain_name=*/false)
                 .write();
         desc += " |";
         for (const auto &s : GetSettingsList(settings, network, name,
                                              ignore_default_section_config)) {
             desc += " ";
             desc += s.write();
         }
         desc += " |";
         if (OnlyHasDefaultSectionSetting(settings, network, name)) {
             desc += " ignored";
         }
         desc += "\n";
 
         out_sha.Write(MakeUCharSpan(desc));
         if (out_file) {
             BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) ==
                           desc.size());
         }
     });
 
     if (out_file) {
         if (fclose(out_file)) {
             throw std::system_error(errno, std::generic_category(),
                                     "fclose failed");
         }
         out_file = nullptr;
     }
 
     uint8_t out_sha_bytes[CSHA256::OUTPUT_SIZE];
     out_sha.Finalize(out_sha_bytes);
     std::string out_sha_hex = HexStr(out_sha_bytes);
 
     // If check below fails, should manually dump the results with:
     //
     //   SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin
     //   --run_test=settings_tests/Merge
     //
     // And verify diff against previous results to make sure the changes are
     // expected.
     //
     // Results file is formatted like:
     //
     //   <input> || GetSetting() | GetSettingsList() |
     //   OnlyHasDefaultSectionSetting()
     BOOST_CHECK_EQUAL(
         out_sha_hex,
         "79db02d74e3e193196541b67c068b40ebd0c124a24b3ecbe9cbf7e85b1c4ba7a");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util/settings.cpp b/src/util/settings.cpp
index fde3a808b..d15b7a724 100644
--- a/src/util/settings.cpp
+++ b/src/util/settings.cpp
@@ -1,300 +1,306 @@
 // Copyright (c) 2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <fs.h>
 #include <util/settings.h>
 
 #include <tinyformat.h>
 #include <univalue.h>
 
 #include <fstream>
 #include <map>
 #include <string>
 #include <vector>
 
 namespace util {
 namespace {
 
     enum class Source {
         FORCED,
         COMMAND_LINE,
         RW_SETTINGS,
         CONFIG_FILE_NETWORK_SECTION,
         CONFIG_FILE_DEFAULT_SECTION
     };
 
     //! Merge settings from multiple sources in precedence order:
     //! Forced config > command line > read-write settings file > config file
     //! network-specific section > config file default section
     //!
     //! This function is provided with a callback function fn that contains
     //! specific logic for how to merge the sources.
     template <typename Fn>
     static void MergeSettings(const Settings &settings,
                               const std::string &section,
                               const std::string &name, Fn &&fn) {
         // Merge in the forced settings
         if (auto *value = FindKey(settings.forced_settings, name)) {
             fn(SettingsSpan(*value), Source::FORCED);
         }
         // Merge in the command-line options
         if (auto *values = FindKey(settings.command_line_options, name)) {
             fn(SettingsSpan(*values), Source::COMMAND_LINE);
         }
         // Merge in the read-write settings
         if (const SettingsValue *value = FindKey(settings.rw_settings, name)) {
             fn(SettingsSpan(*value), Source::RW_SETTINGS);
         }
         // Merge in the network-specific section of the config file
         if (!section.empty()) {
             if (auto *map = FindKey(settings.ro_config, section)) {
                 if (auto *values = FindKey(*map, name)) {
                     fn(SettingsSpan(*values),
                        Source::CONFIG_FILE_NETWORK_SECTION);
                 }
             }
         }
         // Merge in the default section of the config file
         if (auto *map = FindKey(settings.ro_config, "")) {
             if (auto *values = FindKey(*map, name)) {
                 fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
             }
         }
     }
 } // namespace
 
 bool ReadSettings(const fs::path &path,
                   std::map<std::string, SettingsValue> &values,
                   std::vector<std::string> &errors) {
     values.clear();
     errors.clear();
 
     // Ok for file to not exist
     if (!fs::exists(path)) {
         return true;
     }
 
     std::ifstream file;
     file.open(path);
     if (!file.is_open()) {
         errors.emplace_back(
             strprintf("%s. Please check permissions.", fs::PathToString(path)));
         return false;
     }
 
     SettingsValue in;
     if (!in.read(std::string{std::istreambuf_iterator<char>(file),
                              std::istreambuf_iterator<char>()})) {
         errors.emplace_back(strprintf("Unable to parse settings file %s",
                                       fs::PathToString(path)));
         return false;
     }
 
     if (file.fail()) {
         errors.emplace_back(strprintf("Failed reading settings file %s",
                                       fs::PathToString(path)));
         return false;
     }
     // Done with file descriptor. Release while copying data.
     file.close();
 
     if (!in.isObject()) {
         errors.emplace_back(
             strprintf("Found non-object value %s in settings file %s",
                       in.write(), fs::PathToString(path)));
         return false;
     }
 
     const std::vector<std::string> &in_keys = in.getKeys();
     const std::vector<SettingsValue> &in_values = in.getValues();
     for (size_t i = 0; i < in_keys.size(); ++i) {
         auto inserted = values.emplace(in_keys[i], in_values[i]);
         if (!inserted.second) {
             errors.emplace_back(
                 strprintf("Found duplicate key %s in settings file %s",
                           in_keys[i], fs::PathToString(path)));
         }
     }
     return errors.empty();
 }
 
 bool WriteSettings(const fs::path &path,
                    const std::map<std::string, SettingsValue> &values,
                    std::vector<std::string> &errors) {
     SettingsValue out(SettingsValue::VOBJ);
     for (const auto &value : values) {
         out.__pushKV(value.first, value.second);
     }
     std::ofstream file;
     file.open(path);
     if (file.fail()) {
         errors.emplace_back(
             strprintf("Error: Unable to open settings file %s for writing",
                       fs::PathToString(path)));
         return false;
     }
     file << out.write(/* prettyIndent= */ 1, /* indentLevel= */ 4) << std::endl;
     file.close();
     return true;
 }
 
 SettingsValue GetSetting(const Settings &settings, const std::string &section,
                          const std::string &name,
                          bool ignore_default_section_config,
-                         bool get_chain_name) {
+                         bool ignore_nonpersistent, bool get_chain_name) {
     SettingsValue result;
     // Done merging any more settings sources.
     bool done = false;
     MergeSettings(
         settings, section, name, [&](SettingsSpan span, Source source) {
             // Weird behavior preserved for backwards compatibility: Apply
             // negated setting even if non-negated setting would be ignored. A
             // negated value in the default section is applied to network
             // specific options, even though normal non-negated values there
             // would be ignored.
             const bool never_ignore_negated_setting = span.last_negated();
 
             // Weird behavior preserved for backwards compatibility: Take first
             // assigned value instead of last. In general, later settings take
             // precedence over early settings, but for backwards compatibility
             // in the config file the precedence is reversed for all settings
             // except chain name settings.
             const bool reverse_precedence =
                 (source == Source::CONFIG_FILE_NETWORK_SECTION ||
                  source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
                 !get_chain_name;
 
             // Weird behavior preserved for backwards compatibility: Negated
             // -regtest and -testnet arguments which you would expect to
             // override values set in the configuration file are currently
             // accepted but silently ignored. It would be better to apply these
             // just like other negated values, or at least warn they are
             // ignored.
             const bool skip_negated_command_line = get_chain_name;
 
             if (done) {
                 return;
             }
 
             // Ignore settings in default config section if requested.
             if (ignore_default_section_config &&
                 source == Source::CONFIG_FILE_DEFAULT_SECTION &&
                 !never_ignore_negated_setting) {
                 return;
             }
 
+            // Ignore nonpersistent settings if requested.
+            if (ignore_nonpersistent &&
+                (source == Source::COMMAND_LINE || source == Source::FORCED)) {
+                return;
+            }
+
             // Skip negated command line settings.
             if (skip_negated_command_line && span.last_negated()) {
                 return;
             }
 
             if (!span.empty()) {
                 result = reverse_precedence ? span.begin()[0] : span.end()[-1];
                 done = true;
             } else if (span.last_negated()) {
                 result = false;
                 done = true;
             }
         });
     return result;
 }
 
 std::vector<SettingsValue> GetSettingsList(const Settings &settings,
                                            const std::string &section,
                                            const std::string &name,
                                            bool ignore_default_section_config) {
     std::vector<SettingsValue> result;
     // Done merging any more settings sources.
     bool done = false;
     bool prev_negated_empty = false;
     MergeSettings(
         settings, section, name, [&](SettingsSpan span, Source source) {
             // Weird behavior preserved for backwards compatibility: Apply
             // config file settings even if negated on command line. Negating a
             // setting on command line will ignore earlier settings on the
             // command line and ignore settings in the config file, unless the
             // negated command line value is followed by non-negated value, in
             // which case config file settings will be brought back from the
             // dead (but earlier command line settings will still be ignored).
             const bool add_zombie_config_values =
                 (source == Source::CONFIG_FILE_NETWORK_SECTION ||
                  source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
                 !prev_negated_empty;
 
             // Ignore settings in default config section if requested.
             if (ignore_default_section_config &&
                 source == Source::CONFIG_FILE_DEFAULT_SECTION) {
                 return;
             }
 
             // Add new settings to the result if isn't already complete, or if
             // the values are zombies.
             if (!done || add_zombie_config_values) {
                 for (const auto &value : span) {
                     if (value.isArray()) {
                         result.insert(result.end(), value.getValues().begin(),
                                       value.getValues().end());
                     } else {
                         result.push_back(value);
                     }
                 }
             }
 
             // If a setting was negated, or if a setting was forced, set done to
             // true to ignore any later lower priority settings.
             done |= span.negated() > 0 || source == Source::FORCED;
 
             // Update the negated and empty state used for the zombie values
             // check.
             prev_negated_empty |= span.last_negated() && result.empty();
         });
     return result;
 }
 
 bool OnlyHasDefaultSectionSetting(const Settings &settings,
                                   const std::string &section,
                                   const std::string &name) {
     bool has_default_section_setting = false;
     bool has_other_setting = false;
     MergeSettings(
         settings, section, name, [&](SettingsSpan span, Source source) {
             if (span.empty()) {
                 return;
             } else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) {
                 has_default_section_setting = true;
             } else {
                 has_other_setting = true;
             }
         });
     // If a value is set in the default section and not explicitly overwritten
     // by the user on the command line or in a different section, then we want
     // to enable warnings about the value being ignored.
     return has_default_section_setting && !has_other_setting;
 }
 
 SettingsSpan::SettingsSpan(const std::vector<SettingsValue> &vec) noexcept
     : SettingsSpan(vec.data(), vec.size()) {}
 const SettingsValue *SettingsSpan::begin() const {
     return data + negated();
 }
 const SettingsValue *SettingsSpan::end() const {
     return data + size;
 }
 bool SettingsSpan::empty() const {
     return size == 0 || last_negated();
 }
 bool SettingsSpan::last_negated() const {
     return size > 0 && data[size - 1].isFalse();
 }
 size_t SettingsSpan::negated() const {
     for (size_t i = size; i > 0; --i) {
         if (data[i - 1].isFalse()) {
             // Return number of negated values (position of last false value)
             return i;
         }
     }
     return 0;
 }
 
 } // namespace util
diff --git a/src/util/settings.h b/src/util/settings.h
index e695728a3..87351ba5d 100644
--- a/src/util/settings.h
+++ b/src/util/settings.h
@@ -1,117 +1,122 @@
 // Copyright (c) 2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #ifndef BITCOIN_UTIL_SETTINGS_H
 #define BITCOIN_UTIL_SETTINGS_H
 
 #include <fs.h>
 
 #include <map>
 #include <string>
 #include <vector>
 
 class UniValue;
 
 namespace util {
 
 //! Settings value type (string/integer/boolean/null variant).
 //!
 //! @note UniValue is used here for convenience and because it can be easily
 //!       serialized in a readable format. But any other variant type that can
 //!       be assigned strings, int64_t, and bool values and has get_str(),
 //!       get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and
 //!       isNull() methods can be substituted if there's a need to move away
 //!       from UniValue. (An implementation with boost::variant was posted at
 //!       https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812)
 using SettingsValue = UniValue;
 
 //! Stored settings. This struct combines settings from the command line, a
 //! read-only configuration file, and a read-write runtime settings file.
 struct Settings {
     //! Map of setting name to forced setting value.
     std::map<std::string, SettingsValue> forced_settings;
     //! Map of setting name to list of command line values.
     std::map<std::string, std::vector<SettingsValue>> command_line_options;
     //! Map of setting name to read-write file setting value.
     std::map<std::string, SettingsValue> rw_settings;
     //! Map of config section name and setting name to list of config file
     //! values.
     std::map<std::string, std::map<std::string, std::vector<SettingsValue>>>
         ro_config;
 };
 
 //! Read settings file.
 bool ReadSettings(const fs::path &path,
                   std::map<std::string, SettingsValue> &values,
                   std::vector<std::string> &errors);
 
 //! Write settings file.
 bool WriteSettings(const fs::path &path,
                    const std::map<std::string, SettingsValue> &values,
                    std::vector<std::string> &errors);
 
 //! Get settings value from combined sources: forced settings, command line
 //! arguments, runtime read-write settings, and the read-only config file.
 //!
 //! @param ignore_default_section_config - ignore values in the default section
 //!                                        of the config file (part before any
 //!                                        [section] keywords)
+//! @param ignore_nonpersistent - ignore non-persistent settings values (forced
+//!                               settings values and values specified on the
+//!                               command line). Only return settings in the
+//!                               read-only config and read-write settings
+//!                               files.
 //! @param get_chain_name - enable special backwards compatible behavior
 //!                         for GetChainName
 SettingsValue GetSetting(const Settings &settings, const std::string &section,
                          const std::string &name,
                          bool ignore_default_section_config,
-                         bool get_chain_name);
+                         bool ignore_nonpersistent, bool get_chain_name);
 
 //! Get combined setting value similar to GetSetting(), except if setting was
 //! specified multiple times, return a list of all the values specified.
 std::vector<SettingsValue> GetSettingsList(const Settings &settings,
                                            const std::string &section,
                                            const std::string &name,
                                            bool ignore_default_section_config);
 
 //! Return true if a setting is set in the default config file section, and not
 //! overridden by a higher priority command-line or network section value.
 //!
 //! This is used to provide user warnings about values that might be getting
 //! ignored unintentionally.
 bool OnlyHasDefaultSectionSetting(const Settings &settings,
                                   const std::string &section,
                                   const std::string &name);
 
 //! Accessor for list of settings that skips negated values when iterated over.
 //! The last boolean `false` value in the list and all earlier values are
 //! considered negated.
 struct SettingsSpan {
     explicit SettingsSpan() = default;
     explicit SettingsSpan(const SettingsValue &value) noexcept
         : SettingsSpan(&value, 1) {}
     explicit SettingsSpan(const SettingsValue *dataIn, size_t sizeIn) noexcept
         : data(dataIn), size(sizeIn) {}
     explicit SettingsSpan(const std::vector<SettingsValue> &vec) noexcept;
     //! Pointer to first non-negated value.
     const SettingsValue *begin() const;
     //! Pointer to end of values.
     const SettingsValue *end() const;
     //! True if there are any non-negated values.
     bool empty() const;
     //! True if the last value is negated.
     bool last_negated() const;
     //! Number of negated values.
     size_t negated() const;
 
     const SettingsValue *data = nullptr;
     size_t size = 0;
 };
 
 //! Map lookup helper.
 template <typename Map, typename Key>
 auto FindKey(Map &&map, Key &&key) -> decltype(&map.at(key)) {
     auto it = map.find(key);
     return it == map.end() ? nullptr : &it->second;
 }
 
 } // namespace util
 
 #endif // BITCOIN_UTIL_SETTINGS_H
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 5a5620dca..2cacd8685 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -1,1418 +1,1441 @@
 // Copyright (c) 2009-2010 Satoshi Nakamoto
 // Copyright (c) 2009-2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 #include <util/system.h>
 
 #include <chainparamsbase.h>
 #include <fs.h>
 #include <sync.h>
 #include <util/getuniquepath.h>
 #include <util/strencodings.h>
 #include <util/string.h>
 #include <util/translation.h>
 
 #include <univalue.h>
 
 #include <fstream>
 #include <memory>
 #include <optional>
 #include <string>
 #include <thread>
 #include <typeinfo>
 
 #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__))
 #include <pthread.h>
 #include <pthread_np.h>
 #endif
 
 #ifndef WIN32
 // for posix_fallocate, in config/CMakeLists.txt we check if it is present after
 // this
 #ifdef __linux__
 
 #ifdef _POSIX_C_SOURCE
 #undef _POSIX_C_SOURCE
 #endif
 
 #define _POSIX_C_SOURCE 200112L
 
 #endif // __linux__
 
 #include <algorithm>
 #include <cassert>
 #include <fcntl.h>
 #include <sched.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 
 #else
 
 #ifdef _MSC_VER
 #pragma warning(disable : 4786)
 #pragma warning(disable : 4804)
 #pragma warning(disable : 4805)
 #pragma warning(disable : 4717)
 #endif
 
 #ifndef NOMINMAX
 #define NOMINMAX
 #endif
 #include <codecvt>
 
 #include <io.h> /* for _commit */
 #include <shellapi.h>
 #include <shlobj.h>
 #endif
 
 #ifdef HAVE_MALLOPT_ARENA_MAX
 #include <malloc.h>
 #endif
 
 // Application startup time (used for uptime calculation)
 const int64_t nStartupTime = GetTime();
 
 const char *const BITCOIN_CONF_FILENAME = "bitcoin.conf";
 const char *const BITCOIN_SETTINGS_FILENAME = "settings.json";
 
 ArgsManager gArgs;
 
 /** Mutex to protect dir_locks. */
 static GlobalMutex cs_dir_locks;
 /**
  * A map that contains all the currently held directory locks. After successful
  * locking, these will be held here until the global destructor cleans them up
  * and thus automatically unlocks them, or ReleaseDirectoryLocks is called.
  */
 static std::map<std::string, std::unique_ptr<fsbridge::FileLock>>
     dir_locks GUARDED_BY(cs_dir_locks);
 
 bool LockDirectory(const fs::path &directory, const std::string lockfile_name,
                    bool probe_only) {
     LOCK(cs_dir_locks);
     fs::path pathLockFile = directory / lockfile_name;
 
     // If a lock for this directory already exists in the map, don't try to
     // re-lock it
     if (dir_locks.count(fs::PathToString(pathLockFile))) {
         return true;
     }
 
     // Create empty lock file if it doesn't exist.
     FILE *file = fsbridge::fopen(pathLockFile, "a");
     if (file) {
         fclose(file);
     }
     auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile);
     if (!lock->TryLock()) {
         return error("Error while attempting to lock directory %s: %s",
                      fs::PathToString(directory), lock->GetReason());
     }
     if (!probe_only) {
         // Lock successful and we're not just probing, put it into the map
         dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock));
     }
     return true;
 }
 
 void UnlockDirectory(const fs::path &directory,
                      const std::string &lockfile_name) {
     LOCK(cs_dir_locks);
     dir_locks.erase(fs::PathToString(directory / lockfile_name));
 }
 
 void ReleaseDirectoryLocks() {
     LOCK(cs_dir_locks);
     dir_locks.clear();
 }
 
 bool DirIsWritable(const fs::path &directory) {
     fs::path tmpFile = GetUniquePath(directory);
 
     FILE *file = fsbridge::fopen(tmpFile, "a");
     if (!file) {
         return false;
     }
 
     fclose(file);
     remove(tmpFile);
 
     return true;
 }
 
 bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes) {
     // 50 MiB
     constexpr uint64_t min_disk_space = 52428800;
 
     uint64_t free_bytes_available = fs::space(dir).available;
     return free_bytes_available >= min_disk_space + additional_bytes;
 }
 
 std::streampos GetFileSize(const char *path, std::streamsize max) {
     std::ifstream file{path, std::ios::binary};
     file.ignore(max);
     return file.gcount();
 }
 
 /**
  * Interpret a string argument as a boolean.
  *
  * The definition of atoi() requires that non-numeric string values like "foo",
  * return 0. This means that if a user unintentionally supplies a non-integer
  * argument here, the return value is always false. This means that -foo=false
  * does what the user probably expects, but -foo=true is well defined but does
  * not do what they probably expected.
  *
  * The return value of atoi() is undefined when given input not representable as
  * an int. On most systems this means string value between "-2147483648" and
  * "2147483647" are well defined (this method will return true). Setting
  * -txindex=2147483648 on most systems, however, is probably undefined.
  *
  * For a more extensive discussion of this topic (and a wide range of opinions
  * on the Right Way to change this code), see PR12713.
  */
 static bool InterpretBool(const std::string &strValue) {
     if (strValue.empty()) {
         return true;
     }
     return (atoi(strValue) != 0);
 }
 
 static std::string SettingName(const std::string &arg) {
     return arg.size() > 0 && arg[0] == '-' ? arg.substr(1) : arg;
 }
 
 /**
  * Interpret -nofoo as if the user supplied -foo=0.
  *
  * This method also tracks when the -no form was supplied, and if so, checks
  * whether there was a double-negative (-nofoo=0 -> -foo=1).
  *
  * If there was not a double negative, it removes the "no" from the key
  * and returns false.
  *
  * If there was a double negative, it removes "no" from the key, and
  * returns true.
  *
  * If there was no "no", it returns the string value untouched.
  *
  * Where an option was negated can be later checked using the IsArgNegated()
  * method. One use case for this is to have a way to disable options that are
  * not normally boolean (e.g. using -nodebuglogfile to request that debug log
  * output is not sent to any file at all).
  */
 
 static util::SettingsValue InterpretOption(std::string &section,
                                            std::string &key,
                                            const std::string &value) {
     // Split section name from key name for keys like "testnet.foo" or
     // "regtest.bar"
     size_t option_index = key.find('.');
     if (option_index != std::string::npos) {
         section = key.substr(0, option_index);
         key.erase(0, option_index + 1);
     }
     if (key.substr(0, 2) == "no") {
         key.erase(0, 2);
         // Double negatives like -nofoo=0 are supported (but discouraged)
         if (!InterpretBool(value)) {
             LogPrintf("Warning: parsed potentially confusing double-negative "
                       "-%s=%s\n",
                       key, value);
             return true;
         }
         return false;
     }
     return value;
 }
 
 /**
  * Check settings value validity according to flags.
  *
  * TODO: Add more meaningful error checks here in the future
  * See "here's how the flags are meant to behave" in
  * https://github.com/bitcoin/bitcoin/pull/16097#issuecomment-514627823
  */
 static bool CheckValid(const std::string &key, const util::SettingsValue &val,
                        unsigned int flags, std::string &error) {
     if (val.isBool() && !(flags & ArgsManager::ALLOW_BOOL)) {
         error = strprintf(
             "Negating of -%s is meaningless and therefore forbidden", key);
         return false;
     }
     return true;
 }
 
 // Define default constructor and destructor that are not inline, so code
 // instantiating this class doesn't need to #include class definitions for all
 // members. For example, m_settings has an internal dependency on univalue.
 ArgsManager::ArgsManager() {}
 ArgsManager::~ArgsManager() {}
 
 const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const {
     std::set<std::string> unsuitables;
 
     LOCK(cs_args);
 
     // if there's no section selected, don't worry
     if (m_network.empty()) {
         return std::set<std::string>{};
     }
 
     // if it's okay to use the default section for this network, don't worry
     if (m_network == CBaseChainParams::MAIN) {
         return std::set<std::string>{};
     }
 
     for (const auto &arg : m_network_only_args) {
         if (OnlyHasDefaultSectionSetting(m_settings, m_network,
                                          SettingName(arg))) {
             unsuitables.insert(arg);
         }
     }
     return unsuitables;
 }
 
 const std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const {
     // Section names to be recognized in the config file.
     static const std::set<std::string> available_sections{
         CBaseChainParams::REGTEST, CBaseChainParams::TESTNET,
         CBaseChainParams::MAIN};
 
     LOCK(cs_args);
     std::list<SectionInfo> unrecognized = m_config_sections;
     unrecognized.remove_if([](const SectionInfo &appeared) {
         return available_sections.find(appeared.m_name) !=
                available_sections.end();
     });
     return unrecognized;
 }
 
 void ArgsManager::SelectConfigNetwork(const std::string &network) {
     LOCK(cs_args);
     m_network = network;
 }
 
 bool ParseKeyValue(std::string &key, std::string &val) {
     size_t is_index = key.find('=');
     if (is_index != std::string::npos) {
         val = key.substr(is_index + 1);
         key.erase(is_index);
     }
 #ifdef WIN32
     key = ToLower(key);
     if (key[0] == '/') {
         key[0] = '-';
     }
 #endif
 
     if (key[0] != '-') {
         return false;
     }
 
     // Transform --foo to -foo
     if (key.length() > 1 && key[1] == '-') {
         key.erase(0, 1);
     }
     return true;
 }
 
 bool ArgsManager::ParseParameters(int argc, const char *const argv[],
                                   std::string &error) {
     LOCK(cs_args);
     m_settings.command_line_options.clear();
 
     for (int i = 1; i < argc; i++) {
         std::string key(argv[i]);
 
 #ifdef MAC_OSX
         // At the first time when a user gets the "App downloaded from the
         // internet" warning, and clicks the Open button, macOS passes
         // a unique process serial number (PSN) as -psn_... command-line
         // argument, which we filter out.
         if (key.substr(0, 5) == "-psn_") {
             continue;
         }
 #endif
 
         if (key == "-") {
             // bitcoin-tx using stdin
             break;
         }
         std::string val;
         if (!ParseKeyValue(key, val)) {
             break;
         }
 
         // Transform -foo to foo
         key.erase(0, 1);
         std::string section;
         util::SettingsValue value = InterpretOption(section, key, val);
         std::optional<unsigned int> flags = GetArgFlags('-' + key);
 
         // Unknown command line options and command line options with dot
         // characters (which are returned from InterpretOption with nonempty
         // section strings) are not valid.
         if (!flags || !section.empty()) {
             error = strprintf("Invalid parameter %s", argv[i]);
             return false;
         }
 
         if (!CheckValid(key, value, *flags, error)) {
             return false;
         }
 
         m_settings.command_line_options[key].push_back(value);
     }
 
     // we do not allow -includeconf from command line
     bool success = true;
     if (auto *includes =
             util::FindKey(m_settings.command_line_options, "includeconf")) {
         for (const auto &include : util::SettingsSpan(*includes)) {
             error +=
                 "-includeconf cannot be used from commandline; -includeconf=" +
                 include.get_str() + "\n";
             success = false;
         }
     }
     return success;
 }
 
 std::optional<unsigned int>
 ArgsManager::GetArgFlags(const std::string &name) const {
     LOCK(cs_args);
     for (const auto &arg_map : m_available_args) {
         const auto search = arg_map.second.find(name);
         if (search != arg_map.second.end()) {
             return search->second.m_flags;
         }
     }
     return std::nullopt;
 }
 
 fs::path ArgsManager::GetPathArg(std::string arg,
                                  const fs::path &default_value) const {
     if (IsArgNegated(arg)) {
         return fs::path{};
     }
     std::string path_str = GetArg(arg, "");
     if (path_str.empty()) {
         return default_value;
     }
     fs::path result = fs::PathFromString(path_str).lexically_normal();
     // Remove trailing slash, if present.
     return result.has_filename() ? result : result.parent_path();
 }
 
 const fs::path &ArgsManager::GetBlocksDirPath() const {
     LOCK(cs_args);
     fs::path &path = m_cached_blocks_path;
 
     // Cache the path to avoid calling fs::create_directories on every call of
     // this function
     if (!path.empty()) {
         return path;
     }
 
     if (IsArgSet("-blocksdir")) {
         path = fs::absolute(GetPathArg("-blocksdir"));
         if (!fs::is_directory(path)) {
             path = "";
             return path;
         }
     } else {
         path = GetDataDirBase();
     }
 
     path /= fs::PathFromString(BaseParams().DataDir());
     path /= "blocks";
     fs::create_directories(path);
     return path;
 }
 
 const fs::path &ArgsManager::GetDataDir(bool net_specific) const {
     LOCK(cs_args);
     fs::path &path =
         net_specific ? m_cached_network_datadir_path : m_cached_datadir_path;
 
     // Cache the path to avoid calling fs::create_directories on every call of
     // this function
     if (!path.empty()) {
         return path;
     }
 
     const fs::path datadir{GetPathArg("-datadir")};
     if (!datadir.empty()) {
         path = fs::absolute(datadir);
         if (!fs::is_directory(path)) {
             path = "";
             return path;
         }
     } else {
         path = GetDefaultDataDir();
     }
 
     if (!fs::exists(path)) {
         fs::create_directories(path / "wallets");
     }
 
     if (net_specific && !BaseParams().DataDir().empty()) {
         path /= fs::PathFromString(BaseParams().DataDir());
         if (!fs::exists(path)) {
             fs::create_directories(path / "wallets");
         }
     }
 
     return path;
 }
 
 void ArgsManager::ClearPathCache() {
     LOCK(cs_args);
 
     m_cached_datadir_path = fs::path();
     m_cached_network_datadir_path = fs::path();
     m_cached_blocks_path = fs::path();
 }
 
 std::vector<std::string> ArgsManager::GetArgs(const std::string &strArg) const {
     std::vector<std::string> result;
     for (const util::SettingsValue &value : GetSettingsList(strArg)) {
         result.push_back(value.isFalse()  ? "0"
                          : value.isTrue() ? "1"
                                           : value.get_str());
     }
     return result;
 }
 
 bool ArgsManager::IsArgSet(const std::string &strArg) const {
     return !GetSetting(strArg).isNull();
 }
 
 bool ArgsManager::InitSettings(std::string &error) {
     if (!GetSettingsPath()) {
         return true; // Do nothing if settings file disabled.
     }
 
     std::vector<std::string> errors;
     if (!ReadSettingsFile(&errors)) {
         error = strprintf("Failed loading settings file:\n- %s\n",
                           Join(errors, "\n- "));
         return false;
     }
     if (!WriteSettingsFile(&errors)) {
         error = strprintf("Failed saving settings file:\n- %s\n",
                           Join(errors, "\n- "));
         return false;
     }
     return true;
 }
 
 bool ArgsManager::GetSettingsPath(fs::path *filepath, bool temp) const {
     fs::path settings =
         GetPathArg("-settings", fs::path{BITCOIN_SETTINGS_FILENAME});
     if (settings.empty()) {
         return false;
     }
     if (filepath) {
         *filepath = fsbridge::AbsPathJoin(GetDataDirNet(),
                                           temp ? settings + ".tmp" : settings);
     }
     return true;
 }
 
 static void SaveErrors(const std::vector<std::string> errors,
                        std::vector<std::string> *error_out) {
     for (const auto &error : errors) {
         if (error_out) {
             error_out->emplace_back(error);
         } else {
             LogPrintf("%s\n", error);
         }
     }
 }
 
 bool ArgsManager::ReadSettingsFile(std::vector<std::string> *errors) {
     fs::path path;
     if (!GetSettingsPath(&path, /* temp= */ false)) {
         return true; // Do nothing if settings file disabled.
     }
 
     LOCK(cs_args);
     m_settings.rw_settings.clear();
     std::vector<std::string> read_errors;
     if (!util::ReadSettings(path, m_settings.rw_settings, read_errors)) {
         SaveErrors(read_errors, errors);
         return false;
     }
     for (const auto &setting : m_settings.rw_settings) {
         std::string section;
         std::string key = setting.first;
         // Split setting key into section and argname
         (void)InterpretOption(section, key, /* value */ {});
         if (!GetArgFlags('-' + key)) {
             LogPrintf("Ignoring unknown rw_settings value %s\n", setting.first);
         }
     }
     return true;
 }
 
 bool ArgsManager::WriteSettingsFile(std::vector<std::string> *errors) const {
     fs::path path, path_tmp;
     if (!GetSettingsPath(&path, /* temp= */ false) ||
         !GetSettingsPath(&path_tmp, /* temp= */ true)) {
         throw std::logic_error("Attempt to write settings file when dynamic "
                                "settings are disabled.");
     }
 
     LOCK(cs_args);
     std::vector<std::string> write_errors;
     if (!util::WriteSettings(path_tmp, m_settings.rw_settings, write_errors)) {
         SaveErrors(write_errors, errors);
         return false;
     }
     if (!RenameOver(path_tmp, path)) {
         SaveErrors(
             {strprintf("Failed renaming settings file %s to %s\n",
                        fs::PathToString(path_tmp), fs::PathToString(path))},
             errors);
         return false;
     }
     return true;
 }
 
+util::SettingsValue
+ArgsManager::GetPersistentSetting(const std::string &name) const {
+    LOCK(cs_args);
+    return util::GetSetting(
+        m_settings, m_network, name, !UseDefaultSection("-" + name),
+        /*ignore_nonpersistent=*/true, /*get_chain_name=*/false);
+}
+
 bool ArgsManager::IsArgNegated(const std::string &strArg) const {
     return GetSetting(strArg).isFalse();
 }
 
 std::string ArgsManager::GetArg(const std::string &strArg,
                                 const std::string &strDefault) const {
     const util::SettingsValue value = GetSetting(strArg);
+    return SettingToString(value, strDefault);
+}
+
+std::string SettingToString(const util::SettingsValue &value,
+                            const std::string &strDefault) {
     return value.isNull()    ? strDefault
            : value.isFalse() ? "0"
            : value.isTrue()  ? "1"
                              : value.get_str();
 }
 
 int64_t ArgsManager::GetIntArg(const std::string &strArg,
                                int64_t nDefault) const {
     const util::SettingsValue value = GetSetting(strArg);
+    return SettingToInt(value, nDefault);
+}
+
+int64_t SettingToInt(const util::SettingsValue &value, int64_t nDefault) {
     return value.isNull()    ? nDefault
            : value.isFalse() ? 0
            : value.isTrue()  ? 1
            : value.isNum()   ? value.get_int64()
                              : atoi64(value.get_str());
 }
 
 bool ArgsManager::GetBoolArg(const std::string &strArg, bool fDefault) const {
     const util::SettingsValue value = GetSetting(strArg);
+    return SettingToBool(value, fDefault);
+}
+
+bool SettingToBool(const util::SettingsValue &value, bool fDefault) {
     return value.isNull()   ? fDefault
            : value.isBool() ? value.get_bool()
                             : InterpretBool(value.get_str());
 }
 
 bool ArgsManager::SoftSetArg(const std::string &strArg,
                              const std::string &strValue) {
     LOCK(cs_args);
     if (IsArgSet(strArg)) {
         return false;
     }
     ForceSetArg(strArg, strValue);
     return true;
 }
 
 bool ArgsManager::SoftSetBoolArg(const std::string &strArg, bool fValue) {
     if (fValue) {
         return SoftSetArg(strArg, std::string("1"));
     } else {
         return SoftSetArg(strArg, std::string("0"));
     }
 }
 
 void ArgsManager::ForceSetArg(const std::string &strArg,
                               const std::string &strValue) {
     LOCK(cs_args);
     m_settings.forced_settings[SettingName(strArg)] = strValue;
 }
 
 /**
  * This function is only used for testing purpose so
  * so we should not worry about element uniqueness and
  * integrity of the data structure
  */
 void ArgsManager::ForceSetMultiArg(const std::string &strArg,
                                    const std::vector<std::string> &values) {
     LOCK(cs_args);
     util::SettingsValue value;
     value.setArray();
     for (const std::string &s : values) {
         value.push_back(s);
     }
 
     m_settings.forced_settings[SettingName(strArg)] = value;
 }
 
 void ArgsManager::AddArg(const std::string &name, const std::string &help,
                          unsigned int flags, const OptionsCategory &cat) {
     // Split arg name from its help param
     size_t eq_index = name.find('=');
     if (eq_index == std::string::npos) {
         eq_index = name.size();
     }
     std::string arg_name = name.substr(0, eq_index);
 
     LOCK(cs_args);
     std::map<std::string, Arg> &arg_map = m_available_args[cat];
     auto ret = arg_map.emplace(
         arg_name,
         Arg{name.substr(eq_index, name.size() - eq_index), help, flags});
     // Make sure an insertion actually happened.
     assert(ret.second);
 
     if (flags & ArgsManager::NETWORK_ONLY) {
         m_network_only_args.emplace(arg_name);
     }
 }
 
 void ArgsManager::AddHiddenArgs(const std::vector<std::string> &names) {
     for (const std::string &name : names) {
         AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
     }
 }
 
 void ArgsManager::ClearForcedArg(const std::string &strArg) {
     LOCK(cs_args);
     m_settings.forced_settings.erase(SettingName(strArg));
 }
 
 std::string ArgsManager::GetHelpMessage() const {
     const bool show_debug = GetBoolArg("-help-debug", false);
 
     std::string usage = "";
     LOCK(cs_args);
     for (const auto &arg_map : m_available_args) {
         switch (arg_map.first) {
             case OptionsCategory::OPTIONS:
                 usage += HelpMessageGroup("Options:");
                 break;
             case OptionsCategory::CONNECTION:
                 usage += HelpMessageGroup("Connection options:");
                 break;
             case OptionsCategory::ZMQ:
                 usage += HelpMessageGroup("ZeroMQ notification options:");
                 break;
             case OptionsCategory::DEBUG_TEST:
                 usage += HelpMessageGroup("Debugging/Testing options:");
                 break;
             case OptionsCategory::NODE_RELAY:
                 usage += HelpMessageGroup("Node relay options:");
                 break;
             case OptionsCategory::BLOCK_CREATION:
                 usage += HelpMessageGroup("Block creation options:");
                 break;
             case OptionsCategory::RPC:
                 usage += HelpMessageGroup("RPC server options:");
                 break;
             case OptionsCategory::WALLET:
                 usage += HelpMessageGroup("Wallet options:");
                 break;
             case OptionsCategory::WALLET_DEBUG_TEST:
                 if (show_debug) {
                     usage +=
                         HelpMessageGroup("Wallet debugging/testing options:");
                 }
                 break;
             case OptionsCategory::CHAINPARAMS:
                 usage += HelpMessageGroup("Chain selection options:");
                 break;
             case OptionsCategory::GUI:
                 usage += HelpMessageGroup("UI Options:");
                 break;
             case OptionsCategory::COMMANDS:
                 usage += HelpMessageGroup("Commands:");
                 break;
             case OptionsCategory::REGISTER_COMMANDS:
                 usage += HelpMessageGroup("Register Commands:");
                 break;
             case OptionsCategory::AVALANCHE:
                 usage += HelpMessageGroup("Avalanche options:");
                 break;
             case OptionsCategory::CHRONIK:
                 usage += HelpMessageGroup("Chronik options:");
                 break;
             default:
                 break;
         }
 
         // When we get to the hidden options, stop
         if (arg_map.first == OptionsCategory::HIDDEN) {
             break;
         }
 
         for (const auto &arg : arg_map.second) {
             if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) {
                 std::string name;
                 if (arg.second.m_help_param.empty()) {
                     name = arg.first;
                 } else {
                     name = arg.first + arg.second.m_help_param;
                 }
                 usage += HelpMessageOpt(name, arg.second.m_help_text);
             }
         }
     }
     return usage;
 }
 
 bool HelpRequested(const ArgsManager &args) {
     return args.IsArgSet("-?") || args.IsArgSet("-h") ||
            args.IsArgSet("-help") || args.IsArgSet("-help-debug");
 }
 
 void SetupHelpOptions(ArgsManager &args) {
     args.AddArg("-?", "Print this help message and exit", false,
                 OptionsCategory::OPTIONS);
     args.AddHiddenArgs({"-h", "-help"});
 }
 
 static const int screenWidth = 79;
 static const int optIndent = 2;
 static const int msgIndent = 7;
 
 std::string HelpMessageGroup(const std::string &message) {
     return std::string(message) + std::string("\n\n");
 }
 
 std::string HelpMessageOpt(const std::string &option,
                            const std::string &message) {
     return std::string(optIndent, ' ') + std::string(option) +
            std::string("\n") + std::string(msgIndent, ' ') +
            FormatParagraph(message, screenWidth - msgIndent, msgIndent) +
            std::string("\n\n");
 }
 
 static std::string FormatException(const std::exception *pex,
                                    const char *pszThread) {
 #ifdef WIN32
     char pszModule[MAX_PATH] = "";
     GetModuleFileNameA(nullptr, pszModule, sizeof(pszModule));
 #else
     const char *pszModule = "bitcoin";
 #endif
     if (pex) {
         return strprintf("EXCEPTION: %s       \n%s       \n%s in %s       \n",
                          typeid(*pex).name(), pex->what(), pszModule,
                          pszThread);
     } else {
         return strprintf("UNKNOWN EXCEPTION       \n%s in %s       \n",
                          pszModule, pszThread);
     }
 }
 
 void PrintExceptionContinue(const std::exception *pex, const char *pszThread) {
     std::string message = FormatException(pex, pszThread);
     LogPrintf("\n\n************************\n%s\n", message);
     tfm::format(std::cerr, "\n\n************************\n%s\n", message);
 }
 
 fs::path GetDefaultDataDir() {
     // Windows: C:\Users\Username\AppData\Roaming\Bitcoin
     // macOS: ~/Library/Application Support/Bitcoin
     // Unix-like: ~/.bitcoin
 #ifdef WIN32
     // Windows
     return GetSpecialFolderPath(CSIDL_APPDATA) / "Bitcoin";
 #else
     fs::path pathRet;
     char *pszHome = getenv("HOME");
     if (pszHome == nullptr || strlen(pszHome) == 0) {
         pathRet = fs::path("/");
     } else {
         pathRet = fs::path(pszHome);
     }
 #ifdef MAC_OSX
     // macOS
     return pathRet / "Library/Application Support/Bitcoin";
 #else
     // Unix-like
     return pathRet / ".bitcoin";
 #endif
 #endif
 }
 
 bool CheckDataDirOption() {
     const fs::path datadir{gArgs.GetPathArg("-datadir")};
     return datadir.empty() || fs::is_directory(fs::absolute(datadir));
 }
 
 fs::path GetConfigFile(const std::string &confPath) {
     return AbsPathForConfigVal(fs::PathFromString(confPath), false);
 }
 
 static bool
 GetConfigOptions(std::istream &stream, const std::string &filepath,
                  std::string &error,
                  std::vector<std::pair<std::string, std::string>> &options,
                  std::list<SectionInfo> &sections) {
     std::string str, prefix;
     std::string::size_type pos;
     int linenr = 1;
     while (std::getline(stream, str)) {
         bool used_hash = false;
         if ((pos = str.find('#')) != std::string::npos) {
             str = str.substr(0, pos);
             used_hash = true;
         }
         const static std::string pattern = " \t\r\n";
         str = TrimString(str, pattern);
         if (!str.empty()) {
             if (*str.begin() == '[' && *str.rbegin() == ']') {
                 const std::string section = str.substr(1, str.size() - 2);
                 sections.emplace_back(SectionInfo{section, filepath, linenr});
                 prefix = section + '.';
             } else if (*str.begin() == '-') {
                 error = strprintf(
                     "parse error on line %i: %s, options in configuration file "
                     "must be specified without leading -",
                     linenr, str);
                 return false;
             } else if ((pos = str.find('=')) != std::string::npos) {
                 std::string name =
                     prefix + TrimString(str.substr(0, pos), pattern);
                 std::string value = TrimString(str.substr(pos + 1), pattern);
                 if (used_hash &&
                     name.find("rpcpassword") != std::string::npos) {
                     error = strprintf(
                         "parse error on line %i, using # in rpcpassword can be "
                         "ambiguous and should be avoided",
                         linenr);
                     return false;
                 }
                 options.emplace_back(name, value);
                 if ((pos = name.rfind('.')) != std::string::npos &&
                     prefix.length() <= pos) {
                     sections.emplace_back(
                         SectionInfo{name.substr(0, pos), filepath, linenr});
                 }
             } else {
                 error = strprintf("parse error on line %i: %s", linenr, str);
                 if (str.size() >= 2 && str.substr(0, 2) == "no") {
                     error += strprintf(", if you intended to specify a negated "
                                        "option, use %s=1 instead",
                                        str);
                 }
                 return false;
             }
         }
         ++linenr;
     }
     return true;
 }
 
 bool ArgsManager::ReadConfigStream(std::istream &stream,
                                    const std::string &filepath,
                                    std::string &error,
                                    bool ignore_invalid_keys) {
     LOCK(cs_args);
     std::vector<std::pair<std::string, std::string>> options;
     if (!GetConfigOptions(stream, filepath, error, options,
                           m_config_sections)) {
         return false;
     }
     for (const std::pair<std::string, std::string> &option : options) {
         std::string section;
         std::string key = option.first;
         util::SettingsValue value =
             InterpretOption(section, key, option.second);
         std::optional<unsigned int> flags = GetArgFlags('-' + key);
         if (flags) {
             if (!CheckValid(key, value, *flags, error)) {
                 return false;
             }
             m_settings.ro_config[section][key].push_back(value);
         } else {
             if (ignore_invalid_keys) {
                 LogPrintf("Ignoring unknown configuration value %s\n",
                           option.first);
             } else {
                 error = strprintf("Invalid configuration value %s",
                                   option.first.c_str());
                 return false;
             }
         }
     }
     return true;
 }
 
 bool ArgsManager::ReadConfigFiles(std::string &error,
                                   bool ignore_invalid_keys) {
     {
         LOCK(cs_args);
         m_settings.ro_config.clear();
         m_config_sections.clear();
     }
 
     const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME);
     std::ifstream stream{GetConfigFile(confPath)};
 
     // ok to not have a config file
     if (stream.good()) {
         if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) {
             return false;
         }
         // `-includeconf` cannot be included in the command line arguments
         // except as `-noincludeconf` (which indicates that no included conf
         // file should be used).
         bool use_conf_file{true};
         {
             LOCK(cs_args);
             if (auto *includes = util::FindKey(m_settings.command_line_options,
                                                "includeconf")) {
                 // ParseParameters() fails if a non-negated -includeconf is
                 // passed on the command-line
                 assert(util::SettingsSpan(*includes).last_negated());
                 use_conf_file = false;
             }
         }
         if (use_conf_file) {
             std::string chain_id = GetChainName();
             std::vector<std::string> conf_file_names;
 
             auto add_includes = [&](const std::string &network,
                                     size_t skip = 0) {
                 size_t num_values = 0;
                 LOCK(cs_args);
                 if (auto *section =
                         util::FindKey(m_settings.ro_config, network)) {
                     if (auto *values = util::FindKey(*section, "includeconf")) {
                         for (size_t i = std::max(
                                  skip, util::SettingsSpan(*values).negated());
                              i < values->size(); ++i) {
                             conf_file_names.push_back((*values)[i].get_str());
                         }
                         num_values = values->size();
                     }
                 }
                 return num_values;
             };
 
             // We haven't set m_network yet (that happens in SelectParams()), so
             // manually check for network.includeconf args.
             const size_t chain_includes = add_includes(chain_id);
             const size_t default_includes = add_includes({});
 
             for (const std::string &conf_file_name : conf_file_names) {
                 std::ifstream conf_file_stream{GetConfigFile(conf_file_name)};
                 if (conf_file_stream.good()) {
                     if (!ReadConfigStream(conf_file_stream, conf_file_name,
                                           error, ignore_invalid_keys)) {
                         return false;
                     }
                     LogPrintf("Included configuration file %s\n",
                               conf_file_name);
                 } else {
                     error = "Failed to include configuration file " +
                             conf_file_name;
                     return false;
                 }
             }
 
             // Warn about recursive -includeconf
             conf_file_names.clear();
             add_includes(chain_id, /* skip= */ chain_includes);
             add_includes({}, /* skip= */ default_includes);
             std::string chain_id_final = GetChainName();
             if (chain_id_final != chain_id) {
                 // Also warn about recursive includeconf for the chain that was
                 // specified in one of the includeconfs
                 add_includes(chain_id_final);
             }
             for (const std::string &conf_file_name : conf_file_names) {
                 tfm::format(std::cerr,
                             "warning: -includeconf cannot be used from "
                             "included files; ignoring -includeconf=%s\n",
                             conf_file_name);
             }
         }
     }
 
     // If datadir is changed in .conf file:
     gArgs.ClearPathCache();
     if (!CheckDataDirOption()) {
         error = strprintf("specified data directory \"%s\" does not exist.",
                           GetArg("-datadir", "").c_str());
         return false;
     }
     return true;
 }
 
 std::string ArgsManager::GetChainName() const {
     auto get_net = [&](const std::string &arg) {
         LOCK(cs_args);
         util::SettingsValue value =
-            util::GetSetting(m_settings, /* section= */ "", SettingName(arg),
-                             /* ignore_default_section_config= */ false,
-                             /* get_chain_name= */ true);
+            util::GetSetting(m_settings, /*section=*/"", SettingName(arg),
+                             /*ignore_default_section_config=*/false,
+                             /*ignore_nonpersistent=*/false,
+                             /*get_chain_name=*/true);
         return value.isNull()   ? false
                : value.isBool() ? value.get_bool()
                                 : InterpretBool(value.get_str());
     };
 
     const bool fRegTest = get_net("-regtest");
     const bool fTestNet = get_net("-testnet");
     const bool is_chain_arg_set = IsArgSet("-chain");
 
     if (int(is_chain_arg_set) + int(fRegTest) + int(fTestNet) > 1) {
         throw std::runtime_error("Invalid combination of -regtest, -testnet "
                                  "and -chain. Can use at most one.");
     }
     if (fRegTest) {
         return CBaseChainParams::REGTEST;
     }
     if (fTestNet) {
         return CBaseChainParams::TESTNET;
     }
     return GetArg("-chain", CBaseChainParams::MAIN);
 }
 
 bool ArgsManager::UseDefaultSection(const std::string &arg) const {
     return m_network == CBaseChainParams::MAIN ||
            m_network_only_args.count(arg) == 0;
 }
 
 util::SettingsValue ArgsManager::GetSetting(const std::string &arg) const {
     LOCK(cs_args);
     return util::GetSetting(m_settings, m_network, SettingName(arg),
                             !UseDefaultSection(arg),
-                            /* get_chain_name= */ false);
+                            /*ignore_nonpersistent=*/false,
+                            /*get_chain_name=*/false);
 }
 
 std::vector<util::SettingsValue>
 ArgsManager::GetSettingsList(const std::string &arg) const {
     LOCK(cs_args);
     return util::GetSettingsList(m_settings, m_network, SettingName(arg),
                                  !UseDefaultSection(arg));
 }
 
 void ArgsManager::logArgsPrefix(
     const std::string &prefix, const std::string &section,
     const std::map<std::string, std::vector<util::SettingsValue>> &args) const {
     std::string section_str = section.empty() ? "" : "[" + section + "] ";
     for (const auto &arg : args) {
         for (const auto &value : arg.second) {
             std::optional<unsigned int> flags = GetArgFlags('-' + arg.first);
             if (flags) {
                 std::string value_str =
                     (*flags & SENSITIVE) ? "****" : value.write();
                 LogPrintf("%s %s%s=%s\n", prefix, section_str, arg.first,
                           value_str);
             }
         }
     }
 }
 
 void ArgsManager::LogArgs() const {
     LOCK(cs_args);
     for (const auto &section : m_settings.ro_config) {
         logArgsPrefix("Config file arg:", section.first, section.second);
     }
     for (const auto &setting : m_settings.rw_settings) {
         LogPrintf("Setting file arg: %s = %s\n", setting.first,
                   setting.second.write());
     }
     logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
 }
 
 bool RenameOver(fs::path src, fs::path dest) {
 #ifdef WIN32
     return MoveFileExW(src.wstring().c_str(), dest.wstring().c_str(),
                        MOVEFILE_REPLACE_EXISTING) != 0;
 #else
     int rc = std::rename(src.c_str(), dest.c_str());
     return (rc == 0);
 #endif /* WIN32 */
 }
 
 /**
  * Ignores exceptions thrown by create_directories if the requested
  * directory exists. Specifically handles case where path p exists, but it
  * wasn't possible for the user to write to the parent directory.
  */
 bool TryCreateDirectories(const fs::path &p) {
     try {
         return fs::create_directories(p);
     } catch (const fs::filesystem_error &) {
         if (!fs::exists(p) || !fs::is_directory(p)) {
             throw;
         }
     }
 
     // create_directory didn't create the directory, it had to have existed
     // already.
     return false;
 }
 
 bool FileCommit(FILE *file) {
     // harmless if redundantly called
     if (fflush(file) != 0) {
         LogPrintf("%s: fflush failed: %d\n", __func__, errno);
         return false;
     }
 #ifdef WIN32
     HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
     if (FlushFileBuffers(hFile) == 0) {
         LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__,
                   GetLastError());
         return false;
     }
 #else
 #if defined(HAVE_FDATASYNC)
     // Ignore EINVAL for filesystems that don't support sync
     if (fdatasync(fileno(file)) != 0 && errno != EINVAL) {
         LogPrintf("%s: fdatasync failed: %d\n", __func__, errno);
         return false;
     }
 #elif defined(MAC_OSX) && defined(F_FULLFSYNC)
     // Manpage says "value other than -1" is returned on success
     if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) {
         LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno);
         return false;
     }
 #else
     if (fsync(fileno(file)) != 0 && errno != EINVAL) {
         LogPrintf("%s: fsync failed: %d\n", __func__, errno);
         return false;
     }
 #endif
 #endif
     return true;
 }
 
 bool TruncateFile(FILE *file, unsigned int length) {
 #if defined(WIN32)
     return _chsize(_fileno(file), length) == 0;
 #else
     return ftruncate(fileno(file), length) == 0;
 #endif
 }
 
 /**
  * This function tries to raise the file descriptor limit to the requested
  * number. It returns the actual file descriptor limit (which may be more or
  * less than nMinFD)
  */
 int RaiseFileDescriptorLimit(int nMinFD) {
 #if defined(WIN32)
     return 8192;
 #else
     struct rlimit limitFD;
     if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) {
         if (limitFD.rlim_cur < (rlim_t)nMinFD) {
             limitFD.rlim_cur = nMinFD;
             if (limitFD.rlim_cur > limitFD.rlim_max) {
                 limitFD.rlim_cur = limitFD.rlim_max;
             }
             setrlimit(RLIMIT_NOFILE, &limitFD);
             getrlimit(RLIMIT_NOFILE, &limitFD);
         }
         return limitFD.rlim_cur;
     }
     // getrlimit failed, assume it's fine.
     return nMinFD;
 #endif
 }
 
 /**
  * This function tries to make a particular range of a file allocated
  * (corresponding to disk space) it is advisory, and the range specified in the
  * arguments will never contain live data.
  */
 void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) {
 #if defined(WIN32)
     // Windows-specific version.
     HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file));
     LARGE_INTEGER nFileSize;
     int64_t nEndPos = (int64_t)offset + length;
     nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF;
     nFileSize.u.HighPart = nEndPos >> 32;
     SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN);
     SetEndOfFile(hFile);
 #elif defined(MAC_OSX)
     // OSX specific version
     // NOTE: Contrary to other OS versions, the OSX version assumes that
     // NOTE: offset is the size of the file.
     fstore_t fst;
     fst.fst_flags = F_ALLOCATECONTIG;
     fst.fst_posmode = F_PEOFPOSMODE;
     fst.fst_offset = 0;
     // mac os fst_length takes the number of free bytes to allocate,
     // not the desired file size
     fst.fst_length = length;
     fst.fst_bytesalloc = 0;
     if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) {
         fst.fst_flags = F_ALLOCATEALL;
         fcntl(fileno(file), F_PREALLOCATE, &fst);
     }
     ftruncate(fileno(file), static_cast<off_t>(offset) + length);
 #elif defined(HAVE_POSIX_FALLOCATE)
     // Version using posix_fallocate
     off_t nEndPos = (off_t)offset + length;
     posix_fallocate(fileno(file), 0, nEndPos);
 #else
     // Fallback version
     // TODO: just write one byte per block
     static const char buf[65536] = {};
     if (fseek(file, offset, SEEK_SET)) {
         return;
     }
     while (length > 0) {
         unsigned int now = 65536;
         if (length < now) {
             now = length;
         }
         // Allowed to fail; this function is advisory anyway.
         fwrite(buf, 1, now, file);
         length -= now;
     }
 #endif
 }
 
 #ifdef WIN32
 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) {
     WCHAR pszPath[MAX_PATH] = L"";
 
     if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) {
         return fs::path(pszPath);
     }
 
     LogPrintf(
         "SHGetSpecialFolderPathW() failed, could not obtain requested path.\n");
     return fs::path("");
 }
 #endif
 
 #ifndef WIN32
 std::string ShellEscape(const std::string &arg) {
     std::string escaped = arg;
     ReplaceAll(escaped, "'", "'\"'\"'");
     return "'" + escaped + "'";
 }
 #endif
 
 #if defined(HAVE_SYSTEM)
 void runCommand(const std::string &strCommand) {
     if (strCommand.empty()) {
         return;
     }
 #ifndef WIN32
     int nErr = ::system(strCommand.c_str());
 #else
     int nErr = ::_wsystem(
         std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>()
             .from_bytes(strCommand)
             .c_str());
 #endif
     if (nErr) {
         LogPrintf("runCommand error: system(%s) returned %d\n", strCommand,
                   nErr);
     }
 }
 #endif
 
 void SetupEnvironment() {
 #ifdef HAVE_MALLOPT_ARENA_MAX
     // glibc-specific: On 32-bit systems set the number of arenas to 1. By
     // default, since glibc 2.10, the C library will create up to two heap
     // arenas per core. This is known to cause excessive virtual address space
     // usage in our usage. Work around it by setting the maximum number of
     // arenas to 1.
     if (sizeof(void *) == 4) {
         mallopt(M_ARENA_MAX, 1);
     }
 #endif
 // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale may
 // be invalid, in which case the "C.UTF-8" locale is used as fallback.
 #if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) &&           \
     !defined(__OpenBSD__)
     try {
         // Raises a runtime error if current locale is invalid.
         std::locale("");
     } catch (const std::runtime_error &) {
         setenv("LC_ALL", "C.UTF-8", 1);
     }
 #elif defined(WIN32)
     // Set the default input/output charset is utf-8
     SetConsoleCP(CP_UTF8);
     SetConsoleOutputCP(CP_UTF8);
 #endif
 }
 
 bool SetupNetworking() {
 #ifdef WIN32
     // Initialize Windows Sockets.
     WSADATA wsadata;
     int ret = WSAStartup(MAKEWORD(2, 2), &wsadata);
     if (ret != NO_ERROR || LOBYTE(wsadata.wVersion) != 2 ||
         HIBYTE(wsadata.wVersion) != 2) {
         return false;
     }
 #endif
     return true;
 }
 
 int GetNumCores() {
     return std::thread::hardware_concurrency();
 }
 
 std::string CopyrightHolders(const std::string &strPrefix) {
     return strPrefix + strprintf(_(COPYRIGHT_HOLDERS).translated,
                                  COPYRIGHT_HOLDERS_SUBSTITUTION);
 }
 
 // Obtain the application startup time (used for uptime calculation)
 int64_t GetStartupTime() {
     return nStartupTime;
 }
 
 fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific) {
     if (path.is_absolute()) {
         return path;
     }
     return fsbridge::AbsPathJoin(
         net_specific ? gArgs.GetDataDirNet() : gArgs.GetDataDirBase(), path);
 }
 
 void ScheduleBatchPriority() {
 #ifdef SCHED_BATCH
     const static sched_param param{};
     const int rc = pthread_setschedparam(pthread_self(), SCHED_BATCH, &param);
     if (rc != 0) {
         LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(rc));
     }
 #endif
 }
 
 namespace util {
 #ifdef WIN32
 WinCmdLineArgs::WinCmdLineArgs() {
     wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
     std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> utf8_cvt;
     argv = new char *[argc];
     args.resize(argc);
     for (int i = 0; i < argc; i++) {
         args[i] = utf8_cvt.to_bytes(wargv[i]);
         argv[i] = &*args[i].begin();
     }
     LocalFree(wargv);
 }
 
 WinCmdLineArgs::~WinCmdLineArgs() {
     delete[] argv;
 }
 
 std::pair<int, char **> WinCmdLineArgs::get() {
     return std::make_pair(argc, argv);
 }
 #endif
 } // namespace util
diff --git a/src/util/system.h b/src/util/system.h
index be7ea92e5..d8fe59867 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -1,541 +1,551 @@
 // Copyright (c) 2009-2010 Satoshi Nakamoto
 // Copyright (c) 2009-2019 The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
 /**
  * Server/client environment: argument handling, config file parsing,
  * thread wrappers, startup time
  */
 #ifndef BITCOIN_UTIL_SYSTEM_H
 #define BITCOIN_UTIL_SYSTEM_H
 
 #if defined(HAVE_CONFIG_H)
 #include <config/bitcoin-config.h>
 #endif
 
 #include <compat.h>
 #include <compat/assumptions.h>
 #include <fs.h>
 #include <logging.h>
 #include <sync.h>
 #include <tinyformat.h>
 #include <util/settings.h>
 #include <util/time.h>
 
 #include <any>
 #include <cstdint>
 #include <exception>
 #include <map>
 #include <optional>
 #include <set>
 #include <string>
 #include <utility>
 #include <vector>
 
 // Application startup time (used for uptime calculation)
 int64_t GetStartupTime();
 
 extern const char *const BITCOIN_CONF_FILENAME;
 extern const char *const BITCOIN_SETTINGS_FILENAME;
 
 void SetupEnvironment();
 bool SetupNetworking();
 
 template <typename... Args> bool error(const char *fmt, const Args &...args) {
     LogPrintf("ERROR: %s\n", tfm::format(fmt, args...));
     return false;
 }
 
 void PrintExceptionContinue(const std::exception *pex, const char *pszThread);
 bool FileCommit(FILE *file);
 bool TruncateFile(FILE *file, unsigned int length);
 int RaiseFileDescriptorLimit(int nMinFD);
 void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length);
 [[nodiscard]] bool RenameOver(fs::path src, fs::path dest);
 bool LockDirectory(const fs::path &directory, const std::string lockfile_name,
                    bool probe_only = false);
 void UnlockDirectory(const fs::path &directory,
                      const std::string &lockfile_name);
 bool DirIsWritable(const fs::path &directory);
 bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes = 0);
 
 /**
  * Get the size of a file by scanning it.
  *
  * @param[in] path The file path
  * @param[in] max Stop seeking beyond this limit
  * @return The file size or max
  */
 std::streampos
 GetFileSize(const char *path,
             std::streamsize max = std::numeric_limits<std::streamsize>::max());
 
 /**
  * Release all directory locks. This is used for unit testing only, at runtime
  * the global destructor will take care of the locks.
  */
 void ReleaseDirectoryLocks();
 
 bool TryCreateDirectories(const fs::path &p);
 fs::path GetDefaultDataDir();
 // Return true if -datadir option points to a valid directory or is not
 // specified.
 bool CheckDataDirOption();
 fs::path GetConfigFile(const std::string &confPath);
 #ifdef WIN32
 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
 #endif
 #ifndef WIN32
 std::string ShellEscape(const std::string &arg);
 #endif
 #if defined(HAVE_SYSTEM)
 void runCommand(const std::string &strCommand);
 #endif
 
 [[nodiscard]] bool ParseKeyValue(std::string &key, std::string &val);
 
 /**
  * Most paths passed as configuration arguments are treated as relative to
  * the datadir if they are not absolute.
  *
  * @param path The path to be conditionally prefixed with datadir.
  * @param net_specific Use network specific datadir variant
  * @return The normalized path.
  */
 fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific = true);
 
 inline bool IsSwitchChar(char c) {
 #ifdef WIN32
     return c == '-' || c == '/';
 #else
     return c == '-';
 #endif
 }
 
 enum class OptionsCategory {
     OPTIONS,
     CONNECTION,
     WALLET,
     WALLET_DEBUG_TEST,
     ZMQ,
     DEBUG_TEST,
     CHAINPARAMS,
     NODE_RELAY,
     BLOCK_CREATION,
     RPC,
     GUI,
     COMMANDS,
     REGISTER_COMMANDS,
     AVALANCHE,
 
     // Always the last option to avoid printing these in the help
     HIDDEN,
 
     // Hide Chronik for now
     CHRONIK,
 };
 
 struct SectionInfo {
     std::string m_name;
     std::string m_file;
     int m_line;
 };
 
+std::string SettingToString(const util::SettingsValue &, const std::string &);
+int64_t SettingToInt(const util::SettingsValue &, int64_t);
+bool SettingToBool(const util::SettingsValue &, bool);
+
 class ArgsManager {
 public:
     enum Flags {
         // Boolean options can accept negation syntax -noOPTION or -noOPTION=1
         ALLOW_BOOL = 0x01,
         ALLOW_INT = 0x02,
         ALLOW_STRING = 0x04,
         ALLOW_ANY = ALLOW_BOOL | ALLOW_INT | ALLOW_STRING,
         DEBUG_ONLY = 0x100,
         /* Some options would cause cross-contamination if values for
          * mainnet were used while running on regtest/testnet (or vice-versa).
          * Setting them as NETWORK_ONLY ensures that sharing a config file
          * between mainnet and regtest/testnet won't cause problems due to these
          * parameters by accident. */
         NETWORK_ONLY = 0x200,
         // This argument's value is sensitive (such as a password).
         SENSITIVE = 0x400,
     };
 
 protected:
     struct Arg {
         std::string m_help_param;
         std::string m_help_text;
         unsigned int m_flags;
     };
 
     mutable RecursiveMutex cs_args;
     util::Settings m_settings GUARDED_BY(cs_args);
     std::string m_network GUARDED_BY(cs_args);
     std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
     std::map<OptionsCategory, std::map<std::string, Arg>>
         m_available_args GUARDED_BY(cs_args);
     std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
     mutable fs::path m_cached_blocks_path GUARDED_BY(cs_args);
     mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args);
     mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args);
 
     [[nodiscard]] bool ReadConfigStream(std::istream &stream,
                                         const std::string &filepath,
                                         std::string &error,
                                         bool ignore_invalid_keys = false);
 
     /**
      * Returns true if settings values from the default section should be used,
      * depending on the current network and whether the setting is
      * network-specific.
      */
     bool UseDefaultSection(const std::string &arg) const
         EXCLUSIVE_LOCKS_REQUIRED(cs_args);
 
     /**
      * Get setting value.
      *
      * Result will be null if setting was unset, true if "-setting" argument was
      * passed false if "-nosetting" argument was passed, and a string if a
      * "-setting=value" argument was passed.
      */
     util::SettingsValue GetSetting(const std::string &arg) const;
 
     /**
      * Get list of setting values.
      */
     std::vector<util::SettingsValue>
     GetSettingsList(const std::string &arg) const;
 
 public:
     ArgsManager();
     ~ArgsManager();
 
     /**
      * Select the network in use
      */
     void SelectConfigNetwork(const std::string &network);
 
     [[nodiscard]] bool ParseParameters(int argc, const char *const argv[],
                                        std::string &error);
     [[nodiscard]] bool ReadConfigFiles(std::string &error,
                                        bool ignore_invalid_keys = false);
 
     /**
      * Log warnings for options in m_section_only_args when they are specified
      * in the default section but not overridden on the command line or in a
      * network-specific section in the config file.
      */
     const std::set<std::string> GetUnsuitableSectionOnlyArgs() const;
 
     /**
      * Log warnings for unrecognized section names in the config file.
      */
     const std::list<SectionInfo> GetUnrecognizedSections() const;
 
     /**
      * Get blocks directory path
      *
      * @return Blocks path which is network specific
      */
     const fs::path &GetBlocksDirPath() const;
 
     /**
      * Get data directory path
      *
      * @return Absolute path on success, otherwise an empty path when a
      * non-directory path would be returned
      * @post Returned directory path is created unless it is empty
      */
     const fs::path &GetDataDirBase() const { return GetDataDir(false); }
 
     /**
      * Get data directory path with appended network identifier
      *
      * @return Absolute path on success, otherwise an empty path when a
      * non-directory path would be returned
      * @post Returned directory path is created unless it is empty
      */
     const fs::path &GetDataDirNet() const { return GetDataDir(true); }
 
     /**
      * Clear cached directory paths
      */
     void ClearPathCache();
 
     /**
      * Return a vector of strings of the given argument
      *
      * @param strArg Argument to get (e.g. "-foo")
      * @return command-line arguments
      */
     std::vector<std::string> GetArgs(const std::string &strArg) const;
 
     /**
      * Return true if the given argument has been manually set.
      *
      * @param strArg Argument to get (e.g. "-foo")
      * @return true if the argument has been set
      */
     bool IsArgSet(const std::string &strArg) const;
 
     /**
      * Return true if the argument was originally passed as a negated option,
      * i.e. -nofoo.
      *
      * @param strArg Argument to get (e.g. "-foo")
      * @return true if the argument was passed negated
      */
     bool IsArgNegated(const std::string &strArg) const;
 
     /**
      * Return string argument or default value.
      *
      * @param strArg Argument to get (e.g. "-foo")
      * @param strDefault (e.g. "1")
      * @return command-line argument or default value
      */
     std::string GetArg(const std::string &strArg,
                        const std::string &strDefault) const;
 
     /**
      * Return path argument or default value.
      *
      * @param arg Argument to get a path from (e.g., "-datadir", "-blocksdir"
      *     or "-walletdir")
      * @param default_value Optional default value to return instead of the
      *     empty path.
      * @return normalized path if argument is set, with redundant "." and ".."
      *     path components and trailing separators removed (see patharg unit
      *     test for examples or implementation for details). If argument is
      *     empty or not set, default_value is returned unchanged.
      */
     fs::path GetPathArg(std::string arg,
                         const fs::path &default_value = {}) const;
 
     /**
      * Return integer argument or default value.
      *
      * @param strArg Argument to get (e.g. "-foo")
      * @param nDefault (e.g. 1)
      * @return command-line argument (0 if invalid number) or default value
      */
     int64_t GetIntArg(const std::string &strArg, int64_t nDefault) const;
 
     /**
      * Return boolean argument or default value.
      *
      * @param strArg Argument to get (e.g. "-foo")
      * @param fDefault (true or false)
      * @return command-line argument or default value
      */
     bool GetBoolArg(const std::string &strArg, bool fDefault) const;
 
     /**
      * Set an argument if it doesn't already have a value.
      *
      * @param strArg Argument to set (e.g. "-foo")
      * @param strValue Value (e.g. "1")
      * @return true if argument gets set, false if it already had a value
      */
     bool SoftSetArg(const std::string &strArg, const std::string &strValue);
 
     /**
      * Set a boolean argument if it doesn't already have a value.
      *
      * @param strArg Argument to set (e.g. "-foo")
      * @param fValue Value (e.g. false)
      * @return true if argument gets set, false if it already had a value
      */
     bool SoftSetBoolArg(const std::string &strArg, bool fValue);
 
     // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already
     // been set. Also called directly in testing.
     void ForceSetArg(const std::string &strArg, const std::string &strValue);
 
     // Forces a multi arg setting, used only in testing
     void ForceSetMultiArg(const std::string &strArg,
                           const std::vector<std::string> &values);
 
     /**
      * Looks for -regtest, -testnet and returns the appropriate BIP70 chain
      * name.
      * @return CBaseChainParams::MAIN by default; raises runtime error if an
      * invalid combination is given.
      */
     std::string GetChainName() const;
 
     /**
      * Add argument
      */
     void AddArg(const std::string &name, const std::string &help,
                 unsigned int flags, const OptionsCategory &cat);
 
     /**
      * Remove a forced arg setting, used only in testing.
      */
     void ClearForcedArg(const std::string &strArg);
 
     /**
      * Add many hidden arguments
      */
     void AddHiddenArgs(const std::vector<std::string> &args);
 
     /**
      * Clear available arguments
      */
     void ClearArgs() {
         LOCK(cs_args);
         m_available_args.clear();
         m_network_only_args.clear();
     }
 
     /**
      * Get the help string
      */
     std::string GetHelpMessage() const;
 
     /**
      * Return Flags for known arg.
      * Return std::nullopt for unknown arg.
      */
     std::optional<unsigned int> GetArgFlags(const std::string &name) const;
 
     /**
      * Read and update settings file with saved settings. This needs to be
      * called after SelectParams() because the settings file location is
      * network-specific.
      */
     bool InitSettings(std::string &error);
 
     /**
      * Get settings file path, or return false if read-write settings were
      * disabled with -nosettings.
      */
     bool GetSettingsPath(fs::path *filepath = nullptr, bool temp = false) const;
 
     /**
      * Read settings file. Push errors to vector, or log them if null.
      */
     bool ReadSettingsFile(std::vector<std::string> *errors = nullptr);
 
     /**
      * Write settings file. Push errors to vector, or log them if null.
      */
     bool WriteSettingsFile(std::vector<std::string> *errors = nullptr) const;
 
+    /**
+     * Get current setting from config file or read/write settings file,
+     * ignoring nonpersistent command line or forced settings values.
+     */
+    util::SettingsValue GetPersistentSetting(const std::string &name) const;
+
     /**
      * Access settings with lock held.
      */
     template <typename Fn> void LockSettings(Fn &&fn) {
         LOCK(cs_args);
         fn(m_settings);
     }
 
     /**
      * Log the config file options and the command line arguments,
      * useful for troubleshooting.
      */
     void LogArgs() const;
 
 private:
     /**
      * Get data directory path
      *
      * @param net_specific Append network identifier to the returned path
      * @return Absolute path on success, otherwise an empty path when a
      *         non-directory path would be returned
      * @post Returned directory path is created unless it is empty
      */
     const fs::path &GetDataDir(bool net_specific) const;
 
     // Helper function for LogArgs().
     void
     logArgsPrefix(const std::string &prefix, const std::string &section,
                   const std::map<std::string, std::vector<util::SettingsValue>>
                       &args) const;
 };
 
 extern ArgsManager gArgs;
 
 /**
  * @return true if help has been requested via a command-line arg
  */
 bool HelpRequested(const ArgsManager &args);
 
 /** Add help options to the args manager */
 void SetupHelpOptions(ArgsManager &args);
 
 /**
  * Format a string to be used as group of options in help messages.
  *
  * @param message Group name (e.g. "RPC server options:")
  * @return the formatted string
  */
 std::string HelpMessageGroup(const std::string &message);
 
 /**
  * Format a string to be used as option description in help messages.
  *
  * @param option Option message (e.g. "-rpcuser=<user>")
  * @param message Option description (e.g. "Username for JSON-RPC connections")
  * @return the formatted string
  */
 std::string HelpMessageOpt(const std::string &option,
                            const std::string &message);
 
 /**
  * Return the number of cores available on the current system.
  * @note This does count virtual cores, such as those provided by
  * HyperThreading.
  */
 int GetNumCores();
 
 std::string CopyrightHolders(const std::string &strPrefix);
 
 /**
  * On platforms that support it, tell the kernel the calling thread is
  * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details.
  *
  */
 void ScheduleBatchPriority();
 
 namespace util {
 
 //! Simplification of std insertion
 template <typename Tdst, typename Tsrc>
 inline void insert(Tdst &dst, const Tsrc &src) {
     dst.insert(dst.begin(), src.begin(), src.end());
 }
 template <typename TsetT, typename Tsrc>
 inline void insert(std::set<TsetT> &dst, const Tsrc &src) {
     dst.insert(src.begin(), src.end());
 }
 
 /**
  * Helper function to access the contained object of a std::any instance.
  * Returns a pointer to the object if passed instance has a value and the type
  * matches, nullptr otherwise.
  */
 template <typename T> T *AnyPtr(const std::any &any) noexcept {
     T *const *ptr = std::any_cast<T *>(&any);
     return ptr ? *ptr : nullptr;
 }
 
 #ifdef WIN32
 class WinCmdLineArgs {
 public:
     WinCmdLineArgs();
     ~WinCmdLineArgs();
     std::pair<int, char **> get();
 
 private:
     int argc;
     char **argv;
     std::vector<std::string> args;
 };
 #endif
 
 } // namespace util
 
 #endif // BITCOIN_UTIL_SYSTEM_H