Skip to content

Commit 21eeaa4

Browse files
committed
Simple, backwards compatible RPC multiwallet support.
This change allows existing RPCs to work on multiple wallets by calling those RPCs with a wallet=filename named argument. Example usage: bitcoind -regtest -wallet=w1.dat -wallet=w2.dat bitcoin-cli -regtest -named getwalletinfo wallet=w1.dat bitcoin-cli -regtest -named getwalletinfo wallet=w2.dat bitcoin-cli -regtest -named getbalance wallet=w2.dat Individual RPCs can override handling of the wallet named argument, but if they don't, the `GetWalletForJSONRPCRequest` function will automatically chose the right wallet based on the argument value. This change only allows JSON-RPC calls made with named arguments to access multiple wallets. JSON-RPC calls made with positional arguments will still continue to access the default wallet as before. Multiwallet python test based on code originally written by Jonas Schnelli <[email protected]>
1 parent 6dbcc74 commit 21eeaa4

File tree

5 files changed

+80
-2
lines changed

5 files changed

+80
-2
lines changed

src/rpc/server.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ std::string CRPCTable::help(const std::string& strCommand, const JSONRPCRequest&
213213
std::string firstLetter = category.substr(0,1);
214214
boost::to_upper(firstLetter);
215215
strRet += "== " + firstLetter + category.substr(1) + " ==\n";
216+
if (category == "wallet") {
217+
strRet += "\nWhen more than one wallet is loaded (multiple -`wallet=filename` options passed\n"
218+
"to bitcoind), wallet RPCs must be called with an extra named JSON-RPC `wallet`\n"
219+
"parameter containing the wallet filename to disambiguate which wallet file the\n"
220+
"RPC is intended for. Failure to specify will result in method disabled errors.\n\n";
221+
}
216222
}
217223
}
218224
strRet += strHelp + "\n";
@@ -472,6 +478,11 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
472478
hole += 1;
473479
}
474480
}
481+
auto wallet = argsIn.find("wallet");
482+
if (wallet != argsIn.end() && wallet->second->isStr()) {
483+
out.wallet = wallet->second->getValStr();
484+
argsIn.erase(wallet);
485+
}
475486
// If there are still arguments in the argsIn map, this is an error.
476487
if (!argsIn.empty()) {
477488
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown named parameter " + argsIn.begin()->first);

src/rpc/server.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,25 @@ class JSONRPCRequest
4242
public:
4343
UniValue id;
4444
std::string strMethod;
45+
/**
46+
* Parameters from JSON-RPC request.
47+
* This will be either an object or an array when the JSONRPCRequest object is
48+
* originally created and parsed. But it will be transformed into an
49+
* array before being passed to the RPC method implementation (using the
50+
* list of named arguments provided by the implementation).
51+
*/
4552
UniValue params;
4653
bool fHelp;
4754
std::string URI;
4855
std::string authUser;
4956

57+
/**
58+
* Optional wallet name, set for backwards compatibility if the RPC method
59+
* was called with a named "wallet" parameter and the RPC method
60+
* implementation doesn't handle it itself.
61+
*/
62+
std::string wallet;
63+
5064
JSONRPCRequest() : id(NullUniValue), params(NullUniValue), fHelp(false) {}
5165
void parse(const UniValue& valRequest);
5266
};

src/wallet/rpcwallet.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@
3232

3333
CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
3434
{
35-
// TODO: Some way to access secondary wallets
36-
return vpwallets.empty() ? nullptr : vpwallets[0];
35+
if (!request.wallet.empty()) {
36+
for (const auto& wallet : ::vpwallets) {
37+
if (request.wallet == wallet->GetName()) {
38+
return wallet;
39+
}
40+
}
41+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Requested wallet does not exist or is not loaded");
42+
}
43+
return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr;
3744
}
3845

3946
std::string HelpRequiringPassphrase(CWallet * const pwallet)

test/functional/multiwallet.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2017 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test the wallet."""
6+
from test_framework.test_framework import BitcoinTestFramework
7+
from test_framework.util import *
8+
9+
class MultiWalletTest(BitcoinTestFramework):
10+
11+
def __init__(self):
12+
super().__init__()
13+
self.setup_clean_chain = True
14+
self.num_nodes = 1
15+
self.extra_args = [['-wallet=w1', '-wallet=w2','-wallet=w3']]
16+
17+
def setup_network(self):
18+
self.nodes = self.start_nodes(1, self.options.tmpdir, self.extra_args[:1])
19+
20+
def run_test(self):
21+
self.nodes[0].generate(nblocks=1, wallet="w1")
22+
23+
#check default wallet balance
24+
assert_raises_jsonrpc(-32601, "Method not found (disabled)", self.nodes[0].getwalletinfo)
25+
26+
#check w1 wallet balance
27+
walletinfo = self.nodes[0].getwalletinfo(wallet="w1")
28+
assert_equal(walletinfo['immature_balance'], 50)
29+
30+
#check w1 wallet balance
31+
walletinfo = self.nodes[0].getwalletinfo(wallet="w2")
32+
assert_equal(walletinfo['immature_balance'], 0)
33+
34+
self.nodes[0].generate(nblocks=101, wallet="w1")
35+
assert_equal(self.nodes[0].getbalance(wallet="w1"), 100)
36+
assert_equal(self.nodes[0].getbalance(wallet="w2"), 0)
37+
assert_equal(self.nodes[0].getbalance(wallet="w3"), 0)
38+
39+
huh=self.nodes[0].getnewaddress(wallet="w2")
40+
self.nodes[0].sendtoaddress(address=huh, amount=1, wallet="w1")
41+
self.nodes[0].generate(nblocks=1, wallet="w1")
42+
assert_equal(self.nodes[0].getbalance(wallet="w2"), 1)
43+
44+
if __name__ == '__main__':
45+
MultiWalletTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
'segwit.py',
6464
# vv Tests less than 2m vv
6565
'wallet.py',
66+
'multiwallet.py',
6667
'wallet-accounts.py',
6768
'p2p-segwit.py',
6869
'wallet-dump.py',

0 commit comments

Comments
 (0)