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, nullptr); + 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 new file mode 100755 --- /dev/null +++ b/test/functional/multiwallet.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test multiwallet.""" +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + + +class MultiWalletTest(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 1 + self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']] + + def run_test(self): + 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) + + # check w1 wallet balance + walletinfo = w1.getwalletinfo() + assert_equal(walletinfo['immature_balance'], 50) + + # 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) + assert_equal(w3.getbalance(), 0) + + w1.sendtoaddress(w2.getnewaddress(), 1) + w1.sendtoaddress(w3.getnewaddress(), 2) + w1.generate(1) + 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): """ diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -49,6 +49,7 @@ 'p2p-compactblocks.py', # vv Tests less than 2m vv 'wallet.py', + 'multiwallet.py', 'wallet-accounts.py', 'wallet-dump.py', 'listtransactions.py',