From 59b87a27efea819e433c727756bf5fac57b33dd6 Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Mon, 7 May 2018 17:08:03 -0400
Subject: [PATCH 1/7] [wallet] Fix potential memory leak in
 CreateWalletFromFile

Fix proposed by ryanofsky in
https://github.com/bitcoin/bitcoin/pull/12647#discussion_r174875670
---
 src/wallet/wallet.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 5be9f4a29..f9f567009 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -4012,7 +4012,10 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path&
 
     int64_t nStart = GetTimeMillis();
     bool fFirstRun = true;
-    CWallet *walletInstance = new CWallet(name, WalletDatabase::Create(path));
+    // Make a temporary wallet unique pointer so memory doesn't get leaked if
+    // wallet creation fails.
+    auto temp_wallet = MakeUnique<CWallet>(name, WalletDatabase::Create(path));
+    CWallet* walletInstance = temp_wallet.get();
     DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
     if (nLoadWalletRet != DBErrors::LOAD_OK)
     {
@@ -4224,7 +4227,6 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path&
     }
 
     walletInstance->m_last_block_processed = chainActive.Tip();
-    RegisterValidationInterface(walletInstance);
 
     if (chainActive.Tip() && chainActive.Tip() != pindexRescan)
     {
@@ -4290,6 +4292,10 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path&
             }
         }
     }
+
+    // Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main.
+    RegisterValidationInterface(temp_wallet.release());
+
     walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
 
     {

From 470316c3bf5ca343d5d66b94839169a4572eceb7 Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Thu, 19 Apr 2018 17:42:40 -0400
Subject: [PATCH 2/7] [wallet] setup wallet background flushing in WalletInit
 directly

WalletInit::Start calls postInitProcess() for each wallet. Previously
each call to postInitProcess() would attempt to schedule wallet
background flushing.

Just start wallet background flushing once from WalletInit::Start().
---
 src/wallet/init.cpp   |  6 +++++-
 src/wallet/wallet.cpp | 10 +---------
 src/wallet/wallet.h   |  4 +---
 3 files changed, 7 insertions(+), 13 deletions(-)

diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 6c5522e4b..e9710012b 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -6,6 +6,7 @@
 #include <chainparams.h>
 #include <init.h>
 #include <net.h>
+#include <scheduler.h>
 #include <util.h>
 #include <utilmoneystr.h>
 #include <validation.h>
@@ -264,8 +265,11 @@ bool WalletInit::Open() const
 void WalletInit::Start(CScheduler& scheduler) const
 {
     for (CWallet* pwallet : GetWallets()) {
-        pwallet->postInitProcess(scheduler);
+        pwallet->postInitProcess();
     }
+
+    // Run a thread to flush wallet periodically
+    scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
 }
 
 void WalletInit::Flush() const
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index f9f567009..4d3e3813a 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -23,7 +23,6 @@
 #include <primitives/block.h>
 #include <primitives/transaction.h>
 #include <script/script.h>
-#include <scheduler.h>
 #include <timedata.h>
 #include <txmempool.h>
 #include <utilmoneystr.h>
@@ -4308,18 +4307,11 @@ CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path&
     return walletInstance;
 }
 
-std::atomic<bool> CWallet::fFlushScheduled(false);
-
-void CWallet::postInitProcess(CScheduler& scheduler)
+void CWallet::postInitProcess()
 {
     // Add wallet transactions that aren't already in a block to mempool
     // Do this here as mempool requires genesis block to be loaded
     ReacceptWalletTransactions();
-
-    // Run a thread to flush wallet periodically
-    if (!CWallet::fFlushScheduled.exchange(true)) {
-        scheduler.scheduleEvery(MaybeCompactWalletDB, 500);
-    }
 }
 
 bool CWallet::BackupWallet(const std::string& strDest)
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 1e23b44ea..ebf14d438 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -68,7 +68,6 @@ class CCoinControl;
 class COutput;
 class CReserveKey;
 class CScript;
-class CScheduler;
 class CTxMemPool;
 class CBlockPolicyEstimator;
 class CWalletTx;
@@ -675,7 +674,6 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions
 class CWallet final : public CCryptoKeyStore, public CValidationInterface
 {
 private:
-    static std::atomic<bool> fFlushScheduled;
     std::atomic<bool> fAbortRescan{false};
     std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver
     std::mutex mutexScanning;
@@ -1127,7 +1125,7 @@ public:
      * Wallet post-init setup
      * Gives the wallet a chance to register repetitive tasks and complete post-init tasks
      */
-    void postInitProcess(CScheduler& scheduler);
+    void postInitProcess();
 
     bool BackupWallet(const std::string& strDest);
 

From e0e90db07b4e798dd1625bd23c2e9bd96fc6ff49 Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Wed, 18 Apr 2018 13:11:28 -0400
Subject: [PATCH 3/7] [wallet] Add CWallet::Verify function

This allows a single wallet to be verified. Prior to this commit, all
wallets were verified together by the WalletInit::Verify() function at
start-up.

Individual wallet verification will be done when loading wallets
dynamically at runtime.
---
 src/wallet/init.cpp   | 53 +++++++++-------------------------------
 src/wallet/wallet.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++
 src/wallet/wallet.h   |  3 +++
 3 files changed, 70 insertions(+), 42 deletions(-)

diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index e9710012b..01c927f03 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -190,55 +190,24 @@ bool WalletInit::Verify() const
 
     uiInterface.InitMessage(_("Verifying wallet(s)..."));
 
+    std::vector<std::string> wallet_files = gArgs.GetArgs("-wallet");
+
+    // Parameter interaction code should have thrown an error if -salvagewallet
+    // was enabled with more than wallet file, so the wallet_files size check
+    // here should have no effect.
+    bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1;
+
     // Keep track of each wallet absolute path to detect duplicates.
     std::set<fs::path> wallet_paths;
 
-    for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
-        // Do some checking on wallet path. It should be either a:
-        //
-        // 1. Path where a directory can be created.
-        // 2. Path to an existing directory.
-        // 3. Path to a symlink to a directory.
-        // 4. For backwards compatibility, the name of a data file in -walletdir.
-        fs::path wallet_path = fs::absolute(walletFile, GetWalletDir());
-        fs::file_type path_type = fs::symlink_status(wallet_path).type();
-        if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
-              (path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
-              (path_type == fs::regular_file && fs::path(walletFile).filename() == walletFile))) {
-            return InitError(strprintf(
-                _("Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
-                  "database/log.?????????? files can be stored, a location where such a directory could be created, "
-                  "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)"),
-                walletFile, GetWalletDir()));
-        }
+    for (const auto wallet_file : wallet_files) {
+        fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
 
         if (!wallet_paths.insert(wallet_path).second) {
-            return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), walletFile));
+            return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
         }
 
-        std::string strError;
-        if (!WalletBatch::VerifyEnvironment(wallet_path, strError)) {
-            return InitError(strError);
-        }
-
-        if (gArgs.GetBoolArg("-salvagewallet", false)) {
-            // Recover readable keypairs:
-            CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
-            std::string backup_filename;
-            if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
-                return false;
-            }
-        }
-
-        std::string strWarning;
-        bool dbV = WalletBatch::VerifyDatabaseFile(wallet_path, strWarning, strError);
-        if (!strWarning.empty()) {
-            InitWarning(strWarning);
-        }
-        if (!dbV) {
-            InitError(strError);
-            return false;
-        }
+        if (!CWallet::Verify(wallet_file, salvage_wallet)) return false;
     }
 
     return true;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 4d3e3813a..d4bbb7f06 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -27,6 +27,7 @@
 #include <txmempool.h>
 #include <utilmoneystr.h>
 #include <wallet/fees.h>
+#include <wallet/walletutil.h>
 
 #include <algorithm>
 #include <assert.h>
@@ -3989,6 +3990,61 @@ void CWallet::MarkPreSplitKeys()
     }
 }
 
+bool CWallet::Verify(std::string wallet_file, bool salvage_wallet)
+{
+    // Do some checking on wallet path. It should be either a:
+    //
+    // 1. Path where a directory can be created.
+    // 2. Path to an existing directory.
+    // 3. Path to a symlink to a directory.
+    // 4. For backwards compatibility, the name of a data file in -walletdir.
+    LOCK(cs_wallets);
+    fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
+    fs::file_type path_type = fs::symlink_status(wallet_path).type();
+    if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
+          (path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
+          (path_type == fs::regular_file && fs::path(wallet_file).filename() == wallet_file))) {
+        return InitError(strprintf(
+            _("Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
+              "database/log.?????????? files can be stored, a location where such a directory could be created, "
+              "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)"),
+            wallet_file, GetWalletDir()));
+    }
+
+    // Make sure that the wallet path doesn't clash with an existing wallet path
+    for (auto wallet : GetWallets()) {
+        if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) {
+            return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
+        }
+    }
+
+    std::string strError;
+    if (!WalletBatch::VerifyEnvironment(wallet_path, strError)) {
+        return InitError(strError);
+    }
+
+    if (salvage_wallet) {
+        // Recover readable keypairs:
+        CWallet dummyWallet("dummy", WalletDatabase::CreateDummy());
+        std::string backup_filename;
+        if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
+            return false;
+        }
+    }
+
+    std::string strWarning;
+    bool dbV = WalletBatch::VerifyDatabaseFile(wallet_path, strWarning, strError);
+    if (!strWarning.empty()) {
+        InitWarning(strWarning);
+    }
+    if (!dbV) {
+        InitError(strError);
+        return false;
+    }
+
+    return true;
+}
+
 CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path)
 {
     const std::string& walletFile = name;
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index ebf14d438..200248bcf 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1118,6 +1118,9 @@ public:
     /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
     bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
 
+    //! Verify wallet naming and perform salvage on the wallet if required
+    static bool Verify(std::string wallet_file, bool salvage_wallet);
+
     /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
     static CWallet* CreateWalletFromFile(const std::string& name, const fs::path& path);
 

From 876eb64680968c8fe2a28d1ecfd88a08d8967ead Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Wed, 18 Apr 2018 14:17:09 -0400
Subject: [PATCH 4/7] [wallet] Pass error message back from CWallet::Verify()

Pass an error message back from CWallet::Verify(), and call
InitError/InitWarning from WalletInit::Verify().

This means that we can call CWallet::Verify() independently from
WalletInit and not have InitErrors printed to stdout. It also means that
the error can be reported to the user if dynamic wallet load fails.
---
 src/wallet/init.cpp   |  7 ++++++-
 src/wallet/wallet.cpp | 31 +++++++++++--------------------
 src/wallet/wallet.h   |  2 +-
 3 files changed, 18 insertions(+), 22 deletions(-)

diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 01c927f03..5cfa86451 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -207,7 +207,12 @@ bool WalletInit::Verify() const
             return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
         }
 
-        if (!CWallet::Verify(wallet_file, salvage_wallet)) return false;
+        std::string error_string;
+        std::string warning_string;
+        bool verify_success = CWallet::Verify(wallet_file, salvage_wallet, error_string, warning_string);
+        if (!error_string.empty()) InitError(error_string);
+        if (!warning_string.empty()) InitWarning(warning_string);
+        if (!verify_success) return false;
     }
 
     return true;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index d4bbb7f06..74f36e9ab 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -3990,7 +3990,7 @@ void CWallet::MarkPreSplitKeys()
     }
 }
 
-bool CWallet::Verify(std::string wallet_file, bool salvage_wallet)
+bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string)
 {
     // Do some checking on wallet path. It should be either a:
     //
@@ -4004,23 +4004,24 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet)
     if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
           (path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
           (path_type == fs::regular_file && fs::path(wallet_file).filename() == wallet_file))) {
-        return InitError(strprintf(
-            _("Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
+        error_string = strprintf(
+              "Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
               "database/log.?????????? files can be stored, a location where such a directory could be created, "
-              "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)"),
-            wallet_file, GetWalletDir()));
+              "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)",
+              wallet_file, GetWalletDir());
+        return false;
     }
 
     // Make sure that the wallet path doesn't clash with an existing wallet path
     for (auto wallet : GetWallets()) {
         if (fs::absolute(wallet->GetName(), GetWalletDir()) == wallet_path) {
-            return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file));
+            error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", wallet_file);
+            return false;
         }
     }
 
-    std::string strError;
-    if (!WalletBatch::VerifyEnvironment(wallet_path, strError)) {
-        return InitError(strError);
+    if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
+        return false;
     }
 
     if (salvage_wallet) {
@@ -4032,17 +4033,7 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet)
         }
     }
 
-    std::string strWarning;
-    bool dbV = WalletBatch::VerifyDatabaseFile(wallet_path, strWarning, strError);
-    if (!strWarning.empty()) {
-        InitWarning(strWarning);
-    }
-    if (!dbV) {
-        InitError(strError);
-        return false;
-    }
-
-    return true;
+    return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string);
 }
 
 CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path)
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 200248bcf..fecc2178e 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1119,7 +1119,7 @@ public:
     bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
 
     //! Verify wallet naming and perform salvage on the wallet if required
-    static bool Verify(std::string wallet_file, bool salvage_wallet);
+    static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string);
 
     /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
     static CWallet* CreateWalletFromFile(const std::string& name, const fs::path& path);

From 5d152601e940bd5f4043253b216a645679aff75d Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Wed, 18 Apr 2018 16:01:39 -0400
Subject: [PATCH 5/7] [wallet] [rpc] Add loadwallet RPC

The new `loadwallet` RPC method allows an existing wallet to be loaded
dynamically at runtime.

`unloadwallet` and `createwallet` are not implemented. Notably,
`loadwallet` can only be used to load existing wallets, not to create a
new wallet.
---
 src/wallet/rpcwallet.cpp | 48 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index a7b2b05d9..8cd395283 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2994,6 +2994,53 @@ static UniValue listwallets(const JSONRPCRequest& request)
     return obj;
 }
 
+UniValue loadwallet(const JSONRPCRequest& request)
+{
+    if (request.fHelp || request.params.size() != 1)
+        throw std::runtime_error(
+            "loadwallet \"filename\"\n"
+            "\nLoads a wallet from a wallet file or directory."
+            "\nNote that all wallet command-line options used when starting bitcoind will be"
+            "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n"
+            "\nArguments:\n"
+            "1. \"filename\"    (string, required) The wallet directory or .dat file.\n"
+            "\nResult:\n"
+            "{\n"
+            "  \"name\" :    <wallet_name>,        (string) The wallet name if loaded successfully.\n"
+            "  \"warning\" : <warning>,            (string) Warning message if wallet was not loaded cleanly.\n"
+            "}\n"
+            "\nExamples:\n"
+            + HelpExampleCli("loadwallet", "\"test.dat\"")
+            + HelpExampleRpc("loadwallet", "\"test.dat\"")
+        );
+    std::string wallet_file = request.params[0].get_str();
+    std::string error;
+
+    fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
+    if (fs::symlink_status(wallet_path).type() == fs::file_not_found) {
+        throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + wallet_file + " not found.");
+    }
+
+    std::string warning;
+    if (!CWallet::Verify(wallet_file, false, error, warning)) {
+        throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
+    }
+
+    CWallet * const wallet = CWallet::CreateWalletFromFile(wallet_file, fs::absolute(wallet_file, GetWalletDir()));
+    if (!wallet) {
+        throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
+    }
+    AddWallet(wallet);
+
+    wallet->postInitProcess();
+
+    UniValue obj(UniValue::VOBJ);
+    obj.pushKV("name", wallet->GetName());
+    obj.pushKV("warning", warning);
+
+    return obj;
+}
+
 static UniValue resendwallettransactions(const JSONRPCRequest& request)
 {
     CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -4197,6 +4244,7 @@ static const CRPCCommand commands[] =
     { "wallet",             "listtransactions",                 &listtransactions,              {"account|dummy","count","skip","include_watchonly"} },
     { "wallet",             "listunspent",                      &listunspent,                   {"minconf","maxconf","addresses","include_unsafe","query_options"} },
     { "wallet",             "listwallets",                      &listwallets,                   {} },
+    { "wallet",             "loadwallet",                       &loadwallet,                    {"filename"} },
     { "wallet",             "lockunspent",                      &lockunspent,                   {"unlock","transactions"} },
     { "wallet",             "sendfrom",                         &sendfrom,                      {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
     { "wallet",             "sendmany",                         &sendmany,                      {"fromaccount|dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },

From a46aeb690141f8457cf7d19f5b4b84f97ce410c8 Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Wed, 18 Apr 2018 16:32:16 -0400
Subject: [PATCH 6/7] [wallet] [tests] Test loadwallet

Add testcases to wallet_multiwallet.py to test the new `loadwallet` RPC
method.
---
 test/functional/wallet_multiwallet.py | 41 +++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index e0571ea8f..567177352 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -170,5 +170,46 @@ class MultiWalletTest(BitcoinTestFramework):
         assert_equal(w1.getwalletinfo()['paytxfee'], 0)
         assert_equal(w2.getwalletinfo()['paytxfee'], 4.0)
 
+        self.log.info("Test dynamic wallet loading")
+
+        self.restart_node(0, ['-nowallet'])
+        assert_equal(node.listwallets(), [])
+        assert_raises_rpc_error(-32601, "Method not found", node.getwalletinfo)
+
+        self.log.info("Load first wallet")
+        loadwallet_name = node.loadwallet(wallet_names[0])
+        assert_equal(loadwallet_name['name'], wallet_names[0])
+        assert_equal(node.listwallets(), wallet_names[0:1])
+        node.getwalletinfo()
+        w1 = node.get_wallet_rpc(wallet_names[0])
+        w1.getwalletinfo()
+
+        self.log.info("Load second wallet")
+        loadwallet_name = node.loadwallet(wallet_names[1])
+        assert_equal(loadwallet_name['name'], wallet_names[1])
+        assert_equal(node.listwallets(), wallet_names[0:2])
+        assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo)
+        w2 = node.get_wallet_rpc(wallet_names[1])
+        w2.getwalletinfo()
+
+        self.log.info("Load remaining wallets")
+        for wallet_name in wallet_names[2:]:
+            loadwallet_name = self.nodes[0].loadwallet(wallet_name)
+            assert_equal(loadwallet_name['name'], wallet_name)
+
+        assert_equal(set(self.nodes[0].listwallets()), set(wallet_names))
+
+        # Fail to load if wallet doesn't exist
+        assert_raises_rpc_error(-18, 'Wallet wallets not found.', self.nodes[0].loadwallet, 'wallets')
+
+        # Fail to load duplicate wallets
+        assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0])
+
+        # Fail to load if one wallet is a copy of another
+        assert_raises_rpc_error(-1, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy')
+
+        # Fail to load if wallet file is a symlink
+        assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink')
+
 if __name__ == '__main__':
     MultiWalletTest().main()

From cd53981b3d0d4697ed46c7bedbf10f464aca4ccc Mon Sep 17 00:00:00 2001
From: John Newbery <john@johnnewbery.com>
Date: Wed, 18 Apr 2018 16:51:53 -0400
Subject: [PATCH 7/7] [docs] Add release notes for `loadwallet` RPC.

---
 doc/release-notes-pr10740.md | 8 ++++++++
 1 file changed, 8 insertions(+)
 create mode 100644 doc/release-notes-pr10740.md

diff --git a/doc/release-notes-pr10740.md b/doc/release-notes-pr10740.md
new file mode 100644
index 000000000..a57cbc793
--- /dev/null
+++ b/doc/release-notes-pr10740.md
@@ -0,0 +1,8 @@
+Dynamic loading of wallets
+--------------------------
+
+Previously, wallets could only be loaded at startup, by specifying `-wallet` parameters on the command line or in the bitcoin.conf file. It is now possible to load wallets dynamically at runtime by calling the `loadwallet` RPC.
+
+The wallet can be specified as file/directory basename (which must be located in the `walletdir` directory), or as an absolute path to a file/directory.
+
+This feature is currently only available through the RPC interface. Wallets loaded in this way will not display in the bitcoin-qt GUI.