aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@gmail.com>2018-10-19 00:56:39 +0200
committerWladimir J. van der Laan <laanwj@gmail.com>2018-10-19 01:01:17 +0200
commit8eb2cd1ddaab6ec75b8ba02b353b260094798cd4 (patch)
treee73a13ed0bfcc898333194c686cc1ccb4e6d62e8
parent3715b2489e986c27efd1dbd249d3604c3acd27b2 (diff)
parentd56a0689354fb814510c6c393f3e07ac9362dc1f (diff)
Merge #14291: wallet: Add ListWalletDir utility function
d56a0689354fb814510c6c393f3e07ac9362dc1f docs: Add release notes for listwalletdir RPC (João Barbosa) 0cb3cad166bbeb75e9cc1512286453f8e7d4f717 qa: Add tests for listwalletdir RPC (João Barbosa) cc3377360c417780f5cbd7bd69b438817a9d60be rpc: Add listwalletdir RPC (João Barbosa) d1b03b8e5f04a2cc9ebb985bd9a1aebd2068f757 interfaces: Add getWalletDir and listWalletDir to Node (João Barbosa) fc4db35bfd78d85d6b52d5da3d89696160658450 wallet: Add ListWalletDir utility (João Barbosa) Pull request description: `ListWalletDir` returns all available wallets in the current wallet directory. Based on MeshCollider work in pull #11485. Tree-SHA512: 5843e3dbd1e0449f55bb8ea7c241a536078ff6ffcaad88ce5fcf8963971d48c78600fbc4f44919523b8a92329d5d8a5f567a3e0ccb0270fdd27366e19603a716
-rw-r--r--doc/release-notes-13152.md3
-rw-r--r--src/dummywallet.cpp10
-rw-r--r--src/interfaces/node.cpp14
-rw-r--r--src/interfaces/node.h6
-rw-r--r--src/wallet/rpcwallet.cpp33
-rw-r--r--src/wallet/walletutil.cpp50
-rw-r--r--src/wallet/walletutil.h8
-rwxr-xr-xtest/functional/wallet_multiwallet.py8
8 files changed, 129 insertions, 3 deletions
diff --git a/doc/release-notes-13152.md b/doc/release-notes-13152.md
index 72526f5355..ace56f4d1b 100644
--- a/doc/release-notes-13152.md
+++ b/doc/release-notes-13152.md
@@ -1,4 +1,5 @@
New RPC methods
------------
-- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds. \ No newline at end of file
+- `getnodeaddresses` returns peer addresses known to this node. It may be used to connect to nodes over TCP without using the DNS seeds.
+- `listwalletdir` returns a list of wallets in the wallet directory which is configured with `-walletdir` parameter.
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index 3714187a96..3eb77354c1 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -34,6 +34,16 @@ void DummyWalletInit::AddWalletOptions() const
const WalletInitInterface& g_wallet_init_interface = DummyWalletInit();
+fs::path GetWalletDir()
+{
+ throw std::logic_error("Wallet function called in non-wallet build.");
+}
+
+std::vector<fs::path> ListWalletDir()
+{
+ throw std::logic_error("Wallet function called in non-wallet build.");
+}
+
std::vector<std::shared_ptr<CWallet>> GetWallets()
{
throw std::logic_error("Wallet function called in non-wallet build.");
diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp
index d95b41657c..2b19e11f08 100644
--- a/src/interfaces/node.cpp
+++ b/src/interfaces/node.cpp
@@ -38,6 +38,8 @@
#include <univalue.h>
class CWallet;
+fs::path GetWalletDir();
+std::vector<fs::path> ListWalletDir();
std::vector<std::shared_ptr<CWallet>> GetWallets();
namespace interfaces {
@@ -218,6 +220,18 @@ class NodeImpl : public Node
LOCK(::cs_main);
return ::pcoinsTip->GetCoin(output, coin);
}
+ std::string getWalletDir() override
+ {
+ return GetWalletDir().string();
+ }
+ std::vector<std::string> listWalletDir() override
+ {
+ std::vector<std::string> paths;
+ for (auto& path : ListWalletDir()) {
+ paths.push_back(path.string());
+ }
+ return paths;
+ }
std::vector<std::unique_ptr<Wallet>> getWallets() override
{
std::vector<std::unique_ptr<Wallet>> wallets;
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index 8185c015a9..1f8bbbff7a 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -173,6 +173,12 @@ public:
//! Get unspent outputs associated with a transaction.
virtual bool getUnspentOutput(const COutPoint& output, Coin& coin) = 0;
+ //! Return default wallet directory.
+ virtual std::string getWalletDir() = 0;
+
+ //! Return available wallets in wallet directory.
+ virtual std::vector<std::string> listWalletDir() = 0;
+
//! Return interfaces for accessing wallets (if any).
virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0;
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index df10fb48cc..a3f5f8b211 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2443,6 +2443,38 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
return obj;
}
+static UniValue listwalletdir(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() != 0) {
+ throw std::runtime_error(
+ "listwalletdir\n"
+ "Returns a list of wallets in the wallet directory.\n"
+ "{\n"
+ " \"wallets\" : [ (json array of objects)\n"
+ " {\n"
+ " \"name\" : \"name\" (string) The wallet name\n"
+ " }\n"
+ " ,...\n"
+ " ]\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("listwalletdir", "")
+ + HelpExampleRpc("listwalletdir", "")
+ );
+ }
+
+ UniValue wallets(UniValue::VARR);
+ for (const auto& path : ListWalletDir()) {
+ UniValue wallet(UniValue::VOBJ);
+ wallet.pushKV("name", path.string());
+ wallets.push_back(wallet);
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("wallets", wallets);
+ return result;
+}
+
static UniValue listwallets(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 0)
@@ -4096,6 +4128,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", &listtransactions, {"dummy","count","skip","include_watchonly"} },
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
+ { "wallet", "listwalletdir", &listwalletdir, {} },
{ "wallet", "listwallets", &listwallets, {} },
{ "wallet", "loadwallet", &loadwallet, {"filename"} },
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index 34c76ec4c4..c0c9afe13e 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -4,6 +4,8 @@
#include <wallet/walletutil.h>
+#include <util.h>
+
fs::path GetWalletDir()
{
fs::path path;
@@ -25,3 +27,51 @@ fs::path GetWalletDir()
return path;
}
+
+static bool IsBerkeleyBtree(const fs::path& path)
+{
+ // A Berkeley DB Btree file has at least 4K.
+ // This check also prevents opening lock files.
+ boost::system::error_code ec;
+ if (fs::file_size(path, ec) < 4096) return false;
+
+ fs::ifstream file(path.string(), std::ios::binary);
+ if (!file.is_open()) return false;
+
+ file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
+ uint32_t data = 0;
+ file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
+
+ // Berkeley DB Btree magic bytes, from:
+ // https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
+ // - big endian systems - 00 05 31 62
+ // - little endian systems - 62 31 05 00
+ return data == 0x00053162 || data == 0x62310500;
+}
+
+std::vector<fs::path> ListWalletDir()
+{
+ const fs::path wallet_dir = GetWalletDir();
+ std::vector<fs::path> paths;
+
+ for (auto it = fs::recursive_directory_iterator(wallet_dir); it != end(it); ++it) {
+ if (it->status().type() == fs::directory_file && IsBerkeleyBtree(it->path() / "wallet.dat")) {
+ // Found a directory which contains wallet.dat btree file, add it as a wallet.
+ paths.emplace_back(fs::relative(it->path(), wallet_dir));
+ } else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && IsBerkeleyBtree(it->path())) {
+ if (it->path().filename() == "wallet.dat") {
+ // Found top-level wallet.dat btree file, add top level directory ""
+ // as a wallet.
+ paths.emplace_back();
+ } else {
+ // Found top-level btree file not called wallet.dat. Current bitcoin
+ // software will never create these files but will allow them to be
+ // opened in a shared database environment for backwards compatibility.
+ // Add it to the list of available wallets.
+ paths.emplace_back(fs::relative(it->path(), wallet_dir));
+ }
+ }
+ }
+
+ return paths;
+}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 7c42954616..77f5ca428d 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -5,10 +5,14 @@
#ifndef BITCOIN_WALLET_WALLETUTIL_H
#define BITCOIN_WALLET_WALLETUTIL_H
-#include <chainparamsbase.h>
-#include <util.h>
+#include <fs.h>
+
+#include <vector>
//! Get the path of the wallet directory.
fs::path GetWalletDir();
+//! Get wallets in wallet directory.
+std::vector<fs::path> ListWalletDir();
+
#endif // BITCOIN_WALLET_WALLETUTIL_H
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 189bc2d50e..7d7c77638e 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -38,6 +38,8 @@ class MultiWalletTest(BitcoinTestFramework):
return wallet_dir(name, "wallet.dat")
return wallet_dir(name)
+ assert_equal(self.nodes[0].listwalletdir(), { 'wallets': [{ 'name': '' }] })
+
# check wallet.dat is created
self.stop_nodes()
assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True)
@@ -71,6 +73,8 @@ class MultiWalletTest(BitcoinTestFramework):
wallet_names.remove('w7_symlink')
extra_args = ['-wallet={}'.format(n) for n in wallet_names]
self.start_node(0, extra_args)
+ assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', 'sub/w5', 'w7', 'w7', 'w1', 'w8', 'w']))
+
assert_equal(set(node.listwallets()), set(wallet_names))
# check that all requested wallets were created
@@ -143,6 +147,8 @@ class MultiWalletTest(BitcoinTestFramework):
self.restart_node(0, extra_args)
+ assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', 'sub/w5', 'w7', 'w7', 'w8_copy', 'w1', 'w8', 'w']))
+
wallets = [wallet(w) for w in wallet_names]
wallet_bad = wallet("bad")
@@ -281,6 +287,8 @@ class MultiWalletTest(BitcoinTestFramework):
assert_equal(self.nodes[0].listwallets(), ['w1'])
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
+ assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', 'sub/w5', 'w7', 'w9', 'w7', 'w8_copy', 'w1', 'w8', 'w']))
+
# Test backing up and restoring wallets
self.log.info("Test wallet backup")
self.restart_node(0, ['-nowallet'])