Skip to content

Commit bde4f93

Browse files
committed
Merge #10849: Multiwallet: simplest endpoint support
6b9faf7 [QA] add basic multiwallet test (Jonas Schnelli) 979d0b8 [tests] [wallet] Add wallet endpoint support to authproxy (John Newbery) 76603b1 Select wallet based on the given endpoint (Jonas Schnelli) 32c9710 Fix test_bitcoin circular dependency issue (Jonas Schnelli) 31e0720 Add wallet endpoint support to bitcoin-cli (-usewallet) (Jonas Schnelli) dd2185c Register wallet endpoint (Jonas Schnelli) Pull request description: Alternative for #10829 and #10650. It adds the most simplest form of wallet based endpoint support (`/wallet/<filename>`). No v1 and no node/wallet endpoint split. Tree-SHA512: 23de1fd2f9b48d94682928b582fb6909e16ca507c2ee19e1f989d5a4f3aa706194c4b1fe8854d1d79ba531b7092434239776cae1ae715ff536e829424f59f9be
2 parents 7b6e8bc + 6b9faf7 commit bde4f93

File tree

10 files changed

+103
-7
lines changed

10 files changed

+103
-7
lines changed

src/Makefile.test.include

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,13 @@ endif
9696

9797
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
9898
test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS)
99-
test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
100-
$(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
101-
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
99+
test_test_bitcoin_LDADD =
102100
if ENABLE_WALLET
103101
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
104102
endif
103+
test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \
104+
$(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS)
105+
test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
105106

106107
test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS)
107108
test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static

src/bitcoin-cli.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ std::string HelpMessageCli()
4646
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
4747
strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT));
4848
strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases)"));
49+
strUsage += HelpMessageOpt("-usewallet=<walletname>", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in bitcoind directory, required if bitcoind/-Qt runs with multiple wallets)"));
4950

5051
return strUsage;
5152
}
@@ -241,7 +242,20 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params)
241242
assert(output_buffer);
242243
evbuffer_add(output_buffer, strRequest.data(), strRequest.size());
243244

244-
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/");
245+
// check if we should use a special wallet endpoint
246+
std::string endpoint = "/";
247+
std::string walletName = GetArg("-usewallet", "");
248+
if (!walletName.empty()) {
249+
char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
250+
if (encodedURI) {
251+
endpoint = "/wallet/"+ std::string(encodedURI);
252+
free(encodedURI);
253+
}
254+
else {
255+
throw CConnectionFailed("uri-encode failed");
256+
}
257+
}
258+
int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, endpoint.c_str());
245259
req.release(); // ownership moved to evcon in above call
246260
if (r != 0) {
247261
throw CConnectionFailed("send http request failed");

src/httprpc.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,10 @@ bool StartHTTPRPC()
233233
return false;
234234

235235
RegisterHTTPHandler("/", true, HTTPReq_JSONRPC);
236-
236+
#ifdef ENABLE_WALLET
237+
// ifdef can be removed once we switch to better endpoint support and API versioning
238+
RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC);
239+
#endif
237240
assert(EventBase());
238241
httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase());
239242
RPCSetTimerInterface(httpRPCTimerInterface);

src/httpserver.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,3 +666,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
666666
}
667667
}
668668

669+
std::string urlDecode(const std::string &urlEncoded) {
670+
std::string res;
671+
if (!urlEncoded.empty()) {
672+
char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, NULL);
673+
if (decoded) {
674+
res = std::string(decoded);
675+
free(decoded);
676+
}
677+
}
678+
return res;
679+
}

src/httpserver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,6 @@ class HTTPEvent
148148
struct event* ev;
149149
};
150150

151+
std::string urlDecode(const std::string &urlEncoded);
152+
151153
#endif // BITCOIN_HTTPSERVER_H

src/wallet/rpcwallet.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "consensus/validation.h"
1010
#include "core_io.h"
1111
#include "init.h"
12+
#include "httpserver.h"
1213
#include "validation.h"
1314
#include "net.h"
1415
#include "policy/feerate.h"
@@ -30,10 +31,21 @@
3031

3132
#include <univalue.h>
3233

34+
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
35+
3336
CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
3437
{
35-
// TODO: Some way to access secondary wallets
36-
return vpwallets.empty() ? nullptr : vpwallets[0];
38+
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
39+
// wallet endpoint was used
40+
std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
41+
for (CWalletRef pwallet : ::vpwallets) {
42+
if (pwallet->GetName() == requestedWallet) {
43+
return pwallet;
44+
}
45+
}
46+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Requested wallet does not exist or is not loaded");
47+
}
48+
return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr;
3749
}
3850

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

test/functional/multiwallet.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 multiwallet."""
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 run_test(self):
18+
w1 = self.nodes[0] / "wallet/w1"
19+
w1.generate(1)
20+
21+
#accessing wallet RPC without using wallet endpoint fails
22+
assert_raises_jsonrpc(-32601, "Method not found", self.nodes[0].getwalletinfo)
23+
24+
#check w1 wallet balance
25+
walletinfo = w1.getwalletinfo()
26+
assert_equal(walletinfo['immature_balance'], 50)
27+
28+
#check w1 wallet balance
29+
w2 = self.nodes[0] / "wallet/w2"
30+
walletinfo = w2.getwalletinfo()
31+
assert_equal(walletinfo['immature_balance'], 0)
32+
33+
w3 = self.nodes[0] / "wallet/w3"
34+
35+
w1.generate(101)
36+
assert_equal(w1.getbalance(), 100)
37+
assert_equal(w2.getbalance(), 0)
38+
assert_equal(w3.getbalance(), 0)
39+
40+
w1.sendtoaddress(w2.getnewaddress(), 1)
41+
w1.sendtoaddress(w3.getnewaddress(), 2)
42+
w1.generate(1)
43+
assert_equal(w2.getbalance(), 1)
44+
assert_equal(w3.getbalance(), 2)
45+
46+
if __name__ == '__main__':
47+
MultiWalletTest().main()

test/functional/test_framework/authproxy.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,6 @@ def _get_response(self):
191191
else:
192192
log.debug("<-- [%.6f] %s"%(elapsed,responsedata))
193193
return response
194+
195+
def __truediv__(self, relative_uri):
196+
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)

test/functional/test_framework/coverage.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def __call__(self, *args, **kwargs):
5656
def url(self):
5757
return self.auth_service_proxy_instance.url
5858

59+
def __truediv__(self, relative_uri):
60+
return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri)
5961

6062
def get_filename(dirname, n_node):
6163
"""

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)