Skip to content

Commit

Permalink
Merge bitcoin#14291: wallet: Add ListWalletDir utility function
Browse files Browse the repository at this point in the history
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
  • Loading branch information
5tefan committed Aug 13, 2021
1 parent 03bca03 commit 31d09b3
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/dummywallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,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.");
Expand Down
14 changes: 14 additions & 0 deletions src/interfaces/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
#include <univalue.h>

class CWallet;
fs::path GetWalletDir();
std::vector<fs::path> ListWalletDir();
std::vector<std::shared_ptr<CWallet>> GetWallets();

namespace interfaces {
Expand Down Expand Up @@ -350,6 +352,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;
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ class Node
//! 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;

Expand Down
33 changes: 33 additions & 0 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3125,6 +3125,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)
Expand Down Expand Up @@ -4717,6 +4749,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", &listtransactions, {"account|label|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"} },
Expand Down
49 changes: 49 additions & 0 deletions src/wallet/walletutil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <wallet/walletutil.h>
#include <util/system.h>

fs::path GetWalletDir()
{
Expand All @@ -26,6 +27,54 @@ 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;
}

WalletLocation::WalletLocation(const std::string& name)
: m_name(name)
, m_path(fs::absolute(name, GetWalletDir()))
Expand Down
6 changes: 6 additions & 0 deletions src/wallet/walletutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@

#include <chainparamsbase.h>
#include <util/system.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();

//! The WalletLocation class provides wallet information.
class WalletLocation final
{
Expand Down
8 changes: 8 additions & 0 deletions test/functional/wallet_multiwallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def wallet_file(name):
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)
Expand Down Expand Up @@ -66,6 +68,8 @@ def wallet_file(name):
wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', '']
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
Expand Down Expand Up @@ -137,6 +141,8 @@ def wallet_file(name):

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")

Expand Down Expand Up @@ -285,6 +291,8 @@ def wallet_file(name):
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'])
Expand Down

0 comments on commit 31d09b3

Please sign in to comment.