diff --git a/src/Makefile.test.include b/src/Makefile.test.include --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -112,13 +112,13 @@ test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_bitcoin_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) -test_test_bitcoin_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) - -test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_test_bitcoin_LDADD = if ENABLE_WALLET test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) endif +test_test_bitcoin_LDADD += $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ + $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) +test_test_bitcoin_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_test_bitcoin_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -56,14 +56,20 @@ _("Username for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=", _("Password for JSON-RPC connections")); - strUsage += HelpMessageOpt( - "-rpcclienttimeout=", - strprintf(_("Timeout during HTTP requests (default: %d)"), - DEFAULT_HTTP_CLIENT_TIMEOUT)); + strUsage += + HelpMessageOpt("-rpcclienttimeout=", + strprintf(_("Timeout in seconds during HTTP requests, " + "or 0 for no timeout. (default: %d)"), + DEFAULT_HTTP_CLIENT_TIMEOUT)); strUsage += HelpMessageOpt( "-stdin", _("Read extra arguments from standard input, one per line " "until EOF/Ctrl-D (recommended for sensitive information " "such as passphrases)")); + strUsage += HelpMessageOpt( + "-usewallet=", + _("Send RPC for non-default wallet on RPC server (argument is wallet " + "filename in bitcoind directory, required if bitcoind/-Qt runs with " + "multiple wallets)")); return strUsage; } @@ -265,7 +271,21 @@ assert(output_buffer); evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); - int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, "/"); + // check if we should use a special wallet endpoint + std::string endpoint = "/"; + std::string walletName = GetArg("-usewallet", ""); + if (!walletName.empty()) { + char *encodedURI = + evhttp_uriencode(walletName.c_str(), walletName.size(), false); + if (encodedURI) { + endpoint = "/wallet/" + std::string(encodedURI); + free(encodedURI); + } else { + throw CConnectionFailed("uri-encode failed"); + } + } + int r = evhttp_make_request(evcon.get(), req.get(), EVHTTP_REQ_POST, + endpoint.c_str()); // ownership moved to evcon in above call req.release(); if (r != 0) { diff --git a/src/httprpc.cpp b/src/httprpc.cpp --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -245,7 +245,11 @@ if (!InitRPCAuthentication()) return false; RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); - +#ifdef ENABLE_WALLET + // ifdef can be removed once we switch to better endpoint support and API + // versioning + RegisterHTTPHandler("/wallet/", false, HTTPReq_JSONRPC); +#endif assert(EventBase()); httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); RPCSetTimerInterface(httpRPCTimerInterface); diff --git a/src/httpserver.h b/src/httpserver.h --- a/src/httpserver.h +++ b/src/httpserver.h @@ -147,4 +147,6 @@ struct event *ev; }; +std::string urlDecode(const std::string &urlEncoded); + #endif // BITCOIN_HTTPSERVER_H diff --git a/src/httpserver.cpp b/src/httpserver.cpp --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -657,3 +657,15 @@ pathHandlers.erase(i); } } + +std::string urlDecode(const std::string &urlEncoded) { + std::string res; + if (!urlEncoded.empty()) { + char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, NULL); + if (decoded) { + res = std::string(decoded); + free(decoded); + } + } + return res; +} diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -10,6 +10,7 @@ #include "consensus/validation.h" #include "core_io.h" #include "dstencode.h" +#include "httpserver.h" #include "init.h" #include "net.h" #include "rpc/misc.h" @@ -23,11 +24,25 @@ #include -#include +static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest &request) { - // TODO: Some way to access secondary wallets - return vpwallets.empty() ? nullptr : vpwallets[0]; + if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == + WALLET_ENDPOINT_BASE) { + // wallet endpoint was used + std::string requestedWallet = + urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); + for (CWalletRef pwallet : ::vpwallets) { + if (pwallet->GetName() == requestedWallet) { + return pwallet; + } + } + throw JSONRPCError(RPC_INVALID_PARAMETER, + "Requested wallet does not exist or is not loaded"); + } + return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) + ? ::vpwallets[0] + : nullptr; } std::string HelpRequiringPassphrase(CWallet *const pwallet) { diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py --- a/test/functional/multiwallet.py +++ b/test/functional/multiwallet.py @@ -6,6 +6,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import * + class MultiWalletTest(BitcoinTestFramework): def __init__(self): @@ -18,20 +19,21 @@ w1 = self.nodes[0] / "wallet/w1" w1.generate(1) - #accessing wallet RPC without using wallet endpoint fails - assert_raises_jsonrpc(-32601, "Method not found", self.nodes[0].getwalletinfo) + # accessing wallet RPC without using wallet endpoint fails + assert_raises_jsonrpc(-32601, "Method not found", + self.nodes[0].getwalletinfo) - #check w1 wallet balance + # check w1 wallet balance walletinfo = w1.getwalletinfo() assert_equal(walletinfo['immature_balance'], 50) - #check w1 wallet balance + # check w1 wallet balance w2 = self.nodes[0] / "wallet/w2" walletinfo = w2.getwalletinfo() assert_equal(walletinfo['immature_balance'], 0) w3 = self.nodes[0] / "wallet/w3" - + w1.generate(101) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0) @@ -43,5 +45,6 @@ assert_equal(w2.getbalance(), 1) assert_equal(w3.getbalance(), 2) + if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -195,3 +195,6 @@ else: log.debug("<-- " + responsedata) return response + + def __truediv__(self, relative_uri): + return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn) diff --git a/test/functional/test_framework/coverage.py b/test/functional/test_framework/coverage.py --- a/test/functional/test_framework/coverage.py +++ b/test/functional/test_framework/coverage.py @@ -60,6 +60,9 @@ def url(self): return self.auth_service_proxy_instance.url + def __truediv__(self, relative_uri): + return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri) + def get_filename(dirname, n_node): """