diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 0a246abdab..52a6991fd8 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -109,12 +109,13 @@ endif test_test_dash_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) test_test_dash_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -I$(builddir)/test/ $(TESTDEFS) $(EVENT_CFLAGS) -test_test_dash_LDADD = $(LIBBITCOIN_SERVER) $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CONSENSUS) $(LIBBITCOIN_CRYPTO) $(LIBUNIVALUE) \ - $(LIBLEVELDB) $(LIBLEVELDB_SSE42) $(LIBMEMENV) $(BACKTRACE_LIB) $(BOOST_LIBS) $(BOOST_UNIT_TEST_FRAMEWORK_LIB) $(LIBSECP256K1) $(EVENT_LIBS) $(EVENT_PTHREADS_LIBS) -test_test_dash_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_test_dash_LDADD = if ENABLE_WALLET test_test_dash_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_dash_LDADD += $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(BLS_LIBS) test_test_dash_LDFLAGS = $(LDFLAGS_WRAP_EXCEPTIONS) $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static diff --git a/src/dash-cli.cpp b/src/dash-cli.cpp index 9d873b167b..d31c954bed 100644 --- a/src/dash-cli.cpp +++ b/src/dash-cli.cpp @@ -48,6 +48,7 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-rpcpassword=", _("Password for JSON-RPC connections")); 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; } @@ -254,7 +255,20 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params) 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()); req.release(); // ownership moved to evcon in above call if (r != 0) { throw CConnectionFailed("send http request failed"); diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 2104ac7578..b53f5fa4b1 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -233,7 +233,10 @@ bool StartHTTPRPC() 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.cpp b/src/httpserver.cpp index ca6d6b9410..c91b395acc 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -673,3 +673,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) } } +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/httpserver.h b/src/httpserver.h index b7fce07320..7f57d7d790 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -148,4 +148,6 @@ private: struct event* ev; }; +std::string urlDecode(const std::string &urlEncoded); + #endif // BITCOIN_HTTPSERVER_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 00b78daf51..e321d360d3 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -10,6 +10,7 @@ #include "consensus/validation.h" #include "core_io.h" #include "init.h" +#include "httpserver.h" #include "net.h" #include "policy/feerate.h" #include "policy/fees.h" @@ -30,10 +31,21 @@ #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 index 0000000000..2b4dd2d3e7 --- /dev/null +++ b/test/functional/multiwallet.py @@ -0,0 +1,47 @@ +#!/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 index dfcc524313..b3671cbdc5 100644 --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -191,3 +191,6 @@ class AuthServiceProxy(object): else: log.debug("<-- [%.6f] %s"%(elapsed,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 index 3f87ef91f6..227b1a17af 100644 --- a/test/functional/test_framework/coverage.py +++ b/test/functional/test_framework/coverage.py @@ -56,6 +56,8 @@ class AuthServiceProxyWrapper(object): 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 index 1983d9bb26..30cfa886d7 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -64,6 +64,7 @@ BASE_SCRIPTS= [ # vv Tests less than 2m vv 'p2p-instantsend.py', 'wallet.py', + 'multiwallet.py', 'wallet-accounts.py', 'wallet-dump.py', 'listtransactions.py',