Multiwallet: add RPC endpoint support #10650

Open
wants to merge 8 commits into
from
@@ -96,12 +96,13 @@ endif
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) $(EVENT_PTHREADS_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
View
@@ -46,6 +46,7 @@ std::string HelpMessageCli()
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("-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;
}
@@ -235,7 +236,21 @@ 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 = "/";
@promag

promag Jul 4, 2017

Contributor

Use evhttp_uri and it's primitives?

@jonasschnelli

jonasschnelli Jul 7, 2017

Member

I guess for the endpoint a plain std::string is okay.

+ std::string walletName = GetArg("-usewallet", "");
+ if (walletName != "") {
+ // always use v1 endpoint if -usewallet has been provided
+ char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
+ if (encodedURI) {
+ endpoint = "/v1/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");
View
@@ -226,6 +226,11 @@ static bool InitRPCAuthentication()
return true;
}
+void RegisterJSONEndpoint(const std::string& endpoint, bool exactMatch)
@TheBlueMatt

TheBlueMatt Jul 10, 2017

Contributor

This seems superfluous. Why not just either auto-register based on the endpoint in the commands table or just register everything explicitly in httprpc.cpp (its only 4 things).

@ryanofsky

ryanofsky Jul 12, 2017

Contributor

In commit "Expose JSON endpoint registration"

I don't see the reasoning for this either... If you decide to keep this, maybe document the function with a comment to explain why the urls shouldn't be listed in a single place.

@jonasschnelli

jonasschnelli Jul 13, 2017

Member

a) I think we don't want an #ifdef ENABLE_WALLE in httpserver.cpp
b) Using RegisterHTTPHandler(endpoint, exactMatch, HTTPReq_JSONRPC); from the point where we can register based on RPC-tables endpoints would result in exposing RegisterHTTPHandler and HTTPReq_JSONRPC which seems unideal.

But the idea of register based on the tables endpoints makes sense, will implement but will also keep the RegisterJSONEndpoint function.

+{
+ RegisterHTTPHandler(endpoint, exactMatch, HTTPReq_JSONRPC);
+}
+
bool StartHTTPRPC()
{
LogPrint(BCLog::RPC, "Starting HTTP RPC server\n");
View
@@ -32,4 +32,6 @@ void InterruptREST();
*/
void StopREST();
+void RegisterJSONEndpoint(const std::string& endpoint, bool exactMatch);
+
#endif
View
@@ -648,7 +648,11 @@ HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod()
void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler)
{
LogPrint(BCLog::HTTP, "Registering HTTP handler for %s (exactmatch %d)\n", prefix, exactMatch);
- pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler));
+ HTTPPathHandler pathHandler(prefix, exactMatch, handler);
+ if (std::find_if(pathHandlers.begin(), pathHandlers.end(), [pathHandler](const HTTPPathHandler a){ return (a.prefix == pathHandler.prefix && a.exactMatch == pathHandler.exactMatch); }) == pathHandlers.end()) {
@morcos

morcos Jul 14, 2017

Contributor

nit: should your lambda capture list and argument be references?

@promag

promag Jul 14, 2017

Contributor

What about something along these lines:

// only add handlers if they do not exists yet
for (auto handler : pathHandlers) {
    if (handler.prefix == prefix && handler.exactMatch == exactMath) {
        return;
    }
}

pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler));
@promag

promag Jul 14, 2017

Contributor

Another option is to implement HTTPPathHandler::operator==?

@jonasschnelli

jonasschnelli Jul 14, 2017

Member

@promag: I think your solution would be a slower find algorithm (but doesn't matter). I had the == operator in an earlier version but @ryanofsky said (and I agreed) that this may be dangerous if we not check all of the instance variables (including the handler).

@morcos: Yes, Should be referenced.

+ // only add handlers if they do not exists yet
+ pathHandlers.push_back(pathHandler);
+ }
}
void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
@@ -665,3 +669,14 @@ void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch)
}
}
+std::string urlDecode(const std::string &urlEncoded) {
+ std::string res;
+ if (!urlEncoded.empty()) {
@promag

promag Jul 14, 2017

Contributor

evhttp_uridecode works with an empty string, as such this if can be removed.

+ char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, NULL);
+ if (decoded) {
+ res = std::string(decoded);
+ free(decoded);
+ }
@promag

promag Jul 14, 2017

Contributor

} else {?

@jonasschnelli

jonasschnelli Jul 14, 2017

Member

I guess there is no need for an else, it will just return an empty string if URLencode failed which is fine IMO.

+ }
+ return res;
+}
View
@@ -148,4 +148,6 @@ class HTTPEvent
struct event* ev;
};
+std::string urlDecode(const std::string &urlEncoded);
+
#endif // BITCOIN_HTTPSERVER_H
@@ -29,7 +29,7 @@ static UniValue rpcNestedTest_rpc(const JSONRPCRequest& request)
static const CRPCCommand vRPCCommands[] =
{
- { "test", "rpcNestedTest", &rpcNestedTest_rpc, true, {} },
+ { "test", "/", "rpcNestedTest", &rpcNestedTest_rpc, true, {} },
};
void RPCNestedTests::rpcNestedTests()
View
@@ -1533,35 +1533,35 @@ UniValue getchaintxstats(const JSONRPCRequest& request)
}
static const CRPCCommand commands[] =
-{ // category name actor (function) okSafe argNames
- // --------------------- ------------------------ ----------------------- ------ ----------
- { "blockchain", "getblockchaininfo", &getblockchaininfo, true, {} },
- { "blockchain", "getchaintxstats", &getchaintxstats, true, {"nblocks", "blockhash"} },
- { "blockchain", "getbestblockhash", &getbestblockhash, true, {} },
- { "blockchain", "getblockcount", &getblockcount, true, {} },
- { "blockchain", "getblock", &getblock, true, {"blockhash","verbosity|verbose"} },
- { "blockchain", "getblockhash", &getblockhash, true, {"height"} },
- { "blockchain", "getblockheader", &getblockheader, true, {"blockhash","verbose"} },
- { "blockchain", "getchaintips", &getchaintips, true, {} },
- { "blockchain", "getdifficulty", &getdifficulty, true, {} },
- { "blockchain", "getmempoolancestors", &getmempoolancestors, true, {"txid","verbose"} },
- { "blockchain", "getmempooldescendants", &getmempooldescendants, true, {"txid","verbose"} },
- { "blockchain", "getmempoolentry", &getmempoolentry, true, {"txid"} },
- { "blockchain", "getmempoolinfo", &getmempoolinfo, true, {} },
- { "blockchain", "getrawmempool", &getrawmempool, true, {"verbose"} },
- { "blockchain", "gettxout", &gettxout, true, {"txid","n","include_mempool"} },
- { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, {} },
- { "blockchain", "pruneblockchain", &pruneblockchain, true, {"height"} },
- { "blockchain", "verifychain", &verifychain, true, {"checklevel","nblocks"} },
-
- { "blockchain", "preciousblock", &preciousblock, true, {"blockhash"} },
+{ // category endpoint name actor (function) okSafeMode named args
+ // ------------------ ------------ ---------------------- ----------------------- ---------- ------------------
+ { "blockchain", "/v1/node/", "getblockchaininfo", &getblockchaininfo, true, {} },
+ { "blockchain", "/v1/node/", "getchaintxstats", &getchaintxstats, true, {"nblocks", "blockhash"} },
+ { "blockchain", "/v1/node/", "getbestblockhash", &getbestblockhash, true, {} },
+ { "blockchain", "/v1/node/", "getblockcount", &getblockcount, true, {} },
+ { "blockchain", "/v1/node/", "getblock", &getblock, true, {"blockhash","verbosity|verbose"} },
+ { "blockchain", "/v1/node/", "getblockhash", &getblockhash, true, {"height"} },
+ { "blockchain", "/v1/node/", "getblockheader", &getblockheader, true, {"blockhash","verbose"} },
+ { "blockchain", "/v1/node/", "getchaintips", &getchaintips, true, {} },
+ { "blockchain", "/v1/node/", "getdifficulty", &getdifficulty, true, {} },
+ { "blockchain", "/v1/node/", "getmempoolancestors", &getmempoolancestors, true, {"txid","verbose"} },
+ { "blockchain", "/v1/node/", "getmempooldescendants", &getmempooldescendants, true, {"txid","verbose"} },
+ { "blockchain", "/v1/node/", "getmempoolentry", &getmempoolentry, true, {"txid"} },
+ { "blockchain", "/v1/node/", "getmempoolinfo", &getmempoolinfo, true, {} },
+ { "blockchain", "/v1/node/", "getrawmempool", &getrawmempool, true, {"verbose"} },
+ { "blockchain", "/v1/node/", "gettxout", &gettxout, true, {"txid","n","include_mempool"} },
+ { "blockchain", "/v1/node/", "gettxoutsetinfo", &gettxoutsetinfo, true, {} },
+ { "blockchain", "/v1/node/", "pruneblockchain", &pruneblockchain, true, {"height"} },
+ { "blockchain", "/v1/node/", "verifychain", &verifychain, true, {"checklevel","nblocks"} },
+
+ { "blockchain", "/v1/node/", "preciousblock", &preciousblock, true, {"blockhash"} },
/* Not shown in help */
- { "hidden", "invalidateblock", &invalidateblock, true, {"blockhash"} },
- { "hidden", "reconsiderblock", &reconsiderblock, true, {"blockhash"} },
- { "hidden", "waitfornewblock", &waitfornewblock, true, {"timeout"} },
- { "hidden", "waitforblock", &waitforblock, true, {"blockhash","timeout"} },
- { "hidden", "waitforblockheight", &waitforblockheight, true, {"height","timeout"} },
+ { "hidden", "/v1/node/", "invalidateblock", &invalidateblock, true, {"blockhash"} },
+ { "hidden", "/v1/node/", "reconsiderblock", &reconsiderblock, true, {"blockhash"} },
+ { "hidden", "/v1/node/", "waitfornewblock", &waitfornewblock, true, {"timeout"} },
+ { "hidden", "/v1/node/", "waitforblock", &waitforblock, true, {"blockhash","timeout"} },
+ { "hidden", "/v1/node/", "waitforblockheight", &waitforblockheight, true, {"height","timeout"} },
};
void RegisterBlockchainRPCCommands(CRPCTable &t)
View
@@ -942,20 +942,20 @@ UniValue estimaterawfee(const JSONRPCRequest& request)
}
static const CRPCCommand commands[] =
-{ // category name actor (function) okSafeMode
- // --------------------- ------------------------ ----------------------- ----------
- { "mining", "getnetworkhashps", &getnetworkhashps, true, {"nblocks","height"} },
- { "mining", "getmininginfo", &getmininginfo, true, {} },
- { "mining", "prioritisetransaction", &prioritisetransaction, true, {"txid","dummy","fee_delta"} },
- { "mining", "getblocktemplate", &getblocktemplate, true, {"template_request"} },
- { "mining", "submitblock", &submitblock, true, {"hexdata","dummy"} },
+{ // category endpoint name actor (function) okSafeMode named args
+ // ------------------ ------------ ------------------------ ----------------------- ---------- ------------------
+ { "mining", "/v1/node/", "getnetworkhashps", &getnetworkhashps, true, {"nblocks","height"} },
+ { "mining", "/v1/node/", "getmininginfo", &getmininginfo, true, {} },
+ { "mining", "/v1/node/", "prioritisetransaction", &prioritisetransaction, true, {"txid","dummy","fee_delta"} },
+ { "mining", "/v1/node/", "getblocktemplate", &getblocktemplate, true, {"template_request"} },
+ { "mining", "/v1/node/", "submitblock", &submitblock, true, {"hexdata","dummy"} },
- { "generating", "generatetoaddress", &generatetoaddress, true, {"nblocks","address","maxtries"} },
+ { "generating", "/v1/node/", "generatetoaddress", &generatetoaddress, true, {"nblocks","address","maxtries"} },
- { "util", "estimatefee", &estimatefee, true, {"nblocks"} },
- { "util", "estimatesmartfee", &estimatesmartfee, true, {"nblocks", "conservative"} },
+ { "util", "/v1/node/", "estimatefee", &estimatefee, true, {"nblocks"} },
+ { "util", "/v1/node/", "estimatesmartfee", &estimatesmartfee, true, {"nblocks", "conservative"} },
- { "hidden", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold"} },
+ { "hidden", "/v1/node/", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold"} },
};
void RegisterMiningRPCCommands(CRPCTable &t)
View
@@ -644,20 +644,20 @@ UniValue echo(const JSONRPCRequest& request)
}
static const CRPCCommand commands[] =
-{ // category name actor (function) okSafeMode
- // --------------------- ------------------------ ----------------------- ----------
- { "control", "getinfo", &getinfo, true, {} }, /* uses wallet if enabled */
- { "control", "getmemoryinfo", &getmemoryinfo, true, {"mode"} },
- { "util", "validateaddress", &validateaddress, true, {"address"} }, /* uses wallet if enabled */
- { "util", "createmultisig", &createmultisig, true, {"nrequired","keys"} },
- { "util", "verifymessage", &verifymessage, true, {"address","signature","message"} },
- { "util", "signmessagewithprivkey", &signmessagewithprivkey, true, {"privkey","message"} },
+{ // category endpoint name actor (function) okSafeMode named args
+ // ------------------ ------------ ---------------------- ----------------------- ---------- ------------------
+ { "control", "/v1/node/", "getinfo", &getinfo, true, {} }, /* uses wallet if enabled */
+ { "control", "/v1/node/", "getmemoryinfo", &getmemoryinfo, true, {"mode"} },
+ { "util", "/v1/*", "validateaddress", &validateaddress, true, {"address"} }, /* uses wallet if enabled */
+ { "util", "/v1/*", "createmultisig", &createmultisig, true, {"nrequired","keys"} },
+ { "util", "/v1/node/", "verifymessage", &verifymessage, true, {"address","signature","message"} },
+ { "util", "/v1/node/", "signmessagewithprivkey", &signmessagewithprivkey, true, {"privkey","message"} },
/* Not shown in help */
- { "hidden", "setmocktime", &setmocktime, true, {"timestamp"}},
- { "hidden", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
- { "hidden", "echojson", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
- { "hidden", "logging", &logging, true, {"include", "exclude"}},
+ { "hidden", "/v1/node/", "setmocktime", &setmocktime, true, {"timestamp"}},
+ { "hidden", "/v1/node/", "echo", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
+ { "hidden", "/v1/node/", "echojson", &echo, true, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
+ { "hidden", "/v1/node/", "logging", &logging, true, {"include", "exclude"}},
};
void RegisterMiscRPCCommands(CRPCTable &t)
Oops, something went wrong.