Skip to content

Commit 291b84c

Browse files
5tefangades
authored andcommitted
Merge bitcoin#14291: wallet: Add ListWalletDir utility function
d56a068 docs: Add release notes for listwalletdir RPC (João Barbosa) 0cb3cad qa: Add tests for listwalletdir RPC (João Barbosa) cc33773 rpc: Add listwalletdir RPC (João Barbosa) d1b03b8 interfaces: Add getWalletDir and listWalletDir to Node (João Barbosa) fc4db35 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 bitcoin#11485. Tree-SHA512: 5843e3dbd1e0449f55bb8ea7c241a536078ff6ffcaad88ce5fcf8963971d48c78600fbc4f44919523b8a92329d5d8a5f567a3e0ccb0270fdd27366e19603a716
1 parent 4922d77 commit 291b84c

File tree

7 files changed

+127
-2
lines changed

7 files changed

+127
-2
lines changed

src/dummywallet.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ void DummyWalletInit::AddWalletOptions() const
4848

4949
const WalletInitInterface& g_wallet_init_interface = DummyWalletInit();
5050

51+
fs::path GetWalletDir()
52+
{
53+
throw std::logic_error("Wallet function called in non-wallet build.");
54+
}
55+
56+
std::vector<fs::path> ListWalletDir()
57+
{
58+
throw std::logic_error("Wallet function called in non-wallet build.");
59+
}
60+
5161
std::vector<std::shared_ptr<CWallet>> GetWallets()
5262
{
5363
throw std::logic_error("Wallet function called in non-wallet build.");

src/interfaces/node.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
#include <univalue.h>
4848

4949
class CWallet;
50+
fs::path GetWalletDir();
51+
std::vector<fs::path> ListWalletDir();
5052
std::vector<std::shared_ptr<CWallet>> GetWallets();
5153

5254
namespace interfaces {
@@ -364,6 +366,18 @@ class NodeImpl : public Node
364366
LOCK(::cs_main);
365367
return ::pcoinsTip->GetCoin(output, coin);
366368
}
369+
std::string getWalletDir() override
370+
{
371+
return GetWalletDir().string();
372+
}
373+
std::vector<std::string> listWalletDir() override
374+
{
375+
std::vector<std::string> paths;
376+
for (auto& path : ListWalletDir()) {
377+
paths.push_back(path.string());
378+
}
379+
return paths;
380+
}
367381
std::vector<std::unique_ptr<Wallet>> getWallets() override
368382
{
369383
std::vector<std::unique_ptr<Wallet>> wallets;

src/interfaces/node.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,12 @@ class Node
253253
//! Get unspent outputs associated with a transaction.
254254
virtual bool getUnspentOutput(const COutPoint& output, Coin& coin) = 0;
255255

256+
//! Return default wallet directory.
257+
virtual std::string getWalletDir() = 0;
258+
259+
//! Return available wallets in wallet directory.
260+
virtual std::vector<std::string> listWalletDir() = 0;
261+
256262
//! Return interfaces for accessing wallets (if any).
257263
virtual std::vector<std::unique_ptr<Wallet>> getWallets() = 0;
258264

src/wallet/rpcwallet.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,6 +3119,38 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
31193119
return obj;
31203120
}
31213121

3122+
static UniValue listwalletdir(const JSONRPCRequest& request)
3123+
{
3124+
if (request.fHelp || request.params.size() != 0) {
3125+
throw std::runtime_error(
3126+
"listwalletdir\n"
3127+
"Returns a list of wallets in the wallet directory.\n"
3128+
"{\n"
3129+
" \"wallets\" : [ (json array of objects)\n"
3130+
" {\n"
3131+
" \"name\" : \"name\" (string) The wallet name\n"
3132+
" }\n"
3133+
" ,...\n"
3134+
" ]\n"
3135+
"}\n"
3136+
"\nExamples:\n"
3137+
+ HelpExampleCli("listwalletdir", "")
3138+
+ HelpExampleRpc("listwalletdir", "")
3139+
);
3140+
}
3141+
3142+
UniValue wallets(UniValue::VARR);
3143+
for (const auto& path : ListWalletDir()) {
3144+
UniValue wallet(UniValue::VOBJ);
3145+
wallet.pushKV("name", path.string());
3146+
wallets.push_back(wallet);
3147+
}
3148+
3149+
UniValue result(UniValue::VOBJ);
3150+
result.pushKV("wallets", wallets);
3151+
return result;
3152+
}
3153+
31223154
static UniValue listwallets(const JSONRPCRequest& request)
31233155
{
31243156
if (request.fHelp || request.params.size() != 0)
@@ -4710,6 +4742,7 @@ static const CRPCCommand commands[] =
47104742
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
47114743
{ "wallet", "listtransactions", &listtransactions, {"account|dummy","count","skip","include_watchonly"} },
47124744
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
4745+
{ "wallet", "listwalletdir", &listwalletdir, {} },
47134746
{ "wallet", "listwallets", &listwallets, {} },
47144747
{ "wallet", "loadwallet", &loadwallet, {"filename"} },
47154748
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },

src/wallet/walletutil.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include <wallet/walletutil.h>
66

7+
#include <util/system.h>
8+
79
fs::path GetWalletDir()
810
{
911
fs::path path;
@@ -26,6 +28,54 @@ fs::path GetWalletDir()
2628
return path;
2729
}
2830

31+
static bool IsBerkeleyBtree(const fs::path& path)
32+
{
33+
// A Berkeley DB Btree file has at least 4K.
34+
// This check also prevents opening lock files.
35+
boost::system::error_code ec;
36+
if (fs::file_size(path, ec) < 4096) return false;
37+
38+
fs::ifstream file(path.string(), std::ios::binary);
39+
if (!file.is_open()) return false;
40+
41+
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
42+
uint32_t data = 0;
43+
file.read((char*) &data, sizeof(data)); // Read 4 bytes of file to compare against magic
44+
45+
// Berkeley DB Btree magic bytes, from:
46+
// https://github.com/file/file/blob/5824af38469ec1ca9ac3ffd251e7afe9dc11e227/magic/Magdir/database#L74-L75
47+
// - big endian systems - 00 05 31 62
48+
// - little endian systems - 62 31 05 00
49+
return data == 0x00053162 || data == 0x62310500;
50+
}
51+
52+
std::vector<fs::path> ListWalletDir()
53+
{
54+
const fs::path wallet_dir = GetWalletDir();
55+
std::vector<fs::path> paths;
56+
57+
for (auto it = fs::recursive_directory_iterator(wallet_dir); it != end(it); ++it) {
58+
if (it->status().type() == fs::directory_file && IsBerkeleyBtree(it->path() / "wallet.dat")) {
59+
// Found a directory which contains wallet.dat btree file, add it as a wallet.
60+
paths.emplace_back(fs::relative(it->path(), wallet_dir));
61+
} else if (it.level() == 0 && it->symlink_status().type() == fs::regular_file && IsBerkeleyBtree(it->path())) {
62+
if (it->path().filename() == "wallet.dat") {
63+
// Found top-level wallet.dat btree file, add top level directory ""
64+
// as a wallet.
65+
paths.emplace_back();
66+
} else {
67+
// Found top-level btree file not called wallet.dat. Current bitcoin
68+
// software will never create these files but will allow them to be
69+
// opened in a shared database environment for backwards compatibility.
70+
// Add it to the list of available wallets.
71+
paths.emplace_back(fs::relative(it->path(), wallet_dir));
72+
}
73+
}
74+
}
75+
76+
return paths;
77+
}
78+
2979
WalletLocation::WalletLocation(const std::string& name)
3080
: m_name(name)
3181
, m_path(fs::absolute(name, GetWalletDir()))

src/wallet/walletutil.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
#ifndef BITCOIN_WALLET_WALLETUTIL_H
66
#define BITCOIN_WALLET_WALLETUTIL_H
77

8-
#include <chainparamsbase.h>
9-
#include <util/system.h>
8+
#include <fs.h>
9+
10+
#include <vector>
1011

1112
//! Get the path of the wallet directory.
1213
fs::path GetWalletDir();
1314

15+
//! Get wallets in wallet directory.
16+
std::vector<fs::path> ListWalletDir();
17+
1418
//! The WalletLocation class provides wallet information.
1519
class WalletLocation final
1620
{

test/functional/wallet_multiwallet.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ def wallet_file(name):
4040
return wallet_dir(name, "wallet.dat")
4141
return wallet_dir(name)
4242

43+
assert_equal(self.nodes[0].listwalletdir(), { 'wallets': [{ 'name': '' }] })
44+
4345
# check wallet.dat is created
4446
self.stop_nodes()
4547
assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True)
@@ -70,6 +72,8 @@ def wallet_file(name):
7072
wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', '']
7173
extra_args = ['-wallet={}'.format(n) for n in wallet_names]
7274
self.start_node(0, extra_args)
75+
assert_equal(set(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), set(['', 'w3', 'w2', 'sub/w5', 'w7', 'w7', 'w1', 'w8', 'w']))
76+
7377
assert_equal(set(node.listwallets()), set(wallet_names))
7478

7579
# check that all requested wallets were created
@@ -141,6 +145,8 @@ def wallet_file(name):
141145

142146
self.restart_node(0, extra_args)
143147

148+
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']))
149+
144150
wallets = [wallet(w) for w in wallet_names]
145151
wallet_bad = wallet("bad")
146152

@@ -289,6 +295,8 @@ def wallet_file(name):
289295
assert_equal(self.nodes[0].listwallets(), ['w1'])
290296
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
291297

298+
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']))
299+
292300
# Test backing up and restoring wallets
293301
self.log.info("Test wallet backup")
294302
self.restart_node(0, ['-nowallet'])

0 commit comments

Comments
 (0)