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
This commit is contained in:
Wladimir J. van der Laan 2017-07-18 17:15:46 +02:00
commit bde4f937ae
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
10 changed files with 103 additions and 7 deletions

View file

@ -96,12 +96,13 @@ endif
test_test_bitcoin_SOURCES = $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES) 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_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) \ test_test_bitcoin_LDADD =
$(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)
if ENABLE_WALLET if ENABLE_WALLET
test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET) test_test_bitcoin_LDADD += $(LIBBITCOIN_WALLET)
endif 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_LDADD += $(LIBBITCOIN_CONSENSUS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS)
test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static test_test_bitcoin_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) -static

View file

@ -46,6 +46,7 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", strprintf(_("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)"), DEFAULT_HTTP_CLIENT_TIMEOUT)); strUsage += HelpMessageOpt("-rpcclienttimeout=<n>", 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("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases)"));
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)"));
return strUsage; return strUsage;
} }
@ -241,7 +242,20 @@ UniValue CallRPC(const std::string& strMethod, const UniValue& params)
assert(output_buffer); assert(output_buffer);
evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); 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 req.release(); // ownership moved to evcon in above call
if (r != 0) { if (r != 0) {
throw CConnectionFailed("send http request failed"); throw CConnectionFailed("send http request failed");

View file

@ -233,7 +233,10 @@ bool StartHTTPRPC()
return false; return false;
RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); 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()); assert(EventBase());
httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase());
RPCSetTimerInterface(httpRPCTimerInterface); RPCSetTimerInterface(httpRPCTimerInterface);

View file

@ -666,3 +666,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;
}

View file

@ -148,4 +148,6 @@ private:
struct event* ev; struct event* ev;
}; };
std::string urlDecode(const std::string &urlEncoded);
#endif // BITCOIN_HTTPSERVER_H #endif // BITCOIN_HTTPSERVER_H

View file

@ -9,6 +9,7 @@
#include "consensus/validation.h" #include "consensus/validation.h"
#include "core_io.h" #include "core_io.h"
#include "init.h" #include "init.h"
#include "httpserver.h"
#include "validation.h" #include "validation.h"
#include "net.h" #include "net.h"
#include "policy/feerate.h" #include "policy/feerate.h"
@ -30,10 +31,21 @@
#include <univalue.h> #include <univalue.h>
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
{ {
// TODO: Some way to access secondary wallets if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
return vpwallets.empty() ? nullptr : vpwallets[0]; // 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) std::string HelpRequiringPassphrase(CWallet * const pwallet)

47
test/functional/multiwallet.py Executable file
View file

@ -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()

View file

@ -191,3 +191,6 @@ class AuthServiceProxy(object):
else: else:
log.debug("<-- [%.6f] %s"%(elapsed,responsedata)) log.debug("<-- [%.6f] %s"%(elapsed,responsedata))
return response return response
def __truediv__(self, relative_uri):
return AuthServiceProxy("{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn)

View file

@ -56,6 +56,8 @@ class AuthServiceProxyWrapper(object):
def url(self): def url(self):
return self.auth_service_proxy_instance.url 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): def get_filename(dirname, n_node):
""" """

View file

@ -63,6 +63,7 @@ BASE_SCRIPTS= [
'segwit.py', 'segwit.py',
# vv Tests less than 2m vv # vv Tests less than 2m vv
'wallet.py', 'wallet.py',
'multiwallet.py',
'wallet-accounts.py', 'wallet-accounts.py',
'p2p-segwit.py', 'p2p-segwit.py',
'wallet-dump.py', 'wallet-dump.py',